Skip to content

Commit c3c0beb

Browse files
committed
refactor: extract scope/role validation into reusable helper in RoleScopeValidationMixin
1 parent 96bb3f1 commit c3c0beb

1 file changed

Lines changed: 28 additions & 33 deletions

File tree

openedx_authz/rest_api/v1/serializers.py

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -62,28 +62,17 @@ class PermissionValidationResponseSerializer(PermissionValidationSerializer): #
6262
class RoleScopeValidationMixin(serializers.Serializer): # pylint: disable=abstract-method
6363
"""Mixin providing role and scope validation logic."""
6464

65-
def validate(self, attrs) -> dict:
66-
"""Validate that the specified role and scope are valid and that the role exists in the scope.
67-
68-
This method performs the following validations:
69-
1. Validates that the scope is registered in the scope registry
70-
2. Validates that the scope exists in the system
71-
3. Validates that the role is defined into the roles assigned to the scope
65+
def _validate_scope_and_role(self, scope_value: str, role_value: str) -> None:
66+
"""Validate that a single scope exists and the role is defined in it.
7267
7368
Args:
74-
attrs: Dictionary containing 'role' and 'scope' keys with their string values.
75-
76-
Returns:
77-
dict: The validated data dictionary with 'role' and 'scope' keys.
69+
scope_value: The scope string to validate.
70+
role_value: The role string to validate against the scope.
7871
7972
Raises:
8073
serializers.ValidationError: If the scope is not registered, doesn't exist,
8174
or if the role is not defined in the scope.
8275
"""
83-
validated_data = super().validate(attrs)
84-
scope_value = validated_data["scope"]
85-
role_value = validated_data["role"]
86-
8776
try:
8877
scope = api.ScopeData(external_key=scope_value)
8978
except ValueError as exc:
@@ -99,10 +88,31 @@ def validate(self, attrs) -> dict:
9988
if role not in role_definitions:
10089
raise serializers.ValidationError({"role": f"Role '{role_value}' does not exist in scope '{scope_value}'"})
10190

91+
def validate(self, attrs) -> dict:
92+
"""Validate that the specified role and scope are valid and that the role exists in the scope.
93+
94+
This method performs the following validations:
95+
1. Validates that the scope is registered in the scope registry
96+
2. Validates that the scope exists in the system
97+
3. Validates that the role is defined into the roles assigned to the scope
98+
99+
Args:
100+
attrs: Dictionary containing 'role' and 'scope' keys with their string values.
101+
102+
Returns:
103+
dict: The validated data dictionary with 'role' and 'scope' keys.
104+
105+
Raises:
106+
serializers.ValidationError: If the scope is not registered, doesn't exist,
107+
or if the role is not defined in the scope.
108+
"""
109+
validated_data = super().validate(attrs)
110+
self._validate_scope_and_role(validated_data["scope"], validated_data["role"])
102111
return validated_data
103112

104113

105114
class AddUsersToRoleWithScopeSerializer(
115+
RoleScopeValidationMixin,
106116
RoleMixin,
107117
ScopeMixin,
108118
): # pylint: disable=abstract-method
@@ -122,15 +132,15 @@ class AddUsersToRoleWithScopeSerializer(
122132
users = serializers.ListField(child=serializers.CharField(max_length=255), allow_empty=False)
123133

124134
def validate_users(self, value) -> list[str]:
125-
"""Eliminate duplicates preserving order"""
135+
"""Eliminate duplicates preserving order."""
126136
return list(dict.fromkeys(value))
127137

128138
def validate(self, attrs) -> dict:
129139
"""Validate that exactly one of 'scope'/'scopes' is provided and that every
130140
scope exists in the registry, exists in the system, and supports the role.
131141
Returns validated data with a unified ``scopes`` list of strings.
132142
"""
133-
validated_data = super().validate(attrs)
143+
validated_data = super(RoleScopeValidationMixin, self).validate(attrs)
134144
scope = validated_data.get("scope")
135145
scopes = validated_data.get("scopes")
136146
role_value = validated_data["role"]
@@ -147,22 +157,7 @@ def validate(self, attrs) -> dict:
147157
)
148158

149159
for scope_value in scopes_list:
150-
try:
151-
scope_obj = api.ScopeData(external_key=scope_value)
152-
except ValueError as exc:
153-
raise serializers.ValidationError({"scope": str(exc)}) from exc
154-
155-
if not scope_obj.exists():
156-
raise serializers.ValidationError({"scope": f"Scope '{scope_value}' does not exist"})
157-
158-
role_obj = api.RoleData(external_key=role_value)
159-
generic_scope = get_generic_scope(scope_obj)
160-
role_definitions = api.get_role_definitions_in_scope(generic_scope)
161-
162-
if role_obj not in role_definitions:
163-
raise serializers.ValidationError(
164-
{"role": f"Role '{role_value}' does not exist in scope '{scope_value}'"}
165-
)
160+
self._validate_scope_and_role(scope_value, role_value)
166161

167162
validated_data.pop("scope", None)
168163
validated_data["scopes"] = scopes_list

0 commit comments

Comments
 (0)