Skip to content

Commit 6c7f2aa

Browse files
committed
feat: use namespace instead scope to list role definitions
1 parent 2994d2c commit 6c7f2aa

3 files changed

Lines changed: 54 additions & 14 deletions

File tree

openedx_authz/api/data.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,20 @@ def get_subclass_by_external_key(mcs, external_key: str) -> Type["ScopeData"]:
256256

257257
return scope_subclass
258258

259+
@classmethod
260+
def get_all_namespaces(mcs) -> dict[str, Type["ScopeData"]]:
261+
"""Get all registered scope namespaces.
262+
263+
Returns:
264+
dict[str, Type["ScopeData"]]: A dictionary of all namespace prefixes registered in the scope registry.
265+
Each namespace corresponds to a ScopeData subclass (e.g., 'lib', 'sc').
266+
267+
Examples:
268+
>>> ScopeMeta.get_all_namespaces()
269+
{'sc': ScopeData, 'lib': ContentLibraryData, 'org': OrganizationData}
270+
"""
271+
return mcs.scope_registry
272+
259273
@classmethod
260274
def validate_external_key(mcs, external_key: str) -> bool:
261275
"""Validate the external_key format for the subclass.

openedx_authz/rest_api/v1/serializers.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,36 @@ class ListUsersInRoleWithScopeSerializer(ScopeMixin): # pylint: disable=abstrac
9898
search = LowercaseCharField(required=False, default=None)
9999

100100

101-
class ListRolesWithScopeSerializer(ScopeMixin): # pylint: disable=abstract-method
102-
"""Serializer for listing roles with a scope."""
101+
class ListRolesWithNamespaceSerializer(serializers.Serializer): # pylint: disable=abstract-method
102+
"""Serializer for listing roles within a namespace."""
103+
104+
namespace = serializers.CharField(max_length=255)
105+
106+
def validate_namespace(self, value: str) -> api.ScopeData:
107+
"""Validate and convert namespace string to a ScopeData instance.
108+
109+
Checks that the provided namespace is registered in the scope registry and
110+
returns an instance of the appropriate ScopeData subclass with a wildcard
111+
external_key to represent all scopes within that namespace.
112+
113+
Args:
114+
value: The namespace string to validate (e.g., 'lib', 'sc', 'org').
115+
116+
Returns:
117+
ScopeData: An instance of the appropriate ScopeData subclass for the
118+
namespace, initialized with external_key="*".
119+
120+
Raises:
121+
serializers.ValidationError: If the namespace is not registered in the scope registry.
122+
123+
Examples:
124+
>>> validate_namespace('lib')
125+
ContentLibraryData(external_key='*')
126+
"""
127+
namespaces = api.ScopeData.get_all_namespaces()
128+
if value not in namespaces:
129+
raise serializers.ValidationError(f"'{value}' is not a valid namespace")
130+
return namespaces[value](external_key="*")
103131

104132

105133
class ListUsersInRoleWithScopeResponseSerializer(serializers.Serializer): # pylint: disable=abstract-method

openedx_authz/rest_api/v1/views.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
from openedx_authz.rest_api.v1.permissions import HasLibraryPermission
2828
from openedx_authz.rest_api.v1.serializers import (
2929
AddUsersToRoleWithScopeSerializer,
30+
ListRolesWithNamespaceSerializer,
3031
ListRolesWithScopeResponseSerializer,
31-
ListRolesWithScopeSerializer,
3232
ListUsersInRoleWithScopeSerializer,
3333
PermissionValidationResponseSerializer,
3434
PermissionValidationSerializer,
@@ -300,17 +300,17 @@ def delete(self, request: HttpRequest) -> Response:
300300

301301
@view_auth_classes()
302302
class RoleListView(APIView):
303-
"""API view for retrieving role definitions and their associated permissions.
303+
"""API view for retrieving role definitions and their associated permissions within a specific namespace.
304304
305305
This view provides read-only access to role definitions within a specific
306-
authorization scope. It returns detailed information about each role including
306+
authorization namespace. It returns detailed information about each role including
307307
the permissions granted and the number of users assigned to each role.
308308
309309
**Endpoints**
310-
GET: Retrieve all roles and their permissions for a specific scope
310+
GET: Retrieve all roles and their permissions for a specific namespace
311311
312312
**Query Parameters**
313-
- scope (Required): The authorization scope to query roles for (e.g., 'lib^*')
313+
- namespace (Required): The namespace to query roles for (e.g., 'lib')
314314
- page (Optional): Page number for pagination
315315
- page_size (Optional): Number of items per page
316316
@@ -324,7 +324,7 @@ class RoleListView(APIView):
324324
Requires authenticated user.
325325
326326
**Example Request**
327-
GET /api/authz/v1/roles/?scope=lib^*&page=1&page_size=10
327+
GET /api/authz/v1/roles/?namespace=lib&page=1&page_size=10
328328
329329
**Example Response**
330330
{
@@ -350,7 +350,7 @@ class RoleListView(APIView):
350350

351351
@apidocs.schema(
352352
parameters=[
353-
apidocs.query_parameter("scope", str, description="The scope to query roles for"),
353+
apidocs.query_parameter("namespace", str, description="The namespace to query roles for"),
354354
apidocs.query_parameter("page", int, description="Page number for pagination"),
355355
apidocs.query_parameter("page_size", int, description="Number of items per page"),
356356
],
@@ -361,13 +361,11 @@ class RoleListView(APIView):
361361
},
362362
)
363363
def get(self, request: HttpRequest) -> Response:
364-
"""Retrieve all roles and their permissions for a specific scope."""
365-
serializer = ListRolesWithScopeSerializer(data=request.query_params)
364+
"""Retrieve all roles and their permissions for a specific namespace."""
365+
serializer = ListRolesWithNamespaceSerializer(data=request.query_params)
366366
serializer.is_valid(raise_exception=True)
367367

368-
scope = api.ContentLibraryData(external_key=serializer.validated_data["scope"])
369-
roles = api.get_role_definitions_in_scope(scope)
370-
368+
roles = api.get_role_definitions_in_scope(serializer.validated_data["namespace"])
371369
response_data = []
372370
for role in roles:
373371
users = api.get_users_for_role(role.external_key)

0 commit comments

Comments
 (0)