Skip to content

Commit c97cdf9

Browse files
refactor: address PR reviews
1 parent 1383535 commit c97cdf9

10 files changed

Lines changed: 67 additions & 67 deletions

File tree

openedx_authz/api/data.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class AuthZData(AuthzBaseClass):
6363
SEPARATOR: The separator between the namespace and the identifier (e.g., ':', '@').
6464
external_key: The ID for the object outside of the authz system (e.g., username).
6565
Could also be used for human-readable names (e.g., role or action name).
66-
namespaced_key: The ID for the object within the authz system (e.g., 'user@john_doe').
66+
namespaced_key: The ID for the object within the authz system (e.g., 'user^john_doe').
6767
"""
6868

6969
external_key: str = ""
@@ -109,9 +109,9 @@ def __call__(cls, *args, **kwargs):
109109
110110
There are two ways to instantiate:
111111
1. By providing external_key= and format for the external key determines the subclass
112-
(e.g., 'lib^any-library' = ContentLibraryData).
112+
(e.g., 'lib:DemoX:CSPROB' = ContentLibraryData).
113113
2. By providing namespaced_key= and the class is determined from the namespace prefix
114-
in namespaced_key (e.g., 'lib@any-library' = ContentLibraryData).
114+
in namespaced_key (e.g., 'lib^lib:DemoX:CSPROB' = ContentLibraryData).
115115
116116
The namespaced key is usually used when getting objects from the policy store,
117117
while the external key is usually used when initializing from user input or API calls. For example,
@@ -138,7 +138,7 @@ def get_subclass_by_namespaced_key(mcs, namespaced_key: str) -> Type["ScopeData"
138138
"""Get the appropriate subclass based on the namespace in namespaced_key.
139139
140140
Args:
141-
namespaced_key: The namespaced key (e.g., 'lib^any-library').
141+
namespaced_key: The namespaced key (e.g., 'lib^lib:DemoX:CSPROB').
142142
143143
Returns:
144144
The subclass of ScopeData corresponding to the namespace, or ScopeData if not found.
@@ -152,13 +152,13 @@ def get_subclass_by_external_key(mcs, external_key: str) -> Type["ScopeData"]:
152152
"""Get the appropriate subclass based on the format of external_key.
153153
154154
Args:
155-
external_key: The external key (e.g., 'lib:any-library').
155+
external_key: The external key (e.g., 'lib^lib:DemoX:CSPROB').
156156
157157
Returns:
158158
The subclass of ScopeData corresponding to the namespace, or ScopeData if not found.
159159
"""
160160
# Here we need to assume a couple of things:
161-
# 1. The external_key is always in the format 'namespace...:other things'. E.g., 'lib:any-library',
161+
# 1. The external_key is always in the format 'namespace...:other things'. E.g., 'lib:DemoX:CSPROB',
162162
# even 'course-v1:edX+DemoX+2021_T1'. This won't work for org scopes because they don't explicitly indicate
163163
# the namespace in the external key. TODO: We need to handle org scopes differently.
164164
# 2. The namespace is always the part before the first separator.
@@ -228,7 +228,7 @@ class ContentLibraryData(ScopeData):
228228
"""A content library is a collection of content items.
229229
230230
Attributes:
231-
library_id: The content library identifier (e.g., 'library-v1:edX+DemoX+2021_T1').
231+
library_id: The content library identifier (e.g., 'lib:DemoX:CSPROB').
232232
namespaced_key: Inherited from ScopeData, auto-generated from name if not provided.
233233
234234
TODO: this class should live alongside library definitions and not here.
@@ -238,7 +238,7 @@ class ContentLibraryData(ScopeData):
238238

239239
@property
240240
def library_id(self) -> str:
241-
"""The library identifier as used in Open edX (e.g., 'math_101', 'library-v1:edX+DemoX').
241+
"""The library identifier as used in Open edX (e.g., 'lib:DemoX:CSPROB').
242242
243243
This is an alias for external_key that represents the library ID without the namespace prefix.
244244
@@ -321,7 +321,7 @@ class SubjectData(AuthZData, metaclass=SubjectMeta):
321321
"""A subject is an entity that can be assigned roles and permissions.
322322
323323
Attributes:
324-
namespaced_key: The subject identifier namespaced (e.g., 'sub@generic').
324+
namespaced_key: The subject identifier namespaced (e.g., 'sub^generic').
325325
"""
326326

327327
NAMESPACE: ClassVar[str] = "sub"
@@ -335,7 +335,7 @@ class UserData(SubjectData):
335335
username: The username for the user (e.g., 'john_doe').
336336
namespaced_key: Inherited from SubjectData, auto-generated from username if not provided.
337337
338-
This class automatically adds the 'user@' namespace prefix to the subject ID.
338+
This class automatically adds the 'user^' namespace prefix to the subject ID.
339339
Can be initialized with either external_key= or namespaced_key= parameter.
340340
"""
341341

