Source Code — Helpers¶
The shared utility layer. Most of it lives in NKO.Lib/NKO.Shared/Helpers/ and is injected
everywhere; a few pieces sit in NKO.Main/NKO.Main/Helpers/. Between them these files hold the
approval routing, the constants, the exports and the security primitives.
The files¶
| File | Lines | What it is |
|---|---|---|
Helpers/ |
1 909 | The employee/user class — documented in Core Classes |
Helpers/ |
1 111 | Excel generation |
Helpers/ |
972 | 59 enums |
Helpers/ |
826 | 58 static constant classes |
Helpers/ |
668 | Approval routing — who signs next |
Helpers/ |
433 | Dates, enums, formatting |
Helpers/ |
94 | SMTP send |
Helpers/ |
86 | In-app notifications |
Helpers/ |
88 | Image scaling |
Helpers/ |
39 | MD5 hashing — see the warning below |
Helpers/ |
31 | Image conversion |
Helpers/ |
30 | Compiled-in site constants |
Helpers/ |
13 | Dashboard guard for a dashboard that is never mapped |
Helpers/ · Leave |
61 · 60 | Leave configuration |
Helpers/ |
30 | Evaluation enum |
Configs/ |
— | Object storage behind IS3 |
NKO. |
122 | Warehouse operation constants |
NKO. |
29 | The entire NKO. project |
NextUserHelper — approval routing¶
668 lines, transient. The single place that answers "who signs this next?" for every document
type. Each method resolves the next approver from the org hierarchy (ManagerId / TechManagerId)
and the named role-holders configured in Administration → General Settings.
18 methods, one per workflow:
GetPrNextUser · GetPurchaseOrderNextUser · GetGRNNextUser · GetPaymentOrderNextUser ·
GetSRRNextUser · GetStockNextUser · GetAssetRegisterNextUser · GetAssetGRNNextUser ·
GetAssetMaintenanceNextUser · GetDestructionNextUser · GetPayrollNextUser ·
GetTimesheetNextUser · GetLeaveNextUser · GetOvertimeNextUser · GetAdvanceNextUser ·
GetTrainingNextUser · GetSrfNextUser · GetFocalPoint
Every B* class calls its matching method at the end of each Set…Accepted transition, then writes
the result to NextUserId. Change routing for a document and this is the file — not the B* class.
The nightly UpdateNextUserJob re-runs the same resolution across open documents so requests do not
stall on an approver who has left. See Background Jobs.
CommonConstants.cs — the workflow vocabulary¶
826 lines holding 58 public static class blocks of const int values. Two families dominate:
| Family | Pattern | Example members |
|---|---|---|
< |
Which role is acting | PRUser, POUser, Leave, Payroll, RFQUser, Evaluation, … |
< |
Which transition | PROp, POOp, Leave, Training, Advance, GRNOp, … |
These two are what every ContextActions endpoint dispatches on — operationType × userType. The
rest are lookups and scoping:
Areas (workflow area ids) · SharedPermissions (data-scoping areas) · DataPermissionType
(All / Location / Custom / Default) · PermissionTypes · DelegationAreas ·
CommonSettings · CommonDepartmentTypes · ProjectClasses · ProjectPermissionTypes ·
ArchiveTypes · AttachFileSubTypes · FileExtensions · FileType · RedirectTypes ·
CommonLanguages · CommonStatuses · ServiceStatuses · AttendancesStatuses ·
CashBoxStandards · Loggerstatuses · SRRTypes · EvaluatorType · UserTypes
Warehouse operations have their own file — NKO.Main/Helpers/OperationConstants.cs (122 lines).
Looking for a stock or asset op type in CommonConstants will not find it.
Enums.cs adds 59 enums in the same folder. Status enums, confusingly, are not here — seven
of them live in NKO.Shared/ViewModels/Training/TrainingStatus.cs (R7-15).
Utils — general utilities¶
433 lines, transient. Mostly date maths and enum display:
| Group | Methods |
|---|---|
| Date ranges | Start, End, Start, Get, Months, Count, Return, To |
| Enum display | Get, Get, Get, Get, Get |
| Aggregation | Sum, Sum |
| Misc | Get, Get, Get, Compare, Bitmap |
GetUniqueKey uses RNGCryptoServiceProvider — cryptographically sound, unlike Security below.
GetMonthDifference is not here
The same date-difference helper is copy-pasted public static onto seven controllers and
BLeave, rather than living in Utils beside MonthsBetween and CountMonths. Being static,
none of the copies is routable — they are duplication, not endpoints (R3-3).
ExportHelper — Excel¶
1 111 lines but only two public methods: ExportPRTemplate and ExportAllocationCosts.
Everything else is private layout code. Built on EPPlus 6.1.3 (see the licence question,
R1-8). Other exports are built inline in their controllers rather than here, which is why a
1 111-line helper covers only two reports.
It is also the class that oddly : Controller — see Coding Conventions.
EmailManager — SMTP¶
94 lines, transient. Two SendEmail overloads over System.Net.Mail.SmtpClient:
SmtpClient smtp = new SmtpClient();
smtp.Host = "smtp.office365.com"; // hard-coded
smtp.Port = 587;
smtp.EnableSsl = true;
The host is compiled in; the credentials come from SiteSettings — which is also compiled in
(R1-9). MailKit and MimeKit are imported at the top of the file and never used (R1-4).
Both send paths swallow every exception — R1-6
An empty catch body: no log, no retry, no signal to the caller. Approval notifications, security alerts and birthday mail can fail indefinitely with no trace.BNotification — in-app notifications¶
86 lines. Create, Notify, Remove. Writes NotificationUser rows that the top bar reads, and
pairs with RealTimeNotifier to push the count over SignalR without a refresh.
Remove deletes the wrong entity — R7-6
BNotification.Remove(string id) queries db.Note and calls db.Note.Remove(note) — it
deletes a Note, not a notification. A copy/paste bug. The method appears unused.
S3ServiceHandler — object storage¶
Implements IS3ServiceHandler, the one interface used consistently across the codebase.
| Method | Purpose |
|---|---|
Upload |
Store a document |
Download |
Fetch content |
Download |
Fetch as byte[] |
Get |
Pre-signed URL |
Delete |
Remove |
GetSignedLink exists but downloads are proxied anyway
The handler can issue pre-signed URLs, yet FilesController and PublicFilesController stream
bytes through the application instead. Pre-signed, short-lived links are the shape that would
resolve R3-1 (unauthenticated download by storage key), and the primitive is already here.
SiteSettings — compiled-in configuration¶
30 lines of public static string literals: SiteName, SiteNameAr, LowerSiteName,
CapitalSiteName, Workplace, EmailTitle, SystemURL, SiteURL, SiteVacancyURL,
JobCenterURL, JobCenterRFQURL, HREmail, TemplatePath, AutoReplyEmail, SystemEmail — and
AutoReplyEmailPass and SystemEmailPass.
Two live mailbox passwords are compiled in and committed — R1-9
They are in git history from the initial commit, so editing the file does not remove them. Changing any URL or mailbox also requires a rebuild, because none of it is configuration.
Security — MD5, and a broken hex encoder¶
39 lines, namespace NKO.Helpers. Two static methods: GetMD5HashData(data) and
ValidateMD5HashData(input, storedHash).
What it protects: the 4-digit E-signature PIN (BUser.ChangePin, BUser.VerifyPin) and the
RFQ supplier-portal VerifyToken (BRFQ.cs:2551, 2586). VerifyPin gates approval actions in
TimesheetController, AdvanceController, OvertimeController and AccountController.
Two independent problems:
1. MD5 over a 4-digit PIN. The entire keyspace is 10 000 values; every digest can be precomputed instantly. There is no salt and no iteration. The PIN authorises workflow approvals, including leave and advance transitions.
2. The comment says "hexadecimal string" — the code does not produce one.
for (int i = 0; i < hashData.Length; i++)
{
returnValue.Append(hashData[i].ToString()); // decimal, no padding
}
// return hexadecimal string
return returnValue.ToString();
Each byte is appended as an unpadded decimal number, so a 16-byte digest becomes a 16–48 digit
decimal run rather than 32 hex characters. Because the parts are variable-length with no separator,
the encoding is ambiguous: the byte sequences [1, 11] and [11, 1] both render as "111".
Distinct digests can therefore collide after encoding, independently of MD5's own weakness.
ValidateMD5HashData compares with string.Compare(...) == 0 — culture-sensitive ordering used for
an equality test, and not constant-time.
Tracked as R8-6. Note this is separate from password hashing, which correctly uses BCrypt
at work factor 12 (BCryptPasswordHasher) — though that class also carries its own copy of
GetMD5HashData.
Related¶
- Core Classes · Background Jobs · Coding Conventions
- Architecture — External Services — S3, SMTP and fixer.io.
- Recommendations — R1-4, R1-6, R1-8, R1-9, R3-1, R7-6, R8-6.