summaryrefslogtreecommitdiffstats
path: root/keystone/auth
diff options
context:
space:
mode:
authorAdam Young <ayoung@redhat.com>2013-02-26 14:54:32 -0500
committerGerrit Code Review <review@openstack.org>2013-03-05 19:35:38 +0000
commit601eeb50b60a2e99041690fe19238202bc203503 (patch)
treef1be4ab425ba9150b61c1e1c6c5bd1a67f180021 /keystone/auth
parentab6e5529513af656db512b888fed9b320391afbd (diff)
downloadkeystone-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.py85
-rw-r--r--keystone/auth/methods/token.py2
-rw-r--r--keystone/auth/token_factory.py91
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