Skip to content

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/BUser.cs 1 909 The employee/user class — documented in Core Classes
Helpers/ExportHelper.cs 1 111 Excel generation
Helpers/Enums.cs 972 59 enums
Helpers/CommonConstants.cs 826 58 static constant classes
Helpers/NextUserHelper.cs 668 Approval routing — who signs next
Helpers/Utils.cs 433 Dates, enums, formatting
Helpers/EmailManager.cs 94 SMTP send
Helpers/BNotification.cs 86 In-app notifications
Helpers/ImageResize.cs 88 Image scaling
Helpers/Security.cs 39 MD5 hashing — see the warning below
Helpers/CustomImageConvertor.cs 31 Image conversion
Helpers/SiteSettings.cs 30 Compiled-in site constants
Helpers/HangFireAuthorizationFilter.cs 13 Dashboard guard for a dashboard that is never mapped
Helpers/Leave/LeaveSetting.cs · LeaveTypeCodes.cs 61 · 60 Leave configuration
Helpers/Evaluation/EvaluationStatus.cs 30 Evaluation enum
Configs/S3ServiceHandler.cs — Object storage behind IS3ServiceHandler
NKO.Main/Helpers/OperationConstants.cs 122 Warehouse operation constants
NKO.Helpers/Validation/NotNullOrWhiteSpaceValidatorAttribute.cs 29 The entire NKO.Helpers 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
<Doc>UserTypes Which role is acting PRUserTypes, POUserTypes, LeaveUserTypes, PayrollUserTypes, RFQUserTypes, EvaluationUserTypes, …
<Doc>OpTypes Which transition PROpTypes, POOpTypes, LeaveOpTypes, TrainingOpTypes, AdvanceOpTypes, GRNOpTypes, …

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 StartOfDay, EndOfDay, StartOfWeek, GetWeek, MonthsBetween, CountMonths, ReturnDiffereceBetweenTwoDatesInMonths, ToRelativeDate
Enum display GetEnumDescription, GetSelectList, GetDisplayName, GetAttributeDescription, GetDescription
Aggregation SumArray, SumArrayOfCol
Misc GetLanguages, GetIdTypesList, GetUniqueKey, Compare, BitmapToByteArray

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

catch (Exception ex) { }
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
UploadFileToS3(stream, id, fileName, functionality) Store a document
DownloadFile(key) Fetch content
DownloadFileToByte(key) Fetch as byte[]
GetSignedLink(key) Pre-signed URL
DeleteFile(key) 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.