From a1cc5b06b81de0baf6f3af3cf57ae36cb84a4113 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Tue, 1 Mar 2016 17:30:32 -0500 Subject: [PATCH] Remove can_access methods and registration --- awx/main/access.py | 217 ++++++----------------------------- awx/main/migrations/_rbac.py | 21 ++-- awx/main/models/__init__.py | 1 - 3 files changed, 49 insertions(+), 190 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index 231836115e..d3fd865de2 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -20,7 +20,7 @@ from awx.main.models.rbac import ALL_PERMISSIONS from awx.api.license import LicenseForbids from awx.main.task_engine import TaskSerializer -__all__ = ['get_user_queryset', 'check_user_access'] +__all__ = ['get_user_queryset'] PERMISSION_TYPES = [ PERM_INVENTORY_ADMIN, @@ -90,25 +90,6 @@ def get_user_queryset(user, model_class): queryset = queryset.filter(pk__in=qs.values_list('pk', flat=True)) return queryset -def check_user_access(user, model_class, action, *args, **kwargs): - ''' - Return True if user can perform action against model_class with the - provided parameters. - ''' - for access_class in access_registry.get(model_class, []): - access_instance = access_class(user) - access_method = getattr(access_instance, 'can_%s' % action, None) - if not access_method: - logger.debug('%s.%s not found', access_instance.__class__.__name__, - 'can_%s' % action) - continue - result = access_method(*args, **kwargs) - logger.debug('%s.%s %r returned %r', access_instance.__class__.__name__, - access_method.__name__, args, result) - if result: - return result - return False - class BaseAccess(object): ''' @@ -156,7 +137,7 @@ class BaseAccess(object): return self.can_change(obj, None) else: return bool(self.can_change(obj, None) and - self.user.can_access(type(sub_obj), 'read', sub_obj)) + sub_obj.accessible_by(self.user, {'read':True})) def can_unattach(self, obj, sub_obj, relationship): return self.can_change(obj, None) @@ -358,7 +339,7 @@ class HostAccess(BaseAccess): return qs.filter(inventory_id__in=inventory_ids) def can_read(self, obj): - return obj and self.user.can_access(Inventory, 'read', obj.inventory) + return obj and obj.inventory.accessible_by(self.user, {'read':True}) def can_add(self, data): if not data or 'inventory' not in data: @@ -367,7 +348,7 @@ class HostAccess(BaseAccess): # Checks for admin or change permission on inventory. inventory_pk = get_pk_from_dict(data, 'inventory') inventory = get_object_or_400(Inventory, pk=inventory_pk) - if not self.user.can_access(Inventory, 'change', inventory, None): + if not inventory.accessible_by(self.user, {'read':True, 'create':True}): return False # Check to see if we have enough licenses @@ -381,7 +362,7 @@ class HostAccess(BaseAccess): raise PermissionDenied('Unable to change inventory on a host') # Checks for admin or change permission on inventory, controls whether # the user can edit variable data. - return obj and self.user.can_access(Inventory, 'change', obj.inventory, None) + return obj and obj.inventory.accessible_by(self.user, {'read':True, 'update':True, 'write':True}) def can_attach(self, obj, sub_obj, relationship, data, skip_sub_obj_read_check=False): @@ -394,7 +375,7 @@ class HostAccess(BaseAccess): return True def can_delete(self, obj): - return obj and self.user.can_access(Inventory, 'delete', obj.inventory) + return obj and obj.inventory.accessible_by(self.user, {'delete':True}) class GroupAccess(BaseAccess): ''' @@ -412,7 +393,7 @@ class GroupAccess(BaseAccess): return qs.filter(inventory_id__in=inventory_ids) def can_read(self, obj): - return obj and self.user.can_access(Inventory, 'read', obj.inventory) + return obj and obj.inventory.accessible_by(self.user, {'read':True}) def can_add(self, data): if not data or 'inventory' not in data: @@ -420,7 +401,7 @@ class GroupAccess(BaseAccess): # Checks for admin or change permission on inventory. inventory_pk = get_pk_from_dict(data, 'inventory') inventory = get_object_or_400(Inventory, pk=inventory_pk) - return self.user.can_access(Inventory, 'change', inventory, None) + return inventory.accessible_by(self.user, {'read':True, 'create':True}) def can_change(self, obj, data): # Prevent moving a group to a different inventory. @@ -429,7 +410,7 @@ class GroupAccess(BaseAccess): raise PermissionDenied('Unable to change inventory on a group') # Checks for admin or change permission on inventory, controls whether # the user can attach subgroups or edit variable data. - return obj and self.user.can_access(Inventory, 'change', obj.inventory, None) + return obj and obj.inventory.accessible_by(self.user, {'read':True, 'update':True, 'write':True}) def can_attach(self, obj, sub_obj, relationship, data, skip_sub_obj_read_check=False): @@ -453,8 +434,7 @@ class GroupAccess(BaseAccess): return True def can_delete(self, obj): - return obj and self.user.can_access(Inventory, 'delete', obj.inventory) - + return obj and obj.inventory.accessible_by(self.user, {'delete':True}) class InventorySourceAccess(BaseAccess): ''' @@ -473,9 +453,9 @@ class InventorySourceAccess(BaseAccess): def can_read(self, obj): if obj and obj.group: - return self.user.can_access(Group, 'read', obj.group) + return obj.group.accessible_by(self.user, {'read':True}) elif obj and obj.inventory: - return self.user.can_access(Inventory, 'read', obj.inventory) + return obj.inventory.accessible_by(self.user, {'read':True}) else: return False @@ -486,7 +466,7 @@ class InventorySourceAccess(BaseAccess): def can_change(self, obj, data): # Checks for admin or change permission on group. if obj and obj.group: - return self.user.can_access(Group, 'change', obj.group, None) + return obj.group.accessible_by(self.user, {'read':True, 'update':True, 'write':True}) # Can't change inventory sources attached to only the inventory, since # these are created automatically from the management command. else: @@ -596,7 +576,7 @@ class TeamAccess(BaseAccess): else: org_pk = get_pk_from_dict(data, 'organization') org = get_object_or_400(Organization, pk=org_pk) - if self.user.can_access(Organization, 'change', org, None): + if org.accessible_by(self.user, {'read':True, 'update':True, 'write':True}): return True return False @@ -701,100 +681,7 @@ class ProjectUpdateAccess(BaseAccess): return self.can_change(obj, {}) and obj.can_cancel def can_delete(self, obj): - return obj and self.user.can_access(Project, 'delete', obj.project) - -class PermissionAccess(BaseAccess): - ''' - I can see a permission when: - - I'm a superuser. - - I'm an org admin and it's for a user in my org. - - I'm an org admin and it's for a team in my org. - - I'm a user and it's assigned to me. - - I'm a member of a team and it's assigned to the team. - I can create/change/delete when: - - I'm a superuser. - - I'm an org admin and the team/user is in my org and the inventory is in - my org and the project is in my org. - ''' - - model = Permission - - def get_queryset(self): - qs = self.model.objects.filter(active=True).distinct() - qs = qs.select_related('created_by', 'modified_by', 'user', 'team', 'inventory', - 'project') - if self.user.is_superuser: - return qs - orgs_as_admin_ids = set(self.user.admin_of_organizations.filter(active=True).values_list('id', flat=True)) - return qs.filter( - Q(user__organizations__in=orgs_as_admin_ids) | - Q(user__admin_of_organizations__in=orgs_as_admin_ids) | - Q(team__organization__in=orgs_as_admin_ids, team__active=True) | - Q(user=self.user) | - Q(team__users__in=[self.user], team__active=True) - ) - - def can_add(self, data): - if not data: - return True # generic add permission check - user_pk = get_pk_from_dict(data, 'user') - team_pk = get_pk_from_dict(data, 'team') - if user_pk: - user = get_object_or_400(User, pk=user_pk) - if not self.user.can_access(User, 'admin', user, None): - return False - elif team_pk: - team = get_object_or_400(Team, pk=team_pk) - if not self.user.can_access(Team, 'admin', team, None): - return False - else: - return False - inventory_pk = get_pk_from_dict(data, 'inventory') - if inventory_pk: - inventory = get_object_or_400(Inventory, pk=inventory_pk) - if not self.user.can_access(Inventory, 'admin', inventory, None): - return False - project_pk = get_pk_from_dict(data, 'project') - if project_pk: - project = get_object_or_400(Project, pk=project_pk) - if not self.user.can_access(Project, 'admin', project, None): - return False - # FIXME: user/team, inventory and project should probably all be part - # of the same organization. - return True - - def can_change(self, obj, data): - # Prevent assigning a permission to a different user. - user_pk = get_pk_from_dict(data, 'user') - if obj and user_pk and obj.user and obj.user.pk != user_pk: - raise PermissionDenied('Unable to change user on a permission') - # Prevent assigning a permission to a different team. - team_pk = get_pk_from_dict(data, 'team') - if obj and team_pk and obj.team and obj.team.pk != team_pk: - raise PermissionDenied('Unable to change team on a permission') - if self.user.is_superuser: - return True - # If changing inventory, verify access to the new inventory. - new_inventory_pk = get_pk_from_dict(data, 'inventory') - if obj and new_inventory_pk and obj.inventory and obj.inventory.pk != new_inventory_pk: - inventory = get_object_or_400(Inventory, pk=new_inventory_pk) - if not self.user.can_access(Inventory, 'admin', inventory, None): - return False - # If changing project, verify access to the new project. - new_project = get_pk_from_dict(data, 'project') - if obj and new_project and obj.project and obj.project.pk != new_project: - project = get_object_or_400(Project, pk=new_project) - if not self.user.can_access(Project, 'admin', project, None): - return False - # Check for admin access to the user or team. - if obj.user and self.user.can_access(User, 'admin', obj.user, None): - return True - if obj.team and self.user.can_access(Team, 'admin', obj.team, None): - return True - return False - - def can_delete(self, obj): - return self.can_change(obj, None) + return obj and obj.project.accessible_by(self.user, {'delete':True}) class JobTemplateAccess(BaseAccess): ''' @@ -895,7 +782,7 @@ class JobTemplateAccess(BaseAccess): credential_pk = get_pk_from_dict(data, 'credential') if credential_pk: credential = get_object_or_400(Credential, pk=credential_pk) - if not self.user.can_access(Credential, 'read', credential): + if not credential.accessible_by(self.user, {'read':True}): return False # If a cloud credential is provided, the user should have read access. @@ -903,7 +790,7 @@ class JobTemplateAccess(BaseAccess): if cloud_credential_pk: cloud_credential = get_object_or_400(Credential, pk=cloud_credential_pk) - if not self.user.can_access(Credential, 'read', cloud_credential): + if not cloud_credential.accessible_by(self.user, {'read':True}): return False # Check that the given inventory ID is valid. @@ -914,48 +801,19 @@ class JobTemplateAccess(BaseAccess): project_pk = get_pk_from_dict(data, 'project') if 'job_type' in data and data['job_type'] == PERM_INVENTORY_SCAN: - if not project_pk and self.user.can_access(Organization, 'change', inventory[0].organization, None): + org = inventory[0].organization + accessible = org.accessible_by(self.user, {'read':True, 'update':True, 'write':True}) + if not project_pk and accessible: return True - elif not self.user.can_access(Organization, "change", inventory[0].organization, None): + elif not accessible: return False # If the user has admin access to the project (as an org admin), should # be able to proceed without additional checks. project = get_object_or_400(Project, pk=project_pk) - if self.user.can_access(Project, 'admin', project, None): + if project.accessible_by(self.user, ALL_PERMISSIONS): return True - # Otherwise, check for explicitly granted permissions to create job templates - # for the project and inventory. - permission_qs = Permission.objects.filter( - Q(user=self.user) | Q(team__users__in=[self.user]), - inventory=inventory, - project=project, - active=True, - #permission_type__in=[PERM_INVENTORY_CHECK, PERM_INVENTORY_DEPLOY], - permission_type=PERM_JOBTEMPLATE_CREATE, - ) - if permission_qs.exists(): - return True - return False - - # job_type = data.get('job_type', None) - - # for perm in permission_qs: - # # if you have run permissions, you can also create check jobs - # if job_type == PERM_INVENTORY_CHECK: - # has_perm = True - # # you need explicit run permissions to make run jobs - # elif job_type == PERM_INVENTORY_DEPLOY and perm.permission_type == PERM_INVENTORY_DEPLOY: - # has_perm = True - # if not has_perm: - # return False - # return True - - # shouldn't really matter with permissions given, but make sure the user - # is also currently on the team in case they were added a per-user permission and then removed - # from the project. - #if not project.teams.filter(users__in=[self.user]).count(): - # return False + return project.accessible_by(self.user, ALL_PERMISSIONS) and inventory.accessible_by(self.user, {'read':True}) def can_start(self, obj, validate_license=True): # Check license. @@ -973,14 +831,14 @@ class JobTemplateAccess(BaseAccess): if obj.inventory is None: return False if obj.job_type == PERM_INVENTORY_SCAN: - if obj.project is None and self.user.can_access(Organization, 'change', obj.inventory.organization, None): + if obj.project is None and obj.inventory.organization.accessible_by(self.user, {'read':True, 'update':True, 'write':True}): return True - if not self.user.can_access(Organization, 'change', obj.inventory.organization, None): + if not obj.inventory.organization.accessible_by(self.user, {'read':True, 'update':True, 'write':True}): return False if obj.project is None: return False # If the user has admin access to the project they can start a job - if self.user.can_access(Project, 'admin', obj.project, None): + if obj.project.accessible_by(self.user, ALL_PERMISSIONS): return True # Otherwise check for explicitly granted permissions @@ -1004,7 +862,7 @@ class JobTemplateAccess(BaseAccess): obj.job_type == PERM_INVENTORY_CHECK: has_perm = True - dep_access = self.user.can_access(Inventory, 'read', obj.inventory) and self.user.can_access(Project, 'read', obj.project) + dep_access = obj.inventory.accessible_by(self.user, {'read':True}) and obj.project.accessible_by(self.user, {'read':True}) return dep_access and has_perm def can_change(self, obj, data): @@ -1119,10 +977,10 @@ class JobAccess(BaseAccess): # If a user can launch the job template then they can relaunch a job from that # job template has_perm = False - if obj.job_template is not None and self.user.can_access(JobTemplate, 'start', obj.job_template): + if obj.job_template is not None and obj.job_template.accessible_by(self.user, {'execute':True}): has_perm = True - dep_access_inventory = self.user.can_access(Inventory, 'read', obj.inventory) - dep_access_project = obj.project is None or self.user.can_access(Project, 'read', obj.project) + dep_access_inventory = obj.inventory.accessible_by(self.user, {'read':True}) + dep_access_project = obj.project is None or obj.project.accessible_by(self.user, {'read':True}) return self.can_read(obj) and dep_access_inventory and dep_access_project and has_perm def can_cancel(self, obj): @@ -1192,7 +1050,7 @@ class AdHocCommandAccess(BaseAccess): credential_pk = get_pk_from_dict(data, 'credential') if credential_pk: credential = get_object_or_400(Credential, pk=credential_pk, active=True) - if not self.user.can_access(Credential, 'read', credential): + if not credential.accessible_by(self.user, {'read':True}): return False # Check that the user has the run ad hoc command permission on the @@ -1200,7 +1058,7 @@ class AdHocCommandAccess(BaseAccess): inventory_pk = get_pk_from_dict(data, 'inventory') if inventory_pk: inventory = get_object_or_400(Inventory, pk=inventory_pk, active=True) - if not self.user.can_access(Inventory, 'run_ad_hoc_commands', inventory): + if not inventory.accessible_by(self.user, {'execute': True}): return False return True @@ -1403,8 +1261,7 @@ class ScheduleAccess(BaseAccess): if self.user.is_superuser: return True if obj and obj.unified_job_template: - job_class = obj.unified_job_template - return self.user.can_access(type(job_class), 'read', obj.unified_job_template) + return obj.unified_job_template.accessible_by(self.user, {'read':True}) else: return False @@ -1414,7 +1271,7 @@ class ScheduleAccess(BaseAccess): pk = get_pk_from_dict(data, 'unified_job_template') obj = get_object_or_400(UnifiedJobTemplate, pk=pk) if obj: - return self.user.can_access(type(obj), 'change', obj, None) + return obj.accessible_by(self.user, {'read':True, 'update':True, 'write':True}) else: return False @@ -1422,8 +1279,7 @@ class ScheduleAccess(BaseAccess): if self.user.is_superuser: return True if obj and obj.unified_job_template: - job_class = obj.unified_job_template - return self.user.can_access(type(job_class), 'change', job_class, None) + return obj.unified_job_template.accessible_by(self.user, {'read':True, 'update':True, 'write':True}) else: return False @@ -1431,8 +1287,7 @@ class ScheduleAccess(BaseAccess): if self.user.is_superuser: return True if obj and obj.unified_job_template: - job_class = obj.unified_job_template - return self.user.can_access(type(job_class), 'change', job_class, None) + return obj.unified_job_template.accessible_by(self.user, {'read':True, 'update':True, 'write':True}) else: return False diff --git a/awx/main/migrations/_rbac.py b/awx/main/migrations/_rbac.py index 546721c4f6..f26970f9ba 100644 --- a/awx/main/migrations/_rbac.py +++ b/awx/main/migrations/_rbac.py @@ -195,19 +195,24 @@ def migrate_job_templates(apps, schema_editor): Permission = apps.get_model('main', 'Permission') for jt in JobTemplate.objects.all(): + permission = Permission.objects.filter( + inventory=jt.inventory, + project=jt.project, + active=True, + permission_type__in=['create', 'check', 'run'] if jt.job_type == 'check' else ['create', 'run'], + ) + for team in Team.objects.all(): - if Permission.objects.filter( - team=team, - inventory=jt.inventory, - project=jt.project, - active=True, - permission_type__in=['create', 'check', 'run'] if jt.job_type == 'check' else ['create', 'run'] - ): - team.member_role.children.add(jt.executor_role); + if permission.filter(team=team).exists(): + team.member_role.children.add(jt.executor_role) migrations[jt.name]['teams'].add(team) for user in User.objects.all(): + if permission.filter(user=user).exists(): + jt.exector_role.members.add(user) + migrations[jt.name]['users'].add(user) + if jt.accessible_by(user, {'execute': True}): # If the job template is already accessible by the user, because they # are a sytem, organization, or project admin, then don't add an explicit diff --git a/awx/main/models/__init__.py b/awx/main/models/__init__.py index 85476a19e7..41b866f78c 100644 --- a/awx/main/models/__init__.py +++ b/awx/main/models/__init__.py @@ -37,7 +37,6 @@ _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) # Import signal handlers only after models have been defined. import awx.main.signals # noqa