adjutant/adjutant/actions/v1/projects.py

542 lines
18 KiB
Python

# 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.utils import timezone
from confspirator import groups
from confspirator import fields
from adjutant.config import CONF
from adjutant.common import user_store
from adjutant.common.utils import str_datetime
from adjutant.actions.utils import validate_steps
from adjutant.actions.v1.base import BaseAction, UserNameAction, UserMixin, ProjectMixin
from adjutant.actions.v1 import serializers
class NewProjectAction(BaseAction, ProjectMixin, UserMixin):
"""
Creates a new project for the current keystone_user.
This action can only be used for an autheticated task.
"""
required = [
"domain_id",
"parent_id",
"project_name",
"description",
]
serializer = serializers.NewProjectSerializer
config_group = groups.DynamicNameConfigGroup(
children=[
fields.ListConfig(
"default_roles",
help_text="Roles to be given on project to the creating user.",
default=[],
sample_default=["member", "project_admin"],
),
],
)
def __init__(self, *args, **kwargs):
super(NewProjectAction, self).__init__(*args, **kwargs)
def _validate(self):
self.action.valid = validate_steps(
[
self._validate_domain_id,
self._validate_keystone_user_parent_project,
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_keystone_user_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 self._validate_parent_project()
return True
def _prepare(self):
self._validate()
def _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 = self.config.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, default_roles, user.name, 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"
% (user.name, project_id, default_roles)
)
def _submit(self, token_data, keystone_user=None):
"""
Nothing to do here. Everything is done at the approve step.
"""
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"]
serializer = serializers.NewProjectWithUserSerializer
config_group = groups.DynamicNameConfigGroup(
children=[
fields.ListConfig(
"default_roles",
help_text="Roles to be given on project for the user.",
default=[],
sample_default=["member", "project_admin"],
),
],
)
def __init__(self, *args, **kwargs):
super(NewProjectWithUserAction, self).__init__(*args, **kwargs)
def _validate(self):
self.action.valid = validate_steps(
[
self._validate_domain_id,
self._validate_parent_project,
self._validate_project_absent,
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:
self.add_note(
"No user present with username '%s'. "
"Need to create new user." % self.username
)
if not id_manager.can_edit_users:
self.add_note(
"Identity backend does not support user editing, "
"cannot create new user."
)
return False
# add to cache to use in template
self.action.task.cache["user_state"] = "default"
self.action.need_token = True
self.set_token_fields(["password"])
return True
if (
not CONF.identity.username_is_email
and getattr(user, "email", None) != self.email
):
self.add_note("Existing user '%s' with non-matching email." % self.username)
return False
if not user.enabled:
self.add_note(
"Existing disabled user '%s' with matching email." % self.email
)
if not id_manager.can_edit_users:
self.add_note(
"Identity backend does not support user editing, "
"cannot renable user."
)
return False
self.action.state = "disabled"
# add to cache to use in template
self.action.task.cache["user_state"] = "disabled"
self.action.need_token = True
# as they are disabled we'll reset their password
self.set_token_fields(["password"])
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.task.cache["user_state"] = self.action.state
self.action.save()
def _prepare(self):
self._validate()
def _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.
"""
if not self.valid:
return
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.action.task.cache["user_state"] = self.action.state
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 = self.config.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_datetime(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, keystone_user=None):
"""
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 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",
]
serializer = serializers.AddDefaultUsersToProjectSerializer
config_group = groups.DynamicNameConfigGroup(
children=[
fields.ListConfig(
"default_users",
help_text="Users which this action should add to the project.",
default=[],
),
fields.ListConfig(
"default_roles",
help_text="Roles which those users should get.",
default=[],
),
],
)
def __init__(self, *args, **kwargs):
super(AddDefaultUsersToProjectAction, self).__init__(*args, **kwargs)
self.users = self.config.default_users
self.roles = self.config.default_roles
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 = validate_steps(
[
self._validate_users,
]
)
self.action.save()
def _validate(self):
self.action.valid = validate_steps(
[
self._validate_users,
self._validate_project_id,
]
)
self.action.save()
def _prepare(self):
self._pre_validate()
def _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, keystone_user=None):
pass