Policies, Roles, and Permissions Architecture
Overview
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.
Terms
- Permissions (same as Actions) are the actions allowed to be performed on a Resource (i.g page) We caterogize permssions as:
- Page Permissions
- Attachment Permissions
- Search Permissions
- Admin Permissions
- Roles are a specific defined collection of Permissions
- Policy is how roles and permssions come together.
- Defined in "ngdpbase.access.policies"
Core Components
1. PolicyEvaluator (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 status
Returns:
{
hasDecision: boolean, // Whether a policy matched
allowed: boolean, // Whether access is granted
reason: string, // Human-readable reason
policyName: string // ID of the matching policy
}
2. Policy Structure
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/denied
3. Roles
Roles 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)
4. Actions (Permissions)
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 pages
Attachment Permissions:
attachment:upload- Upload file attachmentsattachment:delete- Delete attachments
Export Permissions:
export:pages- Export pages to various formats
Search Permissions:
search:all- Search all contentsearch:restricted- Search restricted/private content
Admin Permissions:
admin:users- Manage usersadmin:roles- Manage roles and permissionsadmin:config- Modify system configurationadmin:system- Full system administration
Default Policies
The system includes 7 default policies defined in config/app-default-config.json:
1. admin-full-access (Priority 100)
- Subject: admin role
- Actions: All 14 permissions
- Effect: Allow
- Administrators have unrestricted access to all features.
2. deny-anonymous-system-pages (Priority 90)
- Subject: anonymous role
- Resources: Pages matching
*Admin*,*System*,*Config* - Actions: All
- Effect: Deny
- Prevents anonymous users from accessing system/admin pages.
3. editor-permissions (Priority 80)
- Subject: editor role
- Actions: 9 page permissions (read, edit, create, delete, rename, upload, export, search)
- Effect: Allow
- Editors can manage content but not administer the system.
4. contributor-permissions (Priority 70)
- Subject: contributor role
- Actions: 6 permissions (read, edit, create, upload, search all)
- Effect: Allow
- Contributors can create and edit but not delete content.
5. reader-permissions (Priority 60)
- Subject: reader role
- Actions: 3 permissions (read, search all, search restricted)
- Effect: Allow
- Readers have read-only access to all content.
6. anonymous-read-only (Priority 50)
- Subject: anonymous role
- Actions: page:read only
- Effect: Allow
- Anonymous users can view non-system pages.
7. default-view-for-all (Priority 1)
- Subject: All role
- Actions: page:read
- Effect: Allow
- Fallback policy - everyone can read pages unless denied by higher-priority policy.
How Policies Are Evaluated
Policy Loading:
- Policies are loaded from configuration at startup
- ACLManager loads policies into PolicyEvaluator
Access Check Flow:
text User Request → ACLManager.checkPagePermission() ↓ Action Mapping (view → page:read) ↓ PolicyEvaluator.evaluateAccess() ↓ Check Each Policy (by priority) ↓ First Match Wins → Return DecisionMatching Logic:
- Policies are checked in priority order (highest first)
- For each policy, three checks are performed:
- Subject Match: Does user have required role?
- Resource Match: Does the page match the pattern?
- Action Match: Is the requested action in the policy?
- First policy where all three match determines the outcome
- If no policy matches, access is denied
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' };
Integration Points
UserManager.hasPermission()
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;
}
ACLManager.checkPagePermission()
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 Context Construction
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.
Example Access Checks
Example 1: Anonymous User Views Welcome Page
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="fc86c577-7"></span>
### Example 3: Anonymous User Tries Admin Page
text
Request: GET /admin/users
User: Anonymous
Action: admin:users
Policy Evaluation:
- admin-full-access: NO (not admin role)
- deny-anonymous-system-pages: YES (anonymous, page matches Admin, deny)
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)
Logging and Debugging
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"
]
}
Security Considerations
- Priority Matters: Higher priority policies override lower ones. Place deny policies before allow policies.
- Default Deny: If no policy matches, access is denied by default.
- Role Accumulation: Users accumulate roles (
Authenticated,All) automatically. Be careful withAllrole policies. - Resource Patterns: Use specific patterns for sensitive resources to avoid unintended access.
- Action Granularity: Separate admin actions (
admin:*) from page actions (page:*) to prevent privilege escalation.
Files Typically Affected
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 definitions
Developer Information
For implementation details, source file locations, and integration guidance, see Documentation for Developers.
No comments yet.