Skip to content

Commit 5a52b1c

Browse files
authored
AAP-51789 - allow_null for field created_by_ansible_id (ansible#813)
Jira - https://issues.redhat.com/browse/AAP-51789 ## Description <!-- Mandatory: Provide a clear, concise description of the changes and their purpose --> - What is being changed? Field `created_by_ansible_id` now accepts null values - Why is this change needed? Needed so that when creating users directly via controller, a user can successfully be added to orgs/teams, even if this field is initially not populated. This allows for successful user creation, which was previously blocked since null being required caused the social_auth_pipeline to fail. - How does this change address the issue? This change addresses the issue by allowing null for the field created_by_ansible_id
1 parent 192336b commit 5a52b1c

File tree

2 files changed

+178
-1
lines changed

2 files changed

+178
-1
lines changed

ansible_base/rbac/service_api/serializers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def to_internal_value(self, value):
5353
class BaseAssignmentSerializer(serializers.ModelSerializer):
5454
content_type = serializers.SlugRelatedField(read_only=True, slug_field='api_slug')
5555
role_definition = serializers.SlugRelatedField(slug_field='name', queryset=RoleDefinition.objects.all())
56-
created_by_ansible_id = ActorAnsibleIdField(source='created_by', required=False)
56+
created_by_ansible_id = ActorAnsibleIdField(source='created_by', required=False, allow_null=True)
5757
object_ansible_id = ObjectIDAnsibleIDField(source='object_id', required=False, allow_null=True)
5858
object_id = serializers.CharField(allow_blank=True, required=False, allow_null=True)
5959
from_service = serializers.CharField(write_only=True)

test_app/tests/rbac/remote/test_service_api.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,180 @@ def test_service_api_permissions(reverse_name, normal_case, unauth_case, admin_a
287287

288288
unauth_response = unauthenticated_api_client.get(url)
289289
assert unauth_response.status_code == unauth_case, unauth_response.data
290+
291+
292+
@pytest.mark.django_db
293+
class TestCreatedByAnsibleIdAllowNull:
294+
"""Test that created_by_ansible_id field accepts null values and omissions"""
295+
296+
def test_service_user_assignment_with_null_created_by(self, admin_api_client, rando, inv_rd, inventory):
297+
"""Test that ServiceRoleUserAssignmentSerializer accepts null created_by_ansible_id"""
298+
url = get_relative_url('serviceuserassignment-assign')
299+
data = {
300+
"role_definition": inv_rd.name,
301+
"user_ansible_id": str(rando.resource.ansible_id),
302+
"object_id": inventory.pk,
303+
"created_by_ansible_id": "", # Use empty string instead of None
304+
}
305+
306+
response = admin_api_client.post(url, data=data)
307+
assert response.status_code == 201, response.data
308+
assert rando.has_obj_perm(inventory, 'change')
309+
310+
def test_service_user_assignment_without_created_by(self, admin_api_client, rando, inv_rd, inventory):
311+
"""Test that ServiceRoleUserAssignmentSerializer works when created_by_ansible_id is omitted"""
312+
url = get_relative_url('serviceuserassignment-assign')
313+
data = {
314+
"role_definition": inv_rd.name,
315+
"user_ansible_id": str(rando.resource.ansible_id),
316+
"object_id": inventory.pk,
317+
# created_by_ansible_id is intentionally omitted
318+
}
319+
320+
response = admin_api_client.post(url, data=data)
321+
assert response.status_code == 201, response.data
322+
assert rando.has_obj_perm(inventory, 'change')
323+
324+
def test_service_user_assignment_with_valid_created_by(self, admin_api_client, rando, inv_rd, inventory):
325+
"""Test that valid created_by_ansible_id values still work correctly"""
326+
creator = User.objects.create(username='creator-user')
327+
url = get_relative_url('serviceuserassignment-assign')
328+
data = {
329+
"role_definition": inv_rd.name,
330+
"user_ansible_id": str(rando.resource.ansible_id),
331+
"object_id": inventory.pk,
332+
"created_by_ansible_id": str(creator.resource.ansible_id),
333+
}
334+
335+
response = admin_api_client.post(url, data=data)
336+
assert response.status_code == 201, response.data
337+
assert rando.has_obj_perm(inventory, 'change')
338+
339+
def test_service_team_assignment_with_null_created_by(self, admin_api_client, team, inv_rd, inventory, member_rd, rando):
340+
"""Test that ServiceRoleTeamAssignmentSerializer accepts null created_by_ansible_id"""
341+
member_rd.give_permission(rando, team)
342+
url = get_relative_url('serviceteamassignment-assign')
343+
data = {
344+
"role_definition": inv_rd.name,
345+
"team_ansible_id": str(team.resource.ansible_id),
346+
"object_id": inventory.pk,
347+
"created_by_ansible_id": "", # Use empty string instead of None
348+
}
349+
350+
response = admin_api_client.post(url, data=data)
351+
assert response.status_code == 201, response.data
352+
assert rando.has_obj_perm(inventory, 'change')
353+
354+
def test_service_team_assignment_without_created_by(self, admin_api_client, team, inv_rd, inventory, member_rd, rando):
355+
"""Test that ServiceRoleTeamAssignmentSerializer works when created_by_ansible_id is omitted"""
356+
member_rd.give_permission(rando, team)
357+
url = get_relative_url('serviceteamassignment-assign')
358+
data = {
359+
"role_definition": inv_rd.name,
360+
"team_ansible_id": str(team.resource.ansible_id),
361+
"object_id": inventory.pk,
362+
}
363+
364+
response = admin_api_client.post(url, data=data)
365+
assert response.status_code == 201, response.data
366+
assert rando.has_obj_perm(inventory, 'change')
367+
368+
def test_service_team_assignment_with_valid_created_by(self, admin_api_client, team, inv_rd, inventory, member_rd, rando):
369+
"""Test that valid created_by_ansible_id values still work correctly for teams"""
370+
member_rd.give_permission(rando, team)
371+
creator = User.objects.create(username='team-creator-user')
372+
url = get_relative_url('serviceteamassignment-assign')
373+
data = {
374+
"role_definition": inv_rd.name,
375+
"team_ansible_id": str(team.resource.ansible_id),
376+
"object_id": inventory.pk,
377+
"created_by_ansible_id": str(creator.resource.ansible_id),
378+
}
379+
380+
response = admin_api_client.post(url, data=data)
381+
assert response.status_code == 201, response.data
382+
assert rando.has_obj_perm(inventory, 'change')
383+
384+
def test_list_assignments_shows_created_by_when_present(self, admin_api_client, rando, inv_rd, inventory):
385+
"""Test that list endpoint properly serializes created_by_ansible_id when present"""
386+
creator = User.objects.create(username='assignment-creator')
387+
388+
# Create assignment with a specific creator
389+
url = get_relative_url('serviceuserassignment-assign')
390+
data = {
391+
"role_definition": inv_rd.name,
392+
"user_ansible_id": str(rando.resource.ansible_id),
393+
"object_id": inventory.pk,
394+
"created_by_ansible_id": str(creator.resource.ansible_id),
395+
}
396+
response = admin_api_client.post(url, data=data)
397+
assert response.status_code == 201, response.data
398+
399+
# Check list endpoint
400+
list_url = get_relative_url('serviceuserassignment-list')
401+
response = admin_api_client.get(list_url + '?page_size=200', format="json")
402+
assert response.status_code == 200, response.data
403+
404+
# Find our assignment
405+
assignments = [a for a in response.data['results'] if a['role_definition'] == inv_rd.name and str(a['object_id']) == str(inventory.id)]
406+
assert len(assignments) >= 1, "Should find at least our assignment"
407+
408+
# Check that created_by_ansible_id is properly serialized
409+
assignment = assignments[0]
410+
assert 'created_by_ansible_id' in assignment
411+
assert assignment['created_by_ansible_id'] == str(creator.resource.ansible_id)
412+
413+
def test_list_assignments_shows_null_created_by_when_null(self, admin_api_client, rando, inv_rd, inventory):
414+
"""Test that list endpoint properly serializes created_by_ansible_id when empty string is provided"""
415+
# Create assignment with empty created_by_ansible_id
416+
url = get_relative_url('serviceuserassignment-assign')
417+
data = {
418+
"role_definition": inv_rd.name,
419+
"user_ansible_id": str(rando.resource.ansible_id),
420+
"object_id": inventory.pk,
421+
"created_by_ansible_id": "", # Use empty string - should be treated as not providing the field
422+
}
423+
response = admin_api_client.post(url, data=data)
424+
assert response.status_code == 201, response.data
425+
426+
# Check list endpoint
427+
list_url = get_relative_url('serviceuserassignment-list')
428+
response = admin_api_client.get(list_url + '?page_size=200', format="json")
429+
assert response.status_code == 200, response.data
430+
431+
# Find our assignment
432+
assignments = [a for a in response.data['results'] if a['role_definition'] == inv_rd.name and str(a['object_id']) == str(inventory.id)]
433+
assert len(assignments) >= 1, "Should find at least our assignment"
434+
435+
# Check that created_by_ansible_id is properly serialized
436+
assignment = assignments[0]
437+
assert 'created_by_ansible_id' in assignment
438+
# When empty string is provided, the system may still set created_by to the current user
439+
# The key test is that the API accepts empty string without error
440+
assert assignment['created_by_ansible_id'] is not None # System will set to current user
441+
442+
def test_serializer_allows_null_values_in_validation(self, admin_api_client, rando, inv_rd, inventory):
443+
"""Test that the serializer field properly handles null validation with allow_null=True"""
444+
from ansible_base.rbac.service_api.serializers import ServiceRoleUserAssignmentSerializer
445+
446+
# Test data with null created_by_ansible_id
447+
data = {
448+
"role_definition": inv_rd.name,
449+
"user_ansible_id": str(rando.resource.ansible_id),
450+
"object_id": str(inventory.pk),
451+
"created_by_ansible_id": None, # Explicit None
452+
"from_service": "test",
453+
}
454+
455+
# Create serializer and validate
456+
serializer = ServiceRoleUserAssignmentSerializer(data=data)
457+
458+
# Should be valid due to allow_null=True
459+
is_valid = serializer.is_valid()
460+
if not is_valid:
461+
print("Validation errors:", serializer.errors)
462+
assert is_valid, f"Serializer should accept null values: {serializer.errors}"
463+
464+
# Verify that created_by is None in validated_data when null is passed
465+
validated_data = serializer.validated_data
466+
assert 'created_by' not in validated_data or validated_data.get('created_by') is None

0 commit comments

Comments
 (0)