ngdpbase uses a Policy-Based Access Control (PBAC) system inspired by JSPWiki's security framework. This architecture provides fine-grained control over who can access what resources and perform which actions.
src/managers/PolicyEvaluator.js)The PolicyEvaluator is the central component that evaluates access requests against defined policies.
Key Method:
async evaluateAccess(context)
Parameters:
context.pageName - The page being accessedcontext.action - The action being performed (e.g., page:read, admin:users)context.userContext - User information including username, roles, and authentication statusReturns:
{
hasDecision: boolean, // Whether a policy matched
allowed: boolean, // Whether access is granted
reason: string, // Human-readable reason
policyName: string // ID of the matching policy
}
Policies are defined in config/app-default-config.json under ngdpbase.access.policies:
{
"id": "admin-full-access",
"name": "Administrator Full Access",
"description": "Full system access for administrators",
"priority": 100,
"effect": "allow",
"subjects": [
{"type": "role", "value": "admin"}
],
"resources": [
{"type": "page", "pattern": "*"}
],
"actions": [
"page:read",
"page:edit",
"admin:users",
...
]
}
Policy Fields:
id - Unique identifiername - Human-readable namedescription - Purpose of the policypriority - Higher priority policies are evaluated first (100 = highest)effect - "allow" or "deny"subjects - Who the policy applies to (roles, users)resources - What resources are covered (pages, patterns)actions - Which actions are permitted/deniedRoles are assigned to users and define their capabilities through policies.
Built-in Roles:
admin - Full system access (priority 100)editor - Can create, edit, and delete pages (priority 80)contributor - Can create and edit pages (priority 70)reader - Can read and search content (priority 60)anonymous - Unauthenticated users (priority 50)Special Roles:
Authenticated - Automatically added to all logged-in usersAll - Automatically added to everyone (including anonymous)Actions are namespaced strings representing operations:
Page Permissions:
page:read - View pagespage:edit - Modify existing pagespage:create - Create new pagespage:delete - Delete pagespage:rename - Rename pagesAttachment Permissions:
attachment:upload - Upload file attachmentsattachment:delete - Delete attachmentsExport Permissions:
export:pages - Export pages to various formatsSearch Permissions:
search:all - Search all contentsearch:restricted - Search restricted/private contentAdmin Permissions:
admin:users - Manage usersadmin:roles - Manage roles and permissionsadmin:config - Modify system configurationadmin:system - Full system administrationThe system includes 7 default policies defined in config/app-default-config.json:
*Admin*, *System*, *Config*Policy Loading:
Access Check Flow:
text
User Request → ACLManager.checkPagePermission()
↓
Action Mapping (view → page:read)
↓
PolicyEvaluator.evaluateAccess()
↓
Check Each Policy (by priority)
↓
First Match Wins → Return Decision
Matching Logic:
Action Name Mapping:
Legacy action names are mapped to policy actions in ACLManager:
javascript
const actionMap = {
'view': 'page:read',
'edit': 'page:edit',
'delete': 'page:delete',
'create': 'page:create',
'rename': 'page:rename',
'upload': 'attachment:upload'
};
Used for generic permission checks (admin routes, features):
async hasPermission(username, action) {
// Builds user context with roles
const userContext = {
username: user.username,
roles: [...user.roles, 'Authenticated', 'All'],
isAuthenticated: true
};
// Evaluates using PolicyEvaluator with generic page '*'
const result = await policyEvaluator.evaluateAccess({
pageName: '*',
action: action,
userContext: userContext
});
return result.allowed;
}
Used for page-specific access control:
async checkPagePermission(pageName, action, userContext, pageContent) {
// 1. Map legacy action names to policy actions
const policyAction = actionMap[action.toLowerCase()] || action;
// 2. Evaluate global policies first
const policyResult = await policyEvaluator.evaluateAccess({
pageName,
action: policyAction,
userContext
});
if (policyResult.hasDecision) {
return policyResult.allowed;
}
// 3. Check page-level ACLs if no policy matched
// 4. Default deny if nothing matched
}
User contexts are built consistently across the system:
Anonymous Users:
{
username: 'Anonymous',
roles: ['anonymous', 'All'],
isAuthenticated: false
}
Authenticated Users:
{
username: 'jim',
roles: ['reader', 'editor', 'admin', 'Authenticated', 'All'],
isAuthenticated: true
}
Note: Authenticated and All roles are automatically added.
text
Request: GET /view/Welcome
User: Anonymous
Action: view → page:read
Policy Evaluation:
1. admin-full-access: NO (not admin role)
2. deny-anonymous-system-pages: NO (Welcome doesn't match *Admin*)
3. editor-permissions: NO (not editor role)
4. contributor-permissions: NO (not contributor role)
5. reader-permissions: NO (not reader role)
6. anonymous-read-only: NO (anonymous role but needs resource match)
7. default-view-for-all: YES (All role, page:read, * pattern)
Result: ALLOWED (policy: default-view-for-all)
<span data-jspwiki-placeholder="d3fdc9d9-7"></span>
### Example 3: Anonymous User Tries Admin Page
text
Request: GET /admin/users
User: Anonymous
Action: admin:users
Policy Evaluation:
Result: DENIED (policy: deny-anonymous-system-pages)
### Example 4: Editor Creates Page
``` text
Request: POST /create
User: editor_user (roles: editor)
Action: page:create
Policy Evaluation:
1. admin-full-access: NO (not admin role)
2. deny-anonymous-system-pages: N/A (not anonymous)
3. editor-permissions: YES (editor role, page:create in actions, * pattern)
Result: ALLOWED (policy: editor-permissions)
The system provides extensive logging for troubleshooting:
``` text
POLICY Evaluate page=Welcome action=page:read user=Anonymous roles=Anonymous|All
POLICY Check policy=admin-full-access effect=allow match=false
POLICY Check policy=default-view-for-all effect=allow match=true
ACL PolicyEvaluator decision hasDecision=true allowed=true policy=default-view-for-all
## Adding Custom Policies
To add a new policy, edit `config/app-default-config.json`:
```json
{
"id": "moderator-access",
"name": "Moderator Permissions",
"description": "Moderators can edit and delete but not create",
"priority": 75,
"effect": "allow",
"subjects": [
{"type": "role", "value": "moderator"}
],
"resources": [
{"type": "page", "pattern": "*"}
],
"actions": [
"page:read",
"page:edit",
"page:delete"
]
}
Authenticated, All) automatically. Be careful with All role policies.admin:*) from page actions (page:*) to prevent privilege escalation.src/managers/ACLManager.js - Action name mapping, policy evaluation integrationsrc/managers/UserManager.js - Policy-based hasPermission() implementationsrc/managers/PolicyEvaluator.js - Core policy evaluation logicsrc/routes/WikiRoutes.ts - Async permission checks with awaitconfig/app-default-config.json - Policy definitionsFor implementation details, source file locations, and integration guidance, see Documentation for Developers.