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>
|
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)
|
valid = models.BooleanField(default=False)
|
||||||
need_token = models.BooleanField(default=False)
|
need_token = models.BooleanField(default=False)
|
||||||
task = models.ForeignKey('api.Task')
|
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()
|
order = models.IntegerField()
|
||||||
|
|
||||||
created = models.DateTimeField(default=timezone.now)
|
created = models.DateTimeField(default=timezone.now)
|
||||||
|
|
||||||
def get_action(self):
|
def get_action(self):
|
||||||
|
@ -114,6 +114,15 @@ class BaseAction(object):
|
|||||||
self.action.cache["token_fields"] = token_fields
|
self.action.cache["token_fields"] = token_fields
|
||||||
self.action.save()
|
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):
|
def add_note(self, note):
|
||||||
"""
|
"""
|
||||||
Logs the note, and also adds it to the task action notes.
|
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):
|
def _pre_approve(self):
|
||||||
self._validate()
|
self._validate()
|
||||||
|
self.set_auto_approve()
|
||||||
|
|
||||||
def _post_approve(self):
|
def _post_approve(self):
|
||||||
self._validate()
|
self._validate()
|
||||||
@ -291,6 +292,7 @@ class EditUserRolesAction(UserIdAction, ProjectMixin, UserMixin):
|
|||||||
|
|
||||||
def _pre_approve(self):
|
def _pre_approve(self):
|
||||||
self._validate()
|
self._validate()
|
||||||
|
self.set_auto_approve()
|
||||||
|
|
||||||
def _post_approve(self):
|
def _post_approve(self):
|
||||||
self._validate()
|
self._validate()
|
||||||
|
@ -218,10 +218,7 @@ class UserRoles(tasks.TaskView):
|
|||||||
timezone.now())
|
timezone.now())
|
||||||
return Response(errors, status=status)
|
return Response(errors, status=status)
|
||||||
|
|
||||||
task = processed['task']
|
response_dict = {'notes': processed.get('notes')}
|
||||||
self.logger.info("(%s) - AutoApproving EditUser request."
|
|
||||||
% timezone.now())
|
|
||||||
response_dict, status = self.approve(request, task)
|
|
||||||
|
|
||||||
add_task_id_for_roles(request, processed, response_dict, ['admin'])
|
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
|
a Task and the linked actions, attaching notes
|
||||||
based on running of the the pre_approve validation
|
based on running of the the pre_approve validation
|
||||||
function on all the actions.
|
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(
|
class_conf = settings.TASK_SETTINGS.get(
|
||||||
self.task_type, settings.DEFAULT_TASK_SETTINGS)
|
self.task_type, settings.DEFAULT_TASK_SETTINGS)
|
||||||
@ -211,6 +215,29 @@ class TaskView(APIViewWithLogger):
|
|||||||
email_conf = class_conf.get('emails', {}).get('initial', None)
|
email_conf = class_conf.get('emails', {}).get('initial', None)
|
||||||
send_email(task, email_conf)
|
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
|
return {'task': task}, 200
|
||||||
|
|
||||||
def _create_token(self, task):
|
def _create_token(self, task):
|
||||||
@ -417,13 +444,12 @@ class InviteUser(TaskView):
|
|||||||
if errors:
|
if errors:
|
||||||
self.logger.info("(%s) - Validation errors with task." %
|
self.logger.info("(%s) - Validation errors with task." %
|
||||||
timezone.now())
|
timezone.now())
|
||||||
return Response(errors, status=status)
|
|
||||||
|
|
||||||
task = processed['task']
|
if isinstance(errors, dict):
|
||||||
self.logger.info("(%s) - AutoApproving AttachUser request."
|
return Response(errors, status=status)
|
||||||
% timezone.now())
|
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'])
|
add_task_id_for_roles(request, processed, response_dict, ['admin'])
|
||||||
|
|
||||||
@ -472,6 +498,8 @@ class ResetPassword(TaskView):
|
|||||||
self.logger.info("(%s) - AutoApproving Resetuser request."
|
self.logger.info("(%s) - AutoApproving Resetuser request."
|
||||||
% timezone.now())
|
% 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)
|
self.approve(request, task)
|
||||||
response_dict = {'notes': [
|
response_dict = {'notes': [
|
||||||
"If user with email exists, reset token will be issued."]}
|
"If user with email exists, reset token will be issued."]}
|
||||||
@ -548,11 +576,7 @@ class EditUser(TaskView):
|
|||||||
timezone.now())
|
timezone.now())
|
||||||
return Response(errors, status=status)
|
return Response(errors, status=status)
|
||||||
|
|
||||||
task = processed['task']
|
response_dict = {'notes': processed.get('notes')}
|
||||||
self.logger.info("(%s) - AutoApproving EditUser request."
|
|
||||||
% timezone.now())
|
|
||||||
response_dict, status = self.approve(request, task)
|
|
||||||
|
|
||||||
add_task_id_for_roles(request, processed, response_dict, ['admin'])
|
add_task_id_for_roles(request, processed, response_dict, ['admin'])
|
||||||
|
|
||||||
return Response(response_dict, status=status)
|
return Response(response_dict, status=status)
|
||||||
|
Loading…
Reference in New Issue
Block a user