aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/trust
diff options
context:
space:
mode:
Diffstat (limited to 'keystone-moon/keystone/trust')
-rw-r--r--keystone-moon/keystone/trust/__init__.py17
-rw-r--r--keystone-moon/keystone/trust/backends/__init__.py0
-rw-r--r--keystone-moon/keystone/trust/backends/sql.py180
-rw-r--r--keystone-moon/keystone/trust/controllers.py287
-rw-r--r--keystone-moon/keystone/trust/core.py251
-rw-r--r--keystone-moon/keystone/trust/routers.py67
-rw-r--r--keystone-moon/keystone/trust/schema.py46
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
+}