assignment backend

Splits the assignments functions off of the identity api
and manager, and moved them into their own backend.

To prevent breaking existing code, this adds assignment delegation
functions to Identity Manager.

There is a circular dependency between ID and assignments.

This code is mostly pure refactoring, with no changes to the
unit tests.  Existing behavior is maintained.

In the future, we will add unit tests for mixing an LDAP
identity provider with a SQL assignment backend.

blueprint split-identity

Change-Id: I6c180aa1ae626ace5b91e0bf1931bdaf2aa031d5
This commit is contained in:
Adam Young 2013-06-05 16:28:16 -04:00
parent b556d8a6ca
commit fafdf072f5
17 changed files with 2453 additions and 1689 deletions

View File

@ -0,0 +1,18 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# flake8: noqa
# Copyright 2013 OpenStack LLC
#
# 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 keystone.assignment.core import *

View File

View File

@ -0,0 +1,498 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 OpenStack LLC
#
# 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 keystone import assignment
from keystone import clean
from keystone.common import kvs
from keystone import exception
from keystone import identity
class Assignment(kvs.Base, assignment.Driver):
def __init__(self):
super(Assignment, self).__init__()
# Public interface
def authorize_for_project(self, user_ref, tenant_id=None):
user_id = user_ref['id']
tenant_ref = None
metadata_ref = {}
if tenant_id is not None:
if tenant_id not in self.get_projects_for_user(user_id):
raise AssertionError('Invalid tenant')
try:
tenant_ref = self.get_project(tenant_id)
metadata_ref = self.get_metadata(user_id, tenant_id)
except exception.ProjectNotFound:
tenant_ref = None
metadata_ref = {}
except exception.MetadataNotFound:
metadata_ref = {}
return (identity.filter_user(user_ref), tenant_ref, metadata_ref)
def get_project(self, tenant_id):
try:
return self.db.get('tenant-%s' % tenant_id)
except exception.NotFound:
raise exception.ProjectNotFound(project_id=tenant_id)
def list_projects(self):
tenant_keys = filter(lambda x: x.startswith("tenant-"),
self.db.keys())
return [self.db.get(key) for key in tenant_keys]
def get_project_by_name(self, tenant_name, domain_id):
try:
return self.db.get('tenant_name-%s' % tenant_name)
except exception.NotFound:
raise exception.ProjectNotFound(project_id=tenant_name)
def get_project_users(self, tenant_id):
self.get_project(tenant_id)
user_keys = filter(lambda x: x.startswith("user-"), self.db.keys())
user_refs = [self.db.get(key) for key in user_keys]
user_refs = filter(lambda x: tenant_id in x['tenants'], user_refs)
return [identity.filter_user(user_ref) for user_ref in user_refs]
def _get_user(self, user_id):
try:
return self.db.get('user-%s' % user_id)
except exception.NotFound:
raise exception.UserNotFound(user_id=user_id)
def _get_user_by_name(self, user_name, domain_id):
try:
return self.db.get('user_name-%s' % user_name)
except exception.NotFound:
raise exception.UserNotFound(user_id=user_name)
def get_metadata(self, user_id=None, tenant_id=None,
domain_id=None, group_id=None):
try:
if user_id:
if tenant_id:
return self.db.get('metadata-%s-%s' % (tenant_id,
user_id))
else:
return self.db.get('metadata-%s-%s' % (domain_id,
user_id))
else:
if tenant_id:
return self.db.get('metadata-%s-%s' % (tenant_id,
group_id))
else:
return self.db.get('metadata-%s-%s' % (domain_id,
group_id))
except exception.NotFound:
raise exception.MetadataNotFound()
def get_role(self, role_id):
try:
return self.db.get('role-%s' % role_id)
except exception.NotFound:
raise exception.RoleNotFound(role_id=role_id)
def list_roles(self):
role_ids = self.db.get('role_list', [])
return [self.get_role(x) for x in role_ids]
def get_projects_for_user(self, user_id):
user_ref = self._get_user(user_id)
return user_ref.get('tenants', [])
def get_roles_for_user_and_project(self, user_id, tenant_id):
self.identity_api.get_user(user_id)
self.get_project(tenant_id)
try:
metadata_ref = self.get_metadata(user_id, tenant_id)
except exception.MetadataNotFound:
metadata_ref = {}
return metadata_ref.get('roles', [])
def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
self.identity_api.get_user(user_id)
self.get_project(tenant_id)
self.get_role(role_id)
try:
metadata_ref = self.get_metadata(user_id, tenant_id)
except exception.MetadataNotFound:
metadata_ref = {}
roles = set(metadata_ref.get('roles', []))
if role_id in roles:
msg = ('User %s already has role %s in tenant %s'
% (user_id, role_id, tenant_id))
raise exception.Conflict(type='role grant', details=msg)
roles.add(role_id)
metadata_ref['roles'] = list(roles)
self.update_metadata(user_id, tenant_id, metadata_ref)
def remove_role_from_user_and_project(self, user_id, tenant_id, role_id):
try:
metadata_ref = self.get_metadata(user_id, tenant_id)
except exception.MetadataNotFound:
metadata_ref = {}
roles = set(metadata_ref.get('roles', []))
if role_id not in roles:
msg = 'Cannot remove role that has not been granted, %s' % role_id
raise exception.RoleNotFound(message=msg)
roles.remove(role_id)
metadata_ref['roles'] = list(roles)
if not len(roles):
self.db.delete('metadata-%s-%s' % (tenant_id, user_id))
user_ref = self._get_user(user_id)
tenants = set(user_ref.get('tenants', []))
tenants.remove(tenant_id)
user_ref['tenants'] = list(tenants)
self.identity_api.update_user(user_id, user_ref)
else:
self.update_metadata(user_id, tenant_id, metadata_ref)
def list_role_assignments(self):
"""List the role assignments.
The kvs backend stores role assignments as key-values:
"metadata-{target}-{actor}", with the value being a role list
i.e. "metadata-MyProjectID-MyUserID" [role1, role2]
...so we enumerate the list and extract the targets, actors
and roles.
"""
assignment_list = []
metadata_keys = filter(lambda x: x.startswith("metadata-"),
self.db.keys())
for key in metadata_keys:
template = {}
meta_id1 = key.split('-')[1]
meta_id2 = key.split('-')[2]
try:
self.get_project(meta_id1)
template['project_id'] = meta_id1
except exception.NotFound:
template['domain_id'] = meta_id1
try:
self._get_user(meta_id2)
template['user_id'] = meta_id2
except exception.NotFound:
template['group_id'] = meta_id2
entry = self.db.get(key)
for r in entry.get('roles', []):
role_assignment = template.copy()
role_assignment['role_id'] = r
assignment_list.append(role_assignment)
return assignment_list
# CRUD
def create_project(self, tenant_id, tenant):
tenant['name'] = clean.project_name(tenant['name'])
try:
self.get_project(tenant_id)
except exception.ProjectNotFound:
pass
else:
msg = 'Duplicate ID, %s.' % tenant_id
raise exception.Conflict(type='tenant', details=msg)
try:
self.get_project_by_name(tenant['name'], tenant['domain_id'])
except exception.ProjectNotFound:
pass
else:
msg = 'Duplicate name, %s.' % tenant['name']
raise exception.Conflict(type='tenant', details=msg)
self.db.set('tenant-%s' % tenant_id, tenant)
self.db.set('tenant_name-%s' % tenant['name'], tenant)
return tenant
def update_project(self, tenant_id, tenant):
if 'name' in tenant:
tenant['name'] = clean.project_name(tenant['name'])
try:
existing = self.db.get('tenant_name-%s' % tenant['name'])
if existing and tenant_id != existing['id']:
msg = 'Duplicate name, %s.' % tenant['name']
raise exception.Conflict(type='tenant', details=msg)
except exception.NotFound:
pass
# get the old name and delete it too
try:
old_project = self.db.get('tenant-%s' % tenant_id)
except exception.NotFound:
raise exception.ProjectNotFound(project_id=tenant_id)
new_project = old_project.copy()
new_project.update(tenant)
new_project['id'] = tenant_id
self.db.delete('tenant_name-%s' % old_project['name'])
self.db.set('tenant-%s' % tenant_id, new_project)
self.db.set('tenant_name-%s' % new_project['name'], new_project)
return new_project
def delete_project(self, tenant_id):
try:
old_project = self.db.get('tenant-%s' % tenant_id)
except exception.NotFound:
raise exception.ProjectNotFound(project_id=tenant_id)
self.db.delete('tenant_name-%s' % old_project['name'])
self.db.delete('tenant-%s' % tenant_id)
def create_metadata(self, user_id, tenant_id, metadata,
domain_id=None, group_id=None):
return self.update_metadata(user_id, tenant_id, metadata,
domain_id, group_id)
def update_metadata(self, user_id, tenant_id, metadata,
domain_id=None, group_id=None):
if user_id:
if tenant_id:
self.db.set('metadata-%s-%s' % (tenant_id, user_id), metadata)
user_ref = self._get_user(user_id)
tenants = set(user_ref.get('tenants', []))
if tenant_id not in tenants:
tenants.add(tenant_id)
user_ref['tenants'] = list(tenants)
self.identity_api.update_user(user_id, user_ref)
else:
self.db.set('metadata-%s-%s' % (domain_id, user_id), metadata)
else:
if tenant_id:
self.db.set('metadata-%s-%s' % (tenant_id, group_id), metadata)
else:
self.db.set('metadata-%s-%s' % (domain_id, group_id), metadata)
return metadata
def create_role(self, role_id, role):
try:
self.get_role(role_id)
except exception.RoleNotFound:
pass
else:
msg = 'Duplicate ID, %s.' % role_id
raise exception.Conflict(type='role', details=msg)
for role_ref in self.list_roles():
if role['name'] == role_ref['name']:
msg = 'Duplicate name, %s.' % role['name']
raise exception.Conflict(type='role', details=msg)
self.db.set('role-%s' % role_id, role)
role_list = set(self.db.get('role_list', []))
role_list.add(role_id)
self.db.set('role_list', list(role_list))
return role
def update_role(self, role_id, role):
old_role_ref = None
for role_ref in self.list_roles():
if role['name'] == role_ref['name'] and role_id != role_ref['id']:
msg = 'Duplicate name, %s.' % role['name']
raise exception.Conflict(type='role', details=msg)
if role_id == role_ref['id']:
old_role_ref = role_ref
if old_role_ref is None:
raise exception.RoleNotFound(role_id=role_id)
new_role = old_role_ref.copy()
new_role.update(role)
new_role['id'] = role_id
self.db.set('role-%s' % role_id, new_role)
return role
def delete_role(self, role_id):
self.get_role(role_id)
metadata_keys = filter(lambda x: x.startswith("metadata-"),
self.db.keys())
for key in metadata_keys:
meta_id1 = key.split('-')[1]
meta_id2 = key.split('-')[2]
try:
self.delete_grant(role_id, project_id=meta_id1,
user_id=meta_id2)
except exception.NotFound:
pass
try:
self.delete_grant(role_id, project_id=meta_id1,
group_id=meta_id2)
except exception.NotFound:
pass
try:
self.delete_grant(role_id, domain_id=meta_id1,
user_id=meta_id2)
except exception.NotFound:
pass
try:
self.delete_grant(role_id, domain_id=meta_id1,
group_id=meta_id2)
except exception.NotFound:
pass
self.db.delete('role-%s' % role_id)
role_list = set(self.db.get('role_list', []))
role_list.remove(role_id)
self.db.set('role_list', list(role_list))
def create_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None):
self.get_role(role_id)
if user_id:
self.identity_api.get_user(user_id)
if group_id:
self.identity_api.get_group(group_id)
if domain_id:
self.get_domain(domain_id)
if project_id:
self.get_project(project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
except exception.MetadataNotFound:
metadata_ref = {}
roles = set(metadata_ref.get('roles', []))
roles.add(role_id)
metadata_ref['roles'] = list(roles)
self.update_metadata(user_id, project_id, metadata_ref,
domain_id, group_id)
def list_grants(self, user_id=None, group_id=None,
domain_id=None, project_id=None):
if user_id:
self.identity_api.get_user(user_id)
if group_id:
self.identity_api.get_group(group_id)
if domain_id:
self.get_domain(domain_id)
if project_id:
self.get_project(project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
except exception.MetadataNotFound:
metadata_ref = {}
return [self.get_role(x) for x in metadata_ref.get('roles', [])]
def get_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None):
self.get_role(role_id)
if user_id:
self.identity_api.get_user(user_id)
if group_id:
self.get_group(group_id)
if domain_id:
self.get_domain(domain_id)
if project_id:
self.get_project(project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
except exception.MetadataNotFound:
metadata_ref = {}
role_ids = set(metadata_ref.get('roles', []))
if role_id not in role_ids:
raise exception.RoleNotFound(role_id=role_id)
return self.get_role(role_id)
def delete_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None):
self.get_role(role_id)
if user_id:
self.identity_api.get_user(user_id)
if group_id:
self.identity_api.get_group(group_id)
if domain_id:
self.get_domain(domain_id)
if project_id:
self.get_project(project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
except exception.MetadataNotFound:
metadata_ref = {}
roles = set(metadata_ref.get('roles', []))
try:
roles.remove(role_id)
except KeyError:
raise exception.RoleNotFound(role_id=role_id)
metadata_ref['roles'] = list(roles)
self.update_metadata(user_id, project_id, metadata_ref,
domain_id, group_id)
# domain crud
def create_domain(self, domain_id, domain):
try:
self.get_domain(domain_id)
except exception.DomainNotFound:
pass
else:
msg = 'Duplicate ID, %s.' % domain_id
raise exception.Conflict(type='domain', details=msg)
try:
self.get_domain_by_name(domain['name'])
except exception.DomainNotFound:
pass
else:
msg = 'Duplicate name, %s.' % domain['name']
raise exception.Conflict(type='domain', details=msg)
self.db.set('domain-%s' % domain_id, domain)
self.db.set('domain_name-%s' % domain['name'], domain)
domain_list = set(self.db.get('domain_list', []))
domain_list.add(domain_id)
self.db.set('domain_list', list(domain_list))
return domain
def list_domains(self):
domain_ids = self.db.get('domain_list', [])
return [self.get_domain(x) for x in domain_ids]
def get_domain(self, domain_id):
try:
return self.db.get('domain-%s' % domain_id)
except exception.NotFound:
raise exception.DomainNotFound(domain_id=domain_id)
def get_domain_by_name(self, domain_name):
try:
return self.db.get('domain_name-%s' % domain_name)
except exception.NotFound:
raise exception.DomainNotFound(domain_id=domain_name)
def update_domain(self, domain_id, domain):
orig_domain = self.get_domain(domain_id)
domain['id'] = domain_id
self.db.set('domain-%s' % domain_id, domain)
self.db.set('domain_name-%s' % domain['name'], domain)
if domain['name'] != orig_domain['name']:
self.db.delete('domain_name-%s' % orig_domain['name'])
return domain
def delete_domain(self, domain_id):
domain = self.get_domain(domain_id)
self.db.delete('domain-%s' % domain_id)
self.db.delete('domain_name-%s' % domain['name'])
domain_list = set(self.db.get('domain_list', []))
domain_list.remove(domain_id)
self.db.set('domain_list', list(domain_list))

View File

@ -0,0 +1,604 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012-2013 OpenStack LLC
#
# 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 __future__ import absolute_import
import uuid
import ldap as ldap
from keystone import assignment
from keystone import clean
from keystone.common import ldap as common_ldap
from keystone.common import logging
from keystone.common import models
from keystone import config
from keystone import exception
from keystone import identity
from keystone.identity.backends import ldap as ldap_identity
CONF = config.CONF
LOG = logging.getLogger(__name__)
DEFAULT_DOMAIN = {
'id': CONF.identity.default_domain_id,
'name': 'Default',
'enabled': True
}
class Assignment(assignment.Driver):
def __init__(self):
super(Assignment, self).__init__()
self.LDAP_URL = CONF.ldap.url
self.LDAP_USER = CONF.ldap.user
self.LDAP_PASSWORD = CONF.ldap.password
self.suffix = CONF.ldap.suffix
#These are the only deep dependency from assignment back
#to identity. The assumption is that if you are using
#LDAP for assignments, you are using it for Id as well.
self.user = ldap_identity.UserApi(CONF)
self.group = ldap_identity.GroupApi(CONF)
self.project = ProjectApi(CONF)
self.role = RoleApi(CONF)
self._identity_api = None
@property
def identity_api(self):
return self._identity_api
@identity_api.setter
def identity_api(self, value):
self._identity_api = value
#TODO(ayoung): only left here to prevent unit test from breaking
#once we remove here. the getter and setter can be removed as well.
self._identity_api.driver.project = self.project
def authorize_for_project(self, user_ref, tenant_id=None):
user_id = user_ref['id']
tenant_ref = None
metadata_ref = {}
if tenant_id is not None:
if tenant_id not in self.get_projects_for_user(user_id):
raise AssertionError('Invalid tenant')
try:
tenant_ref = self.get_project(tenant_id)
# TODO(termie): this should probably be made into a
# get roles call
metadata_ref = self.get_metadata(user_id, tenant_id)
except exception.ProjectNotFound:
tenant_ref = None
metadata_ref = {}
except exception.MetadataNotFound:
metadata_ref = {}
user_ref = self._set_default_domain(identity.filter_user(user_ref))
return (user_ref, tenant_ref, metadata_ref)
def get_project(self, tenant_id):
return self._set_default_domain(self.project.get(tenant_id))
def list_projects(self):
return self._set_default_domain(self.project.get_all())
def get_project_by_name(self, tenant_name, domain_id):
self._validate_domain_id(domain_id)
return self._set_default_domain(self.project.get_by_name(tenant_name))
def _validate_domain(self, ref):
"""Validate that either the default domain or nothing is specified.
Also removes the domain from the ref so that LDAP doesn't have to
persist the attribute.
"""
ref = ref.copy()
domain_id = ref.pop('domain_id', CONF.identity.default_domain_id)
self._validate_domain_id(domain_id)
return ref
def _validate_domain_id(self, domain_id):
"""Validate that the domain ID specified belongs to the default domain.
"""
if domain_id != CONF.identity.default_domain_id:
raise exception.DomainNotFound(domain_id=domain_id)
def _set_default_domain(self, ref):
"""Overrides any domain reference with the default domain."""
if isinstance(ref, dict):
ref = ref.copy()
ref['domain_id'] = CONF.identity.default_domain_id
return ref
elif isinstance(ref, list):
return [self._set_default_domain(x) for x in ref]
else:
raise ValueError(_('Expected dict or list: %s') % type(ref))
def create_project(self, tenant_id, tenant):
tenant = self._validate_domain(tenant)
tenant['name'] = clean.project_name(tenant['name'])
data = tenant.copy()
if 'id' not in data or data['id'] is None:
data['id'] = str(uuid.uuid4().hex)
if 'description' in data and data['description'] in ['', None]:
data.pop('description')
return self._set_default_domain(self.project.create(data))
def update_project(self, tenant_id, tenant):
tenant = self._validate_domain(tenant)
if 'name' in tenant:
tenant['name'] = clean.project_name(tenant['name'])
return self._set_default_domain(self.project.update(tenant_id, tenant))
def get_metadata(self, user_id=None, tenant_id=None,
domain_id=None, group_id=None):
def _get_roles_for_just_user_and_project(user_id, tenant_id):
self.identity_api.get_user(user_id)
self.get_project(tenant_id)
user_dn = self.user._id_to_dn(user_id)
return [self.role._dn_to_id(a.role_dn)
for a in self.role.get_role_assignments
(self.project._id_to_dn(tenant_id))
if a.user_dn == user_dn]
if domain_id is not None:
msg = 'Domain metadata not supported by LDAP'
raise exception.NotImplemented(message=msg)
if (not self.get_project(tenant_id) or
not self.identity_api.get_user(user_id)):
return {}
metadata_ref = _get_roles_for_just_user_and_project(user_id, tenant_id)
if not metadata_ref:
return {}
return {'roles': metadata_ref}
def get_role(self, role_id):
return self.role.get(role_id)
def list_roles(self):
return self.role.get_all()
def get_projects_for_user(self, user_id):
self.identity_api.get_user(user_id)
user_dn = self.user._id_to_dn(user_id)
associations = (self.role.list_project_roles_for_user
(user_dn, self.project.tree_dn))
return [p['id'] for p in
self.project.get_user_projects(user_dn, associations)]
def get_project_users(self, tenant_id):
self.get_project(tenant_id)
tenant_dn = self.project._id_to_dn(tenant_id)
rolegrants = self.role.get_role_assignments(tenant_dn)
users = [self.user.get_filtered(self.user._dn_to_id(user_id))
for user_id in
self.project.get_user_dns(tenant_id, rolegrants)]
return self._set_default_domain(users)
def _subrole_id_to_dn(self, role_id, tenant_id):
if tenant_id is None:
return self.role._id_to_dn(role_id)
else:
return '%s=%s,%s' % (self.role.id_attr,
ldap.dn.escape_dn_chars(role_id),
self.project._id_to_dn(tenant_id))
def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
self.identity_api.get_user(user_id)
self.get_project(tenant_id)
self.get_role(role_id)
user_dn = self.user._id_to_dn(user_id)
role_dn = self._subrole_id_to_dn(role_id, tenant_id)
self.role.add_user(role_id, role_dn, user_dn, user_id, tenant_id)
tenant_dn = self.project._id_to_dn(tenant_id)
return UserRoleAssociation(
role_dn=role_dn,
user_dn=user_dn,
tenant_dn=tenant_dn)
def create_metadata(self, user_id, tenant_id, metadata):
return {}
def create_role(self, role_id, role):
try:
self.get_role(role_id)
except exception.NotFound:
pass
else:
msg = 'Duplicate ID, %s.' % role_id
raise exception.Conflict(type='role', details=msg)
try:
self.role.get_by_name(role['name'])
except exception.NotFound:
pass
else:
msg = 'Duplicate name, %s.' % role['name']
raise exception.Conflict(type='role', details=msg)
return self.role.create(role)
def delete_role(self, role_id):
return self.role.delete(role_id, self.project.tree_dn)
def delete_project(self, tenant_id):
if self.project.subtree_delete_enabled:
self.project.deleteTree(id)
else:
tenant_dn = self.project._id_to_dn(tenant_id)
self.role.roles_delete_subtree_by_project(tenant_dn)
self.project.delete(tenant_id)
def remove_role_from_user_and_project(self, user_id, tenant_id, role_id):
role_dn = self._subrole_id_to_dn(role_id, tenant_id)
return self.role.delete_user(role_dn,
self.user._id_to_dn(user_id),
self.project._id_to_dn(tenant_id),
user_id, role_id)
def update_role(self, role_id, role):
self.get_role(role_id)
self.role.update(role_id, role)
def create_domain(self, domain_id, domain):
if domain_id == CONF.identity.default_domain_id:
msg = 'Duplicate ID, %s.' % domain_id
raise exception.Conflict(type='domain', details=msg)
raise exception.Forbidden('Domains are read-only against LDAP')
def get_domain(self, domain_id):
self._validate_domain_id(domain_id)
return DEFAULT_DOMAIN
def update_domain(self, domain_id, domain):
self._validate_domain_id(domain_id)
raise exception.Forbidden('Domains are read-only against LDAP')
def delete_domain(self, domain_id):
self._validate_domain_id(domain_id)
raise exception.Forbidden('Domains are read-only against LDAP')
def list_domains(self):
return [DEFAULT_DOMAIN]
#Bulk actions on User From identity
def delete_user(self, user_id):
user_dn = self.user._id_to_dn(user_id)
for ref in self.role.list_global_roles_for_user(user_dn):
self.role.delete_user(ref.role_dn, ref.user_dn, ref.project_dn,
user_id, self.role._dn_to_id(ref.role_dn))
for ref in self.role.list_project_roles_for_user(user_dn,
self.project.tree_dn):
self.role.delete_user(ref.role_dn, ref.user_dn, ref.project_dn,
user_id, self.role._dn_to_id(ref.role_dn))
user = self.user.get(user_id)
if hasattr(user, 'tenant_id'):
self.project.remove_user(user.tenant_id,
self.user._id_to_dn(user_id))
#LDAP assignments only supports LDAP identity. Assignments under identity
#are already deleted
def delete_group(self, group_id):
if not self.group.subtree_delete_enabled:
# TODO(spzala): this is only placeholder for group and domain
# role support which will be added under bug 1101287
conn = self.group.get_connection()
query = '(objectClass=%s)' % self.group.object_class
dn = None
dn = self.group._id_to_dn(id)
if dn:
try:
roles = conn.search_s(dn, ldap.SCOPE_ONELEVEL,
query, ['%s' % '1.1'])
for role_dn, _ in roles:
conn.delete_s(role_dn)
except ldap.NO_SUCH_OBJECT:
pass
# TODO(termie): turn this into a data object and move logic to driver
class ProjectApi(common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap):
DEFAULT_OU = 'ou=Groups'
DEFAULT_STRUCTURAL_CLASSES = []
DEFAULT_OBJECTCLASS = 'groupOfNames'
DEFAULT_ID_ATTR = 'cn'
DEFAULT_MEMBER_ATTRIBUTE = 'member'
DEFAULT_ATTRIBUTE_IGNORE = []
NotFound = exception.ProjectNotFound
notfound_arg = 'project_id' # NOTE(yorik-sar): while options_name = tenant
options_name = 'tenant'
attribute_mapping = {'name': 'ou',
'description': 'description',
'tenantId': 'cn',
'enabled': 'enabled',
'domain_id': 'domain_id'}
model = models.Project
def __init__(self, conf):
super(ProjectApi, self).__init__(conf)
self.attribute_mapping['name'] = conf.ldap.tenant_name_attribute
self.attribute_mapping['description'] = conf.ldap.tenant_desc_attribute
self.attribute_mapping['enabled'] = conf.ldap.tenant_enabled_attribute
self.attribute_mapping['domain_id'] = (
conf.ldap.tenant_domain_id_attribute)
self.member_attribute = (getattr(conf.ldap, 'tenant_member_attribute')
or self.DEFAULT_MEMBER_ATTRIBUTE)
self.attribute_ignore = (getattr(conf.ldap, 'tenant_attribute_ignore')
or self.DEFAULT_ATTRIBUTE_IGNORE)
def create(self, values):
self.affirm_unique(values)
data = values.copy()
if data.get('id') is None:
data['id'] = uuid.uuid4().hex
return super(ProjectApi, self).create(data)
def get_user_projects(self, user_dn, associations):
"""Returns list of tenants a user has access to
"""
project_ids = set()
for assoc in associations:
project_ids.add(self._dn_to_id(assoc.project_dn))
projects = []
for project_id in project_ids:
#slower to get them one at a time, but a huge list could blow out
#the connection. This is the safer way
projects.append(self.get(project_id))
return projects
def add_user(self, tenant_id, user_dn):
conn = self.get_connection()
try:
conn.modify_s(
self._id_to_dn(tenant_id),
[(ldap.MOD_ADD,
self.member_attribute,
user_dn)])
except ldap.TYPE_OR_VALUE_EXISTS:
# As adding a user to a tenant is done implicitly in several
# places, and is not part of the exposed API, it's easier for us to
# just ignore this instead of raising exception.Conflict.
pass
def remove_user(self, tenant_id, user_dn, user_id):
conn = self.get_connection()
try:
conn.modify_s(self._id_to_dn(tenant_id),
[(ldap.MOD_DELETE,
self.member_attribute,
user_dn)])
except ldap.NO_SUCH_ATTRIBUTE:
raise exception.NotFound(user_id)
def get_user_dns(self, tenant_id, rolegrants, role_dn=None):
tenant = self._ldap_get(tenant_id)
res = set()
if not role_dn:
# Get users who have default tenant mapping
for user_dn in tenant[1].get(self.member_attribute, []):
if self.use_dumb_member and user_dn == self.dumb_member:
continue
res.add(user_dn)
# Get users who are explicitly mapped via a tenant
for rolegrant in rolegrants:
if role_dn is None or rolegrant.role_dn == role_dn:
res.add(rolegrant.user_dn)
return list(res)
def update(self, id, values):
old_obj = self.get(id)
if old_obj['name'] != values['name']:
msg = 'Changing Name not supported by LDAP'
raise exception.NotImplemented(message=msg)
return super(ProjectApi, self).update(id, values, old_obj)
class UserRoleAssociation(object):
"""Role Grant model."""
def __init__(self, user_dn=None, role_dn=None, tenant_dn=None,
*args, **kw):
self.user_dn = user_dn
self.role_dn = role_dn
self.project_dn = tenant_dn
class GroupRoleAssociation(object):
"""Role Grant model."""
def __init__(self, group_dn=None, role_dn=None, tenant_dn=None,
*args, **kw):
self.group_dn = group_dn
self.role_dn = role_dn
self.project_dn = tenant_dn
# TODO(termie): turn this into a data object and move logic to driver
class RoleApi(common_ldap.BaseLdap):
DEFAULT_OU = 'ou=Roles'
DEFAULT_STRUCTURAL_CLASSES = []
DEFAULT_OBJECTCLASS = 'organizationalRole'
DEFAULT_MEMBER_ATTRIBUTE = 'roleOccupant'
DEFAULT_ATTRIBUTE_IGNORE = []
NotFound = exception.RoleNotFound
options_name = 'role'
attribute_mapping = {'name': 'ou',
#'serviceId': 'service_id',
}
model = models.Role
def __init__(self, conf):
super(RoleApi, self).__init__(conf)
self.attribute_mapping['name'] = conf.ldap.role_name_attribute
self.member_attribute = (getattr(conf.ldap, 'role_member_attribute')
or self.DEFAULT_MEMBER_ATTRIBUTE)
self.attribute_ignore = (getattr(conf.ldap, 'role_attribute_ignore')
or self.DEFAULT_ATTRIBUTE_IGNORE)
def get(self, id, filter=None):
model = super(RoleApi, self).get(id, filter)
return model
def create(self, values):
return super(RoleApi, self).create(values)
def add_user(self, role_id, role_dn, user_dn, user_id, tenant_id=None):
conn = self.get_connection()
try:
conn.modify_s(role_dn, [(ldap.MOD_ADD,
self.member_attribute, user_dn)])
except ldap.TYPE_OR_VALUE_EXISTS:
msg = ('User %s already has role %s in tenant %s'
% (user_id, role_id, tenant_id))
raise exception.Conflict(type='role grant', details=msg)
except ldap.NO_SUCH_OBJECT:
if tenant_id is None or self.get(role_id) is None:
raise Exception(_("Role %s not found") % (role_id,))
attrs = [('objectClass', [self.object_class]),
(self.member_attribute, [user_dn])]
if self.use_dumb_member:
attrs[1][1].append(self.dumb_member)
try:
conn.add_s(role_dn, attrs)
except Exception as inst:
raise inst
def delete_user(self, role_dn, user_dn, tenant_dn,
user_id, role_id):
conn = self.get_connection()
try:
conn.modify_s(role_dn, [(ldap.MOD_DELETE,
self.member_attribute, user_dn)])
except ldap.NO_SUCH_OBJECT:
if tenant_dn is None:
raise exception.RoleNotFound(role_id=role_id)
attrs = [('objectClass', [self.object_class]),
(self.member_attribute, [user_dn])]
if self.use_dumb_member:
attrs[1][1].append(self.dumb_member)
try:
conn.add_s(role_dn, attrs)
except Exception as inst:
raise inst
except ldap.NO_SUCH_ATTRIBUTE:
raise exception.UserNotFound(user_id=user_id)
def get_role_assignments(self, tenant_dn):
conn = self.get_connection()
query = '(objectClass=%s)' % self.object_class
try:
roles = conn.search_s(tenant_dn, ldap.SCOPE_ONELEVEL, query)
except ldap.NO_SUCH_OBJECT:
return []
res = []
for role_dn, attrs in roles:
try:
user_dns = attrs[self.member_attribute]
except KeyError:
continue
for user_dn in user_dns:
if self.use_dumb_member and user_dn == self.dumb_member:
continue
res.append(UserRoleAssociation(
user_dn=user_dn,
role_dn=role_dn,
tenant_dn=tenant_dn))
return res
def list_global_roles_for_user(self, user_dn):
roles = self.get_all('(%s=%s)' % (self.member_attribute, user_dn))
return [UserRoleAssociation(
role_dn=role.dn,
user_dn=user_dn) for role in roles]
def list_project_roles_for_user(self, user_dn, project_subtree):
conn = self.get_connection()
query = '(&(objectClass=%s)(%s=%s))' % (self.object_class,
self.member_attribute,
user_dn)
try:
roles = conn.search_s(project_subtree,
ldap.SCOPE_SUBTREE,
query)
except ldap.NO_SUCH_OBJECT:
return []
res = []
for role_dn, _ in roles:
#ldap.dn.dn2str returns an array, where the first
#element is the first segment.
#For a role assignment, this contains the role ID,
#The remainder is the DN of the tenant.
tenant = ldap.dn.str2dn(role_dn)
tenant.pop(0)
tenant_dn = ldap.dn.dn2str(tenant)
res.append(UserRoleAssociation(
user_dn=user_dn,
role_dn=role_dn,
tenant_dn=tenant_dn))
return res
def roles_delete_subtree_by_project(self, tenant_dn):
conn = self.get_connection()
query = '(objectClass=%s)' % self.object_class
try:
roles = conn.search_s(tenant_dn, ldap.SCOPE_ONELEVEL, query)
for role_dn, _ in roles:
try:
conn.delete_s(role_dn)
except Exception as inst:
raise inst
except ldap.NO_SUCH_OBJECT:
pass
def update(self, role_id, role):
if role['id'] != role_id:
raise exception.ValidationError('Cannot change role ID')
try:
old_name = self.get_by_name(role['name'])
raise exception.Conflict('Cannot duplicate name %s' % old_name)
except exception.NotFound:
pass
return super(RoleApi, self).update(role_id, role)
def delete(self, id, tenant_dn):
conn = self.get_connection()
query = '(&(objectClass=%s)(%s=%s))' % (self.object_class,
self.id_attr, id)
try:
for role_dn, _ in conn.search_s(tenant_dn,
ldap.SCOPE_SUBTREE,
query):
conn.delete_s(role_dn)
except ldap.NO_SUCH_OBJECT:
pass
super(RoleApi, self).delete(id)

View File

@ -0,0 +1,707 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012-13 OpenStack LLC
#
# 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 keystone import assignment
from keystone import clean
from keystone.common import sql
from keystone.common.sql import migration
from keystone import exception
from keystone import identity
class Assignment(sql.Base, assignment.Driver):
def __init__(self):
super(Assignment, self).__init__()
self.identity_api = None
# Internal interface to manage the database
def db_sync(self, version=None):
migration.db_sync(version=version)
def authorize_for_project(self, user_ref, tenant_id=None):
user_id = user_ref['id']
tenant_ref = None
metadata_ref = {}
if tenant_id is not None:
# FIXME(gyee): this should really be
# get_roles_for_user_and_project() after the dusts settle
if tenant_id not in self.get_projects_for_user(user_id):
raise AssertionError('Invalid project')
try:
tenant_ref = self.get_project(tenant_id)
metadata_ref = self.get_metadata(user_id, tenant_id)
except exception.ProjectNotFound:
tenant_ref = None
metadata_ref = {}
except exception.MetadataNotFound:
metadata_ref = {}
user_ref = identity.filter_user(user_ref.to_dict())
return (user_ref, tenant_ref, metadata_ref)
def _get_project(self, session, project_id):
project_ref = session.query(Project).get(project_id)
if project_ref is None:
raise exception.ProjectNotFound(project_id=project_id)
return project_ref
def get_project(self, tenant_id):
session = self.get_session()
return self._get_project(session, tenant_id).to_dict()
def get_project_by_name(self, tenant_name, domain_id):
session = self.get_session()
query = session.query(Project)
query = query.filter_by(name=tenant_name)
query = query.filter_by(domain_id=domain_id)
try:
project_ref = query.one()
except sql.NotFound:
raise exception.ProjectNotFound(project_id=tenant_name)
return project_ref.to_dict()
def get_project_user_ids(self, tenant_id):
session = self.get_session()
self.get_project(tenant_id)
query = session.query(UserProjectGrant)
query = query.filter(UserProjectGrant.project_id ==
tenant_id)
project_refs = query.all()
return [project_ref.user_id for project_ref in project_refs]
def get_project_users(self, tenant_id):
self.get_session()
self.get_project(tenant_id)
user_refs = []
#TODO(ayoung): Move to controller or manager
for user_id in self.get_project_user_ids(tenant_id):
user_ref = self.identity_api.get_user(user_id)
user_refs.append(user_ref)
return user_refs
def get_metadata(self, user_id=None, tenant_id=None,
domain_id=None, group_id=None):
session = self.get_session()
if user_id:
if tenant_id:
q = session.query(UserProjectGrant)
q = q.filter_by(project_id=tenant_id)
elif domain_id:
q = session.query(UserDomainGrant)
q = q.filter_by(domain_id=domain_id)
q = q.filter_by(user_id=user_id)
elif group_id:
if tenant_id:
q = session.query(GroupProjectGrant)
q = q.filter_by(project_id=tenant_id)
elif domain_id:
q = session.query(GroupDomainGrant)
q = q.filter_by(domain_id=domain_id)
q = q.filter_by(group_id=group_id)
try:
return q.one().data
except sql.NotFound:
raise exception.MetadataNotFound()
def create_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None):
session = self.get_session()
self._get_role(session, role_id)
if user_id:
self.identity_api._get_user(session, user_id)
if group_id:
self.identity_api._get_group(session, group_id)
if domain_id:
self._get_domain(session, domain_id)
if project_id:
self._get_project(session, project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
is_new = False
except exception.MetadataNotFound:
metadata_ref = {}
is_new = True
roles = set(metadata_ref.get('roles', []))
roles.add(role_id)
metadata_ref['roles'] = list(roles)
if is_new:
self.create_metadata(user_id, project_id, metadata_ref,
domain_id, group_id)
else:
self.update_metadata(user_id, project_id, metadata_ref,
domain_id, group_id)
def list_grants(self, user_id=None, group_id=None,
domain_id=None, project_id=None):
session = self.get_session()
if user_id:
self.identity_api._get_user(session, user_id)
if group_id:
self.identity_api._get_group(session, group_id)
if domain_id:
self._get_domain(session, domain_id)
if project_id:
self._get_project(session, project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
except exception.MetadataNotFound:
metadata_ref = {}
return [self.get_role(x) for x in metadata_ref.get('roles', [])]
def get_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None):
session = self.get_session()
role_ref = self._get_role(session, role_id)
if user_id:
self.identity_api._get_user(session, user_id)
if group_id:
self.identity_api._get_group(session, group_id)
if domain_id:
self._get_domain(session, domain_id)
if project_id:
self._get_project(session, project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
except exception.MetadataNotFound:
metadata_ref = {}
role_ids = set(metadata_ref.get('roles', []))
if role_id not in role_ids:
raise exception.RoleNotFound(role_id=role_id)
return role_ref.to_dict()
def delete_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None):
session = self.get_session()
self._get_role(session, role_id)
if user_id:
self.identity_api._get_user(session, user_id)
if group_id:
self.identity_api._get_group(session, group_id)
if domain_id:
self._get_domain(session, domain_id)
if project_id:
self._get_project(session, project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
is_new = False
except exception.MetadataNotFound:
metadata_ref = {}
is_new = True
roles = set(metadata_ref.get('roles', []))
try:
roles.remove(role_id)
except KeyError:
raise exception.RoleNotFound(role_id=role_id)
metadata_ref['roles'] = list(roles)
if is_new:
self.create_metadata(user_id, project_id, metadata_ref,
domain_id, group_id)
else:
self.update_metadata(user_id, project_id, metadata_ref,
domain_id, group_id)
def list_projects(self):
session = self.get_session()
tenant_refs = session.query(Project).all()
return [tenant_ref.to_dict() for tenant_ref in tenant_refs]
def get_projects_for_user(self, user_id):
session = self.get_session()
self.identity_api._get_user(session, user_id)
query = session.query(UserProjectGrant)
query = query.filter_by(user_id=user_id)
membership_refs = query.all()
return [x.project_id for x in membership_refs]
def _get_user_group_project_roles(self, metadata_ref, user_id, project_id):
group_refs = self.identity_api.list_groups_for_user(user_id=user_id)
for x in group_refs:
try:
metadata_ref.update(
self.get_metadata(group_id=x['id'],
tenant_id=project_id))
except exception.MetadataNotFound:
# no group grant, skip
pass
def _get_user_project_roles(self, metadata_ref, user_id, project_id):
try:
metadata_ref.update(self.get_metadata(user_id, project_id))
except exception.MetadataNotFound:
pass
def get_roles_for_user_and_project(self, user_id, tenant_id):
session = self.get_session()
self.identity_api._get_user(session, user_id)
self._get_project(session, tenant_id)
metadata_ref = {}
self._get_user_project_roles(metadata_ref, user_id, tenant_id)
self._get_user_group_project_roles(metadata_ref, user_id, tenant_id)
return list(set(metadata_ref.get('roles', [])))
def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
session = self.get_session()
self.identity_api._get_user(session, user_id)
self._get_project(session, tenant_id)
self._get_role(session, role_id)
try:
metadata_ref = self.get_metadata(user_id, tenant_id)
is_new = False
except exception.MetadataNotFound:
metadata_ref = {}
is_new = True
roles = set(metadata_ref.get('roles', []))
if role_id in roles:
msg = ('User %s already has role %s in tenant %s'
% (user_id, role_id, tenant_id))
raise exception.Conflict(type='role grant', details=msg)
roles.add(role_id)
metadata_ref['roles'] = list(roles)
if is_new:
self.create_metadata(user_id, tenant_id, metadata_ref)
else:
self.update_metadata(user_id, tenant_id, metadata_ref)
def remove_role_from_user_and_project(self, user_id, tenant_id, role_id):
try:
metadata_ref = self.get_metadata(user_id, tenant_id)
roles = set(metadata_ref.get('roles', []))
if role_id not in roles:
raise exception.RoleNotFound(message=_(
'Cannot remove role that has not been granted, %s') %
role_id)
roles.remove(role_id)
metadata_ref['roles'] = list(roles)
if len(roles):
self.update_metadata(user_id, tenant_id, metadata_ref)
else:
session = self.get_session()
q = session.query(UserProjectGrant)
q = q.filter_by(user_id=user_id)
q = q.filter_by(project_id=tenant_id)
q.delete()
except exception.MetadataNotFound:
msg = 'Cannot remove role that has not been granted, %s' % role_id
raise exception.RoleNotFound(message=msg)
def list_role_assignments(self):
# TODO(henry-nash): The current implementation is really simulating
# us having a common role assignment table, rather than having the
# four different grant tables we have today. When we move to role
# assignment as a first class entity, we should create the single
# assignment table, simplifying the logic of this (and many other)
# functions.
session = self.get_session()
assignment_list = []
refs = session.query(UserDomainGrant).all()
for x in refs:
for r in x.data.get('roles', []):
assignment_list.append({'user_id': x.user_id,
'domain_id': x.domain_id,
'role_id': r})
refs = session.query(UserProjectGrant).all()
for x in refs:
for r in x.data.get('roles', []):
assignment_list.append({'user_id': x.user_id,
'project_id': x.project_id,
'role_id': r})
refs = session.query(GroupDomainGrant).all()
for x in refs:
for r in x.data.get('roles', []):
assignment_list.append({'group_id': x.group_id,
'domain_id': x.domain_id,
'role_id': r})
refs = session.query(GroupProjectGrant).all()
for x in refs:
for r in x.data.get('roles', []):
assignment_list.append({'group_id': x.group_id,
'project_id': x.project_id,
'role_id': r})
return assignment_list
# CRUD
@sql.handle_conflicts(type='project')
def create_project(self, tenant_id, tenant):
tenant['name'] = clean.project_name(tenant['name'])
session = self.get_session()
with session.begin():
tenant_ref = Project.from_dict(tenant)
session.add(tenant_ref)
session.flush()
return tenant_ref.to_dict()
@sql.handle_conflicts(type='project')
def update_project(self, tenant_id, tenant):
session = self.get_session()
if 'name' in tenant:
tenant['name'] = clean.project_name(tenant['name'])
with session.begin():
tenant_ref = self._get_project(session, tenant_id)
old_project_dict = tenant_ref.to_dict()
for k in tenant:
old_project_dict[k] = tenant[k]
new_project = Project.from_dict(old_project_dict)
for attr in Project.attributes:
if attr != 'id':
setattr(tenant_ref, attr, getattr(new_project, attr))
tenant_ref.extra = new_project.extra
session.flush()
return tenant_ref.to_dict(include_extra_dict=True)
@sql.handle_conflicts(type='project')
def delete_project(self, tenant_id):
session = self.get_session()
with session.begin():
tenant_ref = self._get_project(session, tenant_id)
q = session.query(UserProjectGrant)
q = q.filter_by(project_id=tenant_id)
q.delete(False)
q = session.query(UserProjectGrant)
q = q.filter_by(project_id=tenant_id)
q.delete(False)
q = session.query(GroupProjectGrant)
q = q.filter_by(project_id=tenant_id)
q.delete(False)
session.delete(tenant_ref)
session.flush()
@sql.handle_conflicts(type='metadata')
def create_metadata(self, user_id, tenant_id, metadata,
domain_id=None, group_id=None):
session = self.get_session()
with session.begin():
if user_id:
if tenant_id:
session.add(UserProjectGrant
(user_id=user_id,
project_id=tenant_id,
data=metadata))
elif domain_id:
session.add(UserDomainGrant
(user_id=user_id,
domain_id=domain_id,
data=metadata))
elif group_id:
if tenant_id:
session.add(GroupProjectGrant
(group_id=group_id,
project_id=tenant_id,
data=metadata))
elif domain_id:
session.add(GroupDomainGrant
(group_id=group_id,
domain_id=domain_id,
data=metadata))
session.flush()
return metadata
@sql.handle_conflicts(type='metadata')
def update_metadata(self, user_id, tenant_id, metadata,
domain_id=None, group_id=None):
session = self.get_session()
with session.begin():
if user_id:
if tenant_id:
q = session.query(UserProjectGrant)
q = q.filter_by(user_id=user_id)
q = q.filter_by(project_id=tenant_id)
elif domain_id:
q = session.query(UserDomainGrant)
q = q.filter_by(user_id=user_id)
q = q.filter_by(domain_id=domain_id)
elif group_id:
if tenant_id:
q = session.query(GroupProjectGrant)
q = q.filter_by(group_id=group_id)
q = q.filter_by(project_id=tenant_id)
elif domain_id:
q = session.query(GroupDomainGrant)
q = q.filter_by(group_id=group_id)
q = q.filter_by(domain_id=domain_id)
metadata_ref = q.first()
data = metadata_ref.data.copy()
data.update(metadata)
metadata_ref.data = data
session.flush()
return metadata_ref
# domain crud
@sql.handle_conflicts(type='domain')
def create_domain(self, domain_id, domain):
session = self.get_session()
with session.begin():
ref = Domain.from_dict(domain)
session.add(ref)
session.flush()
return ref.to_dict()
def list_domains(self):
session = self.get_session()
refs = session.query(Domain).all()
return [ref.to_dict() for ref in refs]
def _get_domain(self, session, domain_id):
ref = session.query(Domain).get(domain_id)
if ref is None:
raise exception.DomainNotFound(domain_id=domain_id)
return ref
def get_domain(self, domain_id):
session = self.get_session()
return self._get_domain(session, domain_id).to_dict()
def get_domain_by_name(self, domain_name):
session = self.get_session()
try:
ref = (session.query(Domain).
filter_by(name=domain_name).one())
except sql.NotFound:
raise exception.DomainNotFound(domain_id=domain_name)
return ref.to_dict()
@sql.handle_conflicts(type='domain')
def update_domain(self, domain_id, domain):
session = self.get_session()
with session.begin():
ref = self._get_domain(session, domain_id)
old_dict = ref.to_dict()
for k in domain:
old_dict[k] = domain[k]
new_domain = Domain.from_dict(old_dict)
for attr in Domain.attributes:
if attr != 'id':
setattr(ref, attr, getattr(new_domain, attr))
ref.extra = new_domain.extra
session.flush()
return ref.to_dict()
def delete_domain(self, domain_id):
session = self.get_session()
with session.begin():
ref = self._get_domain(session, domain_id)
session.delete(ref)
session.flush()
def list_user_projects(self, user_id):
session = self.get_session()
user = self.identity_api.get_user(user_id)
metadata_refs = session\
.query(UserProjectGrant)\
.filter_by(user_id=user_id)
project_ids = set([x.project_id for x in metadata_refs
if x.data.get('roles')])
if user.get('project_id'):
project_ids.add(user['project_id'])
# FIXME(dolph): this should be removed with proper migrations
if user.get('tenant_id'):
project_ids.add(user['tenant_id'])
return [self.get_project(x) for x in project_ids]
# role crud
@sql.handle_conflicts(type='role')
def create_role(self, role_id, role):
session = self.get_session()
with session.begin():
ref = Role.from_dict(role)
session.add(ref)
session.flush()
return ref.to_dict()
def list_roles(self):
session = self.get_session()
refs = session.query(Role).all()
return [ref.to_dict() for ref in refs]
def _get_role(self, session, role_id):
ref = session.query(Role).get(role_id)
if ref is None:
raise exception.RoleNotFound(role_id=role_id)
return ref
def get_role(self, role_id):
session = self.get_session()
return self._get_role(session, role_id).to_dict()
@sql.handle_conflicts(type='role')
def update_role(self, role_id, role):
session = self.get_session()
with session.begin():
ref = self._get_role(session, role_id)
old_dict = ref.to_dict()
for k in role:
old_dict[k] = role[k]
new_role = Role.from_dict(old_dict)
for attr in Role.attributes:
if attr != 'id':
setattr(ref, attr, getattr(new_role, attr))
ref.extra = new_role.extra
session.flush()
return ref.to_dict()
def delete_role(self, role_id):
session = self.get_session()
with session.begin():
ref = self._get_role(session, role_id)
for metadata_ref in session.query(UserProjectGrant):
try:
self.delete_grant(role_id, user_id=metadata_ref.user_id,
project_id=metadata_ref.project_id)
except exception.RoleNotFound:
pass
for metadata_ref in session.query(UserDomainGrant):
try:
self.delete_grant(role_id, user_id=metadata_ref.user_id,
domain_id=metadata_ref.domain_id)
except exception.RoleNotFound:
pass
for metadata_ref in session.query(GroupProjectGrant):
try:
self.delete_grant(role_id, group_id=metadata_ref.group_id,
project_id=metadata_ref.project_id)
except exception.RoleNotFound:
pass
for metadata_ref in session.query(GroupDomainGrant):
try:
self.delete_grant(role_id, group_id=metadata_ref.group_id,
domain_id=metadata_ref.domain_id)
except exception.RoleNotFound:
pass
session.delete(ref)
session.flush()
def delete_user(self, user_id):
session = self.get_session()
with session.begin():
q = session.query(UserProjectGrant)
q = q.filter_by(user_id=user_id)
q.delete(False)
q = session.query(UserDomainGrant)
q = q.filter_by(user_id=user_id)
q.delete(False)
session.flush()
def delete_group(self, group_id):
session = self.get_session()
with session.begin():
q = session.query(GroupProjectGrant)
q = q.filter_by(group_id=group_id)
q.delete(False)
q = session.query(GroupDomainGrant)
q = q.filter_by(group_id=group_id)
q.delete(False)
session.flush()
class Domain(sql.ModelBase, sql.DictBase):
__tablename__ = 'domain'
attributes = ['id', 'name', 'enabled']
id = sql.Column(sql.String(64), primary_key=True)
name = sql.Column(sql.String(64), unique=True, nullable=False)
enabled = sql.Column(sql.Boolean, default=True)
extra = sql.Column(sql.JsonBlob())
class Project(sql.ModelBase, sql.DictBase):
__tablename__ = 'project'
attributes = ['id', 'name', 'domain_id', 'description', 'enabled']
id = sql.Column(sql.String(64), primary_key=True)
name = sql.Column(sql.String(64), nullable=False)
domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'),
nullable=False)
description = sql.Column(sql.Text())
enabled = sql.Column(sql.Boolean)
extra = sql.Column(sql.JsonBlob())
# Unique constraint across two columns to create the separation
# rather than just only 'name' being unique
__table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {})
class Role(sql.ModelBase, sql.DictBase):
__tablename__ = 'role'
attributes = ['id', 'name']
id = sql.Column(sql.String(64), primary_key=True)
name = sql.Column(sql.String(64), unique=True, nullable=False)
extra = sql.Column(sql.JsonBlob())
class BaseGrant(sql.DictBase):
def to_dict(self):
"""Override parent to_dict() method with a simpler implementation.
Grant tables don't have non-indexed 'extra' attributes, so the
parent implementation is not applicable.
"""
return dict(self.iteritems())
class UserProjectGrant(sql.ModelBase, BaseGrant):
__tablename__ = 'user_project_metadata'
user_id = sql.Column(sql.String(64),
primary_key=True)
project_id = sql.Column(sql.String(64),
primary_key=True)
data = sql.Column(sql.JsonBlob())
class UserDomainGrant(sql.ModelBase, BaseGrant):
__tablename__ = 'user_domain_metadata'
user_id = sql.Column(sql.String(64), primary_key=True)
domain_id = sql.Column(sql.String(64), primary_key=True)
data = sql.Column(sql.JsonBlob())
class GroupProjectGrant(sql.ModelBase, BaseGrant):
__tablename__ = 'group_project_metadata'
group_id = sql.Column(sql.String(64), primary_key=True)
project_id = sql.Column(sql.String(64), primary_key=True)
data = sql.Column(sql.JsonBlob())
class GroupDomainGrant(sql.ModelBase, BaseGrant):
__tablename__ = 'group_domain_metadata'
group_id = sql.Column(sql.String(64), primary_key=True)
domain_id = sql.Column(sql.String(64), primary_key=True)
data = sql.Column(sql.JsonBlob())

403
keystone/assignment/core.py Normal file
View File

@ -0,0 +1,403 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 OpenStack LLC
#
# 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.
"""Main entry point into the assignment service."""
from keystone.common import dependency
from keystone.common import logging
from keystone.common import manager
from keystone import config
from keystone import exception
CONF = config.CONF
LOG = logging.getLogger(__name__)
@dependency.provider('assignment_api')
class Manager(manager.Manager):
"""Default pivot point for the Assignment backend.
See :mod:`keystone.common.manager.Manager` for more details on how this
dynamically calls the backend.
assignment.Manager() and identity.Manager() have a circular dependency.
The late import works around this. THe if block prevents creation of the
api object by both managers.
"""
def __init__(self, identity_api=None):
if identity_api is None:
from keystone import identity
identity_api = identity.Manager(self)
assignment_driver = CONF.assignment.driver
if assignment_driver is None:
assignment_driver = identity_api.default_assignment_driver()
super(Manager, self).__init__(assignment_driver)
self.driver.identity_api = identity_api
self.identity_api = identity_api
self.identity_api.assignment_api = self
def get_roles_for_user_and_project(self, user_id, tenant_id):
def _get_group_project_roles(user_id, tenant_id):
role_list = []
group_refs = (self.identity_api.list_groups_for_user
(user_id=user_id))
for x in group_refs:
try:
metadata_ref = self.get_metadata(group_id=x['id'],
tenant_id=tenant_id)
role_list += metadata_ref.get('roles', [])
except exception.MetadataNotFound:
# no group grant, skip
pass
return role_list
def _get_user_project_roles(user_id, tenant_id):
metadata_ref = {}
try:
metadata_ref = self.get_metadata(user_id=user_id,
tenant_id=tenant_id)
except exception.MetadataNotFound:
pass
return metadata_ref.get('roles', [])
self.identity_api.get_user(user_id)
self.get_project(tenant_id)
user_role_list = _get_user_project_roles(user_id, tenant_id)
group_role_list = _get_group_project_roles(user_id, tenant_id)
# Use set() to process the list to remove any duplicates
return list(set(user_role_list + group_role_list))
def get_roles_for_user_and_domain(self, user_id, domain_id):
"""Get the roles associated with a user within given domain.
:returns: a list of role ids.
:raises: keystone.exception.UserNotFound,
keystone.exception.DomainNotFound
"""
def _get_group_domain_roles(user_id, domain_id):
role_list = []
group_refs = (self.identity_api.
list_groups_for_user(user_id=user_id))
for x in group_refs:
try:
metadata_ref = self.get_metadata(group_id=x['id'],
domain_id=domain_id)
role_list += metadata_ref.get('roles', [])
except (exception.MetadataNotFound, exception.NotImplemented):
# MetadataNotFound implies no group grant, so skip.
# Ignore NotImplemented since not all backends support
# domains. pass
pass
return role_list
def _get_user_domain_roles(user_id, domain_id):
metadata_ref = {}
try:
metadata_ref = self.get_metadata(user_id=user_id,
domain_id=domain_id)
except (exception.MetadataNotFound, exception.NotImplemented):
# MetadataNotFound implies no user grants.
# Ignore NotImplemented since not all backends support
# domains
pass
return metadata_ref.get('roles', [])
self.identity_api.get_user(user_id)
self.get_domain(domain_id)
user_role_list = _get_user_domain_roles(user_id, domain_id)
group_role_list = _get_group_domain_roles(user_id, domain_id)
# Use set() to process the list to remove any duplicates
return list(set(user_role_list + group_role_list))
def add_user_to_project(self, tenant_id, user_id):
"""Add user to a tenant by creating a default role relationship.
:raises: keystone.exception.ProjectNotFound,
keystone.exception.UserNotFound
"""
self.driver.add_role_to_user_and_project(user_id,
tenant_id,
config.CONF.member_role_id)
def remove_user_from_project(self, tenant_id, user_id):
"""Remove user from a tenant
:raises: keystone.exception.ProjectNotFound,
keystone.exception.UserNotFound
"""
roles = self.get_roles_for_user_and_project(user_id, tenant_id)
if not roles:
raise exception.NotFound(tenant_id)
for role_id in roles:
self.remove_role_from_user_and_project(user_id, tenant_id, role_id)
class Driver(object):
def authorize_for_project(self, tenant_id, user_ref):
"""Authenticate a given user for a tenant.
:returns: (user_ref, tenant_ref, metadata_ref)
:raises: AssertionError
"""
raise exception.NotImplemented()
def get_project_by_name(self, tenant_name, domain_id):
"""Get a tenant by name.
:returns: tenant_ref
:raises: keystone.exception.ProjectNotFound
"""
raise exception.NotImplemented()
def get_project_users(self, tenant_id):
"""Lists all users with a relationship to the specified project.
:returns: a list of user_refs or an empty set.
:raises: keystone.exception.ProjectNotFound
"""
raise exception.NotImplemented()
def get_projects_for_user(self, user_id):
"""Get the tenants associated with a given user.
:returns: a list of tenant_id's.
:raises: keystone.exception.UserNotFound
"""
raise exception.NotImplemented()
def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
"""Add a role to a user within given tenant.
:raises: keystone.exception.UserNotFound,
keystone.exception.ProjectNotFound,
keystone.exception.RoleNotFound
"""
raise exception.NotImplemented()
def remove_role_from_user_and_project(self, user_id, tenant_id, role_id):
"""Remove a role from a user within given tenant.
:raises: keystone.exception.UserNotFound,
keystone.exception.ProjectNotFound,
keystone.exception.RoleNotFound
"""
raise exception.NotImplemented()
def list_role_assignments(self):
raise exception.NotImplemented()
# metadata crud
def get_metadata(self, user_id=None, tenant_id=None,
domain_id=None, group_id=None):
"""Gets the metadata for the specified user/group on project/domain.
:raises: keystone.exception.MetadataNotFound
:returns: metadata
"""
raise exception.NotImplemented()
def create_metadata(self, user_id, tenant_id, metadata,
domain_id=None, group_id=None):
"""Creates the metadata for the specified user/group on project/domain.
:returns: metadata created
"""
raise exception.NotImplemented()
def update_metadata(self, user_id, tenant_id, metadata,
domain_id=None, group_id=None):
"""Updates the metadata for the specified user/group on project/domain.
:returns: metadata updated
"""
raise exception.NotImplemented()
# domain crud
def create_domain(self, domain_id, domain):
"""Creates a new domain.
:raises: keystone.exception.Conflict
"""
raise exception.NotImplemented()
def list_domains(self):
"""List all domains in the system.
:returns: a list of domain_refs or an empty list.
"""
raise exception.NotImplemented()
def get_domain(self, domain_id):
"""Get a domain by ID.
:returns: domain_ref
:raises: keystone.exception.DomainNotFound
"""
raise exception.NotImplemented()
def get_domain_by_name(self, domain_name):
"""Get a domain by name.
:returns: domain_ref
:raises: keystone.exception.DomainNotFound
"""
raise exception.NotImplemented()
def update_domain(self, domain_id, domain):
"""Updates an existing domain.
:raises: keystone.exception.DomainNotFound,
keystone.exception.Conflict
"""
raise exception.NotImplemented()
def delete_domain(self, domain_id):
"""Deletes an existing domain.
:raises: keystone.exception.DomainNotFound
"""
raise exception.NotImplemented()
# project crud
def create_project(self, project_id, project):
"""Creates a new project.
:raises: keystone.exception.Conflict
"""
raise exception.NotImplemented()
def list_projects(self):
"""List all projects in the system.
:returns: a list of project_refs or an empty list.
"""
raise exception.NotImplemented()
def list_user_projects(self, user_id):
"""List all projects associated with a given user.
:returns: a list of project_refs or an empty list.
"""
raise exception.NotImplemented()
def get_project(self, project_id):
"""Get a project by ID.
:returns: project_ref
:raises: keystone.exception.ProjectNotFound
"""
raise exception.NotImplemented()
def update_project(self, project_id, project):
"""Updates an existing project.
:raises: keystone.exception.ProjectNotFound,
keystone.exception.Conflict
"""
raise exception.NotImplemented()
def delete_project(self, project_id):
"""Deletes an existing project.
:raises: keystone.exception.ProjectNotFound
"""
raise exception.NotImplemented()
"""Interface description for an assignment driver."""
# role crud
def create_role(self, role_id, role):
"""Creates a new role.
:raises: keystone.exception.Conflict
"""
raise exception.NotImplemented()
def list_roles(self):
"""List all roles in the system.
:returns: a list of role_refs or an empty list.
"""
raise exception.NotImplemented()
def get_role(self, role_id):
"""Get a role by ID.
:returns: role_ref
:raises: keystone.exception.RoleNotFound
"""
raise exception.NotImplemented()
def update_role(self, role_id, role):
"""Updates an existing role.
:raises: keystone.exception.RoleNotFound,
keystone.exception.Conflict
"""
raise exception.NotImplemented()
def delete_role(self, role_id):
"""Deletes an existing role.
:raises: keystone.exception.RoleNotFound
"""
raise exception.NotImplemented()
#TODO(ayoung): determine what else these two functions raise
def delete_user(self, user_id):
"""Deletes all assignments for a user.
:raises: keystone.exception.RoleNotFound
"""
raise exception.NotImplemented()
def delete_group(self, group_id):
"""Deletes all assignments for a group.
:raises: keystone.exception.RoleNotFound
"""

View File

@ -259,6 +259,12 @@ def configure():
default='sqlite:///keystone.db')
register_int('idle_timeout', group='sql', default=200)
#assignment has no default for backward compatibility reasons.
#If assignment is not specified, the identity driver chooses the backend
register_str(
'driver',
group='assignment',
default=None)
register_str(
'driver',
group='catalog',

View File

@ -146,7 +146,8 @@ def filterprotected(*filters):
@dependency.requires('identity_api', 'policy_api', 'token_api',
'trust_api', 'catalog_api', 'credential_api')
'trust_api', 'catalog_api', 'credential_api',
'assignment_api')
class V2Controller(wsgi.Application):
"""Base controller class for Identity API v2."""

