55# - Scoped role assignments (user roles tied to specific contexts)
66# - Action grouping (manage → read/write/delete to reduce duplication)
77# - System-wide roles (global scope "*" applies everywhere)
8- # - Granular permissions (exact resources) and broad permissions (org-level )
8+ # - Scope-based permissions (authorization based on context, not specific objects )
99# - Negative rules (deny overrides allow for exceptions)
10- # - Namespace support (course:*, lib:*, org:*, etc.)
11- # - Extensibility (new resource types just need new namespaces)
10+ # - Namespace support (course-v1:*, lib:*, org:*, etc.)
11+ # - Extensibility (new resource types only require new scope namespaces)
12+ #
13+ # DESIGN PRINCIPLE:
14+ # - Authorization is scope-based, not object-based
15+ # - Each request is explicitly scoped (course, org, global, etc.)
16+ # - Permissions are granted within scopes, eliminating need for object matching
17+ # - Containment relationships are handled by the application layer
1218#
1319# NOT handled here (deferred to application):
1420# - Resource grouping/containment (app resolves parent-child relationships)
1723############################################
1824
1925[request_definition]
20- # Request format: subject, action, object, scope
26+ # Request format: subject, action, scope
2127#
2228# sub = subject/principal with namespace (e.g., "user:alice", "service:lms")
2329# act = action with namespace (e.g., "act:read", "act:manage", "act:edit-courses")
24- # obj = object/resource with namespace (e.g., "course:course-v1:OpenedX+Demo+2024", "lib:math-basics", "org:OpenedX")
25- # scope = authorization scope context (e.g., "org:OpenedX", "lib:math-basics", "*" for global)
30+ # scope = authorization scope context (e.g., "org:OpenedX", "course-v1:...", "*" for global)
2631#
2732# SCOPE SEMANTICS:
28- # - Scope determines which role assignments are considered
29- # - "*" = global scope (system-wide roles)
30- # - "org:OpenedX" = organization-scoped roles
31- # - "course:course -v1:..." = course-scoped roles
33+ # - Scope determines the authorization context and which role assignments apply
34+ # - "*" = global scope (system-wide roles apply everywhere )
35+ # - "org:OpenedX" = organization-scoped roles (apply within OpenedX org)
36+ # - "course-v1:..." = course-scoped roles (apply within specific course)
3237#
33- # - Application must provide appropriate scope based on business logic
34- r = sub, act, obj, scope
38+ # Application must provide appropriate scope based on business logic.
39+ r = sub, act, scope
3540
3641[policy_definition]
37- # Policy format: subject, action, object, effect
38- #
39- # sub = role or direct user with namespace (e.g., "role:org_admin", "user:bob")
40- # act = action identifier (e.g., "act:manage", "act:read", "act:edit-courses"). Uses g2 relation for action grouping.
41- # obj = object selector - supports multiple patterns via keyMatch:
42- # - Exact ID: "course:course-v1:OpenedX+Demo+2024"
43- # - Namespace wildcard: "course:*" (matches all courses)
44- # - Prefix patterns: "course:course-v1:OpenedX+*" (matches all OpenedX courses)
45- # - Scope targets: "org:OpenedX" (organization itself)
46- # eft = "allow" or "deny" (deny overrides allow for exceptions)
47- p = sub, act, obj, eft
42+ # Policy format: subject, action, scope, effect
43+ #
44+ # sub = role or user with namespace (e.g., "role:org_admin", "user:bob")
45+ # act = action identifier (e.g., "act:manage", "act:read", "act:edit-courses"). Uses g2 relation for action grouping.
46+ # scope = scope where policy applies (e.g., "*", "org:OpenedX", "course-v1:...")
47+ # eft = "allow" or "deny" (deny overrides allow for exceptions)
48+ p = sub, act, scope, eft
4849
4950[role_definition]
50- # g: Role assignments with scope
51- # Format: user/subject, role, scope
51+ # g: Role assignments (without scope)
52+ # Format: user/subject, role
53+ #
54+ # This is a simplified role assignment where users are assigned roles globally,
55+ # without being tied to specific scopes. All role assignments apply system-wide.
5256#
5357# Examples:
54- # g, user:alice, role:org_admin, org:OpenedX # Alice is org admin for OpenedX
55- # g, user:bob, role:course_instructor, course:course-v1:... # Bob is instructor for specific course
56- # g, user:carol, role:platform_admin, * # Carol is global platform admin
57- # g, service:lms, role:system_service, * # LMS service has system-wide access
58+ # g, user:alice, role:org_admin # Alice is org admin
59+ # g, user:bob, role:course_instructor # Bob is course instructor
60+ # g, user:carol, role:platform_admin # Carol is platform admin
61+ # g, service:lms, role:system_service # LMS service has system-wide access
5862#
5963# Role hierarchy (optional):
60- # g, role:org_admin, role:org_editor, org:OpenedX # org_admin inherits org_editor permissions
61- g = _, _, _
64+ # g, role:org_admin, role:org_editor # org_admin inherits org_editor permissions
65+ #
66+ # NOTE: Without scope in role assignments, authorization control must rely entirely
67+ # on policy definitions (p) to restrict access to appropriate scopes/contexts.
68+ g = _, _
6269
6370# g2: Action grouping and implications
6471# Maps high-level actions to specific actions to reduce policy duplication
6572#
6673# Examples:
67- # g2, act:manage, act:read # manage implies read
6874# g2, act:manage, act:edit # manage implies edit
69- # g2, act:manage, act:write # manage implies write
7075# g2, act:manage, act:delete # manage implies delete
71- # g2, act:edit-courses, act:read # edit-courses implies read (for resource grouping )
72- # g2, act:edit-courses, act:write # edit-courses implies write
76+ # g2, act:edit-courses, act:read # edit-courses implies read (for resource access )
77+ # g2, act:edit-courses, act:write # edit-courses implies write (for resource modification)
7378g2 = _, _
7479
7580[policy_effect]
@@ -87,21 +92,26 @@ e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
8792[matchers]
8893# Authorization matching logic
8994#
90- # SCOPE MATCHING:
91- # - g(r.sub, p.sub, r.scope): check if subject has role in requested scope
92- # - g(r.sub, p.sub, "*"): check if subject has global role (applies everywhere)
93- #
94- # OBJECT MATCHING:
95- # - keyMatch(r.obj, p.obj): pattern-based object matching
96- # Supports wildcards like "course:*" matching "course:course-v1:OpenedX+Demo+2024"
97- # Also supports exact matches when no wildcards are used
95+ # SUBJECT MATCHING:
96+ # - g(r.sub, p.sub): check if request subject matches policy subject (role assignment)
97+ # This handles user-to-role mappings defined in the role_definition section
9898#
9999# ACTION MATCHING:
100- # - g2(p.act, r.act): policy action implies requested action via action grouping
100+ # - g2(p.act, r.act): policy action implies requested action via grouping
101101# Allows high-level actions (manage) to grant specific actions (read/write/delete)
102102#
103+ # SCOPE MATCHING:
104+ # - keyMatch(r.scope, p.scope): check if request scope matches policy scope
105+ # Supports wildcard matching (e.g., "*" matches any scope)
106+ # Enables hierarchical scope matching for nested authorization contexts
107+ #
103108# All conditions must be true for a policy to match:
104- # 1. Subject must have the required role in scope OR globally
105- # 2. Object must match the policy object pattern
106- # 3. Policy action must imply the requested action
107- m = (g(r.sub, p.sub, r.scope) || g(r.sub, p.sub, "*")) && keyMatch(r.obj, p.obj) && g2(p.act, r.act)
109+ # 1. Subject must have the required role (via role assignment)
110+ # 2. Policy action must imply the requested action (via action grouping)
111+ # 3. Request scope must match the policy scope (with wildcard support)
112+ #
113+ # SCOPE-BASED AUTHORIZATION:
114+ # The matcher uses keyMatch for flexible scope matching, allowing policies
115+ # to apply to specific scopes (org:OpenedX) or globally (*), providing
116+ # fine-grained control over authorization contexts.
117+ m = g(r.sub, p.sub) && g2(p.act, r.act) && keyMatch(r.scope, p.scope)
0 commit comments