@@ -366,7 +366,7 @@ class ActionData(AuthZData):
366366
"""An action is an operation that can be performed in a specific scope.
367367
368368
Attributes:
369-
action: The action name. Automatically prefixed with 'act@' if not present.
369+
action: The action name. Automatically prefixed with 'act^' if not present.
370370
"""
371371

372372
NAMESPACE: ClassVar[str] = "act"
@@ -417,7 +417,7 @@ class RoleData(AuthZData):
417417
"""A role is a named group of permissions.
418418
419419
Attributes:
420-
name: The name of the role. Must have 'role@' namespace prefix.
420+
name: The name of the role. Must have 'role^' namespace prefix.
421421
permissions: A list of permissions assigned to the role.
422422
"""
423423

openedx_authz/api/roles.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,12 @@ def get_permissions_for_active_roles_in_scope(
9494
9595
Role Definition vs Role Assignment:
9696
97-
- Policy roles define potential permissions with namespace patterns (e.g., 'lib@*')
97+
- Policy roles define potential permissions with namespace patterns (e.g., 'lib^*')
9898
- Actual permissions are granted only when roles are assigned to subjects with
99-
concrete scopes (e.g., 'lib@123')
100-
- The namespace pattern in the policy ('lib@*') indicates the role is designed
99+
concrete scopes (e.g., 'lib^lib:DemoX:CSPROB')
100+
- The namespace pattern in the policy ('lib^*') indicates the role is designed
101101
for resources in that namespace, but doesn't grant blanket access
102-
- The specific scope at assignment time ('lib@123') determines the exact
102+
- The specific scope at assignment time ('lib^lib:DemoX:CSPROB') determines the exact
103103
resource the permissions apply to
104104
105105
Behavior:
@@ -140,7 +140,7 @@ def get_role_definitions_in_scope(scope: ScopeData) -> list[RoleData]:
140140
definitions vs assignments.
141141
142142
Args:
143-
scope: The scope to filter roles (e.g., 'lib@*' or '*' for global).
143+
scope: The scope to filter roles (e.g., 'lib^*' or '*' for global).
144144
145145
Returns:
146146
list[Role]: A list of roles.
@@ -185,7 +185,7 @@ def get_all_roles_in_scope(scope: ScopeData) -> list[list[str]]:
185185
"""Get all the available role grouping policies in a specific scope.
186186
187187
Args:
188-
scope: The scope to filter roles (e.g., 'lib@*' or '*' for global).
188+
scope: The scope to filter roles (e.g., 'lib^*' or '*' for global).
189189
190190
Returns:
191191
list[list[str]]: A list of policies in the specified scope.
@@ -260,7 +260,7 @@ def get_subject_role_assignments(subject: SubjectData) -> list[RoleAssignmentDat
260260
subject: The ID of the subject namespaced (e.g., 'subject^john_doe').
261261
262262
Returns:
263-
list[Role]: A list of role names and all their metadata assigned to the subject.
263+
list[RoleAssignmentData]: A list of role assignments for the subject.
264264
"""
265265
role_assignments = []
266266
for policy in enforcer.get_filtered_grouping_policy(
@@ -289,7 +289,7 @@ def get_subject_role_assignments_in_scope(
289289
scope: The scope to filter roles (e.g., 'library:123').
290290
291291
Returns:
292-
list[RoleAssignment]: A list of role assignments for the subject in the scope.
292+
list[RoleAssignmentData]: A list of role assignments for the subject in the scope.
293293
"""
294294
# TODO: we still need to get the remaining data for the role like email, etc
295295
role_assignments = []
@@ -322,7 +322,7 @@ def get_subject_role_assignments_for_role_in_scope(
322322
scope: The scope to filter subjects (e.g., 'library:123' or '*' for global).
323323
324324
Returns:
325-
list[RoleAssignment]: A list of subjects assigned to the specified role in the specified scope.
325+
list[RoleAssignmentData]: A list of subjects assigned to the specified role in the specified scope.
326326
"""
327327
role_assignments = []
328328
for subject in enforcer.get_users_for_role_in_domain(
@@ -357,7 +357,7 @@ def get_all_subject_role_assignments_in_scope(
357357
scope: The scope to filter subjects (e.g., 'library:123' or '*' for global).
358358
359359
Returns:
360-
list[RoleAssignment]: A list of role assignments for all subjects in the specified scope.
360+
list[RoleAssignmentData]: A list of role assignments for all subjects in the specified scope.
361361
"""
362362
role_assignments_per_subject = {}
363363
roles_in_scope = get_all_roles_in_scope(scope)

openedx_authz/api/users.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
77
These methods internally namespace user identifiers to ensure consistency
88
with the role management system, which uses namespaced subjects
9-
(e.g., 'user@john_doe').
9+
(e.g., 'user^john_doe').
1010
"""
1111

1212
from openedx_authz.api.data import (

openedx_authz/engine/config/model.conf

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,36 @@
66
# - Action grouping (manage → read/write/edit/delete to reduce duplication)
77
# - System-wide roles (global scope "*" applies everywhere)
88
# - Negative rules (deny overrides allow for exceptions)
9-
# - Namespace support (course:*, lib:*, org:*, etc.)
9+
# - Namespace support (user^, role^, act^, lib^, org^, course^, etc.)
1010
# - Extensibility (new resource types just need new namespaces)
11+
# - Separator: ^ for AuthZ policy attributes, : for external keys
1112
############################################
1213

1314
[request_definition]
1415
# Request format: subject (user), action, scope (specific resource being accessed)
1516
#
16-
# sub = subject/principal with namespace (e.g., "user:alice", "service:lms")
17-
# act = action with namespace (e.g., "act:read", "act:manage", "act:edit-courses")
18-
# scope = authorization scope context (e.g., "org:OpenedX", "course-v1:...", "*" for global)
17+
# sub = subject/principal with namespace (e.g., "user^alice", "service^lms")
18+
# act = action with namespace (e.g., "act^read", "act^manage", "act^edit_courses")
19+
# scope = authorization scope context (e.g., "org^OpenedX", "lib^lib:...", "*" for global)
1920
#
2021
# SCOPE SEMANTICS:
2122
# Scope determines the authorization context and which role assignments apply
22-
# - "*" = global scope (system-wide roles apply everywhere)
23-
# - "org:..." = organization-scoped roles (apply within specific organization)
24-
# - "course-v1:..." = course-scoped roles (apply within specific course)
25-
# - "lib:..." = library-scoped roles (apply within specific library)
23+
# - "*" = global scope (system-wide roles apply everywhere)
24+
# - "org^..." = organization-scoped roles (namespaced external org key)
25+
# - "course^course-v1:..." = course-scoped roles (namespaced external course key)
26+
# - "lib^lib:..." = library-scoped roles (namespaced external library key)
2627
#
28+
# Note: AuthZ policy attributes use ^ separator for namespace prefix (e.g., user^alice, role^admin),
29+
# while external keys (course-v1:..., lib:...) retain their original : separator format.
2730
# Application must provide appropriate scope based on business logic.
2831
r = sub, act, scope
2932

3033
[policy_definition]
3134
# Policy format: subject (role), action, scope (pattern), effect
3235
#
33-
# sub = role or user with namespace (e.g., "role:org_admin", "user:bob")
34-
# act = action identifier (e.g., "act:manage", "act:read", "act:edit-courses")
35-
# scope = scope where policy applies (e.g., "*", "org:*", "course-v1:*", "lib:*")
36+
# sub = role or user with namespace (e.g., "role^org_admin", "user^bob")
37+
# act = action identifier (e.g., "act^manage", "act^read", "act^edit_courses")
38+
# scope = scope where policy applies (e.g., "*", "org^*", "lib^*")
3639
# eft = "allow" or "deny" (deny overrides allow for exceptions)
3740
p = sub, act, scope, eft
3841

@@ -41,22 +44,22 @@ p = sub, act, scope, eft
4144
# Format: user/subject, role, scope
4245
#
4346
# Examples:
44-
# g, user:alice, role:org_admin, org:OpenedX # Alice is org admin for OpenedX
45-
# g, user:bob, role:course_instructor, course-v1:... # Bob is instructor for specific course
46-
# g, user:carol, role:library_admin, * # Carol is global library admin
47+
# g, user^alice, role^org_admin, org^OpenedX # Alice is org admin for OpenedX
48+
# g, user^bob, role^course_instructor, course^course-v1:... # Bob is instructor for specific course
49+
# g, user^carol, role^library_admin, * # Carol is global library admin
4750
#
4851
# Role hierarchy (optional):
49-
# g, role:org_admin, role:org_editor, org:OpenedX # org_admin inherits org_editor permissions
52+
# g, role^org_admin, role^org_editor, org^OpenedX # org_admin inherits org_editor permissions
5053
g = _, _, _
5154

5255
# g2: Action grouping and implications
5356
# Maps high-level actions to specific actions to reduce policy duplication
5457
#
5558
# Examples:
56-
# g2, act:manage, act:edit # manage implies edit
57-
# g2, act:manage, act:delete # manage implies delete
58-
# g2, act:edit-courses, act:read # edit-courses implies read (for resource access)
59-
# g2, act:edit-courses, act:write # edit-courses implies write (for resource modification)
59+
# g2, act^manage, act^edit # manage implies edit
60+
# g2, act^manage, act^delete # manage implies delete
61+
# g2, act^edit_courses, act^read # edit_courses implies read (for resource access)
62+
# g2, act^edit_courses, act^write # edit_courses implies write (for resource modification)
6063
g2 = _, _
6164

6265
[policy_effect]

openedx_authz/management/commands/load_policies.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
- Specifying the path to the Casbin policy file. Default is 'openedx_authz/engine/config/authz.policy'.
55
- Specifying the Casbin model configuration file. Default is 'openedx_authz/engine/config/model.conf'.
66
- Optionally clearing existing policies in the database before loading new ones.
7-
8-
Example Usage:
9-
python manage.py load_policies --policy-file-path /path/to/policy.csv
107
"""
118

129
import os
@@ -27,8 +24,8 @@ class Command(BaseCommand):
2724
and persistence of authorization policies within the Django application.
2825
2926
Example Usage:
30-
python manage.py load_policies --policy-file-path /path/to/policy.csv
31-
python manage.py load_policies --policy-file-path /path/to/policy.csv --clear-existing
27+
python manage.py load_policies --policy-file-path /path/to/authz.policy
28+
python manage.py load_policies --policy-file-path /path/to/authz.policy --model-file-path /path/to/model.conf
3229
python manage.py load_policies
3330
"""
3431

openedx_authz/tests/api/test_roles.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def _assign_roles_to_users(
6767
6868
Args:
6969
assignments (list of dict): List of assignment dictionaries, each containing:
70-
- subject (str): ID of the user namespaced (e.g., 'user:john_doe').
70+
- subject (str): ID of the user namespaced (e.g., 'user^john_doe').
7171
- role_id (str): Name of the role to assign.
7272
- scope (str): Scope in which to assign the role.
7373
"""

openedx_authz/tests/api/test_users.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def _assign_roles_to_users(
3939
4040
Args:
4141
assignments (list of dict): List of assignment dictionaries, each containing:
42-
- subject (str): ID of the user namespaced (e.g., 'user:john_doe').
42+
- subject (str): ID of the user namespaced (e.g., 'user^john_doe').
4343
- role_id (str): Name of the role to assign.
4444
- scope (str): Scope in which to assign the role.
4545
"""

openedx_authz/tests/test_enforcement.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ class SystemWideRoleTests(CasbinEnforcementTestCase):
145145
{
146146
"subject": make_user_key("user-1"),
147147
"action": make_action_key("manage"),
148-
"scope": make_library_key("lib@any-org@any-library"),
148+
"scope": make_library_key("lib:DemoX:CSPROB"),
149149
"expected_result": True,
150150
},
151151
]
@@ -235,10 +235,10 @@ class RoleAssignmentTests(CasbinEnforcementTestCase):
235235
make_role_key("course_admin"),
236236
make_scope_key("course", "course-v1:any-org+any-course+any-course-run"),
237237
],
238-
["g", make_user_key("user-6"), make_role_key("library_admin"), make_library_key("lib@any-org@any-library")],
239-
["g", make_user_key("user-7"), make_role_key("library_editor"), make_library_key("lib@any-org@any-library")],
240-
["g", make_user_key("user-8"), make_role_key("library_reviewer"), make_library_key("lib@any-org@any-library")],
241-
["g", make_user_key("user-9"), make_role_key("library_author"), make_library_key("lib@any-org@any-library")],
238+
["g", make_user_key("user-6"), make_role_key("library_admin"), make_library_key("lib:DemoX:CSPROB")],
239+
["g", make_user_key("user-7"), make_role_key("library_editor"), make_library_key("lib:DemoX:CSPROB")],
240+
["g", make_user_key("user-8"), make_role_key("library_reviewer"), make_library_key("lib:DemoX:CSPROB")],
241+
["g", make_user_key("user-9"), make_role_key("library_author"), make_library_key("lib:DemoX:CSPROB")],
242242
] + COMMON_ACTION_GROUPING
243243

244244
CASES = [
@@ -275,25 +275,25 @@ class RoleAssignmentTests(CasbinEnforcementTestCase):
275275
{
276276
"subject": make_user_key("user-6"),
277277
"action": make_action_key("manage"),
278-
"scope": make_library_key("lib@any-org@any-library"),
278+
"scope": make_library_key("lib:DemoX:CSPROB"),
279279
"expected_result": True,
280280
},
281281
{
282282
"subject": make_user_key("user-7"),
283283
"action": make_action_key("edit"),
284-
"scope": make_library_key("lib@any-org@any-library"),
284+
"scope": make_library_key("lib:DemoX:CSPROB"),
285285
"expected_result": True,
286286
},
287287
{
288288
"subject": make_user_key("user-8"),
289289
"action": make_action_key("read"),
290-
"scope": make_library_key("lib@any-org@any-library"),
290+
"scope": make_library_key("lib:DemoX:CSPROB"),
291291
"expected_result": True,
292292
},
293293
{
294294
"subject": make_user_key("user-9"),
295295
"action": make_action_key("write"),
296-
"scope": make_library_key("lib@any-org@any-library"),
296+
"scope": make_library_key("lib:DemoX:CSPROB"),
297297
"expected_result": True,
298298
},
299299
]
@@ -395,7 +395,7 @@ class WildcardScopeTests(CasbinEnforcementTestCase):
395395
("*", True),
396396
(make_scope_key("org", "MIT"), True),
397397
(make_scope_key("course", "course-v1:OpenedX+DemoX+CS101"), True),
398-
(make_library_key("lib@OpenedX:math-basics"), True),
398+
(make_library_key("lib:OpenedX:math-basics"), True),
399399
)
400400
@unpack
401401
def test_wildcard_global_access(self, scope: str, expected_result: bool):
@@ -412,7 +412,7 @@ def test_wildcard_global_access(self, scope: str, expected_result: bool):
412412
("*", False),
413413
(make_scope_key("org", "MIT"), True),
414414
(make_scope_key("course", "course-v1:OpenedX+DemoX+CS101"), False),
415-
(make_library_key("lib@OpenedX:math-basics"), False),
415+
(make_library_key("lib:OpenedX:math-basics"), False),
416416
)
417417
@unpack
418418
def test_wildcard_org_access(self, scope: str, expected_result: bool):
@@ -429,7 +429,7 @@ def test_wildcard_org_access(self, scope: str, expected_result: bool):
429429
("*", False),
430430
(make_scope_key("org", "MIT"), False),
431431
(make_scope_key("course", "course-v1:OpenedX+DemoX+CS101"), True),
432-
(make_library_key("lib@OpenedX:math-basics"), False),
432+
(make_library_key("lib:OpenedX:math-basics"), False),
433433
)
434434
@unpack
435435
def test_wildcard_course_access(self, scope: str, expected_result: bool):
@@ -446,7 +446,7 @@ def test_wildcard_course_access(self, scope: str, expected_result: bool):
446446
("*", False),
447447
(make_scope_key("org", "MIT"), False),
448448
(make_scope_key("course", "course-v1:OpenedX+DemoX+CS101"), False),
449-
(make_library_key("lib@OpenedX:math-basics"), True),
449+
(make_library_key("lib:OpenedX:math-basics"), True),
450450
)
451451
@unpack
452452
def test_wildcard_library_access(self, scope: str, expected_result: bool):

0 commit comments

Comments
 (0)