diff options
author | Adam Young <ayoung@redhat.com> | 2013-02-26 14:54:32 -0500 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2013-03-05 19:35:38 +0000 |
commit | 601eeb50b60a2e99041690fe19238202bc203503 (patch) | |
tree | f1be4ab425ba9150b61c1e1c6c5bd1a67f180021 /keystone/auth | |
parent | ab6e5529513af656db512b888fed9b320391afbd (diff) | |
download | keystone-601eeb50b60a2e99041690fe19238202bc203503.tar.gz keystone-601eeb50b60a2e99041690fe19238202bc203503.tar.xz keystone-601eeb50b60a2e99041690fe19238202bc203503.zip |
Trusts
Blueprint trusts
creates a trust. Using a trust, one user (the trustee), can then
create tokens with a subset of another user's (the trustor) roles and
projects.
If the impersonate flag in the trust is set, the token user_id is set
to the trustor's user ID
If the impersonate flag is not set, the token's user_is is set to the
trustee's user ID
check that both trustor and trustee are enabled prior to creating
the trust token.
sql and kvs backends
sql upgrade scripts
unit tests for backends, auth and v3 api
modifications to the trust controller for creating tokens
Authenticates that only user can be trustor in create
Deleting a trust invalidates all tokens created from that trust
Adds the trust id and the id of the trustee to the header of the token
policy rules for trust
This version has a workaround for testing against the KVS version
of the Service catalog
Change-Id: I5745f4d9a4180b59671a143a55ed87019e98ec76
Diffstat (limited to 'keystone/auth')
-rw-r--r-- | keystone/auth/controllers.py | 85 | ||||
-rw-r--r-- | keystone/auth/methods/token.py | 2 | ||||
-rw-r--r-- | keystone/auth/token_factory.py | 91 |
3 files changed, 131 insertions, 47 deletions
diff --git a/keystone/auth/controllers.py b/keystone/auth/controllers.py index 3760aa3a..96f3dc0c 100644 --- a/keystone/auth/controllers.py +++ b/keystone/auth/controllers.py @@ -24,6 +24,7 @@ from keystone import config from keystone import exception from keystone import identity from keystone import token +from keystone import trust from keystone.openstack.common import importutils @@ -63,13 +64,15 @@ class AuthInfo(object): def __init__(self, context, auth=None): self.identity_api = identity.Manager() + self.trust_api = trust.Manager() self.context = context self.auth = auth - self._scope_data = (None, None) - # self._scope_data is (domain_id, project_id) - # project scope: (None, project_id) - # domain scope: (domain_id, None) - # unscoped: (None, None) + self._scope_data = (None, None, None) + # self._scope_data is (domain_id, project_id, trust_ref) + # project scope: (None, project_id, None) + # domain scope: (domain_id, None, None) + # trust scope: (None, None, trust_id) + # unscoped: (None, None, None) self._validate_and_normalize_auth_data() def _assert_project_is_enabled(self, project_ref): @@ -136,6 +139,16 @@ class AuthInfo(object): self._assert_project_is_enabled(project_ref) return project_ref + def _lookup_trust(self, trust_info): + trust_id = trust_info.get('id') + if not trust_id: + raise exception.ValidationError(attribute='trust_id', + target='trust') + trust = self.trust_api.get_trust(self.context, trust_id) + if not trust: + raise exception.TrustNotFound(trust_id) + return trust + def lookup_user(self, user_info): user_id = user_info.get('id') user_name = user_info.get('name') @@ -165,25 +178,28 @@ class AuthInfo(object): """ Validate and normalize scope data """ if 'scope' not in self.auth: return - - # if scoped, only to a project or domain, but not both - if ('project' not in self.auth['scope'] and - 'domain' not in self.auth['scope']): - # neither domain or project provided - raise exception.ValidationError(attribute='project or domain', - target='scope') - if ('project' in self.auth['scope'] and - 'domain' in self.auth['scope']): - # both domain and project provided - raise exception.ValidationError(attribute='project or domain', - target='scope') + if sum(['project' in self.auth['scope'], + 'domain' in self.auth['scope'], + 'trust' in self.auth['scope']]) != 1: + raise exception.ValidationError( + attribute='project, domain, or trust', + target='scope') if 'project' in self.auth['scope']: project_ref = self._lookup_project(self.auth['scope']['project']) - self._scope_data = (None, project_ref['id']) - else: + self._scope_data = (None, project_ref['id'], None) + elif 'domain' in self.auth['scope']: domain_ref = self._lookup_domain(self.auth['scope']['domain']) - self._scope_data = (domain_ref['id'], None) + self._scope_data = (domain_ref['id'], None, None) + elif 'trust' in self.auth['scope']: + trust_ref = self._lookup_trust(self.auth['scope']['trust']) + #TODO ayoung when trusts support domain, Fill in domain data here + if 'project_id' in trust_ref: + project_ref = self._lookup_project( + {'id': trust_ref['project_id']}) + self._scope_data = (None, project_ref['id'], trust_ref) + else: + self._scope_data = (None, None, trust_ref) def _validate_auth_methods(self): # make sure auth methods are provided @@ -236,20 +252,31 @@ class AuthInfo(object): Verify and return the scoping information. - :returns: (domain_id, project_id). If scope to a project, - (None, project_id) will be returned. If scope to a domain, - (domain_id, None) will be returned. If unscope, - (None, None) will be returned. + :returns: (domain_id, project_id, trust_ref). + If scope to a project, (None, project_id, None) + will be returned. + If scoped to a domain, (domain_id, None,None) + will be returned. + If scoped to a trust, (None, project_id, trust_ref), + Will be returned, where the project_id comes from the + trust definition. + If unscoped, (None, None, None) will be returned. """ return self._scope_data - def set_scope(self, domain_id=None, project_id=None): + def set_scope(self, domain_id=None, project_id=None, trust=None): """ Set scope information. """ if domain_id and project_id: msg = _('Scoping to both domain and project is not allowed') raise ValueError(msg) - self._scope_data = (domain_id, project_id) + if domain_id and trust: + msg = _('Scoping to both domain and trust is not allowed') + raise ValueError(msg) + if project_id and trust: + msg = _('Scoping to both project and trust is not allowed') + raise ValueError(msg) + self._scope_data = (domain_id, project_id, trust) class Auth(controller.V3Controller): @@ -278,8 +305,10 @@ class Auth(controller.V3Controller): raise exception.Unauthorized(e) def _check_and_set_default_scoping(self, context, auth_info, auth_context): - (domain_id, project_id) = auth_info.get_scope() - if domain_id or project_id: + (domain_id, project_id, trust) = auth_info.get_scope() + if trust: + project_id = trust['project_id'] + if domain_id or project_id or trust: # scope is specified return diff --git a/keystone/auth/methods/token.py b/keystone/auth/methods/token.py index 10e99cfb..bb7b8d58 100644 --- a/keystone/auth/methods/token.py +++ b/keystone/auth/methods/token.py @@ -48,6 +48,8 @@ class Token(auth.AuthMethodHandler): token_ref['token_data']['token']['extras']) user_context['method_names'].extend( token_ref['token_data']['token']['methods']) + if 'trust' in token_ref['token_data']: + raise exception.Forbidden(e) except AssertionError as e: LOG.error(e) raise exception.Unauthorized(e) diff --git a/keystone/auth/token_factory.py b/keystone/auth/token_factory.py index d6dc68f9..8460aec6 100644 --- a/keystone/auth/token_factory.py +++ b/keystone/auth/token_factory.py @@ -28,6 +28,7 @@ from keystone import config from keystone import exception from keystone import identity from keystone import token as token_module +from keystone import trust from keystone.openstack.common import jsonutils from keystone.openstack.common import timeutils @@ -42,6 +43,7 @@ class TokenDataHelper(object): def __init__(self, context): self.identity_api = identity.Manager() self.catalog_api = catalog.Manager() + self.trust_api = trust.Manager() self.context = context def _get_filtered_domain(self, domain_id): @@ -100,33 +102,77 @@ class TokenDataHelper(object): roles = self._get_project_roles_for_user(user_id, project_id) return roles - def _populate_user(self, token_data, user_id, domain_id, project_id): + def _populate_user(self, token_data, user_id, domain_id, project_id, + trust): user_ref = self.identity_api.get_user(self.context, user_id) + if trust: + trustor_user_ref = (self.identity_api.get_user(self.context, + trust['trustor_user_id'])) + if not trustor_user_ref['enabled']: + raise exception.Forbidden() + if trust['impersonation']: + user_ref = trustor_user_ref + token_data['trust'] = ( + { + 'id': trust['id'], + 'trustor_user': {'id': trust['trustor_user_id']}, + 'trustee_user': {'id': trust['trustee_user_id']}, + 'impersonation': trust['impersonation'] + }) filtered_user = { 'id': user_ref['id'], 'name': user_ref['name'], 'domain': self._get_filtered_domain(user_ref['domain_id'])} token_data['user'] = filtered_user - def _populate_roles(self, token_data, user_id, domain_id, project_id): - if domain_id or project_id: - roles = self._get_roles_for_user(user_id, domain_id, project_id) - # we only care about id and name + def _populate_roles(self, token_data, user_id, domain_id, project_id, + trust): + if trust: + token_user_id = trust['trustor_user_id'] + token_project_id = trust['project_id'] + #trusts do not support domains yet + token_domain_id = None + else: + token_user_id = user_id + token_project_id = project_id + token_domain_id = domain_id + + if token_domain_id or token_project_id: + roles = self._get_roles_for_user(token_user_id, + token_domain_id, + token_project_id) filtered_roles = [] - for role in roles: - filtered_roles.append({'id': role['id'], 'name': role['name']}) + if trust: + for trust_role in trust['roles']: + match_roles = [x for x in roles + if x['id'] == trust_role['id']] + if match_roles: + filtered_roles.append(match_roles[0]) + else: + raise exception.Forbidden() + else: + for role in roles: + filtered_roles.append({'id': role['id'], + 'name': role['name']}) token_data['roles'] = filtered_roles def _populate_service_catalog(self, token_data, user_id, - domain_id, project_id): + domain_id, project_id, trust): + if trust: + user_id = trust['trustor_user_id'] if project_id or domain_id: - service_catalog = self.catalog_api.get_v3_catalog( - self.context, user_id, project_id) + try: + service_catalog = self.catalog_api.get_v3_catalog( + self.context, user_id, project_id) + #TODO KVS backend needs a sample implementation + except exception.NotImplemented: + service_catalog = {} # TODO(gyee): v3 service catalog is not quite completed yet + #TODO Enforce Endpoints for trust token_data['catalog'] = service_catalog - def _populate_token(self, token_data, expires=None): + def _populate_token(self, token_data, expires=None, trust=None): if not expires: expires = token_module.default_expire_time() if not isinstance(expires, basestring): @@ -135,15 +181,20 @@ class TokenDataHelper(object): token_data['issued_at'] = timeutils.isotime(subsecond=True) def get_token_data(self, user_id, method_names, extras, - domain_id=None, project_id=None, expires=None): + domain_id=None, project_id=None, expires=None, + trust=None): token_data = {'methods': method_names, 'extras': extras} + if trust: + if user_id != trust['trustee_user_id']: + raise exception.Forbidden() + self._populate_scope(token_data, domain_id, project_id) - self._populate_user(token_data, user_id, domain_id, project_id) - self._populate_roles(token_data, user_id, domain_id, project_id) + self._populate_user(token_data, user_id, domain_id, project_id, trust) + self._populate_roles(token_data, user_id, domain_id, project_id, trust) self._populate_service_catalog(token_data, user_id, domain_id, - project_id) - self._populate_token(token_data, expires) + project_id, trust) + self._populate_token(token_data, expires, trust) return {'token': token_data} @@ -189,7 +240,7 @@ def recreate_token_data(context, token_data=None, expires=None, def create_token(context, auth_context, auth_info): token_data_helper = TokenDataHelper(context) - (domain_id, project_id) = auth_info.get_scope() + (domain_id, project_id, trust) = auth_info.get_scope() method_names = list(set(auth_info.get_method_names() + auth_context.get('method_names', []))) token_data = token_data_helper.get_token_data( @@ -198,7 +249,9 @@ def create_token(context, auth_context, auth_info): auth_context['extras'], domain_id, project_id, - auth_context.get('expires_at', None)) + auth_context.get('expires_at', None), + trust) + if CONF.signing.token_format == 'UUID': token_id = uuid.uuid4().hex elif CONF.signing.token_format == 'PKI': @@ -214,7 +267,7 @@ def create_token(context, auth_context, auth_info): try: expiry = token_data['token']['expires_at'] if isinstance(expiry, basestring): - expiry = timeutils.parse_isotime(expiry) + expiry = timeutils.normalize_time(timeutils.parse_isotime(expiry)) role_ids = [] if 'project' in token_data['token']: # project-scoped token, fill in the v2 token data |