33from django .contrib .auth import get_user_model
44from rest_framework import serializers
55
6- from openedx_authz . api . data import RoleAssignmentData
6+ from openedx_authz import api
77from openedx_authz .rest_api .enums import SortField , SortOrder
88from openedx_authz .rest_api .v1 .fields import CommaSeparatedListField , LowercaseCharField
99
@@ -38,13 +38,48 @@ class PermissionValidationResponseSerializer(PermissionValidationSerializer): #
3838 allowed = serializers .BooleanField ()
3939
4040
41- class AddUsersToRoleWithScopeSerializer (RoleMixin , ScopeMixin ): # pylint: disable=abstract-method
41+ class RoleScopeValidationMixin (serializers .Serializer ): # pylint: disable=abstract-method
42+ """Mixin providing role and scope validation logic."""
43+
44+ def validate (self , attrs ):
45+ """Validate that role exists in scope."""
46+ validated_data = super ().validate (attrs )
47+ scope_value = validated_data ["scope" ]
48+ role_value = validated_data ["role" ]
49+
50+ try :
51+ scope = api .ScopeData (external_key = scope_value )
52+ except ValueError as exc :
53+ raise serializers .ValidationError (exc ) from exc
54+
55+ if not scope .exists ():
56+ raise serializers .ValidationError (f"Scope '{ scope_value } ' does not exist" )
57+
58+ role = api .RoleData (external_key = role_value )
59+ general_scope = api .ScopeData (namespaced_key = f"{ scope .NAMESPACE } { scope .SEPARATOR } *" )
60+ role_definitions = api .get_role_definitions_in_scope (general_scope )
61+
62+ if role not in role_definitions :
63+ raise serializers .ValidationError (f"Role '{ role_value } ' does not exist in scope '{ scope_value } '" )
64+
65+ return validated_data
66+
67+
68+ class AddUsersToRoleWithScopeSerializer (
69+ RoleScopeValidationMixin ,
70+ RoleMixin ,
71+ ScopeMixin ,
72+ ): # pylint: disable=abstract-method
4273 """Serializer for adding users to a role with a scope."""
4374
4475 users = serializers .ListField (child = serializers .CharField (max_length = 255 ), allow_empty = False )
4576
4677
47- class RemoveUsersFromRoleWithScopeSerializer (RoleMixin , ScopeMixin ): # pylint: disable=abstract-method
78+ class RemoveUsersFromRoleWithScopeSerializer (
79+ RoleScopeValidationMixin ,
80+ RoleMixin ,
81+ ScopeMixin ,
82+ ): # pylint: disable=abstract-method
4883 """Serializer for removing users from a role with a scope."""
4984
5085 users = CommaSeparatedListField (allow_blank = False )
@@ -100,7 +135,7 @@ def _get_user(self, obj) -> User | None:
100135 user_map = self .context .get ("user_map" , {})
101136 return user_map .get (obj .subject .username )
102137
103- def get_username (self , obj : RoleAssignmentData ) -> str :
138+ def get_username (self , obj : api . RoleAssignmentData ) -> str :
104139 """Get the username for the given role assignment."""
105140 return obj .subject .username
106141
@@ -114,6 +149,6 @@ def get_email(self, obj) -> str:
114149 user = self ._get_user (obj )
115150 return getattr (user , "email" , "" ) if user else ""
116151
117- def get_roles (self , obj : RoleAssignmentData ) -> list [str ]:
152+ def get_roles (self , obj : api . RoleAssignmentData ) -> list [str ]:
118153 """Get the roles for the given role assignment."""
119154 return [role .external_key for role in obj .roles ]
0 commit comments