diff --git a/awx/api/generics.py b/awx/api/generics.py index 3c1321300c..23dcce4d35 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -299,7 +299,7 @@ class SubListAPIView(ListAPIView, ParentMixin): parent = self.get_parent_object() self.check_parent_access(parent) qs = self.request.user.get_queryset(self.model).distinct() - sublist_qs = getattr(parent, self.relationship).distinct() + sublist_qs = getattrd(parent, self.relationship).distinct() return qs & sublist_qs class SubListCreateAPIView(SubListAPIView, ListCreateAPIView): diff --git a/awx/api/permissions.py b/awx/api/permissions.py index bc1447ba03..a6549082f2 100644 --- a/awx/api/permissions.py +++ b/awx/api/permissions.py @@ -117,6 +117,7 @@ class ModelAccessPermission(permissions.BasePermission): check_method = getattr(self, 'check_%s_permissions' % request.method.lower(), None) result = check_method and check_method(request, view, obj) if not result: + print('Yarr permission denied: %s %s %s' % (request.method, repr(view), repr(obj),)) # TODO: XXX: This shouldn't have been committed but anoek is sloppy, remove me after we're done fixing bugs raise PermissionDenied() return result diff --git a/awx/api/urls.py b/awx/api/urls.py index 188cc95e63..d85a9be1ea 100644 --- a/awx/api/urls.py +++ b/awx/api/urls.py @@ -274,7 +274,6 @@ v1_urls = patterns('awx.api.views', url(r'^me/$', 'user_me_list'), url(r'^dashboard/$', 'dashboard_view'), url(r'^dashboard/graphs/jobs/$', 'dashboard_jobs_graph_view'), - url(r'^dashboard/graphs/inventory/$', 'dashboard_inventory_graph_view'), url(r'^settings/', include(settings_urls)), url(r'^schedules/', include(schedule_urls)), url(r'^organizations/', include(organization_urls)), diff --git a/awx/api/views.py b/awx/api/views.py index 1a1ce35346..a9311f877a 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -288,8 +288,7 @@ class DashboardView(APIView): def get(self, request, format=None): ''' Show Dashboard Details ''' data = OrderedDict() - data['related'] = {'jobs_graph': reverse('api:dashboard_jobs_graph_view'), - 'inventory_graph': reverse('api:dashboard_inventory_graph_view')} + data['related'] = {'jobs_graph': reverse('api:dashboard_jobs_graph_view')} user_inventory = get_user_queryset(request.user, Inventory) inventory_with_failed_hosts = user_inventory.filter(hosts_with_active_failures__gt=0) user_inventory_external = user_inventory.filter(has_inventory_sources=True) @@ -435,49 +434,6 @@ class DashboardJobsGraphView(APIView): element[1]]) return Response(dashboard_data) -class DashboardInventoryGraphView(APIView): - - view_name = "Dashboard Inventory Graphs" - new_in_200 = True - - def get(self, request, format=None): - period = request.query_params.get('period', 'month') - - end_date = now() - if period == 'month': - start_date = end_date - dateutil.relativedelta.relativedelta(months=1) - start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0) - delta = dateutil.relativedelta.relativedelta(days=1) - elif period == 'week': - start_date = end_date - dateutil.relativedelta.relativedelta(weeks=1) - start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0) - delta = dateutil.relativedelta.relativedelta(days=1) - elif period == 'day': - start_date = end_date - dateutil.relativedelta.relativedelta(days=1) - start_date = start_date.replace(minute=0, second=0, microsecond=0) - delta = dateutil.relativedelta.relativedelta(hours=1) - else: - raise ParseError(u'Unknown period "%s"' % force_text(period)) - - host_stats = [] - date = start_date - while date < end_date: - next_date = date + delta - # Find all hosts that existed at end of intevral that are still - # active or were deleted after the end of interval. Slow but - # accurate; haven't yet found a better way to do it. - hosts_qs = Host.objects.filter(created__lt=next_date) - hosts_qs = hosts_qs.filter(Q(active=True) | Q(active=False, modified__gte=next_date)) - hostnames = set() - for name, active in hosts_qs.values_list('name', 'active').iterator(): - if not active: - name = re.sub(r'^_deleted_.*?_', '', name) - hostnames.add(name) - host_stats.append((time.mktime(date.timetuple()), len(hostnames))) - date = next_date - - return Response({'hosts': host_stats}) - class ScheduleList(ListAPIView): @@ -633,16 +589,16 @@ class OrganizationUsersList(SubListCreateAttachDetachAPIView): model = User serializer_class = UserSerializer parent_model = Organization - relationship = 'users' + relationship = 'member_role.members' class OrganizationAdminsList(SubListCreateAttachDetachAPIView): model = User serializer_class = UserSerializer parent_model = Organization - relationship = 'admins' + relationship = 'admin_role.members' -class OrganizationProjectsList(SubListCreateAttachDetachAPIView): +class OrganizationProjectsList(SubListCreateAPIView): model = Project serializer_class = ProjectSerializer @@ -725,7 +681,7 @@ class TeamUsersList(SubListCreateAttachDetachAPIView): model = User serializer_class = UserSerializer parent_model = Team - relationship = 'users' + relationship = 'member_role.members' class TeamRolesList(SubListCreateAttachDetachAPIView): @@ -1481,7 +1437,7 @@ class GroupAllHostsList(SubListAPIView): def get_queryset(self): parent = self.get_parent_object() self.check_parent_access(parent) - qs = self.request.user.get_queryset(self.model) + qs = self.request.user.get_queryset(self.model).distinct() # need distinct for '&' operator sublist_qs = parent.all_hosts.distinct() return qs & sublist_qs diff --git a/awx/main/access.py b/awx/main/access.py index 9d67dd5295..b44c8fa577 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -7,10 +7,9 @@ import sys import logging # Django -from django.db.models import F, Q +from django.db.models import Q from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType -from django.db.models.aggregates import Max # Django REST Framework from rest_framework.exceptions import ParseError, PermissionDenied @@ -67,15 +66,7 @@ def user_admin_role(self): return Role.objects.get(content_type=ContentType.objects.get_for_model(User), object_id=self.id) def user_accessible_objects(user, permissions): - content_type = ContentType.objects.get_for_model(User) - qs = RolePermission.objects.filter( - content_type=content_type, - role__ancestors__members=user - ) - for perm in permissions: - qs = qs.annotate(**{'max_' + perm: Max(perm)}) - qs = qs.filter(**{'max_' + perm: int(permissions[perm])}) - return qs + return ResourceMixin._accessible_objects(User, user, permissions) def user_accessible_by(instance, user, permissions): perms = get_user_permissions_on_resource(instance, user) @@ -222,7 +213,7 @@ class UserAccess(BaseAccess): model = User def get_queryset(self): - qs = self.model.accessible_objects(self.user, {'read':True}) + qs = User.accessible_objects(self.user, {'read':True}) return qs def can_add(self, data): @@ -639,7 +630,7 @@ class ProjectAccess(BaseAccess): def can_add(self, data): if self.user.is_superuser: return True - qs = Organization.accessible_objects(self.uesr, ALL_PERMISSIONS) + qs = Organization.accessible_objects(self.user, ALL_PERMISSIONS) return bool(qs.count() > 0) def can_change(self, obj, data): @@ -813,47 +804,11 @@ class JobAccess(BaseAccess): qs = qs.prefetch_related('unified_job_template') if self.user.is_superuser: return qs + credential_ids = self.user.get_queryset(Credential) - base_qs = qs.filter( + return qs.filter( credential_id__in=credential_ids, - ) - org_admin_ids = base_qs.filter( - Q(project__organizations__admins__in=[self.user]) | - (Q(project__isnull=True) & Q(job_type=PERM_INVENTORY_SCAN) & Q(inventory__organization__admins__in=[self.user])) - ) - - allowed_deploy = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY] - allowed_check = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY, PERM_INVENTORY_CHECK] - team_ids = Team.objects.filter(users__in=[self.user]) - - # TODO: I think the below queries can be combined - deploy_permissions_ids = Permission.objects.filter( - Q(user=self.user) | Q(team__in=team_ids), - permission_type__in=allowed_deploy, - ) - check_permissions_ids = Permission.objects.filter( - Q(user=self.user) | Q(team__in=team_ids), - permission_type__in=allowed_check, - ) - - perm_deploy_ids = base_qs.filter( - job_type=PERM_INVENTORY_DEPLOY, - inventory__permissions__in=deploy_permissions_ids, - project__permissions__in=deploy_permissions_ids, - inventory__permissions__pk=F('project__permissions__pk'), - ) - - perm_check_ids = base_qs.filter( - job_type=PERM_INVENTORY_CHECK, - inventory__permissions__in=check_permissions_ids, - project__permissions__in=check_permissions_ids, - inventory__permissions__pk=F('project__permissions__pk'), - ) - - return base_qs.filter( - Q(id__in=org_admin_ids) | - Q(id__in=perm_deploy_ids) | - Q(id__in=perm_check_ids) + job_template__in=JobTemplate.accessible_objects(self.user, {'read': True}) ) def can_add(self, data): @@ -938,21 +893,11 @@ class AdHocCommandAccess(BaseAccess): return qs credential_ids = set(self.user.get_queryset(Credential).values_list('id', flat=True)) - team_ids = set(Team.objects.filter( users__in=[self.user]).values_list('id', flat=True)) - - permission_ids = set(Permission.objects.filter( - Q(user=self.user) | Q(team__in=team_ids), - permission_type__in=PERMISSION_TYPES_ALLOWING_INVENTORY_READ, - run_ad_hoc_commands=True, - ).values_list('id', flat=True)) - - inventory_qs = self.user.get_queryset(Inventory) - inventory_qs = inventory_qs.filter(Q(permissions__in=permission_ids) | Q(organization__admins__in=[self.user])) - inventory_ids = set(inventory_qs.values_list('id', flat=True)) + inventory_qs = Inventory.accessible_objects(self.user, {'read': True, 'execute': True}) qs = qs.filter( credential_id__in=credential_ids, - inventory_id__in=inventory_ids, + inventory__in=inventory_qs, ) return qs @@ -1183,7 +1128,8 @@ class ScheduleAccess(BaseAccess): if self.user.is_superuser: return True if obj and obj.unified_job_template: - return obj.unified_job_template.accessible_by(self.user, {'read':True}) + job_class = obj.unified_job_template + return self.user.can_access(type(job_class), 'read', obj.unified_job_template) else: return False @@ -1193,7 +1139,7 @@ class ScheduleAccess(BaseAccess): pk = get_pk_from_dict(data, 'unified_job_template') obj = get_object_or_400(UnifiedJobTemplate, pk=pk) if obj: - return obj.accessible_by(self.user, {'read':True, 'update':True, 'write':True}) + return self.user.can_access(type(obj), 'change', obj, None) else: return False @@ -1201,7 +1147,8 @@ class ScheduleAccess(BaseAccess): if self.user.is_superuser: return True if obj and obj.unified_job_template: - return obj.unified_job_template.accessible_by(self.user, {'read':True, 'update':True, 'write':True}) + job_class = obj.unified_job_template + return self.user.can_access(type(job_class), 'change', job_class, None) else: return False @@ -1209,7 +1156,8 @@ class ScheduleAccess(BaseAccess): if self.user.is_superuser: return True if obj and obj.unified_job_template: - return obj.unified_job_template.accessible_by(self.user, {'read':True, 'update':True, 'write':True}) + job_class = obj.unified_job_template + return self.user.can_access(type(job_class), 'change', job_class, None) else: return False @@ -1287,10 +1235,6 @@ class ActivityStreamAccess(BaseAccess): #Project Update Filter qs.filter(project_update__project__in=project_qs) - #Permission Filter - permission_qs = self.user.get_queryset(Permission) - qs.filter(permission__in=permission_qs) - #Job Template Filter jobtemplate_qs = self.user.get_queryset(JobTemplate) qs.filter(job_template__in=jobtemplate_qs) diff --git a/awx/main/fields.py b/awx/main/fields.py index 22210124f0..513263ed9f 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -243,18 +243,26 @@ class ImplicitRoleField(models.ForeignKey): def _calc_original_parents(self, instance): if not hasattr(self, '__original_parent_roles'): - setattr(self, '__original_parent_roles', []) # do not just self.__original_parent_roles=[], it's not the same here - paths = self.parent_role if type(self.parent_role) is list else [self.parent_role] - original_parent_roles = set() - for path in paths: - if path.startswith("singleton:"): - parents = [Role.singleton(path[10:])] - else: - parents = resolve_role_field(instance, path) - for parent in parents: - original_parent_roles.add(parent) + setattr(self, '__original_parent_roles', set()) # do not just self.__original_parent_roles=[], it's not the same here, apparently. + # NOTE: The above setattr is required to be called bofore + # _resolve_parent_roles because we can end up recursing, so the enclosing + # if not hasattr protects against this. + original_parent_roles = self._resolve_parent_roles(instance) setattr(self, '__original_parent_roles', original_parent_roles) + def _resolve_parent_roles(self, instance): + paths = self.parent_role if type(self.parent_role) is list else [self.parent_role] + parent_roles = set() + for path in paths: + if path.startswith("singleton:"): + parents = [Role.singleton(path[10:])] + else: + parents = resolve_role_field(instance, path) + for parent in parents: + parent_roles.add(parent) + return parent_roles + + def _post_save(self, instance, created, *args, **kwargs): # Ensure that our field gets initialized after our first save this_role = getattr(instance, self.name) @@ -269,16 +277,8 @@ class ImplicitRoleField(models.ForeignKey): self._calc_original_parents(instance) return - paths = self.parent_role if type(self.parent_role) is list else [self.parent_role] original_parents = getattr(self, '__original_parent_roles') - new_parents = set() - for path in paths: - if path.startswith("singleton:"): - parents = [Role.singleton(path[10:])] - else: - parents = resolve_role_field(instance, path) - for parent in parents: - new_parents.add(parent) + new_parents = self._resolve_parent_roles(instance) with batch_role_ancestor_rebuilding(): for role in original_parents - new_parents: diff --git a/awx/main/models/__init__.py b/awx/main/models/__init__.py index a8b7467db2..5c8f4ec3af 100644 --- a/awx/main/models/__init__.py +++ b/awx/main/models/__init__.py @@ -3,6 +3,7 @@ # Django from django.conf import settings # noqa +from django.contrib.contenttypes.fields import GenericRelation # AWX from awx.main.models.base import * # noqa @@ -38,11 +39,13 @@ _PythonSerializer.handle_m2m_field = _new_handle_m2m_field from django.contrib.auth.models import User # noqa from awx.main.access import * # noqa + User.add_to_class('get_queryset', get_user_queryset) User.add_to_class('can_access', check_user_access) User.add_to_class('accessible_by', user_accessible_by) User.add_to_class('accessible_objects', user_accessible_objects) User.add_to_class('admin_role', user_admin_role) +User.add_to_class('role_permissions', GenericRelation('main.RolePermission')) # Import signal handlers only after models have been defined. import awx.main.signals # noqa diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 50d0fd58bd..0283a5c70c 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -111,10 +111,12 @@ class Inventory(CommonModel, ResourceMixin): updater_role = ImplicitRoleField( role_name='Inventory Updater', role_description='May update the inventory', + permissions = {'read': True, 'update': True} ) executor_role = ImplicitRoleField( role_name='Inventory Executor', role_description='May execute jobs against this inventory', + permissions = {'read': True, 'execute': True} ) def get_absolute_url(self): @@ -545,6 +547,7 @@ class Group(CommonModelNameNotUnique, ResourceMixin): @transaction.atomic def delete_recursive(self): from awx.main.utils import ignore_inventory_computed_fields + from awx.main.tasks import update_inventory_computed_fields from awx.main.signals import disable_activity_stream @@ -1051,7 +1054,7 @@ class InventorySourceOptions(BaseModel): return ','.join(choices) -class InventorySource(UnifiedJobTemplate, InventorySourceOptions, ResourceMixin): +class InventorySource(UnifiedJobTemplate, InventorySourceOptions): class Meta: app_label = 'main' diff --git a/awx/main/models/mixins.py b/awx/main/models/mixins.py index 968fb3712b..639611fbca 100644 --- a/awx/main/models/mixins.py +++ b/awx/main/models/mixins.py @@ -32,7 +32,10 @@ class ResourceMixin(models.Model): performant to resolve the resource in question then call `myresource.get_permissions(user)`. ''' + return ResourceMixin._accessible_objects(cls, user, permissions) + @staticmethod + def _accessible_objects(cls, user, permissions): qs = cls.objects.filter( role_permissions__role__ancestors__members=user ) diff --git a/awx/main/tests/old/ad_hoc.py b/awx/main/tests/old/ad_hoc.py index 95b8130664..3b3f24391e 100644 --- a/awx/main/tests/old/ad_hoc.py +++ b/awx/main/tests/old/ad_hoc.py @@ -459,30 +459,19 @@ class AdHocCommandApiTest(BaseAdHocCommandTest): self.check_get_list(url, 'nobody', qs) self.check_get_list(url, None, qs, expect=401) - # Explicitly give other user admin permission on the inventory (still + # Explicitly give other user updater permission on the inventory (still # not allowed to run ad hoc commands). - user_perm_url = reverse('api:user_permissions_list', args=(self.other_django_user.pk,)) - user_perm_data = { - 'name': 'Allow Other to Admin Inventory', - 'inventory': self.inventory.pk, - 'permission_type': 'admin', - } + user_roles_list_url = reverse('api:user_roles_list', args=(self.other_django_user.pk,)) with self.current_user('admin'): - response = self.post(user_perm_url, user_perm_data, expect=201) - user_perm_id = response['id'] + response = self.post(user_roles_list_url, {"id": self.inventory.updater_role.id}, expect=204) with self.current_user('other'): self.run_test_ad_hoc_command(expect=403) self.check_get_list(url, 'other', qs) - # Update permission to allow other user to run ad hoc commands. Fails + # Add executor role permissions to other. Fails # when other user can't read credential. - user_perm_url = reverse('api:permission_detail', args=(user_perm_id,)) - user_perm_data.update({ - 'name': 'Allow Other to Admin Inventory and Run Ad Hoc Commands', - 'run_ad_hoc_commands': True, - }) with self.current_user('admin'): - response = self.patch(user_perm_url, user_perm_data, expect=200) + response = self.post(user_roles_list_url, {"id": self.inventory.executor_role.id}, expect=204) with self.current_user('other'): self.run_test_ad_hoc_command(expect=403) @@ -496,15 +485,9 @@ class AdHocCommandApiTest(BaseAdHocCommandTest): self.check_get_list(url, 'other', qs) # Explicitly give nobody user read permission on the inventory. - user_perm_url = reverse('api:user_permissions_list', args=(self.nobody_django_user.pk,)) - user_perm_data = { - 'name': 'Allow Nobody to Read Inventory', - 'inventory': self.inventory.pk, - 'permission_type': 'read', - } + nobody_roles_list_url = reverse('api:user_roles_list', args=(self.nobody_django_user.pk,)) with self.current_user('admin'): - response = self.post(user_perm_url, user_perm_data, expect=201) - user_perm_id = response['id'] + response = self.post(nobody_roles_list_url, {"id": self.inventory.auditor_role.id}, expect=204) with self.current_user('nobody'): self.run_test_ad_hoc_command(credential=other_cred.pk, expect=403) self.check_get_list(url, 'other', qs) @@ -520,13 +503,8 @@ class AdHocCommandApiTest(BaseAdHocCommandTest): # Give the nobody user the run_ad_hoc_commands flag, and can now see # the one ad hoc command previously run. - user_perm_url = reverse('api:permission_detail', args=(user_perm_id,)) - user_perm_data.update({ - 'name': 'Allow Nobody to Read Inventory and Run Ad Hoc Commands', - 'run_ad_hoc_commands': True, - }) with self.current_user('admin'): - response = self.patch(user_perm_url, user_perm_data, expect=200) + response = self.post(nobody_roles_list_url, {"id": self.inventory.executor_role.id}, expect=204) qs = AdHocCommand.objects.filter(credential_id=nobody_cred.pk) self.assertEqual(qs.count(), 1) self.check_get_list(url, 'nobody', qs) @@ -947,7 +925,7 @@ class AdHocCommandApiTest(BaseAdHocCommandTest): self.delete(url, expect=405) with self.current_user('normal'): response = self.get(url, expect=200) - #self.assertEqual(response['count'], 1) # FIXME: Enable once activity stream RBAC is fixed. + self.assertEqual(response['count'], 1) self.post(url, {}, expect=405) self.put(url, {}, expect=405) self.patch(url, {}, expect=405) @@ -1026,29 +1004,17 @@ class AdHocCommandApiTest(BaseAdHocCommandTest): # Create another unrelated inventory permission with run_ad_hoc_commands # set; this tests an edge case in the RBAC query where we'll return # can_run_ad_hoc_commands = True when we shouldn't. - nobody_perm_url = reverse('api:user_permissions_list', args=(self.nobody_django_user.pk,)) - nobody_perm_data = { - 'name': 'Allow Nobody to Read Inventory', - 'inventory': self.inventory.pk, - 'permission_type': 'read', - 'run_ad_hoc_commands': True, - } + nobody_roles_list_url = reverse('api:user_roles_list', args=(self.nobody_django_user.pk,)) with self.current_user('admin'): - response = self.post(nobody_perm_url, nobody_perm_data, expect=201) + response = self.post(nobody_roles_list_url, {"id": self.inventory.executor_role.id}, expect=204) # Create a credential for the other user and explicitly give other # user admin permission on the inventory (still not allowed to run ad # hoc commands; can get the list but can't see any items). other_cred = self.create_test_credential(user=self.other_django_user) - user_perm_url = reverse('api:user_permissions_list', args=(self.other_django_user.pk,)) - user_perm_data = { - 'name': 'Allow Other to Admin Inventory', - 'inventory': self.inventory.pk, - 'permission_type': 'admin', - } + user_roles_list_url = reverse('api:user_roles_list', args=(self.other_django_user.pk,)) with self.current_user('admin'): - response = self.post(user_perm_url, user_perm_data, expect=201) - user_perm_id = response['id'] + response = self.post(user_roles_list_url, {"id": self.inventory.updater_role.id}, expect=204) with self.current_user('other'): response = self.get(url, expect=200) self.assertEqual(response['count'], 0) @@ -1058,13 +1024,8 @@ class AdHocCommandApiTest(BaseAdHocCommandTest): # Update permission to allow other user to run ad hoc commands. Can # only see his own ad hoc commands (because of credential permission). - user_perm_url = reverse('api:permission_detail', args=(user_perm_id,)) - user_perm_data.update({ - 'name': 'Allow Other to Admin Inventory and Run Ad Hoc Commands', - 'run_ad_hoc_commands': True, - }) with self.current_user('admin'): - response = self.patch(user_perm_url, user_perm_data, expect=200) + response = self.post(user_roles_list_url, {"id": self.inventory.executor_role.id}, expect=204) with self.current_user('other'): response = self.get(url, expect=200) self.assertEqual(response['count'], 0) diff --git a/awx/main/tests/old/inventory.py b/awx/main/tests/old/inventory.py index dda615e79e..5c4b22e7ab 100644 --- a/awx/main/tests/old/inventory.py +++ b/awx/main/tests/old/inventory.py @@ -2,7 +2,6 @@ # All Rights Reserved. # Python -import datetime import glob import json import os @@ -14,7 +13,6 @@ import time from django.conf import settings from django.core.urlresolvers import reverse from django.test.utils import override_settings -from django.utils.timezone import now # AWX from awx.main.models import * # noqa @@ -47,9 +45,9 @@ class InventoryTest(BaseTest): self.setup_instances() self.setup_users() self.organizations = self.make_organizations(self.super_django_user, 3) - self.organizations[0].deprecated_admins.add(self.normal_django_user) - self.organizations[0].deprecated_users.add(self.other_django_user) - self.organizations[0].deprecated_users.add(self.normal_django_user) + self.organizations[0].admin_role.members.add(self.normal_django_user) + self.organizations[0].member_role.members.add(self.other_django_user) + self.organizations[0].member_role.members.add(self.normal_django_user) self.inventory_a = Inventory.objects.create(name='inventory-a', description='foo', organization=self.organizations[0]) self.inventory_b = Inventory.objects.create(name='inventory-b', description='bar', organization=self.organizations[1]) @@ -58,10 +56,7 @@ class InventoryTest(BaseTest): # create a permission here on the 'other' user so they have edit access on the org # we may add another permission type later. - self.perm_read = Permission.objects.create( - inventory = self.inventory_b, - user = self.other_django_user, - permission_type = 'read') + self.inventory_b.auditor_role.members.add(self.other_django_user) def tearDown(self): super(InventoryTest, self).tearDown() @@ -78,11 +73,11 @@ class InventoryTest(BaseTest): self.check_get_list(url, self.super_django_user, qs) # an org admin can list inventories but is filtered to what he adminsters - normal_qs = qs.filter(organization__deprecated_admins__in=[self.normal_django_user]) + normal_qs = qs.filter(organization__admin_role__members=self.normal_django_user) self.check_get_list(url, self.normal_django_user, normal_qs) # a user who is on a team who has a read permissions on an inventory can see filtered inventories - other_qs = qs.filter(permissions__user__in=[self.other_django_user]) + other_qs = Inventory.accessible_objects(self.other_django_user, {'read': True}).distinct() self.check_get_list(url, self.other_django_user, other_qs) # a regular user not part of anything cannot see any inventories @@ -263,24 +258,20 @@ class InventoryTest(BaseTest): def test_inventory_access_deleted_permissions(self): temp_org = self.make_organizations(self.super_django_user, 1)[0] - temp_org.deprecated_admins.add(self.normal_django_user) - temp_org.deprecated_users.add(self.other_django_user) - temp_org.deprecated_users.add(self.normal_django_user) + temp_org.admin_role.members.add(self.normal_django_user) + temp_org.member_role.members.add(self.other_django_user) + temp_org.member_role.members.add(self.normal_django_user) temp_inv = temp_org.inventories.create(name='Delete Org Inventory') temp_inv.groups.create(name='Delete Org Inventory Group') - temp_perm_read = Permission.objects.create( - inventory = temp_inv, - user = self.other_django_user, - permission_type = 'read' - ) + temp_inv.auditor_role.members.add(self.other_django_user) reverse('api:organization_detail', args=(temp_org.pk,)) inventory_detail = reverse('api:inventory_detail', args=(temp_inv.pk,)) - permission_detail = reverse('api:permission_detail', args=(temp_perm_read.pk,)) + auditor_role_users_list = reverse('api:role_users_list', args=(temp_inv.auditor_role.pk,)) self.get(inventory_detail, expect=200, auth=self.get_other_credentials()) - self.delete(permission_detail, expect=204, auth=self.get_super_credentials()) + self.post(auditor_role_users_list, data={'disassociate': True, "id": self.other_django_user.id}, expect=204, auth=self.get_super_credentials()) self.get(inventory_detail, expect=403, auth=self.get_other_credentials()) def test_create_inventory_script(self): @@ -335,10 +326,8 @@ class InventoryTest(BaseTest): self.post(hosts, data=new_host_b, expect=403, auth=self.get_nobody_credentials()) # a normal user with inventory edit permissions (on any inventory) can create hosts - Permission.objects.create( - user = self.other_django_user, - inventory = Inventory.objects.get(pk=inv.pk), - permission_type = PERM_INVENTORY_WRITE) + + inv.admin_role.members.add(self.other_django_user) host_data3 = self.post(hosts, data=new_host_c, expect=201, auth=self.get_other_credentials()) # Port should be split out into host variables, other variables kept intact. @@ -393,11 +382,6 @@ class InventoryTest(BaseTest): # a normal user with inventory edit permissions (on any inventory) can create groups # already done! - #edit_perm = Permission.objects.create( - # user = self.other_django_user, - # inventory = Inventory.objects.get(pk=inv.pk), - # permission_type = PERM_INVENTORY_WRITE - #) self.post(groups, data=new_group_c, expect=201, auth=self.get_other_credentials()) # hostnames must be unique inside an organization @@ -417,9 +401,10 @@ class InventoryTest(BaseTest): del_children_url = reverse('api:group_children_list', args=(del_group.pk,)) nondel_url = reverse('api:group_detail', args=(Group.objects.get(name='nondel').pk,)) + assert(inv.accessible_by(self.normal_django_user, {'read': True})) del_group.delete() nondel_detail = self.get(nondel_url, expect=200, auth=self.get_normal_credentials()) - self.post(del_children_url, data=nondel_detail, expect=403, auth=self.get_normal_credentials()) + self.post(del_children_url, data=nondel_detail, expect=400, auth=self.get_normal_credentials()) ################################################# @@ -656,11 +641,7 @@ class InventoryTest(BaseTest): gx5 = Group.objects.create(name='group-X5', inventory=inva) gx5.parents.add(gx4) - Permission.objects.create( - inventory = inva, - user = self.other_django_user, - permission_type = PERM_INVENTORY_WRITE - ) + inva.admin_role.members.add(self.other_django_user) # data used for testing listing all hosts that are transitive members of a group g2 = Group.objects.get(name='web4') @@ -1115,59 +1096,6 @@ class InventoryTest(BaseTest): self.assertEqual(response['hosts']['total'], 8) self.assertEqual(response['hosts']['failed'], 8) - def test_dashboard_inventory_graph_view(self): - url = reverse('api:dashboard_inventory_graph_view') - # Test with zero hosts. - with self.current_user(self.super_django_user): - response = self.get(url) - self.assertFalse(sum([x[1] for x in response['hosts']])) - # Create hosts in inventory_a, with created one day apart, and check - # the time series results. - dtnow = now() - hostnames = list('abcdefg') - for x in xrange(len(hostnames) - 1, -1, -1): - hostname = hostnames[x] - created = dtnow - datetime.timedelta(days=x, seconds=60) - self.inventory_a.hosts.create(name=hostname, created=created) - with self.current_user(self.super_django_user): - response = self.get(url) - for n, d in enumerate(reversed(response['hosts'])): - self.assertEqual(d[1], max(len(hostnames) - n, 0)) - # Create more hosts a day apart in inventory_b and check the time - # series results. - hostnames2 = list('hijklmnop') - for x in xrange(len(hostnames2) - 1, -1, -1): - hostname = hostnames2[x] - created = dtnow - datetime.timedelta(days=x, seconds=120) - self.inventory_b.hosts.create(name=hostname, created=created) - with self.current_user(self.super_django_user): - response = self.get(url) - for n, d in enumerate(reversed(response['hosts'])): - self.assertEqual(d[1], max(len(hostnames2) - n, 0) + max(len(hostnames) - n, 0)) - # Now create some hosts in inventory_a with the same hostnames already - # used in inventory_b; duplicate hostnames should only be counted the - # first time they were seen in inventory_b. - hostnames3 = list('lmnop') - for x in xrange(len(hostnames3) - 1, -1, -1): - hostname = hostnames3[x] - created = dtnow - datetime.timedelta(days=x, seconds=180) - self.inventory_a.hosts.create(name=hostname, created=created) - with self.current_user(self.super_django_user): - response = self.get(url) - for n, d in enumerate(reversed(response['hosts'])): - self.assertEqual(d[1], max(len(hostnames2) - n, 0) + max(len(hostnames) - n, 0)) - # Delete recently added hosts and verify the count drops. - hostnames4 = list('defg') - for host in Host.objects.filter(name__in=hostnames4): - host.delete() - with self.current_user(self.super_django_user): - response = self.get(url) - for n, d in enumerate(reversed(response['hosts'])): - count = max(len(hostnames2) - n, 0) + max(len(hostnames) - n, 0) - if n == 0: - count -= 4 - self.assertEqual(d[1], count) - @override_settings(CELERY_ALWAYS_EAGER=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True, @@ -1181,9 +1109,9 @@ class InventoryUpdatesTest(BaseTransactionTest): self.setup_instances() self.setup_users() self.organization = self.make_organizations(self.super_django_user, 1)[0] - self.organization.deprecated_admins.add(self.normal_django_user) - self.organization.deprecated_users.add(self.other_django_user) - self.organization.deprecated_users.add(self.normal_django_user) + self.organization.admin_role.members.add(self.normal_django_user) + self.organization.member_role.members.add(self.other_django_user) + self.organization.member_role.members.add(self.normal_django_user) self.inventory = self.organization.inventories.create(name='Cloud Inventory') self.group = self.inventory.groups.create(name='Cloud Group') self.inventory2 = self.organization.inventories.create(name='Cloud Inventory 2') @@ -1256,7 +1184,7 @@ class InventoryUpdatesTest(BaseTransactionTest): url = reverse('api:inventory_source_hosts_list', args=(inventory_source.pk,)) response = self.get(url, expect=200) self.assertNotEqual(response['count'], 0) - for host in inventory.hosts: + for host in inventory.hosts.all(): source_pks = host.inventory_sources.values_list('pk', flat=True) self.assertTrue(inventory_source.pk in source_pks) self.assertTrue(host.has_inventory_sources) @@ -1270,7 +1198,7 @@ class InventoryUpdatesTest(BaseTransactionTest): url = reverse('api:host_inventory_sources_list', args=(host.pk,)) response = self.get(url, expect=200) self.assertNotEqual(response['count'], 0) - for group in inventory.groups: + for group in inventory.groups.all(): source_pks = group.inventory_sources.values_list('pk', flat=True) self.assertTrue(inventory_source.pk in source_pks) self.assertTrue(group.has_inventory_sources) @@ -1293,7 +1221,7 @@ class InventoryUpdatesTest(BaseTransactionTest): self.assertNotEqual(response['count'], 0) # Try to set a source on a child group that was imported. Should not # be allowed. - for group in inventory_source.group.children: + for group in inventory_source.group.children.all(): inv_src_2 = group.inventory_source inv_src_url2 = reverse('api:inventory_source_detail', args=(inv_src_2.pk,)) with self.current_user(self.super_django_user): @@ -1540,16 +1468,9 @@ class InventoryUpdatesTest(BaseTransactionTest): self.post(inv_src_update_url, {}, expect=403) # If given read permission to the inventory, other user should be able # to see the inventory source and update view, but not start an update. - other_perms_url = reverse('api:user_permissions_list', - args=(self.other_django_user.pk,)) - other_perms_data = { - 'name': 'read only inventory permission for other', - 'user': self.other_django_user.pk, - 'inventory': self.inventory.pk, - 'permission_type': 'read', - } + user_roles_list_url = reverse('api:user_roles_list', args=(self.other_django_user.pk,)) with self.current_user(self.super_django_user): - self.post(other_perms_url, other_perms_data, expect=201) + self.post(user_roles_list_url, {"id": self.inventory.auditor_role.id}, expect=204) with self.current_user(self.other_django_user): self.get(inv_src_url, expect=200) response = self.get(inv_src_update_url, expect=200) @@ -1557,14 +1478,8 @@ class InventoryUpdatesTest(BaseTransactionTest): self.post(inv_src_update_url, {}, expect=403) # Once given write permission, the normal user is able to update the # inventory source. - other_perms_data = { - 'name': 'read-write inventory permission for other', - 'user': self.other_django_user.pk, - 'inventory': self.inventory.pk, - 'permission_type': 'write', - } with self.current_user(self.super_django_user): - self.post(other_perms_url, other_perms_data, expect=201) + self.post(user_roles_list_url, {"id": self.inventory.admin_role.id}, expect=204) with self.current_user(self.other_django_user): self.get(inv_src_url, expect=200) response = self.get(inv_src_update_url, expect=200) @@ -1649,7 +1564,7 @@ class InventoryUpdatesTest(BaseTransactionTest): inventory_source.overwrite = True inventory_source.save() self.check_inventory_source(inventory_source, initial=False) - for host in self.inventory.hosts: + for host in self.inventory.hosts.all(): self.assertEqual(host.variables_dict['ec2_instance_type'], instance_type) # Try invalid instance filters that should be ignored: diff --git a/awx/main/tests/old/licenses.py b/awx/main/tests/old/licenses.py index a236ee458f..136c3ce8a5 100644 --- a/awx/main/tests/old/licenses.py +++ b/awx/main/tests/old/licenses.py @@ -19,7 +19,7 @@ class LicenseTests(BaseTest): self.setup_users() u = self.super_django_user org = Organization.objects.create(name='o1', created_by=u) - org.deprecated_admins.add(self.normal_django_user) + org.admin_role.members.add(self.normal_django_user) self.inventory = Inventory.objects.create(name='hi', organization=org, created_by=u) Host.objects.create(name='a1', inventory=self.inventory, created_by=u) Host.objects.create(name='a2', inventory=self.inventory, created_by=u) diff --git a/awx/main/tests/old/organizations.py b/awx/main/tests/old/organizations.py index c5b1569513..bee2d3182c 100644 --- a/awx/main/tests/old/organizations.py +++ b/awx/main/tests/old/organizations.py @@ -88,12 +88,12 @@ class OrganizationsTest(BaseTest): # nobody_user is a user not a member of any organizations for x in self.organizations: - x.deprecated_admins.add(self.super_django_user) - x.deprecated_users.add(self.super_django_user) - x.deprecated_users.add(self.other_django_user) + x.admin_role.members.add(self.super_django_user) + x.member_role.members.add(self.super_django_user) + x.member_role.members.add(self.other_django_user) - self.organizations[0].deprecated_users.add(self.normal_django_user) - self.organizations[1].deprecated_admins.add(self.normal_django_user) + self.organizations[0].member_role.members.add(self.normal_django_user) + self.organizations[1].admin_role.members.add(self.normal_django_user) def test_get_organization_list(self): url = reverse('api:organization_list') diff --git a/awx/main/tests/old/projects.py b/awx/main/tests/old/projects.py index 04de530dad..254dd5216d 100644 --- a/awx/main/tests/old/projects.py +++ b/awx/main/tests/old/projects.py @@ -75,10 +75,10 @@ class ProjectsTest(BaseTransactionTest): for x in self.organizations: # NOTE: superuser does not have to be explicitly added to admin group # x.admins.add(self.super_django_user) - x.deprecated_users.add(self.super_django_user) + x.member_role.members.add(self.super_django_user) - self.organizations[0].deprecated_users.add(self.normal_django_user) - self.organizations[1].deprecated_admins.add(self.normal_django_user) + self.organizations[0].member_role.members.add(self.normal_django_user) + self.organizations[1].admin_role.members.add(self.normal_django_user) self.team1 = Team.objects.create( name = 'team1', organization = self.organizations[0] @@ -97,8 +97,8 @@ class ProjectsTest(BaseTransactionTest): self.team2.projects.add(self.projects[5]) self.team1.save() self.team2.save() - self.team1.deprecated_users.add(self.normal_django_user) - self.team2.deprecated_users.add(self.other_django_user) + self.team1.member_role.members.add(self.normal_django_user) + self.team2.member_role.members.add(self.other_django_user) def test_playbooks(self): def write_test_file(project, name, content): @@ -312,7 +312,7 @@ class ProjectsTest(BaseTransactionTest): # Verify that creatorship doesn't imply access if access is removed a_new_proj = self.make_project(created_by=self.other_django_user, playbook_content=TEST_PLAYBOOK) - self.organizations[0].deprecated_admins.add(self.other_django_user) + self.organizations[0].admin_role.members.add(self.other_django_user) self.organizations[0].projects.add(a_new_proj) proj_detail = reverse('api:project_detail', args=(a_new_proj.pk,)) self.patch(proj_detail, data=dict(description="test"), expect=200, auth=self.get_other_credentials()) @@ -337,7 +337,7 @@ class ProjectsTest(BaseTransactionTest): self.assertEquals(got['url'], reverse('api:team_detail', args=(self.team1.pk,))) got = self.get(team1, expect=200, auth=self.get_normal_credentials()) got = self.get(team1, expect=403, auth=self.get_other_credentials()) - self.team1.deprecated_users.add(User.objects.get(username='other')) + self.team1.member_role.members.add(User.objects.get(username='other')) self.team1.save() got = self.get(team1, expect=200, auth=self.get_other_credentials()) got = self.get(team1, expect=403, auth=self.get_nobody_credentials()) @@ -422,7 +422,7 @@ class ProjectsTest(BaseTransactionTest): team = Team.objects.filter( organization__pk=self.organizations[1].pk)[0] team_users = reverse('api:team_users_list', args=(team.pk,)) for x in team.deprecated_users.all(): - team.deprecated_users.remove(x) + team.member_role.members.remove(x) team.save() # can list uses on teams @@ -787,7 +787,7 @@ class ProjectsTest(BaseTransactionTest): # User is still a team member self.get(reverse('api:project_detail', args=(project.pk,)), expect=200, auth=self.get_other_credentials()) - team.deprecated_users.remove(self.other_django_user) + team.member_role.members.remove(self.other_django_user) # User is no longer a team member and has no permissions self.get(reverse('api:project_detail', args=(project.pk,)), expect=403, auth=self.get_other_credentials()) @@ -1351,7 +1351,7 @@ class ProjectUpdatesTest(BaseTransactionTest): 'scm_url': scm_url, } org = self.make_organizations(self.super_django_user, 1)[0] - org.deprecated_admins.add(self.normal_django_user) + org.admin_role.members.add(self.normal_django_user) with self.current_user(self.super_django_user): del_proj = self.post(projects_url, project_data, expect=201) del_proj = Project.objects.get(pk=del_proj["id"]) diff --git a/awx/main/tests/old/schedules.py b/awx/main/tests/old/schedules.py index fa95892eb8..1abcdc5c26 100644 --- a/awx/main/tests/old/schedules.py +++ b/awx/main/tests/old/schedules.py @@ -54,12 +54,12 @@ class ScheduleTest(BaseTest): self.setup_instances() self.setup_users() self.organizations = self.make_organizations(self.super_django_user, 2) - self.organizations[0].deprecated_admins.add(self.normal_django_user) - self.organizations[0].deprecated_users.add(self.other_django_user) - self.organizations[0].deprecated_users.add(self.normal_django_user) + self.organizations[0].admin_role.members.add(self.normal_django_user) + self.organizations[0].member_role.members.add(self.other_django_user) + self.organizations[0].member_role.members.add(self.normal_django_user) self.diff_org_user = self.make_user('fred') - self.organizations[1].deprecated_users.add(self.diff_org_user) + self.organizations[1].member_role.members.add(self.diff_org_user) self.cloud_source = Credential.objects.create(kind='awx', user=self.super_django_user, username='Dummy', password='Dummy') @@ -71,11 +71,7 @@ class ScheduleTest(BaseTest): self.first_inventory_source.source = 'ec2' self.first_inventory_source.save() - Permission.objects.create( - inventory = self.first_inventory, - user = self.other_django_user, - permission_type = 'read' - ) + self.first_inventory.auditor_role.members.add(self.other_django_user) self.second_inventory = Inventory.objects.create(name='test_inventory_2', description='for org 0', organization=self.organizations[0]) self.second_inventory.hosts.create(name='host_2') @@ -139,11 +135,7 @@ class ScheduleTest(BaseTest): self.post(first_url, data=unauth_schedule, expect=403) #give normal user write access and then they can post - Permission.objects.create( - user = self.other_django_user, - inventory = self.first_inventory, - permission_type = PERM_INVENTORY_WRITE - ) + self.first_inventory.admin_role.members.add(self.other_django_user) auth_schedule = unauth_schedule with self.current_user(self.other_django_user): self.post(first_url, data=auth_schedule, expect=201) diff --git a/awx/main/tests/old/users.py b/awx/main/tests/old/users.py index 75377037da..1068354349 100644 --- a/awx/main/tests/old/users.py +++ b/awx/main/tests/old/users.py @@ -100,7 +100,7 @@ class AuthTokenProxyTest(BaseTest): self.setup_users() self.setup_instances() self.organizations = self.make_organizations(self.super_django_user, 2) - self.organizations[0].deprecated_admins.add(self.normal_django_user) + self.organizations[0].admin_role.members.add(self.normal_django_user) self.assertIn('REMOTE_ADDR', settings.REMOTE_HOST_HEADERS) self.assertIn('REMOTE_HOST', settings.REMOTE_HOST_HEADERS) @@ -174,10 +174,10 @@ class UsersTest(BaseTest): super(UsersTest, self).setUp() self.setup_users() self.organizations = self.make_organizations(self.super_django_user, 2) - self.organizations[0].deprecated_admins.add(self.normal_django_user) - self.organizations[0].deprecated_users.add(self.other_django_user) - self.organizations[0].deprecated_users.add(self.normal_django_user) - self.organizations[1].deprecated_users.add(self.other_django_user) + self.organizations[0].admin_role.members.add(self.normal_django_user) + self.organizations[0].member_role.members.add(self.other_django_user) + self.organizations[0].member_role.members.add(self.normal_django_user) + self.organizations[1].member_role.members.add(self.other_django_user) def test_user_creation_fails_without_password(self): url = reverse('api:user_list')