Skip to content

Commit a11161f

Browse files
refactor: address PR reviews
1 parent e27c02a commit a11161f

10 files changed

Lines changed: 175 additions & 272 deletions

File tree

openedx_authz/api/data.py

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,7 @@ def __attrs_post_init__(self):
8989

9090
# Case 3: Neither provided, raise error
9191
if not self.external_key and not self.namespaced_key:
92-
raise ValueError(
93-
"Either external_key or namespaced_key must be provided."
94-
)
92+
raise ValueError("Either external_key or namespaced_key must be provided.")
9593

9694

9795
class ScopeMeta(type):
@@ -329,7 +327,6 @@ class ActionData(AuthZData):
329327
"""
330328

331329
NAMESPACE: ClassVar[str] = "act"
332-
name: str = ""
333330

334331
@property
335332
def name(self) -> str:
@@ -356,36 +353,17 @@ class PermissionData(AuthZData):
356353
effect: Literal["allow", "deny"] = "allow"
357354

358355

359-
@define
360-
class RoleMetadataData(AuthZData):
361-
"""Metadata for a role.
362-
363-
Attributes:
364-
description: A description of the role.
365-
created_at: The date and time the role was created.
366-
created_by: The ID of the subject who created the role.
367-
"""
368-
369-
description: str = None
370-
created_at: str = None
371-
created_by: str = None
372-
373-
374356
@define
375357
class RoleData(AuthZData):
376358
"""A role is a named group of permissions.
377359
378360
Attributes:
379361
name: The name of the role. Must have 'role@' namespace prefix.
380-
role_id: The role identifier namespaced (e.g., 'role@instructor').
381362
permissions: A list of permissions assigned to the role.
382-
metadata: A dictionary of metadata assigned to the role. This can include
383-
information such as the description of the role, creation date, etc.
384363
"""
385364

386365
NAMESPACE: ClassVar[str] = "role"
387-
permissions: list[PermissionData] = None
388-
metadata: RoleMetadataData = None
366+
permissions: list[PermissionData] = list()
389367

390368
@property
391369
def name(self) -> str:
@@ -405,10 +383,9 @@ class RoleAssignmentData(AuthZData):
405383
"""A role assignment is the assignment of a role to a subject in a specific scope.
406384
407385
Attributes:
408-
subject: The ID of the user namespaced (e.g., 'user@john_doe').
409-
email: The email of the user.
410-
role_name: The name of the role.
411-
scope: The scope in which the role is assigned.
386+
subject: The subject to whom the role is assigned (e.g., user or service).
387+
role: The role being assigned.
388+
scope: The scope in which the role is assigned (e.g., organization, course).
412389
"""
413390

414391
subject: SubjectData = None # Needs defaults to avoid value error from attrs

openedx_authz/api/permissions.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55
are not explicitly defined, but are inferred from the policy rules.
66
"""
77

8-
from openedx_authz.api.data import ActionData, PermissionData, PolicyIndex, ScopeData, SubjectData
8+
from openedx_authz.api.data import (
9+
ActionData,
10+
PermissionData,
11+
PolicyIndex,
12+
ScopeData,
13+
SubjectData,
14+
)
915
from openedx_authz.engine.enforcer import enforcer
1016

