Source Code — Core Classes¶
The classes almost every request passes through, and the pattern the 44 business classes follow. If you read only one page of this section before changing code, read this one.
The request-scoped trio¶
Three small classes carry the caller's identity from the authorization filter into every controller, view and business class for the rest of the request.
| Class | File | Lifetime | Role |
|---|---|---|---|
Current |
NKO. (12 ln) |
Scoped | A plain bag: Employee (App), User (Application), Permissions (List<). User is never assigned anywhere — it is always null. |
Permissions |
NKO. (44 ln) |
Transient | Reads the caller's permission keys from the database. |
Permission |
NKO. (20 ln) |
Scoped | Has — used in views to show or hide a control. |
CurrentRequestData is filled only by ClaimRequirementFilter, and only on the success path —
and only two of its three properties: Employee and Permissions. Its User property is never set
by any code in the solution. That has a consequence worth internalising:
CurrentRequestData.Employee is null on an ungated action
An action with no [ClaimRequirement] never runs the filter, so nothing populates
CurrentRequestData. Any code reading _currentRequestData.Employee there gets null. This is
part of why the ungated actions (R3-2, R3-7) matter beyond authorization: several of
them would throw rather than leak.
PermissionsHandler.GetUserPermissions(username)¶
Returns KeyValuePair<AppUser, List<string>> — the employee record and their keys in one call.
- Load
ApplicationUserbyUserName. - Load the
AppUserbyIdentityUserId, eager-loadingAppUserGroupMember,AppUserExtend,CommonBranch,CommonSubBranch. - If
!user.IsEnableLogin→ return the employee with an empty key list. A disabled login is authenticated but can reach nothing that needs a key. - Otherwise load every
AppUserGroupMemberwhereIsEnable, includeAppUserGroup → AppUserGroupPermission → AppPermission, and flatten everyPermissionNameinto the list.
This runs on every request to a gated action — there is no cache. Both lookups use FirstAsync,
which throws when there is no match, so an ApplicationUser with no matching AppUser row
produces an unhandled InvalidOperationException (and, per R1-1, the developer exception page).
The two permission checks disagree on case — R8-3
ClaimRequirementFilter compares case-sensitively:
PermissionChecker, used by the views, compares case-insensitively:
A key stored as humanresourceindex against an attribute reading HumanResourceIndex would
therefore render the menu item and then 403 the click. Keys are free-form strings typed by
an administrator, so the mismatch is reachable through the UI, not just theoretical.
ClaimRequirementFilter¶
NKO.Shared/Helpers/Sec/ClaimRequirementFilter.cs (60 ln), namespace NKO.Shared.Web.Helpers.
Two classes in one file:
ClaimRequirementAttribute : TypeFilterAttribute— what you write as[ClaimRequirement("Key")]. It packs the key into aClaimwith an empty type and passes it as a filter argument.ClaimRequirementFilter : IAsyncAuthorizationFilter— the logic.
flowchart TD
A["OnAuthorizationAsync"] --> B{"User.Identity.IsAuthenticated?"}
B -- no --> C["RedirectResult ~/Account/Login?ReturnUrl=path"]
B -- yes --> D["PermissionsHandler.GetUserPermissions — hits the DB"]
D --> E{"key present? (case-sensitive)"}
E -- no --> F["ForbidResult + StatusCode 403"]
E -- yes --> G["fill CurrentRequestData.Employee + .Permissions"]
F --> H["trailing check: status == 403 → redirect ~/Error/UnAuthorised"]
G --> I["action runs"]
Note the trailing if (context.HttpContext.Response.StatusCode == 403) sits outside the
authenticated branch, so it also catches a 403 set earlier in the pipeline.
BUser — the class everything depends on¶
NKO.Shared/Helpers/BUser.cs, 1 909 lines, namespace NKO.BAL. The largest shared class and
the most widely injected. Despite the name and namespace it is not in NKO.Shared/BAL/ — it is
in Helpers/.
94 public methods, in six groups:
| Group | Representative methods |
|---|---|
| Lookup | Get, Get, Get, Get, Get, Get, Get, Search |
| Org hierarchy | Get, Get, Get, Get, Is, Get, Get, Get |
| Named role-holders | Get, Get, Get, Get, Get, Get, Get, Get, Get |
| Contract & salary | Get, Get, Getcurrent, Get, Get, Get, Get |
| Calendar | Get (3 overloads), Add, Is, Start |
| Security & audit | Get (data scoping), Verify, Change, Reset, Set, Get, Export, Disable |
GetPermission(SharedPermissions.<Area>, userId) is the one to know: it returns the row-level
scope (All / Location / Custom / Default) that callers apply by hand inside their own
queries. See Security & Permissions.
StaffPermissionchecker — the org subtree¶
NKO.Shared/BAL/Permissionchecker.cs (163 ln). Registered Scoped. Walks ManagerId /
TechManagerId so a line manager sees their own people and no one else's.
| Method | Returns |
|---|---|
Get |
Every employee below this user, recursively |
Get |
Direct reports only |
Get |
The subtree excluding the caller |
Get |
The subtree as List< for display |
Get |
The chain upward — used to resolve approvers |
Is |
Guard used before showing another employee's record |
DataPermissionType.Default in the scoping switch resolves through GetStaff, which is what makes
"my team only" work across leave, timesheet, overtime and advance screens.
BSign — the e-signature gate¶
NKO.Shared/BAL/BSign.cs (336 ln). Every workflow transition calls BSign.HasSign(userId) before
doing anything; without an approved AppUserSign the transition is refused with "Please upload
your E-signature." Uploading a signature is itself an approval workflow, so signing authority is
administered rather than self-granted. GetSign(id) returns the stored base64 image, which
BaseController.Thumbnail renders.
Identity classes¶
| Class | File | Notes |
|---|---|---|
Application |
Helpers/ (24 ln) |
Subclasses User; its constructor assigns Password, overriding the Identity default. |
BCrypt |
Helpers/ (54 ln) |
IPassword over BCrypt., salt generated at work factor 12. |
Claims |
Helpers/ (34 ln) |
A User that would add every permission key to the sign-in cookie as a Role claim. Dead code — it is not registered in DI and nothing references it, so Identity uses its default factory. |
BCryptPasswordHasher is not resolved from the container
Startup.cs registers it twice (services.AddTransient<BCryptPasswordHasher>(), the same
line repeated), but nothing injects it — ApplicationUserManager news it up directly. Both
registrations are dead. Work factor 12 also means any hand-crafted bootstrap row must use the
same factor; see R5-1.
The B* business-class pattern¶
44 classes, all built the same way. There is no interface for 40 of them and no repository
layer — each takes DbContext plus whatever collaborators it needs and queries with LINQ.
public class BTraining
{
private readonly NKO_DBEntities1 db;
private readonly BUser _user;
private readonly NextUserHelper _nextUserHelper;
private readonly BNotification _notification;
private readonly EmailManager _emailManager;
// … ctor assigns them
}
| Convention | Shape |
|---|---|
| Class name | B< (BPayroll, BLeave) or < (Allocation) |
| Create | Create(< — assigns a new Guid, Status, Next |
| Visibility | Get applies data scoping; Get does not |
| Transition | Set< — Set, Set, Set |
| Transition guard | Next and an exact Status and prior sign flags |
| Transition body | set …Sign / …Sign / …Note / …Id, advance Status, resolve Next via Next, notify + e-mail |
The five largest classes¶
| Class | Lines | Project folder |
|---|---|---|
BAsset |
4 205 | Ware |
BStock |
4 024 | Ware |
BRFQ |
3 843 | BAL/ |
BLeave |
2 703 | BAL/ |
BJobs |
2 689 | BAL/ |
Together these five are 17 464 lines — about 40 % of all business logic. They hold the most intricate rules in the system (CBA supplier scoring, stock availability and release, leave accrual, interview scoring) and are the ones to approach most carefully.
The five interface-backed services¶
Only these are registered against a contract; the other 40 business classes are injected as concrete types.
| Interface | Implementation |
|---|---|
IS3 |
S3 |
IProc |
Proc |
IAllocation |
Allocation |
IBudget |
Budget |
IAttendances |
Attenadances (note the spelling) |
See Services & DI for the full registration list and its defects.
Related¶
- Services & DI · Helpers · Coding Conventions
- Architecture — Security & Permissions · Request Pipeline
- User Flows — Permissions — the same model as a process.