Skip to content

Commit 8dabe43

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 8dabe43

File tree

1 file changed

+56
-0
lines changed

1 file changed

+56
-0
lines changed

ansible_base/rbac/api/views.py

Lines changed: 56 additions & 0 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
@@ -257,6 +258,61 @@ class RoleUserAssignmentViewSet(BaseAssignmentViewSet):
257258
ansible_id_backend.RoleAssignmentFilterBackend,
258259
]
259260

261+
@extend_schema_if_available(
262+
request={
263+
'application/json': {
264+
'schema': {
265+
'allOf': [
266+
{'$ref': '#/components/schemas/RoleUserAssignment'}, # Keep auto-generated schema
267+
{
268+
'oneOf': [
269+
{'required': ['user'], 'not': {'required': ['user_ansible_id']}},
270+
{'required': ['user_ansible_id'], 'not': {'required': ['user']}},
271+
]
272+
},
273+
{
274+
'oneOf': [
275+
{'properties': {'object_id': {'type': 'integer'}, 'object_ansible_id': False}},
276+
{'properties': {'object_ansible_id': {'type': 'string', 'format': 'uuid'}, 'object_id': False}},
277+
{'not': {'anyOf': [{'required': ['object_id']}, {'required': ['object_ansible_id']}]}},
278+
]
279+
},
280+
]
281+
}
282+
},
283+
'application/x-www-form-urlencoded': {
284+
'schema': {
285+
'allOf': [
286+
{'$ref': '#/components/schemas/RoleUserAssignment'},
287+
{
288+
'oneOf': [
289+
{'required': ['user'], 'not': {'required': ['user_ansible_id']}},
290+
{'required': ['user_ansible_id'], 'not': {'required': ['user']}},
291+
]
292+
},
293+
]
294+
}
295+
},
296+
'multipart/form-data': {
297+
'schema': {
298+
'allOf': [
299+
{'$ref': '#/components/schemas/RoleUserAssignment'},
300+
{
301+
'oneOf': [
302+
{'required': ['user'], 'not': {'required': ['user_ansible_id']}},
303+
{'required': ['user_ansible_id'], 'not': {'required': ['user']}},
304+
]
305+
},
306+
]
307+
}
308+
},
309+
},
310+
description="Create role assignment. Must specify 'role_definition' and exactly one of 'user' or 'user_ansible_id'. "
311+
"Can specify at most one of 'object_id' or 'object_ansible_id' (omit both for global roles).",
312+
)
313+
def create(self, request, *args, **kwargs):
314+
return super().create(request, *args, **kwargs)
315+
260316

261317
class AccessURLMixin:
262318
def get_actor_model(self):

0 commit comments

Comments
 (0)