summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDolph Mathews <dolph.mathews@gmail.com>2012-12-11 14:40:27 -0600
committerDolph Mathews <dolph.mathews@gmail.com>2012-12-12 07:47:46 -0600
commit4e2be8a8880f03b1c6d1dc663d7259dbb45ddf67 (patch)
tree58fc05007116a591f96c8e19288d0aade27a1581
parent6397580a52be5288b4cb5e0a86a8c340fe4fd0ae (diff)
downloadkeystone-4e2be8a8880f03b1c6d1dc663d7259dbb45ddf67.tar.gz
keystone-4e2be8a8880f03b1c6d1dc663d7259dbb45ddf67.tar.xz
keystone-4e2be8a8880f03b1c6d1dc663d7259dbb45ddf67.zip
Move token controller into keystone.token
Change-Id: Ie8277529185f645854e0aebaafa173c06a7c5164
-rw-r--r--keystone/common/controller.py12
-rw-r--r--keystone/contrib/ec2/core.py5
-rw-r--r--keystone/service.py556
-rw-r--r--keystone/token/__init__.py1
-rw-r--r--keystone/token/controllers.py545
-rw-r--r--tests/test_auth.py (renamed from tests/test_service.py)37
6 files changed, 589 insertions, 567 deletions
diff --git a/keystone/common/controller.py b/keystone/common/controller.py
index 5db57b61..5f351411 100644
--- a/keystone/common/controller.py
+++ b/keystone/common/controller.py
@@ -55,15 +55,19 @@ def protected(f):
return wrapper
-class V3Controller(wsgi.Application):
- """Base controller class for Identity API v3."""
+class V2Controller(wsgi.Application):
+ """Base controller class for Identity API v2."""
- def __init__(self, catalog_api, identity_api, token_api, policy_api):
+ def __init__(self, catalog_api, identity_api, policy_api, token_api):
self.catalog_api = catalog_api
self.identity_api = identity_api
self.policy_api = policy_api
self.token_api = token_api
- super(V3Controller, self).__init__()
+ super(V2Controller, self).__init__()
+
+
+class V3Controller(V2Controller):
+ """Base controller class for Identity API v3."""
def _paginate(self, context, refs):
"""Paginates a list of references by page & per_page query strings."""
diff --git a/keystone/contrib/ec2/core.py b/keystone/contrib/ec2/core.py
index d9e9eaae..078a845c 100644
--- a/keystone/contrib/ec2/core.py
+++ b/keystone/contrib/ec2/core.py
@@ -44,7 +44,6 @@ from keystone import config
from keystone import exception
from keystone import identity
from keystone import policy
-from keystone import service
from keystone import token
@@ -190,12 +189,10 @@ class Ec2Controller(wsgi.Application):
tenant=tenant_ref,
metadata=metadata_ref))
- # TODO(termie): make this a util function or something
# TODO(termie): i don't think the ec2 middleware currently expects a
# full return, but it contains a note saying that it
# would be better to expect a full return
- token_controller = service.TokenController()
- return token_controller._format_authenticate(
+ return token.controllers.Auth.format_authenticate(
token_ref, roles_ref, catalog_ref)
def create_credential(self, context, user_id, tenant_id):
diff --git a/keystone/service.py b/keystone/service.py
index 67167604..f103aac3 100644
--- a/keystone/service.py
+++ b/keystone/service.py
@@ -14,18 +14,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
import routes
-import json
-from keystone import config
from keystone import catalog
-from keystone.common import cms
from keystone.common import logging
from keystone.common import wsgi
from keystone import exception
from keystone import identity
-from keystone.openstack.common import timeutils
from keystone import policy
from keystone import token
@@ -207,13 +202,19 @@ class AdminRouter(wsgi.ComposingRouter):
def __init__(self):
mapper = routes.Mapper()
+ apis = dict(
+ catalog_api=catalog.Manager(),
+ identity_api=identity.Manager(),
+ policy_api=policy.Manager(),
+ token_api=token.Manager())
+
version_controller = VersionController('admin')
mapper.connect('/',
controller=version_controller,
action='get_version')
# Token Operations
- auth_controller = TokenController()
+ auth_controller = token.controllers.Auth(**apis)
mapper.connect('/tokens',
controller=auth_controller,
action='authenticate',
@@ -269,13 +270,19 @@ class PublicRouter(wsgi.ComposingRouter):
def __init__(self):
mapper = routes.Mapper()
+ apis = dict(
+ catalog_api=catalog.Manager(),
+ identity_api=identity.Manager(),
+ policy_api=policy.Manager(),
+ token_api=token.Manager())
+
version_controller = VersionController('public')
mapper.connect('/',
controller=version_controller,
action='get_version')
# Token Operations
- auth_controller = TokenController()
+ auth_controller = token.controllers.Auth(**apis)
mapper.connect('/tokens',
controller=auth_controller,
action='authenticate',
@@ -414,541 +421,6 @@ class NoopController(wsgi.Application):
return {}
-class ExternalAuthNotApplicable(Exception):
- """External authentication is not applicable"""
-
-
-class TokenController(wsgi.Application):
- def __init__(self):
- self.catalog_api = catalog.Manager()
- self.identity_api = identity.Manager()
- self.token_api = token.Manager()
- self.policy_api = policy.Manager()
- super(TokenController, self).__init__()
-
- def ca_cert(self, context, auth=None):
- ca_file = open(config.CONF.signing.ca_certs, 'r')
- data = ca_file.read()
- ca_file.close()
- return data
-
- def signing_cert(self, context, auth=None):
- cert_file = open(config.CONF.signing.certfile, 'r')
- data = cert_file.read()
- cert_file.close()
- return data
-
- def authenticate(self, context, auth=None):
- """Authenticate credentials and return a token.
-
- Accept auth as a dict that looks like::
-
- {
- "auth":{
- "passwordCredentials":{
- "username":"test_user",
- "password":"mypass"
- },
- "tenantName":"customer-x"
- }
- }
-
- In this case, tenant is optional, if not provided the token will be
- considered "unscoped" and can later be used to get a scoped token.
-
- Alternatively, this call accepts auth with only a token and tenant
- that will return a token that is scoped to that tenant.
- """
-
- if auth is None:
- raise exception.ValidationError(attribute='auth',
- target='request body')
-
- auth_token_data = None
-
- if "token" in auth:
- # Try to authenticate using a token
- auth_token_data, auth_info = self._authenticate_token(
- context, auth)
- else:
- # Try external authentication
- try:
- auth_token_data, auth_info = self._authenticate_external(
- context, auth)
- except ExternalAuthNotApplicable:
- # Try local authentication
- auth_token_data, auth_info = self._authenticate_local(
- context, auth)
-
- user_ref, tenant_ref, metadata_ref = auth_info
-
- # If the user is disabled don't allow them to authenticate
- if not user_ref.get('enabled', True):
- msg = 'User is disabled: %s' % user_ref['id']
- LOG.warning(msg)
- raise exception.Unauthorized(msg)
-
- # If the tenant is disabled don't allow them to authenticate
- if tenant_ref and not tenant_ref.get('enabled', True):
- msg = 'Tenant is disabled: %s' % tenant_ref['id']
- LOG.warning(msg)
- raise exception.Unauthorized(msg)
-
- if tenant_ref:
- catalog_ref = self.catalog_api.get_catalog(
- context=context,
- user_id=user_ref['id'],
- tenant_id=tenant_ref['id'],
- metadata=metadata_ref)
- else:
- catalog_ref = {}
-
- auth_token_data['id'] = 'placeholder'
-
- roles_ref = []
- for role_id in metadata_ref.get('roles', []):
- role_ref = self.identity_api.get_role(context, role_id)
- roles_ref.append(dict(name=role_ref['name']))
-
- token_data = self._format_token(auth_token_data, roles_ref)
-
- service_catalog = self._format_catalog(catalog_ref)
- token_data['access']['serviceCatalog'] = service_catalog
-
- if config.CONF.signing.token_format == 'UUID':
- token_id = uuid.uuid4().hex
- elif config.CONF.signing.token_format == 'PKI':
- token_id = cms.cms_sign_token(json.dumps(token_data),
- config.CONF.signing.certfile,
- config.CONF.signing.keyfile)
- else:
- raise exception.UnexpectedError(
- 'Invalid value for token_format: %s.'
- ' Allowed values are PKI or UUID.' %
- config.CONF.signing.token_format)
- try:
- self.token_api.create_token(
- context, token_id, dict(key=token_id,
- id=token_id,
- expires=auth_token_data['expires'],
- user=user_ref,
- tenant=tenant_ref,
- metadata=metadata_ref))
- except Exception as e:
- # an identical token may have been created already.
- # if so, return the token_data as it is also identical
- try:
- self.token_api.get_token(context=context,
- token_id=token_id)
- except exception.TokenNotFound:
- raise e
-
- token_data['access']['token']['id'] = token_id
-
- return token_data
-
- def _authenticate_token(self, context, auth):
- """Try to authenticate using an already existing token.
-
- Returns auth_token_data, (user_ref, tenant_ref, metadata_ref)
- """
- if 'token' not in auth:
- raise exception.ValidationError(
- attribute='token', target='auth')
-
- if "id" not in auth['token']:
- raise exception.ValidationError(
- attribute="id", target="token")
-
- old_token = auth['token']['id']
-
- try:
- old_token_ref = self.token_api.get_token(context=context,
- token_id=old_token)
- except exception.NotFound as e:
- raise exception.Unauthorized(e)
-
- user_ref = old_token_ref['user']
- user_id = user_ref['id']
-
- current_user_ref = self.identity_api.get_user(context=context,
- user_id=user_id)
-
- tenant_id = self._get_tenant_id_from_auth(context, auth)
-
- tenant_ref = self._get_tenant_ref(context, user_id, tenant_id)
- metadata_ref = self._get_metadata_ref(context, user_id, tenant_id)
-
- expiry = old_token_ref['expires']
- auth_token_data = self._get_auth_token_data(current_user_ref,
- tenant_ref,
- metadata_ref,
- expiry)
-
- return auth_token_data, (current_user_ref, tenant_ref, metadata_ref)
-
- def _authenticate_local(self, context, auth):
- """Try to authenticate against the identity backend.
-
- Returns auth_token_data, (user_ref, tenant_ref, metadata_ref)
- """
- if 'passwordCredentials' not in auth:
- raise exception.ValidationError(
- attribute='passwordCredentials', target='auth')
-
- if "password" not in auth['passwordCredentials']:
- raise exception.ValidationError(
- attribute='password', target='passwordCredentials')
-
- password = auth['passwordCredentials']['password']
-
- if ("userId" not in auth['passwordCredentials'] and
- "username" not in auth['passwordCredentials']):
- raise exception.ValidationError(
- attribute='username or userId',
- target='passwordCredentials')
-
- user_id = auth['passwordCredentials'].get('userId', None)
- username = auth['passwordCredentials'].get('username', '')
-
- if username:
- try:
- user_ref = self.identity_api.get_user_by_name(
- context=context, user_name=username)
- user_id = user_ref['id']
- except exception.UserNotFound as e:
- raise exception.Unauthorized(e)
-
- tenant_id = self._get_tenant_id_from_auth(context, auth)
-
- try:
- auth_info = self.identity_api.authenticate(
- context=context,
- user_id=user_id,
- password=password,
- tenant_id=tenant_id)
- except AssertionError as e:
- raise exception.Unauthorized(e)
- (user_ref, tenant_ref, metadata_ref) = auth_info
-
- expiry = self.token_api._get_default_expire_time(context=context)
- auth_token_data = self._get_auth_token_data(user_ref,
- tenant_ref,
- metadata_ref,
- expiry)
-
- return auth_token_data, (user_ref, tenant_ref, metadata_ref)
-
- def _authenticate_external(self, context, auth):
- """Try to authenticate an external user via REMOTE_USER variable.
-
- Returns auth_token_data, (user_ref, tenant_ref, metadata_ref)
- """
- if 'REMOTE_USER' not in context:
- raise ExternalAuthNotApplicable()
-
- username = context['REMOTE_USER']
- try:
- user_ref = self.identity_api.get_user_by_name(
- context=context, user_name=username)
- user_id = user_ref['id']
- except exception.UserNotFound as e:
- raise exception.Unauthorized(e)
-
- tenant_id = self._get_tenant_id_from_auth(context, auth)
-
- tenant_ref = self._get_tenant_ref(context, user_id, tenant_id)
- metadata_ref = self._get_metadata_ref(context, user_id, tenant_id)
-
- expiry = self.token_api._get_default_expire_time(context=context)
- auth_token_data = self._get_auth_token_data(user_ref,
- tenant_ref,
- metadata_ref,
- expiry)
-
- return auth_token_data, (user_ref, tenant_ref, metadata_ref)
-
- def _get_auth_token_data(self, user, tenant, metadata, expiry):
- return dict(dict(user=user,
- tenant=tenant,
- metadata=metadata,
- expires=expiry))
-
- def _get_tenant_id_from_auth(self, context, auth):
- """Extract tenant information from auth dict.
-
- Returns a valid tenant_id if it exists, or None if not specified.
- """
- tenant_id = auth.get('tenantId', None)
- tenant_name = auth.get('tenantName', None)
- if tenant_name:
- try:
- tenant_ref = self.identity_api.get_tenant_by_name(
- context=context, tenant_name=tenant_name)
- tenant_id = tenant_ref['id']
- except exception.TenantNotFound as e:
- raise exception.Unauthorized(e)
- return tenant_id
-
- def _get_tenant_ref(self, context, user_id, tenant_id):
- """Returns the tenant_ref for the user's tenant"""
- tenant_ref = None
- if tenant_id:
- tenants = self.identity_api.get_tenants_for_user(context, user_id)
- if tenant_id not in tenants:
- msg = 'User %s is unauthorized for tenant %s' % (
- user_id, tenant_id)
- LOG.warning(msg)
- raise exception.Unauthorized(msg)
-
- try:
- tenant_ref = self.identity_api.get_tenant(context=context,
- tenant_id=tenant_id)
- except exception.TenantNotFound as e:
- exception.Unauthorized(e)
- return tenant_ref
-
- def _get_metadata_ref(self, context, user_id, tenant_id):
- """Returns the metadata_ref for a user in a tenant"""
- metadata_ref = {}
- if tenant_id:
- try:
- metadata_ref = self.identity_api.get_metadata(
- context=context,
- user_id=user_id,
- tenant_id=tenant_id)
- except exception.MetadataNotFound:
- metadata_ref = {}
-
- return metadata_ref
-
- def _get_token_ref(self, context, token_id, belongs_to=None):
- """Returns a token if a valid one exists.
-
- Optionally, limited to a token owned by a specific tenant.
-
- """
- # TODO(termie): this stuff should probably be moved to middleware
- self.assert_admin(context)
-
- if cms.is_ans1_token(token_id):
- data = json.loads(cms.cms_verify(cms.token_to_cms(token_id),
- config.CONF.signing.certfile,
- config.CONF.signing.ca_certs))
- data['access']['token']['user'] = data['access']['user']
- data['access']['token']['metadata'] = data['access']['metadata']
- if belongs_to:
- assert data['access']['token']['tenant']['id'] == belongs_to
- token_ref = data['access']['token']
- else:
- token_ref = self.token_api.get_token(context=context,
- token_id=token_id)
- return token_ref
-
- # admin only
- def validate_token_head(self, context, token_id):
- """Check that a token is valid.
-
- Optionally, also ensure that it is owned by a specific tenant.
-
- Identical to ``validate_token``, except does not return a response.
-
- """
- belongs_to = context['query_string'].get('belongsTo')
- assert self._get_token_ref(context, token_id, belongs_to)
-
- # admin only
- def validate_token(self, context, token_id):
- """Check that a token is valid.
-
- Optionally, also ensure that it is owned by a specific tenant.
-
- Returns metadata about the token along any associated roles.
-
- """
- belongs_to = context['query_string'].get('belongsTo')
- token_ref = self._get_token_ref(context, token_id, belongs_to)
-
- # TODO(termie): optimize this call at some point and put it into the
- # the return for metadata
- # fill out the roles in the metadata
- metadata_ref = token_ref['metadata']
- roles_ref = []
- for role_id in metadata_ref.get('roles', []):
- roles_ref.append(self.identity_api.get_role(context, role_id))
-
- # Get a service catalog if possible
- # This is needed for on-behalf-of requests
- catalog_ref = None
- if token_ref.get('tenant'):
- catalog_ref = self.catalog_api.get_catalog(
- context=context,
- user_id=token_ref['user']['id'],
- tenant_id=token_ref['tenant']['id'],
- metadata=metadata_ref)
- return self._format_token(token_ref, roles_ref, catalog_ref)
-
- def delete_token(self, context, token_id):
- """Delete a token, effectively invalidating it for authz."""
- # TODO(termie): this stuff should probably be moved to middleware
- self.assert_admin(context)
- self.token_api.delete_token(context=context, token_id=token_id)
-
- def revocation_list(self, context, auth=None):
- self.assert_admin(context)
- tokens = self.token_api.list_revoked_tokens(context)
-
- for t in tokens:
- expires = t['expires']
- if not (expires and isinstance(expires, unicode)):
- t['expires'] = timeutils.isotime(expires)
- data = {'revoked': tokens}
- json_data = json.dumps(data)
- signed_text = cms.cms_sign_text(json_data,
- config.CONF.signing.certfile,
- config.CONF.signing.keyfile)
-
- return {'signed': signed_text}
-
- def endpoints(self, context, token_id):
- """Return a list of endpoints available to the token."""
- self.assert_admin(context)
-
- token_ref = self._get_token_ref(context, token_id)
-
- catalog_ref = None
- if token_ref.get('tenant'):
- catalog_ref = self.catalog_api.get_catalog(
- context=context,
- user_id=token_ref['user']['id'],
- tenant_id=token_ref['tenant']['id'],
- metadata=token_ref['metadata'])
-
- return self._format_endpoint_list(catalog_ref)
-
- def _format_authenticate(self, token_ref, roles_ref, catalog_ref):
- o = self._format_token(token_ref, roles_ref)
- o['access']['serviceCatalog'] = self._format_catalog(catalog_ref)
- return o
-
- def _format_token(self, token_ref, roles_ref, catalog_ref=None):
- user_ref = token_ref['user']
- metadata_ref = token_ref['metadata']
- expires = token_ref['expires']
- if expires is not None:
- if not isinstance(expires, unicode):
- expires = timeutils.isotime(expires)
- o = {'access': {'token': {'id': token_ref['id'],
- 'expires': expires,
- 'issued_at': timeutils.strtime()
- },
- 'user': {'id': user_ref['id'],
- 'name': user_ref['name'],
- 'username': user_ref['name'],
- 'roles': roles_ref,
- 'roles_links': metadata_ref.get('roles_links',
- [])
- }
- }
- }
- if 'tenant' in token_ref and token_ref['tenant']:
- token_ref['tenant']['enabled'] = True
- o['access']['token']['tenant'] = token_ref['tenant']
- if catalog_ref is not None:
- o['access']['serviceCatalog'] = self._format_catalog(catalog_ref)
- if metadata_ref:
- if 'is_admin' in metadata_ref:
- o['access']['metadata'] = {'is_admin':
- metadata_ref['is_admin']}
- else:
- o['access']['metadata'] = {'is_admin': 0}
- if 'roles' in metadata_ref:
- o['access']['metadata']['roles'] = metadata_ref['roles']
- return o
-
- def _format_catalog(self, catalog_ref):
- """Munge catalogs from internal to output format
- Internal catalogs look like:
-
- {$REGION: {
- {$SERVICE: {
- $key1: $value1,
- ...
- }
- }
- }
-
- The legacy api wants them to look like
-
- [{'name': $SERVICE[name],
- 'type': $SERVICE,
- 'endpoints': [{
- 'tenantId': $tenant_id,
- ...
- 'region': $REGION,
- }],
- 'endpoints_links': [],
- }]
-
- """
- if not catalog_ref:
- return []
-
- services = {}
- for region, region_ref in catalog_ref.iteritems():
- for service, service_ref in region_ref.iteritems():
- new_service_ref = services.get(service, {})
- new_service_ref['name'] = service_ref.pop('name')
- new_service_ref['type'] = service
- new_service_ref['endpoints_links'] = []
- service_ref['region'] = region
-
- endpoints_ref = new_service_ref.get('endpoints', [])
- endpoints_ref.append(service_ref)
-
- new_service_ref['endpoints'] = endpoints_ref
- services[service] = new_service_ref
-
- return services.values()
-
- def _format_endpoint_list(self, catalog_ref):
- """Formats a list of endpoints according to Identity API v2.
-
- The v2.0 API wants an endpoint list to look like::
-
- {
- 'endpoints': [
- {
- 'id': $endpoint_id,
- 'name': $SERVICE[name],
- 'type': $SERVICE,
- 'tenantId': $tenant_id,
- 'region': $REGION,
- }
- ],
- 'endpoints_links': [],
- }
-
- """
- if not catalog_ref:
- return {}
-
- endpoints = []
- for region_name, region_ref in catalog_ref.iteritems():
- for service_type, service_ref in region_ref.iteritems():
- endpoints.append({
- 'id': service_ref.get('id'),
- 'name': service_ref.get('name'),
- 'type': service_type,
- 'region': region_name,
- 'publicURL': service_ref.get('publicURL'),
- 'internalURL': service_ref.get('internalURL'),
- 'adminURL': service_ref.get('adminURL'),
- })
-
- return {'endpoints': endpoints, 'endpoints_links': []}
-
-
class ExtensionsController(wsgi.Application):
"""Base extensions controller to be extended by public and admin API's."""
diff --git a/keystone/token/__init__.py b/keystone/token/__init__.py
index 884f2658..b0765ece 100644
--- a/keystone/token/__init__.py
+++ b/keystone/token/__init__.py
@@ -15,3 +15,4 @@
# under the License.
from keystone.token.core import *
+from keystone.token import controllers
diff --git a/keystone/token/controllers.py b/keystone/token/controllers.py
new file mode 100644
index 00000000..329235ad
--- /dev/null
+++ b/keystone/token/controllers.py
@@ -0,0 +1,545 @@
+import uuid
+import json
+
+from keystone import config
+from keystone.common import cms
+from keystone.common import controller
+from keystone.common import logging
+from keystone import exception
+from keystone.openstack.common import timeutils
+
+
+LOG = logging.getLogger(__name__)
+
+
+class ExternalAuthNotApplicable(Exception):
+ """External authentication is not applicable"""
+ pass
+
+
+class Auth(controller.V2Controller):
+ def ca_cert(self, context, auth=None):
+ ca_file = open(config.CONF.signing.ca_certs, 'r')
+ data = ca_file.read()
+ ca_file.close()
+ return data
+
+ def signing_cert(self, context, auth=None):
+ cert_file = open(config.CONF.signing.certfile, 'r')
+ data = cert_file.read()
+ cert_file.close()
+ return data
+
+ def authenticate(self, context, auth=None):
+ """Authenticate credentials and return a token.
+
+ Accept auth as a dict that looks like::
+
+ {
+ "auth":{
+ "passwordCredentials":{
+ "username":"test_user",
+ "password":"mypass"
+ },
+ "tenantName":"customer-x"
+ }
+ }
+
+ In this case, tenant is optional, if not provided the token will be
+ considered "unscoped" and can later be used to get a scoped token.
+
+ Alternatively, this call accepts auth with only a token and tenant
+ that will return a token that is scoped to that tenant.
+ """
+
+ if auth is None:
+ raise exception.ValidationError(attribute='auth',
+ target='request body')
+
+ auth_token_data = None
+
+ if "token" in auth:
+ # Try to authenticate using a token
+ auth_token_data, auth_info = self._authenticate_token(
+ context, auth)
+ else:
+ # Try external authentication
+ try:
+ auth_token_data, auth_info = self._authenticate_external(
+ context, auth)
+ except ExternalAuthNotApplicable:
+ # Try local authentication
+ auth_token_data, auth_info = self._authenticate_local(
+ context, auth)
+
+ user_ref, tenant_ref, metadata_ref = auth_info
+
+ # If the user is disabled don't allow them to authenticate
+ if not user_ref.get('enabled', True):
+ msg = 'User is disabled: %s' % user_ref['id']
+ LOG.warning(msg)
+ raise exception.Unauthorized(msg)
+
+ # If the tenant is disabled don't allow them to authenticate
+ if tenant_ref and not tenant_ref.get('enabled', True):
+ msg = 'Tenant is disabled: %s' % tenant_ref['id']
+ LOG.warning(msg)
+ raise exception.Unauthorized(msg)
+
+ if tenant_ref:
+ catalog_ref = self.catalog_api.get_catalog(
+ context=context,
+ user_id=user_ref['id'],
+ tenant_id=tenant_ref['id'],
+ metadata=metadata_ref)
+ else:
+ catalog_ref = {}
+
+ auth_token_data['id'] = 'placeholder'
+
+ roles_ref = []
+ for role_id in metadata_ref.get('roles', []):
+ role_ref = self.identity_api.get_role(context, role_id)
+ roles_ref.append(dict(name=role_ref['name']))
+
+ token_data = Auth.format_token(auth_token_data, roles_ref)
+
+ service_catalog = Auth.format_catalog(catalog_ref)
+ token_data['access']['serviceCatalog'] = service_catalog
+
+ if config.CONF.signing.token_format == 'UUID':
+ token_id = uuid.uuid4().hex
+ elif config.CONF.signing.token_format == 'PKI':
+ token_id = cms.cms_sign_token(json.dumps(token_data),
+ config.CONF.signing.certfile,
+ config.CONF.signing.keyfile)
+ else:
+ raise exception.UnexpectedError(
+ 'Invalid value for token_format: %s.'
+ ' Allowed values are PKI or UUID.' %
+ config.CONF.signing.token_format)
+ try:
+ self.token_api.create_token(
+ context, token_id, dict(key=token_id,
+ id=token_id,
+ expires=auth_token_data['expires'],
+ user=user_ref,
+ tenant=tenant_ref,
+ metadata=metadata_ref))
+ except Exception as e:
+ # an identical token may have been created already.
+ # if so, return the token_data as it is also identical
+ try:
+ self.token_api.get_token(context=context,
+ token_id=token_id)
+ except exception.TokenNotFound:
+ raise e
+
+ token_data['access']['token']['id'] = token_id
+
+ return token_data
+
+ def _authenticate_token(self, context, auth):
+ """Try to authenticate using an already existing token.
+
+ Returns auth_token_data, (user_ref, tenant_ref, metadata_ref)
+ """
+ if 'token' not in auth:
+ raise exception.ValidationError(
+ attribute='token', target='auth')
+
+ if "id" not in auth['token']:
+ raise exception.ValidationError(
+ attribute="id", target="token")
+
+ old_token = auth['token']['id']
+
+ try:
+ old_token_ref = self.token_api.get_token(context=context,
+ token_id=old_token)
+ except exception.NotFound as e:
+ raise exception.Unauthorized(e)
+
+ user_ref = old_token_ref['user']
+ user_id = user_ref['id']
+
+ current_user_ref = self.identity_api.get_user(context=context,
+ user_id=user_id)
+
+ tenant_id = self._get_tenant_id_from_auth(context, auth)
+
+ tenant_ref = self._get_tenant_ref(context, user_id, tenant_id)
+ metadata_ref = self._get_metadata_ref(context, user_id, tenant_id)
+
+ expiry = old_token_ref['expires']
+ auth_token_data = self._get_auth_token_data(current_user_ref,
+ tenant_ref,
+ metadata_ref,
+ expiry)
+
+ return auth_token_data, (current_user_ref, tenant_ref, metadata_ref)
+
+ def _authenticate_local(self, context, auth):
+ """Try to authenticate against the identity backend.
+
+ Returns auth_token_data, (user_ref, tenant_ref, metadata_ref)
+ """
+ if 'passwordCredentials' not in auth:
+ raise exception.ValidationError(
+ attribute='passwordCredentials', target='auth')
+
+ if "password" not in auth['passwordCredentials']:
+ raise exception.ValidationError(
+ attribute='password', target='passwordCredentials')
+
+ password = auth['passwordCredentials']['password']
+
+ if ("userId" not in auth['passwordCredentials'] and
+ "username" not in auth['passwordCredentials']):
+ raise exception.ValidationError(
+ attribute='username or userId',
+ target='passwordCredentials')
+
+ user_id = auth['passwordCredentials'].get('userId', None)
+ username = auth['passwordCredentials'].get('username', '')
+
+ if username:
+ try:
+ user_ref = self.identity_api.get_user_by_name(
+ context=context, user_name=username)
+ user_id = user_ref['id']
+ except exception.UserNotFound as e:
+ raise exception.Unauthorized(e)
+
+ tenant_id = self._get_tenant_id_from_auth(context, auth)
+
+ try:
+ auth_info = self.identity_api.authenticate(
+ context=context,
+ user_id=user_id,
+ password=password,
+ tenant_id=tenant_id)
+ except AssertionError as e:
+ raise exception.Unauthorized(e)
+ (user_ref, tenant_ref, metadata_ref) = auth_info
+
+ expiry = self.token_api._get_default_expire_time(context=context)
+ auth_token_data = self._get_auth_token_data(user_ref,
+ tenant_ref,
+ metadata_ref,
+ expiry)
+
+ return auth_token_data, (user_ref, tenant_ref, metadata_ref)
+
+ def _authenticate_external(self, context, auth):
+ """Try to authenticate an external user via REMOTE_USER variable.
+
+ Returns auth_token_data, (user_ref, tenant_ref, metadata_ref)
+ """
+ if 'REMOTE_USER' not in context:
+ raise ExternalAuthNotApplicable()
+
+ username = context['REMOTE_USER']
+ try:
+ user_ref = self.identity_api.get_user_by_name(
+ context=context, user_name=username)
+ user_id = user_ref['id']
+ except exception.UserNotFound as e:
+ raise exception.Unauthorized(e)
+
+ tenant_id = self._get_tenant_id_from_auth(context, auth)
+
+ tenant_ref = self._get_tenant_ref(context, user_id, tenant_id)
+ metadata_ref = self._get_metadata_ref(context, user_id, tenant_id)
+
+ expiry = self.token_api._get_default_expire_time(context=context)
+ auth_token_data = self._get_auth_token_data(user_ref,
+ tenant_ref,
+ metadata_ref,
+ expiry)
+
+ return auth_token_data, (user_ref, tenant_ref, metadata_ref)
+
+ def _get_auth_token_data(self, user, tenant, metadata, expiry):
+ return dict(dict(user=user,
+ tenant=tenant,
+ metadata=metadata,
+ expires=expiry))
+
+ def _get_tenant_id_from_auth(self, context, auth):
+ """Extract tenant information from auth dict.
+
+ Returns a valid tenant_id if it exists, or None if not specified.
+ """
+ tenant_id = auth.get('tenantId', None)
+ tenant_name = auth.get('tenantName', None)
+ if tenant_name:
+ try:
+ tenant_ref = self.identity_api.get_tenant_by_name(
+ context=context, tenant_name=tenant_name)
+ tenant_id = tenant_ref['id']
+ except exception.TenantNotFound as e:
+ raise exception.Unauthorized(e)
+ return tenant_id
+
+ def _get_tenant_ref(self, context, user_id, tenant_id):
+ """Returns the tenant_ref for the user's tenant"""
+ tenant_ref = None
+ if tenant_id:
+ tenants = self.identity_api.get_tenants_for_user(context, user_id)
+ if tenant_id not in tenants:
+ msg = 'User %s is unauthorized for tenant %s' % (
+ user_id, tenant_id)
+ LOG.warning(msg)
+ raise exception.Unauthorized(msg)
+
+ try:
+ tenant_ref = self.identity_api.get_tenant(context=context,
+ tenant_id=tenant_id)
+ except exception.TenantNotFound as e:
+ exception.Unauthorized(e)
+ return tenant_ref
+
+ def _get_metadata_ref(self, context, user_id, tenant_id):
+ """Returns the metadata_ref for a user in a tenant"""
+ metadata_ref = {}
+ if tenant_id:
+ try:
+ metadata_ref = self.identity_api.get_metadata(
+ context=context,
+ user_id=user_id,
+ tenant_id=tenant_id)
+ except exception.MetadataNotFound:
+ metadata_ref = {}
+
+ return metadata_ref
+
+ def _get_token_ref(self, context, token_id, belongs_to=None):
+ """Returns a token if a valid one exists.
+
+ Optionally, limited to a token owned by a specific tenant.
+
+ """
+ # TODO(termie): this stuff should probably be moved to middleware
+ self.assert_admin(context)
+
+ if cms.is_ans1_token(token_id):
+ data = json.loads(cms.cms_verify(cms.token_to_cms(token_id),
+ config.CONF.signing.certfile,
+ config.CONF.signing.ca_certs))
+ data['access']['token']['user'] = data['access']['user']
+ data['access']['token']['metadata'] = data['access']['metadata']
+ if belongs_to:
+ assert data['access']['token']['tenant']['id'] == belongs_to
+ token_ref = data['access']['token']
+ else:
+ token_ref = self.token_api.get_token(context=context,
+ token_id=token_id)
+ return token_ref
+
+ # admin only
+ def validate_token_head(self, context, token_id):
+ """Check that a token is valid.
+
+ Optionally, also ensure that it is owned by a specific tenant.
+
+ Identical to ``validate_token``, except does not return a response.
+
+ """
+ belongs_to = context['query_string'].get('belongsTo')
+ assert self._get_token_ref(context, token_id, belongs_to)
+
+ # admin only
+ def validate_token(self, context, token_id):
+ """Check that a token is valid.
+
+ Optionally, also ensure that it is owned by a specific tenant.
+
+ Returns metadata about the token along any associated roles.
+
+ """
+ belongs_to = context['query_string'].get('belongsTo')
+ token_ref = self._get_token_ref(context, token_id, belongs_to)
+
+ # TODO(termie): optimize this call at some point and put it into the
+ # the return for metadata
+ # fill out the roles in the metadata
+ metadata_ref = token_ref['metadata']
+ roles_ref = []
+ for role_id in metadata_ref.get('roles', []):
+ roles_ref.append(self.identity_api.get_role(context, role_id))
+
+ # Get a service catalog if possible
+ # This is needed for on-behalf-of requests
+ catalog_ref = None
+ if token_ref.get('tenant'):
+ catalog_ref = self.catalog_api.get_catalog(
+ context=context,
+ user_id=token_ref['user']['id'],
+ tenant_id=token_ref['tenant']['id'],
+ metadata=metadata_ref)
+ return Auth.format_token(token_ref, roles_ref, catalog_ref)
+
+ def delete_token(self, context, token_id):
+ """Delete a token, effectively invalidating it for authz."""
+ # TODO(termie): this stuff should probably be moved to middleware
+ self.assert_admin(context)
+ self.token_api.delete_token(context=context, token_id=token_id)
+
+ def revocation_list(self, context, auth=None):
+ self.assert_admin(context)
+ tokens = self.token_api.list_revoked_tokens(context)
+
+ for t in tokens:
+ expires = t['expires']
+ if not (expires and isinstance(expires, unicode)):
+ t['expires'] = timeutils.isotime(expires)
+ data = {'revoked': tokens}
+ json_data = json.dumps(data)
+ signed_text = cms.cms_sign_text(json_data,
+ config.CONF.signing.certfile,
+ config.CONF.signing.keyfile)
+
+ return {'signed': signed_text}
+
+ def endpoints(self, context, token_id):
+ """Return a list of endpoints available to the token."""
+ self.assert_admin(context)
+
+ token_ref = self._get_token_ref(context, token_id)
+
+ catalog_ref = None
+ if token_ref.get('tenant'):
+ catalog_ref = self.catalog_api.get_catalog(
+ context=context,
+ user_id=token_ref['user']['id'],
+ tenant_id=token_ref['tenant']['id'],
+ metadata=token_ref['metadata'])
+
+ return Auth.format_endpoint_list(catalog_ref)
+
+ @classmethod
+ def format_authenticate(cls, token_ref, roles_ref, catalog_ref):
+ o = Auth.format_token(token_ref, roles_ref)
+ o['access']['serviceCatalog'] = Auth.format_catalog(catalog_ref)
+ return o
+
+ @classmethod
+ def format_token(cls, token_ref, roles_ref, catalog_ref=None):
+ user_ref = token_ref['user']
+ metadata_ref = token_ref['metadata']
+ expires = token_ref['expires']
+ if expires is not None:
+ if not isinstance(expires, unicode):
+ expires = timeutils.isotime(expires)
+ o = {'access': {'token': {'id': token_ref['id'],
+ 'expires': expires,
+ 'issued_at': timeutils.strtime()
+ },
+ 'user': {'id': user_ref['id'],
+ 'name': user_ref['name'],
+ 'username': user_ref['name'],
+ 'roles': roles_ref,
+ 'roles_links': metadata_ref.get('roles_links',
+ [])
+ }
+ }
+ }
+ if 'tenant' in token_ref and token_ref['tenant']:
+ token_ref['tenant']['enabled'] = True
+ o['access']['token']['tenant'] = token_ref['tenant']
+ if catalog_ref is not None:
+ o['access']['serviceCatalog'] = Auth.format_catalog(catalog_ref)
+ if metadata_ref:
+ if 'is_admin' in metadata_ref:
+ o['access']['metadata'] = {'is_admin':
+ metadata_ref['is_admin']}
+ else:
+ o['access']['metadata'] = {'is_admin': 0}
+ if 'roles' in metadata_ref:
+ o['access']['metadata']['roles'] = metadata_ref['roles']
+ return o
+
+ @classmethod
+ def format_catalog(cls, catalog_ref):
+ """Munge catalogs from internal to output format
+ Internal catalogs look like:
+
+ {$REGION: {
+ {$SERVICE: {
+ $key1: $value1,
+ ...
+ }
+ }
+ }
+
+ The legacy api wants them to look like
+
+ [{'name': $SERVICE[name],
+ 'type': $SERVICE,
+ 'endpoints': [{
+ 'tenantId': $tenant_id,
+ ...
+ 'region': $REGION,
+ }],
+ 'endpoints_links': [],
+ }]
+
+ """
+ if not catalog_ref:
+ return []
+
+ services = {}
+ for region, region_ref in catalog_ref.iteritems():
+ for service, service_ref in region_ref.iteritems():
+ new_service_ref = services.get(service, {})
+ new_service_ref['name'] = service_ref.pop('name')
+ new_service_ref['type'] = service
+ new_service_ref['endpoints_links'] = []
+ service_ref['region'] = region
+
+ endpoints_ref = new_service_ref.get('endpoints', [])
+ endpoints_ref.append(service_ref)
+
+ new_service_ref['endpoints'] = endpoints_ref
+ services[service] = new_service_ref
+
+ return services.values()
+
+ @classmethod
+ def format_endpoint_list(cls, catalog_ref):
+ """Formats a list of endpoints according to Identity API v2.
+
+ The v2.0 API wants an endpoint list to look like::
+
+ {
+ 'endpoints': [
+ {
+ 'id': $endpoint_id,
+ 'name': $SERVICE[name],
+ 'type': $SERVICE,
+ 'tenantId': $tenant_id,
+ 'region': $REGION,
+ }
+ ],
+ 'endpoints_links': [],
+ }
+
+ """
+ if not catalog_ref:
+ return {}
+
+ endpoints = []
+ for region_name, region_ref in catalog_ref.iteritems():
+ for service_type, service_ref in region_ref.iteritems():
+ endpoints.append({
+ 'id': service_ref.get('id'),
+ 'name': service_ref.get('name'),
+ 'type': service_type,
+ 'region': region_name,
+ 'publicURL': service_ref.get('publicURL'),
+ 'internalURL': service_ref.get('internalURL'),
+ 'adminURL': service_ref.get('adminURL'),
+ })
+
+ return {'endpoints': endpoints, 'endpoints_links': []}
diff --git a/tests/test_service.py b/tests/test_auth.py
index c256e7e0..c0dbbd2c 100644
--- a/tests/test_service.py
+++ b/tests/test_auth.py
@@ -17,13 +17,15 @@ import uuid
import default_fixtures
+from keystone import catalog
from keystone import config
from keystone import exception
from keystone import identity
-from keystone import service
-from keystone import test
from keystone.identity.backends import kvs as kvs_identity
from keystone.openstack.common import timeutils
+from keystone import policy
+from keystone import test
+from keystone import token
CONF = config.CONF
@@ -50,12 +52,19 @@ def _build_user_auth(token=None, username=None,
return auth_json
-class TokenControllerTest(test.TestCase):
+class AuthTest(test.TestCase):
def setUp(self):
- super(TokenControllerTest, self).setUp()
+ super(AuthTest, self).setUp()
+
+ # load_fixtures checks for 'identity_api' to be defined
self.identity_api = kvs_identity.Identity()
self.load_fixtures(default_fixtures)
- self.api = service.TokenController()
+
+ self.api = token.controllers.Auth(
+ catalog_api=catalog.Manager(),
+ identity_api=identity.Manager(),
+ policy_api=policy.Manager(),
+ token_api=token.Manager())
def assertEqualTokens(self, a, b):
"""Assert that two tokens are equal.
@@ -78,7 +87,7 @@ class TokenControllerTest(test.TestCase):
return self.assertDictEqual(normalize(a), normalize(b))
-class AuthBadRequests(TokenControllerTest):
+class AuthBadRequests(AuthTest):
def setUp(self):
super(AuthBadRequests, self).setUp()
@@ -86,7 +95,7 @@ class AuthBadRequests(TokenControllerTest):
"""Verify that _authenticate_external() raises exception if
not applicable"""
self.assertRaises(
- service.ExternalAuthNotApplicable,
+ token.controllers.ExternalAuthNotApplicable,
self.api._authenticate_external,
{}, {})
@@ -121,7 +130,7 @@ class AuthBadRequests(TokenControllerTest):
{}, {'auth': 'abcd'})
-class AuthWithToken(TokenControllerTest):
+class AuthWithToken(AuthTest):
def setUp(self):
super(AuthWithToken, self).setUp()
@@ -179,7 +188,7 @@ class AuthWithToken(TokenControllerTest):
self.assertEquals(tenant["id"], self.tenant_bar['id'])
-class AuthWithPasswordCredentials(TokenControllerTest):
+class AuthWithPasswordCredentials(AuthTest):
def setUp(self):
super(AuthWithPasswordCredentials, self).setUp()
@@ -236,7 +245,7 @@ class AuthWithPasswordCredentials(TokenControllerTest):
{}, body_dict)
-class AuthWithRemoteUser(TokenControllerTest):
+class AuthWithRemoteUser(AuthTest):
def setUp(self):
super(AuthWithRemoteUser, self).setUp()
@@ -303,13 +312,7 @@ class AuthWithRemoteUser(TokenControllerTest):
body_dict)
-class TokenExpirationTest(test.TestCase):
- def setUp(self):
- super(TokenExpirationTest, self).setUp()
- self.identity_api = kvs_identity.Identity()
- self.load_fixtures(default_fixtures)
- self.api = service.TokenController()
-
+class TokenExpirationTest(AuthTest):
def _maintain_token_expiration(self):
"""Token expiration should be maintained after re-auth & validation."""
r = self.api.authenticate(