mirror of
https://github.com/ansible/awx.git
synced 2026-03-26 21:35:01 -02:30
Initial (editable) pass of adding JT.organization
This is the old version of this feature from 2019 this allows setting the organization in the data sent to the API when creating a JT, and exposes the field in the UI as well Subsequent commit changes the field from editable to read-only, but as of this commit, the machinery is not hooked up to infer it from project
This commit is contained in:
@@ -1411,7 +1411,7 @@ class JobTemplateAccess(NotificationAttachMixin, BaseAccess):
|
||||
'''
|
||||
|
||||
model = JobTemplate
|
||||
select_related = ('created_by', 'modified_by', 'inventory', 'project',
|
||||
select_related = ('created_by', 'modified_by', 'inventory', 'project', 'organization',
|
||||
'next_schedule',)
|
||||
prefetch_related = (
|
||||
'instance_groups',
|
||||
@@ -1435,9 +1435,7 @@ class JobTemplateAccess(NotificationAttachMixin, BaseAccess):
|
||||
Users who are able to create deploy jobs can also run normal and check (dry run) jobs.
|
||||
'''
|
||||
if not data: # So the browseable API will work
|
||||
return (
|
||||
Project.accessible_objects(self.user, 'use_role').exists() or
|
||||
Inventory.accessible_objects(self.user, 'use_role').exists())
|
||||
return Organization.accessible_objects(self.user, 'job_template_admin_role').exists()
|
||||
|
||||
# if reference_obj is provided, determine if it can be copied
|
||||
reference_obj = data.get('reference_obj', None)
|
||||
@@ -1467,6 +1465,10 @@ class JobTemplateAccess(NotificationAttachMixin, BaseAccess):
|
||||
if self.user not in inventory.use_role:
|
||||
return False
|
||||
|
||||
organization = get_value(Organization, 'organization')
|
||||
if (not organization) or (self.user not in organization.job_template_admin_role):
|
||||
return False
|
||||
|
||||
project = get_value(Project, 'project')
|
||||
# If the user has admin access to the project (as an org admin), should
|
||||
# be able to proceed without additional checks.
|
||||
@@ -1504,22 +1506,31 @@ class JobTemplateAccess(NotificationAttachMixin, BaseAccess):
|
||||
return self.user in obj.execute_role
|
||||
|
||||
def can_change(self, obj, data):
|
||||
data_for_change = data
|
||||
if self.user not in obj.admin_role and not self.user.is_superuser:
|
||||
return False
|
||||
if data is not None:
|
||||
data = dict(data)
|
||||
if data is None:
|
||||
return True
|
||||
|
||||
if self.changes_are_non_sensitive(obj, data):
|
||||
if 'survey_enabled' in data and obj.survey_enabled != data['survey_enabled'] and data['survey_enabled']:
|
||||
self.check_license(feature='surveys')
|
||||
return True
|
||||
# standard type of check for organization - cannot change the value
|
||||
# unless posessing the respective job_template_admin_role, otherwise non-blocking
|
||||
if not self.check_related('organization', Organization, data, obj=obj, role_field='job_template_admin_role'):
|
||||
return False
|
||||
|
||||
for required_field in ('inventory', 'project'):
|
||||
required_obj = getattr(obj, required_field, None)
|
||||
if required_field not in data_for_change and required_obj is not None:
|
||||
data_for_change[required_field] = required_obj.pk
|
||||
return self.can_read(obj) and (self.can_add(data_for_change) if data is not None else True)
|
||||
data = dict(data)
|
||||
|
||||
if 'survey_enabled' in data and obj.survey_enabled != data['survey_enabled'] and data['survey_enabled']:
|
||||
self.check_license(feature='surveys')
|
||||
|
||||
if self.changes_are_non_sensitive(obj, data):
|
||||
return True
|
||||
|
||||
for required_field, cls in (('inventory', Inventory), ('project', Project)):
|
||||
is_mandatory = True
|
||||
if not getattr(obj, '{}_id'.format(required_field)):
|
||||
is_mandatory = False
|
||||
if not self.check_related(required_field, cls, data, obj=obj, role_field='use_role', mandatory=is_mandatory):
|
||||
return False
|
||||
return True
|
||||
|
||||
def changes_are_non_sensitive(self, obj, data):
|
||||
'''
|
||||
@@ -1554,9 +1565,9 @@ class JobTemplateAccess(NotificationAttachMixin, BaseAccess):
|
||||
@check_superuser
|
||||
def can_attach(self, obj, sub_obj, relationship, data, skip_sub_obj_read_check=False):
|
||||
if relationship == "instance_groups":
|
||||
if not obj.project.organization:
|
||||
if not obj.organization:
|
||||
return False
|
||||
return self.user.can_access(type(sub_obj), "read", sub_obj) and self.user in obj.project.organization.admin_role
|
||||
return self.user.can_access(type(sub_obj), "read", sub_obj) and self.user in obj.organization.admin_role
|
||||
if relationship == 'credentials' and isinstance(sub_obj, Credential):
|
||||
return self.user in obj.admin_role and self.user in sub_obj.use_role
|
||||
return super(JobTemplateAccess, self).can_attach(
|
||||
@@ -1587,6 +1598,7 @@ class JobAccess(BaseAccess):
|
||||
select_related = ('created_by', 'modified_by', 'job_template', 'inventory',
|
||||
'project', 'project_update',)
|
||||
prefetch_related = (
|
||||
'organization',
|
||||
'unified_job_template',
|
||||
'instance_group',
|
||||
'credentials__credential_type',
|
||||
@@ -1607,42 +1619,19 @@ class JobAccess(BaseAccess):
|
||||
|
||||
return qs.filter(
|
||||
Q(job_template__in=JobTemplate.accessible_objects(self.user, 'read_role')) |
|
||||
Q(inventory__organization__in=org_access_qs) |
|
||||
Q(project__organization__in=org_access_qs)).distinct()
|
||||
|
||||
def related_orgs(self, obj):
|
||||
orgs = []
|
||||
if obj.inventory and obj.inventory.organization:
|
||||
orgs.append(obj.inventory.organization)
|
||||
if obj.project and obj.project.organization and obj.project.organization not in orgs:
|
||||
orgs.append(obj.project.organization)
|
||||
return orgs
|
||||
|
||||
def org_access(self, obj, role_types=['admin_role']):
|
||||
orgs = self.related_orgs(obj)
|
||||
for org in orgs:
|
||||
for role_type in role_types:
|
||||
role = getattr(org, role_type)
|
||||
if self.user in role:
|
||||
return True
|
||||
return False
|
||||
Q(organization__in=org_access_qs)).distinct()
|
||||
|
||||
def can_add(self, data, validate_license=True):
|
||||
if validate_license:
|
||||
self.check_license()
|
||||
|
||||
if not data: # So the browseable API will work
|
||||
return True
|
||||
return self.user.is_superuser
|
||||
raise NotImplementedError('Direct job creation not possible in v2 API')
|
||||
|
||||
def can_change(self, obj, data):
|
||||
return (obj.status == 'new' and
|
||||
self.can_read(obj) and
|
||||
self.can_add(data, validate_license=False))
|
||||
raise NotImplementedError('Direct job editing not supported in v2 API')
|
||||
|
||||
@check_superuser
|
||||
def can_delete(self, obj):
|
||||
return self.org_access(obj)
|
||||
if not obj.organization:
|
||||
return False
|
||||
return self.user in obj.organization.admin_role
|
||||
|
||||
def can_start(self, obj, validate_license=True):
|
||||
if validate_license:
|
||||
@@ -1662,6 +1651,7 @@ class JobAccess(BaseAccess):
|
||||
except JobLaunchConfig.DoesNotExist:
|
||||
config = None
|
||||
|
||||
# Standard permissions model (1)
|
||||
if obj.job_template and (self.user not in obj.job_template.execute_role):
|
||||
return False
|
||||
|
||||
@@ -1676,24 +1666,15 @@ class JobAccess(BaseAccess):
|
||||
if JobLaunchConfigAccess(self.user).can_add({'reference_obj': config}):
|
||||
return True
|
||||
|
||||
org_access = bool(obj.inventory) and self.user in obj.inventory.organization.inventory_admin_role
|
||||
project_access = obj.project is None or self.user in obj.project.admin_role
|
||||
credential_access = all([self.user in cred.use_role for cred in obj.credentials.all()])
|
||||
# Standard permissions model (2)
|
||||
if obj.organization and self.user in obj.organization.execute_role:
|
||||
# Respect organization ownership of orphaned jobs
|
||||
return True
|
||||
elif not (obj.job_template or obj.organization):
|
||||
if self.save_messages:
|
||||
self.messages['detail'] = _('Job has been orphaned from its job template and organization.')
|
||||
|
||||
# job can be relaunched if user could make an equivalent JT
|
||||
ret = org_access and credential_access and project_access
|
||||
if not ret and self.save_messages and not self.messages:
|
||||
if not obj.job_template:
|
||||
pretext = _('Job has been orphaned from its job template.')
|
||||
elif config is None:
|
||||
pretext = _('Job was launched with unknown prompted fields.')
|
||||
else:
|
||||
pretext = _('Job was launched with prompted fields.')
|
||||
if credential_access:
|
||||
self.messages['detail'] = '{} {}'.format(pretext, _(' Organization level permissions required.'))
|
||||
else:
|
||||
self.messages['detail'] = '{} {}'.format(pretext, _(' You do not have permission to related resources.'))
|
||||
return ret
|
||||
return False
|
||||
|
||||
def get_method_capability(self, method, obj, parent_obj):
|
||||
if method == 'start':
|
||||
@@ -1706,10 +1687,16 @@ class JobAccess(BaseAccess):
|
||||
def can_cancel(self, obj):
|
||||
if not obj.can_cancel:
|
||||
return False
|
||||
# Delete access allows org admins to stop running jobs
|
||||
if self.user == obj.created_by or self.can_delete(obj):
|
||||
# Users may always cancel their own jobs
|
||||
if self.user == obj.created_by:
|
||||
return True
|
||||
return obj.job_template is not None and self.user in obj.job_template.admin_role
|
||||
# Users with direct admin to JT may cancel jobs started by anyone
|
||||
if obj.job_template and self.user in obj.job_template.admin_role:
|
||||
return True
|
||||
# If orphaned, allow org JT admins to stop running jobs
|
||||
if not obj.job_template and obj.organization and self.user in obj.organization.job_template_admin_role:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class SystemJobTemplateAccess(BaseAccess):
|
||||
@@ -1944,11 +1931,11 @@ class WorkflowJobNodeAccess(BaseAccess):
|
||||
# TODO: notification attachments?
|
||||
class WorkflowJobTemplateAccess(NotificationAttachMixin, BaseAccess):
|
||||
'''
|
||||
I can only see/manage Workflow Job Templates if I'm a super user
|
||||
I can see/manage Workflow Job Templates based on object roles
|
||||
'''
|
||||
|
||||
model = WorkflowJobTemplate
|
||||
select_related = ('created_by', 'modified_by', 'next_schedule',
|
||||
select_related = ('created_by', 'modified_by', 'organization', 'next_schedule',
|
||||
'admin_role', 'execute_role', 'read_role',)
|
||||
|
||||
def filtered_queryset(self):
|
||||
@@ -2038,7 +2025,7 @@ class WorkflowJobAccess(BaseAccess):
|
||||
I can also cancel it if I started it
|
||||
'''
|
||||
model = WorkflowJob
|
||||
select_related = ('created_by', 'modified_by',)
|
||||
select_related = ('created_by', 'modified_by', 'organization',)
|
||||
|
||||
def filtered_queryset(self):
|
||||
return WorkflowJob.objects.filter(
|
||||
@@ -2332,6 +2319,7 @@ class UnifiedJobTemplateAccess(BaseAccess):
|
||||
prefetch_related = (
|
||||
'last_job',
|
||||
'current_job',
|
||||
'organization',
|
||||
'credentials__credential_type',
|
||||
Prefetch('labels', queryset=Label.objects.all().order_by('name')),
|
||||
)
|
||||
@@ -2371,6 +2359,7 @@ class UnifiedJobAccess(BaseAccess):
|
||||
prefetch_related = (
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'organization',
|
||||
'unified_job_node__workflow_job',
|
||||
'unified_job_template',
|
||||
'instance_group',
|
||||
@@ -2401,8 +2390,7 @@ class UnifiedJobAccess(BaseAccess):
|
||||
Q(unified_job_template_id__in=UnifiedJobTemplate.accessible_pk_qs(self.user, 'read_role')) |
|
||||
Q(inventoryupdate__inventory_source__inventory__id__in=inv_pk_qs) |
|
||||
Q(adhoccommand__inventory__id__in=inv_pk_qs) |
|
||||
Q(job__inventory__organization__in=org_auditor_qs) |
|
||||
Q(job__project__organization__in=org_auditor_qs)
|
||||
Q(organization__in=org_auditor_qs)
|
||||
)
|
||||
return qs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user