diff options
author | WuKong <rebirthmonkey@gmail.com> | 2015-06-30 18:47:29 +0200 |
---|---|---|
committer | WuKong <rebirthmonkey@gmail.com> | 2015-06-30 18:47:29 +0200 |
commit | b8c756ecdd7cced1db4300935484e8c83701c82e (patch) | |
tree | 87e51107d82b217ede145de9d9d59e2100725bd7 /keystone-moon/keystone/trust | |
parent | c304c773bae68fb854ed9eab8fb35c4ef17cf136 (diff) |
migrate moon code from github to opnfv
Change-Id: Ice53e368fd1114d56a75271aa9f2e598e3eba604
Signed-off-by: WuKong <rebirthmonkey@gmail.com>
Diffstat (limited to 'keystone-moon/keystone/trust')
-rw-r--r-- | keystone-moon/keystone/trust/__init__.py | 17 | ||||
-rw-r--r-- | keystone-moon/keystone/trust/backends/__init__.py | 0 | ||||
-rw-r--r-- | keystone-moon/keystone/trust/backends/sql.py | 180 | ||||
-rw-r--r-- | keystone-moon/keystone/trust/controllers.py | 287 | ||||
-rw-r--r-- | keystone-moon/keystone/trust/core.py | 251 | ||||
-rw-r--r-- | keystone-moon/keystone/trust/routers.py | 67 | ||||
-rw-r--r-- | keystone-moon/keystone/trust/schema.py | 46 |
7 files changed, 848 insertions, 0 deletions
diff --git a/keystone-moon/keystone/trust/__init__.py b/keystone-moon/keystone/trust/__init__.py new file mode 100644 index 00000000..e5ee61fb --- /dev/null +++ b/keystone-moon/keystone/trust/__init__.py @@ -0,0 +1,17 @@ +# 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. + +from keystone.trust import controllers # noqa +from keystone.trust.core import * # noqa +from keystone.trust import routers # noqa diff --git a/keystone-moon/keystone/trust/backends/__init__.py b/keystone-moon/keystone/trust/backends/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/keystone-moon/keystone/trust/backends/__init__.py diff --git a/keystone-moon/keystone/trust/backends/sql.py b/keystone-moon/keystone/trust/backends/sql.py new file mode 100644 index 00000000..4f5ee2e5 --- /dev/null +++ b/keystone-moon/keystone/trust/backends/sql.py @@ -0,0 +1,180 @@ +# 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. + +import time + +from oslo_log import log +from oslo_utils import timeutils + +from keystone.common import sql +from keystone import exception +from keystone import trust + + +LOG = log.getLogger(__name__) +# The maximum number of iterations that will be attempted for optimistic +# locking on consuming a limited-use trust. +MAXIMUM_CONSUME_ATTEMPTS = 10 + + +class TrustModel(sql.ModelBase, sql.DictBase): + __tablename__ = 'trust' + attributes = ['id', 'trustor_user_id', 'trustee_user_id', + 'project_id', 'impersonation', 'expires_at', + 'remaining_uses', 'deleted_at'] + id = sql.Column(sql.String(64), primary_key=True) + # user id of owner + trustor_user_id = sql.Column(sql.String(64), nullable=False,) + # user_id of user allowed to consume this preauth + trustee_user_id = sql.Column(sql.String(64), nullable=False) + project_id = sql.Column(sql.String(64)) + impersonation = sql.Column(sql.Boolean, nullable=False) + deleted_at = sql.Column(sql.DateTime) + expires_at = sql.Column(sql.DateTime) + remaining_uses = sql.Column(sql.Integer, nullable=True) + extra = sql.Column(sql.JsonBlob()) + + +class TrustRole(sql.ModelBase): + __tablename__ = 'trust_role' + attributes = ['trust_id', 'role_id'] + trust_id = sql.Column(sql.String(64), primary_key=True, nullable=False) + role_id = sql.Column(sql.String(64), primary_key=True, nullable=False) + + +class Trust(trust.Driver): + @sql.handle_conflicts(conflict_type='trust') + def create_trust(self, trust_id, trust, roles): + with sql.transaction() as session: + ref = TrustModel.from_dict(trust) + ref['id'] = trust_id + if ref.get('expires_at') and ref['expires_at'].tzinfo is not None: + ref['expires_at'] = timeutils.normalize_time(ref['expires_at']) + session.add(ref) + added_roles = [] + for role in roles: + trust_role = TrustRole() + trust_role.trust_id = trust_id + trust_role.role_id = role['id'] + added_roles.append({'id': role['id']}) + session.add(trust_role) + trust_dict = ref.to_dict() + trust_dict['roles'] = added_roles + return trust_dict + + def _add_roles(self, trust_id, session, trust_dict): + roles = [] + for role in session.query(TrustRole).filter_by(trust_id=trust_id): + roles.append({'id': role.role_id}) + trust_dict['roles'] = roles + + @sql.handle_conflicts(conflict_type='trust') + def consume_use(self, trust_id): + + for attempt in range(MAXIMUM_CONSUME_ATTEMPTS): + with sql.transaction() as session: + try: + query_result = (session.query(TrustModel.remaining_uses). + filter_by(id=trust_id). + filter_by(deleted_at=None).one()) + except sql.NotFound: + raise exception.TrustNotFound(trust_id=trust_id) + + remaining_uses = query_result.remaining_uses + + if remaining_uses is None: + # unlimited uses, do nothing + break + elif remaining_uses > 0: + # NOTE(morganfainberg): use an optimistic locking method + # to ensure we only ever update a trust that has the + # expected number of remaining uses. + rows_affected = ( + session.query(TrustModel). + filter_by(id=trust_id). + filter_by(deleted_at=None). + filter_by(remaining_uses=remaining_uses). + update({'remaining_uses': (remaining_uses - 1)}, + synchronize_session=False)) + if rows_affected == 1: + # Successfully consumed a single limited-use trust. + # Since trust_id is the PK on the Trust table, there is + # no case we should match more than 1 row in the + # update. We either update 1 row or 0 rows. + break + else: + raise exception.TrustUseLimitReached(trust_id=trust_id) + # NOTE(morganfainberg): Ensure we have a yield point for eventlet + # here. This should cost us nothing otherwise. This can be removed + # if/when oslo_db cleanly handles yields on db calls. + time.sleep(0) + else: + # NOTE(morganfainberg): In the case the for loop is not prematurely + # broken out of, this else block is executed. This means the trust + # was not unlimited nor was it consumed (we hit the maximum + # iteration limit). This is just an indicator that we were unable + # to get the optimistic lock rather than silently failing or + # incorrectly indicating a trust was consumed. + raise exception.TrustConsumeMaximumAttempt(trust_id=trust_id) + + def get_trust(self, trust_id, deleted=False): + session = sql.get_session() + query = session.query(TrustModel).filter_by(id=trust_id) + if not deleted: + query = query.filter_by(deleted_at=None) + ref = query.first() + if ref is None: + return None + if ref.expires_at is not None and not deleted: + now = timeutils.utcnow() + if now > ref.expires_at: + return None + # Do not return trusts that can't be used anymore + if ref.remaining_uses is not None and not deleted: + if ref.remaining_uses <= 0: + return None + trust_dict = ref.to_dict() + + self._add_roles(trust_id, session, trust_dict) + return trust_dict + + @sql.handle_conflicts(conflict_type='trust') + def list_trusts(self): + session = sql.get_session() + trusts = session.query(TrustModel).filter_by(deleted_at=None) + return [trust_ref.to_dict() for trust_ref in trusts] + + @sql.handle_conflicts(conflict_type='trust') + def list_trusts_for_trustee(self, trustee_user_id): + session = sql.get_session() + trusts = (session.query(TrustModel). + filter_by(deleted_at=None). + filter_by(trustee_user_id=trustee_user_id)) + return [trust_ref.to_dict() for trust_ref in trusts] + + @sql.handle_conflicts(conflict_type='trust') + def list_trusts_for_trustor(self, trustor_user_id): + session = sql.get_session() + trusts = (session.query(TrustModel). + filter_by(deleted_at=None). + filter_by(trustor_user_id=trustor_user_id)) + return [trust_ref.to_dict() for trust_ref in trusts] + + @sql.handle_conflicts(conflict_type='trust') + def delete_trust(self, trust_id): + with sql.transaction() as session: + trust_ref = session.query(TrustModel).get(trust_id) + if not trust_ref: + raise exception.TrustNotFound(trust_id=trust_id) + trust_ref.deleted_at = timeutils.utcnow() diff --git a/keystone-moon/keystone/trust/controllers.py b/keystone-moon/keystone/trust/controllers.py new file mode 100644 index 00000000..60e34ccd --- /dev/null +++ b/keystone-moon/keystone/trust/controllers.py @@ -0,0 +1,287 @@ +# Copyright 2013 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. + +import uuid + +from oslo_config import cfg +from oslo_log import log +from oslo_utils import timeutils +import six + +from keystone import assignment +from keystone.common import controller +from keystone.common import dependency +from keystone.common import validation +from keystone import exception +from keystone.i18n import _ +from keystone.models import token_model +from keystone import notifications +from keystone.openstack.common import versionutils +from keystone.trust import schema + + +CONF = cfg.CONF + +LOG = log.getLogger(__name__) + + +def _trustor_trustee_only(trust, user_id): + if (user_id != trust.get('trustee_user_id') and + user_id != trust.get('trustor_user_id')): + raise exception.Forbidden() + + +def _admin_trustor_only(context, trust, user_id): + if user_id != trust.get('trustor_user_id') and not context['is_admin']: + raise exception.Forbidden() + + +@dependency.requires('assignment_api', 'identity_api', 'role_api', + 'token_provider_api', 'trust_api') +class TrustV3(controller.V3Controller): + collection_name = "trusts" + member_name = "trust" + + @classmethod + def base_url(cls, context, path=None): + """Construct a path and pass it to V3Controller.base_url method.""" + + # NOTE(stevemar): Overriding path to /OS-TRUST/trusts so that + # V3Controller.base_url handles setting the self link correctly. + path = '/OS-TRUST/' + cls.collection_name + return super(TrustV3, cls).base_url(context, path=path) + + def _get_user_id(self, context): + if 'token_id' in context: + token_id = context['token_id'] + token_data = self.token_provider_api.validate_token(token_id) + token_ref = token_model.KeystoneToken(token_id=token_id, + token_data=token_data) + return token_ref.user_id + return None + + def get_trust(self, context, trust_id): + user_id = self._get_user_id(context) + trust = self.trust_api.get_trust(trust_id) + if not trust: + raise exception.TrustNotFound(trust_id=trust_id) + _trustor_trustee_only(trust, user_id) + self._fill_in_roles(context, trust, + self.role_api.list_roles()) + return TrustV3.wrap_member(context, trust) + + def _fill_in_roles(self, context, trust, all_roles): + if trust.get('expires_at') is not None: + trust['expires_at'] = (timeutils.isotime + (trust['expires_at'], + subsecond=True)) + + if 'roles' not in trust: + trust['roles'] = [] + trust_full_roles = [] + for trust_role in trust['roles']: + if isinstance(trust_role, six.string_types): + trust_role = {'id': trust_role} + matching_roles = [x for x in all_roles + if x['id'] == trust_role['id']] + if matching_roles: + full_role = assignment.controllers.RoleV3.wrap_member( + context, matching_roles[0])['role'] + trust_full_roles.append(full_role) + trust['roles'] = trust_full_roles + trust['roles_links'] = { + 'self': (self.base_url(context) + "/%s/roles" % trust['id']), + 'next': None, + 'previous': None} + + def _normalize_role_list(self, trust, all_roles): + trust_roles = [] + all_role_names = {r['name']: r for r in all_roles} + for role in trust.get('roles', []): + if 'id' in role: + trust_roles.append({'id': role['id']}) + elif 'name' in role: + rolename = role['name'] + if rolename in all_role_names: + trust_roles.append({'id': + all_role_names[rolename]['id']}) + else: + raise exception.RoleNotFound("role %s is not defined" % + rolename) + else: + raise exception.ValidationError(attribute='id or name', + target='roles') + return trust_roles + + @controller.protected() + @validation.validated(schema.trust_create, 'trust') + def create_trust(self, context, trust=None): + """Create a new trust. + + The user creating the trust must be the trustor. + + """ + if not trust: + raise exception.ValidationError(attribute='trust', + target='request') + + auth_context = context.get('environment', + {}).get('KEYSTONE_AUTH_CONTEXT', {}) + + # Check if delegated via trust + if auth_context.get('is_delegated_auth'): + # Redelegation case + src_trust_id = auth_context['trust_id'] + if not src_trust_id: + raise exception.Forbidden( + _('Redelegation allowed for delegated by trust only')) + + redelegated_trust = self.trust_api.get_trust(src_trust_id) + else: + redelegated_trust = None + + if trust.get('project_id'): + self._require_role(trust) + self._require_user_is_trustor(context, trust) + self._require_trustee_exists(trust['trustee_user_id']) + all_roles = self.role_api.list_roles() + # Normalize roles + normalized_roles = self._normalize_role_list(trust, all_roles) + trust['roles'] = normalized_roles + self._require_trustor_has_role_in_project(trust) + trust['expires_at'] = self._parse_expiration_date( + trust.get('expires_at')) + trust_id = uuid.uuid4().hex + initiator = notifications._get_request_audit_info(context) + new_trust = self.trust_api.create_trust(trust_id, trust, + normalized_roles, + redelegated_trust, + initiator) + self._fill_in_roles(context, new_trust, all_roles) + return TrustV3.wrap_member(context, new_trust) + + def _require_trustee_exists(self, trustee_user_id): + self.identity_api.get_user(trustee_user_id) + + def _require_user_is_trustor(self, context, trust): + user_id = self._get_user_id(context) + if user_id != trust.get('trustor_user_id'): + raise exception.Forbidden( + _("The authenticated user should match the trustor.")) + + def _require_role(self, trust): + if not trust.get('roles'): + raise exception.Forbidden( + _('At least one role should be specified.')) + + def _get_user_role(self, trust): + if not self._attribute_is_empty(trust, 'project_id'): + return self.assignment_api.get_roles_for_user_and_project( + trust['trustor_user_id'], trust['project_id']) + else: + return [] + + def _require_trustor_has_role_in_project(self, trust): + user_roles = self._get_user_role(trust) + for trust_role in trust['roles']: + matching_roles = [x for x in user_roles + if x == trust_role['id']] + if not matching_roles: + raise exception.RoleNotFound(role_id=trust_role['id']) + + def _parse_expiration_date(self, expiration_date): + if expiration_date is None: + return None + if not expiration_date.endswith('Z'): + expiration_date += 'Z' + try: + return timeutils.parse_isotime(expiration_date) + except ValueError: + raise exception.ValidationTimeStampError() + + def _check_role_for_trust(self, context, trust_id, role_id): + """Checks if a role has been assigned to a trust.""" + trust = self.trust_api.get_trust(trust_id) + if not trust: + raise exception.TrustNotFound(trust_id=trust_id) + user_id = self._get_user_id(context) + _trustor_trustee_only(trust, user_id) + if not any(role['id'] == role_id for role in trust['roles']): + raise exception.RoleNotFound(role_id=role_id) + + @controller.protected() + def list_trusts(self, context): + query = context['query_string'] + trusts = [] + if not query: + self.assert_admin(context) + trusts += self.trust_api.list_trusts() + if 'trustor_user_id' in query: + user_id = query['trustor_user_id'] + calling_user_id = self._get_user_id(context) + if user_id != calling_user_id: + raise exception.Forbidden() + trusts += (self.trust_api. + list_trusts_for_trustor(user_id)) + if 'trustee_user_id' in query: + user_id = query['trustee_user_id'] + calling_user_id = self._get_user_id(context) + if user_id != calling_user_id: + raise exception.Forbidden() + trusts += self.trust_api.list_trusts_for_trustee(user_id) + for trust in trusts: + # get_trust returns roles, list_trusts does not + # It seems in some circumstances, roles does not + # exist in the query response, so check first + if 'roles' in trust: + del trust['roles'] + if trust.get('expires_at') is not None: + trust['expires_at'] = (timeutils.isotime + (trust['expires_at'], + subsecond=True)) + return TrustV3.wrap_collection(context, trusts) + + @controller.protected() + def delete_trust(self, context, trust_id): + trust = self.trust_api.get_trust(trust_id) + if not trust: + raise exception.TrustNotFound(trust_id=trust_id) + + user_id = self._get_user_id(context) + _admin_trustor_only(context, trust, user_id) + initiator = notifications._get_request_audit_info(context) + self.trust_api.delete_trust(trust_id, initiator) + + @controller.protected() + def list_roles_for_trust(self, context, trust_id): + trust = self.get_trust(context, trust_id)['trust'] + if not trust: + raise exception.TrustNotFound(trust_id=trust_id) + user_id = self._get_user_id(context) + _trustor_trustee_only(trust, user_id) + return {'roles': trust['roles'], + 'links': trust['roles_links']} + + @versionutils.deprecated( + versionutils.deprecated.KILO, + remove_in=+2) + def check_role_for_trust(self, context, trust_id, role_id): + return self._check_role_for_trust(self, context, trust_id, role_id) + + @controller.protected() + def get_role_for_trust(self, context, trust_id, role_id): + """Get a role that has been assigned to a trust.""" + self._check_role_for_trust(context, trust_id, role_id) + role = self.role_api.get_role(role_id) + return assignment.controllers.RoleV3.wrap_member(context, role) diff --git a/keystone-moon/keystone/trust/core.py b/keystone-moon/keystone/trust/core.py new file mode 100644 index 00000000..de6b6d85 --- /dev/null +++ b/keystone-moon/keystone/trust/core.py @@ -0,0 +1,251 @@ +# 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. + +"""Main entry point into the Identity service.""" + +import abc + +from oslo_config import cfg +from oslo_log import log +import six + +from keystone.common import dependency +from keystone.common import manager +from keystone import exception +from keystone.i18n import _ +from keystone import notifications + + +CONF = cfg.CONF + +LOG = log.getLogger(__name__) + + +@dependency.requires('identity_api') +@dependency.provider('trust_api') +class Manager(manager.Manager): + """Default pivot point for the Trust backend. + + See :mod:`keystone.common.manager.Manager` for more details on how this + dynamically calls the backend. + + """ + _TRUST = "OS-TRUST:trust" + + def __init__(self): + super(Manager, self).__init__(CONF.trust.driver) + + @staticmethod + def _validate_redelegation(redelegated_trust, trust): + # Validate against: + # 0 < redelegation_count <= max_redelegation_count + max_redelegation_count = CONF.trust.max_redelegation_count + redelegation_depth = redelegated_trust.get('redelegation_count', 0) + if not (0 < redelegation_depth <= max_redelegation_count): + raise exception.Forbidden( + _('Remaining redelegation depth of %(redelegation_depth)d' + ' out of allowed range of [0..%(max_count)d]'), + redelegation_depth=redelegation_depth, + max_count=max_redelegation_count) + + # remaining_uses is None + remaining_uses = trust.get('remaining_uses') + if remaining_uses is not None: + raise exception.Forbidden( + _('Field "remaining_uses" is set to %(value)s' + ' while it must not be set in order to redelegate a trust'), + value=remaining_uses) + + # expiry times + trust_expiry = trust.get('expires_at') + redelegated_expiry = redelegated_trust['expires_at'] + if trust_expiry: + # redelegated trust is from backend and has no tzinfo + if redelegated_expiry < trust_expiry.replace(tzinfo=None): + raise exception.Forbidden( + _('Requested expiration time is more ' + 'than redelegated trust can provide')) + else: + trust['expires_at'] = redelegated_expiry + + # trust roles is a subset of roles of the redelegated trust + parent_roles = set(role['id'] + for role in redelegated_trust['roles']) + if not all(role['id'] in parent_roles for role in trust['roles']): + raise exception.Forbidden( + _('Some of requested roles are not in redelegated trust')) + + def get_trust_pedigree(self, trust_id): + trust = self.driver.get_trust(trust_id) + trust_chain = [trust] + if trust and trust.get('redelegated_trust_id'): + trusts = self.driver.list_trusts_for_trustor( + trust['trustor_user_id']) + while trust_chain[-1].get('redelegated_trust_id'): + for t in trusts: + if t['id'] == trust_chain[-1]['redelegated_trust_id']: + trust_chain.append(t) + break + + return trust_chain + + def get_trust(self, trust_id, deleted=False): + trust = self.driver.get_trust(trust_id, deleted) + + if trust and trust.get('redelegated_trust_id') and not deleted: + trust_chain = self.get_trust_pedigree(trust_id) + + for parent, child in zip(trust_chain[1:], trust_chain): + self._validate_redelegation(parent, child) + try: + self.identity_api.assert_user_enabled( + parent['trustee_user_id']) + except (AssertionError, exception.NotFound): + raise exception.Forbidden( + _('One of the trust agents is disabled or deleted')) + + return trust + + def create_trust(self, trust_id, trust, roles, redelegated_trust=None, + initiator=None): + """Create a new trust. + + :returns: a new trust + """ + # Default for initial trust in chain is max_redelegation_count + max_redelegation_count = CONF.trust.max_redelegation_count + requested_count = trust.get('redelegation_count') + redelegatable = (trust.pop('allow_redelegation', False) + and requested_count != 0) + if not redelegatable: + trust['redelegation_count'] = requested_count = 0 + remaining_uses = trust.get('remaining_uses') + if remaining_uses is not None and remaining_uses <= 0: + msg = _('remaining_uses must be a positive integer or null.') + raise exception.ValidationError(msg) + else: + # Validate requested redelegation depth + if requested_count and requested_count > max_redelegation_count: + raise exception.Forbidden( + _('Requested redelegation depth of %(requested_count)d ' + 'is greater than allowed %(max_count)d'), + requested_count=requested_count, + max_count=max_redelegation_count) + # Decline remaining_uses + if 'remaining_uses' in trust: + exception.ValidationError(_('remaining_uses must not be set ' + 'if redelegation is allowed')) + + if redelegated_trust: + trust['redelegated_trust_id'] = redelegated_trust['id'] + remaining_count = redelegated_trust['redelegation_count'] - 1 + + # Validate depth consistency + if (redelegatable and requested_count and + requested_count != remaining_count): + msg = _('Modifying "redelegation_count" upon redelegation is ' + 'forbidden. Omitting this parameter is advised.') + raise exception.Forbidden(msg) + trust.setdefault('redelegation_count', remaining_count) + + # Check entire trust pedigree validity + pedigree = self.get_trust_pedigree(redelegated_trust['id']) + for t in pedigree: + self._validate_redelegation(t, trust) + + trust.setdefault('redelegation_count', max_redelegation_count) + ref = self.driver.create_trust(trust_id, trust, roles) + + notifications.Audit.created(self._TRUST, trust_id, initiator=initiator) + + return ref + + def delete_trust(self, trust_id, initiator=None): + """Remove a trust. + + :raises: keystone.exception.TrustNotFound + + Recursively remove given and redelegated trusts + """ + trust = self.driver.get_trust(trust_id) + if not trust: + raise exception.TrustNotFound(trust_id) + + trusts = self.driver.list_trusts_for_trustor( + trust['trustor_user_id']) + + for t in trusts: + if t.get('redelegated_trust_id') == trust_id: + # recursive call to make sure all notifications are sent + try: + self.delete_trust(t['id']) + except exception.TrustNotFound: + # if trust was deleted by concurrent process + # consistency must not suffer + pass + + # end recursion + self.driver.delete_trust(trust_id) + + notifications.Audit.deleted(self._TRUST, trust_id, initiator) + + +@six.add_metaclass(abc.ABCMeta) +class Driver(object): + + @abc.abstractmethod + def create_trust(self, trust_id, trust, roles): + """Create a new trust. + + :returns: a new trust + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def get_trust(self, trust_id, deleted=False): + """Get a trust by the trust id. + + :param trust_id: the trust identifier + :type trust_id: string + :param deleted: return the trust even if it is deleted, expired, or + has no consumptions left + :type deleted: bool + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_trusts(self): + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_trusts_for_trustee(self, trustee): + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_trusts_for_trustor(self, trustor): + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_trust(self, trust_id): + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def consume_use(self, trust_id): + """Consume one use when a trust was created with a limitation on its + uses, provided there are still uses available. + + :raises: keystone.exception.TrustUseLimitReached, + keystone.exception.TrustNotFound + """ + raise exception.NotImplemented() # pragma: no cover diff --git a/keystone-moon/keystone/trust/routers.py b/keystone-moon/keystone/trust/routers.py new file mode 100644 index 00000000..3a6243cc --- /dev/null +++ b/keystone-moon/keystone/trust/routers.py @@ -0,0 +1,67 @@ +# 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 Trust service.""" + +import functools + +from keystone.common import json_home +from keystone.common import wsgi +from keystone.trust import controllers + + +_build_resource_relation = functools.partial( + json_home.build_v3_extension_resource_relation, extension_name='OS-TRUST', + extension_version='1.0') + +TRUST_ID_PARAMETER_RELATION = json_home.build_v3_extension_parameter_relation( + 'OS-TRUST', '1.0', 'trust_id') + + +class Routers(wsgi.RoutersBase): + + def append_v3_routers(self, mapper, routers): + trust_controller = controllers.TrustV3() + + self._add_resource( + mapper, trust_controller, + path='/OS-TRUST/trusts', + get_action='list_trusts', + post_action='create_trust', + rel=_build_resource_relation(resource_name='trusts')) + self._add_resource( + mapper, trust_controller, + path='/OS-TRUST/trusts/{trust_id}', + get_action='get_trust', + delete_action='delete_trust', + rel=_build_resource_relation(resource_name='trust'), + path_vars={ + 'trust_id': TRUST_ID_PARAMETER_RELATION, + }) + self._add_resource( + mapper, trust_controller, + path='/OS-TRUST/trusts/{trust_id}/roles', + get_action='list_roles_for_trust', + rel=_build_resource_relation(resource_name='trust_roles'), + path_vars={ + 'trust_id': TRUST_ID_PARAMETER_RELATION, + }) + self._add_resource( + mapper, trust_controller, + path='/OS-TRUST/trusts/{trust_id}/roles/{role_id}', + get_head_action='get_role_for_trust', + rel=_build_resource_relation(resource_name='trust_role'), + path_vars={ + 'trust_id': TRUST_ID_PARAMETER_RELATION, + 'role_id': json_home.Parameters.ROLE_ID, + }) diff --git a/keystone-moon/keystone/trust/schema.py b/keystone-moon/keystone/trust/schema.py new file mode 100644 index 00000000..087cd1e9 --- /dev/null +++ b/keystone-moon/keystone/trust/schema.py @@ -0,0 +1,46 @@ +# 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 + + +_trust_properties = { + 'trustor_user_id': parameter_types.id_string, + 'trustee_user_id': parameter_types.id_string, + 'impersonation': parameter_types.boolean, + 'project_id': validation.nullable(parameter_types.id_string), + 'remaining_uses': { + 'type': ['integer', 'null'], + 'minimum': 1 + }, + 'expires_at': { + 'type': ['null', 'string'] + }, + 'allow_redelegation': { + 'type': ['boolean', 'null'] + }, + 'redelegation_count': { + 'type': ['integer', 'null'], + 'minimum': 0 + }, + # TODO(lbragstad): Need to find a better way to do this. We should be + # checking that a role is a list of IDs and/or names. + 'roles': validation.add_array_type(parameter_types.id_string) +} + +trust_create = { + 'type': 'object', + 'properties': _trust_properties, + 'required': ['trustor_user_id', 'trustee_user_id', 'impersonation'], + 'additionalProperties': True +} |