diff --git a/awx/main/models/rbac.py b/awx/main/models/rbac.py index 697d51f1e2..5e040b85a1 100644 --- a/awx/main/models/rbac.py +++ b/awx/main/models/rbac.py @@ -75,7 +75,7 @@ def check_singleton(func): if user in sys_admin or user in sys_audit: if len(args) == 2: return args[1] - return user.roles.all() + return Role.objects.all() return func(*args, **kwargs) return wrapper @@ -382,9 +382,10 @@ class Role(models.Model): qs = Role.objects.extra( where = [''' %(roles_table)s.id IN ( - SELECT descendent_id FROM %(ancestors_table)s WHERE ancestor_id IN (%(ids)s) - UNION - SELECT ancestor_id FROM %(ancestors_table)s WHERE descendent_id IN (%(ids)s) + SELECT DISTINCT visible_roles_t2.ancestor_id + FROM %(ancestors_table)s as visible_roles_t1 + LEFT JOIN %(ancestors_table)s as visible_roles_t2 ON (visible_roles_t1.descendent_id = visible_roles_t2.descendent_id) + WHERE visible_roles_t1.ancestor_id IN (%(ids)s) ) ''' % sql_params] ) @@ -403,10 +404,11 @@ class Role(models.Model): qs = roles_qs.extra( where = [''' EXISTS ( - SELECT 1 FROM - %(ancestors_table)s - WHERE (descendent_id = %(roles_table)s.id AND ancestor_id IN (%(ids)s)) - OR (ancestor_id = %(roles_table)s.id AND descendent_id IN (%(ids)s)) + SELECT 1 + FROM %(ancestors_table)s as visible_roles_t1 + LEFT JOIN %(ancestors_table)s as visible_roles_t2 ON (visible_roles_t1.descendent_id = visible_roles_t2.descendent_id) + WHERE visible_roles_t1.ancestor_id = %(roles_table)s.id + AND visible_roles_t2.ancestor_id IN (%(ids)s) ) ''' % sql_params] ) return qs diff --git a/awx/main/tests/functional/test_rbac_api.py b/awx/main/tests/functional/test_rbac_api.py index e6f5959355..d5ccbdf5d0 100644 --- a/awx/main/tests/functional/test_rbac_api.py +++ b/awx/main/tests/functional/test_rbac_api.py @@ -57,6 +57,28 @@ def test_get_roles_list_user(organization, inventory, team, get, user): assert inventory.admin_role.id not in role_hash assert team.member_role.id not in role_hash +@pytest.mark.django_db +def test_roles_visibility(get, organization, project, admin, alice, bob): + Role.singleton('system_auditor').members.add(alice) + assert get(reverse('api:role_list') + '?id=%d' % project.update_role.id, user=admin).data['count'] == 1 + assert get(reverse('api:role_list') + '?id=%d' % project.update_role.id, user=alice).data['count'] == 1 + assert get(reverse('api:role_list') + '?id=%d' % project.update_role.id, user=bob).data['count'] == 0 + organization.auditor_role.members.add(bob) + assert get(reverse('api:role_list') + '?id=%d' % project.update_role.id, user=bob).data['count'] == 1 + +@pytest.mark.django_db +def test_roles_filter_visibility(get, organization, project, admin, alice, bob): + Role.singleton('system_auditor').members.add(alice) + project.update_role.members.add(admin) + + assert get(reverse('api:user_roles_list', args=(admin.id,)) + '?id=%d' % project.update_role.id, user=admin).data['count'] == 1 + assert get(reverse('api:user_roles_list', args=(admin.id,)) + '?id=%d' % project.update_role.id, user=alice).data['count'] == 1 + assert get(reverse('api:user_roles_list', args=(admin.id,)) + '?id=%d' % project.update_role.id, user=bob).data['count'] == 0 + organization.auditor_role.members.add(bob) + assert get(reverse('api:user_roles_list', args=(admin.id,)) + '?id=%d' % project.update_role.id, user=bob).data['count'] == 1 + organization.auditor_role.members.remove(bob) + project.use_role.members.add(bob) # sibling role should still grant visibility + assert get(reverse('api:user_roles_list', args=(admin.id,)) + '?id=%d' % project.update_role.id, user=bob).data['count'] == 1 @pytest.mark.django_db def test_cant_create_role(post, admin):