Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ Unreleased

*

0.9.0 - 2025-10-27
******************

Added
=====

* Function API to retrieve scopes for a given role and subject.

0.8.0 - 2025-10-24
******************

Expand Down
2 changes: 1 addition & 1 deletion openedx_authz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

import os

__version__ = "0.8.0"
__version__ = "0.9.0"

ROOT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
20 changes: 20 additions & 0 deletions openedx_authz/api/roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"get_subject_role_assignments_for_role_in_scope",
"get_all_subject_role_assignments_in_scope",
"get_subject_role_assignments",
"get_scopes_for_role_and_subject",
]

# TODO: these are the concerns we still have to address:
Expand Down Expand Up @@ -373,3 +374,22 @@ def get_subjects_for_role(role: RoleData) -> list[SubjectData]:
enforcer = AuthzEnforcer.get_enforcer()
policies = enforcer.get_filtered_grouping_policy(GroupingPolicyIndex.ROLE.value, role.namespaced_key)
return [SubjectData(namespaced_key=policy[GroupingPolicyIndex.SUBJECT.value]) for policy in policies]


def get_scopes_for_role_and_subject(role: RoleData, subject: SubjectData) -> list[ScopeData]:
"""Get all the scopes where a specific subject is assigned a specific role.

Args:
role (RoleData): The role to filter scopes.
subject (SubjectData): The subject to filter scopes.

Returns:
list[ScopeData]: A list of scopes where the subject is assigned the specified role.
"""
enforcer = AuthzEnforcer.get_enforcer()
policies = enforcer.get_filtered_grouping_policy(GroupingPolicyIndex.SUBJECT.value, subject.namespaced_key)
return [
ScopeData(namespaced_key=policy[GroupingPolicyIndex.SCOPE.value])
for policy in policies
if policy[GroupingPolicyIndex.ROLE.value] == role.namespaced_key
]
17 changes: 17 additions & 0 deletions openedx_authz/api/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
batch_assign_role_to_subjects_in_scope,
batch_unassign_role_from_subjects_in_scope,
get_all_subject_role_assignments_in_scope,
get_scopes_for_role_and_subject,
get_subject_role_assignments,
get_subject_role_assignments_for_role_in_scope,
get_subject_role_assignments_in_scope,
Expand Down Expand Up @@ -198,3 +199,19 @@ def get_users_for_role(role_external_key: str) -> list[UserData]:
"""
users = get_subjects_for_role(RoleData(external_key=role_external_key))
return [UserData(namespaced_key=user.namespaced_key) for user in users]


def get_scopes_for_role_and_user(role_external_key: str, user_external_key: str) -> list[ScopeData]:
"""Get all scopes where a specific user has been assigned a specific role.

Args:
role_external_key (str): The role to filter scopes (e.g., 'instructor').
user_external_key (str): ID of the user (e.g., 'john_doe').

Returns:
list[ScopeData]: A list of scopes where the user has the specified role.
"""
return get_scopes_for_role_and_subject(
RoleData(external_key=role_external_key),
UserData(external_key=user_external_key),
)
19 changes: 19 additions & 0 deletions openedx_authz/tests/api/test_roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
get_permissions_for_active_roles_in_scope,
get_permissions_for_single_role,
get_role_definitions_in_scope,
get_scopes_for_role_and_subject,
get_subject_role_assignments,
get_subject_role_assignments_for_role_in_scope,
get_subject_role_assignments_in_scope,
Expand Down Expand Up @@ -505,6 +506,24 @@ def test_get_role_assignments_in_scope(self, role_name, scope_name, expected_cou

self.assertEqual(len(role_assignments), expected_count)

def test_get_scopes_for_role_and_subject(self):
"""Test retrieving scopes for a given role and subject.

Expected result:
- The scopes associated with the specified role and subject are correctly retrieved.
"""
role_name = "library_author"
subject_name = "liam"
expected_scopes = {"lib:Org4:art_101", "lib:Org4:art_201", "lib:Org4:art_301"}

scopes = get_scopes_for_role_and_subject(
RoleData(external_key=role_name),
SubjectData(external_key=subject_name),
)

scope_names = {scope.external_key for scope in scopes}
self.assertEqual(scope_names, expected_scopes)


@ddt
class TestRoleAssignmentAPI(RolesTestSetupMixin):
Expand Down