1117
__all__ = [

openedx_authz/api/roles.py

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from openedx_authz.engine.enforcer import enforcer
2424

2525
__all__ = [
26+
"get_permissions_for_single_role",
2627
"get_permissions_for_roles",
2728
"get_all_roles_names",
2829
"get_all_roles_in_scope",
@@ -47,8 +48,23 @@
4748
# in this case, ALL the policies, but that might not be the case
4849

4950

51+
def get_permissions_for_single_role(
52+
role: RoleData,
53+
) -> list[PermissionData]:
54+
"""Get the permissions (actions) for a single role.
55+
56+
Args:
57+
role: A RoleData object representing the role.
58+
59+
Returns:
60+
list[PermissionData]: A list of PermissionData objects associated with the given role.
61+
"""
62+
policies = enforcer.get_implicit_permissions_for_user(role.namespaced_key)
63+
return [get_permission_from_policy(policy) for policy in policies]
64+
65+
5066
def get_permissions_for_roles(
51-
roles: list[RoleData] | RoleData,
67+
roles: list[RoleData],
5268
) -> dict[str, dict[str, list[PermissionData | str]]]:
5369
"""Get the permissions (actions) for a list of roles.
5470
@@ -59,22 +75,11 @@ def get_permissions_for_roles(
5975
dict[str, list[PermissionData]]: A dictionary mapping role names to their permissions and scopes.
6076
"""
6177
permissions_by_role = {}
62-
if not roles:
63-
return permissions_by_role
64-
65-
if isinstance(roles, RoleData):
66-
roles = [roles]
6778

6879
for role in roles:
69-
policies = enforcer.get_implicit_permissions_for_user(role.namespaced_key)
70-
71-
permissions_by_role[role.external_key] = (
72-
{ # Index by role external_key for easy lookup
73-
"permissions": [
74-
get_permission_from_policy(policy) for policy in policies
75-
],
76-
}
77-
)
80+
permissions_by_role[role.external_key] = {
81+
"permissions": get_permissions_for_single_role(role)
82+
}
7883

7984
return permissions_by_role
8085

@@ -252,7 +257,7 @@ def get_subject_role_assignments(subject: SubjectData) -> list[RoleAssignmentDat
252257
"""Get all the roles for a subject across all scopes.
253258
254259
Args:
255-
subject: The ID of the subject namespaced (e.g., 'subject:john_doe').
260+
subject: The ID of the subject namespaced (e.g., 'subject^john_doe').
256261
257262
Returns:
258263
list[Role]: A list of role names and all their metadata assigned to the subject.
@@ -262,11 +267,7 @@ def get_subject_role_assignments(subject: SubjectData) -> list[RoleAssignmentDat
262267
GroupingPolicyIndex.SUBJECT.value, subject.namespaced_key
263268
):
264269
role = RoleData(namespaced_key=policy[GroupingPolicyIndex.ROLE.value])
265-
role.permissions = get_permissions_for_roles(role)[
266-
role.external_key
267-
][ # Index by role external_key for readability
268-
"permissions"
269-
]
270+
role.permissions = get_permissions_for_single_role(role)
270271

271272
role_assignments.append(
272273
RoleAssignmentData(
@@ -284,7 +285,7 @@ def get_subject_role_assignments_in_scope(
284285
"""Get the roles for a subject in a specific scope.
285286
286287
Args:
287-
subject: The ID of the subject namespaced (e.g., 'subject:john_doe').
288+
subject: The ID of the subject namespaced (e.g., 'subject^john_doe').
288289
scope: The scope to filter roles (e.g., 'library:123').
289290
290291
Returns:
@@ -301,9 +302,7 @@ def get_subject_role_assignments_in_scope(
301302
subject=subject,
302303
role=RoleData(
303304
namespaced_key=namespaced_key,
304-
permissions=get_permissions_for_roles(role)[role.external_key][
305-
"permissions"
306-
],
305+
permissions=get_permissions_for_single_role(role),
307306
),
308307
scope=scope,
309308
)
@@ -332,14 +331,10 @@ def get_subjects_role_assignments_for_role_in_scope(
332331
continue
333332
role_assignments.append(
334333
RoleAssignmentData(
335-
subject=SubjectData(
336-
namespaced_key=subject
337-
),
334+
subject=SubjectData(namespaced_key=subject),
338335
role=RoleData(
339336
external_key=role.external_key,
340-
permissions=get_permissions_for_roles(role)[role.external_key][
341-
"permissions"
342-
],
337+
permissions=get_permissions_for_single_role(role),
343338
),
344339
scope=scope,
345340
)
@@ -364,9 +359,7 @@ def get_all_subject_role_assignments_in_scope(
364359
for policy in roles_in_scope:
365360
subject = SubjectData(namespaced_key=policy[GroupingPolicyIndex.SUBJECT.value])
366361
role = RoleData(namespaced_key=policy[GroupingPolicyIndex.ROLE.value])
367-
role.permissions = get_permissions_for_roles(role)[role.external_key][
368-
"permissions"
369-
] # Index by role external_key for easy lookup
362+
role.permissions = get_permissions_for_single_role(role)
370363

371364
role_assignments.append(
372365
RoleAssignmentData(

openedx_authz/api/users.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
(e.g., 'user@john_doe').
1010
"""
1111

12-
from openedx_authz.api.data import ActionData, RoleAssignmentData, RoleData, ScopeData, UserData
12+
from openedx_authz.api.data import (
13+
ActionData,
14+
RoleAssignmentData,
15+
RoleData,
16+
ScopeData,
17+
UserData,
18+
)
1319
from openedx_authz.api.permissions import has_permission
1420
from openedx_authz.api.roles import (
1521
assign_role_to_subject_in_scope,
@@ -24,7 +30,7 @@
2430

2531
__all__ = [
2632
"assign_role_to_user_in_scope",
27-
"batch_assign_role_to_users",
33+
"batch_assign_role_to_users_in_scope",
2834
"unassign_role_from_user",
2935
"batch_unassign_role_from_users",
3036
"get_user_role_assignments",
@@ -52,9 +58,7 @@ def assign_role_to_user_in_scope(
5258
)
5359

5460

55-
def batch_assign_role_to_users(
56-
users: list[str], role_external_key: str, scope_external_key: str
57-
) -> dict[str, bool]:
61+
def batch_assign_role_to_users_in_scope(users: list[str], role_external_key: str, scope_external_key: str):
5862
"""Assign a role to multiple users in a specific scope.
5963
6064
Args:
@@ -70,9 +74,7 @@ def batch_assign_role_to_users(
7074
)
7175

7276

73-
def unassign_role_from_user(
74-
user_external_key: str, role_external_key: str, scope_external_key: str
75-
) -> bool:
77+
def unassign_role_from_user(user_external_key: str, role_external_key: str, scope_external_key: str):
7678
"""Unassign a role from a user in a specific scope.
7779
7880
Args:
@@ -87,9 +89,7 @@ def unassign_role_from_user(
8789
)
8890

8991

90-
def batch_unassign_role_from_users(
91-
users: list[str], role_external_key: str, scope_external_key: str
92-
) -> dict[str, bool]:
92+
def batch_unassign_role_from_users(users: list[str], role_external_key: str, scope_external_key: str):
9393
"""Unassign a role from multiple users in a specific scope.
9494
9595
Args:
@@ -112,7 +112,7 @@ def get_user_role_assignments(user_external_key: str) -> list[RoleAssignmentData
112112
user_external_key (str): ID of the user (e.g., 'john_doe').
113113
114114
Returns:
115-
list[dict]: A list of role assignments and all their metadata assigned to the user.
115+
list[RoleAssignmentData]: A list of role assignments and all their metadata assigned to the user.
116116
"""
117117
return get_subject_role_assignments(UserData(external_key=user_external_key))
118118

@@ -127,7 +127,7 @@ def get_user_role_assignments_in_scope(
127127
scope (str): Scope in which to retrieve the roles.
128128
129129
Returns:
130-
list: A list of role assignments assigned to the user in the specified scope.
130+
list[RoleAssignmentData]: A list of role assignments assigned to the user in the specified scope.
131131
"""
132132
return get_subject_role_assignments_in_scope(
133133
UserData(external_key=user_external_key),
@@ -145,7 +145,7 @@ def get_user_role_assignments_for_role_in_scope(
145145
scope (str): Scope in which to retrieve the role assignments.
146146
147147
Returns:
148-
list[dict]: A list of user names and all their metadata assigned to the role.
148+
list[RoleAssignmentData]: A list of user names and all their metadata assigned to the role.
149149
"""
150150
# TODO: this SHOULD definitely be managed in a better way by using class inheritance and factories
151151
# But for now we'll keep it simple and explicit
@@ -164,7 +164,7 @@ def get_all_user_role_assignments_in_scope(
164164
scope (str): Scope in which to retrieve the user role assignments.
165165
166166
Returns:
167-
list[dict]: A list of user role assignments and all their metadata in the specified scope.
167+
list[RoleAssignmentData]: A list of user role assignments and all their metadata in the specified scope.
168168
"""
169169
return get_all_subject_role_assignments_in_scope(
170170
ScopeData(external_key=scope_external_key)

openedx_authz/engine/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
GROUPING_POLICY_PTYPES = ["g", "g2", "g3", "g4", "g5", "g6"]
1414

1515

16-
def migrate_policy_from_file_to_db(
16+
def migrate_policy_between_enforcers(
1717
source_enforcer: Enforcer,
1818
target_enforcer: Enforcer,
1919
) -> None:

openedx_authz/management/commands/load_policies.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from django.core.management.base import BaseCommand
1414

1515
from openedx_authz.engine.enforcer import enforcer as global_enforcer
16-
from openedx_authz.engine.utils import migrate_policy_from_file_to_db
16+
from openedx_authz.engine.utils import migrate_policy_between_enforcers
1717

1818

1919
class Command(BaseCommand):
@@ -49,11 +49,6 @@ def add_arguments(self, parser) -> None:
4949
default="openedx_authz/engine/config/model.conf",
5050
help="Path to the Casbin model configuration file",
5151
)
52-
parser.add_argument(
53-
"--clear-existing",
54-
action="store_true",
55-
help="Clear existing policies in the database before loading new ones",
56-
)
5752

5853
def handle(self, *args, **options):
5954
"""Execute the policy loading command.
@@ -87,4 +82,4 @@ def migrate_policies(self, source_enforcer, target_enforcer):
8782
source_enforcer: The Casbin enforcer instance to migrate policies from.
8883
target_enforcer: The Casbin enforcer instance to migrate policies to.
8984
"""
90-
migrate_policy_from_file_to_db(source_enforcer, target_enforcer)
85+
migrate_policy_between_enforcers(source_enforcer, target_enforcer)

openedx_authz/tests/api/test_data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def test_scope_content_lib_data_namespace(self, external_key):
7474

7575

7676
@ddt
77-
class TestPolymorphismLowLevelAPIs(TestCase):
77+
class TestPolymorphicData(TestCase):
7878
"""Test polymorphic factory pattern for SubjectData and ScopeData."""
7979

8080
@data(

0 commit comments

Comments
 (0)