diff --git a/awx/api/views.py b/awx/api/views.py index 3fc668e615..83fa184ab7 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -3759,7 +3759,16 @@ class RoleList(ListAPIView): new_in_300 = True def get_queryset(self): - return Role.visible_roles(self.request.user) + result = Role.visible_roles(self.request.user) + # Sanity check: is the requesting user an orphaned non-admin/auditor? + # if yes, make system admin/auditor mandatorily visible. + if not self.request.user.organizations.exists() and\ + not self.request.user.is_superuser and\ + not self.request.user.is_system_auditor: + mandatories = ('system_administrator', 'system_auditor') + super_qs = Role.objects.filter(singleton_name__in=mandatories) + result = result | super_qs + return result class RoleDetail(RetrieveAPIView): diff --git a/awx/main/models/rbac.py b/awx/main/models/rbac.py index 3cb016ffde..f469c1a7ac 100644 --- a/awx/main/models/rbac.py +++ b/awx/main/models/rbac.py @@ -381,7 +381,7 @@ class Role(models.Model): 'ancestors_table': Role.ancestors.through._meta.db_table, 'parents_table': Role.parents.through._meta.db_table, 'roles_table': Role._meta.db_table, - 'ids': ','.join(str(x) for x in user.roles.values_list('id', flat=True)) + 'ids': ','.join(str(x) for x in user.roles.values_list('id', flat=True)), } qs = Role.objects.extra( diff --git a/awx/main/tests/functional/api/test_role.py b/awx/main/tests/functional/api/test_role.py new file mode 100644 index 0000000000..94215521a5 --- /dev/null +++ b/awx/main/tests/functional/api/test_role.py @@ -0,0 +1,13 @@ +import pytest + +from django.core.urlresolvers import reverse + +@pytest.mark.django_db +def test_admin_visible_to_orphaned_users(get, alice): + names = set() + + response = get(reverse('api:role_list'), user=alice) + for item in response.data['results']: + names.add(item['name']) + assert 'System Auditor' in names + assert 'System Administrator' in names