View File

@ -50,6 +50,8 @@ class Base(object):
def __init__(self, db=None):
if db is None:
db = INMEMDB
elif isinstance(db, DictKvs):
db = db
elif isinstance(db, dict):
db = DictKvs(db)
self.db = db

View File

@ -19,6 +19,8 @@ import re
import sqlalchemy
from sqlalchemy import exc
from keystone.assignment.backends import sql as assignment_sql
from keystone.common import logging
from keystone import config
from keystone.contrib.ec2.backends import sql as ec2_sql
@ -57,8 +59,14 @@ def _translate_replacements(s):
class LegacyMigration(object):
def __init__(self, db_string):
self.db = sqlalchemy.create_engine(db_string)
#TODO(ayoung): Replace with call via Manager
self.identity_driver = identity_sql.Identity()
self.assignment_driver = assignment_sql.Assignment()
self.identity_driver.assignment_api = self.assignment_driver
self.assignment_driver.identity_api = self.identity_driver
self.identity_driver.db_sync()
self.assignment_driver.db_sync()
self.ec2_driver = ec2_sql.Ec2()
self._data = {}
self._user_map = {}
@ -113,7 +121,7 @@ class LegacyMigration(object):
self._project_map[x.get('id')] = new_dict['id']
# create
#print 'create_project(%s, %s)' % (new_dict['id'], new_dict)
self.identity_driver.create_project(new_dict['id'], new_dict)
self.assignment_driver.create_project(new_dict['id'], new_dict)
def _migrate_users(self):
for x in self._data['users']:
@ -144,7 +152,7 @@ class LegacyMigration(object):
# track internal ids
self._role_map[x.get('id')] = new_dict['id']
# create
self.identity_driver.create_role(new_dict['id'], new_dict)
self.assignment_driver.create_role(new_dict['id'], new_dict)
def _migrate_user_roles(self):
for x in self._data['user_roles']:
@ -162,7 +170,7 @@ class LegacyMigration(object):
except Exception:
pass
self.identity_driver.add_role_to_user_and_project(
self.assignment_driver.add_role_to_user_and_project(
user_id, tenant_id, role_id)
def _migrate_tokens(self):

View File

@ -18,10 +18,11 @@
import uuid
from keystone import assignment
from keystone.common import logging
from keystone import config
from keystone.contrib.ec2.backends import sql as ec2_sql
from keystone.identity.backends import sql as identity_sql
from keystone import identity
LOG = logging.getLogger(__name__)
@ -30,18 +31,20 @@ DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
def import_auth(data):
identity_api = identity_sql.Identity()
tenant_map = _create_projects(identity_api, data['tenants'])
identity_api = identity.Manager()
assignment_api = assignment.Manager()
tenant_map = _create_projects(assignment_api, data['tenants'])
user_map = _create_users(identity_api, data['users'])
_create_memberships(identity_api, data['user_tenant_list'],
_create_memberships(assignment_api, data['user_tenant_list'],
user_map, tenant_map)
role_map = _create_roles(identity_api, data['roles'])
_assign_roles(identity_api, data['role_user_tenant_list'],
role_map = _create_roles(assignment_api, data['roles'])
_assign_roles(assignment_api, data['role_user_tenant_list'],
role_map, user_map, tenant_map)
ec2_api = ec2_sql.Ec2()
ec2_creds = data['ec2_credentials']
_create_ec2_creds(ec2_api, identity_api, ec2_creds, user_map)
_create_ec2_creds(ec2_api, assignment_api, ec2_creds, user_map)
def _generate_uuid():
@ -120,10 +123,10 @@ def _assign_roles(api, assignments, role_map, user_map, tenant_map):
api.add_role_to_user_and_project(user_id, tenant_id, role_id)
def _create_ec2_creds(ec2_api, identity_api, ec2_creds, user_map):
def _create_ec2_creds(ec2_api, assignment_api, ec2_creds, user_map):
for ec2_cred in ec2_creds:
user_id = user_map[ec2_cred['user_id']]
for tenant_id in identity_api.get_projects_for_user(user_id):
for tenant_id in assignment_api.get_projects_for_user(user_id):
cred_dict = {
'access': '%s:%s' % (tenant_id, ec2_cred['access_key']),
'secret': ec2_cred['secret_key'],

View File

@ -14,7 +14,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystone import clean
from keystone.common import kvs
from keystone.common import utils
from keystone import exception
@ -22,6 +21,12 @@ from keystone import identity
class Identity(kvs.Base, identity.Driver):
def __init__(self):
super(Identity, self).__init__()
def default_assignment_driver(self):
return "keystone.assignment.backends.kvs.Assignment"
# Public interface
def authenticate_user(self, user_id=None, password=None):
user_ref = None
@ -33,47 +38,6 @@ class Identity(kvs.Base, identity.Driver):
raise AssertionError('Invalid user / password')
return user_ref
def authorize_for_project(self, user_ref, tenant_id=None):
user_id = user_ref['id']
tenant_ref = None
metadata_ref = {}
if tenant_id is not None:
if tenant_id not in self.get_projects_for_user(user_id):
raise AssertionError('Invalid tenant')
try:
tenant_ref = self.get_project(tenant_id)
metadata_ref = self.get_metadata(user_id, tenant_id)
except exception.ProjectNotFound:
tenant_ref = None
metadata_ref = {}
except exception.MetadataNotFound:
metadata_ref = {}
return (identity.filter_user(user_ref), tenant_ref, metadata_ref)
def get_project(self, tenant_id):
try:
return self.db.get('tenant-%s' % tenant_id)
except exception.NotFound:
raise exception.ProjectNotFound(project_id=tenant_id)
def list_projects(self):
tenant_keys = filter(lambda x: x.startswith("tenant-"),
self.db.keys())
return [self.db.get(key) for key in tenant_keys]
def get_project_by_name(self, tenant_name, domain_id):
try:
return self.db.get('tenant_name-%s' % tenant_name)
except exception.NotFound:
raise exception.ProjectNotFound(project_id=tenant_name)
def get_project_users(self, tenant_id):
self.get_project(tenant_id)
user_keys = filter(lambda x: x.startswith("user-"), self.db.keys())
user_refs = [self.db.get(key) for key in user_keys]
user_refs = filter(lambda x: tenant_id in x['tenants'], user_refs)
return [identity.filter_user(user_ref) for user_ref in user_refs]
def _get_user(self, user_id):
try:
return self.db.get('user-%s' % user_id)
@ -93,123 +57,10 @@ class Identity(kvs.Base, identity.Driver):
return identity.filter_user(
self._get_user_by_name(user_name, domain_id))
def get_metadata(self, user_id=None, tenant_id=None,
domain_id=None, group_id=None):
try:
if user_id:
if tenant_id:
return self.db.get('metadata-%s-%s' % (tenant_id,
user_id))
else:
return self.db.get('metadata-%s-%s' % (domain_id,
user_id))
else:
if tenant_id:
return self.db.get('metadata-%s-%s' % (tenant_id,
group_id))
else:
return self.db.get('metadata-%s-%s' % (domain_id,
group_id))
except exception.NotFound:
raise exception.MetadataNotFound()
def get_role(self, role_id):
try:
return self.db.get('role-%s' % role_id)
except exception.NotFound:
raise exception.RoleNotFound(role_id=role_id)
def list_users(self):
user_ids = self.db.get('user_list', [])
return [self.get_user(x) for x in user_ids]
def list_roles(self):
role_ids = self.db.get('role_list', [])
return [self.get_role(x) for x in role_ids]
def get_projects_for_user(self, user_id):
user_ref = self._get_user(user_id)
return user_ref.get('tenants', [])
def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
self.get_user(user_id)
self.get_project(tenant_id)
self.get_role(role_id)
try:
metadata_ref = self.get_metadata(user_id, tenant_id)
except exception.MetadataNotFound:
metadata_ref = {}
roles = set(metadata_ref.get('roles', []))
if role_id in roles:
msg = ('User %s already has role %s in tenant %s'
% (user_id, role_id, tenant_id))
raise exception.Conflict(type='role grant', details=msg)
roles.add(role_id)
metadata_ref['roles'] = list(roles)
self.update_metadata(user_id, tenant_id, metadata_ref)
def remove_role_from_user_and_project(self, user_id, tenant_id, role_id):
try:
metadata_ref = self.get_metadata(user_id, tenant_id)
except exception.MetadataNotFound:
metadata_ref = {}
roles = set(metadata_ref.get('roles', []))
if role_id not in roles:
msg = 'Cannot remove role that has not been granted, %s' % role_id
raise exception.RoleNotFound(message=msg)
roles.remove(role_id)
metadata_ref['roles'] = list(roles)
if not len(roles):
self.db.delete('metadata-%s-%s' % (tenant_id, user_id))
user_ref = self._get_user(user_id)
tenants = set(user_ref.get('tenants', []))
tenants.remove(tenant_id)
user_ref['tenants'] = list(tenants)
self.update_user(user_id, user_ref)
else:
self.update_metadata(user_id, tenant_id, metadata_ref)
def list_role_assignments(self):
"""List the role assignments.
The kvs backend stores role assignments as key-values:
"metadata-{target}-{actor}", with the value being a role list
i.e. "metadata-MyProjectID-MyUserID" [role1, role2]
...so we enumerate the list and extract the targets, actors
and roles.
"""
assignment_list = []
metadata_keys = filter(lambda x: x.startswith("metadata-"),
self.db.keys())
for key in metadata_keys:
template = {}
meta_id1 = key.split('-')[1]
meta_id2 = key.split('-')[2]
try:
self.get_project(meta_id1)
template['project_id'] = meta_id1
except exception.NotFound:
template['domain_id'] = meta_id1
try:
self._get_user(meta_id2)
template['user_id'] = meta_id2
except exception.NotFound:
template['group_id'] = meta_id2
entry = self.db.get(key)
for r in entry.get('roles', []):
role_assignment = template.copy()
role_assignment['role_id'] = r
assignment_list.append(role_assignment)
return assignment_list
# CRUD
def create_user(self, user_id, user):
try:
@ -308,301 +159,6 @@ class Identity(kvs.Base, identity.Driver):
user_list.remove(user_id)
self.db.set('user_list', list(user_list))
def create_project(self, tenant_id, tenant):
tenant['name'] = clean.project_name(tenant['name'])
try:
self.get_project(tenant_id)
except exception.ProjectNotFound:
pass
else:
msg = 'Duplicate ID, %s.' % tenant_id
raise exception.Conflict(type='tenant', details=msg)
try:
self.get_project_by_name(tenant['name'], tenant['domain_id'])
except exception.ProjectNotFound:
pass
else:
msg = 'Duplicate name, %s.' % tenant['name']
raise exception.Conflict(type='tenant', details=msg)
self.db.set('tenant-%s' % tenant_id, tenant)
self.db.set('tenant_name-%s' % tenant['name'], tenant)
return tenant
def update_project(self, tenant_id, tenant):
if 'name' in tenant:
tenant['name'] = clean.project_name(tenant['name'])
try:
existing = self.db.get('tenant_name-%s' % tenant['name'])
if existing and tenant_id != existing['id']:
msg = 'Duplicate name, %s.' % tenant['name']
raise exception.Conflict(type='tenant', details=msg)
except exception.NotFound:
pass
# get the old name and delete it too
try:
old_project = self.db.get('tenant-%s' % tenant_id)
except exception.NotFound:
raise exception.ProjectNotFound(project_id=tenant_id)
new_project = old_project.copy()
new_project.update(tenant)
new_project['id'] = tenant_id
self.db.delete('tenant_name-%s' % old_project['name'])
self.db.set('tenant-%s' % tenant_id, new_project)
self.db.set('tenant_name-%s' % new_project['name'], new_project)
return new_project
def delete_project(self, tenant_id):
try:
old_project = self.db.get('tenant-%s' % tenant_id)
except exception.NotFound:
raise exception.ProjectNotFound(project_id=tenant_id)
self.db.delete('tenant_name-%s' % old_project['name'])
self.db.delete('tenant-%s' % tenant_id)
def create_metadata(self, user_id, tenant_id, metadata,
domain_id=None, group_id=None):
return self.update_metadata(user_id, tenant_id, metadata,
domain_id, group_id)
def update_metadata(self, user_id, tenant_id, metadata,
domain_id=None, group_id=None):
if user_id:
if tenant_id:
self.db.set('metadata-%s-%s' % (tenant_id, user_id), metadata)
user_ref = self._get_user(user_id)
tenants = set(user_ref.get('tenants', []))
if tenant_id not in tenants:
tenants.add(tenant_id)
user_ref['tenants'] = list(tenants)
self.update_user(user_id, user_ref)
else:
self.db.set('metadata-%s-%s' % (domain_id, user_id), metadata)
else:
if tenant_id:
self.db.set('metadata-%s-%s' % (tenant_id, group_id), metadata)
else:
self.db.set('metadata-%s-%s' % (domain_id, group_id), metadata)
return metadata
def create_role(self, role_id, role):
try:
self.get_role(role_id)
except exception.RoleNotFound:
pass
else:
msg = 'Duplicate ID, %s.' % role_id
raise exception.Conflict(type='role', details=msg)
for role_ref in self.list_roles():
if role['name'] == role_ref['name']:
msg = 'Duplicate name, %s.' % role['name']
raise exception.Conflict(type='role', details=msg)
self.db.set('role-%s' % role_id, role)
role_list = set(self.db.get('role_list', []))
role_list.add(role_id)
self.db.set('role_list', list(role_list))
return role
def update_role(self, role_id, role):
old_role_ref = None
for role_ref in self.list_roles():
if role['name'] == role_ref['name'] and role_id != role_ref['id']:
msg = 'Duplicate name, %s.' % role['name']
raise exception.Conflict(type='role', details=msg)
if role_id == role_ref['id']:
old_role_ref = role_ref
if old_role_ref is None:
raise exception.RoleNotFound(role_id=role_id)
new_role = old_role_ref.copy()
new_role.update(role)
new_role['id'] = role_id
self.db.set('role-%s' % role_id, new_role)
return role
def delete_role(self, role_id):
self.get_role(role_id)
metadata_keys = filter(lambda x: x.startswith("metadata-"),
self.db.keys())
for key in metadata_keys:
meta_id1 = key.split('-')[1]
meta_id2 = key.split('-')[2]
try:
self.delete_grant(role_id, project_id=meta_id1,
user_id=meta_id2)
except exception.NotFound:
pass
try:
self.delete_grant(role_id, project_id=meta_id1,
group_id=meta_id2)
except exception.NotFound:
pass
try:
self.delete_grant(role_id, domain_id=meta_id1,
user_id=meta_id2)
except exception.NotFound:
pass
try:
self.delete_grant(role_id, domain_id=meta_id1,
group_id=meta_id2)
except exception.NotFound:
pass
self.db.delete('role-%s' % role_id)
role_list = set(self.db.get('role_list', []))
role_list.remove(role_id)
self.db.set('role_list', list(role_list))
def create_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None):
self.get_role(role_id)
if user_id:
self.get_user(user_id)
if group_id:
self.get_group(group_id)
if domain_id:
self.get_domain(domain_id)
if project_id:
self.get_project(project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
except exception.MetadataNotFound:
metadata_ref = {}
roles = set(metadata_ref.get('roles', []))
roles.add(role_id)
metadata_ref['roles'] = list(roles)
self.update_metadata(user_id, project_id, metadata_ref,
domain_id, group_id)
def list_grants(self, user_id=None, group_id=None,
domain_id=None, project_id=None):
if user_id:
self.get_user(user_id)
if group_id:
self.get_group(group_id)
if domain_id:
self.get_domain(domain_id)
if project_id:
self.get_project(project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
except exception.MetadataNotFound:
metadata_ref = {}
return [self.get_role(x) for x in metadata_ref.get('roles', [])]
def get_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None):
self.get_role(role_id)
if user_id:
self.get_user(user_id)
if group_id:
self.get_group(group_id)
if domain_id:
self.get_domain(domain_id)
if project_id:
self.get_project(project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
except exception.MetadataNotFound:
metadata_ref = {}
role_ids = set(metadata_ref.get('roles', []))
if role_id not in role_ids:
raise exception.RoleNotFound(role_id=role_id)
return self.get_role(role_id)
def delete_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None):
self.get_role(role_id)
if user_id:
self.get_user(user_id)
if group_id:
self.get_group(group_id)
if domain_id:
self.get_domain(domain_id)
if project_id:
self.get_project(project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
except exception.MetadataNotFound:
metadata_ref = {}
roles = set(metadata_ref.get('roles', []))
try:
roles.remove(role_id)
except KeyError:
raise exception.RoleNotFound(role_id=role_id)
metadata_ref['roles'] = list(roles)
self.update_metadata(user_id, project_id, metadata_ref,
domain_id, group_id)
# domain crud
def create_domain(self, domain_id, domain):
try:
self.get_domain(domain_id)
except exception.DomainNotFound:
pass
else:
msg = 'Duplicate ID, %s.' % domain_id
raise exception.Conflict(type='domain', details=msg)
try:
self.get_domain_by_name(domain['name'])
except exception.DomainNotFound:
pass
else:
msg = 'Duplicate name, %s.' % domain['name']
raise exception.Conflict(type='domain', details=msg)
self.db.set('domain-%s' % domain_id, domain)
self.db.set('domain_name-%s' % domain['name'], domain)
domain_list = set(self.db.get('domain_list', []))
domain_list.add(domain_id)
self.db.set('domain_list', list(domain_list))
return domain
def list_domains(self):
domain_ids = self.db.get('domain_list', [])
return [self.get_domain(x) for x in domain_ids]
def get_domain(self, domain_id):
try:
return self.db.get('domain-%s' % domain_id)
except exception.NotFound:
raise exception.DomainNotFound(domain_id=domain_id)
def get_domain_by_name(self, domain_name):
try:
return self.db.get('domain_name-%s' % domain_name)
except exception.NotFound:
raise exception.DomainNotFound(domain_id=domain_name)
def update_domain(self, domain_id, domain):
orig_domain = self.get_domain(domain_id)
domain['id'] = domain_id
self.db.set('domain-%s' % domain_id, domain)
self.db.set('domain_name-%s' % domain['name'], domain)
if domain['name'] != orig_domain['name']:
self.db.delete('domain_name-%s' % orig_domain['name'])
return domain
def delete_domain(self, domain_id):
domain = self.get_domain(domain_id)
self.db.delete('domain-%s' % domain_id)
self.db.delete('domain_name-%s' % domain['name'])
domain_list = set(self.db.get('domain_list', []))
domain_list.remove(domain_id)
self.db.set('domain_list', list(domain_list))
# group crud
def create_group(self, group_id, group):

View File

@ -47,42 +47,19 @@ class Identity(identity.Driver):
self.suffix = CONF.ldap.suffix
self.user = UserApi(CONF)
self.project = ProjectApi(CONF)
self.role = RoleApi(CONF)
self.group = GroupApi(CONF)
def _validate_domain(self, ref):
"""Validate that either the default domain or nothing is specified.
Also removes the domain from the ref so that LDAP doesn't have to
persist the attribute.
"""
ref = ref.copy()
domain_id = ref.pop('domain_id', CONF.identity.default_domain_id)
self._validate_domain_id(domain_id)
return ref
def _validate_domain_id(self, domain_id):
"""Validate that the domain ID specified belongs to the default domain.
"""
if domain_id != CONF.identity.default_domain_id:
raise exception.DomainNotFound(domain_id=domain_id)
def _set_default_domain(self, ref):
"""Overrides any domain reference with the default domain."""
if isinstance(ref, dict):
ref = ref.copy()
ref['domain_id'] = CONF.identity.default_domain_id
return ref
elif isinstance(ref, list):
return [self._set_default_domain(x) for x in ref]
else:
raise ValueError(_('Expected dict or list: %s') % type(ref))
def default_assignment_driver(self):
return "keystone.assignment.backends.ldap.Assignment"
# Identity interface
def create_project(self, project_id, project):
return self.assignment.create_project(project_id, project)
def get_project(self, project_id):
return self.assignment.get_project(project_id)
def authenticate_user(self, user_id=None, password=None):
try:
user_ref = self._get_user(user_id)
@ -99,133 +76,33 @@ class Identity(identity.Driver):
raise AssertionError('Invalid user / password')
return user_ref
def authorize_for_project(self, user_ref, tenant_id=None):
user_id = user_ref['id']
tenant_ref = None
metadata_ref = {}
if tenant_id is not None:
if tenant_id not in self.get_projects_for_user(user_id):
raise AssertionError('Invalid tenant')
try:
tenant_ref = self.get_project(tenant_id)
# TODO(termie): this should probably be made into a
# get roles call
metadata_ref = self.get_metadata(user_id, tenant_id)
except exception.ProjectNotFound:
tenant_ref = None
metadata_ref = {}
except exception.MetadataNotFound:
metadata_ref = {}
user_ref = self._set_default_domain(identity.filter_user(user_ref))
return (user_ref, tenant_ref, metadata_ref)
def get_project(self, tenant_id):
return self._set_default_domain(self.project.get(tenant_id))
def list_projects(self):
return self._set_default_domain(self.project.get_all())
def get_project_by_name(self, tenant_name, domain_id):
self._validate_domain_id(domain_id)
return self._set_default_domain(self.project.get_by_name(tenant_name))
def _get_user(self, user_id):
return self.user.get(user_id)
def get_user(self, user_id):
ref = identity.filter_user(self._get_user(user_id))
return self._set_default_domain(ref)
return self.assignment._set_default_domain(ref)
def list_users(self):
return self._set_default_domain(self.user.get_all())
return self.assignment._set_default_domain(self.user.get_all())
def get_user_by_name(self, user_name, domain_id):
self._validate_domain_id(domain_id)
self.assignment._validate_domain_id(domain_id)
ref = identity.filter_user(self.user.get_by_name(user_name))
return self._set_default_domain(ref)
def get_metadata(self, user_id=None, tenant_id=None,
domain_id=None, group_id=None):
def _get_roles_for_just_user_and_project(user_id, tenant_id):
self.get_user(user_id)
self.get_project(tenant_id)
user_dn = self.user._id_to_dn(user_id)
return [self.role._dn_to_id(a.role_dn)
for a in self.role.get_role_assignments
(self.project._id_to_dn(tenant_id))
if a.user_dn == user_dn]
if domain_id is not None:
msg = 'Domain metadata not supported by LDAP'
raise exception.NotImplemented(message=msg)
if not self.get_project(tenant_id) or not self.get_user(user_id):
return {}
metadata_ref = _get_roles_for_just_user_and_project(user_id, tenant_id)
if not metadata_ref:
return {}
return {'roles': metadata_ref}
def get_role(self, role_id):
return self.role.get(role_id)
def list_roles(self):
return self.role.get_all()
def get_projects_for_user(self, user_id):
self.get_user(user_id)
user_dn = self.user._id_to_dn(user_id)
associations = (self.role.list_project_roles_for_user
(user_dn, self.project.tree_dn))
return [p['id'] for p in
self.project.get_user_projects(user_dn, associations)]
def get_project_users(self, tenant_id):
self.get_project(tenant_id)
tenant_dn = self.project._id_to_dn(tenant_id)
rolegrants = self.role.get_role_assignments(tenant_dn)
users = [self.user.get_filtered(self.user._dn_to_id(user_id))
for user_id in
self.project.get_user_dns(tenant_id, rolegrants)]
return self._set_default_domain(users)
def _subrole_id_to_dn(self, role_id, tenant_id):
if tenant_id is None:
return self.role._id_to_dn(role_id)
else:
return '%s=%s,%s' % (self.role.id_attr,
ldap.dn.escape_dn_chars(role_id),
self.project._id_to_dn(tenant_id))
def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
self.get_user(user_id)
self.get_project(tenant_id)
self.get_role(role_id)
user_dn = self.user._id_to_dn(user_id)
role_dn = self._subrole_id_to_dn(role_id, tenant_id)
self.role.add_user(role_id, role_dn, user_dn, user_id, tenant_id)
tenant_dn = self.project._id_to_dn(tenant_id)
return UserRoleAssociation(
role_dn=role_dn,
user_dn=user_dn,
tenant_dn=tenant_dn)
return self.assignment._set_default_domain(ref)
# CRUD
def create_user(self, user_id, user):
user = self._validate_domain(user)
user = self.assignment._validate_domain(user)
user_ref = self.user.create(user)
tenant_id = user.get('tenant_id')
user_dn = self.user._id_to_dn(user['id'])
if tenant_id is not None:
self.project.add_user(tenant_id, user_dn)
return self._set_default_domain(identity.filter_user(user_ref))
self.assignment.add_user_to_project(tenant_id, user_id)
return (self.assignment._set_default_domain
(identity.filter_user(user_ref)))
def update_user(self, user_id, user):
user = self._validate_domain(user)
user = self.assignment._validate_domain(user)
if 'id' in user and user['id'] != user_id:
raise exception.ValidationError('Cannot change user ID')
old_obj = self.user.get(user_id)
@ -248,67 +125,12 @@ class Identity(identity.Driver):
user['enabled_nomask'] = old_obj['enabled_nomask']
self.user.mask_enabled_attribute(user)
self.user.update(user_id, user, old_obj)
return self._set_default_domain(self.user.get_filtered(user_id))
def create_project(self, tenant_id, tenant):
tenant = self._validate_domain(tenant)
tenant['name'] = clean.project_name(tenant['name'])
data = tenant.copy()
if 'id' not in data or data['id'] is None:
data['id'] = str(uuid.uuid4().hex)
if 'description' in data and data['description'] in ['', None]:
data.pop('description')
return self._set_default_domain(self.project.create(data))
def update_project(self, tenant_id, tenant):
tenant = self._validate_domain(tenant)
if 'name' in tenant:
tenant['name'] = clean.project_name(tenant['name'])
return self._set_default_domain(self.project.update(tenant_id, tenant))
def create_metadata(self, user_id, tenant_id, metadata):
return {}
def create_role(self, role_id, role):
try:
self.get_role(role_id)
except exception.NotFound:
pass
else:
msg = 'Duplicate ID, %s.' % role_id
raise exception.Conflict(type='role', details=msg)
try:
self.role.get_by_name(role['name'])
except exception.NotFound:
pass
else:
msg = 'Duplicate name, %s.' % role['name']
raise exception.Conflict(type='role', details=msg)
return self.role.create(role)
def delete_role(self, role_id):
return self.role.delete(role_id, self.project.tree_dn)
def delete_project(self, tenant_id):
if self.project.subtree_delete_enabled:
self.project.deleteTree(id)
else:
tenant_dn = self.project._id_to_dn(tenant_id)
self.role.roles_delete_subtree_by_project(tenant_dn)
self.project.delete(tenant_id)
return (self.assignment._set_default_domain
(self.user.get_filtered(user_id)))
def delete_user(self, user_id):
self.assignment.delete_user(user_id)
user_dn = self.user._id_to_dn(user_id)
for ref in self.role.list_global_roles_for_user(user_dn):
self.role.delete_user(ref.role_dn, ref.user_dn, ref.project_dn,
user_id, self.role._dn_to_id(ref.role_dn))
for ref in self.role.list_project_roles_for_user(user_dn,
self.project.tree_dn):
self.role.delete_user(ref.role_dn, ref.user_dn, ref.project_dn,
user_id, self.role._dn_to_id(ref.role_dn))
groups = self.group.list_user_groups(user_dn)
for group in groups:
self.group.remove_user(user_dn, group['id'], user_id)
@ -319,30 +141,20 @@ class Identity(identity.Driver):
self.user._id_to_dn(user_id))
self.user.delete(user_id)
def remove_role_from_user_and_project(self, user_id, tenant_id, role_id):
role_dn = self._subrole_id_to_dn(role_id, tenant_id)
return self.role.delete_user(role_dn,
self.user._id_to_dn(user_id),
self.project._id_to_dn(tenant_id),
user_id, role_id)
def update_role(self, role_id, role):
self.get_role(role_id)
self.role.update(role_id, role)
def create_group(self, group_id, group):
group = self._validate_domain(group)
group = self.assignment._validate_domain(group)
group['name'] = clean.group_name(group['name'])
return self._set_default_domain(self.group.create(group))
return self.assignment._set_default_domain(self.group.create(group))
def get_group(self, group_id):
return self._set_default_domain(self.group.get(group_id))
return self.assignment._set_default_domain(self.group.get(group_id))
def update_group(self, group_id, group):
group = self._validate_domain(group)
group = self.assignment._validate_domain(group)
if 'name' in group:
group['name'] = clean.group_name(group['name'])
return self._set_default_domain(self.group.update(group_id, group))
return (self.assignment._set_default_domain
(self.group.update(group_id, group)))
def delete_group(self, group_id):
return self.group.delete(group_id)
@ -362,10 +174,11 @@ class Identity(identity.Driver):
def list_groups_for_user(self, user_id):
self.get_user(user_id)
user_dn = self.user._id_to_dn(user_id)
return self._set_default_domain(self.group.list_user_groups(user_dn))
return (self.assignment._set_default_domain
(self.group.list_user_groups(user_dn)))
def list_groups(self):
return self._set_default_domain(self.group.get_all())
return self.assignment._set_default_domain(self.group.get_all())
def list_users_in_group(self, group_id):
self.get_group(group_id)
@ -379,7 +192,7 @@ class Identity(identity.Driver):
" '%(group_id)s'. The user should be removed"
" from the group. The user will be ignored.") %
dict(user_dn=user_dn, group_id=group_id))
return self._set_default_domain(users)
return self.assignment._set_default_domain(users)
def check_user_in_group(self, user_id, group_id):
self.get_user(user_id)
@ -392,27 +205,6 @@ class Identity(identity.Driver):
break
return found
def create_domain(self, domain_id, domain):
if domain_id == CONF.identity.default_domain_id:
msg = 'Duplicate ID, %s.' % domain_id
raise exception.Conflict(type='domain', details=msg)
raise exception.Forbidden('Domains are read-only against LDAP')
def get_domain(self, domain_id):
self._validate_domain_id(domain_id)
return DEFAULT_DOMAIN
def update_domain(self, domain_id, domain):
self._validate_domain_id(domain_id)
raise exception.Forbidden('Domains are read-only against LDAP')
def delete_domain(self, domain_id):
self._validate_domain_id(domain_id)
raise exception.Forbidden('Domains are read-only against LDAP')
def list_domains(self):
return [DEFAULT_DOMAIN]
# TODO(termie): turn this into a data object and move logic to driver
class UserApi(common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap):

View File

@ -14,7 +14,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystone import clean
from keystone.common import sql
from keystone.common.sql import migration
from keystone.common import utils
@ -51,78 +50,6 @@ class Group(sql.ModelBase, sql.DictBase):
__table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {})
class Domain(sql.ModelBase, sql.DictBase):
__tablename__ = 'domain'
attributes = ['id', 'name', 'enabled']
id = sql.Column(sql.String(64), primary_key=True)
name = sql.Column(sql.String(64), unique=True, nullable=False)
enabled = sql.Column(sql.Boolean, default=True)
extra = sql.Column(sql.JsonBlob())
class Project(sql.ModelBase, sql.DictBase):
__tablename__ = 'project'
attributes = ['id', 'name', 'domain_id', 'description', 'enabled']
id = sql.Column(sql.String(64), primary_key=True)
name = sql.Column(sql.String(64), nullable=False)
domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'),
nullable=False)
description = sql.Column(sql.Text())
enabled = sql.Column(sql.Boolean)
extra = sql.Column(sql.JsonBlob())
# Unique constraint across two columns to create the separation
# rather than just only 'name' being unique
__table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {})
class Role(sql.ModelBase, sql.DictBase):
__tablename__ = 'role'
attributes = ['id', 'name']
id = sql.Column(sql.String(64), primary_key=True)
name = sql.Column(sql.String(64), unique=True, nullable=False)
extra = sql.Column(sql.JsonBlob())
class BaseGrant(sql.DictBase):
def to_dict(self):
"""Override parent to_dict() method with a simpler implementation.
Grant tables don't have non-indexed 'extra' attributes, so the
parent implementation is not applicable.
"""
return dict(self.iteritems())
class UserProjectGrant(sql.ModelBase, BaseGrant):
__tablename__ = 'user_project_metadata'
user_id = sql.Column(sql.String(64),
primary_key=True)
project_id = sql.Column(sql.String(64),
primary_key=True)
data = sql.Column(sql.JsonBlob())
class UserDomainGrant(sql.ModelBase, BaseGrant):
__tablename__ = 'user_domain_metadata'
user_id = sql.Column(sql.String(64), primary_key=True)
domain_id = sql.Column(sql.String(64), primary_key=True)
data = sql.Column(sql.JsonBlob())
class GroupProjectGrant(sql.ModelBase, BaseGrant):
__tablename__ = 'group_project_metadata'
group_id = sql.Column(sql.String(64), primary_key=True)
project_id = sql.Column(sql.String(64), primary_key=True)
data = sql.Column(sql.JsonBlob())
class GroupDomainGrant(sql.ModelBase, BaseGrant):
__tablename__ = 'group_domain_metadata'
group_id = sql.Column(sql.String(64), primary_key=True)
domain_id = sql.Column(sql.String(64), primary_key=True)
data = sql.Column(sql.JsonBlob())
class UserGroupMembership(sql.ModelBase, sql.DictBase):
"""Group membership join table."""
__tablename__ = 'user_group_membership'
@ -135,6 +62,9 @@ class UserGroupMembership(sql.ModelBase, sql.DictBase):
class Identity(sql.Base, identity.Driver):
def default_assignment_driver(self):
return "keystone.assignment.backends.sql.Assignment"
# Internal interface to manage the database
def db_sync(self, version=None):
migration.db_sync(version=version)
@ -165,474 +95,6 @@ class Identity(sql.Base, identity.Driver):
raise AssertionError('Invalid user / password')
return user_ref
def authorize_for_project(self, user_ref, tenant_id=None):
user_id = user_ref['id']
tenant_ref = None
metadata_ref = {}
if tenant_id is not None:
# FIXME(gyee): this should really be
# get_roles_for_user_and_project() after the dusts settle
if tenant_id not in self.get_projects_for_user(user_id):
raise AssertionError('Invalid project')
try:
tenant_ref = self.get_project(tenant_id)
metadata_ref = self.get_metadata(user_id, tenant_id)
except exception.ProjectNotFound:
tenant_ref = None
metadata_ref = {}
except exception.MetadataNotFound:
metadata_ref = {}
user_ref = identity.filter_user(user_ref.to_dict())
return (user_ref, tenant_ref, metadata_ref)
def _get_project(self, session, project_id):
project_ref = session.query(Project).get(project_id)
if project_ref is None:
raise exception.ProjectNotFound(project_id=project_id)
return project_ref
def get_project(self, tenant_id):
session = self.get_session()
return self._get_project(session, tenant_id).to_dict()
def get_project_by_name(self, tenant_name, domain_id):
session = self.get_session()
query = session.query(Project)
query = query.filter_by(name=tenant_name)
query = query.filter_by(domain_id=domain_id)
try:
project_ref = query.one()
except sql.NotFound:
raise exception.ProjectNotFound(project_id=tenant_name)
return project_ref.to_dict()
def get_project_user_ids(self, tenant_id):
session = self.get_session()
self.get_project(tenant_id)
query = session.query(UserProjectGrant)
query = query.filter(UserProjectGrant.project_id == tenant_id)
project_refs = query.all()
return [project_ref.user_id for project_ref in project_refs]
def get_project_users(self, tenant_id):
session = self.get_session()
self.get_project(tenant_id)
user_refs = []
for user_id in self.get_project_user_ids(tenant_id):
query = session.query(User)
query = query.filter(User.id == user_id)
user_ref = query.first()
user_refs.append(identity.filter_user(user_ref.to_dict()))
return user_refs
def get_metadata(self, user_id=None, tenant_id=None,
domain_id=None, group_id=None):
session = self.get_session()
if user_id:
if tenant_id:
q = session.query(UserProjectGrant)
q = q.filter_by(project_id=tenant_id)
elif domain_id:
q = session.query(UserDomainGrant)
q = q.filter_by(domain_id=domain_id)
q = q.filter_by(user_id=user_id)
elif group_id:
if tenant_id:
q = session.query(GroupProjectGrant)
q = q.filter_by(project_id=tenant_id)
elif domain_id:
q = session.query(GroupDomainGrant)
q = q.filter_by(domain_id=domain_id)
q = q.filter_by(group_id=group_id)
try:
return q.one().data
except sql.NotFound:
raise exception.MetadataNotFound()
def create_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None):
session = self.get_session()
self._get_role(session, role_id)
if user_id:
self._get_user(session, user_id)
if group_id:
self._get_group(session, group_id)
if domain_id:
self._get_domain(session, domain_id)
if project_id:
self._get_project(session, project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
is_new = False
except exception.MetadataNotFound:
metadata_ref = {}
is_new = True
roles = set(metadata_ref.get('roles', []))
roles.add(role_id)
metadata_ref['roles'] = list(roles)
if is_new:
self.create_metadata(user_id, project_id, metadata_ref,
domain_id, group_id)
else:
self.update_metadata(user_id, project_id, metadata_ref,
domain_id, group_id)
def list_grants(self, user_id=None, group_id=None,
domain_id=None, project_id=None):
session = self.get_session()
if user_id:
self._get_user(session, user_id)
if group_id:
self._get_group(session, group_id)
if domain_id:
self._get_domain(session, domain_id)
if project_id:
self._get_project(session, project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
except exception.MetadataNotFound:
metadata_ref = {}
return [self.get_role(x) for x in metadata_ref.get('roles', [])]
def get_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None):
session = self.get_session()
role_ref = self._get_role(session, role_id)
if user_id:
self._get_user(session, user_id)
if group_id:
self._get_group(session, group_id)
if domain_id:
self._get_domain(session, domain_id)
if project_id:
self._get_project(session, project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
except exception.MetadataNotFound:
metadata_ref = {}
role_ids = set(metadata_ref.get('roles', []))
if role_id not in role_ids:
raise exception.RoleNotFound(role_id=role_id)
return role_ref.to_dict()
def delete_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None):
session = self.get_session()
self._get_role(session, role_id)
if user_id:
self._get_user(session, user_id)
if group_id:
self._get_group(session, group_id)
if domain_id:
self._get_domain(session, domain_id)
if project_id:
self._get_project(session, project_id)
try:
metadata_ref = self.get_metadata(user_id, project_id,
domain_id, group_id)
is_new = False
except exception.MetadataNotFound:
metadata_ref = {}
is_new = True
roles = set(metadata_ref.get('roles', []))
try:
roles.remove(role_id)
except KeyError:
raise exception.RoleNotFound(role_id=role_id)
metadata_ref['roles'] = list(roles)
if is_new:
self.create_metadata(user_id, project_id, metadata_ref,
domain_id, group_id)
else:
self.update_metadata(user_id, project_id, metadata_ref,
domain_id, group_id)
def list_role_assignments(self):
# TODO(henry-nash): The current implementation is really simulating
# us having a common role assignment table, rather than having the
# four different grant tables we have today. When we move to role
# assignment as a first class entity, we should create the single
# assignment table, simplifying the logic of this (and many other)
# functions.
session = self.get_session()
assignment_list = []
refs = session.query(UserDomainGrant).all()
for x in refs:
for r in x.data.get('roles', []):
assignment_list.append({'user_id': x.user_id,
'domain_id': x.domain_id,
'role_id': r})
refs = session.query(UserProjectGrant).all()
for x in refs:
for r in x.data.get('roles', []):
assignment_list.append({'user_id': x.user_id,
'project_id': x.project_id,
'role_id': r})
refs = session.query(GroupDomainGrant).all()
for x in refs:
for r in x.data.get('roles', []):
assignment_list.append({'group_id': x.group_id,
'domain_id': x.domain_id,
'role_id': r})
refs = session.query(GroupProjectGrant).all()
for x in refs:
for r in x.data.get('roles', []):
assignment_list.append({'group_id': x.group_id,
'project_id': x.project_id,
'role_id': r})
return assignment_list
def list_projects(self):
session = self.get_session()
tenant_refs = session.query(Project).all()
return [tenant_ref.to_dict() for tenant_ref in tenant_refs]
def get_projects_for_user(self, user_id):
session = self.get_session()
self._get_user(session, user_id)
query = session.query(UserProjectGrant)
query = query.filter_by(user_id=user_id)
membership_refs = query.all()
return [x.project_id for x in membership_refs]
def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
session = self.get_session()
self._get_user(session, user_id)
self._get_project(session, tenant_id)
self._get_role(session, role_id)
try:
metadata_ref = self.get_metadata(user_id, tenant_id)
is_new = False
except exception.MetadataNotFound:
metadata_ref = {}
is_new = True
roles = set(metadata_ref.get('roles', []))
if role_id in roles:
msg = ('User %s already has role %s in tenant %s'
% (user_id, role_id, tenant_id))
raise exception.Conflict(type='role grant', details=msg)
roles.add(role_id)
metadata_ref['roles'] = list(roles)
if is_new:
self.create_metadata(user_id, tenant_id, metadata_ref)
else:
self.update_metadata(user_id, tenant_id, metadata_ref)
def remove_role_from_user_and_project(self, user_id, tenant_id, role_id):
try:
metadata_ref = self.get_metadata(user_id, tenant_id)
roles = set(metadata_ref.get('roles', []))
if role_id not in roles:
raise exception.RoleNotFound(message=_(
'Cannot remove role that has not been granted, %s') %
role_id)
roles.remove(role_id)
metadata_ref['roles'] = list(roles)
if len(roles):
self.update_metadata(user_id, tenant_id, metadata_ref)
else:
session = self.get_session()
q = session.query(UserProjectGrant)
q = q.filter_by(user_id=user_id)
q = q.filter_by(project_id=tenant_id)
q.delete()
except exception.MetadataNotFound:
msg = 'Cannot remove role that has not been granted, %s' % role_id
raise exception.RoleNotFound(message=msg)
# CRUD
@sql.handle_conflicts(type='project')
def create_project(self, tenant_id, tenant):
tenant['name'] = clean.project_name(tenant['name'])
session = self.get_session()
with session.begin():
tenant_ref = Project.from_dict(tenant)
session.add(tenant_ref)
session.flush()
return tenant_ref.to_dict()
@sql.handle_conflicts(type='project')
def update_project(self, tenant_id, tenant):
session = self.get_session()
if 'name' in tenant:
tenant['name'] = clean.project_name(tenant['name'])
with session.begin():
tenant_ref = self._get_project(session, tenant_id)
old_project_dict = tenant_ref.to_dict()
for k in tenant:
old_project_dict[k] = tenant[k]
new_project = Project.from_dict(old_project_dict)
for attr in Project.attributes:
if attr != 'id':
setattr(tenant_ref, attr, getattr(new_project, attr))
tenant_ref.extra = new_project.extra
session.flush()
return tenant_ref.to_dict(include_extra_dict=True)
@sql.handle_conflicts(type='project')
def delete_project(self, tenant_id):
session = self.get_session()
with session.begin():
tenant_ref = self._get_project(session, tenant_id)
q = session.query(UserProjectGrant)
q = q.filter_by(project_id=tenant_id)
q.delete(False)
q = session.query(UserProjectGrant)
q = q.filter_by(project_id=tenant_id)
q.delete(False)
q = session.query(GroupProjectGrant)
q = q.filter_by(project_id=tenant_id)
q.delete(False)
session.delete(tenant_ref)
session.flush()
@sql.handle_conflicts(type='metadata')
def create_metadata(self, user_id, tenant_id, metadata,
domain_id=None, group_id=None):
session = self.get_session()
with session.begin():
if user_id:
if tenant_id:
session.add(UserProjectGrant(user_id=user_id,
project_id=tenant_id,
data=metadata))
elif domain_id:
session.add(UserDomainGrant(user_id=user_id,
domain_id=domain_id,
data=metadata))
elif group_id:
if tenant_id:
session.add(GroupProjectGrant(group_id=group_id,
project_id=tenant_id,
data=metadata))
elif domain_id:
session.add(GroupDomainGrant(group_id=group_id,
domain_id=domain_id,
data=metadata))
session.flush()
return metadata
@sql.handle_conflicts(type='metadata')
def update_metadata(self, user_id, tenant_id, metadata,
domain_id=None, group_id=None):
session = self.get_session()
with session.begin():
if user_id:
if tenant_id:
q = session.query(UserProjectGrant)
q = q.filter_by(user_id=user_id)
q = q.filter_by(project_id=tenant_id)
elif domain_id:
q = session.query(UserDomainGrant)
q = q.filter_by(user_id=user_id)
q = q.filter_by(domain_id=domain_id)
elif group_id:
if tenant_id:
q = session.query(GroupProjectGrant)
q = q.filter_by(group_id=group_id)
q = q.filter_by(project_id=tenant_id)
elif domain_id:
q = session.query(GroupDomainGrant)
q = q.filter_by(group_id=group_id)
q = q.filter_by(domain_id=domain_id)
metadata_ref = q.first()
data = metadata_ref.data.copy()
data.update(metadata)
metadata_ref.data = data
session.flush()
return metadata_ref
# domain crud
@sql.handle_conflicts(type='domain')
def create_domain(self, domain_id, domain):
session = self.get_session()
with session.begin():
ref = Domain.from_dict(domain)
session.add(ref)
session.flush()
return ref.to_dict()
def list_domains(self):
session = self.get_session()
refs = session.query(Domain).all()
return [ref.to_dict() for ref in refs]
def _get_domain(self, session, domain_id):
ref = session.query(Domain).get(domain_id)
if ref is None:
raise exception.DomainNotFound(domain_id=domain_id)
return ref
def get_domain(self, domain_id):
session = self.get_session()
return self._get_domain(session, domain_id).to_dict()
def get_domain_by_name(self, domain_name):
session = self.get_session()
try:
ref = session.query(Domain).filter_by(name=domain_name).one()
except sql.NotFound:
raise exception.DomainNotFound(domain_id=domain_name)
return ref.to_dict()
@sql.handle_conflicts(type='domain')
def update_domain(self, domain_id, domain):
session = self.get_session()
with session.begin():
ref = self._get_domain(session, domain_id)
old_dict = ref.to_dict()
for k in domain:
old_dict[k] = domain[k]
new_domain = Domain.from_dict(old_dict)
for attr in Domain.attributes:
if attr != 'id':
setattr(ref, attr, getattr(new_domain, attr))
ref.extra = new_domain.extra
session.flush()
return ref.to_dict()
def delete_domain(self, domain_id):
session = self.get_session()
with session.begin():
ref = self._get_domain(session, domain_id)
session.delete(ref)
session.flush()
def list_user_projects(self, user_id):
session = self.get_session()
user = self.get_user(user_id)
metadata_refs = session\
.query(UserProjectGrant)\
.filter_by(user_id=user_id)
project_ids = set([x.project_id for x in metadata_refs
if x.data.get('roles')])
if user.get('project_id'):
project_ids.add(user['project_id'])
# FIXME(dolph): this should be removed with proper migrations
if user.get('tenant_id'):
project_ids.add(user['tenant_id'])
return [self.get_project(x) for x in project_ids]
# user crud
@sql.handle_conflicts(type='user')
@ -753,20 +215,13 @@ class Identity(sql.Base, identity.Driver):
with session.begin():
ref = self._get_user(session, user_id)
q = session.query(UserProjectGrant)
q = q.filter_by(user_id=user_id)
q.delete(False)
q = session.query(UserDomainGrant)
q = q.filter_by(user_id=user_id)
q.delete(False)
q = session.query(UserGroupMembership)
q = q.filter_by(user_id=user_id)
q.delete(False)
session.delete(ref)
session.flush()
self.assignment.delete_user(user_id)
# group crud
@ -817,92 +272,10 @@ class Identity(sql.Base, identity.Driver):
with session.begin():
ref = self._get_group(session, group_id)
q = session.query(GroupProjectGrant)
q = q.filter_by(group_id=group_id)
q.delete(False)
q = session.query(GroupDomainGrant)
q = q.filter_by(group_id=group_id)
q.delete(False)
q = session.query(UserGroupMembership)
q = q.filter_by(group_id=group_id)
q.delete(False)
session.delete(ref)
session.flush()
# role crud
@sql.handle_conflicts(type='role')
def create_role(self, role_id, role):
session = self.get_session()
with session.begin():
ref = Role.from_dict(role)
session.add(ref)
session.flush()
return ref.to_dict()
def list_roles(self):
session = self.get_session()
refs = session.query(Role).all()
return [ref.to_dict() for ref in refs]
def _get_role(self, session, role_id):
ref = session.query(Role).get(role_id)
if ref is None:
raise exception.RoleNotFound(role_id=role_id)
return ref
def get_role(self, role_id):
session = self.get_session()
return self._get_role(session, role_id).to_dict()
@sql.handle_conflicts(type='role')
def update_role(self, role_id, role):
session = self.get_session()
with session.begin():
ref = self._get_role(session, role_id)
old_dict = ref.to_dict()
for k in role:
old_dict[k] = role[k]
new_role = Role.from_dict(old_dict)
for attr in Role.attributes:
if attr != 'id':
setattr(ref, attr, getattr(new_role, attr))
ref.extra = new_role.extra
session.flush()
return ref.to_dict()
def delete_role(self, role_id):
session = self.get_session()
with session.begin():
ref = self._get_role(session, role_id)
for metadata_ref in session.query(UserProjectGrant):
try:
self.delete_grant(role_id, user_id=metadata_ref.user_id,
project_id=metadata_ref.project_id)
except exception.RoleNotFound:
pass
for metadata_ref in session.query(UserDomainGrant):
try:
self.delete_grant(role_id, user_id=metadata_ref.user_id,
domain_id=metadata_ref.domain_id)
except exception.RoleNotFound:
pass
for metadata_ref in session.query(GroupProjectGrant):
try:
self.delete_grant(role_id, group_id=metadata_ref.group_id,
project_id=metadata_ref.project_id)
except exception.RoleNotFound:
pass
for metadata_ref in session.query(GroupDomainGrant):
try:
self.delete_grant(role_id, group_id=metadata_ref.group_id,
domain_id=metadata_ref.domain_id)
except exception.RoleNotFound:
pass
session.delete(ref)
session.flush()
self.assignment.delete_group(group_id)

View File

@ -16,6 +16,7 @@
"""Main entry point into the Identity service."""
from keystone import assignment
from keystone import clean
from keystone.common import dependency
from keystone.common import logging
@ -60,8 +61,12 @@ class Manager(manager.Manager):
"""
def __init__(self):
def __init__(self, assignment_api=None):
super(Manager, self).__init__(CONF.identity.driver)
if assignment_api is None:
assignment_api = assignment.Manager(self)
self.assignment = assignment_api
self.driver.assignment = assignment_api
def authenticate(self, user_id=None, tenant_id=None, password=None):
"""Authenticate a given user and password and
@ -70,7 +75,7 @@ class Manager(manager.Manager):
:raises: AssertionError
"""
user_ref = self.driver.authenticate_user(user_id, password)
return self.driver.authorize_for_project(user_ref, tenant_id)
return self.assignment_api.authorize_for_project(user_ref, tenant_id)
def create_user(self, user_id, user_ref):
user = user_ref.copy()
@ -97,329 +102,152 @@ class Manager(manager.Manager):
tenant.setdefault('enabled', True)
tenant['enabled'] = clean.project_enabled(tenant['enabled'])
tenant.setdefault('description', '')
return self.driver.create_project(tenant_id, tenant)
return self.assignment_api.create_project(tenant_id, tenant)
def update_project(self, tenant_id, tenant_ref):
tenant = tenant_ref.copy()
if 'enabled' in tenant:
tenant['enabled'] = clean.project_enabled(tenant['enabled'])
return self.driver.update_project(tenant_id, tenant)
return self.assignment_api.update_project(tenant_id, tenant)
def authorize_for_project(self, user_ref, tenant_id=None):
return self.assignment.authorize_for_project(user_ref, tenant_id)
def get_project_by_name(self, tenant_name, domain_id):
return self.assignment.get_project_by_name(tenant_name, domain_id)
def get_project(self, tenant_id):
return self.assignment.get_project(tenant_id)
def list_projects(self):
return self.assignment.list_projects()
def _validate_domain(self, ref):
return self.assignment._validate_domain(ref)
def _validate_domain_id(self, domain_id):
return self.assignment._validate_domain_id(domain_id)
def _set_default_domain(self, ref):
return self.assignment._set_default_domain(ref)
def get_metadata(self, user_id=None, tenant_id=None,
domain_id=None, group_id=None):
return self.assignment_api.get_metadata(user_id, tenant_id,
domain_id, group_id)
def get_role(self, role_id):
return self.assignment.get_role(role_id)
def list_roles(self):
return self.assignment.list_roles()
def get_projects_for_user(self, user_id):
return self.assignment.get_projects_for_user(user_id)
def get_project_users(self, tenant_id):
return self.assignment.get_project_users(tenant_id)
def get_roles_for_user_and_project(self, user_id, tenant_id):
return self.assignment.get_roles_for_user_and_project(user_id,
tenant_id)
def get_roles_for_user_and_domain(self, user_id, domain_id):
return (self.assignment.get_roles_for_user_and_domain
(user_id, domain_id))
def _subrole_id_to_dn(self, role_id, tenant_id):
return self.assignment._subrole_id_to_dn(role_id, tenant_id)
def add_role_to_user_and_project(self, user_id,
tenant_id, role_id):
return (self.assignment_api.add_role_to_user_and_project
(user_id, tenant_id, role_id))
def create_metadata(self, user_id, tenant_id, metadata):
return self.assignment.create_metadata(user_id, tenant_id, metadata)
def create_role(self, role_id, role):
return self.assignment.create_role(role_id, role)
def delete_role(self, role_id):
return self.assignment.delete_role(role_id)
def delete_project(self, tenant_id):
return self.assignment.delete_project(tenant_id)
def remove_role_from_user_and_project(self, user_id,
tenant_id, role_id):
return (self.assignment_api.remove_role_from_user_and_project
(user_id, tenant_id, role_id))
def update_role(self, role_id, role):
return self.assignment.update_role(role_id, role)
def create_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None):
return (self.assignment_api.create_grant
(role_id, user_id, group_id, domain_id, project_id))
def list_grants(self, user_id=None, group_id=None,
domain_id=None, project_id=None):
return (self.assignment_api.list_grants
(user_id, group_id, domain_id, project_id))
def get_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None):
return (self.assignment_api.get_grant
(role_id, user_id, group_id, domain_id, project_id))
def delete_grant(self, role_id, user_id=None, group_id=None,
domain_id=None, project_id=None):
return (self.assignment_api.delete_grant
(role_id, user_id, group_id, domain_id, project_id))
def create_domain(self, domain_id, domain):
return self.assignment.create_domain(domain_id, domain)
def get_domain_by_name(self, domain_name):
return self.assignment.get_domain_by_name(domain_name)
def get_domain(self, domain_id):
return self.assignment.get_domain(domain_id)
def update_domain(self, domain_id, domain):
return self.assignment.update_domain(domain_id, domain)
def delete_domain(self, domain_id):
return self.assignment.delete_domain(domain_id)
def list_domains(self):
return self.assignment.list_domains()
def list_user_projects(self, user_id):
return self.assignment.list_user_projects(user_id)
def add_user_to_project(self, tenant_id, user_id):
return self.assignment.add_user_to_project(tenant_id, user_id)
def remove_user_from_project(self, tenant_id, user_id):
return self.assignment.remove_user_from_project(tenant_id, user_id)
def update_metadata(self, user_id, tenant_id, metadata,
domain_id=None, group_id=None):
return (self.assignment_api.update_metadata
(user_id, tenant_id, metadata, domain_id, group_id))
def list_role_assignments(self):
return self.assignment_api.list_role_assignments()
class Driver(object):
"""Interface description for an Identity driver."""
def authenticate_user(self, user_id, password):
"""Authenticate a given user and password.
:returns: user_ref
:raises: AssertionError
"""
raise exception.NotImplemented()
def authorize_for_project(self, tenant_id, user_ref):
"""Authenticate a given user for a tenant.
:returns: (user_ref, tenant_ref, metadata_ref)
:raises: AssertionError
"""
raise exception.NotImplemented()
def get_project_by_name(self, tenant_name, domain_id):
"""Get a tenant by name.
:returns: tenant_ref
:raises: keystone.exception.ProjectNotFound
"""
raise exception.NotImplemented()
def get_user_by_name(self, user_name, domain_id):
"""Get a user by name.
:returns: user_ref
:raises: keystone.exception.UserNotFound
"""
raise exception.NotImplemented()
def add_user_to_project(self, tenant_id, user_id):
"""Add user to a tenant by creating a default role relationship.
:raises: keystone.exception.ProjectNotFound,
keystone.exception.UserNotFound
"""
self.add_role_to_user_and_project(user_id,
tenant_id,
config.CONF.member_role_id)
def remove_user_from_project(self, tenant_id, user_id):
"""Remove user from a tenant
:raises: keystone.exception.ProjectNotFound,
keystone.exception.UserNotFound
"""
roles = self.get_roles_for_user_and_project(user_id, tenant_id)
if not roles:
raise exception.NotFound(tenant_id)
for role_id in roles:
self.remove_role_from_user_and_project(user_id, tenant_id, role_id)
def get_project_users(self, tenant_id):
"""Lists all users with a relationship to the specified project.
:returns: a list of user_refs or an empty set.
:raises: keystone.exception.ProjectNotFound
"""
raise exception.NotImplemented()
def get_projects_for_user(self, user_id):
"""Get the tenants associated with a given user.
:returns: a list of tenant_id's.
:raises: keystone.exception.UserNotFound
"""
raise exception.NotImplemented()
def get_roles_for_user_and_project(self, user_id, tenant_id):
"""Get the roles associated with a user within given tenant.
This includes roles directly assigned to the user on the
project, as well as those by virtue of group membership.
:returns: a list of role ids.
:raises: keystone.exception.UserNotFound,
keystone.exception.ProjectNotFound
"""
def _get_group_project_roles(user_id, tenant_id):
role_list = []
group_refs = self.list_groups_for_user(user_id=user_id)
for x in group_refs:
try:
metadata_ref = self.get_metadata(group_id=x['id'],
tenant_id=tenant_id)
role_list += metadata_ref.get('roles', [])
except exception.MetadataNotFound:
# no group grant, skip
pass
return role_list
def _get_user_project_roles(user_id, tenant_id):
metadata_ref = {}
try:
metadata_ref = self.get_metadata(user_id=user_id,
tenant_id=tenant_id)
except exception.MetadataNotFound:
pass
return metadata_ref.get('roles', [])
self.get_user(user_id)
self.get_project(tenant_id)
user_role_list = _get_user_project_roles(user_id, tenant_id)
group_role_list = _get_group_project_roles(user_id, tenant_id)
# Use set() to process the list to remove any duplicates
return list(set(user_role_list + group_role_list))
def get_roles_for_user_and_domain(self, user_id, domain_id):
"""Get the roles associated with a user within given domain.
This includes roles directly assigned to the user on the
domain, as well as those by virtue of group membership.
:returns: a list of role ids.
:raises: keystone.exception.UserNotFound,
keystone.exception.DomainNotFound
"""
def _get_group_domain_roles(user_id, domain_id):
role_list = []
group_refs = self.list_groups_for_user(user_id=user_id)
for x in group_refs:
try:
metadata_ref = self.get_metadata(group_id=x['id'],
domain_id=domain_id)
role_list += metadata_ref.get('roles', [])
except (exception.MetadataNotFound, exception.NotImplemented):
# MetadataNotFound implies no group grant, so skip.
# Ignore NotImplemented since not all backends support
# domains.
pass
return role_list
def _get_user_domain_roles(user_id, domain_id):
metadata_ref = {}
try:
metadata_ref = self.get_metadata(user_id=user_id,
domain_id=domain_id)
except (exception.MetadataNotFound, exception.NotImplemented):
# MetadataNotFound implies no user grants.
# Ignore NotImplemented since not all backends support
# domains.
pass
return metadata_ref.get('roles', [])
self.get_user(user_id)
self.get_domain(domain_id)
user_role_list = _get_user_domain_roles(user_id, domain_id)
group_role_list = _get_group_domain_roles(user_id, domain_id)
# Use set() to process the list to remove any duplicates
return list(set(user_role_list + group_role_list))
def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
"""Add a role to a user within given tenant.
:raises: keystone.exception.UserNotFound,
keystone.exception.ProjectNotFound,
keystone.exception.RoleNotFound
"""
raise exception.NotImplemented()
def remove_role_from_user_and_project(self, user_id, tenant_id, role_id):
"""Remove a role from a user within given tenant.
:raises: keystone.exception.UserNotFound,
keystone.exception.ProjectNotFound,
keystone.exception.RoleNotFound
"""
raise exception.NotImplemented()
# metadata crud
def get_metadata(self, user_id=None, tenant_id=None,
domain_id=None, group_id=None):
"""Gets the metadata for the specified user/group on project/domain.
:raises: keystone.exception.MetadataNotFound
:returns: metadata
"""
raise exception.NotImplemented()
def create_metadata(self, user_id, tenant_id, metadata,
domain_id=None, group_id=None):
"""Creates the metadata for the specified user/group on project/domain.
:returns: metadata created
"""
raise exception.NotImplemented()
def update_metadata(self, user_id, tenant_id, metadata,
domain_id=None, group_id=None):
"""Updates the metadata for the specified user/group on project/domain.
:returns: metadata updated
"""
raise exception.NotImplemented()
# domain crud
def create_domain(self, domain_id, domain):
"""Creates a new domain.
:raises: keystone.exception.Conflict
"""
raise exception.NotImplemented()
def list_domains(self):
"""List all domains in the system.
:returns: a list of domain_refs or an empty list.
"""
raise exception.NotImplemented()
def get_domain(self, domain_id):
"""Get a domain by ID.
:returns: domain_ref
:raises: keystone.exception.DomainNotFound
"""
raise exception.NotImplemented()
def get_domain_by_name(self, domain_name):
"""Get a domain by name.
:returns: domain_ref
:raises: keystone.exception.DomainNotFound
"""
raise exception.NotImplemented()
def update_domain(self, domain_id, domain):
"""Updates an existing domain.
:raises: keystone.exception.DomainNotFound,
keystone.exception.Conflict
"""
raise exception.NotImplemented()
def delete_domain(self, domain_id):
"""Deletes an existing domain.
:raises: keystone.exception.DomainNotFound
"""
raise exception.NotImplemented()
# project crud
def create_project(self, project_id, project):
"""Creates a new project.
:raises: keystone.exception.Conflict
"""
raise exception.NotImplemented()
def list_projects(self):
"""List all projects in the system.
:returns: a list of project_refs or an empty list.
"""
raise exception.NotImplemented()
def list_user_projects(self, user_id):
"""List all projects associated with a given user.
:returns: a list of project_refs or an empty list.
"""
raise exception.NotImplemented()
def get_project(self, project_id):
"""Get a project by ID.
:returns: project_ref
:raises: keystone.exception.ProjectNotFound
"""
raise exception.NotImplemented()
def update_project(self, project_id, project):
"""Updates an existing project.
:raises: keystone.exception.ProjectNotFound,
keystone.exception.Conflict
"""
raise exception.NotImplemented()
def delete_project(self, project_id):
"""Deletes an existing project.
:raises: keystone.exception.ProjectNotFound
"""
raise exception.NotImplemented()
# user crud
def create_user(self, user_id, user):
@ -498,54 +326,15 @@ class Driver(object):
"""
raise exception.NotImplemented()
# role crud
def get_user_by_name(self, user_name, domain_id):
"""Get a user by name.
def create_role(self, role_id, role):
"""Creates a new role.
:raises: keystone.exception.Conflict
:returns: user_ref
:raises: keystone.exception.UserNotFound
"""
raise exception.NotImplemented()
def list_roles(self):
"""List all roles in the system.
:returns: a list of role_refs or an empty list.
"""
raise exception.NotImplemented()
def get_role(self, role_id):
"""Get a role by ID.
:returns: role_ref
:raises: keystone.exception.RoleNotFound
"""
raise exception.NotImplemented()
def update_role(self, role_id, role):
"""Updates an existing role.
:raises: keystone.exception.RoleNotFound,
keystone.exception.Conflict
"""
raise exception.NotImplemented()
def delete_role(self, role_id):
"""Deletes an existing role.
:raises: keystone.exception.RoleNotFound
"""
raise exception.NotImplemented()
def list_role_assignments(self):
raise exception.NotImplemented()
# group crud
def create_group(self, group_id, group):
@ -597,3 +386,7 @@ class Driver(object):
"""
raise exception.NotImplemented()
#end of identity
# Assignments

View File

@ -35,6 +35,7 @@ gettext.install('keystone', unicode=1)
from keystone.common import environment
environment.use_eventlet()
from keystone import assignment
from keystone import catalog
from keystone.common import kvs
from keystone.common import logging
@ -223,7 +224,8 @@ class TestCase(NoModule, unittest.TestCase):
def load_backends(self):
"""Initializes each manager and assigns them to an attribute."""
for manager in [catalog, credential, identity, policy, token, trust]:
for manager in [assignment, catalog, credential,
identity, policy, token, trust]:
manager_name = '%s_api' % manager.__name__.split('.')[-1]
setattr(self, manager_name, manager.Manager())

View File

@ -4,7 +4,6 @@ import uuid
from keystone.common import cms
from keystone.common import controller
from keystone.common import dependency
from keystone.common import environment
from keystone.common import logging
from keystone.common import utils
@ -23,7 +22,6 @@ class ExternalAuthNotApplicable(Exception):
pass
@dependency.requires('catalog_api', 'trust_api', 'token_api')
class Auth(controller.V2Controller):
def ca_cert(self, context, auth=None):
ca_file = open(CONF.signing.ca_certs, 'r')