Added auto approval as part of actions
In actions pre-approve stage the function self.set_auto_approve() can be called, to identify the action as one that is allowed to be pre-approved. (True, False and None can be specified). If the function has not been called when auto_approve is accessed it will default to None. This is saved as a new attribute in the actions model. At the task layer before process_actions finishes, it checks to see the status of it's actions auto_approve. If none of these are False and at least one of them is True it will auto approve if all of it's actions have auto_approve set to true, if so instead of returning it it will run (and return the values of) the approve function. ResetPassword is the only pre-approved action that has not been switched to this way, due to possible security implications, as it would return 'actions invalid' if the user did not exist. Change-Id: I678849d212b7e91de541120e0d70ddf08cf9b488
This commit is contained in:
parent
8a2f2f2107
commit
607cc93d67
@ -220,4 +220,9 @@ EMAIL_SETTINGS:
|
||||
EMAIL_HOST_PASSWORD: <password>
|
||||
```
|
||||
|
||||
Once the service has reset, it should now send emails via that server rather than print them to console.
|
||||
Once the service has reset, it should now send emails via that server rather than print them to console.
|
||||
|
||||
## Updating stacktask
|
||||
|
||||
Stacktask doesn't have a typical manage.py file, instead this functionality is installed into the virtual enviroment when stacktask is installed.
|
||||
All of the expected Django functionality can be used using the 'stacktask-api' cli.
|
||||
|
19
stacktask/actions/migrations/0002_action_auto_approve.py
Normal file
19
stacktask/actions/migrations/0002_action_auto_approve.py
Normal file
@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('actions', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='action',
|
||||
name='auto_approve',
|
||||
field=models.NullBooleanField(default=None),
|
||||
),
|
||||
]
|
@ -30,9 +30,16 @@ class Action(models.Model):
|
||||
valid = models.BooleanField(default=False)
|
||||
need_token = models.BooleanField(default=False)
|
||||
task = models.ForeignKey('api.Task')
|
||||
|
||||
# NOTE(amelia): Auto approve is technically a ternary operator
|
||||
# If all in a task are None it will not auto approve
|
||||
# However if at least one action has it set to True it
|
||||
# will auto approve. If any are set to False this will
|
||||
# override all of them.
|
||||
# Can be thought of in terms of priority, None has the
|
||||
# lowest priority, then True with False having the
|
||||
# highest priority
|
||||
auto_approve = models.NullBooleanField(default=None)
|
||||
order = models.IntegerField()
|
||||
|
||||
created = models.DateTimeField(default=timezone.now)
|
||||
|
||||
def get_action(self):
|
||||
|
@ -114,6 +114,15 @@ class BaseAction(object):
|
||||
self.action.cache["token_fields"] = token_fields
|
||||
self.action.save()
|
||||
|
||||
@property
|
||||
def auto_approve(self):
|
||||
return self.action.auto_approve
|
||||
|
||||
def set_auto_approve(self, can_approve=True):
|
||||
self.add_note("Auto approve set to %s." % can_approve)
|
||||
self.action.auto_approve = can_approve
|
||||
self.action.save()
|
||||
|
||||
def add_note(self, note):
|
||||
"""
|
||||
Logs the note, and also adds it to the task action notes.
|
||||
|
@ -100,6 +100,7 @@ class NewUserAction(UserNameAction, ProjectMixin, UserMixin):
|
||||
|
||||
def _pre_approve(self):
|
||||
self._validate()
|
||||
self.set_auto_approve()
|
||||
|
||||
def _post_approve(self):
|
||||
self._validate()
|
||||
@ -291,6 +292,7 @@ class EditUserRolesAction(UserIdAction, ProjectMixin, UserMixin):
|
||||
|
||||
def _pre_approve(self):
|
||||
self._validate()
|
||||
self.set_auto_approve()
|
||||
|
||||
def _post_approve(self):
|
||||
self._validate()
|
||||
|
@ -218,10 +218,7 @@ class UserRoles(tasks.TaskView):
|
||||
timezone.now())
|
||||
return Response(errors, status=status)
|
||||
|
||||
task = processed['task']
|
||||
self.logger.info("(%s) - AutoApproving EditUser request."
|
||||
% timezone.now())
|
||||
response_dict, status = self.approve(request, task)
|
||||
response_dict = {'notes': processed.get('notes')}
|
||||
|
||||
add_task_id_for_roles(request, processed, response_dict, ['admin'])
|
||||
|
||||
|
@ -138,6 +138,10 @@ class TaskView(APIViewWithLogger):
|
||||
a Task and the linked actions, attaching notes
|
||||
based on running of the the pre_approve validation
|
||||
function on all the actions.
|
||||
|
||||
If during the pre_approve step at least one of the actions
|
||||
sets auto_approve to True, and none of them set it to False
|
||||
the approval steps will also be run.
|
||||
"""
|
||||
class_conf = settings.TASK_SETTINGS.get(
|
||||
self.task_type, settings.DEFAULT_TASK_SETTINGS)
|
||||
@ -211,6 +215,29 @@ class TaskView(APIViewWithLogger):
|
||||
email_conf = class_conf.get('emails', {}).get('initial', None)
|
||||
send_email(task, email_conf)
|
||||
|
||||
action_models = task.actions
|
||||
approve_list = [act.get_action().auto_approve for act in action_models]
|
||||
|
||||
# TODO(amelia): It would be nice to explicitly test this, however
|
||||
# currently we don't have the right combinations of
|
||||
# actions to allow for it.
|
||||
if False in approve_list:
|
||||
can_auto_approve = False
|
||||
elif True in approve_list:
|
||||
can_auto_approve = True
|
||||
else:
|
||||
can_auto_approve = False
|
||||
|
||||
if can_auto_approve:
|
||||
task_name = self.__class__.__name__
|
||||
self.logger.info("(%s) - AutoApproving %s request."
|
||||
% (timezone.now(), task_name))
|
||||
approval_data, status = self.approve(request, task)
|
||||
# Additional information that would be otherwise expected
|
||||
approval_data['task'] = task
|
||||
approval_data['auto_approved'] = True
|
||||
return approval_data, status
|
||||
|
||||
return {'task': task}, 200
|
||||
|
||||
def _create_token(self, task):
|
||||
@ -417,13 +444,12 @@ class InviteUser(TaskView):
|
||||
if errors:
|
||||
self.logger.info("(%s) - Validation errors with task." %
|
||||
timezone.now())
|
||||
return Response(errors, status=status)
|
||||
|
||||
task = processed['task']
|
||||
self.logger.info("(%s) - AutoApproving AttachUser request."
|
||||
% timezone.now())
|
||||
if isinstance(errors, dict):
|
||||
return Response(errors, status=status)
|
||||
return Response({'errors': errors}, status=status)
|
||||
|
||||
response_dict, status = self.approve(request, task)
|
||||
response_dict = {'notes': processed['notes']}
|
||||
|
||||
add_task_id_for_roles(request, processed, response_dict, ['admin'])
|
||||
|
||||
@ -472,6 +498,8 @@ class ResetPassword(TaskView):
|
||||
self.logger.info("(%s) - AutoApproving Resetuser request."
|
||||
% timezone.now())
|
||||
|
||||
# NOTE(amelia): Not using auto approve due to security implications
|
||||
# as it will return all errors including whether the user exists
|
||||
self.approve(request, task)
|
||||
response_dict = {'notes': [
|
||||
"If user with email exists, reset token will be issued."]}
|
||||
@ -548,11 +576,7 @@ class EditUser(TaskView):
|
||||
timezone.now())
|
||||
return Response(errors, status=status)
|
||||
|
||||
task = processed['task']
|
||||
self.logger.info("(%s) - AutoApproving EditUser request."
|
||||
% timezone.now())
|
||||
response_dict, status = self.approve(request, task)
|
||||
|
||||
response_dict = {'notes': processed.get('notes')}
|
||||
add_task_id_for_roles(request, processed, response_dict, ['admin'])
|
||||
|
||||
return Response(response_dict, status=status)
|
||||
|
Loading…
Reference in New Issue
Block a user