keystone/keystone/identity/controllers.py
Lance Bragstad 90c15100c4 Add notifications to user/group membership
When a user is added or removed from a group we should emit a notification. The
notification has group as the resource type and the group ID as the
resource ID. The notification also includes the user, user ID, and the
operation that was done ('removed' or 'added').

This way consumers are notified of user and group memberships.

Change-Id: I93ca3a0cb2fe9b93f5370e5871be1f1b30d87f72
Closes-Bug: 1552639
2016-03-09 17:20:33 +00:00

345 lines
14 KiB
Python

# Copyright 2012 OpenStack Foundation
#
# 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.
"""Workflow Logic the Identity service."""
from oslo_config import cfg
from oslo_log import log
from keystone.common import controller
from keystone.common import dependency
from keystone.common import validation
from keystone import exception
from keystone.i18n import _, _LW
from keystone.identity import schema
from keystone import notifications
CONF = cfg.CONF
LOG = log.getLogger(__name__)
@dependency.requires('assignment_api', 'identity_api', 'resource_api')
class User(controller.V2Controller):
@controller.v2_deprecated
def get_user(self, context, user_id):
self.assert_admin(context)
ref = self.identity_api.get_user(user_id)
return {'user': self.v3_to_v2_user(ref)}
@controller.v2_deprecated
def get_users(self, context):
# NOTE(termie): i can't imagine that this really wants all the data
# about every single user in the system...
if 'name' in context['query_string']:
return self.get_user_by_name(
context, context['query_string'].get('name'))
self.assert_admin(context)
user_list = self.identity_api.list_users(
CONF.identity.default_domain_id)
return {'users': self.v3_to_v2_user(user_list)}
@controller.v2_deprecated
def get_user_by_name(self, context, user_name):
self.assert_admin(context)
ref = self.identity_api.get_user_by_name(
user_name, CONF.identity.default_domain_id)
return {'user': self.v3_to_v2_user(ref)}
# CRUD extension
@controller.v2_deprecated
def create_user(self, context, user):
user = self._normalize_OSKSADM_password_on_request(user)
user = self.normalize_username_in_request(user)
user = self._normalize_dict(user)
self.assert_admin(context)
if 'name' not in user or not user['name']:
msg = _('Name field is required and cannot be empty')
raise exception.ValidationError(message=msg)
if 'enabled' in user and not isinstance(user['enabled'], bool):
msg = _('Enabled field must be a boolean')
raise exception.ValidationError(message=msg)
default_project_id = user.pop('tenantId', None)
if default_project_id is not None:
# Check to see if the project is valid before moving on.
self.resource_api.get_project(default_project_id)
user['default_project_id'] = default_project_id
self.resource_api.ensure_default_domain_exists()
# The manager layer will generate the unique ID for users
user_ref = self._normalize_domain_id(context, user.copy())
initiator = notifications._get_request_audit_info(context)
new_user_ref = self.v3_to_v2_user(
self.identity_api.create_user(user_ref, initiator))
if default_project_id is not None:
self.assignment_api.add_user_to_project(default_project_id,
new_user_ref['id'])
return {'user': new_user_ref}
@controller.v2_deprecated
def update_user(self, context, user_id, user):
# NOTE(termie): this is really more of a patch than a put
user = self.normalize_username_in_request(user)
self.assert_admin(context)
if 'enabled' in user and not isinstance(user['enabled'], bool):
msg = _('Enabled field should be a boolean')
raise exception.ValidationError(message=msg)
default_project_id = user.pop('tenantId', None)
if default_project_id is not None:
user['default_project_id'] = default_project_id
old_user_ref = self.v3_to_v2_user(
self.identity_api.get_user(user_id))
# Check whether a tenant is being added or changed for the user.
# Catch the case where the tenant is being changed for a user and also
# where a user previously had no tenant but a tenant is now being
# added for the user.
if (('tenantId' in old_user_ref and
old_user_ref['tenantId'] != default_project_id and
default_project_id is not None) or
('tenantId' not in old_user_ref and
default_project_id is not None)):
# Make sure the new project actually exists before we perform the
# user update.
self.resource_api.get_project(default_project_id)
initiator = notifications._get_request_audit_info(context)
user_ref = self.v3_to_v2_user(
self.identity_api.update_user(user_id, user, initiator))
# If 'tenantId' is in either ref, we might need to add or remove the
# user from a project.
if 'tenantId' in user_ref or 'tenantId' in old_user_ref:
if user_ref['tenantId'] != old_user_ref.get('tenantId'):
if old_user_ref.get('tenantId'):
try:
member_role_id = CONF.member_role_id
self.assignment_api.remove_role_from_user_and_project(
user_id, old_user_ref['tenantId'], member_role_id)
except exception.NotFound:
# NOTE(morganfainberg): This is not a critical error it
# just means that the user cannot be removed from the
# old tenant. This could occur if roles aren't found
# or if the project is invalid or if there are no roles
# for the user on that project.
msg = _LW('Unable to remove user %(user)s from '
'%(tenant)s.')
LOG.warning(msg, {'user': user_id,
'tenant': old_user_ref['tenantId']})
if user_ref['tenantId']:
try:
self.assignment_api.add_user_to_project(
user_ref['tenantId'], user_id)
except exception.Conflict: # nosec
# We are already a member of that tenant
pass
except exception.NotFound:
# NOTE(morganfainberg): Log this and move on. This is
# not the end of the world if we can't add the user to
# the appropriate tenant. Most of the time this means
# that the project is invalid or roles are some how
# incorrect. This shouldn't prevent the return of the
# new ref.
msg = _LW('Unable to add user %(user)s to %(tenant)s.')
LOG.warning(msg, {'user': user_id,
'tenant': user_ref['tenantId']})
return {'user': user_ref}
@controller.v2_deprecated
def delete_user(self, context, user_id):
self.assert_admin(context)
initiator = notifications._get_request_audit_info(context)
self.identity_api.delete_user(user_id, initiator)
@controller.v2_deprecated
def set_user_enabled(self, context, user_id, user):
return self.update_user(context, user_id, user)
@controller.v2_deprecated
def set_user_password(self, context, user_id, user):
user = self._normalize_OSKSADM_password_on_request(user)
return self.update_user(context, user_id, user)
@staticmethod
def _normalize_OSKSADM_password_on_request(ref):
"""Sets the password from the OS-KSADM Admin Extension.
The OS-KSADM Admin Extension documentation says that
`OS-KSADM:password` can be used in place of `password`.
"""
if 'OS-KSADM:password' in ref:
ref['password'] = ref.pop('OS-KSADM:password')
return ref
@dependency.requires('identity_api')
class UserV3(controller.V3Controller):
collection_name = 'users'
member_name = 'user'
def __init__(self):
super(UserV3, self).__init__()
self.get_member_from_driver = self.identity_api.get_user
def _check_user_and_group_protection(self, context, prep_info,
user_id, group_id):
ref = {}
ref['user'] = self.identity_api.get_user(user_id)
ref['group'] = self.identity_api.get_group(group_id)
self.check_protection(context, prep_info, ref)
@controller.protected()
@validation.validated(schema.user_create, 'user')
def create_user(self, context, user):
# The manager layer will generate the unique ID for users
ref = self._normalize_dict(user)
ref = self._normalize_domain_id(context, ref)
initiator = notifications._get_request_audit_info(context)
ref = self.identity_api.create_user(ref, initiator)
return UserV3.wrap_member(context, ref)
@controller.filterprotected('domain_id', 'enabled', 'name')
def list_users(self, context, filters):
hints = UserV3.build_driver_hints(context, filters)
refs = self.identity_api.list_users(
domain_scope=self._get_domain_id_for_list_request(context),
hints=hints)
return UserV3.wrap_collection(context, refs, hints=hints)
@controller.filterprotected('domain_id', 'enabled', 'name')
def list_users_in_group(self, context, filters, group_id):
hints = UserV3.build_driver_hints(context, filters)
refs = self.identity_api.list_users_in_group(group_id, hints=hints)
return UserV3.wrap_collection(context, refs, hints=hints)
@controller.protected()
def get_user(self, context, user_id):
ref = self.identity_api.get_user(user_id)
return UserV3.wrap_member(context, ref)
def _update_user(self, context, user_id, user):
self._require_matching_id(user_id, user)
self._require_matching_domain_id(
user_id, user, self.identity_api.get_user)
initiator = notifications._get_request_audit_info(context)
ref = self.identity_api.update_user(user_id, user, initiator)
return UserV3.wrap_member(context, ref)
@controller.protected()
@validation.validated(schema.user_update, 'user')
def update_user(self, context, user_id, user):
return self._update_user(context, user_id, user)
@controller.protected(callback=_check_user_and_group_protection)
def add_user_to_group(self, context, user_id, group_id):
initiator = notifications._get_request_audit_info(context)
self.identity_api.add_user_to_group(user_id, group_id, initiator)
@controller.protected(callback=_check_user_and_group_protection)
def check_user_in_group(self, context, user_id, group_id):
return self.identity_api.check_user_in_group(user_id, group_id)
@controller.protected(callback=_check_user_and_group_protection)
def remove_user_from_group(self, context, user_id, group_id):
initiator = notifications._get_request_audit_info(context)
self.identity_api.remove_user_from_group(user_id, group_id, initiator)
@controller.protected()
def delete_user(self, context, user_id):
initiator = notifications._get_request_audit_info(context)
return self.identity_api.delete_user(user_id, initiator)
@controller.protected()
def change_password(self, context, user_id, user):
original_password = user.get('original_password')
if original_password is None:
raise exception.ValidationError(target='user',
attribute='original_password')
password = user.get('password')
if password is None:
raise exception.ValidationError(target='user',
attribute='password')
try:
self.identity_api.change_password(
context, user_id, original_password, password)
except AssertionError:
raise exception.Unauthorized()
@dependency.requires('identity_api')
class GroupV3(controller.V3Controller):
collection_name = 'groups'
member_name = 'group'
def __init__(self):
super(GroupV3, self).__init__()
self.get_member_from_driver = self.identity_api.get_group
@controller.protected()
@validation.validated(schema.group_create, 'group')
def create_group(self, context, group):
# The manager layer will generate the unique ID for groups
ref = self._normalize_dict(group)
ref = self._normalize_domain_id(context, ref)
initiator = notifications._get_request_audit_info(context)
ref = self.identity_api.create_group(ref, initiator)
return GroupV3.wrap_member(context, ref)
@controller.filterprotected('domain_id', 'name')
def list_groups(self, context, filters):
hints = GroupV3.build_driver_hints(context, filters)
refs = self.identity_api.list_groups(
domain_scope=self._get_domain_id_for_list_request(context),
hints=hints)
return GroupV3.wrap_collection(context, refs, hints=hints)
@controller.filterprotected('name')
def list_groups_for_user(self, context, filters, user_id):
hints = GroupV3.build_driver_hints(context, filters)
refs = self.identity_api.list_groups_for_user(user_id, hints=hints)
return GroupV3.wrap_collection(context, refs, hints=hints)
@controller.protected()
def get_group(self, context, group_id):
ref = self.identity_api.get_group(group_id)
return GroupV3.wrap_member(context, ref)
@controller.protected()
@validation.validated(schema.group_update, 'group')
def update_group(self, context, group_id, group):
self._require_matching_id(group_id, group)
self._require_matching_domain_id(
group_id, group, self.identity_api.get_group)
initiator = notifications._get_request_audit_info(context)
ref = self.identity_api.update_group(group_id, group, initiator)
return GroupV3.wrap_member(context, ref)
@controller.protected()
def delete_group(self, context, group_id):
initiator = notifications._get_request_audit_info(context)
self.identity_api.delete_group(group_id, initiator)