From 4531f276c259a58adba1bc30f75597eeda26630a Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Tue, 5 Apr 2016 22:24:43 -0400 Subject: [PATCH 1/2] Updated access_list to include team information for indirect access coming from teams Finally addresses #1212 --- awx/api/serializers.py | 36 ++++++++++++++-- .../api/test_resource_access_lists.py | 41 +++++++++++++++++++ awx/main/tests/functional/test_rbac_api.py | 14 ------- 3 files changed, 74 insertions(+), 17 deletions(-) create mode 100644 awx/main/tests/functional/api/test_resource_access_lists.py diff --git a/awx/api/serializers.py b/awx/api/serializers.py index ebecaa26e1..772784d875 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -1485,17 +1485,47 @@ class ResourceAccessListElementSerializer(UserSerializer): role_dict['related'] = reverse_gfk(role.content_object) except: pass - return { 'role': role_dict, 'permissions': get_role_permissions_on_resource(obj, role)} + def format_team_role_perm(team_role, all_permissive_role_ids): + role = team_role.children.filter(id__in=all_permissive_role_ids)[0] + + role_dict = { + 'id': role.id, + 'name': role.name, + 'description': role.description, + 'team_id': team_role.object_id, + 'team_name': team_role.content_object.name + } + try: + role_dict['resource_name'] = role.content_object.name + role_dict['resource_type'] = role.content_type.name + role_dict['related'] = reverse_gfk(role.content_object) + except: + pass + return { 'role': role_dict, 'permissions': get_role_permissions_on_resource(obj, team_role)} + + team_content_type = ContentType.objects.get_for_model(Team) content_type = ContentType.objects.get_for_model(obj) direct_permissive_role_ids = RolePermission.objects.filter(content_type=content_type, object_id=obj.id).values_list('role__id') direct_access_roles = user.roles.filter(id__in=direct_permissive_role_ids).all() ret['summary_fields']['direct_access'] = [format_role_perm(r) for r in direct_access_roles] all_permissive_role_ids = RolePermission.objects.filter(content_type=content_type, object_id=obj.id).values_list('role__ancestors__id') - indirect_access_roles = user.roles.filter(id__in=all_permissive_role_ids).exclude(id__in=direct_permissive_role_ids).all() - ret['summary_fields']['indirect_access'] = [format_role_perm(r) for r in indirect_access_roles] + + team_roles = Role.objects \ + .filter(content_type=team_content_type, + members=user, + children__in=all_permissive_role_ids) + + indirect_access_roles = user.roles \ + .filter(id__in=all_permissive_role_ids) \ + .exclude(id__in=direct_permissive_role_ids) \ + .exclude(id__in=team_roles) + ret['summary_fields']['indirect_access'] \ + = [format_role_perm(r) for r in indirect_access_roles] \ + + [format_team_role_perm(r, all_permissive_role_ids) for r in team_roles] + return ret diff --git a/awx/main/tests/functional/api/test_resource_access_lists.py b/awx/main/tests/functional/api/test_resource_access_lists.py new file mode 100644 index 0000000000..48e261b977 --- /dev/null +++ b/awx/main/tests/functional/api/test_resource_access_lists.py @@ -0,0 +1,41 @@ +import pytest + +from django.core.urlresolvers import reverse + +@pytest.mark.django_db +def test_indirect_access_list(get, organization, project, team, alice, bob, admin): + + project.admin_role.members.add(alice) + team.member_role.members.add(bob) + team.member_role.children.add(organization.admin_role) + + result = get(reverse('api:project_access_list', args=(project.id,)), admin) + assert result.status_code == 200 + + # Result should be alice should have direct access, bob should have + # indirect access through being a team member -> org admin -> project admin, + # and admin should have access through system admin -> org admin -> project admin + assert result.data['count'] == 3 + + alice_res = [r for r in result.data['results'] if r['id'] == alice.id][0] + bob_res = [r for r in result.data['results'] if r['id'] == bob.id][0] + admin_res = [r for r in result.data['results'] if r['id'] == admin.id][0] + + assert len(alice_res['summary_fields']['direct_access']) == 1 + assert len(alice_res['summary_fields']['indirect_access']) == 0 + assert len(bob_res['summary_fields']['direct_access']) == 0 + assert len(bob_res['summary_fields']['indirect_access']) == 1 + assert len(admin_res['summary_fields']['direct_access']) == 0 + assert len(admin_res['summary_fields']['indirect_access']) == 1 + + alice_entry = alice_res['summary_fields']['direct_access'][0]['role'] + assert alice_entry['id'] == project.admin_role.id + + bob_entry = bob_res['summary_fields']['indirect_access'][0]['role'] + assert bob_entry['id'] == organization.admin_role.id + assert bob_entry['team_id'] == team.id + assert bob_entry['team_name'] == team.name + + admin_entry = admin_res['summary_fields']['indirect_access'][0]['role'] + assert admin_entry['name'] == 'System Administrator' + diff --git a/awx/main/tests/functional/test_rbac_api.py b/awx/main/tests/functional/test_rbac_api.py index e50206d3f3..6200a46289 100644 --- a/awx/main/tests/functional/test_rbac_api.py +++ b/awx/main/tests/functional/test_rbac_api.py @@ -389,20 +389,6 @@ def test_role_children(get, team, admin, role): - -# -# /resource//access_list -# - -@pytest.mark.django_db -def test_resource_access_list(get, team, admin, role): - team.member_role.members.add(admin) - url = reverse('api:team_access_list', args=(team.id,)) - res = get(url, admin) - assert res.status_code == 200 - - - # # Generics # From 09f1473ef9cdc3f3211789d1800421e447660c17 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Thu, 7 Apr 2016 11:13:24 -0400 Subject: [PATCH 2/2] Added direct access via teams to the access_list endpoint --- awx/api/serializers.py | 47 ++++++++++++---- .../api/test_resource_access_lists.py | 56 ++++++++++++------- 2 files changed, 71 insertions(+), 32 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 772784d875..bda63004c4 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -1469,6 +1469,16 @@ class RoleSerializer(BaseSerializer): class ResourceAccessListElementSerializer(UserSerializer): def to_representation(self, user): + ''' + With this method we derive "direct" and "indirect" access lists. Contained + in the direct access list are all the roles the user is a member of, and + all of the roles that are directly granted to any teams that the user is a + member of. + + The indirect access list is a list of all of the roles that the user is + a member of that are ancestors of any roles that grant permissions to + the resource. + ''' ret = super(ResourceAccessListElementSerializer, self).to_representation(user) object_id = self.context['view'].object_id obj = self.context['view'].resource_model.objects.get(pk=object_id) @@ -1487,8 +1497,8 @@ class ResourceAccessListElementSerializer(UserSerializer): pass return { 'role': role_dict, 'permissions': get_role_permissions_on_resource(obj, role)} - def format_team_role_perm(team_role, all_permissive_role_ids): - role = team_role.children.filter(id__in=all_permissive_role_ids)[0] + def format_team_role_perm(team_role, permissive_role_ids): + role = team_role.children.filter(id__in=permissive_role_ids)[0] role_dict = { 'id': role.id, @@ -1507,24 +1517,37 @@ class ResourceAccessListElementSerializer(UserSerializer): team_content_type = ContentType.objects.get_for_model(Team) content_type = ContentType.objects.get_for_model(obj) - direct_permissive_role_ids = RolePermission.objects.filter(content_type=content_type, object_id=obj.id).values_list('role__id') - direct_access_roles = user.roles.filter(id__in=direct_permissive_role_ids).all() - ret['summary_fields']['direct_access'] = [format_role_perm(r) for r in direct_access_roles] + direct_permissive_role_ids = RolePermission.objects.filter(content_type=content_type, object_id=obj.id).values_list('role__id') all_permissive_role_ids = RolePermission.objects.filter(content_type=content_type, object_id=obj.id).values_list('role__ancestors__id') - team_roles = Role.objects \ - .filter(content_type=team_content_type, - members=user, - children__in=all_permissive_role_ids) + direct_access_roles = user.roles \ + .filter(id__in=direct_permissive_role_ids).all() + + direct_team_roles = Role.objects \ + .filter(content_type=team_content_type, + members=user, + children__in=direct_permissive_role_ids) + + indirect_team_roles = Role.objects \ + .filter(content_type=team_content_type, + members=user, + children__in=all_permissive_role_ids) \ + .exclude(id__in=direct_team_roles) indirect_access_roles = user.roles \ - .filter(id__in=all_permissive_role_ids) \ + .filter(id__in=all_permissive_role_ids) \ .exclude(id__in=direct_permissive_role_ids) \ - .exclude(id__in=team_roles) + .exclude(id__in=direct_team_roles) \ + .exclude(id__in=indirect_team_roles) + + ret['summary_fields']['direct_access'] \ + = [format_role_perm(r) for r in direct_access_roles] \ + + [format_team_role_perm(r, direct_permissive_role_ids) for r in direct_team_roles] + ret['summary_fields']['indirect_access'] \ = [format_role_perm(r) for r in indirect_access_roles] \ - + [format_team_role_perm(r, all_permissive_role_ids) for r in team_roles] + + [format_team_role_perm(r, all_permissive_role_ids) for r in indirect_team_roles] return ret diff --git a/awx/main/tests/functional/api/test_resource_access_lists.py b/awx/main/tests/functional/api/test_resource_access_lists.py index 48e261b977..75e55fd8ca 100644 --- a/awx/main/tests/functional/api/test_resource_access_lists.py +++ b/awx/main/tests/functional/api/test_resource_access_lists.py @@ -3,38 +3,54 @@ import pytest from django.core.urlresolvers import reverse @pytest.mark.django_db -def test_indirect_access_list(get, organization, project, team, alice, bob, admin): +def test_indirect_access_list(get, organization, project, team_factory, user, admin): + project_admin = user('project_admin') + org_admin_team_member = user('org_admin_team_member') + project_admin_team_member = user('project_admin_team_member') - project.admin_role.members.add(alice) - team.member_role.members.add(bob) - team.member_role.children.add(organization.admin_role) + org_admin_team = team_factory('org-admin-team') + project_admin_team = team_factory('project-admin-team') + + project.admin_role.members.add(project_admin) + org_admin_team.member_role.members.add(org_admin_team_member) + org_admin_team.member_role.children.add(organization.admin_role) + project_admin_team.member_role.members.add(project_admin_team_member) + project_admin_team.member_role.children.add(project.admin_role) result = get(reverse('api:project_access_list', args=(project.id,)), admin) assert result.status_code == 200 - # Result should be alice should have direct access, bob should have - # indirect access through being a team member -> org admin -> project admin, - # and admin should have access through system admin -> org admin -> project admin - assert result.data['count'] == 3 + # Result should be: + # project_admin should have direct access, + # project_team_admin should have "direct" access through being a team member -> project admin, + # org_admin_team_member should have indirect access through being a team member -> org admin -> project admin, + # admin should have access through system admin -> org admin -> project admin + assert result.data['count'] == 4 - alice_res = [r for r in result.data['results'] if r['id'] == alice.id][0] - bob_res = [r for r in result.data['results'] if r['id'] == bob.id][0] + project_admin_res = [r for r in result.data['results'] if r['id'] == project_admin.id][0] + org_admin_team_member_res = [r for r in result.data['results'] if r['id'] == org_admin_team_member.id][0] + project_admin_team_member_res = [r for r in result.data['results'] if r['id'] == project_admin_team_member.id][0] admin_res = [r for r in result.data['results'] if r['id'] == admin.id][0] - assert len(alice_res['summary_fields']['direct_access']) == 1 - assert len(alice_res['summary_fields']['indirect_access']) == 0 - assert len(bob_res['summary_fields']['direct_access']) == 0 - assert len(bob_res['summary_fields']['indirect_access']) == 1 + assert len(project_admin_res['summary_fields']['direct_access']) == 1 + assert len(project_admin_res['summary_fields']['indirect_access']) == 0 + assert len(org_admin_team_member_res['summary_fields']['direct_access']) == 0 + assert len(org_admin_team_member_res['summary_fields']['indirect_access']) == 1 assert len(admin_res['summary_fields']['direct_access']) == 0 assert len(admin_res['summary_fields']['indirect_access']) == 1 - alice_entry = alice_res['summary_fields']['direct_access'][0]['role'] - assert alice_entry['id'] == project.admin_role.id + project_admin_entry = project_admin_res['summary_fields']['direct_access'][0]['role'] + assert project_admin_entry['id'] == project.admin_role.id - bob_entry = bob_res['summary_fields']['indirect_access'][0]['role'] - assert bob_entry['id'] == organization.admin_role.id - assert bob_entry['team_id'] == team.id - assert bob_entry['team_name'] == team.name + project_admin_team_member_entry = project_admin_team_member_res['summary_fields']['direct_access'][0]['role'] + assert project_admin_team_member_entry['id'] == project.admin_role.id + assert project_admin_team_member_entry['team_id'] == project_admin_team.id + assert project_admin_team_member_entry['team_name'] == project_admin_team.name + + org_admin_team_member_entry = org_admin_team_member_res['summary_fields']['indirect_access'][0]['role'] + assert org_admin_team_member_entry['id'] == organization.admin_role.id + assert org_admin_team_member_entry['team_id'] == org_admin_team.id + assert org_admin_team_member_entry['team_name'] == org_admin_team.name admin_entry = admin_res['summary_fields']['indirect_access'][0]['role'] assert admin_entry['name'] == 'System Administrator'