Refactor of action structure
* Massive restructure of actions folders ** This was followed by a series of renames and changes that were requried throughout the codebase. * The 'tenant_setup' app is gone and merged into the core actions app properly. * Action classes and tests now split into multiple modules. * Clean up of test decorators so they are only set at the class level rather than per test (much cleaner). * Actions now allow version structuring in some capacity similar to the api modules. * Added app configs for 'stacktask.action.v1' and 'stacktask.api.v1' to avoid a duplicate label problem in django. * Moved the startup checks to their own app, and ensured that it is imported last. This is required because 'stacktask.api' was being imported before 'stacktasl.actions.v1' and thus not all the action classes had been setup yet. This way it is a global set of checks that always occur last. Change-Id: Iaf3e8d8147ccbf230b7ca6592dd9a017bee70ddb
This commit is contained in:
parent
16a607a66b
commit
f9330d19ac
@ -8,7 +8,7 @@ ALLOWED_HOSTS:
|
||||
|
||||
ADDITIONAL_APPS:
|
||||
- stacktask.api.v1
|
||||
- stacktask.actions.tenant_setup
|
||||
- stacktask.actions.v1
|
||||
- stacktask.notifications.request_tracker
|
||||
|
||||
DATABASES:
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,34 +0,0 @@
|
||||
# Copyright (C) 2015 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class NewDefaultNetworkSerializer(serializers.Serializer):
|
||||
setup_network = serializers.BooleanField(default=True)
|
||||
project_id = serializers.CharField(max_length=64)
|
||||
region = serializers.CharField(max_length=100)
|
||||
|
||||
|
||||
class NewProjectDefaultNetworkSerializer(serializers.Serializer):
|
||||
setup_network = serializers.BooleanField(default=False)
|
||||
region = serializers.CharField(max_length=100)
|
||||
|
||||
|
||||
class AddDefaultUsersToProjectSerializer(serializers.Serializer):
|
||||
domain_id = serializers.CharField(max_length=64, default='default')
|
||||
|
||||
|
||||
class SetProjectQuotaSerializer(serializers.Serializer):
|
||||
pass
|
1
stacktask/actions/v1/__init__.py
Normal file
1
stacktask/actions/v1/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
default_app_config = 'stacktask.actions.v1.app.ActionV1Config'
|
7
stacktask/actions/v1/app.py
Normal file
7
stacktask/actions/v1/app.py
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ActionV1Config(AppConfig):
|
||||
name = "stacktask.actions.v1"
|
||||
label = 'actions_v1'
|
397
stacktask/actions/v1/base.py
Normal file
397
stacktask/actions/v1/base.py
Normal file
@ -0,0 +1,397 @@
|
||||
# Copyright (C) 2015 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
from stacktask.actions import user_store
|
||||
from stacktask.actions.models import Action
|
||||
|
||||
|
||||
class BaseAction(object):
|
||||
"""
|
||||
Base class for the object wrapping around the database model.
|
||||
Setup to allow multiple action types and different internal logic
|
||||
per type but built from a single database type.
|
||||
- 'required' defines what fields to setup from the data.
|
||||
|
||||
If need_token MAY be true, you must implement '_token_email',
|
||||
which should return the email the action wants the token sent to.
|
||||
While there are checks to prevent duplicates or different emails,
|
||||
try and only have one action in your chain provide the email.
|
||||
|
||||
The Action can do anything it needs at one of the three functions
|
||||
called by the views:
|
||||
- 'pre_approve'
|
||||
- 'post_approve'
|
||||
- 'submit'
|
||||
|
||||
All logic and validation should be handled within the action itself,
|
||||
and any other actions it is linked to. The way in which pre_approve,
|
||||
post_approve, and submit are called should rarely change. Actions
|
||||
should be built with those steps in mind, thinking about what they mean,
|
||||
and when they execute.
|
||||
|
||||
By using 'get_cache' and 'set_cache' they can pass data along which
|
||||
may be needed by the action later. This cache is backed to the database.
|
||||
|
||||
Passing data along to other actions is done via the task and
|
||||
it's cache, but this is in memory only, so it is only useful during the
|
||||
same action stage ('post_approve', etc.).
|
||||
|
||||
Other than the task cache, actions should not be altering database
|
||||
models other than themselves. This is not enforced, just a guideline.
|
||||
"""
|
||||
|
||||
required = []
|
||||
|
||||
def __init__(self, data, action_model=None, task=None,
|
||||
order=None):
|
||||
"""
|
||||
Build itself around an existing database model,
|
||||
or build itself and creates a new database model.
|
||||
Sets up required data as fields.
|
||||
"""
|
||||
|
||||
self.logger = getLogger('stacktask')
|
||||
|
||||
for field in self.required:
|
||||
field_data = data[field]
|
||||
setattr(self, field, field_data)
|
||||
|
||||
if action_model:
|
||||
self.action = action_model
|
||||
else:
|
||||
# make new model and save in db
|
||||
action = Action.objects.create(
|
||||
action_name=self.__class__.__name__,
|
||||
action_data=data,
|
||||
task=task,
|
||||
order=order
|
||||
)
|
||||
action.save()
|
||||
self.action = action
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
return self.action.valid
|
||||
|
||||
@property
|
||||
def need_token(self):
|
||||
return self.action.need_token
|
||||
|
||||
def get_email(self):
|
||||
return self._get_email()
|
||||
|
||||
def _get_email(self):
|
||||
return None
|
||||
|
||||
def get_cache(self, key):
|
||||
return self.action.cache.get(key, None)
|
||||
|
||||
def set_cache(self, key, value):
|
||||
self.action.cache[key] = value
|
||||
self.action.save()
|
||||
|
||||
@property
|
||||
def token_fields(self):
|
||||
return self.action.cache.get("token_fields", [])
|
||||
|
||||
def set_token_fields(self, token_fields):
|
||||
self.action.cache["token_fields"] = token_fields
|
||||
self.action.save()
|
||||
|
||||
def add_note(self, note):
|
||||
"""
|
||||
Logs the note, and also adds it to the task action notes.
|
||||
"""
|
||||
self.logger.info("(%s) - %s" % (timezone.now(), note))
|
||||
note = "%s - (%s)" % (note, timezone.now())
|
||||
self.action.task.add_action_note(
|
||||
str(self), note)
|
||||
|
||||
def pre_approve(self):
|
||||
return self._pre_approve()
|
||||
|
||||
def post_approve(self):
|
||||
return self._post_approve()
|
||||
|
||||
def submit(self, token_data):
|
||||
return self._submit(token_data)
|
||||
|
||||
def _pre_approve(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def _post_approve(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def _submit(self, token_data):
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
|
||||
class ResourceMixin(object):
|
||||
"""Base Mixin class for dealing with Openstack resources."""
|
||||
|
||||
def _validate_keystone_user(self):
|
||||
keystone_user = self.action.task.keystone_user
|
||||
|
||||
if keystone_user['project_domain_id'] != self.domain_id:
|
||||
self.add_note('Domain id does not match keystone user domain.')
|
||||
return False
|
||||
|
||||
if keystone_user['project_id'] != self.project_id:
|
||||
self.add_note('Project id does not match keystone user project.')
|
||||
return False
|
||||
return True
|
||||
|
||||
def _validate_domain_id(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
domain = id_manager.get_domain(self.domain_id)
|
||||
if not domain:
|
||||
self.add_note('Domain does not exist.')
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _validate_project_id(self):
|
||||
# Handle an edge_case where some actions set their
|
||||
# own project_id value.
|
||||
if not self.project_id:
|
||||
self.add_note('No project_id given.')
|
||||
return False
|
||||
|
||||
# Now actually check the project exists.
|
||||
id_manager = user_store.IdentityManager()
|
||||
project = id_manager.get_project(self.project_id)
|
||||
if not project:
|
||||
self.add_note('Project with id %s does not exist.' %
|
||||
self.project_id)
|
||||
return False
|
||||
self.add_note('Project with id %s exists.' % self.project_id)
|
||||
return True
|
||||
|
||||
def _validate_domain_name(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
self.domain = id_manager.find_domain(self.domain_name)
|
||||
if not self.domain:
|
||||
self.add_note('Domain does not exist.')
|
||||
return False
|
||||
# also store the domain_id separately for later use
|
||||
self.domain_id = self.domain.id
|
||||
return True
|
||||
|
||||
|
||||
class UserMixin(ResourceMixin):
|
||||
"""Mixin with functions for users."""
|
||||
|
||||
# Accessors
|
||||
def _validate_username_exists(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
|
||||
self.user = id_manager.find_user(self.username, self.domain.id)
|
||||
if not self.user:
|
||||
self.add_note('No user present with username')
|
||||
return False
|
||||
return True
|
||||
|
||||
def _validate_role_permissions(self):
|
||||
keystone_user = self.action.task.keystone_user
|
||||
# Role permissions check
|
||||
if not self.are_roles_managable(user_roles=keystone_user['roles'],
|
||||
requested_roles=self.roles):
|
||||
self.add_note('User does not have permission to edit role(s).')
|
||||
return False
|
||||
return True
|
||||
|
||||
def are_roles_managable(self, user_roles=[], requested_roles=[]):
|
||||
requested_roles = set(requested_roles)
|
||||
# blacklist checks
|
||||
blacklist_roles = set(['admin'])
|
||||
if len(blacklist_roles & requested_roles) > 0:
|
||||
return False
|
||||
|
||||
# user managable role
|
||||
managable_roles = user_store.get_managable_roles(user_roles)
|
||||
intersection = set(managable_roles) & requested_roles
|
||||
# if all requested roles match, we can proceed
|
||||
return intersection == requested_roles
|
||||
|
||||
def find_user(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
return id_manager.find_user(self.username, self.domain_id)
|
||||
|
||||
# Mutators
|
||||
def grant_roles(self, user, roles, project_id):
|
||||
return self._user_roles_edit(user, roles, project_id, remove=False)
|
||||
|
||||
def remove_roles(self, user, roles, project_id):
|
||||
return self._user_roles_edit(user, roles, project_id, remove=True)
|
||||
|
||||
# Helper function to add or remove roles
|
||||
def _user_roles_edit(self, user, roles, project_id, remove=False):
|
||||
id_manager = user_store.IdentityManager()
|
||||
if not remove:
|
||||
action_fn = id_manager.add_user_role
|
||||
action_string = "granting"
|
||||
else:
|
||||
action_fn = id_manager.remove_user_role
|
||||
action_string = "removing"
|
||||
ks_roles = []
|
||||
try:
|
||||
for role in roles:
|
||||
ks_role = id_manager.find_role(role)
|
||||
if ks_role:
|
||||
ks_roles.append(ks_role)
|
||||
else:
|
||||
raise TypeError("Keystone missing role: %s" % role)
|
||||
|
||||
for role in ks_roles:
|
||||
action_fn(user, role, project_id)
|
||||
except Exception as e:
|
||||
self.add_note(
|
||||
"Error: '%s' while %s the roles: %s on user: %s " %
|
||||
(e, action_string, self.roles, user))
|
||||
raise
|
||||
|
||||
def enable_user(self, user=None):
|
||||
id_manager = user_store.IdentityManager()
|
||||
try:
|
||||
if not user:
|
||||
user = self.find_user()
|
||||
id_manager.enable_user(user)
|
||||
except Exception as e:
|
||||
self.add_note(
|
||||
"Error: '%s' while re-enabling user: %s with roles: %s" %
|
||||
(e, self.username, self.roles))
|
||||
raise
|
||||
|
||||
def create_user(self, password):
|
||||
id_manager = user_store.IdentityManager()
|
||||
try:
|
||||
user = id_manager.create_user(
|
||||
name=self.username, password=password,
|
||||
email=self.email, domain=self.domain_id,
|
||||
created_on=str(timezone.now()))
|
||||
except Exception as e:
|
||||
# TODO: Narrow the Exceptions caught to a relevant set.
|
||||
self.add_note(
|
||||
"Error: '%s' while creating user: %s with roles: %s" %
|
||||
(e, self.username, self.roles))
|
||||
raise
|
||||
return user
|
||||
|
||||
def update_password(self, password, user=None):
|
||||
id_manager = user_store.IdentityManager()
|
||||
try:
|
||||
if not user:
|
||||
user = self.find_user()
|
||||
id_manager.update_user_password(user, password)
|
||||
except Exception as e:
|
||||
self.add_note(
|
||||
"Error: '%s' while changing password for user: %s" %
|
||||
(e, self.username))
|
||||
raise
|
||||
|
||||
|
||||
class ProjectMixin(ResourceMixin):
|
||||
"""Mixin with functions for projects."""
|
||||
|
||||
def _validate_parent_project(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
# NOTE(adriant): If parent id is None, Keystone defaults to the domain.
|
||||
# So we only care to validate if parent_id is not None.
|
||||
if self.parent_id:
|
||||
parent = id_manager.get_project(self.parent_id)
|
||||
if not parent:
|
||||
self.add_note("Parent id: '%s' does not exist." %
|
||||
self.project_name)
|
||||
return False
|
||||
return True
|
||||
|
||||
def _validate_project_absent(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
project = id_manager.find_project(
|
||||
self.project_name, self.domain_id)
|
||||
if project:
|
||||
self.add_note("Existing project with name '%s'." %
|
||||
self.project_name)
|
||||
return False
|
||||
|
||||
self.add_note("No existing project with name '%s'." %
|
||||
self.project_name)
|
||||
return True
|
||||
|
||||
def _create_project(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
try:
|
||||
project = id_manager.create_project(
|
||||
self.project_name, created_on=str(timezone.now()),
|
||||
parent=self.parent_id, domain=self.domain_id)
|
||||
except Exception as e:
|
||||
self.add_note(
|
||||
"Error: '%s' while creating project: %s" %
|
||||
(e, self.project_name))
|
||||
raise
|
||||
# put project_id into action cache:
|
||||
self.action.task.cache['project_id'] = project.id
|
||||
self.set_cache('project_id', project.id)
|
||||
self.add_note("New project '%s' created." % project.name)
|
||||
|
||||
|
||||
class UserIdAction(BaseAction):
|
||||
|
||||
def _get_target_user(self):
|
||||
"""
|
||||
Gets the target user by id
|
||||
"""
|
||||
id_manager = user_store.IdentityManager()
|
||||
user = id_manager.get_user(self.user_id)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
class UserNameAction(BaseAction):
|
||||
"""
|
||||
Base action for dealing with users. Removes username if
|
||||
USERNAME_IS_EMAIL and sets email to be username.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if settings.USERNAME_IS_EMAIL:
|
||||
try:
|
||||
self.required.remove('username')
|
||||
except ValueError:
|
||||
pass
|
||||
# nothing to remove
|
||||
super(UserNameAction, self).__init__(*args, **kwargs)
|
||||
self.username = self.email
|
||||
else:
|
||||
super(UserNameAction, self).__init__(*args, **kwargs)
|
||||
|
||||
def _get_email(self):
|
||||
return self.email
|
||||
|
||||
def _get_target_user(self):
|
||||
"""
|
||||
Gets the target user by their username
|
||||
"""
|
||||
id_manager = user_store.IdentityManager()
|
||||
user = id_manager.find_user(self.username, self.domain_id)
|
||||
|
||||
return user
|
54
stacktask/actions/v1/models.py
Normal file
54
stacktask/actions/v1/models.py
Normal file
@ -0,0 +1,54 @@
|
||||
# Copyright (C) 2015 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from stacktask.actions.v1 import serializers
|
||||
from stacktask.actions.v1.projects import (
|
||||
NewProjectWithUserAction, AddDefaultUsersToProjectAction)
|
||||
from stacktask.actions.v1.users import (
|
||||
EditUserRolesAction, NewUserAction, ResetUserPasswordAction)
|
||||
from stacktask.actions.v1.resources import (
|
||||
NewDefaultNetworkAction, NewProjectDefaultNetworkAction,
|
||||
SetProjectQuotaAction)
|
||||
|
||||
|
||||
# Update settings dict with tuples in the format:
|
||||
# (<ActionClass>, <ActionSerializer>)
|
||||
def register_action_class(action_class, serializer_class):
|
||||
data = {}
|
||||
data[action_class.__name__] = (action_class, serializer_class)
|
||||
settings.ACTION_CLASSES.update(data)
|
||||
|
||||
|
||||
# Register Project actions:
|
||||
register_action_class(
|
||||
NewProjectWithUserAction, serializers.NewProjectWithUserSerializer)
|
||||
register_action_class(
|
||||
AddDefaultUsersToProjectAction,
|
||||
serializers.AddDefaultUsersToProjectSerializer)
|
||||
|
||||
# Register User actions:
|
||||
register_action_class(NewUserAction, serializers.NewUserSerializer)
|
||||
register_action_class(ResetUserPasswordAction, serializers.ResetUserSerializer)
|
||||
register_action_class(EditUserRolesAction, serializers.EditUserRolesSerializer)
|
||||
|
||||
# Register Resource actions:
|
||||
register_action_class(
|
||||
NewDefaultNetworkAction, serializers.NewDefaultNetworkSerializer)
|
||||
register_action_class(
|
||||
NewProjectDefaultNetworkAction,
|
||||
serializers.NewProjectDefaultNetworkSerializer)
|
||||
register_action_class(
|
||||
SetProjectQuotaAction, serializers.SetProjectQuotaSerializer)
|
456
stacktask/actions/v1/projects.py
Normal file
456
stacktask/actions/v1/projects.py
Normal file
@ -0,0 +1,456 @@
|
||||
# Copyright (C) 2015 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
from stacktask.actions import user_store
|
||||
from stacktask.actions.v1.base import (
|
||||
BaseAction, UserNameAction, UserMixin, ProjectMixin)
|
||||
|
||||
|
||||
# TODO(adriant): Write tests for this action.
|
||||
class NewProjectAction(BaseAction, ProjectMixin, UserMixin):
|
||||
"""
|
||||
Creates a new project for the current keystone_user.
|
||||
|
||||
This action can only be used for an autheticated taskview.
|
||||
"""
|
||||
|
||||
required = [
|
||||
'domain_id',
|
||||
'parent_id',
|
||||
'project_name',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NewProjectAction, self).__init__(*args, **kwargs)
|
||||
|
||||
def _validate(self):
|
||||
self.action.valid = (
|
||||
self._validate_domain_id() and
|
||||
self._validate_parent_project() and
|
||||
self._validate_project_absent())
|
||||
self.action.save()
|
||||
|
||||
def _validate_domain_id(self):
|
||||
keystone_user = self.action.task.keystone_user
|
||||
|
||||
if keystone_user['project_domain_id'] != self.domain_id:
|
||||
self.add_note('Domain id does not match keystone user domain.')
|
||||
return False
|
||||
|
||||
return super(NewProjectAction, self)._validate_domain_id()
|
||||
|
||||
def _validate_parent_project(self):
|
||||
if self.parent_id:
|
||||
keystone_user = self.action.task.keystone_user
|
||||
|
||||
if self.parent_id != keystone_user['project_id']:
|
||||
self.add_note(
|
||||
'Parent id does not match keystone user project.')
|
||||
return False
|
||||
return super(NewProjectAction, self)._validate_parent_project()
|
||||
return True
|
||||
|
||||
def _pre_approve(self):
|
||||
self._validate()
|
||||
|
||||
def _post_approve(self):
|
||||
project_id = self.get_cache('project_id')
|
||||
if project_id:
|
||||
self.action.task.cache['project_id'] = project_id
|
||||
self.add_note("Project already created.")
|
||||
else:
|
||||
self._validate()
|
||||
|
||||
if not self.valid:
|
||||
return
|
||||
|
||||
self._create_project()
|
||||
|
||||
user_id = self.get_cache('user_id')
|
||||
if user_id:
|
||||
self.action.task.cache['user_id'] = user_id
|
||||
self.add_note("User already given roles.")
|
||||
else:
|
||||
default_roles = settings.ACTION_SETTINGS.get(
|
||||
'NewProjectAction', {}).get("default_roles", {})
|
||||
|
||||
project_id = self.get_cache('project_id')
|
||||
keystone_user = self.action.task.keystone_user
|
||||
|
||||
try:
|
||||
id_manager = user_store.IdentityManager()
|
||||
user = id_manager.get_user(keystone_user['user_id'])
|
||||
|
||||
self.grant_roles(user, default_roles, project_id)
|
||||
except Exception as e:
|
||||
self.add_note(
|
||||
("Error: '%s' while adding roles %s "
|
||||
"to user '%s' on project '%s'") %
|
||||
(e, self.username, default_roles, project_id))
|
||||
raise
|
||||
|
||||
# put user_id into action cache:
|
||||
self.action.task.cache['user_id'] = user.id
|
||||
self.set_cache('user_id', user.id)
|
||||
self.add_note(("Existing user '%s' attached to project %s" +
|
||||
" with roles: %s")
|
||||
% (self.username, project_id,
|
||||
default_roles))
|
||||
|
||||
def _submit(self, token_data):
|
||||
"""
|
||||
Nothing to do here. Everything is done at post_approve.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NewProjectWithUserAction(UserNameAction, ProjectMixin, UserMixin):
|
||||
"""
|
||||
Makes a new project for the given username. Will create the user if it
|
||||
doesn't exists.
|
||||
"""
|
||||
|
||||
required = [
|
||||
'domain_id',
|
||||
'parent_id',
|
||||
'project_name',
|
||||
'username',
|
||||
'email'
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NewProjectWithUserAction, self).__init__(*args, **kwargs)
|
||||
|
||||
def _validate(self):
|
||||
self.action.valid = (
|
||||
self._validate_domain_id() and
|
||||
self._validate_parent_project() and
|
||||
self._validate_project_absent() and
|
||||
self._validate_user())
|
||||
self.action.save()
|
||||
|
||||
def _validate_user(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
user = id_manager.find_user(self.username, self.domain_id)
|
||||
|
||||
if not user:
|
||||
# add to cache to use in template
|
||||
self.action.task.cache['user_state'] = "default"
|
||||
self.action.need_token = True
|
||||
self.set_token_fields(["password"])
|
||||
self.add_note("No user present with username '%s'." %
|
||||
self.username)
|
||||
return True
|
||||
|
||||
if user.email != self.email:
|
||||
self.add_note("Existing user '%s' with non-matching email." %
|
||||
self.username)
|
||||
return False
|
||||
|
||||
if not user.enabled:
|
||||
self.action.state = "disabled"
|
||||
# add to cache to use in template
|
||||
self.action.task.cache['user_state'] = "disabled"
|
||||
self.action.need_token = True
|
||||
self.add_note(
|
||||
"Existing disabled user '%s' with matching email." %
|
||||
self.email)
|
||||
return True
|
||||
else:
|
||||
self.action.state = "existing"
|
||||
# add to cache to use in template
|
||||
self.action.task.cache['user_state'] = "existing"
|
||||
self.action.need_token = False
|
||||
self.add_note("Existing user '%s' with matching email." %
|
||||
self.email)
|
||||
return True
|
||||
|
||||
def _validate_user_submit(self):
|
||||
user_id = self.get_cache('user_id')
|
||||
project_id = self.get_cache('project_id')
|
||||
|
||||
id_manager = user_store.IdentityManager()
|
||||
|
||||
user = id_manager.get_user(user_id)
|
||||
project = id_manager.get_project(project_id)
|
||||
|
||||
if user and project:
|
||||
self.action.valid = True
|
||||
else:
|
||||
self.action.valid = False
|
||||
|
||||
self.action.save()
|
||||
|
||||
def _pre_approve(self):
|
||||
self._validate()
|
||||
|
||||
def _post_approve(self):
|
||||
"""
|
||||
Approving a new project means we set up the project itself,
|
||||
and if the user doesn't exist, create it right away. An existing
|
||||
user automatically gets added to the new project.
|
||||
"""
|
||||
project_id = self.get_cache('project_id')
|
||||
if project_id:
|
||||
self.action.task.cache['project_id'] = project_id
|
||||
self.add_note("Project already created.")
|
||||
else:
|
||||
self.action.valid = (
|
||||
self._validate_domain_id() and
|
||||
self._validate_parent_project() and
|
||||
self._validate_project_absent())
|
||||
self.action.save()
|
||||
|
||||
if not self.valid:
|
||||
return
|
||||
|
||||
self._create_project()
|
||||
|
||||
# User validation and checks
|
||||
user_id = self.get_cache('user_id')
|
||||
roles_granted = self.get_cache('roles_granted')
|
||||
if user_id and roles_granted:
|
||||
self.action.task.cache['user_id'] = user_id
|
||||
self.add_note("User already setup.")
|
||||
elif not user_id:
|
||||
self.action.valid = self._validate_user()
|
||||
self.action.save()
|
||||
|
||||
if not self.valid:
|
||||
return
|
||||
|
||||
self._create_user_for_project()
|
||||
elif not roles_granted:
|
||||
self._create_user_for_project()
|
||||
|
||||
def _create_user_for_project(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
default_roles = settings.ACTION_SETTINGS.get(
|
||||
'NewProjectAction', {}).get("default_roles", {})
|
||||
|
||||
project_id = self.get_cache('project_id')
|
||||
|
||||
if self.action.state == "default":
|
||||
try:
|
||||
# Generate a temporary password:
|
||||
password = uuid4().hex + uuid4().hex
|
||||
|
||||
user_id = self.get_cache('user_id')
|
||||
if not user_id:
|
||||
user = id_manager.create_user(
|
||||
name=self.username, password=password,
|
||||
email=self.email, domain=self.domain_id,
|
||||
created_on=str(timezone.now()))
|
||||
self.set_cache('user_id', user.id)
|
||||
else:
|
||||
user = id_manager.get_user(user_id)
|
||||
# put user_id into action cache:
|
||||
self.action.task.cache['user_id'] = user.id
|
||||
|
||||
self.grant_roles(user, default_roles, project_id)
|
||||
except Exception as e:
|
||||
self.add_note(
|
||||
"Error: '%s' while creating user: %s with roles: %s" %
|
||||
(e, self.username, default_roles))
|
||||
raise
|
||||
|
||||
self.set_cache('roles_granted', True)
|
||||
self.add_note(
|
||||
"New user '%s' created for project %s with roles: %s" %
|
||||
(self.username, project_id, default_roles))
|
||||
elif self.action.state == "existing":
|
||||
try:
|
||||
user_id = self.get_cache('user_id')
|
||||
if not user_id:
|
||||
user = id_manager.find_user(
|
||||
self.username, self.domain_id)
|
||||
self.set_cache('user_id', user.id)
|
||||
else:
|
||||
user = id_manager.get_user(user_id)
|
||||
self.action.task.cache['user_id'] = user.id
|
||||
|
||||
self.grant_roles(user, default_roles, project_id)
|
||||
except Exception as e:
|
||||
self.add_note(
|
||||
"Error: '%s' while granting roles: %s to user: %s" %
|
||||
(e, default_roles, self.username))
|
||||
raise
|
||||
|
||||
self.set_cache('roles_granted', True)
|
||||
self.add_note(("Existing user '%s' setup on project %s" +
|
||||
" with roles: %s")
|
||||
% (self.username, project_id,
|
||||
default_roles))
|
||||
elif self.action.state == "disabled":
|
||||
user_id = self.get_cache('user_id')
|
||||
if not user_id:
|
||||
# first re-enable user
|
||||
try:
|
||||
user = id_manager.find_user(self.username, self.domain_id)
|
||||
id_manager.enable_user(user)
|
||||
except Exception as e:
|
||||
self.add_note(
|
||||
"Error: '%s' while re-enabling user: %s" %
|
||||
(e, self.username))
|
||||
raise
|
||||
|
||||
# and now update their password
|
||||
# Generate a temporary password:
|
||||
password = uuid4().hex + uuid4().hex
|
||||
try:
|
||||
id_manager.update_user_password(user, password)
|
||||
except Exception as e:
|
||||
self.add_note(
|
||||
"Error: '%s' while changing password for user: %s" %
|
||||
(e, self.username))
|
||||
raise
|
||||
self.add_note(
|
||||
'User %s password has been changed.' % self.username)
|
||||
|
||||
self.set_cache('user_id', user.id)
|
||||
else:
|
||||
user = id_manager.get_user(user_id)
|
||||
self.action.task.cache['user_id'] = user.id
|
||||
|
||||
# now add their roles
|
||||
roles_granted = self.get_cache('roles_granted')
|
||||
if not roles_granted:
|
||||
try:
|
||||
self.grant_roles(user, default_roles, project_id)
|
||||
except Exception as e:
|
||||
self.add_note(
|
||||
"Error: '%s' while granting user: %s roles: %s" %
|
||||
(e, self.username, default_roles))
|
||||
raise
|
||||
self.set_cache('roles_granted', True)
|
||||
|
||||
self.add_note(("Existing user '%s' setup on project %s" +
|
||||
" with roles: %s")
|
||||
% (self.username, project_id,
|
||||
default_roles))
|
||||
|
||||
def _submit(self, token_data):
|
||||
"""
|
||||
The submit action is performed when a token is submitted.
|
||||
This is done to set a user password only, and so should now only
|
||||
change the user password. The project and user themselves are created
|
||||
on post_approve.
|
||||
"""
|
||||
|
||||
self._validate_user_submit()
|
||||
|
||||
if not self.valid:
|
||||
return
|
||||
|
||||
project_id = self.get_cache('project_id')
|
||||
self.action.task.cache['project_id'] = project_id
|
||||
user_id = self.get_cache('user_id')
|
||||
self.action.task.cache['user_id'] = user_id
|
||||
id_manager = user_store.IdentityManager()
|
||||
|
||||
if self.action.state in ["default", "disabled"]:
|
||||
user = id_manager.get_user(user_id)
|
||||
try:
|
||||
id_manager.update_user_password(
|
||||
user, token_data['password'])
|
||||
except Exception as e:
|
||||
self.add_note(
|
||||
"Error: '%s' while changing password for user: %s" %
|
||||
(e, self.username))
|
||||
raise
|
||||
self.add_note('User %s password has been changed.' % self.username)
|
||||
|
||||
elif self.action.state == "existing":
|
||||
# do nothing, everything is already done.
|
||||
self.add_note(
|
||||
"Existing user '%s' already attached to project %s" % (
|
||||
user_id, project_id))
|
||||
|
||||
|
||||
class AddDefaultUsersToProjectAction(BaseAction, ProjectMixin, UserMixin):
|
||||
"""
|
||||
The purpose of this action is to add a given set of users after
|
||||
the creation of a new Project. This is mainly for administrative
|
||||
purposes, and for users involved with migrations, monitoring, and
|
||||
general admin tasks that should be present by default.
|
||||
"""
|
||||
|
||||
required = [
|
||||
'domain_id',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.users = settings.ACTION_SETTINGS.get(
|
||||
'AddDefaultUsersToProjectAction', {}).get('default_users', [])
|
||||
self.roles = settings.ACTION_SETTINGS.get(
|
||||
'AddDefaultUsersToProjectAction', {}).get('default_roles', [])
|
||||
super(AddDefaultUsersToProjectAction, self).__init__(*args, **kwargs)
|
||||
|
||||
def _validate_users(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
all_found = True
|
||||
for user in self.users:
|
||||
ks_user = id_manager.find_user(user, self.domain_id)
|
||||
if ks_user:
|
||||
self.add_note('User: %s exists.' % user)
|
||||
else:
|
||||
self.add_note('ERROR: User: %s does not exist.' % user)
|
||||
all_found = False
|
||||
|
||||
return all_found
|
||||
|
||||
def _pre_validate(self):
|
||||
self.action.valid = self._validate_users()
|
||||
self.action.save()
|
||||
|
||||
def _validate(self):
|
||||
self.action.valid = (
|
||||
self._validate_users() and
|
||||
self._validate_project_id()
|
||||
)
|
||||
self.action.save()
|
||||
|
||||
def _pre_approve(self):
|
||||
self._pre_validate()
|
||||
|
||||
def _post_approve(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
self.project_id = self.action.task.cache.get('project_id', None)
|
||||
self._validate()
|
||||
|
||||
if self.valid and not self.action.state == "completed":
|
||||
try:
|
||||
for user in self.users:
|
||||
ks_user = id_manager.find_user(user, self.domain_id)
|
||||
|
||||
self.grant_roles(ks_user, self.roles, self.project_id)
|
||||
self.add_note(
|
||||
'User: "%s" given roles: %s on project: %s.' %
|
||||
(ks_user.name, self.roles, self.project_id))
|
||||
except Exception as e:
|
||||
self.add_note(
|
||||
"Error: '%s' while adding users to project: %s" %
|
||||
(e, self.project_id))
|
||||
raise
|
||||
self.action.state = "completed"
|
||||
self.action.save()
|
||||
self.add_note("All users added.")
|
||||
|
||||
def _submit(self, token_data):
|
||||
pass
|
@ -12,8 +12,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from stacktask.actions.models import BaseAction, ProjectMixin, UserMixin
|
||||
from stacktask.actions.tenant_setup import serializers
|
||||
from stacktask.actions.v1.base import BaseAction, ProjectMixin
|
||||
from django.conf import settings
|
||||
from stacktask.actions import openstack_clients, user_store
|
||||
import six
|
||||
@ -217,79 +216,6 @@ class NewProjectDefaultNetworkAction(NewDefaultNetworkAction):
|
||||
self._create_network()
|
||||
|
||||
|
||||
class AddDefaultUsersToProjectAction(BaseAction, ProjectMixin, UserMixin):
|
||||
"""
|
||||
The purpose of this action is to add a given set of users after
|
||||
the creation of a new Project. This is mainly for administrative
|
||||
purposes, and for users involved with migrations, monitoring, and
|
||||
general admin tasks that should be present by default.
|
||||
"""
|
||||
|
||||
required = [
|
||||
'domain_id',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.users = settings.ACTION_SETTINGS.get(
|
||||
'AddDefaultUsersToProjectAction', {}).get('default_users', [])
|
||||
self.roles = settings.ACTION_SETTINGS.get(
|
||||
'AddDefaultUsersToProjectAction', {}).get('default_roles', [])
|
||||
super(AddDefaultUsersToProjectAction, self).__init__(*args, **kwargs)
|
||||
|
||||
def _validate_users(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
all_found = True
|
||||
for user in self.users:
|
||||
ks_user = id_manager.find_user(user, self.domain_id)
|
||||
if ks_user:
|
||||
self.add_note('User: %s exists.' % user)
|
||||
else:
|
||||
self.add_note('ERROR: User: %s does not exist.' % user)
|
||||
all_found = False
|
||||
|
||||
return all_found
|
||||
|
||||
def _pre_validate(self):
|
||||
self.action.valid = self._validate_users()
|
||||
self.action.save()
|
||||
|
||||
def _validate(self):
|
||||
self.action.valid = (
|
||||
self._validate_users() and
|
||||
self._validate_project_id()
|
||||
)
|
||||
self.action.save()
|
||||
|
||||
def _pre_approve(self):
|
||||
self._pre_validate()
|
||||
|
||||
def _post_approve(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
self.project_id = self.action.task.cache.get('project_id', None)
|
||||
self._validate()
|
||||
|
||||
if self.valid and not self.action.state == "completed":
|
||||
try:
|
||||
for user in self.users:
|
||||
ks_user = id_manager.find_user(user, self.domain_id)
|
||||
|
||||
self.grant_roles(ks_user, self.roles, self.project_id)
|
||||
self.add_note(
|
||||
'User: "%s" given roles: %s on project: %s.' %
|
||||
(ks_user.name, self.roles, self.project_id))
|
||||
except Exception as e:
|
||||
self.add_note(
|
||||
"Error: '%s' while adding users to project: %s" %
|
||||
(e, self.project_id))
|
||||
raise
|
||||
self.action.state = "completed"
|
||||
self.action.save()
|
||||
self.add_note("All users added.")
|
||||
|
||||
def _submit(self, token_data):
|
||||
pass
|
||||
|
||||
|
||||
class SetProjectQuotaAction(BaseAction):
|
||||
""" Updates quota for a given project to a configured quota level """
|
||||
|
||||
@ -391,21 +317,3 @@ class SetProjectQuotaAction(BaseAction):
|
||||
|
||||
def _submit(self, token_data):
|
||||
pass
|
||||
|
||||
|
||||
action_classes = {
|
||||
'NewDefaultNetworkAction':
|
||||
(NewDefaultNetworkAction,
|
||||
serializers.NewDefaultNetworkSerializer),
|
||||
'NewProjectDefaultNetworkAction':
|
||||
(NewProjectDefaultNetworkAction,
|
||||
serializers.NewProjectDefaultNetworkSerializer),
|
||||
'AddDefaultUsersToProjectAction':
|
||||
(AddDefaultUsersToProjectAction,
|
||||
serializers.AddDefaultUsersToProjectSerializer),
|
||||
'SetProjectQuotaAction':
|
||||
(SetProjectQuotaAction,
|
||||
serializers.SetProjectQuotaSerializer)
|
||||
}
|
||||
|
||||
settings.ACTION_CLASSES.update(action_classes)
|
@ -68,3 +68,22 @@ class EditUserRolesSerializer(BaseUserIdSerializer):
|
||||
remove = serializers.BooleanField(default=False)
|
||||
project_id = serializers.CharField(max_length=64)
|
||||
domain_id = serializers.CharField(max_length=64, default='default')
|
||||
|
||||
|
||||
class NewDefaultNetworkSerializer(serializers.Serializer):
|
||||
setup_network = serializers.BooleanField(default=True)
|
||||
project_id = serializers.CharField(max_length=64)
|
||||
region = serializers.CharField(max_length=100)
|
||||
|
||||
|
||||
class NewProjectDefaultNetworkSerializer(serializers.Serializer):
|
||||
setup_network = serializers.BooleanField(default=False)
|
||||
region = serializers.CharField(max_length=100)
|
||||
|
||||
|
||||
class AddDefaultUsersToProjectSerializer(serializers.Serializer):
|
||||
domain_id = serializers.CharField(max_length=64, default='default')
|
||||
|
||||
|
||||
class SetProjectQuotaSerializer(serializers.Serializer):
|
||||
pass
|
111
stacktask/actions/v1/tests/__init__.py
Normal file
111
stacktask/actions/v1/tests/__init__.py
Normal file
@ -0,0 +1,111 @@
|
||||
# Copyright (C) 2015 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
neutron_cache = {}
|
||||
nova_cache = {}
|
||||
cinder_cache = {}
|
||||
|
||||
|
||||
class FakeOpenstackClient(object):
|
||||
class Quotas(object):
|
||||
""" Stub class for testing quotas """
|
||||
def __init__(self, service):
|
||||
self.service = service
|
||||
|
||||
def update(self, project_id, **kwargs):
|
||||
self.service.update_quota(project_id, **kwargs)
|
||||
|
||||
def __init__(self, region, cache):
|
||||
self.region = region
|
||||
self._cache = cache
|
||||
self.quotas = FakeOpenstackClient.Quotas(self)
|
||||
|
||||
def update_quota(self, project_id, **kwargs):
|
||||
if self.region not in self._cache:
|
||||
self._cache[self.region] = {}
|
||||
if project_id not in self._cache[self.region]:
|
||||
self._cache[self.region][project_id] = {
|
||||
'quota': {}
|
||||
}
|
||||
quota = self._cache[self.region][project_id]['quota']
|
||||
quota.update(kwargs)
|
||||
|
||||
|
||||
class FakeNeutronClient(object):
|
||||
|
||||
def create_network(self, body):
|
||||
global neutron_cache
|
||||
net = {'network': {'id': 'net_id_%s' % neutron_cache['i'],
|
||||
'body': body}}
|
||||
neutron_cache['networks'][net['network']['id']] = net
|
||||
neutron_cache['i'] += 1
|
||||
return net
|
||||
|
||||
def create_subnet(self, body):
|
||||
global neutron_cache
|
||||
subnet = {'subnet': {'id': 'subnet_id_%s' % neutron_cache['i'],
|
||||
'body': body}}
|
||||
neutron_cache['subnets'][subnet['subnet']['id']] = subnet
|
||||
neutron_cache['i'] += 1
|
||||
return subnet
|
||||
|
||||
def create_router(self, body):
|
||||
global neutron_cache
|
||||
router = {'router': {'id': 'router_id_%s' % neutron_cache['i'],
|
||||
'body': body}}
|
||||
neutron_cache['routers'][router['router']['id']] = router
|
||||
neutron_cache['i'] += 1
|
||||
return router
|
||||
|
||||
def add_interface_router(self, router_id, body):
|
||||
global neutron_cache
|
||||
router = neutron_cache['routers'][router_id]
|
||||
router['router']['interface'] = body
|
||||
return router
|
||||
|
||||
def update_quota(self, project_id, body):
|
||||
global neutron_cache
|
||||
if project_id not in neutron_cache:
|
||||
neutron_cache[project_id] = {}
|
||||
if 'quota' not in neutron_cache[project_id]:
|
||||
neutron_cache[project_id]['quota'] = {}
|
||||
|
||||
quota = neutron_cache[project_id]['quota']
|
||||
quota.update(body['quota'])
|
||||
|
||||
|
||||
def setup_neutron_cache():
|
||||
global neutron_cache
|
||||
neutron_cache.clear()
|
||||
neutron_cache.update({
|
||||
'i': 0,
|
||||
'networks': {},
|
||||
'subnets': {},
|
||||
'routers': {},
|
||||
})
|
||||
|
||||
|
||||
def get_fake_neutron(region):
|
||||
return FakeNeutronClient()
|
||||
|
||||
|
||||
def get_fake_novaclient(region):
|
||||
global nova_cache
|
||||
return FakeOpenstackClient(region, nova_cache)
|
||||
|
||||
|
||||
def get_fake_cinderclient(region):
|
||||
global cinder_cache
|
||||
return FakeOpenstackClient(region, cinder_cache)
|
548
stacktask/actions/v1/tests/test_project_actions.py
Normal file
548
stacktask/actions/v1/tests/test_project_actions.py
Normal file
@ -0,0 +1,548 @@
|
||||
# Copyright (C) 2015 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
import mock
|
||||
|
||||
from stacktask.actions.v1.projects import (
|
||||
NewProjectWithUserAction, AddDefaultUsersToProjectAction)
|
||||
from stacktask.api.models import Task
|
||||
from stacktask.api.v1 import tests
|
||||
from stacktask.api.v1.tests import FakeManager, setup_temp_cache
|
||||
|
||||
|
||||
@mock.patch('stacktask.actions.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
class ProjectActionTests(TestCase):
|
||||
|
||||
def test_new_project(self):
|
||||
"""
|
||||
Base case, no project, no user.
|
||||
|
||||
Project and user created at post_approve step,
|
||||
user password at submit step.
|
||||
"""
|
||||
|
||||
setup_temp_cache({}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={}
|
||||
)
|
||||
|
||||
data = {
|
||||
'domain_id': 'default',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
self.assertEquals(
|
||||
tests.temp_cache['projects']['test_project'].name,
|
||||
'test_project')
|
||||
self.assertEquals(
|
||||
task.cache,
|
||||
{'project_id': 'project_id_1', 'user_id': 'user_id_1',
|
||||
'user_state': 'default'})
|
||||
|
||||
token_data = {'password': '123456'}
|
||||
action.submit(token_data)
|
||||
self.assertEquals(action.valid, True)
|
||||
self.assertEquals(
|
||||
tests.temp_cache['users']["user_id_1"].email,
|
||||
'test@example.com')
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(
|
||||
sorted(project.roles["user_id_1"]),
|
||||
sorted(['_member_', 'project_admin',
|
||||
'project_mod', 'heat_stack_owner']))
|
||||
|
||||
def test_new_project_reapprove(self):
|
||||
"""
|
||||
Project created at post_approve step,
|
||||
ensure reapprove does nothing.
|
||||
"""
|
||||
|
||||
setup_temp_cache({}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={}
|
||||
)
|
||||
|
||||
data = {
|
||||
'domain_id': 'default',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
self.assertEquals(
|
||||
tests.temp_cache['projects']['test_project'].name,
|
||||
'test_project')
|
||||
self.assertEquals(
|
||||
task.cache,
|
||||
{'project_id': 'project_id_1', 'user_id': 'user_id_1',
|
||||
'user_state': 'default'})
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
self.assertEquals(
|
||||
tests.temp_cache['projects']['test_project'].name,
|
||||
'test_project')
|
||||
self.assertEquals(
|
||||
task.cache,
|
||||
{'project_id': 'project_id_1', 'user_id': 'user_id_1',
|
||||
'user_state': 'default'})
|
||||
|
||||
token_data = {'password': '123456'}
|
||||
action.submit(token_data)
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
self.assertEquals(
|
||||
tests.temp_cache['users']["user_id_1"].email,
|
||||
'test@example.com')
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(
|
||||
sorted(project.roles["user_id_1"]),
|
||||
sorted(['_member_', 'project_admin',
|
||||
'project_mod', 'heat_stack_owner']))
|
||||
|
||||
def test_new_project_reapprove_failure(self):
|
||||
"""
|
||||
Project created at post_approve step, failure at role grant.
|
||||
|
||||
Ensure reapprove correctly finishes.
|
||||
"""
|
||||
|
||||
setup_temp_cache({}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={}
|
||||
)
|
||||
|
||||
data = {
|
||||
'domain_id': 'default',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
# NOTE(adrian): We need the code to fail at the
|
||||
# grant roles step so we can attempt reapproving it
|
||||
class FakeException(Exception):
|
||||
pass
|
||||
|
||||
def fail_grant(user, default_roles, project_id):
|
||||
raise FakeException
|
||||
# We swap out the old grant function and keep
|
||||
# it for later.
|
||||
old_grant_function = action.grant_roles
|
||||
action.grant_roles = fail_grant
|
||||
|
||||
# Now we expect the failure
|
||||
self.assertRaises(FakeException, action.post_approve)
|
||||
|
||||
# No roles_granted yet, but user created
|
||||
self.assertTrue("user_id" in action.action.cache)
|
||||
self.assertFalse("roles_granted" in action.action.cache)
|
||||
self.assertEquals(
|
||||
tests.temp_cache['users']["user_id_1"].email,
|
||||
'test@example.com')
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertFalse("user_id_1" in project.roles)
|
||||
|
||||
# And then swap back the correct function
|
||||
action.grant_roles = old_grant_function
|
||||
# and try again, it should work this time
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
# roles_granted in cache
|
||||
self.assertTrue("roles_granted" in action.action.cache)
|
||||
|
||||
token_data = {'password': '123456'}
|
||||
action.submit(token_data)
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(
|
||||
sorted(project.roles["user_id_1"]),
|
||||
sorted(['_member_', 'project_admin',
|
||||
'project_mod', 'heat_stack_owner']))
|
||||
|
||||
def test_new_project_existing_user(self):
|
||||
"""
|
||||
Create a project for a user that already exists.
|
||||
"""
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id_1'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
|
||||
setup_temp_cache({}, {user.id: user})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={}
|
||||
)
|
||||
|
||||
data = {
|
||||
'domain_id': 'default',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
self.assertEquals(
|
||||
tests.temp_cache['projects']['test_project'].name,
|
||||
'test_project')
|
||||
self.assertEquals(
|
||||
task.cache,
|
||||
{'user_id': 'user_id_1', 'project_id': 'project_id_1',
|
||||
'user_state': 'existing'})
|
||||
|
||||
token_data = {'password': '123456'}
|
||||
action.submit(token_data)
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
self.assertEquals(
|
||||
tests.temp_cache['users'][user.id].email,
|
||||
'test@example.com')
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(
|
||||
sorted(project.roles[user.id]),
|
||||
sorted(['_member_', 'project_admin',
|
||||
'project_mod', 'heat_stack_owner']))
|
||||
|
||||
def test_new_project_disabled_user(self):
|
||||
"""
|
||||
Create a project for a user that is disabled.
|
||||
"""
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id_1'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.enabled = False
|
||||
|
||||
# create disabled user
|
||||
setup_temp_cache({}, {user.id: user})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={}
|
||||
)
|
||||
|
||||
data = {
|
||||
'domain_id': 'default',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
# Sign up, approve
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
self.assertEquals(
|
||||
tests.temp_cache['projects']['test_project'].name,
|
||||
'test_project')
|
||||
self.assertEquals(
|
||||
task.cache,
|
||||
{'user_id': 'user_id_1',
|
||||
'project_id': 'project_id_1',
|
||||
'user_state': 'disabled'})
|
||||
|
||||
# submit password reset
|
||||
token_data = {'password': '123456'}
|
||||
action.submit(token_data)
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
# check that user has been created correctly
|
||||
self.assertEquals(
|
||||
tests.temp_cache['users'][user.id].email,
|
||||
'test@example.com')
|
||||
self.assertEquals(
|
||||
tests.temp_cache['users'][user.id].enabled,
|
||||
True)
|
||||
|
||||
# Check user has correct roles in new project
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(
|
||||
sorted(project.roles[user.id]),
|
||||
sorted(['_member_', 'project_admin',
|
||||
'project_mod', 'heat_stack_owner']))
|
||||
|
||||
def test_new_project_user_disabled_during_signup(self):
|
||||
"""
|
||||
Create a project for a user that is created and disabled during signup.
|
||||
|
||||
This exercises the tasks ability to correctly act based on changed
|
||||
circumstances between two states.
|
||||
"""
|
||||
|
||||
# Start with nothing created
|
||||
setup_temp_cache({}, {})
|
||||
|
||||
# Sign up for the project+user, validate.
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={}
|
||||
)
|
||||
|
||||
data = {
|
||||
'domain_id': 'default',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
# Sign up
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
# Create the disabled user directly with the Identity Manager.
|
||||
fm = FakeManager()
|
||||
user = fm.create_user(
|
||||
name="test@example.com",
|
||||
password='origpass',
|
||||
email="test@example.com",
|
||||
created_on=None,
|
||||
domain='default',
|
||||
default_project=None
|
||||
)
|
||||
fm.disable_user(user.id)
|
||||
|
||||
# approve previous signup
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(
|
||||
project.name,
|
||||
'test_project')
|
||||
self.assertEquals(
|
||||
task.cache,
|
||||
{'user_id': user.id,
|
||||
'project_id': project.id,
|
||||
'user_state': 'disabled'})
|
||||
|
||||
# check that user has been re-enabled with a generated password.
|
||||
self.assertEquals(user.enabled, True)
|
||||
self.assertNotEquals(user.password, 'origpass')
|
||||
|
||||
# submit password reset
|
||||
token_data = {'password': '123456'}
|
||||
action.submit(token_data)
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
# Ensure user has new password:
|
||||
self.assertEquals(user.password, '123456')
|
||||
|
||||
def test_new_project_existing_project(self):
|
||||
"""
|
||||
Create a project that already exists.
|
||||
"""
|
||||
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
setup_temp_cache({project.name: project}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={
|
||||
'roles': ['admin', 'project_mod'],
|
||||
'project_id': 'test_project_id',
|
||||
'project_domain_id': 'default',
|
||||
})
|
||||
|
||||
data = {
|
||||
'domain_id': 'default',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, False)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, False)
|
||||
|
||||
def test_new_project_invalid_domain_id(self):
|
||||
""" Create a project using an invalid domain """
|
||||
|
||||
setup_temp_cache({}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={
|
||||
'roles': ['admin', 'project_mod'],
|
||||
'project_id': 'test_project_id',
|
||||
'project_domain_id': 'default',
|
||||
})
|
||||
|
||||
data = {
|
||||
'domain_id': 'not_default_id',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, False)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, False)
|
||||
|
||||
def test_add_default_users(self):
|
||||
"""
|
||||
Base case, adds admin user with admin role to project.
|
||||
|
||||
NOTE(adriant): both the lists of users, and the roles to add
|
||||
come from test_settings. This test assumes the conf setting of:
|
||||
default_users = ['admin']
|
||||
default_roles = ['admin']
|
||||
"""
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
setup_temp_cache({'test_project': project}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
|
||||
|
||||
task.cache = {'project_id': "test_project_id"}
|
||||
|
||||
action = AddDefaultUsersToProjectAction(
|
||||
{'domain_id': 'default'}, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(project.roles['user_id_0'], ['admin'])
|
||||
|
||||
def test_add_default_users_invalid_project(self):
|
||||
"""Add default users to a project that doesn't exist.
|
||||
|
||||
Action should become invalid at the post_approve state, it's ok if
|
||||
the project isn't created yet during pre_approve.
|
||||
"""
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
setup_temp_cache({'test_project': project}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
|
||||
|
||||
task.cache = {'project_id': "invalid_project_id"}
|
||||
|
||||
action = AddDefaultUsersToProjectAction(
|
||||
{'domain_id': 'default'}, task=task, order=1)
|
||||
action.pre_approve()
|
||||
# No need to test project yet - it's ok if it doesn't exist
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
# Now the missing project should make the action invalid
|
||||
self.assertEquals(action.valid, False)
|
||||
|
||||
def test_add_default_users_reapprove(self):
|
||||
"""
|
||||
Ensure nothing happens or changes during rerun of approve.
|
||||
"""
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
setup_temp_cache({'test_project': project}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
|
||||
|
||||
task.cache = {'project_id': "test_project_id"}
|
||||
|
||||
action = AddDefaultUsersToProjectAction(
|
||||
{'domain_id': 'default'}, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(project.roles['user_id_0'], ['admin'])
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(project.roles['user_id_0'], ['admin'])
|
@ -16,120 +16,32 @@ from django.test import TestCase
|
||||
|
||||
import mock
|
||||
|
||||
from stacktask.actions.tenant_setup.models import (
|
||||
from stacktask.actions.v1.resources import (
|
||||
NewDefaultNetworkAction, NewProjectDefaultNetworkAction,
|
||||
AddDefaultUsersToProjectAction, SetProjectQuotaAction)
|
||||
SetProjectQuotaAction)
|
||||
from stacktask.api.models import Task
|
||||
from stacktask.api.v1 import tests
|
||||
from stacktask.api.v1.tests import FakeManager, setup_temp_cache
|
||||
from stacktask.actions.v1.tests import (
|
||||
get_fake_neutron, get_fake_novaclient, get_fake_cinderclient,
|
||||
setup_neutron_cache, neutron_cache, cinder_cache, nova_cache)
|
||||
|
||||
|
||||
neutron_cache = {}
|
||||
nova_cache = {}
|
||||
cinder_cache = {}
|
||||
|
||||
|
||||
class FakeOpenstackClient(object):
|
||||
class Quotas(object):
|
||||
""" Stub class for testing quotas """
|
||||
def __init__(self, service):
|
||||
self.service = service
|
||||
|
||||
def update(self, project_id, **kwargs):
|
||||
self.service.update_quota(project_id, **kwargs)
|
||||
|
||||
def __init__(self, region, cache):
|
||||
self.region = region
|
||||
self._cache = cache
|
||||
self.quotas = FakeOpenstackClient.Quotas(self)
|
||||
|
||||
def update_quota(self, project_id, **kwargs):
|
||||
if self.region not in self._cache:
|
||||
self._cache[self.region] = {}
|
||||
if project_id not in self._cache[self.region]:
|
||||
self._cache[self.region][project_id] = {
|
||||
'quota': {}
|
||||
}
|
||||
quota = self._cache[self.region][project_id]['quota']
|
||||
quota.update(kwargs)
|
||||
|
||||
|
||||
class FakeNeutronClient(object):
|
||||
|
||||
def create_network(self, body):
|
||||
global neutron_cache
|
||||
net = {'network': {'id': 'net_id_%s' % neutron_cache['i'],
|
||||
'body': body}}
|
||||
neutron_cache['networks'][net['network']['id']] = net
|
||||
neutron_cache['i'] += 1
|
||||
return net
|
||||
|
||||
def create_subnet(self, body):
|
||||
global neutron_cache
|
||||
subnet = {'subnet': {'id': 'subnet_id_%s' % neutron_cache['i'],
|
||||
'body': body}}
|
||||
neutron_cache['subnets'][subnet['subnet']['id']] = subnet
|
||||
neutron_cache['i'] += 1
|
||||
return subnet
|
||||
|
||||
def create_router(self, body):
|
||||
global neutron_cache
|
||||
router = {'router': {'id': 'router_id_%s' % neutron_cache['i'],
|
||||
'body': body}}
|
||||
neutron_cache['routers'][router['router']['id']] = router
|
||||
neutron_cache['i'] += 1
|
||||
return router
|
||||
|
||||
def add_interface_router(self, router_id, body):
|
||||
global neutron_cache
|
||||
router = neutron_cache['routers'][router_id]
|
||||
router['router']['interface'] = body
|
||||
return router
|
||||
|
||||
def update_quota(self, project_id, body):
|
||||
global neutron_cache
|
||||
if project_id not in neutron_cache:
|
||||
neutron_cache[project_id] = {}
|
||||
if 'quota' not in neutron_cache[project_id]:
|
||||
neutron_cache[project_id]['quota'] = {}
|
||||
|
||||
quota = neutron_cache[project_id]['quota']
|
||||
quota.update(body['quota'])
|
||||
|
||||
|
||||
def setup_neutron_cache():
|
||||
global neutron_cache
|
||||
neutron_cache = {
|
||||
'i': 0,
|
||||
'networks': {},
|
||||
'subnets': {},
|
||||
'routers': {},
|
||||
}
|
||||
|
||||
|
||||
def get_fake_neutron(region):
|
||||
return FakeNeutronClient()
|
||||
|
||||
|
||||
def get_fake_novaclient(region):
|
||||
global nova_cache
|
||||
return FakeOpenstackClient(region, nova_cache)
|
||||
|
||||
|
||||
def get_fake_cinderclient(region):
|
||||
global cinder_cache
|
||||
return FakeOpenstackClient(region, cinder_cache)
|
||||
|
||||
|
||||
class ProjectSetupActionTests(TestCase):
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
@mock.patch('stacktask.actions.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.' +
|
||||
@mock.patch(
|
||||
'stacktask.actions.v1.resources.' +
|
||||
'openstack_clients.get_neutronclient',
|
||||
get_fake_neutron)
|
||||
@mock.patch(
|
||||
'stacktask.actions.v1.resources.' +
|
||||
'openstack_clients.get_novaclient',
|
||||
get_fake_novaclient)
|
||||
@mock.patch(
|
||||
'stacktask.actions.v1.resources.' +
|
||||
'openstack_clients.get_cinderclient',
|
||||
get_fake_cinderclient)
|
||||
class ProjectSetupActionTests(TestCase):
|
||||
|
||||
def test_network_setup(self):
|
||||
"""
|
||||
Base case, setup a new network , no issues.
|
||||
@ -176,13 +88,6 @@ class ProjectSetupActionTests(TestCase):
|
||||
self.assertEquals(len(neutron_cache['routers']), 1)
|
||||
self.assertEquals(len(neutron_cache['subnets']), 1)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.' +
|
||||
'openstack_clients.get_neutronclient',
|
||||
get_fake_neutron)
|
||||
def test_network_setup_no_setup(self):
|
||||
"""
|
||||
Told not to setup, should do nothing.
|
||||
@ -224,13 +129,6 @@ class ProjectSetupActionTests(TestCase):
|
||||
self.assertEquals(len(neutron_cache['routers']), 0)
|
||||
self.assertEquals(len(neutron_cache['subnets']), 0)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.' +
|
||||
'openstack_clients.get_neutronclient',
|
||||
get_fake_neutron)
|
||||
def test_network_setup_fail(self):
|
||||
"""
|
||||
Should fail, but on re_approve will continue where it left off.
|
||||
@ -296,13 +194,6 @@ class ProjectSetupActionTests(TestCase):
|
||||
self.assertEquals(len(neutron_cache['routers']), 1)
|
||||
self.assertEquals(len(neutron_cache['subnets']), 1)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.' +
|
||||
'openstack_clients.get_neutronclient',
|
||||
get_fake_neutron)
|
||||
def test_new_project_network_setup(self):
|
||||
"""
|
||||
Base case, setup network after a new project, no issues.
|
||||
@ -349,13 +240,6 @@ class ProjectSetupActionTests(TestCase):
|
||||
self.assertEquals(len(neutron_cache['routers']), 1)
|
||||
self.assertEquals(len(neutron_cache['subnets']), 1)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.' +
|
||||
'openstack_clients.get_neutronclient',
|
||||
get_fake_neutron)
|
||||
def test_new_project_network_setup_no_id(self):
|
||||
"""
|
||||
No project id given, should do nothing.
|
||||
@ -385,13 +269,6 @@ class ProjectSetupActionTests(TestCase):
|
||||
self.assertEquals(len(neutron_cache['routers']), 0)
|
||||
self.assertEquals(len(neutron_cache['subnets']), 0)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.' +
|
||||
'openstack_clients.get_neutronclient',
|
||||
get_fake_neutron)
|
||||
def test_new_project_network_setup_no_setup(self):
|
||||
"""
|
||||
Told not to setup, should do nothing.
|
||||
@ -433,13 +310,6 @@ class ProjectSetupActionTests(TestCase):
|
||||
self.assertEquals(len(neutron_cache['routers']), 0)
|
||||
self.assertEquals(len(neutron_cache['subnets']), 0)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.' +
|
||||
'openstack_clients.get_neutronclient',
|
||||
get_fake_neutron)
|
||||
def test_new_project_network_setup_fail(self):
|
||||
"""
|
||||
Should fail, but on re_approve will continue where it left off.
|
||||
@ -505,129 +375,6 @@ class ProjectSetupActionTests(TestCase):
|
||||
self.assertEquals(len(neutron_cache['routers']), 1)
|
||||
self.assertEquals(len(neutron_cache['subnets']), 1)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_add_default_users(self):
|
||||
"""
|
||||
Base case, adds admin user with admin role to project.
|
||||
|
||||
NOTE(adriant): both the lists of users, and the roles to add
|
||||
come from test_settings. This test assumes the conf setting of:
|
||||
default_users = ['admin']
|
||||
default_roles = ['admin']
|
||||
"""
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
setup_temp_cache({'test_project': project}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
|
||||
|
||||
task.cache = {'project_id': "test_project_id"}
|
||||
|
||||
action = AddDefaultUsersToProjectAction(
|
||||
{'domain_id': 'default'}, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(project.roles['user_id_0'], ['admin'])
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_add_default_users_invalid_project(self):
|
||||
"""Add default users to a project that doesn't exist.
|
||||
|
||||
Action should become invalid at the post_approve state, it's ok if
|
||||
the project isn't created yet during pre_approve.
|
||||
"""
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
setup_temp_cache({'test_project': project}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
|
||||
|
||||
task.cache = {'project_id': "invalid_project_id"}
|
||||
|
||||
action = AddDefaultUsersToProjectAction(
|
||||
{'domain_id': 'default'}, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
# No need to test project yet - it's ok if it doesn't exist
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
# Now the missing project should make the action invalid
|
||||
self.assertEquals(action.valid, False)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_add_default_users_reapprove(self):
|
||||
"""
|
||||
Ensure nothing happens or changes during rerun of approve.
|
||||
"""
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
setup_temp_cache({'test_project': project}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
|
||||
|
||||
task.cache = {'project_id': "test_project_id"}
|
||||
|
||||
action = AddDefaultUsersToProjectAction(
|
||||
{'domain_id': 'default'}, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(project.roles['user_id_0'], ['admin'])
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(project.roles['user_id_0'], ['admin'])
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.' +
|
||||
'openstack_clients.get_neutronclient',
|
||||
get_fake_neutron)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.' +
|
||||
'openstack_clients.get_novaclient',
|
||||
get_fake_novaclient)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.' +
|
||||
'openstack_clients.get_cinderclient',
|
||||
get_fake_cinderclient)
|
||||
def test_set_quota(self):
|
||||
"""
|
||||
Base case, sets quota on all services of the cached project id.
|
@ -16,18 +16,17 @@ from django.test import TestCase
|
||||
|
||||
import mock
|
||||
|
||||
from stacktask.actions.models import (
|
||||
EditUserRolesAction, NewProjectWithUserAction, NewUserAction,
|
||||
ResetUserPasswordAction)
|
||||
from stacktask.actions.v1.users import (
|
||||
EditUserRolesAction, NewUserAction, ResetUserPasswordAction)
|
||||
from stacktask.api.models import Task
|
||||
from stacktask.api.v1 import tests
|
||||
from stacktask.api.v1.tests import FakeManager, setup_temp_cache
|
||||
|
||||
|
||||
class ActionTests(TestCase):
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
@mock.patch('stacktask.actions.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
class UserActionTests(TestCase):
|
||||
|
||||
def test_new_user(self):
|
||||
"""
|
||||
Test the default case, all valid.
|
||||
@ -78,8 +77,6 @@ class ActionTests(TestCase):
|
||||
|
||||
self.assertEquals(project.roles["user_id_1"], ['_member_'])
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_user_existing(self):
|
||||
"""
|
||||
Existing user, valid tenant, no role.
|
||||
@ -127,8 +124,6 @@ class ActionTests(TestCase):
|
||||
|
||||
self.assertEquals(project.roles[user.id], ['_member_'])
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_user_disabled(self):
|
||||
"""
|
||||
Disabled user, valid existing tenant, no role.
|
||||
@ -188,8 +183,6 @@ class ActionTests(TestCase):
|
||||
|
||||
self.assertEquals(project.roles["user_id_1"], ['_member_'])
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_user_existing_role(self):
|
||||
"""
|
||||
Existing user, valid tenant, has role.
|
||||
@ -242,8 +235,6 @@ class ActionTests(TestCase):
|
||||
|
||||
self.assertEquals(project.roles[user.id], ['_member_'])
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_user_no_tenant(self):
|
||||
"""
|
||||
No user, no tenant.
|
||||
@ -278,8 +269,6 @@ class ActionTests(TestCase):
|
||||
action.submit(token_data)
|
||||
self.assertEquals(action.valid, False)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_user_wrong_project(self):
|
||||
"""
|
||||
Existing user, valid project, project does not match keystone user.
|
||||
@ -321,8 +310,6 @@ class ActionTests(TestCase):
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, False)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_user_only_member(self):
|
||||
"""
|
||||
Existing user, valid project, no edit permissions.
|
||||
@ -364,8 +351,6 @@ class ActionTests(TestCase):
|
||||
action.pre_approve()
|
||||
self.assertFalse(action.valid)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_user_wrong_domain(self):
|
||||
"""
|
||||
Existing user, valid project, invalid domain.
|
||||
@ -407,446 +392,6 @@ class ActionTests(TestCase):
|
||||
action.pre_approve()
|
||||
self.assertFalse(action.valid)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_project(self):
|
||||
"""
|
||||
Base case, no project, no user.
|
||||
|
||||
Project and user created at post_approve step,
|
||||
user password at submit step.
|
||||
"""
|
||||
|
||||
setup_temp_cache({}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={}
|
||||
)
|
||||
|
||||
data = {
|
||||
'domain_id': 'default',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
self.assertEquals(
|
||||
tests.temp_cache['projects']['test_project'].name,
|
||||
'test_project')
|
||||
self.assertEquals(
|
||||
task.cache,
|
||||
{'project_id': 'project_id_1', 'user_id': 'user_id_1',
|
||||
'user_state': 'default'})
|
||||
|
||||
token_data = {'password': '123456'}
|
||||
action.submit(token_data)
|
||||
self.assertEquals(action.valid, True)
|
||||
self.assertEquals(
|
||||
tests.temp_cache['users']["user_id_1"].email,
|
||||
'test@example.com')
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(
|
||||
sorted(project.roles["user_id_1"]),
|
||||
sorted(['_member_', 'project_admin',
|
||||
'project_mod', 'heat_stack_owner']))
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_project_reapprove(self):
|
||||
"""
|
||||
Project created at post_approve step,
|
||||
ensure reapprove does nothing.
|
||||
"""
|
||||
|
||||
setup_temp_cache({}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={}
|
||||
)
|
||||
|
||||
data = {
|
||||
'domain_id': 'default',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
self.assertEquals(
|
||||
tests.temp_cache['projects']['test_project'].name,
|
||||
'test_project')
|
||||
self.assertEquals(
|
||||
task.cache,
|
||||
{'project_id': 'project_id_1', 'user_id': 'user_id_1',
|
||||
'user_state': 'default'})
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
self.assertEquals(
|
||||
tests.temp_cache['projects']['test_project'].name,
|
||||
'test_project')
|
||||
self.assertEquals(
|
||||
task.cache,
|
||||
{'project_id': 'project_id_1', 'user_id': 'user_id_1',
|
||||
'user_state': 'default'})
|
||||
|
||||
token_data = {'password': '123456'}
|
||||
action.submit(token_data)
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
self.assertEquals(
|
||||
tests.temp_cache['users']["user_id_1"].email,
|
||||
'test@example.com')
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(
|
||||
sorted(project.roles["user_id_1"]),
|
||||
sorted(['_member_', 'project_admin',
|
||||
'project_mod', 'heat_stack_owner']))
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_project_reapprove_failure(self):
|
||||
"""
|
||||
Project created at post_approve step, failure at role grant.
|
||||
|
||||
Ensure reapprove correctly finishes.
|
||||
"""
|
||||
|
||||
setup_temp_cache({}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={}
|
||||
)
|
||||
|
||||
data = {
|
||||
'domain_id': 'default',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
# NOTE(adrian): We need the code to fail at the
|
||||
# grant roles step so we can attempt reapproving it
|
||||
class FakeException(Exception):
|
||||
pass
|
||||
|
||||
def fail_grant(user, default_roles, project_id):
|
||||
raise FakeException
|
||||
# We swap out the old grant function and keep
|
||||
# it for later.
|
||||
old_grant_function = action.grant_roles
|
||||
action.grant_roles = fail_grant
|
||||
|
||||
# Now we expect the failure
|
||||
self.assertRaises(FakeException, action.post_approve)
|
||||
|
||||
# No roles_granted yet, but user created
|
||||
self.assertTrue("user_id" in action.action.cache)
|
||||
self.assertFalse("roles_granted" in action.action.cache)
|
||||
self.assertEquals(
|
||||
tests.temp_cache['users']["user_id_1"].email,
|
||||
'test@example.com')
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertFalse("user_id_1" in project.roles)
|
||||
|
||||
# And then swap back the correct function
|
||||
action.grant_roles = old_grant_function
|
||||
# and try again, it should work this time
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
# roles_granted in cache
|
||||
self.assertTrue("roles_granted" in action.action.cache)
|
||||
|
||||
token_data = {'password': '123456'}
|
||||
action.submit(token_data)
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(
|
||||
sorted(project.roles["user_id_1"]),
|
||||
sorted(['_member_', 'project_admin',
|
||||
'project_mod', 'heat_stack_owner']))
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_project_existing_user(self):
|
||||
"""
|
||||
Create a project for a user that already exists.
|
||||
"""
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id_1'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
|
||||
setup_temp_cache({}, {user.id: user})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={}
|
||||
)
|
||||
|
||||
data = {
|
||||
'domain_id': 'default',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
self.assertEquals(
|
||||
tests.temp_cache['projects']['test_project'].name,
|
||||
'test_project')
|
||||
self.assertEquals(
|
||||
task.cache,
|
||||
{'user_id': 'user_id_1', 'project_id': 'project_id_1',
|
||||
'user_state': 'existing'})
|
||||
|
||||
token_data = {'password': '123456'}
|
||||
action.submit(token_data)
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
self.assertEquals(
|
||||
tests.temp_cache['users'][user.id].email,
|
||||
'test@example.com')
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(
|
||||
sorted(project.roles[user.id]),
|
||||
sorted(['_member_', 'project_admin',
|
||||
'project_mod', 'heat_stack_owner']))
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_project_disabled_user(self):
|
||||
"""
|
||||
Create a project for a user that is disabled.
|
||||
"""
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id_1'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.enabled = False
|
||||
|
||||
# create disabled user
|
||||
setup_temp_cache({}, {user.id: user})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={}
|
||||
)
|
||||
|
||||
data = {
|
||||
'domain_id': 'default',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
# Sign up, approve
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
self.assertEquals(
|
||||
tests.temp_cache['projects']['test_project'].name,
|
||||
'test_project')
|
||||
self.assertEquals(
|
||||
task.cache,
|
||||
{'user_id': 'user_id_1',
|
||||
'project_id': 'project_id_1',
|
||||
'user_state': 'disabled'})
|
||||
|
||||
# submit password reset
|
||||
token_data = {'password': '123456'}
|
||||
action.submit(token_data)
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
# check that user has been created correctly
|
||||
self.assertEquals(
|
||||
tests.temp_cache['users'][user.id].email,
|
||||
'test@example.com')
|
||||
self.assertEquals(
|
||||
tests.temp_cache['users'][user.id].enabled,
|
||||
True)
|
||||
|
||||
# Check user has correct roles in new project
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(
|
||||
sorted(project.roles[user.id]),
|
||||
sorted(['_member_', 'project_admin',
|
||||
'project_mod', 'heat_stack_owner']))
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_project_user_disabled_during_signup(self):
|
||||
"""
|
||||
Create a project for a user that is created and disabled during signup.
|
||||
|
||||
This exercises the tasks ability to correctly act based on changed
|
||||
circumstances between two states.
|
||||
"""
|
||||
|
||||
# Start with nothing created
|
||||
setup_temp_cache({}, {})
|
||||
|
||||
# Sign up for the project+user, validate.
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={}
|
||||
)
|
||||
|
||||
data = {
|
||||
'domain_id': 'default',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
# Sign up
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
# Create the disabled user directly with the Identity Manager.
|
||||
fm = FakeManager()
|
||||
user = fm.create_user(
|
||||
name="test@example.com",
|
||||
password='origpass',
|
||||
email="test@example.com",
|
||||
created_on=None,
|
||||
domain='default',
|
||||
default_project=None
|
||||
)
|
||||
fm.disable_user(user.id)
|
||||
|
||||
# approve previous signup
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
project = tests.temp_cache['projects']['test_project']
|
||||
self.assertEquals(
|
||||
project.name,
|
||||
'test_project')
|
||||
self.assertEquals(
|
||||
task.cache,
|
||||
{'user_id': user.id,
|
||||
'project_id': project.id,
|
||||
'user_state': 'disabled'})
|
||||
|
||||
# check that user has been re-enabled with a generated password.
|
||||
self.assertEquals(user.enabled, True)
|
||||
self.assertNotEquals(user.password, 'origpass')
|
||||
|
||||
# submit password reset
|
||||
token_data = {'password': '123456'}
|
||||
action.submit(token_data)
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
# Ensure user has new password:
|
||||
self.assertEquals(user.password, '123456')
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_project_existing_project(self):
|
||||
"""
|
||||
Create a project that already exists.
|
||||
"""
|
||||
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
setup_temp_cache({project.name: project}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={
|
||||
'roles': ['admin', 'project_mod'],
|
||||
'project_id': 'test_project_id',
|
||||
'project_domain_id': 'default',
|
||||
})
|
||||
|
||||
data = {
|
||||
'domain_id': 'default',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, False)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, False)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_project_invalid_domain_id(self):
|
||||
""" Create a project using an invalid domain """
|
||||
|
||||
setup_temp_cache({}, {})
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0",
|
||||
keystone_user={
|
||||
'roles': ['admin', 'project_mod'],
|
||||
'project_id': 'test_project_id',
|
||||
'project_domain_id': 'default',
|
||||
})
|
||||
|
||||
data = {
|
||||
'domain_id': 'not_default_id',
|
||||
'parent_id': None,
|
||||
'email': 'test@example.com',
|
||||
'project_name': 'test_project',
|
||||
}
|
||||
|
||||
action = NewProjectWithUserAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, False)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, False)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_reset_user_password(self):
|
||||
"""
|
||||
Base case, existing user.
|
||||
@ -891,8 +436,6 @@ class ActionTests(TestCase):
|
||||
tests.temp_cache['users'][user.id].password,
|
||||
'123456')
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_reset_user_password_no_user(self):
|
||||
"""
|
||||
Reset password for a non-existant user.
|
||||
@ -926,8 +469,6 @@ class ActionTests(TestCase):
|
||||
action.submit(token_data)
|
||||
self.assertEquals(action.valid, False)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_edit_user_roles_add(self):
|
||||
"""
|
||||
Add roles to existing user.
|
||||
@ -978,8 +519,6 @@ class ActionTests(TestCase):
|
||||
self.assertEquals(set(project.roles[user.id]),
|
||||
set(['_member_', 'project_mod']))
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_edit_user_roles_add_complete(self):
|
||||
"""
|
||||
Add roles to existing user.
|
||||
@ -1031,8 +570,6 @@ class ActionTests(TestCase):
|
||||
self.assertEquals(set(project.roles[user.id]),
|
||||
set(['_member_', 'project_mod']))
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_edit_user_roles_remove(self):
|
||||
"""
|
||||
Remove roles from existing user.
|
||||
@ -1082,8 +619,6 @@ class ActionTests(TestCase):
|
||||
|
||||
self.assertEquals(project.roles[user.id], ['_member_'])
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_edit_user_roles_remove_complete(self):
|
||||
"""
|
||||
Remove roles from user that does not have them.
|
315
stacktask/actions/v1/users.py
Normal file
315
stacktask/actions/v1/users.py
Normal file
@ -0,0 +1,315 @@
|
||||
# Copyright (C) 2015 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
from stacktask.actions import user_store
|
||||
from stacktask.actions.v1.base import (
|
||||
UserNameAction, UserIdAction, UserMixin, ProjectMixin)
|
||||
|
||||
|
||||
class NewUserAction(UserNameAction, ProjectMixin, UserMixin):
|
||||
"""
|
||||
Setup a new user with a role on the given project.
|
||||
Creates the user if they don't exist, otherwise
|
||||
if the username and email for the request match the
|
||||
existing one, will simply add the project role.
|
||||
"""
|
||||
|
||||
required = [
|
||||
'username',
|
||||
'email',
|
||||
'project_id',
|
||||
'roles',
|
||||
'domain_id',
|
||||
]
|
||||
|
||||
def _validate_target_user(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
|
||||
# check if user exists and is valid
|
||||
# this may mean we need a token.
|
||||
user = self._get_target_user()
|
||||
if not user:
|
||||
self.action.need_token = True
|
||||
# add to cache to use in template
|
||||
self.action.task.cache['user_state'] = "default"
|
||||
self.set_token_fields(["password"])
|
||||
self.add_note(
|
||||
'No user present with username. Need to create new user.')
|
||||
return True
|
||||
if user.email != self.email:
|
||||
self.add_note(
|
||||
'Found matching username, but email did not match.' +
|
||||
'Reporting as invalid.')
|
||||
return False
|
||||
|
||||
if not user.enabled:
|
||||
self.action.need_token = True
|
||||
self.action.state = "disabled"
|
||||
# add to cache to use in template
|
||||
self.action.task.cache['user_state'] = "disabled"
|
||||
# as they are disabled we'll reset their password
|
||||
self.set_token_fields(["password"])
|
||||
self.add_note(
|
||||
'Existing disabled user with matching email.')
|
||||
return True
|
||||
|
||||
# role_validation
|
||||
roles = id_manager.get_roles(user, self.project_id)
|
||||
role_names = {role.name for role in roles}
|
||||
missing = set(self.roles) - role_names
|
||||
if not missing:
|
||||
self.action.need_token = False
|
||||
self.action.state = "complete"
|
||||
self.add_note(
|
||||
'Existing user already has roles.'
|
||||
)
|
||||
else:
|
||||
self.roles = list(missing)
|
||||
self.action.need_token = True
|
||||
self.set_token_fields(["confirm"])
|
||||
self.action.state = "existing"
|
||||
# add to cache to use in template
|
||||
self.action.task.cache['user_state'] = "existing"
|
||||
self.add_note(
|
||||
'Existing user with matching email missing roles.')
|
||||
|
||||
return True
|
||||
|
||||
def _validate(self):
|
||||
self.action.valid = (
|
||||
self._validate_role_permissions() and
|
||||
self._validate_keystone_user() and
|
||||
self._validate_domain_id() and
|
||||
self._validate_project_id() and
|
||||
self._validate_target_user()
|
||||
)
|
||||
self.action.save()
|
||||
|
||||
def _pre_approve(self):
|
||||
self._validate()
|
||||
|
||||
def _post_approve(self):
|
||||
self._validate()
|
||||
|
||||
def _submit(self, token_data):
|
||||
self._validate()
|
||||
|
||||
if not self.valid:
|
||||
return
|
||||
|
||||
if self.action.state == "default":
|
||||
# default action: Create a new user in the tenant and add roles
|
||||
user = self.create_user(token_data['password'])
|
||||
self.grant_roles(user, self.roles, self.project_id)
|
||||
|
||||
self.add_note(
|
||||
'User %s has been created, with roles %s in project %s.'
|
||||
% (self.username, self.roles, self.project_id))
|
||||
|
||||
elif self.action.state == "disabled":
|
||||
# first re-enable user
|
||||
user = self.find_user()
|
||||
self.enable_user(user)
|
||||
self.grant_roles(user, self.roles, self.project_id)
|
||||
self.update_password(token_data['password'])
|
||||
|
||||
self.add_note('User %s password has been changed.' % self.username)
|
||||
|
||||
self.add_note(
|
||||
'Existing user %s has been re-enabled and given roles %s'
|
||||
' in project %s.'
|
||||
% (self.username, self.roles, self.project_id))
|
||||
|
||||
elif self.action.state == "existing":
|
||||
# Existing action: only add roles.
|
||||
user = self.find_user()
|
||||
self.grant_roles(user, self.roles, self.project_id)
|
||||
|
||||
self.add_note(
|
||||
'Existing user %s has been given roles %s in project %s.'
|
||||
% (self.username, self.roles, self.project_id))
|
||||
elif self.action.state == "complete":
|
||||
# complete action: nothing to do.
|
||||
self.add_note(
|
||||
'Existing user %s already had roles %s in project %s.'
|
||||
% (self.username, self.roles, self.project_id))
|
||||
|
||||
|
||||
class ResetUserPasswordAction(UserNameAction, UserMixin):
|
||||
"""
|
||||
Simple action to reset a password for a given user.
|
||||
"""
|
||||
|
||||
username = models.CharField(max_length=200)
|
||||
email = models.EmailField()
|
||||
|
||||
required = [
|
||||
'domain_name',
|
||||
'username',
|
||||
'email'
|
||||
]
|
||||
|
||||
blacklist = settings.ACTION_SETTINGS.get(
|
||||
'ResetUserPasswordAction', {}).get("blacklisted_roles", {})
|
||||
|
||||
def _validate_user_roles(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
|
||||
self.user = id_manager.find_user(self.username, self.domain.id)
|
||||
roles = id_manager.get_all_roles(self.user)
|
||||
|
||||
user_roles = []
|
||||
for roles in roles.itervalues():
|
||||
user_roles.extend(role.name for role in roles)
|
||||
|
||||
if set(self.blacklist) & set(user_roles):
|
||||
self.add_note('Cannot reset users with blacklisted roles.')
|
||||
return False
|
||||
|
||||
if self.user.email == self.email:
|
||||
self.action.need_token = True
|
||||
self.set_token_fields(["password"])
|
||||
self.add_note('Existing user with matching email.')
|
||||
return True
|
||||
else:
|
||||
self.add_note('Existing user with non-matching email.')
|
||||
return False
|
||||
|
||||
def _validate(self):
|
||||
# Here, the order of validation matters
|
||||
# as each one adds new class variables
|
||||
self.action.valid = (
|
||||
self._validate_domain_name() and
|
||||
self._validate_username_exists() and
|
||||
self._validate_user_roles()
|
||||
)
|
||||
self.action.save()
|
||||
|
||||
def _pre_approve(self):
|
||||
self._validate()
|
||||
|
||||
def _post_approve(self):
|
||||
self._validate()
|
||||
|
||||
def _submit(self, token_data):
|
||||
self._validate()
|
||||
|
||||
if not self.valid:
|
||||
return
|
||||
|
||||
self.update_password(token_data['password'])
|
||||
self.add_note('User %s password has been changed.' % self.username)
|
||||
|
||||
|
||||
class EditUserRolesAction(UserIdAction, ProjectMixin, UserMixin):
|
||||
"""
|
||||
A class for adding or removing roles
|
||||
on a user for the given project.
|
||||
"""
|
||||
|
||||
required = [
|
||||
'domain_id',
|
||||
'project_id',
|
||||
'user_id',
|
||||
'roles',
|
||||
'remove'
|
||||
]
|
||||
|
||||
def _validate_target_user(self):
|
||||
# Get target user
|
||||
user = self._get_target_user()
|
||||
if not user:
|
||||
self.add_note('No user present with user_id')
|
||||
return False
|
||||
return True
|
||||
|
||||
def _validate_user_roles(self):
|
||||
id_manager = user_store.IdentityManager()
|
||||
user = self._get_target_user()
|
||||
project = id_manager.get_project(self.project_id)
|
||||
# user roles
|
||||
current_roles = id_manager.get_roles(user, project)
|
||||
current_role_names = {role.name for role in current_roles}
|
||||
if self.remove:
|
||||
remaining = set(current_role_names) & set(self.roles)
|
||||
if not remaining:
|
||||
self.action.state = "complete"
|
||||
self.add_note(
|
||||
"User doesn't have roles to remove.")
|
||||
else:
|
||||
self.roles = list(remaining)
|
||||
self.add_note(
|
||||
'User has roles to remove.')
|
||||
else:
|
||||
missing = set(self.roles) - set(current_role_names)
|
||||
if not missing:
|
||||
self.action.state = "complete"
|
||||
self.add_note(
|
||||
'User already has roles.')
|
||||
else:
|
||||
self.roles = list(missing)
|
||||
self.add_note(
|
||||
'User user missing roles.')
|
||||
# All paths are valid here
|
||||
# We've just set state and roles that need to be changed.
|
||||
return True
|
||||
|
||||
def _validate(self):
|
||||
self.action.valid = (
|
||||
self._validate_keystone_user() and
|
||||
self._validate_role_permissions() and
|
||||
self._validate_domain_id() and
|
||||
self._validate_project_id() and
|
||||
self._validate_target_user() and
|
||||
self._validate_user_roles()
|
||||
)
|
||||
self.action.save()
|
||||
|
||||
def _pre_approve(self):
|
||||
self._validate()
|
||||
|
||||
def _post_approve(self):
|
||||
self._validate()
|
||||
|
||||
def _submit(self, token_data):
|
||||
self._validate()
|
||||
|
||||
if not self.valid:
|
||||
return
|
||||
|
||||
if self.action.state == "default":
|
||||
user = self._get_target_user()
|
||||
self._user_roles_edit(user, self.roles, self.project_id,
|
||||
remove=self.remove)
|
||||
|
||||
if self.remove:
|
||||
self.add_note(
|
||||
'User %s has had roles %s removed from project %s.'
|
||||
% (self.user_id, self.roles, self.project_id))
|
||||
else:
|
||||
self.add_note(
|
||||
'User %s has been given roles %s in project %s.'
|
||||
% (self.user_id, self.roles, self.project_id))
|
||||
elif self.action.state == "complete":
|
||||
if self.remove:
|
||||
self.add_note(
|
||||
'User %s already had roles %s in project %s.'
|
||||
% (self.user_id, self.roles, self.project_id))
|
||||
else:
|
||||
self.add_note(
|
||||
"User %s didn't have roles %s in project %s."
|
||||
% (self.user_id, self.roles, self.project_id))
|
@ -1 +0,0 @@
|
||||
default_app_config = 'stacktask.api.startup.APIConfig'
|
@ -0,0 +1 @@
|
||||
default_app_config = 'stacktask.api.v1.app.APIV1Config'
|
6
stacktask/api/v1/app.py
Normal file
6
stacktask/api/v1/app.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class APIV1Config(AppConfig):
|
||||
name = "stacktask.api.v1"
|
||||
label = 'api_v1'
|
@ -29,6 +29,8 @@ from stacktask.api.models import Task, Token
|
||||
from stacktask.api.v1.tests import FakeManager, setup_temp_cache
|
||||
|
||||
|
||||
@mock.patch('stacktask.actions.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
class AdminAPITests(APITestCase):
|
||||
"""
|
||||
Tests to ensure the admin api endpoints work as expected within
|
||||
@ -57,11 +59,6 @@ class AdminAPITests(APITestCase):
|
||||
response.data,
|
||||
{'errors': ['This token does not exist or has expired.']})
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager', FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_task_get(self):
|
||||
"""
|
||||
Test the basic task detail view.
|
||||
@ -122,8 +119,6 @@ class AdminAPITests(APITestCase):
|
||||
self.assertEqual(
|
||||
response.data, {'errors': ['No task with this id.']})
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_token_expired_post(self):
|
||||
"""
|
||||
Expired token should do nothing, then delete itself.
|
||||
@ -158,8 +153,6 @@ class AdminAPITests(APITestCase):
|
||||
{'errors': ['This token does not exist or has expired.']})
|
||||
self.assertEqual(0, Token.objects.count())
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_token_expired_get(self):
|
||||
"""
|
||||
Expired token should do nothing, then delete itself.
|
||||
@ -193,11 +186,6 @@ class AdminAPITests(APITestCase):
|
||||
{'errors': ['This token does not exist or has expired.']})
|
||||
self.assertEqual(0, Token.objects.count())
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_task_complete(self):
|
||||
"""
|
||||
Can't approve a completed task.
|
||||
@ -228,11 +216,6 @@ class AdminAPITests(APITestCase):
|
||||
response.data,
|
||||
{'errors': ['This task has already been completed.']})
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_task_update(self):
|
||||
"""
|
||||
Creates a invalid task.
|
||||
@ -287,11 +270,6 @@ class AdminAPITests(APITestCase):
|
||||
response.data,
|
||||
{'notes': ['created token']})
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_notification_acknowledge(self):
|
||||
"""
|
||||
Test that you can acknowledge a notification.
|
||||
@ -339,11 +317,6 @@ class AdminAPITests(APITestCase):
|
||||
)
|
||||
self.assertEqual(response.data, {'notifications': []})
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_notification_acknowledge_list(self):
|
||||
"""
|
||||
Test that you can acknowledge a list of notifications.
|
||||
@ -389,8 +362,6 @@ class AdminAPITests(APITestCase):
|
||||
)
|
||||
self.assertEqual(response.data, {'notifications': []})
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_token_expired_delete(self):
|
||||
"""
|
||||
test deleting of expired tokens.
|
||||
@ -451,8 +422,6 @@ class AdminAPITests(APITestCase):
|
||||
{'notes': ['Deleted all expired tokens.']})
|
||||
self.assertEqual(Token.objects.count(), 1)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_token_reissue(self):
|
||||
"""
|
||||
test for reissue of tokens
|
||||
@ -499,8 +468,6 @@ class AdminAPITests(APITestCase):
|
||||
new_token = Token.objects.all()[0]
|
||||
self.assertNotEquals(new_token.token, uuid)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_token_reissue_non_admin(self):
|
||||
"""
|
||||
test for reissue of tokens for non-admin
|
||||
@ -553,11 +520,6 @@ class AdminAPITests(APITestCase):
|
||||
self.assertEqual(response.data,
|
||||
{'errors': ['No task with this id.']})
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_cancel_task(self):
|
||||
"""
|
||||
Ensure the ability to cancel a task.
|
||||
@ -592,11 +554,6 @@ class AdminAPITests(APITestCase):
|
||||
headers=headers)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_cancel_task_sent_token(self):
|
||||
"""
|
||||
Ensure the ability to cancel a task after the token is sent.
|
||||
@ -633,11 +590,6 @@ class AdminAPITests(APITestCase):
|
||||
response = self.client.post(url, data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_task_update_unapprove(self):
|
||||
"""
|
||||
Ensure task update doesn't work for approved actions.
|
||||
@ -678,11 +630,6 @@ class AdminAPITests(APITestCase):
|
||||
headers=headers)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager', FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_cancel_task_own(self):
|
||||
"""
|
||||
Ensure the ability to cancel your own task.
|
||||
@ -726,11 +673,6 @@ class AdminAPITests(APITestCase):
|
||||
headers=headers)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager', FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_cancel_task_own_fail(self):
|
||||
"""
|
||||
Ensure the ability to cancel ONLY your own task.
|
||||
@ -766,8 +708,6 @@ class AdminAPITests(APITestCase):
|
||||
headers=headers)
|
||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager', FakeManager)
|
||||
def test_task_list(self):
|
||||
"""
|
||||
"""
|
||||
@ -814,8 +754,6 @@ class AdminAPITests(APITestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(len(response.data['tasks']), 3)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager', FakeManager)
|
||||
def test_task_list_ordering(self):
|
||||
"""
|
||||
Test that tasks returns in the default sort.
|
||||
@ -870,11 +808,6 @@ class AdminAPITests(APITestCase):
|
||||
for i, task in enumerate(sorted_list):
|
||||
self.assertEqual(task, response.data['tasks'][i])
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager', FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_task_list_filter(self):
|
||||
"""
|
||||
"""
|
||||
@ -944,8 +877,6 @@ class AdminAPITests(APITestCase):
|
||||
# TODO(adriant): enable this test again when filters are properly
|
||||
# blacklisted.
|
||||
@skip("Does not apply yet.")
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager', FakeManager)
|
||||
def test_task_list_filter_cross_project(self):
|
||||
"""
|
||||
Ensure you can't override the initial project_id filter if
|
||||
@ -1003,8 +934,6 @@ class AdminAPITests(APITestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(len(response.data['tasks']), 0)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager', FakeManager)
|
||||
def test_task_list_filter_formating(self):
|
||||
"""
|
||||
"""
|
||||
@ -1093,11 +1022,6 @@ class AdminAPITests(APITestCase):
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager', FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_reset_admin(self):
|
||||
"""
|
||||
Ensure that you cannot issue a password reset for an
|
||||
|
@ -21,6 +21,8 @@ from stacktask.api.models import Token
|
||||
from stacktask.api.v1.tests import FakeManager, setup_temp_cache
|
||||
|
||||
|
||||
@mock.patch('stacktask.actions.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
class OpenstackAPITests(APITestCase):
|
||||
"""
|
||||
TaskView tests specific to the openstack style urls.
|
||||
@ -29,8 +31,6 @@ class OpenstackAPITests(APITestCase):
|
||||
unique TaskViews need testing.
|
||||
"""
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_user(self):
|
||||
"""
|
||||
Ensure the new user workflow goes as expected.
|
||||
@ -65,8 +65,6 @@ class OpenstackAPITests(APITestCase):
|
||||
response = self.client.post(url, data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_user_list(self):
|
||||
"""
|
||||
"""
|
||||
@ -110,8 +108,6 @@ class OpenstackAPITests(APITestCase):
|
||||
response = self.client.get(url, headers=headers)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager', FakeManager)
|
||||
def test_force_reset_password(self):
|
||||
"""
|
||||
Ensure the force password endpoint works as expected,
|
||||
@ -158,8 +154,6 @@ class OpenstackAPITests(APITestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(user.password, 'new_test_password')
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager', FakeManager)
|
||||
def test_remove_user_role(self):
|
||||
""" Remove all roles on a user from our project """
|
||||
project = mock.Mock()
|
||||
|
@ -21,6 +21,8 @@ from stacktask.api.models import Task, Token
|
||||
from stacktask.api.v1.tests import FakeManager, setup_temp_cache
|
||||
|
||||
|
||||
@mock.patch('stacktask.actions.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
class TaskViewTests(APITestCase):
|
||||
"""
|
||||
Tests to ensure the approval/token workflow does what is
|
||||
@ -65,8 +67,6 @@ class TaskViewTests(APITestCase):
|
||||
'email': ['Enter a valid email address.'],
|
||||
'roles': ['"not_a_valid_role" is not a valid choice.']})
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_user(self):
|
||||
"""
|
||||
Ensure the new user workflow goes as expected.
|
||||
@ -101,8 +101,6 @@ class TaskViewTests(APITestCase):
|
||||
response = self.client.post(url, data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_user_no_project(self):
|
||||
"""
|
||||
Can't create a user for a non-existent project.
|
||||
@ -124,8 +122,6 @@ class TaskViewTests(APITestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(response.data, {'errors': ['actions invalid']})
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_user_not_my_project(self):
|
||||
"""
|
||||
Can't create a user for project that isn't mine.
|
||||
@ -146,8 +142,6 @@ class TaskViewTests(APITestCase):
|
||||
response = self.client.post(url, data, format='json', headers=headers)
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_user_not_authenticated(self):
|
||||
"""
|
||||
Can't create a user if unauthenticated.
|
||||
@ -166,8 +160,6 @@ class TaskViewTests(APITestCase):
|
||||
{'errors': ["Credentials incorrect or none given."]}
|
||||
)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_add_user_existing(self):
|
||||
"""
|
||||
Adding existing user to project.
|
||||
@ -207,8 +199,6 @@ class TaskViewTests(APITestCase):
|
||||
response = self.client.post(url, data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_add_user_existing_with_role(self):
|
||||
"""
|
||||
Adding existing user to project.
|
||||
@ -246,11 +236,6 @@ class TaskViewTests(APITestCase):
|
||||
response.data,
|
||||
{'notes': ['Task completed successfully.']})
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_project(self):
|
||||
"""
|
||||
Ensure the new project workflow goes as expected.
|
||||
@ -287,12 +272,6 @@ class TaskViewTests(APITestCase):
|
||||
response = self.client.post(url, data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_project_existing(self):
|
||||
"""
|
||||
Test to ensure validation marks actions as invalid
|
||||
@ -330,12 +309,6 @@ class TaskViewTests(APITestCase):
|
||||
{'errors': ['Cannot approve an invalid task. ' +
|
||||
'Update data and rerun pre_approve.']})
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_project_existing_user(self):
|
||||
"""
|
||||
Project created if not present, existing user attached.
|
||||
@ -378,12 +351,6 @@ class TaskViewTests(APITestCase):
|
||||
{'notes': ['Task completed successfully.']}
|
||||
)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_new_project_existing_project_new_user(self):
|
||||
"""
|
||||
Project already exists but new user attempting to create it.
|
||||
@ -431,8 +398,6 @@ class TaskViewTests(APITestCase):
|
||||
{'errors': ['actions invalid']}
|
||||
)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_reset_user(self):
|
||||
"""
|
||||
Ensure the reset user workflow goes as expected.
|
||||
@ -463,8 +428,6 @@ class TaskViewTests(APITestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(user.password, 'new_test_password')
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_reset_user_duplicate(self):
|
||||
"""
|
||||
Request password reset twice in a row
|
||||
@ -513,8 +476,6 @@ class TaskViewTests(APITestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(user.password, 'new_test_password2')
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_reset_user_no_existing(self):
|
||||
"""
|
||||
Actions should be successful, so usernames are not exposed.
|
||||
@ -530,11 +491,6 @@ class TaskViewTests(APITestCase):
|
||||
response.data['notes'],
|
||||
['If user with email exists, reset token will be issued.'])
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_notification_createproject(self):
|
||||
"""
|
||||
CreateProject should create a notification.
|
||||
@ -565,11 +521,6 @@ class TaskViewTests(APITestCase):
|
||||
response.data['notifications'][0]['task'],
|
||||
new_task.uuid)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager', FakeManager)
|
||||
@mock.patch(
|
||||
'stacktask.actions.tenant_setup.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_duplicate_tasks_new_project(self):
|
||||
"""
|
||||
Ensure we can't submit duplicate tasks
|
||||
@ -594,8 +545,6 @@ class TaskViewTests(APITestCase):
|
||||
response = self.client.post(url, data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
@mock.patch(
|
||||
'stacktask.actions.models.user_store.IdentityManager', FakeManager)
|
||||
def test_duplicate_tasks_new_user(self):
|
||||
"""
|
||||
Ensure we can't submit duplicate tasks
|
||||
@ -633,8 +582,6 @@ class TaskViewTests(APITestCase):
|
||||
response = self.client.post(url, data, format='json', headers=headers)
|
||||
self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_return_task_id_if_admin(self):
|
||||
"""
|
||||
Confirm that the task id is returned when admin.
|
||||
@ -667,8 +614,6 @@ class TaskViewTests(APITestCase):
|
||||
response.data['task'],
|
||||
new_task.uuid)
|
||||
|
||||
@mock.patch('stacktask.actions.models.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
def test_return_task_id_if_admin_fail(self):
|
||||
"""
|
||||
Confirm that the task id is not returned unless admin.
|
||||
|
@ -124,6 +124,10 @@ for app in CONFIG['ADDITIONAL_APPS']:
|
||||
INSTALLED_APPS = list(INSTALLED_APPS)
|
||||
INSTALLED_APPS.append(app)
|
||||
|
||||
# NOTE(adriant): Because the order matters, we want this import to be last
|
||||
# so the startup checks run after everything is imported.
|
||||
INSTALLED_APPS.append("stacktask.startup")
|
||||
|
||||
DATABASES = CONFIG['DATABASES']
|
||||
|
||||
LOGGING = CONFIG['LOGGING']
|
||||
|
1
stacktask/startup/__init__.py
Normal file
1
stacktask/startup/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
default_app_config = 'stacktask.startup.checks.StartUpConfig'
|
@ -1,5 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
|
||||
from stacktask.exceptions import ActionNotFound, TaskViewNotFound
|
||||
|
||||
|
||||
@ -38,17 +39,16 @@ def check_configured_actions():
|
||||
"Configured actions are unregistered: %s" % missing_actions)
|
||||
|
||||
|
||||
class APIConfig(AppConfig):
|
||||
name = 'stacktask.api'
|
||||
class StartUpConfig(AppConfig):
|
||||
name = "stacktask.startup"
|
||||
|
||||
def ready(self):
|
||||
"""A pre-startup function for the api.
|
||||
"""A pre-startup function for the api
|
||||
|
||||
Code run here will occur before the API is up and active but after
|
||||
all models have been loaded.
|
||||
|
||||
Useful for any start up checks.
|
||||
|
||||
"""
|
||||
|
||||
# First check that all expect taskviews are present
|
@ -12,11 +12,11 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
SECRET_KEY = '+er!!4olta#17a=n%uotcazg2ncpl==yjog%1*o-(cr%zys-)!'
|
||||
SECRET_KEY = '+er!4olta#17a=n%uotcazg2ncpl==yjog%1*o-(cr%zys-)!'
|
||||
|
||||
ADDITIONAL_APPS = [
|
||||
'stacktask.api.v1',
|
||||
'stacktask.actions.tenant_setup'
|
||||
'stacktask.actions.v1',
|
||||
]
|
||||
|
||||
DATABASES = {
|
||||
|
Loading…
Reference in New Issue
Block a user