summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Young <ayoung@redhat.com>2013-06-05 16:28:16 -0400
committerAdam Young <ayoung@redhat.com>2013-07-09 11:50:08 -0400
commitfafdf072f5a34ee12ffe9d7651551c83459759bb (patch)
tree7bf26796b05fd61a5daa102aadaaecbe8a232280
parentb556d8a6cad15b7dea0318c6164b10529969807d (diff)
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
-rw-r--r--keystone/assignment/__init__.py18
-rw-r--r--keystone/assignment/backends/__init__.py0
-rw-r--r--keystone/assignment/backends/kvs.py498
-rw-r--r--keystone/assignment/backends/ldap.py604
-rw-r--r--keystone/assignment/backends/sql.py707
-rw-r--r--keystone/assignment/core.py403
-rw-r--r--keystone/common/config.py6
-rw-r--r--keystone/common/controller.py3
-rw-r--r--keystone/common/kvs.py2
-rw-r--r--keystone/common/sql/legacy.py14
-rw-r--r--keystone/common/sql/nova.py21
-rw-r--r--keystone/identity/backends/kvs.py456
-rw-r--r--keystone/identity/backends/ldap.py266
-rw-r--r--keystone/identity/backends/sql.py637
-rw-r--r--keystone/identity/core.py419
-rw-r--r--keystone/test.py4
-rw-r--r--keystone/token/controllers.py2
17 files changed, 2412 insertions, 1648 deletions
diff --git a/keystone/assignment/__init__.py b/keystone/assignment/__init__.py
new file mode 100644
index 00000000..5a848308
--- /dev/null
+++ b/keystone/assignment/__init__.py
@@ -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 *
diff --git a/keystone/assignment/backends/__init__.py b/keystone/assignment/backends/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/keystone/assignment/backends/__init__.py
diff --git a/keystone/assignment/backends/kvs.py b/keystone/assignment/backends/kvs.py
new file mode 100644
index 00000000..9a09598f
--- /dev/null
+++ b/keystone/assignment/backends/kvs.py
@@ -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))
diff --git a/keystone/assignment/backends/ldap.py b/keystone/assignment/backends/ldap.py
new file mode 100644
index 00000000..500f304a
--- /dev/null
+++ b/keystone/assignment/backends/ldap.py
@@ -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)
diff --git a/keystone/assignment/backends/sql.py b/keystone/assignment/backends/sql.py
new file mode 100644
index 00000000..65d1bbc7
--- /dev/null
+++ b/keystone/assignment/backends/sql.py
@@ -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())
diff --git a/keystone/assignment/core.py b/keystone/assignment/core.py
new file mode 100644
index 00000000..251a3bc6
--- /dev/null
+++ b/keystone/assignment/core.py
@@ -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
+
+ """
diff --git a/keystone/common/config.py b/keystone/common/config.py
index c0bd34e6..64089ec8 100644
--- a/keystone/common/config.py
+++ b/keystone/common/config.py
@@ -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',
diff --git a/keystone/common/controller.py b/keystone/common/controller.py
index 3ca1bf8b..f19f13ec 100644
--- a/keystone/common/controller.py
+++ b/keystone/common/controller.py
@@ -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."""
diff --git a/keystone/common/kvs.py b/keystone/common/kvs.py
index b517bc5d..09693999 100644
--- a/keystone/common/kvs.py
+++ b/keystone/common/kvs.py
@@ -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
diff --git a/keystone/common/sql/legacy.py b/keystone/common/sql/legacy.py
index f2e0b994..c8adc900 100644
--- a/keystone/common/sql/legacy.py
+++ b/keystone/common/sql/legacy.py
@@ -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):
diff --git a/keystone/common/sql/nova.py b/keystone/common/sql/nova.py
index c6d452cd..fd8d2481 100644
--- a/keystone/common/sql/nova.py
+++ b/keystone/common/sql/nova.py
@@ -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'],
diff --git a/keystone/identity/backends/kvs.py b/keystone/identity/backends/kvs.py
index 89c05aaf..4fbc1e23 100644
--- a/keystone/identity/backends/kvs.py
+++ b/keystone/identity/backends/kvs.py
@@ -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):
diff --git a/keystone/identity/backends/ldap.py b/keystone/identity/backends/ldap.py
index 2387c84e..231d857d 100644
--- a/keystone/identity/backends/ldap.py
+++ b/keystone/identity/backends/ldap.py
@@ -47,41 +47,18 @@ 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.
+ def default_assignment_driver(self):
+ return "keystone.assignment.backends.ldap.Assignment"
- 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
+ # Identity interface
- def _validate_domain_id(self, domain_id):
- """Validate that the domain ID specified belongs to the default domain.
+ def create_project(self, project_id, project):
+ return self.assignment.create_project(project_id, project)
- """
- 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))
-
- # Identity interface
+ def get_project(self, project_id):
+ return self.assignment.get_project(project_id)
def authenticate_user(self, user_id=None, password=None):
try:
@@ -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):
diff --git a/keystone/identity/backends/sql.py b/keystone/identity/backends/sql.py
index c02ffd0b..381f547c 100644
--- a/keystone/identity/backends/sql.py
+++ b/keystone/identity/backends/sql.py
@@ -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)
diff --git a/keystone/identity/core.py b/keystone/identity/core.py
index 1e2ed733..248915d6 100644
--- a/keystone/identity/core.py
+++ b/keystone/identity/core.py
@@ -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)
-
-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 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):
- """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.
+ return self.assignment.get_project_by_name(tenant_name, domain_id)
- :raises: keystone.exception.ProjectNotFound,
- keystone.exception.UserNotFound
+ def get_project(self, tenant_id):
+ return self.assignment.get_project(tenant_id)
- """
- self.add_role_to_user_and_project(user_id,
- tenant_id,
- config.CONF.member_role_id)
+ def list_projects(self):
+ return self.assignment.list_projects()
- def remove_user_from_project(self, tenant_id, user_id):
- """Remove user from a tenant
+ def _validate_domain(self, ref):
+ return self.assignment._validate_domain(ref)
- :raises: keystone.exception.ProjectNotFound,
- keystone.exception.UserNotFound
+ def _validate_domain_id(self, domain_id):
+ return self.assignment._validate_domain_id(domain_id)
- """
- 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 _set_default_domain(self, ref):
+ return self.assignment._set_default_domain(ref)
- def get_project_users(self, tenant_id):
- """Lists all users with a relationship to the specified project.
+ 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)
- :returns: a list of user_refs or an empty set.
- :raises: keystone.exception.ProjectNotFound
+ def get_role(self, role_id):
+ return self.assignment.get_role(role_id)
- """
- raise exception.NotImplemented()
+ def list_roles(self):
+ return self.assignment.list_roles()
def get_projects_for_user(self, user_id):
- """Get the tenants associated with a given user.
+ return self.assignment.get_projects_for_user(user_id)
- :returns: a list of tenant_id's.
- :raises: keystone.exception.UserNotFound
-
- """
- raise exception.NotImplemented()
+ 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):
- """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))
+ 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):
- """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.
+ return (self.assignment.get_roles_for_user_and_domain
+ (user_id, domain_id))
- :returns: a list of role ids.
- :raises: keystone.exception.UserNotFound,
- keystone.exception.DomainNotFound
-
- """
+ def _subrole_id_to_dn(self, role_id, tenant_id):
+ return self.assignment._subrole_id_to_dn(role_id, tenant_id)
- 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.
+ 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))
- :raises: keystone.exception.UserNotFound,
- keystone.exception.ProjectNotFound,
- keystone.exception.RoleNotFound
- """
- raise exception.NotImplemented()
+ def create_metadata(self, user_id, tenant_id, metadata):
+ return self.assignment.create_metadata(user_id, tenant_id, metadata)
- 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 create_role(self, role_id, role):
+ return self.assignment.create_role(role_id, role)
- # 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.
+ def delete_role(self, role_id):
+ return self.assignment.delete_role(role_id)
- :raises: keystone.exception.MetadataNotFound
- :returns: metadata
+ def delete_project(self, tenant_id):
+ return self.assignment.delete_project(tenant_id)
- """
- raise exception.NotImplemented()
+ 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 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.
+ def update_role(self, role_id, role):
+ return self.assignment.update_role(role_id, role)
- :returns: metadata created
+ 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))
- """
- raise exception.NotImplemented()
+ 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 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.
+ 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))
- :returns: metadata updated
+ 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))
- """
- 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()
+ return self.assignment.create_domain(domain_id, domain)
def get_domain_by_name(self, domain_name):
- """Get a domain by name.
-
- :returns: domain_ref
- :raises: keystone.exception.DomainNotFound
+ return self.assignment.get_domain_by_name(domain_name)
- """
- raise exception.NotImplemented()
+ def get_domain(self, domain_id):
+ return self.assignment.get_domain(domain_id)
def update_domain(self, domain_id, domain):
- """Updates an existing domain.
-
- :raises: keystone.exception.DomainNotFound,
- keystone.exception.Conflict
-
- """
- raise exception.NotImplemented()
+ return self.assignment.update_domain(domain_id, domain)
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()
+ return self.assignment.delete_domain(domain_id)
- 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_domains(self):
+ return self.assignment.list_domains()
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
+ return self.assignment.list_user_projects(user_id)
- """
- raise exception.NotImplemented()
-
- def update_project(self, project_id, project):
- """Updates an existing project.
+ def add_user_to_project(self, tenant_id, user_id):
+ return self.assignment.add_user_to_project(tenant_id, user_id)
- :raises: keystone.exception.ProjectNotFound,
- keystone.exception.Conflict
+ def remove_user_from_project(self, tenant_id, user_id):
+ return self.assignment.remove_user_from_project(tenant_id, user_id)
- """
- raise exception.NotImplemented()
+ 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 delete_project(self, project_id):
- """Deletes an existing project.
+ def list_role_assignments(self):
+ return self.assignment_api.list_role_assignments()
- :raises: keystone.exception.ProjectNotFound
+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()
-
# user crud
def create_user(self, user_id, user):
@@ -498,54 +326,15 @@ class Driver(object):
"""
raise exception.NotImplemented()
- # 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.
+ def get_user_by_name(self, user_name, domain_id):
+ """Get a user by name.
- :raises: keystone.exception.RoleNotFound
+ :returns: user_ref
+ :raises: keystone.exception.UserNotFound
"""
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
diff --git a/keystone/test.py b/keystone/test.py
index e68676ca..4d9e95e9 100644
--- a/keystone/test.py
+++ b/keystone/test.py
@@ -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())
diff --git a/keystone/token/controllers.py b/keystone/token/controllers.py
index 34700106..bfffa3ad 100644
--- a/keystone/token/controllers.py
+++ b/keystone/token/controllers.py
@@ -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')