Skip to content

Commit 135d65e

Browse files
committed
fix: Add proper constraints to RoleUserAssignmentViewSet OpenAPI spec
Implements complete schema override using allOf + $ref pattern to document mutually exclusive field requirements for POST /role_user_assignments: - Exactly one of 'user' or 'user_ansible_id' must be provided - At most one of 'object_id' or 'object_ansible_id' can be provided Uses extend_schema_if_available with full content type support (JSON, form-encoded, multipart). Preserves auto-generated schema via $ref while adding oneOf constraints for MCP consumption. Documentation-only fix - no API behavior changes. This prevents MCP clients from submitting invalid requests and improves API documentation accuracy for automated consumption. Co-Authored-By: Claude <[email protected]> Vibe-Coder: Andrew Potozniak <[email protected]> AIA: Entirely AI, Human-initiated, Reviewed, Claude Code [Sonnet 4] v1.0 https://aiattribution.github.io/statements/AIA-EAI-Hin-R-?model=Claude%20Code%20%5BSonnet%204%5D-v1.0
1 parent 4398287 commit 135d65e

File tree

1 file changed

+43
-10
lines changed

1 file changed

+43
-10
lines changed

ansible_base/rbac/api/views.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from rest_framework.viewsets import GenericViewSet, ModelViewSet, mixins
1515

1616
from ansible_base.lib.utils.auth import get_team_model, get_user_model
17+
from ansible_base.lib.utils.schema import extend_schema_if_available
1718
from ansible_base.lib.utils.views.django_app_api import AnsibleBaseDjangoAppApiView
1819
from ansible_base.lib.utils.views.permissions import try_add_oauth2_scope_permission
1920
from ansible_base.rbac.api.permissions import RoleDefinitionPermissions
@@ -236,17 +237,34 @@ class RoleTeamAssignmentViewSet(BaseAssignmentViewSet):
236237
]
237238

238239

239-
class RoleUserAssignmentViewSet(BaseAssignmentViewSet):
240-
"""
241-
Use this endpoint to give a user permission to a resource or an organization.
242-
The needed data is the user, the role definition, and the object id.
243-
The object must be of the type specified in the role definition.
244-
The type given in the role definition and the provided object_id are used
245-
to look up the resource.
240+
# Schema fragments for RoleUserAssignmentViewSet OpenAPI spec
241+
_USER_ACTOR_ONEOF = {
242+
'oneOf': [
243+
{'required': ['user'], 'not': {'required': ['user_ansible_id']}},
244+
{'required': ['user_ansible_id'], 'not': {'required': ['user']}},
245+
]
246+
}
247+
248+
_OBJECT_ID_ONEOF = {
249+
'oneOf': [
250+
{'properties': {'object_id': {'oneOf': [{'type': 'integer'}, {'type': 'string', 'format': 'uuid'}]}, 'object_ansible_id': False}},
251+
{'properties': {'object_ansible_id': {'type': 'string', 'format': 'uuid'}, 'object_id': False}},
252+
{'not': {'anyOf': [{'required': ['object_id']}, {'required': ['object_ansible_id']}]}},
253+
]
254+
}
255+
256+
_ROLE_USER_ASSIGNMENT_REQUEST_SCHEMA = {
257+
'schema': {
258+
'allOf': [
259+
{'$ref': '#/components/schemas/RoleUserAssignment'},
260+
_USER_ACTOR_ONEOF,
261+
_OBJECT_ID_ONEOF,
262+
]
263+
}
264+
}
246265

247-
After creation, the assignment cannot be edited, but can be deleted to
248-
remove those permissions.
249-
"""
266+
267+
class RoleUserAssignmentViewSet(BaseAssignmentViewSet):
250268

251269
resource_purpose = "RBAC role grants assigning permissions to users for specific resources"
252270

@@ -257,6 +275,21 @@ class RoleUserAssignmentViewSet(BaseAssignmentViewSet):
257275
ansible_id_backend.RoleAssignmentFilterBackend,
258276
]
259277

278+
@extend_schema_if_available(
279+
request={
280+
'application/json': _ROLE_USER_ASSIGNMENT_REQUEST_SCHEMA,
281+
'application/x-www-form-urlencoded': _ROLE_USER_ASSIGNMENT_REQUEST_SCHEMA,
282+
'multipart/form-data': _ROLE_USER_ASSIGNMENT_REQUEST_SCHEMA,
283+
},
284+
description="Give a user permission to a resource, an organization, or globally (when allowed)."
285+
"Must specify 'role_definition' and exactly one of 'user' or 'user_ansible_id'."
286+
"Can specify at most one of 'object_id' or 'object_ansible_id' (omit both for global roles)."
287+
"The content_type of the role definition and the provided object_id are used to look up the resource."
288+
"After creation, the assignment cannot be edited, but can be deleted to remove those permissions.",
289+
)
290+
def create(self, request, *args, **kwargs):
291+
return super().create(request, *args, **kwargs)
292+
260293

261294
class AccessURLMixin:
262295
def get_actor_model(self):

0 commit comments

Comments
 (0)