summaryrefslogtreecommitdiffstats
path: root/keystone/assignment
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 /keystone/assignment
parentb556d8a6cad15b7dea0318c6164b10529969807d (diff)
downloadkeystone-fafdf072f5a34ee12ffe9d7651551c83459759bb.tar.gz
keystone-fafdf072f5a34ee12ffe9d7651551c83459759bb.tar.xz
keystone-fafdf072f5a34ee12ffe9d7651551c83459759bb.zip
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
Diffstat (limited to 'keystone/assignment')
-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
6 files changed, 2230 insertions, 0 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
+
+ """