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:
parent
dc9a1d5f70
commit
7f3f596351
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user