Roles & Permissions

RBAC system with scope override, field-level restrictions, anti-escalation, and tenant-scoped roles.

SCRIPE implements a comprehensive, highly optimized RBAC (Role-Based Access Control) system with modular category-based permissions, scope overrides, field-level restrictions (FLS), and tenant scoping. Permissions are dynamically loaded from providers, cached server-side using IMemoryCache, and validated via programmatic checkers.

Permission Hierarchy

Permission Hierarchy
System Protected Admin (Full Access)
Spawns
Tenant Super Admin
Creates & Configures
Custom Tenant Roles
Assigns
Default / Regular User Roles

Permission System

Permissions follow a strict {Resource}.{Action} naming convention. Instead of static declarations, each backend module defines its permissions by implementing IModulePermissionProvider (e.g., IdentityPermissionProvider, CompliancePermissionProvider). On startup, these providers are automatically discovered, and the DatabaseSeeder uses PermissionSeeder.SyncFromProvidersAsync to synchronize and seed permissions in the database.

Permission CategoryPermissionsFormat
Admin ManagementCreate, Read, Update, Delete, Block, Impersonate, Transferadmins.view, admins.create, admins.impersonate, admins.transfer
Role ManagementCreate, Read, Update, Delete, Assign Permissions, Clone Roleroles.view, roles.create, roles.manage_permissions, roles.clone
Tenant ManagementCreate, Read, Update, Delete, Settings, Drill-Down, Cascade Deletetenants.view, tenants.create, tenants.drill_down, tenants.cascade_delete
Compliance & PrivacyRegulations, Consent Management, DSR Create/Review/Executecompliance_regulations.view, compliance_dsr.execute, compliance_consent.manage
Audit LogsRead, Export, View Childrenaudit.view, audit.export, audit.view_children
DashboardView Stats, View Charts, View Security Feeddashboard.view, analytics.view, security.view

Scope Override (Data Access Control)

Each RolePermission can override the default scope of a permission via the ScopeOverride field. The DataScopeService resolves the effective scope using a strict priority list: 1) Drill-down Context (ContextTenantId claim), 2) RolePermission Scope Override, 3) God-Mode Check (SystemProtectedAdmin), 4) Hierarchy Flag (IncludeChildTenants), 5) Tenant Assigned, 6) Global Fallback. When multiple roles are assigned, AdminSecurityService.GetWidestScope resolves the widest scope: all_tenants > hierarchy > own_tenant > own. To prevent privilege leakage, tenant administrators are strictly capped to their own sub-hierarchy during scope resolution.

RolePermission.csC#
public class RolePermission : AuditableEntity
{
    public Guid RoleId { get; set; }
    public Guid PermissionId { get; set; }

    // Override the permission's default scope for THIS role
    public string? ScopeOverride { get; set; }

    // JSON array of field names hidden for this role
    public string? RestrictedFieldsJson { get; set; }
    // Example: ["salary", "ssn", "bankAccount"]

    // Navigation
    public virtual Role Role { get; set; } = null!;
    public virtual Permission Permission { get; set; } = null!;
}
Scope ValueData Access DescriptionUse Case / Scoping Boundary
all_tenantsSystem-wide access across all tenants in the systemSuper admin global operations
context_tenantData restricted to a specific tenant context (drill-down mode)Super admin tenant drill-down
hierarchyData from own tenant + all descendant child tenantsBranch manager overseeing sub-tenants
own_tenantData from own tenant only (default isolation boundary)Default for tenant-level administrators
ownOnly data created by the current user (CreatedBy == UserId)Individual team members seeing only their entries

Authorization Pipeline

Authorization is decoupled from static configurations. The DynamicPermissionPolicyProvider dynamically constructs ASP.NET Core authorization policies for routes containing the [PermissionRequired] attribute. To keep JWT token size under 400 bytes, user permissions are not stored in claims but are cached server-side using AdminPermissionCache (with a 10-minute sliding expiration). Caching utilizes a central CancellationTokenSource for thread-safe global eviction upon any role modification. Programmatic checks are performed in handlers and services via the IPermissionChecker interface.

Authorization & Security AttributesC#
// Route-level and pipeline authorization attributes

// 1. Dynamic RBAC verification with dynamic policy generation
[PermissionRequired("admins.view")]

// 2. Requires active administrator token
[AdminOnly]

// 3. Requires standard client/user token
[UserOnly]

// 4. Rate limiting policies applied per-user or at auth endpoints
[EnableRateLimiting("per-user")]
[EnableRateLimiting("login")]
Permission Policy & Caching Flow
Request with [PermissionRequired]
DynamicPermissionPolicyProvider (Resolves dynamic policy)
PermissionAuthorizationHandler (Evaluates requirements)
AdminPermissionCache (Checks IMemoryCache - 10m sliding)
Cache Miss
LoadAndCacheAsync (DB Fallback)
PermissionChecker (Validates tenant & scopes)
Allow / Deny access

Field-Level Restrictions

Field-Level Security (FLS) allows administrators to restrict specific fields of an entity for specific roles. Restricted fields are configured as a JSON array of string paths (e.g., ["salary", "ssn"]) and stored in the RestrictedFieldsJson column (varchar/nvarchar/VARCHAR2 up to 2000 chars) of the RolePermission table. During execution, the RestrictedFieldsAuthorizationFilter identifies the target resource and sets HttpContext.Items["RestrictedFields"]. The FieldProjectionMiddleware intercepts HTTP 2xx JSON responses, parses the body into a JsonNode tree, and recursively nullifies restricted properties matching the exact or relative path (e.g., address.street) to prevent reflection overhead.

