mirror of
https://github.com/ansible/awx.git
synced 2026-02-18 11:40:05 -03:30
Add more RBAC for approval nodes
This commit is contained in:
@@ -3410,9 +3410,16 @@ class WorkflowApprovalViewSerializer(UnifiedJobSerializer):
|
|||||||
|
|
||||||
class WorkflowApprovalSerializer(UnifiedJobSerializer):
|
class WorkflowApprovalSerializer(UnifiedJobSerializer):
|
||||||
|
|
||||||
|
can_approve_or_deny = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = WorkflowApproval
|
model = WorkflowApproval
|
||||||
fields = (['*', '-controller_node', '-execution_node',])
|
fields = (['*', '-controller_node', '-execution_node', 'can_approve_or_deny'])
|
||||||
|
|
||||||
|
def get_can_approve_or_deny(self, obj):
|
||||||
|
request = self.context.get('request', None)
|
||||||
|
allowed = request.user.can_access(WorkflowApproval, 'approve_or_deny', obj)
|
||||||
|
return allowed is True and obj.status == 'pending'
|
||||||
|
|
||||||
def get_related(self, obj):
|
def get_related(self, obj):
|
||||||
res = super(WorkflowApprovalSerializer, self).get_related(obj)
|
res = super(WorkflowApprovalSerializer, self).get_related(obj)
|
||||||
@@ -3420,17 +3427,21 @@ class WorkflowApprovalSerializer(UnifiedJobSerializer):
|
|||||||
if obj.workflow_approval_template:
|
if obj.workflow_approval_template:
|
||||||
res['workflow_approval_template'] = self.reverse('api:workflow_approval_template_detail',
|
res['workflow_approval_template'] = self.reverse('api:workflow_approval_template_detail',
|
||||||
kwargs={'pk': obj.workflow_approval_template.pk})
|
kwargs={'pk': obj.workflow_approval_template.pk})
|
||||||
res['notifications'] = self.reverse('api:workflow_approval_notifications_list', kwargs={'pk': obj.pk})
|
|
||||||
res['approve'] = self.reverse('api:workflow_approval_approve', kwargs={'pk': obj.pk})
|
res['approve'] = self.reverse('api:workflow_approval_approve', kwargs={'pk': obj.pk})
|
||||||
res['deny'] = self.reverse('api:workflow_approval_deny', kwargs={'pk': obj.pk})
|
res['deny'] = self.reverse('api:workflow_approval_deny', kwargs={'pk': obj.pk})
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WorkflowApprovalListSerializer(WorkflowApprovalSerializer, UnifiedJobListSerializer):
|
class WorkflowApprovalListSerializer(WorkflowApprovalSerializer, UnifiedJobListSerializer):
|
||||||
|
|
||||||
|
can_approve_or_deny = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ('*', '-execution_node', '-controller_node',)
|
fields = ('*', '-execution_node', '-controller_node', 'can_approve_or_deny')
|
||||||
|
|
||||||
|
def get_can_approve_or_deny(self, obj):
|
||||||
|
request = self.context.get('request', None)
|
||||||
|
return request.user.can_access(WorkflowApproval, 'approve_or_deny', obj) is True
|
||||||
|
|
||||||
|
|
||||||
class WorkflowApprovalTemplateSerializer(UnifiedJobTemplateSerializer):
|
class WorkflowApprovalTemplateSerializer(UnifiedJobTemplateSerializer):
|
||||||
@@ -3446,13 +3457,7 @@ class WorkflowApprovalTemplateSerializer(UnifiedJobTemplateSerializer):
|
|||||||
|
|
||||||
res.update(dict(
|
res.update(dict(
|
||||||
jobs = self.reverse('api:workflow_approval_template_jobs_list', kwargs={'pk': obj.pk}),
|
jobs = self.reverse('api:workflow_approval_template_jobs_list', kwargs={'pk': obj.pk}),
|
||||||
# &&&&&& Placeholder for notification things!
|
))
|
||||||
# notification_templates_started = self.reverse('api:workflow_approval_template_notification_templates_started_list', kwargs={'pk': obj.pk}),
|
|
||||||
# notification_templates_needs_approval = self.reverse(
|
|
||||||
#'api:workflow_approval_template_notification_templates_needs_approval_list', kwargs={'pk': obj.pk}),
|
|
||||||
# notification_templates_success = self.reverse('api:workflow_approval_template_notification_templates_success_list', kwargs={'pk': obj.pk}),
|
|
||||||
# notification_templates_error = self.reverse('api:workflow_approval_template_notification_templates_error_list', kwargs={'pk': obj.pk}),
|
|
||||||
))
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4440,8 +4440,6 @@ class WorkflowApprovalList(ListCreateAPIView):
|
|||||||
serializer_class = serializers.WorkflowApprovalListSerializer
|
serializer_class = serializers.WorkflowApprovalListSerializer
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
if not request.user.is_superuser and not request.user.is_system_auditor:
|
|
||||||
raise PermissionDenied(_("Superuser privileges needed."))
|
|
||||||
return super(WorkflowApprovalList, self).get(request, *args, **kwargs)
|
return super(WorkflowApprovalList, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@@ -4455,22 +4453,29 @@ class WorkflowApprovalApprove(RetrieveAPIView):
|
|||||||
model = models.WorkflowApproval
|
model = models.WorkflowApproval
|
||||||
serializer_class = serializers.WorkflowApprovalViewSerializer
|
serializer_class = serializers.WorkflowApprovalViewSerializer
|
||||||
|
|
||||||
# &&&&&& To address later
|
# &&&&&& Changed per the PR review, notes/questions in additional comments...
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
|
request.user.can_access(models.WorkflowApproval, 'approve_or_deny', obj)
|
||||||
|
if obj.status != 'pending':
|
||||||
|
return Response("This workflow step has already been approved or denied.", status=status.HTTP_400_BAD_REQUEST)
|
||||||
obj.approve()
|
obj.approve()
|
||||||
return Response(status=status.HTTP_202_ACCEPTED)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
class WorkflowApprovalDeny(RetrieveAPIView):
|
class WorkflowApprovalDeny(RetrieveAPIView):
|
||||||
model = models.WorkflowApproval
|
model = models.WorkflowApproval
|
||||||
serializer_class = serializers.WorkflowApprovalViewSerializer
|
serializer_class = serializers.WorkflowApprovalViewSerializer
|
||||||
|
|
||||||
# &&&&&& To address later
|
# &&&&&& Changed per the PR review, notes/questions in additional comments...
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
|
request.user.can_access(models.WorkflowApproval, 'approve_or_deny', obj)
|
||||||
|
if obj.status != 'pending':
|
||||||
|
return Response("This workflow step has already been approved or denied.", status=status.HTTP_400_BAD_REQUEST)
|
||||||
obj.deny()
|
obj.deny()
|
||||||
return Response(status=status.HTTP_202_ACCEPTED)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WorkflowApprovalNotificationsList(SubListAPIView):
|
class WorkflowApprovalNotificationsList(SubListAPIView):
|
||||||
|
|||||||
@@ -2378,7 +2378,8 @@ class UnifiedJobTemplateAccess(BaseAccess):
|
|||||||
return self.model.objects.filter(
|
return self.model.objects.filter(
|
||||||
Q(pk__in=self.model.accessible_pk_qs(self.user, 'read_role')) |
|
Q(pk__in=self.model.accessible_pk_qs(self.user, 'read_role')) |
|
||||||
Q(inventorysource__inventory__id__in=Inventory._accessible_pk_qs(
|
Q(inventorysource__inventory__id__in=Inventory._accessible_pk_qs(
|
||||||
Inventory, self.user, 'read_role')))
|
Inventory, self.user, 'read_role'))
|
||||||
|
).exclude(polymorphic_ctype__model='workflowapprovaltemplate') # &&&&&&
|
||||||
|
|
||||||
def can_start(self, obj, validate_license=True):
|
def can_start(self, obj, validate_license=True):
|
||||||
access_class = access_registry[obj.__class__]
|
access_class = access_registry[obj.__class__]
|
||||||
@@ -2428,7 +2429,7 @@ class UnifiedJobAccess(BaseAccess):
|
|||||||
Q(adhoccommand__inventory__id__in=inv_pk_qs) |
|
Q(adhoccommand__inventory__id__in=inv_pk_qs) |
|
||||||
Q(job__inventory__organization__in=org_auditor_qs) |
|
Q(job__inventory__organization__in=org_auditor_qs) |
|
||||||
Q(job__project__organization__in=org_auditor_qs)
|
Q(job__project__organization__in=org_auditor_qs)
|
||||||
)
|
).exclude(polymorphic_ctype__model='workflowapproval') # &&&&&&
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
@@ -2793,7 +2794,7 @@ class WorkflowApprovalAccess(BaseAccess):
|
|||||||
|
|
||||||
def filtered_queryset(self):
|
def filtered_queryset(self):
|
||||||
return self.model.objects.filter(
|
return self.model.objects.filter(
|
||||||
unified_job_node__in=WorkflowJobNode.accessible_pk_qs(
|
unified_job_node__workflow_job__unified_job_template__in=WorkflowJobTemplate.accessible_pk_qs(
|
||||||
self.user, 'read_role'))
|
self.user, 'read_role'))
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@@ -2801,7 +2802,8 @@ class WorkflowApprovalAccess(BaseAccess):
|
|||||||
workflow_approval_template__isnull=False)
|
workflow_approval_template__isnull=False)
|
||||||
|
|
||||||
def can_approve_or_deny(self, obj):
|
def can_approve_or_deny(self, obj):
|
||||||
if self.user.approval_role or self.user.system_administrator:
|
wfjt = obj.unified_job_node.workflow_job.unified_job_template
|
||||||
|
if self.user in wfjt.approval_role or self.user.is_superuser:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -482,6 +482,10 @@ class TaskManager():
|
|||||||
found_acceptable_queue = False
|
found_acceptable_queue = False
|
||||||
idle_instance_that_fits = None
|
idle_instance_that_fits = None
|
||||||
if isinstance(task, WorkflowJob):
|
if isinstance(task, WorkflowJob):
|
||||||
|
# &&&&&& Timeout implementation (pseudo-code)
|
||||||
|
# if (tz_now() - task.created).seconds > the_timeout_in_seconds:
|
||||||
|
# logger.error('meaning log message')
|
||||||
|
# mark it as status failed and set a reasonable `job_explanation` value
|
||||||
if task.unified_job_template_id in running_workflow_templates:
|
if task.unified_job_template_id in running_workflow_templates:
|
||||||
if not task.allow_simultaneous:
|
if not task.allow_simultaneous:
|
||||||
logger.debug("{} is blocked from running, workflow already running".format(task.log_format))
|
logger.debug("{} is blocked from running, workflow already running".format(task.log_format))
|
||||||
|
|||||||
@@ -505,6 +505,10 @@ def activity_stream_update(sender, instance, **kwargs):
|
|||||||
else:
|
else:
|
||||||
activity_entry.setting = conf_to_dict(instance)
|
activity_entry.setting = conf_to_dict(instance)
|
||||||
activity_entry.save()
|
activity_entry.save()
|
||||||
|
# &&&&&&
|
||||||
|
# for approvals in kwargs['pk_set']:
|
||||||
|
# if isinstance(WorkflowApprovalTemplate) or isinstance(kwargs['model'].objects.filter(id=approvals), WorkflowApprovalTemplate):
|
||||||
|
# continue
|
||||||
|
|
||||||
|
|
||||||
def activity_stream_delete(sender, instance, **kwargs):
|
def activity_stream_delete(sender, instance, **kwargs):
|
||||||
@@ -641,14 +645,14 @@ def delete_inventory_for_org(sender, instance, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=WorkflowJobTemplateNode)
|
@receiver(pre_delete, sender=WorkflowJobTemplateNode)
|
||||||
def delete_approval_nodes(sender, instance, **kwargs):
|
def delete_approval_templates(sender, instance, **kwargs):
|
||||||
if type(instance.unified_job_template) is WorkflowApprovalTemplate:
|
if type(instance.unified_job_template) is WorkflowApprovalTemplate:
|
||||||
instance.unified_job_template.delete()
|
instance.unified_job_template.delete()
|
||||||
|
|
||||||
|
|
||||||
# When setting UJT to anything other than "is approval node" - update this comment!
|
# When setting UJT to anything other than "is approval node" - delete this comment!
|
||||||
@receiver(pre_save, sender=WorkflowJobTemplateNode)
|
@receiver(pre_save, sender=WorkflowJobTemplateNode)
|
||||||
def placeholder_name(sender, instance, **kwargs):
|
def delete_approval_node_type_change(sender, instance, **kwargs):
|
||||||
try:
|
try:
|
||||||
old = WorkflowJobTemplateNode.objects.get(id=instance.id)
|
old = WorkflowJobTemplateNode.objects.get(id=instance.id)
|
||||||
except sender.DoesNotExist:
|
except sender.DoesNotExist:
|
||||||
@@ -659,6 +663,13 @@ def placeholder_name(sender, instance, **kwargs):
|
|||||||
old.unified_job_template.delete()
|
old.unified_job_template.delete()
|
||||||
|
|
||||||
|
|
||||||
|
# &&&&&& New stuff to test!
|
||||||
|
@receiver(post_delete, sender=WorkflowApprovalTemplate)
|
||||||
|
def deny_orphaned_approvals(sender, instance, **kwargs):
|
||||||
|
for approval in WorkflowApproval.objects.filter(workflow_approval_template=instance, status='pending'):
|
||||||
|
approval.deny()
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Session)
|
@receiver(post_save, sender=Session)
|
||||||
def save_user_session_membership(sender, **kwargs):
|
def save_user_session_membership(sender, **kwargs):
|
||||||
session = kwargs.get('instance', None)
|
session = kwargs.get('instance', None)
|
||||||
|
|||||||
Reference in New Issue
Block a user