aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/resource
diff options
context:
space:
mode:
authorRHE <rebirthmonkey@gmail.com>2017-11-24 13:54:26 +0100
committerRHE <rebirthmonkey@gmail.com>2017-11-24 13:54:26 +0100
commit920a49cfa055733d575282973e23558c33087a4a (patch)
treed371dab34efa5028600dad2e7ca58063626e7ba4 /keystone-moon/keystone/resource
parentef3eefca70d8abb4a00dafb9419ad32738e934b2 (diff)
remove keystone-moon
Change-Id: I80d7c9b669f19d5f6607e162de8e0e55c2f80fdd Signed-off-by: RHE <rebirthmonkey@gmail.com>
Diffstat (limited to 'keystone-moon/keystone/resource')
-rw-r--r--keystone-moon/keystone/resource/V8_backends/__init__.py0
-rw-r--r--keystone-moon/keystone/resource/V8_backends/sql.py260
-rw-r--r--keystone-moon/keystone/resource/__init__.py14
-rw-r--r--keystone-moon/keystone/resource/backends/__init__.py0
-rw-r--r--keystone-moon/keystone/resource/backends/ldap.py217
-rw-r--r--keystone-moon/keystone/resource/backends/sql.py267
-rw-r--r--keystone-moon/keystone/resource/config_backends/__init__.py0
-rw-r--r--keystone-moon/keystone/resource/config_backends/sql.py152
-rw-r--r--keystone-moon/keystone/resource/controllers.py334
-rw-r--r--keystone-moon/keystone/resource/core.py2161
-rw-r--r--keystone-moon/keystone/resource/routers.py125
-rw-r--r--keystone-moon/keystone/resource/schema.py74
12 files changed, 0 insertions, 3604 deletions
diff --git a/keystone-moon/keystone/resource/V8_backends/__init__.py b/keystone-moon/keystone/resource/V8_backends/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/keystone-moon/keystone/resource/V8_backends/__init__.py
+++ /dev/null
diff --git a/keystone-moon/keystone/resource/V8_backends/sql.py b/keystone-moon/keystone/resource/V8_backends/sql.py
deleted file mode 100644
index 6c9b7912..00000000
--- a/keystone-moon/keystone/resource/V8_backends/sql.py
+++ /dev/null
@@ -1,260 +0,0 @@
-# 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_log import log
-
-from keystone.common import clean
-from keystone.common import driver_hints
-from keystone.common import sql
-from keystone import exception
-from keystone.i18n import _LE
-from keystone import resource as keystone_resource
-
-
-LOG = log.getLogger(__name__)
-
-
-class Resource(keystone_resource.ResourceDriverV8):
-
- def default_assignment_driver(self):
- return 'sql'
-
- 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.session_for_read() as session:
- return self._get_project(session, tenant_id).to_dict()
-
- def get_project_by_name(self, tenant_name, domain_id):
- with sql.session_for_read() 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()
-
- @driver_hints.truncated
- def list_projects(self, hints):
- with sql.session_for_read() 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.session_for_read() 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.session_for_read() 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.session_for_read() 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.session_for_read() as session:
- 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.update(children_ids)
- subtree += children
- children = self._get_children(session, children_ids)
- return subtree
-
- def list_project_parents(self, project_id):
- with sql.session_for_read() 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.session_for_read() 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.session_for_write() 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.session_for_write() 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.session_for_write() 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.session_for_write() as session:
- ref = Domain.from_dict(domain)
- session.add(ref)
- return ref.to_dict()
-
- @driver_hints.truncated
- def list_domains(self, hints):
- with sql.session_for_read() 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.session_for_read() 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.session_for_read() as session:
- return self._get_domain(session, domain_id).to_dict()
-
- def get_domain_by_name(self, domain_name):
- with sql.session_for_read() 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.session_for_write() 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.session_for_write() 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', 'is_domain']
- 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'))
- is_domain = sql.Column(sql.Boolean, default=False, nullable=False,
- server_default='0')
- # Unique constraint across two columns to create the separation
- # rather than just only 'name' being unique
- __table_args__ = (sql.UniqueConstraint('domain_id', 'name'),)
diff --git a/keystone-moon/keystone/resource/__init__.py b/keystone-moon/keystone/resource/__init__.py
deleted file mode 100644
index 7f879f4b..00000000
--- a/keystone-moon/keystone/resource/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# 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.resource import controllers # noqa
-from keystone.resource.core import * # noqa
diff --git a/keystone-moon/keystone/resource/backends/__init__.py b/keystone-moon/keystone/resource/backends/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/keystone-moon/keystone/resource/backends/__init__.py
+++ /dev/null
diff --git a/keystone-moon/keystone/resource/backends/ldap.py b/keystone-moon/keystone/resource/backends/ldap.py
deleted file mode 100644
index 566adc5d..00000000
--- a/keystone-moon/keystone/resource/backends/ldap.py
+++ /dev/null
@@ -1,217 +0,0 @@
-# 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 oslo_log import versionutils
-
-from keystone.common 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.ResourceDriverV8):
- @versionutils.deprecated(
- versionutils.deprecated.LIBERTY,
- remove_in=+1,
- what='ldap resource')
- 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 'ldap'
-
- 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 _set_default_is_domain_project(self, ref):
- if isinstance(ref, dict):
- return dict(ref, is_domain=False)
- elif isinstance(ref, list):
- return [self._set_default_is_domain_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 _validate_is_domain_field_is_false(self, ref):
- is_domain = ref.pop('is_domain', None)
- if is_domain:
- raise exception.ValidationError(_('LDAP does not support projects '
- 'with is_domain flag enabled'))
-
- def _set_default_attributes(self, project_ref):
- project_ref = self._set_default_domain(project_ref)
- project_ref = self._set_default_is_domain_project(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()
- self._validate_parent_project_is_none(tenant)
- self._validate_is_domain_field_is_false(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)
- self._validate_is_domain_field_is_false(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
deleted file mode 100644
index 39bb4f3b..00000000
--- a/keystone-moon/keystone/resource/backends/sql.py
+++ /dev/null
@@ -1,267 +0,0 @@
-# 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_log import log
-
-from keystone.common import clean
-from keystone.common import driver_hints
-from keystone.common import sql
-from keystone import exception
-from keystone.i18n import _LE, _LW
-from keystone import resource as keystone_resource
-
-
-LOG = log.getLogger(__name__)
-
-
-class Resource(keystone_resource.ResourceDriverV9):
-
- def default_assignment_driver(self):
- return 'sql'
-
- def _encode_domain_id(self, ref):
- if 'domain_id' in ref and ref['domain_id'] is None:
- new_ref = ref.copy()
- new_ref['domain_id'] = keystone_resource.NULL_DOMAIN_ID
- return new_ref
- else:
- return ref
-
- def _is_hidden_ref(self, ref):
- return ref.id == keystone_resource.NULL_DOMAIN_ID
-
- def _get_project(self, session, project_id):
- project_ref = session.query(Project).get(project_id)
- if project_ref is None or self._is_hidden_ref(project_ref):
- raise exception.ProjectNotFound(project_id=project_id)
- return project_ref
-
- def get_project(self, project_id):
- with sql.session_for_read() as session:
- return self._get_project(session, project_id).to_dict()
-
- def get_project_by_name(self, project_name, domain_id):
- with sql.session_for_read() as session:
- query = session.query(Project)
- query = query.filter_by(name=project_name)
- if domain_id is None:
- query = query.filter_by(
- domain_id=keystone_resource.NULL_DOMAIN_ID)
- else:
- query = query.filter_by(domain_id=domain_id)
- try:
- project_ref = query.one()
- except sql.NotFound:
- raise exception.ProjectNotFound(project_id=project_name)
-
- if self._is_hidden_ref(project_ref):
- raise exception.ProjectNotFound(project_id=project_name)
- return project_ref.to_dict()
-
- @driver_hints.truncated
- def list_projects(self, hints):
- # If there is a filter on domain_id and the value is None, then to
- # ensure that the sql filtering works correctly, we need to patch
- # the value to be NULL_DOMAIN_ID. This is safe to do here since we
- # know we are able to satisfy any filter of this type in the call to
- # filter_limit_query() below, which will remove the filter from the
- # hints (hence ensuring our substitution is not exposed to the caller).
- for f in hints.filters:
- if (f['name'] == 'domain_id' and f['value'] is None):
- f['value'] = keystone_resource.NULL_DOMAIN_ID
- with sql.session_for_read() 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
- if not self._is_hidden_ref(project_ref)]
-
- def list_projects_from_ids(self, ids):
- if not ids:
- return []
- else:
- with sql.session_for_read() as session:
- query = session.query(Project)
- query = query.filter(Project.id.in_(ids))
- return [project_ref.to_dict() for project_ref in query.all()
- if not self._is_hidden_ref(project_ref)]
-
- def list_project_ids_from_domain_ids(self, domain_ids):
- if not domain_ids:
- return []
- else:
- with sql.session_for_read() as session:
- query = session.query(Project.id)
- query = (
- query.filter(Project.domain_id.in_(domain_ids)))
- return [x.id for x in query.all()
- if not self._is_hidden_ref(x)]
-
- def list_projects_in_domain(self, domain_id):
- with sql.session_for_read() as session:
- try:
- self._get_project(session, domain_id)
- except exception.ProjectNotFound:
- raise exception.DomainNotFound(domain_id=domain_id)
- query = session.query(Project)
- project_refs = query.filter(Project.domain_id == domain_id)
- return [project_ref.to_dict() for project_ref in project_refs]
-
- def list_projects_acting_as_domain(self, hints):
- hints.add_filter('is_domain', True)
- return self.list_projects(hints)
-
- def _get_children(self, session, project_ids, domain_id=None):
- 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.session_for_read() as session:
- 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.update(children_ids)
- subtree += children
- children = self._get_children(session, children_ids)
- return subtree
-
- def list_project_parents(self, project_id):
- with sql.session_for_read() 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.session_for_read() 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, project_id, project):
- project['name'] = clean.project_name(project['name'])
- new_project = self._encode_domain_id(project)
- with sql.session_for_write() as session:
- project_ref = Project.from_dict(new_project)
- session.add(project_ref)
- return project_ref.to_dict()
-
- @sql.handle_conflicts(conflict_type='project')
- def update_project(self, project_id, project):
- if 'name' in project:
- project['name'] = clean.project_name(project['name'])
-
- update_project = self._encode_domain_id(project)
- with sql.session_for_write() as session:
- project_ref = self._get_project(session, project_id)
- old_project_dict = project_ref.to_dict()
- for k in update_project:
- old_project_dict[k] = update_project[k]
- # When we read the old_project_dict, any "null" domain_id will have
- # been decoded, so we need to re-encode it
- old_project_dict = self._encode_domain_id(old_project_dict)
- new_project = Project.from_dict(old_project_dict)
- for attr in Project.attributes:
- if attr != 'id':
- setattr(project_ref, attr, getattr(new_project, attr))
- project_ref.extra = new_project.extra
- return project_ref.to_dict(include_extra_dict=True)
-
- @sql.handle_conflicts(conflict_type='project')
- def delete_project(self, project_id):
- with sql.session_for_write() as session:
- project_ref = self._get_project(session, project_id)
- session.delete(project_ref)
-
- @sql.handle_conflicts(conflict_type='project')
- def delete_projects_from_ids(self, project_ids):
- if not project_ids:
- return
- with sql.session_for_write() as session:
- query = session.query(Project).filter(Project.id.in_(
- project_ids))
- project_ids_from_bd = [p['id'] for p in query.all()]
- for project_id in project_ids:
- if (project_id not in project_ids_from_bd or
- project_id == keystone_resource.NULL_DOMAIN_ID):
- LOG.warning(_LW('Project %s does not exist and was not '
- 'deleted.') % project_id)
- query.delete(synchronize_session=False)
-
-
-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):
- # NOTE(henry-nash): From the manager and above perspective, the domain_id
- # is nullable. However, to ensure uniqueness in multi-process
- # configurations, it is better to still use the sql uniqueness constraint.
- # Since the support for a nullable component of a uniqueness constraint
- # across different sql databases is mixed, we instead store a special value
- # to represent null, as defined in NULL_DOMAIN_ID above.
-
- def to_dict(self, include_extra_dict=False):
- d = super(Project, self).to_dict(
- include_extra_dict=include_extra_dict)
- if d['domain_id'] == keystone_resource.NULL_DOMAIN_ID:
- d['domain_id'] = None
- return d
-
- __tablename__ = 'project'
- attributes = ['id', 'name', 'domain_id', 'description', 'enabled',
- 'parent_id', 'is_domain']
- 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('project.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'))
- is_domain = sql.Column(sql.Boolean, default=False, nullable=False,
- server_default='0')
- # Unique constraint across two columns to create the separation
- # rather than just only 'name' being unique
- __table_args__ = (sql.UniqueConstraint('domain_id', 'name'),)
diff --git a/keystone-moon/keystone/resource/config_backends/__init__.py b/keystone-moon/keystone/resource/config_backends/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/keystone-moon/keystone/resource/config_backends/__init__.py
+++ /dev/null
diff --git a/keystone-moon/keystone/resource/config_backends/sql.py b/keystone-moon/keystone/resource/config_backends/sql.py
deleted file mode 100644
index 6413becc..00000000
--- a/keystone-moon/keystone/resource/config_backends/sql.py
+++ /dev/null
@@ -1,152 +0,0 @@
-# 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.common import sql
-from keystone import exception
-from keystone.i18n import _
-from keystone import resource
-
-
-class WhiteListedConfig(sql.ModelBase, sql.ModelDictMixin):
- __tablename__ = 'whitelisted_config'
- domain_id = sql.Column(sql.String(64), primary_key=True)
- group = sql.Column(sql.String(255), primary_key=True)
- option = sql.Column(sql.String(255), primary_key=True)
- value = sql.Column(sql.JsonBlob(), nullable=False)
-
- def to_dict(self):
- d = super(WhiteListedConfig, self).to_dict()
- d.pop('domain_id')
- return d
-
-
-class SensitiveConfig(sql.ModelBase, sql.ModelDictMixin):
- __tablename__ = 'sensitive_config'
- domain_id = sql.Column(sql.String(64), primary_key=True)
- group = sql.Column(sql.String(255), primary_key=True)
- option = sql.Column(sql.String(255), primary_key=True)
- value = sql.Column(sql.JsonBlob(), nullable=False)
-
- def to_dict(self):
- d = super(SensitiveConfig, self).to_dict()
- d.pop('domain_id')
- return d
-
-
-class ConfigRegister(sql.ModelBase, sql.ModelDictMixin):
- __tablename__ = 'config_register'
- type = sql.Column(sql.String(64), primary_key=True)
- domain_id = sql.Column(sql.String(64), nullable=False)
-
-
-class DomainConfig(resource.DomainConfigDriverV8):
-
- def choose_table(self, sensitive):
- if sensitive:
- return SensitiveConfig
- else:
- return WhiteListedConfig
-
- @sql.handle_conflicts(conflict_type='domain_config')
- def create_config_option(self, domain_id, group, option, value,
- sensitive=False):
- with sql.session_for_write() as session:
- config_table = self.choose_table(sensitive)
- ref = config_table(domain_id=domain_id, group=group,
- option=option, value=value)
- session.add(ref)
- return ref.to_dict()
-
- def _get_config_option(self, session, domain_id, group, option, sensitive):
- try:
- config_table = self.choose_table(sensitive)
- ref = (session.query(config_table).
- filter_by(domain_id=domain_id, group=group,
- option=option).one())
- except sql.NotFound:
- msg = _('option %(option)s in group %(group)s') % {
- 'group': group, 'option': option}
- raise exception.DomainConfigNotFound(
- domain_id=domain_id, group_or_option=msg)
- return ref
-
- def get_config_option(self, domain_id, group, option, sensitive=False):
- with sql.session_for_read() as session:
- ref = self._get_config_option(session, domain_id, group, option,
- sensitive)
- return ref.to_dict()
-
- def list_config_options(self, domain_id, group=None, option=None,
- sensitive=False):
- with sql.session_for_read() as session:
- config_table = self.choose_table(sensitive)
- query = session.query(config_table)
- query = query.filter_by(domain_id=domain_id)
- if group:
- query = query.filter_by(group=group)
- if option:
- query = query.filter_by(option=option)
- return [ref.to_dict() for ref in query.all()]
-
- def update_config_option(self, domain_id, group, option, value,
- sensitive=False):
- with sql.session_for_write() as session:
- ref = self._get_config_option(session, domain_id, group, option,
- sensitive)
- ref.value = value
- return ref.to_dict()
-
- def delete_config_options(self, domain_id, group=None, option=None,
- sensitive=False):
- """Deletes config options that match the filter parameters.
-
- Since the public API is broken down into calls for delete in both the
- whitelisted and sensitive methods, we are silent at the driver level
- if there was nothing to delete.
-
- """
- with sql.session_for_write() as session:
- config_table = self.choose_table(sensitive)
- query = session.query(config_table)
- query = query.filter_by(domain_id=domain_id)
- if group:
- query = query.filter_by(group=group)
- if option:
- query = query.filter_by(option=option)
- query.delete(False)
-
- def obtain_registration(self, domain_id, type):
- try:
- with sql.session_for_write() as session:
- ref = ConfigRegister(type=type, domain_id=domain_id)
- session.add(ref)
- return True
- except sql.DBDuplicateEntry: # nosec
- # Continue on and return False to indicate failure.
- pass
- return False
-
- def read_registration(self, type):
- with sql.session_for_read() as session:
- ref = session.query(ConfigRegister).get(type)
- if not ref:
- raise exception.ConfigRegistrationNotFound()
- return ref.domain_id
-
- def release_registration(self, domain_id, type=None):
- """Silently delete anything registered for the domain specified."""
- with sql.session_for_write() as session:
- query = session.query(ConfigRegister)
- if type:
- query = query.filter_by(type=type)
- query = query.filter_by(domain_id=domain_id)
- query.delete(False)
diff --git a/keystone-moon/keystone/resource/controllers.py b/keystone-moon/keystone/resource/controllers.py
deleted file mode 100644
index 5cabe064..00000000
--- a/keystone-moon/keystone/resource/controllers.py
+++ /dev/null
@@ -1,334 +0,0 @@
-# Copyright 2013 Metacloud, Inc.
-# Copyright 2012 OpenStack Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-"""Workflow Logic the Resource service."""
-
-import uuid
-
-from oslo_config import cfg
-
-from keystone.common import controller
-from keystone.common import dependency
-from keystone.common import validation
-from keystone.common import wsgi
-from keystone import exception
-from keystone.i18n import _
-from keystone import notifications
-from keystone.resource import schema
-
-
-CONF = cfg.CONF
-
-
-@dependency.requires('resource_api')
-class Tenant(controller.V2Controller):
-
- @controller.v2_deprecated
- def get_all_projects(self, context, **kw):
- """Gets a list of all tenants for an admin user."""
- self.assert_admin(context)
-
- if 'name' in context['query_string']:
- return self._get_project_by_name(context['query_string']['name'])
-
- try:
- tenant_refs = self.resource_api.list_projects_in_domain(
- CONF.identity.default_domain_id)
- except exception.DomainNotFound:
- # If the default domain doesn't exist then there are no V2
- # projects.
- tenant_refs = []
- tenant_refs = [self.v3_to_v2_project(tenant_ref)
- for tenant_ref in tenant_refs
- if not tenant_ref.get('is_domain')]
- params = {
- 'limit': context['query_string'].get('limit'),
- 'marker': context['query_string'].get('marker'),
- }
- return self.format_project_list(tenant_refs, **params)
-
- def _assert_not_is_domain_project(self, project_id, project_ref=None):
- # Projects acting as a domain should not be visible via v2
- if not project_ref:
- project_ref = self.resource_api.get_project(project_id)
- if project_ref.get('is_domain'):
- raise exception.ProjectNotFound(project_id)
-
- @controller.v2_deprecated
- def get_project(self, context, tenant_id):
- # TODO(termie): this stuff should probably be moved to middleware
- self.assert_admin(context)
- ref = self.resource_api.get_project(tenant_id)
- self._assert_not_is_domain_project(tenant_id, ref)
- return {'tenant': self.v3_to_v2_project(ref)}
-
- def _get_project_by_name(self, tenant_name):
- # Projects acting as a domain should not be visible via v2
- ref = self.resource_api.get_project_by_name(
- tenant_name, CONF.identity.default_domain_id)
- self._assert_not_is_domain_project(ref['id'], ref)
- return {'tenant': self.v3_to_v2_project(ref)}
-
- # CRUD Extension
- @controller.v2_deprecated
- def create_project(self, context, tenant):
- tenant_ref = self._normalize_dict(tenant)
-
- if 'name' not in tenant_ref or not tenant_ref['name']:
- msg = _('Name field is required and cannot be empty')
- raise exception.ValidationError(message=msg)
-
- if 'is_domain' in tenant_ref:
- msg = _('The creation of projects acting as domains is not '
- 'allowed in v2.')
- raise exception.ValidationError(message=msg)
-
- self.assert_admin(context)
-
- self.resource_api.ensure_default_domain_exists()
-
- tenant_ref['id'] = tenant_ref.get('id', uuid.uuid4().hex)
- initiator = notifications._get_request_audit_info(context)
- tenant = self.resource_api.create_project(
- tenant_ref['id'],
- self._normalize_domain_id(context, tenant_ref),
- initiator)
- return {'tenant': self.v3_to_v2_project(tenant)}
-
- @controller.v2_deprecated
- def update_project(self, context, tenant_id, tenant):
- self.assert_admin(context)
- self._assert_not_is_domain_project(tenant_id)
- # Remove domain_id and is_domain if specified - a v2 api caller
- # should not be specifying that
- clean_tenant = tenant.copy()
- clean_tenant.pop('domain_id', None)
- clean_tenant.pop('is_domain', None)
- initiator = notifications._get_request_audit_info(context)
- tenant_ref = self.resource_api.update_project(
- tenant_id, clean_tenant, initiator)
- return {'tenant': self.v3_to_v2_project(tenant_ref)}
-
- @controller.v2_deprecated
- def delete_project(self, context, tenant_id):
- self.assert_admin(context)
- self._assert_not_is_domain_project(tenant_id)
- initiator = notifications._get_request_audit_info(context)
- self.resource_api.delete_project(tenant_id, initiator)
-
-
-@dependency.requires('resource_api')
-class DomainV3(controller.V3Controller):
- collection_name = 'domains'
- member_name = 'domain'
-
- def __init__(self):
- super(DomainV3, self).__init__()
- self.get_member_from_driver = self.resource_api.get_domain
-
- @controller.protected()
- @validation.validated(schema.domain_create, 'domain')
- def create_domain(self, context, domain):
- ref = self._assign_unique_id(self._normalize_dict(domain))
- initiator = notifications._get_request_audit_info(context)
- ref = self.resource_api.create_domain(ref['id'], ref, initiator)
- return DomainV3.wrap_member(context, ref)
-
- @controller.filterprotected('enabled', 'name')
- def list_domains(self, context, filters):
- hints = DomainV3.build_driver_hints(context, filters)
- refs = self.resource_api.list_domains(hints=hints)
- return DomainV3.wrap_collection(context, refs, hints=hints)
-
- @controller.protected()
- def get_domain(self, context, domain_id):
- ref = self.resource_api.get_domain(domain_id)
- return DomainV3.wrap_member(context, ref)
-
- @controller.protected()
- @validation.validated(schema.domain_update, 'domain')
- def update_domain(self, context, domain_id, domain):
- self._require_matching_id(domain_id, domain)
- initiator = notifications._get_request_audit_info(context)
- ref = self.resource_api.update_domain(domain_id, domain, initiator)
- return DomainV3.wrap_member(context, ref)
-
- @controller.protected()
- def delete_domain(self, context, domain_id):
- initiator = notifications._get_request_audit_info(context)
- return self.resource_api.delete_domain(domain_id, initiator)
-
-
-@dependency.requires('domain_config_api')
-@dependency.requires('resource_api')
-class DomainConfigV3(controller.V3Controller):
- member_name = 'config'
-
- @controller.protected()
- def create_domain_config(self, context, domain_id, config):
- self.resource_api.get_domain(domain_id)
- original_config = (
- self.domain_config_api.get_config_with_sensitive_info(domain_id))
- ref = self.domain_config_api.create_config(domain_id, config)
- if original_config:
- # Return status code 200, since config already existed
- return wsgi.render_response(body={self.member_name: ref})
- else:
- return wsgi.render_response(body={self.member_name: ref},
- status=('201', 'Created'))
-
- @controller.protected()
- def get_domain_config(self, context, domain_id, group=None, option=None):
- self.resource_api.get_domain(domain_id)
- ref = self.domain_config_api.get_config(domain_id, group, option)
- return {self.member_name: ref}
-
- @controller.protected()
- def update_domain_config(
- self, context, domain_id, config, group, option):
- self.resource_api.get_domain(domain_id)
- ref = self.domain_config_api.update_config(
- domain_id, config, group, option)
- return wsgi.render_response(body={self.member_name: ref})
-
- def update_domain_config_group(self, context, domain_id, group, config):
- self.resource_api.get_domain(domain_id)
- return self.update_domain_config(
- context, domain_id, config, group, option=None)
-
- def update_domain_config_only(self, context, domain_id, config):
- self.resource_api.get_domain(domain_id)
- return self.update_domain_config(
- context, domain_id, config, group=None, option=None)
-
- @controller.protected()
- def delete_domain_config(
- self, context, domain_id, group=None, option=None):
- self.resource_api.get_domain(domain_id)
- self.domain_config_api.delete_config(domain_id, group, option)
-
- @controller.protected()
- def get_domain_config_default(self, context, group=None, option=None):
- ref = self.domain_config_api.get_config_default(group, option)
- return {self.member_name: ref}
-
-
-@dependency.requires('resource_api')
-class ProjectV3(controller.V3Controller):
- collection_name = 'projects'
- member_name = 'project'
-
- def __init__(self):
- super(ProjectV3, self).__init__()
- self.get_member_from_driver = self.resource_api.get_project
-
- @controller.protected()
- @validation.validated(schema.project_create, 'project')
- def create_project(self, context, project):
- ref = self._assign_unique_id(self._normalize_dict(project))
-
- if not ref.get('is_domain'):
- ref = self._normalize_domain_id(context, ref)
- # Our API requires that you specify the location in the hierarchy
- # unambiguously. This could be by parent_id or, if it is a top level
- # project, just by providing a domain_id.
- if not ref.get('parent_id'):
- ref['parent_id'] = ref.get('domain_id')
-
- initiator = notifications._get_request_audit_info(context)
- try:
- ref = self.resource_api.create_project(ref['id'], ref,
- initiator=initiator)
- except (exception.DomainNotFound, exception.ProjectNotFound) as e:
- raise exception.ValidationError(e)
- return ProjectV3.wrap_member(context, ref)
-
- @controller.filterprotected('domain_id', 'enabled', 'name',
- 'parent_id', 'is_domain')
- def list_projects(self, context, filters):
- hints = ProjectV3.build_driver_hints(context, filters)
- # If 'is_domain' has not been included as a query, we default it to
- # False (which in query terms means '0'
- if 'is_domain' not in context['query_string']:
- hints.add_filter('is_domain', '0')
- refs = self.resource_api.list_projects(hints=hints)
- return ProjectV3.wrap_collection(context, refs, hints=hints)
-
- def _expand_project_ref(self, context, ref):
- params = context['query_string']
-
- parents_as_list = 'parents_as_list' in params and (
- self.query_filter_is_true(params['parents_as_list']))
- parents_as_ids = 'parents_as_ids' in params and (
- self.query_filter_is_true(params['parents_as_ids']))
-
- subtree_as_list = 'subtree_as_list' in params and (
- self.query_filter_is_true(params['subtree_as_list']))
- subtree_as_ids = 'subtree_as_ids' in params and (
- self.query_filter_is_true(params['subtree_as_ids']))
-
- # parents_as_list and parents_as_ids are mutually exclusive
- if parents_as_list and parents_as_ids:
- msg = _('Cannot use parents_as_list and parents_as_ids query '
- 'params at the same time.')
- raise exception.ValidationError(msg)
-
- # subtree_as_list and subtree_as_ids are mutually exclusive
- if subtree_as_list and subtree_as_ids:
- msg = _('Cannot use subtree_as_list and subtree_as_ids query '
- 'params at the same time.')
- raise exception.ValidationError(msg)
-
- user_id = self.get_auth_context(context).get('user_id')
-
- if parents_as_list:
- parents = self.resource_api.list_project_parents(
- ref['id'], user_id)
- ref['parents'] = [ProjectV3.wrap_member(context, p)
- for p in parents]
- elif parents_as_ids:
- ref['parents'] = self.resource_api.get_project_parents_as_ids(ref)
-
- if subtree_as_list:
- subtree = self.resource_api.list_projects_in_subtree(
- ref['id'], user_id)
- ref['subtree'] = [ProjectV3.wrap_member(context, p)
- for p in subtree]
- elif subtree_as_ids:
- ref['subtree'] = self.resource_api.get_projects_in_subtree_as_ids(
- ref['id'])
-
- @controller.protected()
- def get_project(self, context, project_id):
- ref = self.resource_api.get_project(project_id)
- self._expand_project_ref(context, ref)
- return ProjectV3.wrap_member(context, ref)
-
- @controller.protected()
- @validation.validated(schema.project_update, 'project')
- def update_project(self, context, project_id, project):
- self._require_matching_id(project_id, project)
- self._require_matching_domain_id(
- project_id, project, self.resource_api.get_project)
- initiator = notifications._get_request_audit_info(context)
- ref = self.resource_api.update_project(project_id, project,
- initiator=initiator)
- return ProjectV3.wrap_member(context, ref)
-
- @controller.protected()
- def delete_project(self, context, project_id):
- initiator = notifications._get_request_audit_info(context)
- return self.resource_api.delete_project(project_id,
- initiator=initiator)
diff --git a/keystone-moon/keystone/resource/core.py b/keystone-moon/keystone/resource/core.py
deleted file mode 100644
index f8d72e91..00000000
--- a/keystone-moon/keystone/resource/core.py
+++ /dev/null
@@ -1,2161 +0,0 @@
-# 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 Resource service."""
-
-import abc
-import copy
-
-from oslo_config import cfg
-from oslo_log import log
-from oslo_log import versionutils
-import six
-
-from keystone import assignment
-from keystone.common import cache
-from keystone.common import clean
-from keystone.common import dependency
-from keystone.common import driver_hints
-from keystone.common import manager
-from keystone.common import utils
-from keystone import exception
-from keystone.i18n import _, _LE, _LW
-from keystone import notifications
-
-
-CONF = cfg.CONF
-LOG = log.getLogger(__name__)
-MEMOIZE = cache.get_memoization_decorator(group='resource')
-
-
-def calc_default_domain():
- return {'description':
- (u'The default domain'),
- 'enabled': True,
- 'id': CONF.identity.default_domain_id,
- 'name': u'Default'}
-
-
-def _get_project_from_domain(domain_ref):
- """Creates a project ref from the provided domain ref."""
- project_ref = domain_ref.copy()
- project_ref['is_domain'] = True
- project_ref['domain_id'] = None
- project_ref['parent_id'] = None
-
- return project_ref
-
-
-@dependency.provider('resource_api')
-@dependency.requires('assignment_api', 'credential_api', 'domain_config_api',
- 'identity_api', 'revoke_api')
-class Manager(manager.Manager):
- """Default pivot point for the Resource backend.
-
- See :mod:`keystone.common.manager.Manager` for more details on how this
- dynamically calls the backend.
-
- """
-
- driver_namespace = 'keystone.resource'
-
- _DOMAIN = 'domain'
- _PROJECT = 'project'
-
- def __init__(self):
- # If there is a specific driver specified for resource, then use it.
- # Otherwise retrieve the driver type from the assignment driver.
- resource_driver = CONF.resource.driver
-
- if resource_driver is None:
- assignment_manager = dependency.get_provider('assignment_api')
- resource_driver = assignment_manager.default_resource_driver()
-
- super(Manager, self).__init__(resource_driver)
-
- # Make sure it is a driver version we support, and if it is a legacy
- # driver, then wrap it.
- if isinstance(self.driver, ResourceDriverV8):
- self.driver = V9ResourceWrapperForV8Driver(self.driver)
- elif not isinstance(self.driver, ResourceDriverV9):
- raise exception.UnsupportedDriverVersion(driver=resource_driver)
-
- def _get_hierarchy_depth(self, parents_list):
- return len(parents_list) + 1
-
- def _assert_max_hierarchy_depth(self, project_id, parents_list=None):
- if parents_list is None:
- parents_list = self.list_project_parents(project_id)
- # NOTE(henry-nash): In upgrading to a scenario where domains are
- # represented as projects acting as domains, we will effectively
- # increase the depth of any existing project hierarchy by one. To avoid
- # pushing any existing hierarchies over the limit, we add one to the
- # maximum depth allowed, as specified in the configuration file.
- max_depth = CONF.max_project_tree_depth + 1
- if self._get_hierarchy_depth(parents_list) > max_depth:
- raise exception.ForbiddenNotSecurity(
- _('Max hierarchy depth reached for %s branch.') % project_id)
-
- def _assert_is_domain_project_constraints(self, project_ref):
- """Enforces specific constraints of projects that act as domains
-
- Called when is_domain is true, this method ensures that:
-
- * multiple domains are enabled
- * the project name is not the reserved name for a federated domain
- * the project is a root project
-
- :raises keystone.exception.ValidationError: If one of the constraints
- was not satisfied.
- """
- if (not self.identity_api.multiple_domains_supported and
- project_ref['id'] != CONF.identity.default_domain_id):
- raise exception.ValidationError(
- message=_('Multiple domains are not supported'))
-
- self.assert_domain_not_federated(project_ref['id'], project_ref)
-
- if project_ref['parent_id']:
- raise exception.ValidationError(
- message=_('only root projects are allowed to act as '
- 'domains.'))
-
- def _assert_regular_project_constraints(self, project_ref):
- """Enforces regular project hierarchy constraints
-
- Called when is_domain is false. The project must contain a valid
- domain_id and parent_id. The goal of this method is to check
- that the domain_id specified is consistent with the domain of its
- parent.
-
- :raises keystone.exception.ValidationError: If one of the constraints
- was not satisfied.
- :raises keystone.exception.DomainNotFound: In case the domain is not
- found.
- """
- # Ensure domain_id is valid, and by inference will not be None.
- domain = self.get_domain(project_ref['domain_id'])
- parent_ref = self.get_project(project_ref['parent_id'])
-
- if parent_ref['is_domain']:
- if parent_ref['id'] != domain['id']:
- raise exception.ValidationError(
- message=_('Cannot create project, since its parent '
- '(%(domain_id)s) is acting as a domain, '
- 'but project\'s specified parent_id '
- '(%(parent_id)s) does not match '
- 'this domain_id.')
- % {'domain_id': domain['id'],
- 'parent_id': parent_ref['id']})
- else:
- parent_domain_id = parent_ref.get('domain_id')
- if parent_domain_id != domain['id']:
- raise exception.ValidationError(
- message=_('Cannot create project, since it specifies '
- 'its owner as domain %(domain_id)s, but '
- 'specifies a parent in a different domain '
- '(%(parent_domain_id)s).')
- % {'domain_id': domain['id'],
- 'parent_domain_id': parent_domain_id})
-
- def _enforce_project_constraints(self, project_ref):
- if project_ref.get('is_domain'):
- self._assert_is_domain_project_constraints(project_ref)
- else:
- self._assert_regular_project_constraints(project_ref)
- # The whole hierarchy (upwards) must be enabled
- parent_id = project_ref['parent_id']
- parents_list = self.list_project_parents(parent_id)
- parent_ref = self.get_project(parent_id)
- parents_list.append(parent_ref)
- for ref in parents_list:
- if not ref.get('enabled', True):
- raise exception.ValidationError(
- message=_('cannot create a project in a '
- 'branch containing a disabled '
- 'project: %s') % ref['id'])
-
- self._assert_max_hierarchy_depth(project_ref.get('parent_id'),
- parents_list)
-
- def _raise_reserved_character_exception(self, entity_type, name):
- msg = _('%(entity)s name cannot contain the following reserved '
- 'characters: %(chars)s')
- raise exception.ValidationError(
- message=msg % {
- 'entity': entity_type,
- 'chars': utils.list_url_unsafe_chars(name)
- })
-
- def _generate_project_name_conflict_msg(self, project):
- if project['is_domain']:
- return _('it is not permitted to have two projects '
- 'acting as domains with the same name: %s'
- ) % project['name']
- else:
- return _('it is not permitted to have two projects '
- 'within a domain with the same name : %s'
- ) % project['name']
-
- def create_project(self, project_id, project, initiator=None):
- project = project.copy()
-
- if (CONF.resource.project_name_url_safe != 'off' and
- utils.is_not_url_safe(project['name'])):
- self._raise_reserved_character_exception('Project',
- project['name'])
-
- project.setdefault('enabled', True)
- project['enabled'] = clean.project_enabled(project['enabled'])
- project.setdefault('description', '')
-
- # For regular projects, the controller will ensure we have a valid
- # domain_id. For projects acting as a domain, the project_id
- # is, effectively, the domain_id - and for such projects we don't
- # bother to store a copy of it in the domain_id attribute.
- project.setdefault('domain_id', None)
- project.setdefault('parent_id', None)
- if not project['parent_id']:
- project['parent_id'] = project['domain_id']
- project.setdefault('is_domain', False)
-
- self._enforce_project_constraints(project)
-
- # We leave enforcing name uniqueness to the underlying driver (instead
- # of doing it in code in the project_constraints above), so as to allow
- # this check to be done at the storage level, avoiding race conditions
- # in multi-process keystone configurations.
- try:
- ret = self.driver.create_project(project_id, project)
- except exception.Conflict:
- raise exception.Conflict(
- type='project',
- details=self._generate_project_name_conflict_msg(project))
-
- if project.get('is_domain'):
- notifications.Audit.created(self._DOMAIN, project_id, initiator)
- else:
- notifications.Audit.created(self._PROJECT, project_id, initiator)
- if MEMOIZE.should_cache(ret):
- self.get_project.set(ret, self, project_id)
- self.get_project_by_name.set(ret, self, ret['name'],
- ret['domain_id'])
- return ret
-
- def assert_domain_enabled(self, domain_id, domain=None):
- """Assert the Domain is enabled.
-
- :raise AssertionError: if domain is disabled.
- """
- if domain is None:
- domain = self.get_domain(domain_id)
- if not domain.get('enabled', True):
- raise AssertionError(_('Domain is disabled: %s') % domain_id)
-
- def assert_domain_not_federated(self, domain_id, domain):
- """Assert the Domain's name and id do not match the reserved keyword.
-
- Note that the reserved keyword is defined in the configuration file,
- by default, it is 'Federated', it is also case insensitive.
- If config's option is empty the default hardcoded value 'Federated'
- will be used.
-
- :raise AssertionError: if domain named match the value in the config.
-
- """
- # NOTE(marek-denis): We cannot create this attribute in the __init__ as
- # config values are always initialized to default value.
- federated_domain = CONF.federation.federated_domain_name.lower()
- if (domain.get('name') and domain['name'].lower() == federated_domain):
- raise AssertionError(_('Domain cannot be named %s')
- % domain['name'])
- if (domain_id.lower() == federated_domain):
- raise AssertionError(_('Domain cannot have ID %s')
- % domain_id)
-
- def assert_project_enabled(self, project_id, project=None):
- """Assert the project is enabled and its associated domain is enabled.
-
- :raise AssertionError: if the project or domain is disabled.
- """
- if project is None:
- project = self.get_project(project_id)
- # If it's a regular project (i.e. it has a domain_id), we need to make
- # sure the domain itself is not disabled
- if project['domain_id']:
- self.assert_domain_enabled(domain_id=project['domain_id'])
- if not project.get('enabled', True):
- raise AssertionError(_('Project is disabled: %s') % project_id)
-
- def _assert_all_parents_are_enabled(self, project_id):
- parents_list = self.list_project_parents(project_id)
- for project in parents_list:
- if not project.get('enabled', True):
- raise exception.ForbiddenNotSecurity(
- _('Cannot enable project %s since it has disabled '
- 'parents') % project_id)
-
- def _check_whole_subtree_is_disabled(self, project_id, subtree_list=None):
- if not subtree_list:
- subtree_list = self.list_projects_in_subtree(project_id)
- subtree_enabled = [ref.get('enabled', True) for ref in subtree_list]
- return (not any(subtree_enabled))
-
- def _update_project(self, project_id, project, initiator=None,
- cascade=False):
- # Use the driver directly to prevent using old cached value.
- original_project = self.driver.get_project(project_id)
- project = project.copy()
-
- if original_project['is_domain']:
- domain = self._get_domain_from_project(original_project)
- self.assert_domain_not_federated(project_id, domain)
- if 'enabled' in domain:
- domain['enabled'] = clean.domain_enabled(domain['enabled'])
- url_safe_option = CONF.resource.domain_name_url_safe
- exception_entity = 'Domain'
- else:
- url_safe_option = CONF.resource.project_name_url_safe
- exception_entity = 'Project'
-
- if (url_safe_option != 'off' and
- 'name' in project and
- project['name'] != original_project['name'] and
- utils.is_not_url_safe(project['name'])):
- self._raise_reserved_character_exception(exception_entity,
- project['name'])
-
- parent_id = original_project.get('parent_id')
- if 'parent_id' in project and project.get('parent_id') != parent_id:
- raise exception.ForbiddenNotSecurity(
- _('Update of `parent_id` is not allowed.'))
-
- if ('is_domain' in project and
- project['is_domain'] != original_project['is_domain']):
- raise exception.ValidationError(
- message=_('Update of `is_domain` is not allowed.'))
-
- update_domain = ('domain_id' in project and
- project['domain_id'] != original_project['domain_id'])
-
- # NOTE(htruta): Even if we are allowing domain_ids to be
- # modified (i.e. 'domain_id_immutable' is set False),
- # a project.domain_id can only be updated for root projects
- # that have no children. The update of domain_id of a project in
- # the middle of the hierarchy creates an inconsistent project
- # hierarchy.
- if update_domain:
- if original_project['is_domain']:
- raise exception.ValidationError(
- message=_('Update of domain_id of projects acting as '
- 'domains is not allowed.'))
- parent_project = (
- self.driver.get_project(original_project['parent_id']))
- is_root_project = parent_project['is_domain']
- if not is_root_project:
- raise exception.ValidationError(
- message=_('Update of domain_id is only allowed for '
- 'root projects.'))
- subtree_list = self.list_projects_in_subtree(project_id)
- if subtree_list:
- raise exception.ValidationError(
- message=_('Cannot update domain_id of a project that '
- 'has children.'))
- versionutils.report_deprecated_feature(
- LOG,
- _('update of domain_id is deprecated as of Mitaka '
- 'and will be removed in O.')
- )
-
- if 'enabled' in project:
- project['enabled'] = clean.project_enabled(project['enabled'])
-
- original_project_enabled = original_project.get('enabled', True)
- project_enabled = project.get('enabled', True)
- if not original_project_enabled and project_enabled:
- self._assert_all_parents_are_enabled(project_id)
- if original_project_enabled and not project_enabled:
- # NOTE(htruta): In order to disable a regular project, all its
- # children must already be disabled. However, to keep
- # compatibility with the existing domain behaviour, we allow a
- # project acting as a domain to be disabled irrespective of the
- # state of its children. Disabling a project acting as domain
- # effectively disables its children.
- if (not original_project.get('is_domain') and not cascade and not
- self._check_whole_subtree_is_disabled(project_id)):
- raise exception.ForbiddenNotSecurity(
- _('Cannot disable project %(project_id)s since its '
- 'subtree contains enabled projects.')
- % {'project_id': project_id})
-
- notifications.Audit.disabled(self._PROJECT, project_id,
- public=False)
- if cascade:
- self._only_allow_enabled_to_update_cascade(project,
- original_project)
- self._update_project_enabled_cascade(project_id, project_enabled)
-
- try:
- project['is_domain'] = (project.get('is_domain') or
- original_project['is_domain'])
- ret = self.driver.update_project(project_id, project)
- except exception.Conflict:
- raise exception.Conflict(
- type='project',
- details=self._generate_project_name_conflict_msg(project))
-
- notifications.Audit.updated(self._PROJECT, project_id, initiator)
- if original_project['is_domain']:
- notifications.Audit.updated(self._DOMAIN, project_id, initiator)
- # If the domain is being disabled, issue the disable notification
- # as well
- if original_project_enabled and not project_enabled:
- notifications.Audit.disabled(self._DOMAIN, project_id,
- public=False)
-
- self.get_project.invalidate(self, project_id)
- self.get_project_by_name.invalidate(self, original_project['name'],
- original_project['domain_id'])
-
- if ('domain_id' in project and
- project['domain_id'] != original_project['domain_id']):
- # If the project's domain_id has been updated, invalidate user
- # role assignments cache region, as it may be caching inherited
- # assignments from the old domain to the specified project
- assignment.COMPUTED_ASSIGNMENTS_REGION.invalidate()
-
- return ret
-
- def _only_allow_enabled_to_update_cascade(self, project, original_project):
- for attr in project:
- if attr != 'enabled':
- if project.get(attr) != original_project.get(attr):
- raise exception.ValidationError(
- message=_('Cascade update is only allowed for '
- 'enabled attribute.'))
-
- def _update_project_enabled_cascade(self, project_id, enabled):
- subtree = self.list_projects_in_subtree(project_id)
- # Update enabled only if different from original value
- subtree_to_update = [child for child in subtree
- if child['enabled'] != enabled]
- for child in subtree_to_update:
- child['enabled'] = enabled
-
- if not enabled:
- # Does not in fact disable the project, only emits a
- # notification that it was disabled. The actual disablement
- # is done in the next line.
- notifications.Audit.disabled(self._PROJECT, child['id'],
- public=False)
-
- self.driver.update_project(child['id'], child)
-
- def update_project(self, project_id, project, initiator=None,
- cascade=False):
- ret = self._update_project(project_id, project, initiator, cascade)
- if ret['is_domain']:
- self.get_domain.invalidate(self, project_id)
- self.get_domain_by_name.invalidate(self, ret['name'])
-
- return ret
-
- def _pre_delete_cleanup_project(self, project_id, project, initiator=None):
- project_user_ids = (
- self.assignment_api.list_user_ids_for_project(project_id))
- for user_id in project_user_ids:
- payload = {'user_id': user_id, 'project_id': project_id}
- notifications.Audit.internal(
- notifications.INVALIDATE_USER_PROJECT_TOKEN_PERSISTENCE,
- payload
- )
-
- def _post_delete_cleanup_project(self, project_id, project,
- initiator=None):
- self.assignment_api.delete_project_assignments(project_id)
- self.get_project.invalidate(self, project_id)
- self.get_project_by_name.invalidate(self, project['name'],
- project['domain_id'])
- self.credential_api.delete_credentials_for_project(project_id)
- notifications.Audit.deleted(self._PROJECT, project_id, initiator)
- # Invalidate user role assignments cache region, as it may
- # be caching role assignments where the target is
- # the specified project
- assignment.COMPUTED_ASSIGNMENTS_REGION.invalidate()
-
- def delete_project(self, project_id, initiator=None, cascade=False):
- project = self.driver.get_project(project_id)
- if project.get('is_domain'):
- self.delete_domain(project_id, initiator)
- else:
- self._delete_project(project_id, initiator, cascade)
-
- def _delete_project(self, project_id, initiator=None, cascade=False):
- # Use the driver directly to prevent using old cached value.
- project = self.driver.get_project(project_id)
- if project['is_domain'] and project['enabled']:
- raise exception.ValidationError(
- message=_('cannot delete an enabled project acting as a '
- 'domain. Please disable the project %s first.')
- % project.get('id'))
-
- if not self.is_leaf_project(project_id) and not cascade:
- raise exception.ForbiddenNotSecurity(
- _('Cannot delete the project %s since it is not a leaf in the '
- 'hierarchy. Use the cascade option if you want to delete a '
- 'whole subtree.')
- % project_id)
-
- if cascade:
- # Getting reversed project's subtrees list, i.e. from the leaves
- # to the root, so we do not break parent_id FK.
- subtree_list = self.list_projects_in_subtree(project_id)
- subtree_list.reverse()
- if not self._check_whole_subtree_is_disabled(
- project_id, subtree_list=subtree_list):
- raise exception.ForbiddenNotSecurity(
- _('Cannot delete project %(project_id)s since its subtree '
- 'contains enabled projects.')
- % {'project_id': project_id})
-
- project_list = subtree_list + [project]
- projects_ids = [x['id'] for x in project_list]
-
- for prj in project_list:
- self._pre_delete_cleanup_project(prj['id'], prj, initiator)
- ret = self.driver.delete_projects_from_ids(projects_ids)
- for prj in project_list:
- self._post_delete_cleanup_project(prj['id'], prj, initiator)
- else:
- self._pre_delete_cleanup_project(project_id, project, initiator)
- ret = self.driver.delete_project(project_id)
- self._post_delete_cleanup_project(project_id, project, initiator)
-
- return ret
-
- def _filter_projects_list(self, projects_list, user_id):
- user_projects = self.assignment_api.list_projects_for_user(user_id)
- user_projects_ids = set([proj['id'] for proj in user_projects])
- # Keep only the projects present in user_projects
- return [proj for proj in projects_list
- if proj['id'] in user_projects_ids]
-
- def _assert_valid_project_id(self, project_id):
- if project_id is None:
- msg = _('Project field is required and cannot be empty.')
- raise exception.ValidationError(message=msg)
- # Check if project_id exists
- self.get_project(project_id)
-
- def list_project_parents(self, project_id, user_id=None):
- self._assert_valid_project_id(project_id)
- parents = self.driver.list_project_parents(project_id)
- # If a user_id was provided, the returned list should be filtered
- # against the projects this user has access to.
- if user_id:
- parents = self._filter_projects_list(parents, user_id)
- return parents
-
- def _build_parents_as_ids_dict(self, project, parents_by_id):
- # NOTE(rodrigods): we don't rely in the order of the projects returned
- # by the list_project_parents() method. Thus, we create a project cache
- # (parents_by_id) in order to access each parent in constant time and
- # traverse up the hierarchy.
- def traverse_parents_hierarchy(project):
- parent_id = project.get('parent_id')
- if not parent_id:
- return None
-
- parent = parents_by_id[parent_id]
- return {parent_id: traverse_parents_hierarchy(parent)}
-
- return traverse_parents_hierarchy(project)
-
- def get_project_parents_as_ids(self, project):
- """Gets the IDs from the parents from a given project.
-
- The project IDs are returned as a structured dictionary traversing up
- the hierarchy to the top level project. For example, considering the
- following project hierarchy::
-
- A
- |
- +-B-+
- | |
- C D
-
- If we query for project C parents, the expected return is the following
- dictionary::
-
- 'parents': {
- B['id']: {
- A['id']: None
- }
- }
-
- """
- parents_list = self.list_project_parents(project['id'])
- parents_as_ids = self._build_parents_as_ids_dict(
- project, {proj['id']: proj for proj in parents_list})
- return parents_as_ids
-
- def list_projects_in_subtree(self, project_id, user_id=None):
- self._assert_valid_project_id(project_id)
- subtree = self.driver.list_projects_in_subtree(project_id)
- # If a user_id was provided, the returned list should be filtered
- # against the projects this user has access to.
- if user_id:
- subtree = self._filter_projects_list(subtree, user_id)
- return subtree
-
- def _build_subtree_as_ids_dict(self, project_id, subtree_by_parent):
- # NOTE(rodrigods): we perform a depth first search to construct the
- # dictionaries representing each level of the subtree hierarchy. In
- # order to improve this traversal performance, we create a cache of
- # projects (subtree_py_parent) that accesses in constant time the
- # direct children of a given project.
- def traverse_subtree_hierarchy(project_id):
- children = subtree_by_parent.get(project_id)
- if not children:
- return None
-
- children_ids = {}
- for child in children:
- children_ids[child['id']] = traverse_subtree_hierarchy(
- child['id'])
- return children_ids
-
- return traverse_subtree_hierarchy(project_id)
-
- def get_projects_in_subtree_as_ids(self, project_id):
- """Gets the IDs from the projects in the subtree from a given project.
-
- The project IDs are returned as a structured dictionary representing
- their hierarchy. For example, considering the following project
- hierarchy::
-
- A
- |
- +-B-+
- | |
- C D
-
- If we query for project A subtree, the expected return is the following
- dictionary::
-
- 'subtree': {
- B['id']: {
- C['id']: None,
- D['id']: None
- }
- }
-
- """
- def _projects_indexed_by_parent(projects_list):
- projects_by_parent = {}
- for proj in projects_list:
- parent_id = proj.get('parent_id')
- if parent_id:
- if parent_id in projects_by_parent:
- projects_by_parent[parent_id].append(proj)
- else:
- projects_by_parent[parent_id] = [proj]
- return projects_by_parent
-
- subtree_list = self.list_projects_in_subtree(project_id)
- subtree_as_ids = self._build_subtree_as_ids_dict(
- project_id, _projects_indexed_by_parent(subtree_list))
- return subtree_as_ids
-
- def list_domains_from_ids(self, domain_ids):
- """List domains for the provided list of ids.
-
- :param domain_ids: list of ids
-
- :returns: a list of domain_refs.
-
- This method is used internally by the assignment manager to bulk read
- a set of domains given their ids.
-
- """
- # Retrieve the projects acting as domains get their correspondent
- # domains
- projects = self.list_projects_from_ids(domain_ids)
- domains = [self._get_domain_from_project(project)
- for project in projects]
-
- return domains
-
- @MEMOIZE
- def get_domain(self, domain_id):
- try:
- # Retrieve the corresponding project that acts as a domain
- project = self.driver.get_project(domain_id)
- except exception.ProjectNotFound:
- raise exception.DomainNotFound(domain_id=domain_id)
-
- # Return its correspondent domain
- return self._get_domain_from_project(project)
-
- @MEMOIZE
- def get_domain_by_name(self, domain_name):
- try:
- # Retrieve the corresponding project that acts as a domain
- project = self.driver.get_project_by_name(domain_name,
- domain_id=None)
- except exception.ProjectNotFound:
- raise exception.DomainNotFound(domain_id=domain_name)
-
- # Return its correspondent domain
- return self._get_domain_from_project(project)
-
- def _get_domain_from_project(self, project_ref):
- """Creates a domain ref from a project ref.
-
- Based on the provided project ref, create a domain ref, so that the
- result can be returned in response to a domain API call.
- """
- if not project_ref['is_domain']:
- LOG.error(_LE('Asked to convert a non-domain project into a '
- 'domain - Domain: %(domain_id)s, Project ID: '
- '%(id)s, Project Name: %(project_name)s'),
- {'domain_id': project_ref['domain_id'],
- 'id': project_ref['id'],
- 'project_name': project_ref['name']})
- raise exception.DomainNotFound(domain_id=project_ref['id'])
-
- domain_ref = project_ref.copy()
- # As well as the project specific attributes that we need to remove,
- # there is an old compatibility issue in that update project (as well
- # as extracting an extra attributes), also includes a copy of the
- # actual extra dict as well - something that update domain does not do.
- for k in ['parent_id', 'domain_id', 'is_domain', 'extra']:
- domain_ref.pop(k, None)
-
- return domain_ref
-
- def create_domain(self, domain_id, domain, initiator=None):
- if (CONF.resource.domain_name_url_safe != 'off' and
- utils.is_not_url_safe(domain['name'])):
- self._raise_reserved_character_exception('Domain', domain['name'])
- project_from_domain = _get_project_from_domain(domain)
- is_domain_project = self.create_project(
- domain_id, project_from_domain, initiator)
-
- return self._get_domain_from_project(is_domain_project)
-
- @manager.response_truncated
- def list_domains(self, hints=None):
- projects = self.list_projects_acting_as_domain(hints)
- domains = [self._get_domain_from_project(project)
- for project in projects]
- return domains
-
- def update_domain(self, domain_id, domain, initiator=None):
- # TODO(henry-nash): We shouldn't have to check for the federated domain
- # here as well as _update_project, but currently our tests assume the
- # checks are done in a specific order. The tests should be refactored.
- self.assert_domain_not_federated(domain_id, domain)
- project = _get_project_from_domain(domain)
- try:
- original_domain = self.driver.get_project(domain_id)
- project = self._update_project(domain_id, project, initiator)
- except exception.ProjectNotFound:
- raise exception.DomainNotFound(domain_id=domain_id)
-
- domain_from_project = self._get_domain_from_project(project)
- self.get_domain.invalidate(self, domain_id)
- self.get_domain_by_name.invalidate(self, original_domain['name'])
-
- return domain_from_project
-
- def delete_domain(self, domain_id, initiator=None):
- # Use the driver directly to get the project that acts as a domain and
- # prevent using old cached value.
- try:
- domain = self.driver.get_project(domain_id)
- except exception.ProjectNotFound:
- raise exception.DomainNotFound(domain_id=domain_id)
-
- # To help avoid inadvertent deletes, we insist that the domain
- # has been previously disabled. This also prevents a user deleting
- # their own domain since, once it is disabled, they won't be able
- # to get a valid token to issue this delete.
- if domain['enabled']:
- raise exception.ForbiddenNotSecurity(
- _('Cannot delete a domain that is enabled, please disable it '
- 'first.'))
-
- self._delete_domain_contents(domain_id)
- self._delete_project(domain_id, initiator)
- # Delete any database stored domain config
- self.domain_config_api.delete_config_options(domain_id)
- self.domain_config_api.delete_config_options(domain_id, sensitive=True)
- self.domain_config_api.release_registration(domain_id)
- # TODO(henry-nash): Although the controller will ensure deletion of
- # all users & groups within the domain (which will cause all
- # assignments for those users/groups to also be deleted), there
- # could still be assignments on this domain for users/groups in
- # other domains - so we should delete these here by making a call
- # to the backend to delete all assignments for this domain.
- # (see Bug #1277847)
- notifications.Audit.deleted(self._DOMAIN, domain_id, initiator)
- self.get_domain.invalidate(self, domain_id)
- self.get_domain_by_name.invalidate(self, domain['name'])
-
- # Invalidate user role assignments cache region, as it may be caching
- # role assignments where the target is the specified domain
- assignment.COMPUTED_ASSIGNMENTS_REGION.invalidate()
-
- def _delete_domain_contents(self, domain_id):
- """Delete the contents of a domain.
-
- Before we delete a domain, we need to remove all the entities
- that are owned by it, i.e. Projects. To do this we
- call the delete function for these entities, which are
- themselves responsible for deleting any credentials and role grants
- associated with them as well as revoking any relevant tokens.
-
- """
- def _delete_projects(project, projects, examined):
- if project['id'] in examined:
- msg = _LE('Circular reference or a repeated entry found '
- 'projects hierarchy - %(project_id)s.')
- LOG.error(msg, {'project_id': project['id']})
- return
-
- examined.add(project['id'])
- children = [proj for proj in projects
- if proj.get('parent_id') == project['id']]
- for proj in children:
- _delete_projects(proj, projects, examined)
-
- try:
- self.delete_project(project['id'], initiator=None)
- except exception.ProjectNotFound:
- LOG.debug(('Project %(projectid)s not found when '
- 'deleting domain contents for %(domainid)s, '
- 'continuing with cleanup.'),
- {'projectid': project['id'],
- 'domainid': domain_id})
-
- proj_refs = self.list_projects_in_domain(domain_id)
-
- # Deleting projects recursively
- roots = [x for x in proj_refs if x.get('parent_id') == domain_id]
- examined = set()
- for project in roots:
- _delete_projects(project, proj_refs, examined)
-
- @manager.response_truncated
- def list_projects(self, hints=None):
- return self.driver.list_projects(hints or driver_hints.Hints())
-
- # NOTE(henry-nash): list_projects_in_domain is actually an internal method
- # and not exposed via the API. Therefore there is no need to support
- # driver hints for it.
- def list_projects_in_domain(self, domain_id):
- return self.driver.list_projects_in_domain(domain_id)
-
- def list_projects_acting_as_domain(self, hints=None):
- return self.driver.list_projects_acting_as_domain(
- hints or driver_hints.Hints())
-
- @MEMOIZE
- def get_project(self, project_id):
- return self.driver.get_project(project_id)
-
- @MEMOIZE
- def get_project_by_name(self, project_name, domain_id):
- return self.driver.get_project_by_name(project_name, domain_id)
-
- def ensure_default_domain_exists(self):
- """Creates the default domain if it doesn't exist.
-
- This is only used for the v2 API and can go away when V2 does.
-
- """
- try:
- default_domain_attrs = {
- 'name': 'Default',
- 'id': CONF.identity.default_domain_id,
- 'description': 'Domain created automatically to support V2.0 '
- 'operations.',
- }
- self.create_domain(CONF.identity.default_domain_id,
- default_domain_attrs)
- LOG.warning(_LW(
- 'The default domain was created automatically to contain V2 '
- 'resources. This is deprecated in the M release and will not '
- 'be supported in the O release. Create the default domain '
- 'manually or use the keystone-manage bootstrap command.'))
- except exception.Conflict:
- LOG.debug('The default domain already exists.')
- except Exception:
- LOG.error(_LE('Failed to create the default domain.'))
- raise
-
-
-# The ResourceDriverBase class is the set of driver methods from earlier
-# drivers that we still support, that have not been removed or modified. This
-# class is then used to created the augmented V8 and V9 version abstract driver
-# classes, without having to duplicate a lot of abstract method signatures.
-# If you remove a method from V9, then move the abstract methods from this Base
-# class to the V8 class. Do not modify any of the method signatures in the Base
-# class - changes should only be made in the V8 and subsequent classes.
-
-# Starting with V9, some drivers use a special value to represent a domain_id
-# of None. See comment in Project class of resource/backends/sql.py for more
-# details.
-NULL_DOMAIN_ID = '<<keystone.domain.root>>'
-
-
-@six.add_metaclass(abc.ABCMeta)
-class ResourceDriverBase(object):
-
- def _get_list_limit(self):
- return CONF.resource.list_limit or CONF.list_limit
-
- # project crud
- @abc.abstractmethod
- def list_projects(self, hints):
- """List projects in the system.
-
- :param hints: filter hints which the driver should
- implement if at all possible.
-
- :returns: a list of project_refs or an empty list.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_projects_from_ids(self, project_ids):
- """List projects for the provided list of ids.
-
- :param project_ids: list of ids
-
- :returns: a list of project_refs.
-
- This method is used internally by the assignment manager to bulk read
- a set of projects given their ids.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_project_ids_from_domain_ids(self, domain_ids):
- """List project ids for the provided list of domain ids.
-
- :param domain_ids: list of domain ids
-
- :returns: a list of project ids owned by the specified domain ids.
-
- This method is used internally by the assignment manager to bulk read
- a set of project ids given a list of domain ids.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_projects_in_domain(self, domain_id):
- """List projects in the domain.
-
- :param domain_id: the driver MUST only return projects
- within this domain.
-
- :returns: a list of project_refs or an empty list.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def get_project(self, project_id):
- """Get a project by ID.
-
- :returns: project_ref
- :raises keystone.exception.ProjectNotFound: if project_id does not
- exist
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def update_project(self, project_id, project):
- """Updates an existing project.
-
- :raises keystone.exception.ProjectNotFound: if project_id does not
- exist
- :raises keystone.exception.Conflict: if project name already exists
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def delete_project(self, project_id):
- """Deletes an existing project.
-
- :raises keystone.exception.ProjectNotFound: if project_id does not
- exist
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_project_parents(self, project_id):
- """List all parents from a project by its ID.
-
- :param project_id: the driver will list the parents of this
- project.
-
- :returns: a list of project_refs or an empty list.
- :raises keystone.exception.ProjectNotFound: if project_id does not
- exist
-
- """
- raise exception.NotImplemented()
-
- @abc.abstractmethod
- def list_projects_in_subtree(self, project_id):
- """List all projects in the subtree of a given project.
-
- :param project_id: the driver will get the subtree under
- this project.
-
- :returns: a list of project_refs or an empty list
- :raises keystone.exception.ProjectNotFound: if project_id does not
- exist
-
- """
- raise exception.NotImplemented()
-
- @abc.abstractmethod
- def is_leaf_project(self, project_id):
- """Checks if a project is a leaf in the hierarchy.
-
- :param project_id: the driver will check if this project
- is a leaf in the hierarchy.
-
- :raises keystone.exception.ProjectNotFound: if project_id does not
- exist
-
- """
- raise exception.NotImplemented()
-
- def _validate_default_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_default_domain_id(domain_id)
- return ref
-
- def _validate_default_domain_id(self, domain_id):
- """Validate that the domain ID belongs to the default domain."""
- if domain_id != CONF.identity.default_domain_id:
- raise exception.DomainNotFound(domain_id=domain_id)
-
-
-class ResourceDriverV8(ResourceDriverBase):
- """Removed or redefined methods from V8.
-
- Move the abstract methods of any methods removed or modified in later
- versions of the driver from ResourceDriverBase to here. We maintain this
- so that legacy drivers, which will be a subclass of ResourceDriverV8, can
- still reference them.
-
- """
-
- @abc.abstractmethod
- def create_project(self, tenant_id, tenant):
- """Creates a new project.
-
- :param tenant_id: This parameter can be ignored.
- :param dict tenant: The new project
-
- Project schema::
-
- type: object
- properties:
- id:
- type: string
- name:
- type: string
- domain_id:
- type: string
- description:
- type: string
- enabled:
- type: boolean
- parent_id:
- type: string
- is_domain:
- type: boolean
- required: [id, name, domain_id]
- additionalProperties: true
-
- If project doesn't match the schema the behavior is undefined.
-
- The driver can impose requirements such as the maximum length of a
- field. If these requirements are not met the behavior is undefined.
-
- :raises keystone.exception.Conflict: if the project id already exists
- or the name already exists for the domain_id.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def get_project_by_name(self, tenant_name, domain_id):
- """Get a tenant by name.
-
- :returns: tenant_ref
- :raises keystone.exception.ProjectNotFound: if a project with the
- tenant_name does not exist within the domain
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- # Domain management functions for backends that only allow a single
- # domain. Although we no longer use this, a custom legacy driver might
- # have made use of it, so keep it here in case.
- def _set_default_domain(self, ref):
- """If the domain ID has not been set, set it to the default."""
- if isinstance(ref, dict):
- if 'domain_id' not in ref:
- 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))
-
- # domain crud
- @abc.abstractmethod
- def create_domain(self, domain_id, domain):
- """Creates a new domain.
-
- :raises keystone.exception.Conflict: if the domain_id or domain name
- already exists
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_domains(self, hints):
- """List domains in the system.
-
- :param hints: filter hints which the driver should
- implement if at all possible.
-
- :returns: a list of domain_refs or an empty list.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_domains_from_ids(self, domain_ids):
- """List domains for the provided list of ids.
-
- :param domain_ids: list of ids
-
- :returns: a list of domain_refs.
-
- This method is used internally by the assignment manager to bulk read
- a set of domains given their ids.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def get_domain(self, domain_id):
- """Get a domain by ID.
-
- :returns: domain_ref
- :raises keystone.exception.DomainNotFound: if domain_id does not exist
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def get_domain_by_name(self, domain_name):
- """Get a domain by name.
-
- :returns: domain_ref
- :raises keystone.exception.DomainNotFound: if domain_name does not
- exist
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def update_domain(self, domain_id, domain):
- """Updates an existing domain.
-
- :raises keystone.exception.DomainNotFound: if domain_id does not exist
- :raises keystone.exception.Conflict: if domain name already exists
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def delete_domain(self, domain_id):
- """Deletes an existing domain.
-
- :raises keystone.exception.DomainNotFound: if domain_id does not exist
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
-
-class ResourceDriverV9(ResourceDriverBase):
- """New or redefined methods from V8.
-
- Add any new V9 abstract methods (or those with modified signatures) to
- this class.
-
- """
-
- @abc.abstractmethod
- def create_project(self, project_id, project):
- """Creates a new project.
-
- :param project_id: This parameter can be ignored.
- :param dict project: The new project
-
- Project schema::
-
- type: object
- properties:
- id:
- type: string
- name:
- type: string
- domain_id:
- type: [string, null]
- description:
- type: string
- enabled:
- type: boolean
- parent_id:
- type: string
- is_domain:
- type: boolean
- required: [id, name, domain_id]
- additionalProperties: true
-
- If the project doesn't match the schema the behavior is undefined.
-
- The driver can impose requirements such as the maximum length of a
- field. If these requirements are not met the behavior is undefined.
-
- :raises keystone.exception.Conflict: if the project id already exists
- or the name already exists for the domain_id.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def get_project_by_name(self, project_name, domain_id):
- """Get a project by name.
-
- :returns: project_ref
- :raises keystone.exception.ProjectNotFound: if a project with the
- project_name does not exist within the domain
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def delete_projects_from_ids(self, project_ids):
- """Deletes a given list of projects.
-
- Deletes a list of projects. Ensures no project on the list exists
- after it is successfully called. If an empty list is provided,
- the it is silently ignored. In addition, if a project ID in the list
- of project_ids is not found in the backend, no exception is raised,
- but a message is logged.
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_projects_acting_as_domain(self, hints):
- """List all projects acting as domains.
-
- :param hints: filter hints which the driver should
- implement if at all possible.
-
- :returns: a list of project_refs or an empty list.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
-
-class V9ResourceWrapperForV8Driver(ResourceDriverV9):
- """Wrapper class to supported a V8 legacy driver.
-
- In order to support legacy drivers without having to make the manager code
- driver-version aware, we wrap legacy drivers so that they look like the
- latest version. For the various changes made in a new driver, here are the
- actions needed in this wrapper:
-
- Method removed from new driver - remove the call-through method from this
- class, since the manager will no longer be
- calling it.
- Method signature (or meaning) changed - wrap the old method in a new
- signature here, and munge the input
- and output parameters accordingly.
- New method added to new driver - add a method to implement the new
- functionality here if possible. If that is
- not possible, then return NotImplemented,
- since we do not guarantee to support new
- functionality with legacy drivers.
-
- This wrapper contains the following support for newer manager code:
-
- - The current manager code expects domains to be represented as projects
- acting as domains, something that may not be possible in a legacy driver.
- Hence the wrapper will map any calls for projects acting as a domain back
- onto the driver domain methods. The caveat for this, is that this assumes
- that there can not be a clash between a project_id and a domain_id, in
- which case it may not be able to locate the correct entry.
-
- """
-
- @versionutils.deprecated(
- as_of=versionutils.deprecated.MITAKA,
- what='keystone.resource.ResourceDriverV8',
- in_favor_of='keystone.resource.ResourceDriverV9',
- remove_in=+2)
- def __init__(self, wrapped_driver):
- self.driver = wrapped_driver
-
- def _get_domain_from_project(self, project_ref):
- """Creates a domain ref from a project ref.
-
- Based on the provided project ref (or partial ref), creates a
- domain ref, so that the result can be passed to the driver
- domain methods.
- """
- domain_ref = project_ref.copy()
- for k in ['parent_id', 'domain_id', 'is_domain']:
- domain_ref.pop(k, None)
- return domain_ref
-
- def get_project_by_name(self, project_name, domain_id):
- if domain_id is None:
- try:
- domain_ref = self.driver.get_domain_by_name(project_name)
- return _get_project_from_domain(domain_ref)
- except exception.DomainNotFound:
- raise exception.ProjectNotFound(project_id=project_name)
- else:
- return self.driver.get_project_by_name(project_name, domain_id)
-
- def create_project(self, project_id, project):
- if project['is_domain']:
- new_domain = self._get_domain_from_project(project)
- domain_ref = self.driver.create_domain(project_id, new_domain)
- return _get_project_from_domain(domain_ref)
- else:
- return self.driver.create_project(project_id, project)
-
- def list_projects(self, hints):
- """List projects and/or domains.
-
- We use the hints filter to determine whether we are listing projects,
- domains or both.
-
- If the filter includes domain_id==None, then we should only list
- domains (convert to a project acting as a domain) since regular
- projcets always have a non-None value for domain_id.
-
- Likewise, if the filter includes domain_id==<non-None value>, then we
- should only list projects.
-
- If there is no domain_id filter, then we need to do a combained listing
- of domains and projects, converting domains to projects acting as a
- domain.
-
- """
- domain_listing_filter = None
- for f in hints.filters:
- if (f['name'] == 'domain_id'):
- domain_listing_filter = f
-
- if domain_listing_filter is not None:
- if domain_listing_filter['value'] is not None:
- proj_list = self.driver.list_projects(hints)
- else:
- domains = self.driver.list_domains(hints)
- proj_list = [_get_project_from_domain(p) for p in domains]
- hints.filters.remove(domain_listing_filter)
- return proj_list
- else:
- # No domain_id filter, so combine domains and projects. Although
- # we hand any remaining filters into each driver, since each filter
- # might need to be carried out more than once, we use copies of the
- # filters, allowing the original filters to be passed back up to
- # controller level where a final filter will occur.
- local_hints = copy.deepcopy(hints)
- proj_list = self.driver.list_projects(local_hints)
- local_hints = copy.deepcopy(hints)
- domains = self.driver.list_domains(local_hints)
- for domain in domains:
- proj_list.append(_get_project_from_domain(domain))
- return proj_list
-
- def list_projects_from_ids(self, project_ids):
- return [self.get_project(id) for id in project_ids]
-
- def list_project_ids_from_domain_ids(self, domain_ids):
- return self.driver.list_project_ids_from_domain_ids(domain_ids)
-
- def list_projects_in_domain(self, domain_id):
- return self.driver.list_projects_in_domain(domain_id)
-
- def get_project(self, project_id):
- try:
- domain_ref = self.driver.get_domain(project_id)
- return _get_project_from_domain(domain_ref)
- except exception.DomainNotFound:
- return self.driver.get_project(project_id)
-
- def _is_domain(self, project_id):
- ref = self.get_project(project_id)
- return ref.get('is_domain', False)
-
- def update_project(self, project_id, project):
- if self._is_domain(project_id):
- update_domain = self._get_domain_from_project(project)
- domain_ref = self.driver.update_domain(project_id, update_domain)
- return _get_project_from_domain(domain_ref)
- else:
- return self.driver.update_project(project_id, project)
-
- def delete_project(self, project_id):
- if self._is_domain(project_id):
- try:
- self.driver.delete_domain(project_id)
- except exception.DomainNotFound:
- raise exception.ProjectNotFound(project_id=project_id)
- else:
- self.driver.delete_project(project_id)
-
- def delete_projects_from_ids(self, project_ids):
- raise exception.NotImplemented() # pragma: no cover
-
- def list_project_parents(self, project_id):
- """List a project's ancestors.
-
- The current manager expects the ancestor tree to end with the project
- acting as the domain (since that's now the top of the tree), but a
- legacy driver will not have that top project in their projects table,
- since it's still in the domain table. Hence we lift the algorithm for
- traversing up the tree from the driver to here, so that our version of
- get_project() is called, which will fetch the "project" from the right
- table.
-
- """
- project = self.get_project(project_id)
- 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(project['parent_id'])
- parents.append(parent_project)
- project = parent_project
- return parents
-
- def list_projects_in_subtree(self, project_id):
- return self.driver.list_projects_in_subtree(project_id)
-
- def is_leaf_project(self, project_id):
- return self.driver.is_leaf_project(project_id)
-
- def list_projects_acting_as_domain(self, hints):
- refs = self.driver.list_domains(hints)
- return [_get_project_from_domain(p) for p in refs]
-
-
-Driver = manager.create_legacy_driver(ResourceDriverV8)
-
-
-MEMOIZE_CONFIG = cache.get_memoization_decorator(group='domain_config')
-
-
-@dependency.provider('domain_config_api')
-class DomainConfigManager(manager.Manager):
- """Default pivot point for the Domain Config backend."""
-
- # NOTE(henry-nash): In order for a config option to be stored in the
- # standard table, it must be explicitly whitelisted. Options marked as
- # sensitive are stored in a separate table. Attempting to store options
- # that are not listed as either whitelisted or sensitive will raise an
- # exception.
- #
- # Only those options that affect the domain-specific driver support in
- # the identity manager are supported.
-
- driver_namespace = 'keystone.resource.domain_config'
-
- whitelisted_options = {
- 'identity': ['driver', 'list_limit'],
- 'ldap': [
- 'url', 'user', 'suffix', 'use_dumb_member', 'dumb_member',
- 'allow_subtree_delete', 'query_scope', 'page_size',
- 'alias_dereferencing', 'debug_level', 'chase_referrals',
- 'user_tree_dn', 'user_filter', 'user_objectclass',
- 'user_id_attribute', 'user_name_attribute', 'user_mail_attribute',
- 'user_description_attribute', 'user_pass_attribute',
- 'user_enabled_attribute', 'user_enabled_invert',
- 'user_enabled_mask', 'user_enabled_default',
- 'user_attribute_ignore', 'user_default_project_id_attribute',
- 'user_allow_create', 'user_allow_update', 'user_allow_delete',
- 'user_enabled_emulation', 'user_enabled_emulation_dn',
- 'user_enabled_emulation_use_group_config',
- 'user_additional_attribute_mapping', 'group_tree_dn',
- 'group_filter', 'group_objectclass', 'group_id_attribute',
- 'group_name_attribute', 'group_member_attribute',
- 'group_desc_attribute', 'group_attribute_ignore',
- 'group_allow_create', 'group_allow_update', 'group_allow_delete',
- 'group_additional_attribute_mapping', 'tls_cacertfile',
- 'tls_cacertdir', 'use_tls', 'tls_req_cert', 'use_pool',
- 'pool_size', 'pool_retry_max', 'pool_retry_delay',
- 'pool_connection_timeout', 'pool_connection_lifetime',
- 'use_auth_pool', 'auth_pool_size', 'auth_pool_connection_lifetime'
- ]
- }
- sensitive_options = {
- 'identity': [],
- 'ldap': ['password']
- }
-
- def __init__(self):
- super(DomainConfigManager, self).__init__(CONF.domain_config.driver)
-
- def _assert_valid_config(self, config):
- """Ensure the options in the config are valid.
-
- This method is called to validate the request config in create and
- update manager calls.
-
- :param config: config structure being created or updated
-
- """
- # Something must be defined in the request
- if not config:
- raise exception.InvalidDomainConfig(
- reason=_('No options specified'))
-
- # Make sure the groups/options defined in config itself are valid
- for group in config:
- if (not config[group] or not
- isinstance(config[group], dict)):
- msg = _('The value of group %(group)s specified in the '
- 'config should be a dictionary of options') % {
- 'group': group}
- raise exception.InvalidDomainConfig(reason=msg)
- for option in config[group]:
- self._assert_valid_group_and_option(group, option)
-
- def _assert_valid_group_and_option(self, group, option):
- """Ensure the combination of group and option is valid.
-
- :param group: optional group name, if specified it must be one
- we support
- :param option: optional option name, if specified it must be one
- we support and a group must also be specified
-
- """
- if not group and not option:
- # For all calls, it's OK for neither to be defined, it means you
- # are operating on all config options for that domain.
- return
-
- if not group and option:
- # Our API structure should prevent this from ever happening, so if
- # it does, then this is coding error.
- msg = _('Option %(option)s found with no group specified while '
- 'checking domain configuration request') % {
- 'option': option}
- raise exception.UnexpectedError(exception=msg)
-
- if (group and group not in self.whitelisted_options and
- group not in self.sensitive_options):
- msg = _('Group %(group)s is not supported '
- 'for domain specific configurations') % {'group': group}
- raise exception.InvalidDomainConfig(reason=msg)
-
- if option:
- if (option not in self.whitelisted_options[group] and option not in
- self.sensitive_options[group]):
- msg = _('Option %(option)s in group %(group)s is not '
- 'supported for domain specific configurations') % {
- 'group': group, 'option': option}
- raise exception.InvalidDomainConfig(reason=msg)
-
- def _is_sensitive(self, group, option):
- return option in self.sensitive_options[group]
-
- def _config_to_list(self, config):
- """Build whitelisted and sensitive lists for use by backend drivers."""
- whitelisted = []
- sensitive = []
- for group in config:
- for option in config[group]:
- the_list = (sensitive if self._is_sensitive(group, option)
- else whitelisted)
- the_list.append({
- 'group': group, 'option': option,
- 'value': config[group][option]})
-
- return whitelisted, sensitive
-
- def _list_to_config(self, whitelisted, sensitive=None, req_option=None):
- """Build config dict from a list of option dicts.
-
- :param whitelisted: list of dicts containing options and their groups,
- this has already been filtered to only contain
- those options to include in the output.
- :param sensitive: list of dicts containing sensitive options and their
- groups, this has already been filtered to only
- contain those options to include in the output.
- :param req_option: the individual option requested
-
- :returns: a config dict, including sensitive if specified
-
- """
- the_list = whitelisted + (sensitive or [])
- if not the_list:
- return {}
-
- if req_option:
- # The request was specific to an individual option, so
- # no need to include the group in the output. We first check that
- # there is only one option in the answer (and that it's the right
- # one) - if not, something has gone wrong and we raise an error
- if len(the_list) > 1 or the_list[0]['option'] != req_option:
- LOG.error(_LE('Unexpected results in response for domain '
- 'config - %(count)s responses, first option is '
- '%(option)s, expected option %(expected)s'),
- {'count': len(the_list), 'option': list[0]['option'],
- 'expected': req_option})
- raise exception.UnexpectedError(
- _('An unexpected error occurred when retrieving domain '
- 'configs'))
- return {the_list[0]['option']: the_list[0]['value']}
-
- config = {}
- for option in the_list:
- config.setdefault(option['group'], {})
- config[option['group']][option['option']] = option['value']
-
- return config
-
- def create_config(self, domain_id, config):
- """Create config for a domain
-
- :param domain_id: the domain in question
- :param config: the dict of config groups/options to assign to the
- domain
-
- Creates a new config, overwriting any previous config (no Conflict
- error will be generated).
-
- :returns: a dict of group dicts containing the options, with any that
- are sensitive removed
- :raises keystone.exception.InvalidDomainConfig: when the config
- contains options we do not support
-
- """
- self._assert_valid_config(config)
- whitelisted, sensitive = self._config_to_list(config)
- # Delete any existing config
- self.delete_config_options(domain_id)
- self.delete_config_options(domain_id, sensitive=True)
- # ...and create the new one
- for option in whitelisted:
- self.create_config_option(
- domain_id, option['group'], option['option'], option['value'])
- for option in sensitive:
- self.create_config_option(
- domain_id, option['group'], option['option'], option['value'],
- sensitive=True)
- # Since we are caching on the full substituted config, we just
- # invalidate here, rather than try and create the right result to
- # cache.
- self.get_config_with_sensitive_info.invalidate(self, domain_id)
- return self._list_to_config(whitelisted)
-
- def get_config(self, domain_id, group=None, option=None):
- """Get config, or partial config, for a domain
-
- :param domain_id: the domain in question
- :param group: an optional specific group of options
- :param option: an optional specific option within the group
-
- :returns: a dict of group dicts containing the whitelisted options,
- filtered by group and option specified
- :raises keystone.exception.DomainConfigNotFound: when no config found
- that matches domain_id, group and option specified
- :raises keystone.exception.InvalidDomainConfig: when the config
- and group/option parameters specify an option we do not
- support
-
- An example response::
-
- {
- 'ldap': {
- 'url': 'myurl'
- 'user_tree_dn': 'OU=myou'},
- 'identity': {
- 'driver': 'ldap'}
-
- }
-
- """
- self._assert_valid_group_and_option(group, option)
- whitelisted = self.list_config_options(domain_id, group, option)
- if whitelisted:
- return self._list_to_config(whitelisted, req_option=option)
-
- if option:
- msg = _('option %(option)s in group %(group)s') % {
- 'group': group, 'option': option}
- elif group:
- msg = _('group %(group)s') % {'group': group}
- else:
- msg = _('any options')
- raise exception.DomainConfigNotFound(
- domain_id=domain_id, group_or_option=msg)
-
- def update_config(self, domain_id, config, group=None, option=None):
- """Update config, or partial config, for a domain
-
- :param domain_id: the domain in question
- :param config: the config dict containing and groups/options being
- updated
- :param group: an optional specific group of options, which if specified
- must appear in config, with no other groups
- :param option: an optional specific option within the group, which if
- specified must appear in config, with no other options
-
- The contents of the supplied config will be merged with the existing
- config for this domain, updating or creating new options if these did
- not previously exist. If group or option is specified, then the update
- will be limited to those specified items and the inclusion of other
- options in the supplied config will raise an exception, as will the
- situation when those options do not already exist in the current
- config.
-
- :returns: a dict of groups containing all whitelisted options
- :raises keystone.exception.InvalidDomainConfig: when the config
- and group/option parameters specify an option we do not
- support or one that does not exist in the original config
-
- """
- def _assert_valid_update(domain_id, config, group=None, option=None):
- """Ensure the combination of config, group and option is valid."""
- self._assert_valid_config(config)
- self._assert_valid_group_and_option(group, option)
-
- # If a group has been specified, then the request is to
- # explicitly only update the options in that group - so the config
- # must not contain anything else. Further, that group must exist in
- # the original config. Likewise, if an option has been specified,
- # then the group in the config must only contain that option and it
- # also must exist in the original config.
- if group:
- if len(config) != 1 or (option and len(config[group]) != 1):
- if option:
- msg = _('Trying to update option %(option)s in group '
- '%(group)s, so that, and only that, option '
- 'must be specified in the config') % {
- 'group': group, 'option': option}
- else:
- msg = _('Trying to update group %(group)s, so that, '
- 'and only that, group must be specified in '
- 'the config') % {'group': group}
- raise exception.InvalidDomainConfig(reason=msg)
-
- # So we now know we have the right number of entries in the
- # config that align with a group/option being specified, but we
- # must also make sure they match.
- if group not in config:
- msg = _('request to update group %(group)s, but config '
- 'provided contains group %(group_other)s '
- 'instead') % {
- 'group': group,
- 'group_other': list(config.keys())[0]}
- raise exception.InvalidDomainConfig(reason=msg)
- if option and option not in config[group]:
- msg = _('Trying to update option %(option)s in group '
- '%(group)s, but config provided contains option '
- '%(option_other)s instead') % {
- 'group': group, 'option': option,
- 'option_other': list(config[group].keys())[0]}
- raise exception.InvalidDomainConfig(reason=msg)
-
- # Finally, we need to check if the group/option specified
- # already exists in the original config - since if not, to keep
- # with the semantics of an update, we need to fail with
- # a DomainConfigNotFound
- if not self._get_config_with_sensitive_info(domain_id,
- group, option):
- if option:
- msg = _('option %(option)s in group %(group)s') % {
- 'group': group, 'option': option}
- raise exception.DomainConfigNotFound(
- domain_id=domain_id, group_or_option=msg)
- else:
- msg = _('group %(group)s') % {'group': group}
- raise exception.DomainConfigNotFound(
- domain_id=domain_id, group_or_option=msg)
-
- def _update_or_create(domain_id, option, sensitive):
- """Update the option, if it doesn't exist then create it."""
- try:
- self.create_config_option(
- domain_id, option['group'], option['option'],
- option['value'], sensitive=sensitive)
- except exception.Conflict:
- self.update_config_option(
- domain_id, option['group'], option['option'],
- option['value'], sensitive=sensitive)
-
- update_config = config
- if group and option:
- # The config will just be a dict containing the option and
- # its value, so make it look like a single option under the
- # group in question
- update_config = {group: config}
-
- _assert_valid_update(domain_id, update_config, group, option)
-
- whitelisted, sensitive = self._config_to_list(update_config)
-
- for new_option in whitelisted:
- _update_or_create(domain_id, new_option, sensitive=False)
- for new_option in sensitive:
- _update_or_create(domain_id, new_option, sensitive=True)
-
- self.get_config_with_sensitive_info.invalidate(self, domain_id)
- return self.get_config(domain_id)
-
- def delete_config(self, domain_id, group=None, option=None):
- """Delete config, or partial config, for the domain.
-
- :param domain_id: the domain in question
- :param group: an optional specific group of options
- :param option: an optional specific option within the group
-
- If group and option are None, then the entire config for the domain
- is deleted. If group is not None, then just that group of options will
- be deleted. If group and option are both specified, then just that
- option is deleted.
-
- :raises keystone.exception.InvalidDomainConfig: when group/option
- parameters specify an option we do not support or one that
- does not exist in the original config.
-
- """
- self._assert_valid_group_and_option(group, option)
- if group:
- # As this is a partial delete, then make sure the items requested
- # are valid and exist in the current config
- current_config = self._get_config_with_sensitive_info(domain_id)
- # Raise an exception if the group/options specified don't exist in
- # the current config so that the delete method provides the
- # correct error semantics.
- current_group = current_config.get(group)
- if not current_group:
- msg = _('group %(group)s') % {'group': group}
- raise exception.DomainConfigNotFound(
- domain_id=domain_id, group_or_option=msg)
- if option and not current_group.get(option):
- msg = _('option %(option)s in group %(group)s') % {
- 'group': group, 'option': option}
- raise exception.DomainConfigNotFound(
- domain_id=domain_id, group_or_option=msg)
-
- self.delete_config_options(domain_id, group, option)
- self.delete_config_options(domain_id, group, option, sensitive=True)
- self.get_config_with_sensitive_info.invalidate(self, domain_id)
-
- def _get_config_with_sensitive_info(self, domain_id, group=None,
- option=None):
- """Get config for a domain/group/option with sensitive info included.
-
- This is only used by the methods within this class, which may need to
- check individual groups or options.
-
- """
- whitelisted = self.list_config_options(domain_id, group, option)
- sensitive = self.list_config_options(domain_id, group, option,
- sensitive=True)
-
- # Check if there are any sensitive substitutions needed. We first try
- # and simply ensure any sensitive options that have valid substitution
- # references in the whitelisted options are substituted. We then check
- # the resulting whitelisted option and raise a warning if there
- # appears to be an unmatched or incorrectly constructed substitution
- # reference. To avoid the risk of logging any sensitive options that
- # have already been substituted, we first take a copy of the
- # whitelisted option.
-
- # Build a dict of the sensitive options ready to try substitution
- sensitive_dict = {s['option']: s['value'] for s in sensitive}
-
- for each_whitelisted in whitelisted:
- if not isinstance(each_whitelisted['value'], six.string_types):
- # We only support substitutions into string types, if its an
- # integer, list etc. then just continue onto the next one
- continue
-
- # Store away the original value in case we need to raise a warning
- # after substitution.
- original_value = each_whitelisted['value']
- warning_msg = ''
- try:
- each_whitelisted['value'] = (
- each_whitelisted['value'] % sensitive_dict)
- except KeyError:
- warning_msg = _LW(
- 'Found what looks like an unmatched config option '
- 'substitution reference - domain: %(domain)s, group: '
- '%(group)s, option: %(option)s, value: %(value)s. Perhaps '
- 'the config option to which it refers has yet to be '
- 'added?')
- except (ValueError, TypeError):
- warning_msg = _LW(
- 'Found what looks like an incorrectly constructed '
- 'config option substitution reference - domain: '
- '%(domain)s, group: %(group)s, option: %(option)s, '
- 'value: %(value)s.')
-
- if warning_msg:
- LOG.warning(warning_msg % {
- 'domain': domain_id,
- 'group': each_whitelisted['group'],
- 'option': each_whitelisted['option'],
- 'value': original_value})
-
- return self._list_to_config(whitelisted, sensitive)
-
- @MEMOIZE_CONFIG
- def get_config_with_sensitive_info(self, domain_id):
- """Get config for a domain with sensitive info included.
-
- This method is not exposed via the public API, but is used by the
- identity manager to initialize a domain with the fully formed config
- options.
-
- """
- return self._get_config_with_sensitive_info(domain_id)
-
- def get_config_default(self, group=None, option=None):
- """Get default config, or partial default config
-
- :param group: an optional specific group of options
- :param option: an optional specific option within the group
-
- :returns: a dict of group dicts containing the default options,
- filtered by group and option if specified
- :raises keystone.exception.InvalidDomainConfig: when the config
- and group/option parameters specify an option we do not
- support (or one that is not whitelisted).
-
- An example response::
-
- {
- 'ldap': {
- 'url': 'myurl',
- 'user_tree_dn': 'OU=myou',
- ....},
- 'identity': {
- 'driver': 'ldap'}
-
- }
-
- """
- def _option_dict(group, option):
- group_attr = getattr(CONF, group)
- if group_attr is None:
- msg = _('Group %s not found in config') % group
- raise exception.UnexpectedError(msg)
- return {'group': group, 'option': option,
- 'value': getattr(group_attr, option)}
-
- self._assert_valid_group_and_option(group, option)
- config_list = []
- if group:
- if option:
- if option not in self.whitelisted_options[group]:
- msg = _('Reading the default for option %(option)s in '
- 'group %(group)s is not supported') % {
- 'option': option, 'group': group}
- raise exception.InvalidDomainConfig(reason=msg)
- config_list.append(_option_dict(group, option))
- else:
- for each_option in self.whitelisted_options[group]:
- config_list.append(_option_dict(group, each_option))
- else:
- for each_group in self.whitelisted_options:
- for each_option in self.whitelisted_options[each_group]:
- config_list.append(_option_dict(each_group, each_option))
-
- return self._list_to_config(config_list, req_option=option)
-
-
-@six.add_metaclass(abc.ABCMeta)
-class DomainConfigDriverV8(object):
- """Interface description for a Domain Config driver."""
-
- @abc.abstractmethod
- def create_config_option(self, domain_id, group, option, value,
- sensitive=False):
- """Creates a config option for a domain.
-
- :param domain_id: the domain for this option
- :param group: the group name
- :param option: the option name
- :param value: the value to assign to this option
- :param sensitive: whether the option is sensitive
-
- :returns: dict containing group, option and value
- :raises keystone.exception.Conflict: when the option already exists
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def get_config_option(self, domain_id, group, option, sensitive=False):
- """Gets the config option for a domain.
-
- :param domain_id: the domain for this option
- :param group: the group name
- :param option: the option name
- :param sensitive: whether the option is sensitive
-
- :returns: dict containing group, option and value
- :raises keystone.exception.DomainConfigNotFound: the option doesn't
- exist.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def list_config_options(self, domain_id, group=None, option=False,
- sensitive=False):
- """Gets a config options for a domain.
-
- :param domain_id: the domain for this option
- :param group: optional group option name
- :param option: optional option name. If group is None, then this
- parameter is ignored
- :param sensitive: whether the option is sensitive
-
- :returns: list of dicts containing group, option and value
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def update_config_option(self, domain_id, group, option, value,
- sensitive=False):
- """Updates a config option for a domain.
-
- :param domain_id: the domain for this option
- :param group: the group option name
- :param option: the option name
- :param value: the value to assign to this option
- :param sensitive: whether the option is sensitive
-
- :returns: dict containing updated group, option and value
- :raises keystone.exception.DomainConfigNotFound: the option doesn't
- exist.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def delete_config_options(self, domain_id, group=None, option=None,
- sensitive=False):
- """Deletes config options for a domain.
-
- Allows deletion of all options for a domain, all options in a group
- or a specific option. The driver is silent if there are no options
- to delete.
-
- :param domain_id: the domain for this option
- :param group: optional group option name
- :param option: optional option name. If group is None, then this
- parameter is ignored
- :param sensitive: whether the option is sensitive
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def obtain_registration(self, domain_id, type):
- """Try and register this domain to use the type specified.
-
- :param domain_id: the domain required
- :param type: type of registration
- :returns: True if the domain was registered, False otherwise. Failing
- to register means that someone already has it (which could
- even be the domain being requested).
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def read_registration(self, type):
- """Get the domain ID of who is registered to use this type.
-
- :param type: type of registration
- :returns: domain_id of who is registered.
- :raises keystone.exception.ConfigRegistrationNotFound: If nobody is
- registered.
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
- @abc.abstractmethod
- def release_registration(self, domain_id, type=None):
- """Release registration if it is held by the domain specified.
-
- If the specified domain is registered for this domain then free it,
- if it is not then do nothing - no exception is raised.
-
- :param domain_id: the domain in question
- :param type: type of registration, if None then all registrations
- for this domain will be freed
-
- """
- raise exception.NotImplemented() # pragma: no cover
-
-
-DomainConfigDriver = manager.create_legacy_driver(DomainConfigDriverV8)
diff --git a/keystone-moon/keystone/resource/routers.py b/keystone-moon/keystone/resource/routers.py
deleted file mode 100644
index d58474e2..00000000
--- a/keystone-moon/keystone/resource/routers.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# Copyright 2013 Metacloud, Inc.
-# Copyright 2012 OpenStack Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-"""WSGI Routers for the Resource service."""
-
-from keystone.common import json_home
-from keystone.common import router
-from keystone.common import wsgi
-from keystone.resource import controllers
-
-
-class Admin(wsgi.ComposableRouter):
- def add_routes(self, mapper):
- # Tenant Operations
- tenant_controller = controllers.Tenant()
- mapper.connect('/tenants',
- controller=tenant_controller,
- action='get_all_projects',
- conditions=dict(method=['GET']))
- mapper.connect('/tenants/{tenant_id}',
- controller=tenant_controller,
- action='get_project',
- conditions=dict(method=['GET']))
-
-
-class Routers(wsgi.RoutersBase):
-
- def append_v3_routers(self, mapper, routers):
- routers.append(
- router.Router(controllers.DomainV3(),
- 'domains', 'domain',
- resource_descriptions=self.v3_resources))
-
- config_controller = controllers.DomainConfigV3()
-
- self._add_resource(
- mapper, config_controller,
- path='/domains/{domain_id}/config',
- get_head_action='get_domain_config',
- put_action='create_domain_config',
- patch_action='update_domain_config_only',
- delete_action='delete_domain_config',
- rel=json_home.build_v3_resource_relation('domain_config'),
- status=json_home.Status.EXPERIMENTAL,
- path_vars={
- 'domain_id': json_home.Parameters.DOMAIN_ID
- })
-
- config_group_param = (
- json_home.build_v3_parameter_relation('config_group'))
- self._add_resource(
- mapper, config_controller,
- path='/domains/{domain_id}/config/{group}',
- get_head_action='get_domain_config',
- patch_action='update_domain_config_group',
- delete_action='delete_domain_config',
- rel=json_home.build_v3_resource_relation('domain_config_group'),
- status=json_home.Status.EXPERIMENTAL,
- path_vars={
- 'domain_id': json_home.Parameters.DOMAIN_ID,
- 'group': config_group_param
- })
-
- self._add_resource(
- mapper, config_controller,
- path='/domains/{domain_id}/config/{group}/{option}',
- get_head_action='get_domain_config',
- patch_action='update_domain_config',
- delete_action='delete_domain_config',
- rel=json_home.build_v3_resource_relation('domain_config_option'),
- status=json_home.Status.EXPERIMENTAL,
- path_vars={
- 'domain_id': json_home.Parameters.DOMAIN_ID,
- 'group': config_group_param,
- 'option': json_home.build_v3_parameter_relation(
- 'config_option')
- })
-
- self._add_resource(
- mapper, config_controller,
- path='/domains/config/default',
- get_action='get_domain_config_default',
- rel=json_home.build_v3_resource_relation('domain_config_default'),
- status=json_home.Status.EXPERIMENTAL)
-
- self._add_resource(
- mapper, config_controller,
- path='/domains/config/{group}/default',
- get_action='get_domain_config_default',
- rel=json_home.build_v3_resource_relation(
- 'domain_config_default_group'),
- status=json_home.Status.EXPERIMENTAL,
- path_vars={
- 'group': config_group_param
- })
-
- self._add_resource(
- mapper, config_controller,
- path='/domains/config/{group}/{option}/default',
- get_action='get_domain_config_default',
- rel=json_home.build_v3_resource_relation(
- 'domain_config_default_option'),
- status=json_home.Status.EXPERIMENTAL,
- path_vars={
- 'group': config_group_param,
- 'option': json_home.build_v3_parameter_relation(
- 'config_option')
- })
-
- routers.append(
- router.Router(controllers.ProjectV3(),
- 'projects', 'project',
- resource_descriptions=self.v3_resources))
diff --git a/keystone-moon/keystone/resource/schema.py b/keystone-moon/keystone/resource/schema.py
deleted file mode 100644
index 7e2cd667..00000000
--- a/keystone-moon/keystone/resource/schema.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# 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.common import validation
-from keystone.common.validation import parameter_types
-
-
-_project_properties = {
- 'description': validation.nullable(parameter_types.description),
- # NOTE(htruta): domain_id is nullable for projects acting as a domain.
- 'domain_id': validation.nullable(parameter_types.id_string),
- 'enabled': parameter_types.boolean,
- 'is_domain': parameter_types.boolean,
- 'parent_id': validation.nullable(parameter_types.id_string),
- 'name': {
- 'type': 'string',
- 'minLength': 1,
- 'maxLength': 64
- }
-}
-
-project_create = {
- 'type': 'object',
- 'properties': _project_properties,
- # NOTE(lbragstad): A project name is the only parameter required for
- # project creation according to the Identity V3 API. We should think
- # about using the maxProperties validator here, and in update.
- 'required': ['name'],
- 'additionalProperties': True
-}
-
-project_update = {
- 'type': 'object',
- 'properties': _project_properties,
- # NOTE(lbragstad): Make sure at least one property is being updated
- 'minProperties': 1,
- 'additionalProperties': True
-}
-
-_domain_properties = {
- 'description': validation.nullable(parameter_types.description),
- 'enabled': parameter_types.boolean,
- 'name': {
- 'type': 'string',
- 'minLength': 1,
- 'maxLength': 64
- }
-}
-
-domain_create = {
- 'type': 'object',
- 'properties': _domain_properties,
- # TODO(lbragstad): According to the V3 API spec, name isn't required but
- # the current implementation in assignment.controller:DomainV3 requires a
- # name for the domain.
- 'required': ['name'],
- 'additionalProperties': True
-}
-
-domain_update = {
- 'type': 'object',
- 'properties': _domain_properties,
- 'minProperties': 1,
- 'additionalProperties': True
-}