RestrictedFieldsJson ExampleJSON
// RolePermission for role "HR_VIEWER" on permission "admins.view"
{
  "scopeOverride": "own_tenant",
  "restrictedFieldsJson": "["salary", "ssn", "bankAccount", "nationalId"]"
}

// API Response: restricted fields are recursively nullified by middleware
{
  "id": "abc-123",
  "name": "John Doe",
  "email": "john@example.com",
  "salary": null,       // Nullified
  "ssn": null,          // Nullified
  "bankAccount": null   // Nullified
}
FieldProjectionMiddleware.cs (Recursive Nullification)C#
private void FilterNode(JsonNode? node, HashSet<string> restrictedFields, string path)
{
    if (node == null) return;

    if (node is JsonObject obj)
    {
        var keysToNullify = new List<string>();
        foreach (var prop in obj)
        {
            var fieldPath = string.IsNullOrEmpty(path) ? prop.Key : path + "." + prop.Key;
            if (restrictedFields.Contains(prop.Key, StringComparer.OrdinalIgnoreCase) ||
                restrictedFields.Contains(fieldPath, StringComparer.OrdinalIgnoreCase))
            {
                keysToNullify.Add(prop.Key);
            }
            else
            {
                FilterNode(prop.Value, restrictedFields, fieldPath);
            }
        }

        foreach (var key in keysToNullify)
        {
            obj[key] = null; // Zero-reflection nullification
        }
    }
    else if (node is JsonArray arr)
    {
        foreach (var item in arr)
        {
            FilterNode(item, restrictedFields, path);
        }
    }
}
Field-Level Security (FLS) Pipeline Flow
Incoming API Request
RestrictedFieldsAuthorizationFilter (Checks cache, sets HttpContext.Items["RestrictedFields"])
Controller & Handler Execution (Fetches data & maps to DTO)
FieldProjectionMiddleware (Intercepts 2xx JSON, parses JsonNode tree)
Recursive Nullifier (Nullifies restricted properties in response)
Cleaned JSON Response Sent

Clone Role (Anti-Escalation)

To prevent privilege escalation, the CloneRoleCommandHandler filters the copied permission list against the cloner's own active permissions, silently dropping any unpossessed permissions. In the permission assignment command, attempting to explicitly add permissions that the administrator does not possess throws a Forbidden role.permissionEscalation error. Additionally, the TenantGuardianService validates all role updates and blocks any changes to locked system-critical roles (IsPermissionLocked == true).

Clone Role & Permission Assignment Anti-Escalation Flow
Cloner / Assignor Admin (Current User Permissions Pool)
Security Operation (Clone Role OR Assign Permissions)
Clone Command
Cloning Flow (Silent Intersection Filtering)
Assignment Flow (Strict Validation: Has permission?)
Yes
TenantGuardianService (Is role permission-locked?)
Locked (throws 403)
Forbidden (PermissionEscalation / LockedRole)
Permit Operation & Update Cache

Role Entity Properties

Each role has several system flags that control its behavior and protection level.

PropertyTypePurpose
NameEn / NameArstringBilingual display name
CodestringUnique identifier (e.g. ACME_SUPER_ADMIN)
TenantIdGuidScoped to a specific tenant
IsSystemboolSeeded role, cannot be modified
IsDeletableboolSystem roles are protected from deletion
IsPermissionLockedboolSuper admin roles permissions cannot be changed
IsTenantSuperAdminboolExactly one per tenant, gets all tenant permissions
IsDefaultRoleboolAutomatically assigned to new users/admins
PriorityintLower = higher priority. Controls role hierarchy.

Role API Endpoints

MethodEndpointDescriptionAuth
GET/api/v1/rolesList all roles (data-scope aware)🔒 Required
GET/api/v1/roles/{id}Get role with permissions🔒 Required
GET/api/v1/roles/{id}/admin-countCount admins with this role (delete warning)🔒 Required
GET/api/v1/roles/{id}/permissionsGet role's assigned permissions with scopes🔒 Required
POST/api/v1/rolesCreate new role (with TenantId)🔒 Required
PUT/api/v1/roles/{id}Update role details🔒 Required
DELETE/api/v1/roles/{id}Delete role (if IsDeletable)🔒 Required
PUT/api/v1/roles/{id}/permissionsAssign permissions with scope overrides🔒 Required
DELETE/api/v1/roles/{id}/permissions/{permId}Remove single permission from role🔒 Required
POST/api/v1/roles/{id}/cloneClone role (anti-escalation)🔒 Required

My Tenant Endpoints

MethodEndpointDescriptionAuth
GET/api/v1/roles/my-tenantRoles in current user's tenant (from JWT)🔒 Required
GET/api/v1/roles/my-tenant/available-permissionsPermissions available for role assignment (filtered by tenant pool)🔒 Required
POST/api/v1/roles/my-tenantCreate role for current tenant🔒 Required

Permission Endpoints

MethodEndpointDescriptionAuth
GET/api/v1/permissionsList all permissions grouped by category🔒 Required
GET/api/v1/permissions/categoriesGet permission category list🔒 Required
GET/api/v1/permissions/flatFlat list of all permissions🔒 Required