diff options
Diffstat (limited to 'keystone-moon/keystone/resource/backends')
-rw-r--r-- | keystone-moon/keystone/resource/backends/__init__.py | 0 | ||||
-rw-r--r-- | keystone-moon/keystone/resource/backends/ldap.py | 196 | ||||
-rw-r--r-- | keystone-moon/keystone/resource/backends/sql.py | 260 |
3 files changed, 456 insertions, 0 deletions
diff --git a/keystone-moon/keystone/resource/backends/__init__.py b/keystone-moon/keystone/resource/backends/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/keystone-moon/keystone/resource/backends/__init__.py diff --git a/keystone-moon/keystone/resource/backends/ldap.py b/keystone-moon/keystone/resource/backends/ldap.py new file mode 100644 index 00000000..434c2b04 --- /dev/null +++ b/keystone-moon/keystone/resource/backends/ldap.py @@ -0,0 +1,196 @@ +# 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 + +from oslo_config import cfg +from oslo_log import log + +from keystone import clean +from keystone.common import driver_hints +from keystone.common import ldap as common_ldap +from keystone.common import models +from keystone import exception +from keystone.i18n import _ +from keystone.identity.backends import ldap as ldap_identity +from keystone import resource + + +CONF = cfg.CONF +LOG = log.getLogger(__name__) + + +class Resource(resource.Driver): + def __init__(self): + super(Resource, 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 + + # This is the only deep dependency from resource back to identity. + # This is safe to do since if you are using LDAP for resource, it is + # required that you are using it for identity as well. + self.user = ldap_identity.UserApi(CONF) + + self.project = ProjectApi(CONF) + + def default_assignment_driver(self): + return 'keystone.assignment.backends.ldap.Assignment' + + def _set_default_parent_project(self, ref): + """If the parent project ID has not been set, set it to None.""" + if isinstance(ref, dict): + if 'parent_id' not in ref: + ref = dict(ref, parent_id=None) + return ref + elif isinstance(ref, list): + return [self._set_default_parent_project(x) for x in ref] + else: + raise ValueError(_('Expected dict or list: %s') % type(ref)) + + def _validate_parent_project_is_none(self, ref): + """If a parent_id different from None was given, + raises InvalidProjectException. + + """ + parent_id = ref.get('parent_id') + if parent_id is not None: + raise exception.InvalidParentProject(parent_id) + + def _set_default_attributes(self, project_ref): + project_ref = self._set_default_domain(project_ref) + return self._set_default_parent_project(project_ref) + + def get_project(self, tenant_id): + return self._set_default_attributes( + self.project.get(tenant_id)) + + def list_projects(self, hints): + return self._set_default_attributes( + self.project.get_all_filtered(hints)) + + def list_projects_in_domain(self, domain_id): + # We don't support multiple domains within this driver, so ignore + # any domain specified + return self.list_projects(driver_hints.Hints()) + + def list_projects_in_subtree(self, project_id): + # We don't support projects hierarchy within this driver, so a + # project will never have children + return [] + + def list_project_parents(self, project_id): + # We don't support projects hierarchy within this driver, so a + # project will never have parents + return [] + + def is_leaf_project(self, project_id): + # We don't support projects hierarchy within this driver, so a + # project will always be a root and a leaf at the same time + return True + + def list_projects_from_ids(self, ids): + return [self.get_project(id) for id in ids] + + def list_project_ids_from_domain_ids(self, domain_ids): + # We don't support multiple domains within this driver, so ignore + # any domain specified + return [x.id for x in self.list_projects(driver_hints.Hints())] + + def get_project_by_name(self, tenant_name, domain_id): + self._validate_default_domain_id(domain_id) + return self._set_default_attributes( + self.project.get_by_name(tenant_name)) + + def create_project(self, tenant_id, tenant): + self.project.check_allow_create() + tenant = self._validate_default_domain(tenant) + self._validate_parent_project_is_none(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_attributes( + self.project.create(data)) + + def update_project(self, tenant_id, tenant): + self.project.check_allow_update() + tenant = self._validate_default_domain(tenant) + if 'name' in tenant: + tenant['name'] = clean.project_name(tenant['name']) + return self._set_default_attributes( + self.project.update(tenant_id, tenant)) + + def delete_project(self, tenant_id): + self.project.check_allow_delete() + if self.project.subtree_delete_enabled: + self.project.deleteTree(tenant_id) + else: + # The manager layer will call assignments to delete the + # role assignments, so we just have to delete the project itself. + self.project.delete(tenant_id) + + 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_default_domain_id(domain_id) + return resource.calc_default_domain() + + def update_domain(self, domain_id, domain): + self._validate_default_domain_id(domain_id) + raise exception.Forbidden(_('Domains are read-only against LDAP')) + + def delete_domain(self, domain_id): + self._validate_default_domain_id(domain_id) + raise exception.Forbidden(_('Domains are read-only against LDAP')) + + def list_domains(self, hints): + return [resource.calc_default_domain()] + + def list_domains_from_ids(self, ids): + return [resource.calc_default_domain()] + + def get_domain_by_name(self, domain_name): + default_domain = resource.calc_default_domain() + if domain_name != default_domain['name']: + raise exception.DomainNotFound(domain_id=domain_name) + return default_domain + + +# TODO(termie): turn this into a data object and move logic to driver +class ProjectApi(common_ldap.ProjectLdapStructureMixin, + common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap): + + model = models.Project + + def create(self, values): + data = values.copy() + if data.get('id') is None: + data['id'] = uuid.uuid4().hex + return super(ProjectApi, self).create(data) + + def update(self, project_id, values): + old_obj = self.get(project_id) + return super(ProjectApi, self).update(project_id, values, old_obj) + + def get_all_filtered(self, hints): + query = self.filter_query(hints) + return super(ProjectApi, self).get_all(query) diff --git a/keystone-moon/keystone/resource/backends/sql.py b/keystone-moon/keystone/resource/backends/sql.py new file mode 100644 index 00000000..fb117240 --- /dev/null +++ b/keystone-moon/keystone/resource/backends/sql.py @@ -0,0 +1,260 @@ +# 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 oslo_config import cfg +from oslo_log import log + +from keystone import clean +from keystone.common import sql +from keystone import exception +from keystone.i18n import _LE +from keystone import resource as keystone_resource + + +CONF = cfg.CONF +LOG = log.getLogger(__name__) + + +class Resource(keystone_resource.Driver): + + def default_assignment_driver(self): + return 'keystone.assignment.backends.sql.Assignment' + + 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): + with sql.transaction() as session: + return self._get_project(session, tenant_id).to_dict() + + def get_project_by_name(self, tenant_name, domain_id): + with sql.transaction() as 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() + + @sql.truncated + def list_projects(self, hints): + with sql.transaction() as session: + query = session.query(Project) + project_refs = sql.filter_limit_query(Project, query, hints) + return [project_ref.to_dict() for project_ref in project_refs] + + def list_projects_from_ids(self, ids): + if not ids: + return [] + else: + with sql.transaction() as session: + query = session.query(Project) + query = query.filter(Project.id.in_(ids)) + return [project_ref.to_dict() for project_ref in query.all()] + + def list_project_ids_from_domain_ids(self, domain_ids): + if not domain_ids: + return [] + else: + with sql.transaction() as session: + query = session.query(Project.id) + query = ( + query.filter(Project.domain_id.in_(domain_ids))) + return [x.id for x in query.all()] + + def list_projects_in_domain(self, domain_id): + with sql.transaction() as session: + self._get_domain(session, domain_id) + query = session.query(Project) + project_refs = query.filter_by(domain_id=domain_id) + return [project_ref.to_dict() for project_ref in project_refs] + + def _get_children(self, session, project_ids): + query = session.query(Project) + query = query.filter(Project.parent_id.in_(project_ids)) + project_refs = query.all() + return [project_ref.to_dict() for project_ref in project_refs] + + def list_projects_in_subtree(self, project_id): + with sql.transaction() as session: + project = self._get_project(session, project_id).to_dict() + children = self._get_children(session, [project['id']]) + subtree = [] + examined = set(project['id']) + while children: + children_ids = set() + for ref in children: + if ref['id'] in examined: + msg = _LE('Circular reference or a repeated ' + 'entry found in projects hierarchy - ' + '%(project_id)s.') + LOG.error(msg, {'project_id': ref['id']}) + return + children_ids.add(ref['id']) + + examined.union(children_ids) + subtree += children + children = self._get_children(session, children_ids) + return subtree + + def list_project_parents(self, project_id): + with sql.transaction() as session: + project = self._get_project(session, project_id).to_dict() + parents = [] + examined = set() + while project.get('parent_id') is not None: + if project['id'] in examined: + msg = _LE('Circular reference or a repeated ' + 'entry found in projects hierarchy - ' + '%(project_id)s.') + LOG.error(msg, {'project_id': project['id']}) + return + + examined.add(project['id']) + parent_project = self._get_project( + session, project['parent_id']).to_dict() + parents.append(parent_project) + project = parent_project + return parents + + def is_leaf_project(self, project_id): + with sql.transaction() as session: + project_refs = self._get_children(session, [project_id]) + return not project_refs + + # CRUD + @sql.handle_conflicts(conflict_type='project') + def create_project(self, tenant_id, tenant): + tenant['name'] = clean.project_name(tenant['name']) + with sql.transaction() as session: + tenant_ref = Project.from_dict(tenant) + session.add(tenant_ref) + return tenant_ref.to_dict() + + @sql.handle_conflicts(conflict_type='project') + def update_project(self, tenant_id, tenant): + if 'name' in tenant: + tenant['name'] = clean.project_name(tenant['name']) + + with sql.transaction() as session: + 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 + return tenant_ref.to_dict(include_extra_dict=True) + + @sql.handle_conflicts(conflict_type='project') + def delete_project(self, tenant_id): + with sql.transaction() as session: + tenant_ref = self._get_project(session, tenant_id) + session.delete(tenant_ref) + + # domain crud + + @sql.handle_conflicts(conflict_type='domain') + def create_domain(self, domain_id, domain): + with sql.transaction() as session: + ref = Domain.from_dict(domain) + session.add(ref) + return ref.to_dict() + + @sql.truncated + def list_domains(self, hints): + with sql.transaction() as session: + query = session.query(Domain) + refs = sql.filter_limit_query(Domain, query, hints) + return [ref.to_dict() for ref in refs] + + def list_domains_from_ids(self, ids): + if not ids: + return [] + else: + with sql.transaction() as session: + query = session.query(Domain) + query = query.filter(Domain.id.in_(ids)) + domain_refs = query.all() + return [domain_ref.to_dict() for domain_ref in domain_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): + with sql.transaction() as session: + return self._get_domain(session, domain_id).to_dict() + + def get_domain_by_name(self, domain_name): + with sql.transaction() as 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(conflict_type='domain') + def update_domain(self, domain_id, domain): + with sql.transaction() as session: + 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 + return ref.to_dict() + + def delete_domain(self, domain_id): + with sql.transaction() as session: + ref = self._get_domain(session, domain_id) + session.delete(ref) + + +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), nullable=False) + enabled = sql.Column(sql.Boolean, default=True, nullable=False) + extra = sql.Column(sql.JsonBlob()) + __table_args__ = (sql.UniqueConstraint('name'), {}) + + +class Project(sql.ModelBase, sql.DictBase): + __tablename__ = 'project' + attributes = ['id', 'name', 'domain_id', 'description', 'enabled', + 'parent_id'] + 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()) + parent_id = sql.Column(sql.String(64), sql.ForeignKey('project.id')) + # Unique constraint across two columns to create the separation + # rather than just only 'name' being unique + __table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {}) |