Merge pull request #4515 from AlanCoding/relaunch_rbac

Custom PermissionDenied error for Workflow Job relaunch
This commit is contained in:
Alan Rominger 2017-01-06 16:04:13 -05:00 committed by GitHub
commit d5247c6d95
3 changed files with 74 additions and 20 deletions

View File

@ -2957,6 +2957,13 @@ class WorkflowJobRelaunch(WorkflowsEnforcementMixin, GenericAPIView):
serializer_class = EmptySerializer
is_job_start = True
def check_object_permissions(self, request, obj):
if request.method == 'POST' and obj:
relaunch_perm, messages = request.user.can_access_with_errors(self.model, 'start', obj)
if not relaunch_perm and 'workflow_job_template' in messages:
self.permission_denied(request, message=messages['workflow_job_template'])
return super(WorkflowJobRelaunch, self).check_object_permissions(request, obj)
def get(self, request, *args, **kwargs):
return Response({})

View File

@ -363,27 +363,30 @@ class BaseAccess(object):
continue
# Compute permission
data = {}
access_method = getattr(self, "can_%s" % method)
if method in ['change']: # 3 args
user_capabilities[display_method] = access_method(obj, data)
elif method in ['delete', 'run_ad_hoc_commands', 'copy']:
user_capabilities[display_method] = access_method(obj)
elif method in ['start']:
user_capabilities[display_method] = access_method(obj, validate_license=False)
elif method in ['add']: # 2 args with data
user_capabilities[display_method] = access_method(data)
elif method in ['attach', 'unattach']: # parent/sub-object call
if type(parent_obj) == Team:
relationship = 'parents'
parent_obj = parent_obj.member_role
else:
relationship = 'members'
user_capabilities[display_method] = access_method(
obj, parent_obj, relationship, skip_sub_obj_read_check=True, data=data)
user_capabilities[display_method] = self.get_method_capability(method, obj, parent_obj)
return user_capabilities
def get_method_capability(self, method, obj, parent_obj):
if method in ['change']: # 3 args
return self.can_change(obj, {})
elif method in ['delete', 'run_ad_hoc_commands', 'copy']:
access_method = getattr(self, "can_%s" % method)
return access_method(obj)
elif method in ['start']:
return self.can_start(obj, validate_license=False)
elif method in ['add']: # 2 args with data
return self.can_add({})
elif method in ['attach', 'unattach']: # parent/sub-object call
access_method = getattr(self, "can_%s" % method)
if type(parent_obj) == Team:
relationship = 'parents'
parent_obj = parent_obj.member_role
else:
relationship = 'members'
return access_method(obj, parent_obj, relationship, skip_sub_obj_read_check=True, data={})
return False
class UserAccess(BaseAccess):
'''
@ -1478,8 +1481,14 @@ class WorkflowJobNodeAccess(BaseAccess):
qs = qs.prefetch_related('success_nodes', 'failure_nodes', 'always_nodes')
return qs
@check_superuser
def can_add(self, data):
return False
if data is None: # Hide direct creation in API browser
return False
return (
self.check_related('unified_job_template', UnifiedJobTemplate, data, role_field='execute_role') and
self.check_related('credential', Credential, data, role_field='use_role') and
self.check_related('inventory', Inventory, data, role_field='use_role'))
def can_change(self, obj, data):
return False
@ -1617,6 +1626,14 @@ class WorkflowJobAccess(BaseAccess):
return self.user.is_superuser
return self.user in obj.workflow_job_template.admin_role
def get_method_capability(self, method, obj, parent_obj):
if method == 'start':
# Return simplistic permission, will perform detailed check on POST
if not obj.workflow_job_template:
return self.user.is_superuser
return self.user in obj.workflow_job_template.execute_role
return super(WorkflowJobAccess, self).get_method_capability(method, obj, parent_obj)
def can_start(self, obj, validate_license=True):
if validate_license:
self.check_license()
@ -1624,7 +1641,34 @@ class WorkflowJobAccess(BaseAccess):
if self.user.is_superuser:
return True
return (obj.workflow_job_template and self.user in obj.workflow_job_template.execute_role)
wfjt = obj.workflow_job_template
# only superusers can relaunch orphans
if not wfjt:
return False
# execute permission to WFJT is mandatory for any relaunch
if self.user not in wfjt.execute_role:
return False
# WFJT is valid base for WJ, launch permitted
last_modified = wfjt.nodes_last_modified()
if last_modified and obj.created > last_modified:
return True
# user's WFJT access doesn't guarentee permission to launch, introspect nodes
return self.can_recreate(obj)
def can_recreate(self, obj):
node_qs = obj.workflow_job_nodes.all().prefetch_related('inventory', 'credential', 'unified_job_template')
node_access = WorkflowJobNodeAccess(user=self.user)
wj_add_perm = True
for node in node_qs:
if not node_access.can_add({'reference_obj': node}):
wj_add_perm = False
if not wj_add_perm and self.save_messages:
self.messages['workflow_job_template'] = _('Template has been modified since job was launched, '
'and you do not have permission to its resources.')
return wj_add_perm
def can_cancel(self, obj):
if not obj.can_cancel:

View File

@ -327,6 +327,9 @@ class WorkflowJobOptions(BaseModel):
new_workflow_job.copy_nodes_from_original(original=self)
return new_workflow_job
def nodes_last_modified(self):
return self.workflow_nodes.aggregate(models.Max('modified'))['modified__max']
class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTemplateMixin, ResourceMixin):
class Meta: