Fix the belongsTo query parameter

The belongsTo query parameter is only supported by the v2.0
token validation API. It would check the ID of the project passed
to the belongsTo parameter against the project a token was scoped to.

This commit corrects the implementation, tests, and adds
documentation. It also moves the check to keystone.token.controller
since belongsTo is a v2-ism and doesn't belong in the
keystone.token.provider.

Closes-Bug: 1627085
Closes-Bug: 1626794
Change-Id: I4a06a498112b81093d7e5ef3142bb1e2d0f78138
This commit is contained in:
Lance Bragstad 2016-09-22 20:29:46 +00:00
parent dc9a1d5f70
commit 7f3f596351
6 changed files with 63 additions and 40 deletions

View File

@ -113,6 +113,10 @@ Validates a token and confirms that it belongs to a tenant.
Returns the permissions relevant to a particular client. Valid
tokens are in the ``/tokens/{tokenId}`` path. If the token is not
valid, this call returns the ``itemNotFound (404)`` response code.
This method supports an optional parameter ``belongsTo`` to check
the token scope against the ID of a project. If the token does
not belong to the project specified in the parameter a
``unauthorized (401)`` response code will be returned.
Normal response codes: 200,203
Error response codes: 413,405,404,403,401,400,503
@ -123,6 +127,7 @@ Request
.. rest_parameters:: parameters.yaml
- tokenId: tokenId
- belongsTo: belongsTo
Response Example
----------------
@ -137,6 +142,10 @@ Validate token (admin)
.. rest_method:: HEAD /v2.0/tokens/{tokenId}
Validates a token and confirms that it belongs to a tenant, for performance.
This method supports an optional parameter ``belongsTo`` to check
the token scope against the ID of a project. If the token does
not belong to the project specified in the parameter a
``unauthorized (401)`` response code will be returned.
Normal response codes: 200,203,204
Error response codes: 413,405,404,403,401,400,503
@ -147,6 +156,7 @@ Request
.. rest_parameters:: parameters.yaml
- tokenId: tokenId
- belongsTo: belongsTo
Delete token

View File

@ -47,6 +47,12 @@ userId:
type: string
# variables in query
belongsTo:
description: |
Project ID to check against token scope.
in: query
required: false
type: string
tenant_name_query:
description: |
Filters the response by a tenant name.

View File

@ -410,34 +410,53 @@ class AuthWithToken(object):
}
})
unscoped_token_id = r['access']['token']['id']
query_string = 'belongsTo=%s' % self.tenant_bar['id']
self.assertRaises(
exception.Unauthorized,
self.controller.validate_token,
self.make_request(is_admin=True, query_string='belongsTo=BAR'),
self.make_request(is_admin=True, query_string=query_string),
token_id=unscoped_token_id)
self.assertRaises(
exception.Unauthorized,
self.controller.validate_token_head,
self.make_request(is_admin=True, query_string=query_string),
token_id=unscoped_token_id)
def test_belongs_to(self):
body_dict = _build_user_auth(
username='FOO',
password='foo2',
tenant_name="BAR")
tenant_name=self.tenant_bar['name'])
scoped_token = self.controller.authenticate(self.make_request(),
body_dict)
scoped_token_id = scoped_token['access']['token']['id']
query_string = 'belongsTo=%s' % uuid.uuid4().hex
self.assertRaises(
exception.Unauthorized,
self.controller.validate_token,
self.make_request(is_admin=True, query_string='belongsTo=me'),
self.make_request(is_admin=True, query_string=query_string),
token_id=scoped_token_id)
self.assertRaises(
exception.Unauthorized,
self.controller.validate_token,
self.make_request(is_admin=True, query_string='belongsTo=BAR'),
self.controller.validate_token_head,
self.make_request(is_admin=True, query_string=query_string),
token_id=scoped_token_id)
query_string = 'belongsTo=%s' % self.tenant_bar['id']
self.controller.validate_token(
self.make_request(is_admin=True, query_string=query_string),
token_id=scoped_token_id
)
self.controller.validate_token_head(
self.make_request(is_admin=True, query_string=query_string),
token_id=scoped_token_id
)
def test_token_auth_with_binding(self):
self.config_fixture.config(group='token', bind=['kerberos'])
body_dict = _build_user_auth()

View File

@ -498,28 +498,14 @@ class TokenCacheInvalidation(object):
exception.TokenNotFound,
self.token_provider_api.validate_token,
self.scoped_token_id)
self.assertRaises(
exception.TokenNotFound,
self.token_provider_api.validate_token,
self.scoped_token_id,
self.tenant['id'])
self.assertRaises(
exception.TokenNotFound,
self.token_provider_api.validate_v2_token,
self.scoped_token_id)
self.assertRaises(
exception.TokenNotFound,
self.token_provider_api.validate_v2_token,
self.scoped_token_id,
self.tenant['id'])
def _check_scoped_tokens_are_valid(self):
self.token_provider_api.validate_token(self.scoped_token_id)
self.token_provider_api.validate_token(
self.scoped_token_id, belongs_to=self.tenant['id'])
self.token_provider_api.validate_v2_token(self.scoped_token_id)
self.token_provider_api.validate_v2_token(
self.scoped_token_id, belongs_to=self.tenant['id'])
def _check_unscoped_tokens_are_valid(self):
self.token_provider_api.validate_token(self.unscoped_token_id)

View File

@ -416,6 +416,18 @@ class Auth(controller.V2Controller):
_('Token does not belong to specified tenant.'))
return token_ref
def _token_belongs_to(self, token, belongs_to):
"""Check if the token belongs to the right project.
:param token: token reference
:param belongs_to: project ID that the token belongs to
"""
token_data = token['access']['token']
if ('tenant' not in token_data or
token_data['tenant']['id'] != belongs_to):
raise exception.Unauthorized()
@controller.v2_deprecated
@controller.protected()
def validate_token_head(self, request, token_id):
@ -429,8 +441,11 @@ class Auth(controller.V2Controller):
the content body.
"""
token = self.token_provider_api.validate_v2_token(token_id)
belongs_to = request.params.get('belongsTo')
return self.token_provider_api.validate_v2_token(token_id, belongs_to)
if belongs_to:
self._token_belongs_to(token, belongs_to)
return token
@controller.v2_deprecated
@controller.protected()
@ -442,9 +457,12 @@ class Auth(controller.V2Controller):
Returns metadata about the token along any associated roles.
"""
belongs_to = request.params.get('belongsTo')
# TODO(ayoung) validate against revocation API
return self.token_provider_api.validate_v2_token(token_id, belongs_to)
token = self.token_provider_api.validate_v2_token(token_id)
belongs_to = request.params.get('belongsTo')
if belongs_to:
self._token_belongs_to(token, belongs_to)
return token
@controller.v2_deprecated
def delete_token(self, request, token_id):

View File

@ -207,12 +207,11 @@ class Manager(manager.Manager):
except exception.TokenNotFound:
six.reraise(*exc_info)
def validate_token(self, token_id, belongs_to=None):
def validate_token(self, token_id):
unique_id = utils.generate_unique_id(token_id)
# NOTE(morganfainberg): Ensure we never use the long-form token_id
# (PKI) as part of the cache_key.
token = self._validate_token(unique_id)
self._token_belongs_to(token, belongs_to)
self._is_valid_token(token)
return token
@ -226,7 +225,7 @@ class Manager(manager.Manager):
token_data, CONF.identity.default_domain_id)
self.revoke_api.check_token(token_values)
def validate_v2_token(self, token_id, belongs_to=None):
def validate_v2_token(self, token_id):
# NOTE(lbragstad): Only go to the persistence backend if the token
# provider requires it.
if self._needs_persistence:
@ -249,8 +248,6 @@ class Manager(manager.Manager):
v2_token_data_helper = providers.common.V2TokenDataHelper()
token = v2_token_data_helper.v3_to_v2_token(v3_token_ref, token_id)
# these are common things that happen regardless of token provider
self._token_belongs_to(token, belongs_to)
self._is_valid_token(token)
return token
@ -351,19 +348,6 @@ class Manager(manager.Manager):
else:
raise exception.TokenNotFound(_('Failed to validate token'))
def _token_belongs_to(self, token, belongs_to):
"""Check if the token belongs to the right tenant.
This is only used on v2 tokens. The structural validity of the token
will have already been checked before this method is called.
"""
if belongs_to:
token_data = token['access']['token']
if ('tenant' not in token_data or
token_data['tenant']['id'] != belongs_to):
raise exception.Unauthorized()
def issue_v2_token(self, token_ref, roles_ref=None, catalog_ref=None):
token_id, token_data = self.driver.issue_v2_token(
token_ref, roles_ref, catalog_ref)