mirror of
https://github.com/ansible/awx.git
synced 2026-05-13 12:27:37 -02:30
Merge pull request #4515 from AlanCoding/relaunch_rbac
Custom PermissionDenied error for Workflow Job relaunch
This commit is contained in:
@@ -2957,6 +2957,13 @@ class WorkflowJobRelaunch(WorkflowsEnforcementMixin, GenericAPIView):
|
|||||||
serializer_class = EmptySerializer
|
serializer_class = EmptySerializer
|
||||||
is_job_start = True
|
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):
|
def get(self, request, *args, **kwargs):
|
||||||
return Response({})
|
return Response({})
|
||||||
|
|
||||||
|
|||||||
@@ -363,27 +363,30 @@ class BaseAccess(object):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Compute permission
|
# Compute permission
|
||||||
data = {}
|
user_capabilities[display_method] = self.get_method_capability(method, obj, parent_obj)
|
||||||
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)
|
|
||||||
|
|
||||||
return user_capabilities
|
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):
|
class UserAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
@@ -1478,8 +1481,14 @@ class WorkflowJobNodeAccess(BaseAccess):
|
|||||||
qs = qs.prefetch_related('success_nodes', 'failure_nodes', 'always_nodes')
|
qs = qs.prefetch_related('success_nodes', 'failure_nodes', 'always_nodes')
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_add(self, data):
|
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):
|
def can_change(self, obj, data):
|
||||||
return False
|
return False
|
||||||
@@ -1617,6 +1626,14 @@ class WorkflowJobAccess(BaseAccess):
|
|||||||
return self.user.is_superuser
|
return self.user.is_superuser
|
||||||
return self.user in obj.workflow_job_template.admin_role
|
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):
|
def can_start(self, obj, validate_license=True):
|
||||||
if validate_license:
|
if validate_license:
|
||||||
self.check_license()
|
self.check_license()
|
||||||
@@ -1624,7 +1641,34 @@ class WorkflowJobAccess(BaseAccess):
|
|||||||
if self.user.is_superuser:
|
if self.user.is_superuser:
|
||||||
return True
|
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):
|
def can_cancel(self, obj):
|
||||||
if not obj.can_cancel:
|
if not obj.can_cancel:
|
||||||
|
|||||||
@@ -327,6 +327,9 @@ class WorkflowJobOptions(BaseModel):
|
|||||||
new_workflow_job.copy_nodes_from_original(original=self)
|
new_workflow_job.copy_nodes_from_original(original=self)
|
||||||
return new_workflow_job
|
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 WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTemplateMixin, ResourceMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
Reference in New Issue
Block a user