Merge "Populate application credential data in token"

This commit is contained in:
Zuul 2018-02-20 04:50:33 +00:00 committed by Gerrit Code Review
commit 68df7bf1f3
8 changed files with 144 additions and 35 deletions

View File

@ -86,7 +86,8 @@ class ApplicationCredentialV3(controller.V3Controller):
def _check_unrestricted(self, token):
auth_methods = token['methods']
if 'application_credential' in auth_methods:
if token.token_data['token']['application_credential_restricted']:
td = token.token_data['token']
if td['application_credential']['restricted']:
action = _("Using method 'application_credential' is not "
"allowed for managing additional application "
"credentials.")

View File

@ -5300,6 +5300,27 @@ class ApplicationCredentialAuth(test_v3.RestfulTestCase):
app_cred_id=app_cred_ref['id'], secret=app_cred_ref['secret'])
self.v3_create_token(auth_data, expected_status=http_client.CREATED)
def test_validate_application_credential_token_populates_restricted(self):
self.config_fixture.config(group='token', cache_on_issue=False)
app_cred = self._make_app_cred()
app_cred_ref = self.app_cred_api.create_application_credential(
app_cred)
auth_data = self.build_authentication_request(
app_cred_id=app_cred_ref['id'], secret=app_cred_ref['secret'])
auth_response = self.v3_create_token(
auth_data, expected_status=http_client.CREATED)
self.assertTrue(
auth_response.json['token']['application_credential']['restricted']
)
token_id = auth_response.headers.get('X-Subject-Token')
headers = {'X-Auth-Token': token_id, 'X-Subject-Token': token_id}
validate_response = self.get(
'/auth/tokens', headers=headers
).json_body
self.assertTrue(
validate_response['token']['application_credential']['restricted']
)
def test_valid_application_credential_with_name_succeeds(self):
app_cred = self._make_app_cred()
app_cred_ref = self.app_cred_api.create_application_credential(

View File

@ -337,7 +337,8 @@ class TestPayloads(unit.TestCase):
def _test_payload(self, payload_class, exp_user_id=None, exp_methods=None,
exp_system=None, exp_project_id=None,
exp_domain_id=None, exp_trust_id=None,
exp_federated_info=None, exp_access_token_id=None):
exp_federated_info=None, exp_access_token_id=None,
exp_app_cred_id=None):
exp_user_id = exp_user_id or uuid.uuid4().hex
exp_methods = exp_methods or ['password']
exp_expires_at = utils.isotime(timeutils.utcnow(), subsecond=True)
@ -346,12 +347,12 @@ class TestPayloads(unit.TestCase):
payload = payload_class.assemble(
exp_user_id, exp_methods, exp_system, exp_project_id,
exp_domain_id, exp_expires_at, exp_audit_ids, exp_trust_id,
exp_federated_info, exp_access_token_id)
exp_federated_info, exp_access_token_id, exp_app_cred_id)
(user_id, methods, system, project_id,
domain_id, expires_at, audit_ids,
trust_id, federated_info,
access_token_id) = payload_class.disassemble(payload)
access_token_id, app_cred_id) = payload_class.disassemble(payload)
self.assertEqual(exp_user_id, user_id)
self.assertEqual(exp_methods, methods)
@ -362,6 +363,7 @@ class TestPayloads(unit.TestCase):
self.assertEqual(exp_domain_id, domain_id)
self.assertEqual(exp_trust_id, trust_id)
self.assertEqual(exp_access_token_id, access_token_id)
self.assertEqual(exp_app_cred_id, app_cred_id)
if exp_federated_info:
self.assertDictEqual(exp_federated_info, federated_info)
@ -476,6 +478,18 @@ class TestPayloads(unit.TestCase):
exp_project_id=uuid.uuid4().hex,
exp_access_token_id=uuid.uuid4().hex)
def test_app_cred_scoped_payload_with_non_uuid_ids(self):
self._test_payload(token_formatters.ApplicationCredentialScopedPayload,
exp_user_id='someNonUuidUserId',
exp_project_id='someNonUuidProjectId',
exp_app_cred_id='someNonUuidAppCredId')
def test_app_cred_scoped_payload_with_16_char_non_uuid_ids(self):
self._test_payload(token_formatters.ApplicationCredentialScopedPayload,
exp_user_id='0123456789abcdef',
exp_project_id='0123456789abcdef',
exp_app_cred_id='0123456789abcdef')
class TestFernetKeyRotation(unit.TestCase):
def setUp(self):

View File

@ -488,12 +488,15 @@ class V3TokenDataHelper(provider_api.ProviderAPIMixin, object):
LOG.error(msg)
raise exception.UnexpectedError(msg)
def _populate_app_cred_restrictions(self, token_data, app_cred_id):
def _populate_app_cred(self, token_data, app_cred_id):
if app_cred_id:
app_cred_api = PROVIDERS.application_credential_api
app_cred = app_cred_api.get_application_credential(app_cred_id)
restricted = not app_cred['unrestricted']
token_data['application_credential_restricted'] = restricted
token_data['application_credential'] = {}
token_data['application_credential']['id'] = app_cred['id']
token_data['application_credential']['name'] = app_cred['name']
token_data['application_credential']['restricted'] = restricted
def get_token_data(self, user_id, method_names, system=None,
domain_id=None, project_id=None, expires=None,
@ -528,7 +531,7 @@ class V3TokenDataHelper(provider_api.ProviderAPIMixin, object):
self._populate_token_dates(token_data, expires=expires,
issued_at=issued_at)
self._populate_oauth_section(token_data, access_token)
self._populate_app_cred_restrictions(token_data, app_cred_id)
self._populate_app_cred(token_data, app_cred_id)
return {'token': token_data}
@ -644,7 +647,7 @@ class BaseProvider(provider_api.ProviderAPIMixin, base.Provider):
try:
(user_id, methods, audit_ids, system, domain_id,
project_id, trust_id, federated_info, access_token_id,
issued_at, expires_at) = (
app_cred_id, issued_at, expires_at) = (
self.token_formatter.validate_token(token_id))
except exception.ValidationError as e:
raise exception.TokenNotFound(e)
@ -693,4 +696,5 @@ class BaseProvider(provider_api.ProviderAPIMixin, base.Provider):
token=token_dict,
bind=bind,
access_token=access_token,
audit_info=audit_ids)
audit_info=audit_ids,
app_cred_id=app_cred_id)

View File

@ -166,6 +166,8 @@ class Provider(common.BaseProvider):
access_token_id = token_data['token'].get('OS-OAUTH1', {}).get(
'access_token_id')
federated_info = self._build_federated_info(token_data)
app_cred_id = token_data['token'].get('application_credential',
{}).get('id')
return self.token_formatter.create_token(
user_id,
@ -177,7 +179,8 @@ class Provider(common.BaseProvider):
project_id=project_id,
trust_id=trust_id,
federated_info=federated_info,
access_token_id=access_token_id
access_token_id=access_token_id,
app_cred_id=app_cred_id
)
@property

View File

@ -137,20 +137,22 @@ class TokenFormatter(object):
def create_token(self, user_id, expires_at, audit_ids, methods=None,
system=None, domain_id=None, project_id=None,
trust_id=None, federated_info=None, access_token_id=None):
trust_id=None, federated_info=None, access_token_id=None,
app_cred_id=None):
"""Given a set of payload attributes, generate a Fernet token."""
for payload_class in PAYLOAD_CLASSES:
if payload_class.create_arguments_apply(
project_id=project_id, domain_id=domain_id,
system=system, trust_id=trust_id,
federated_info=federated_info,
access_token_id=access_token_id):
access_token_id=access_token_id,
app_cred_id=app_cred_id):
break
version = payload_class.version
payload = payload_class.assemble(
user_id, methods, system, project_id, domain_id, expires_at,
audit_ids, trust_id, federated_info, access_token_id
audit_ids, trust_id, federated_info, access_token_id, app_cred_id
)
versioned_payload = (version,) + payload
@ -184,7 +186,8 @@ class TokenFormatter(object):
if version == payload_class.version:
(user_id, methods, system, project_id, domain_id,
expires_at, audit_ids, trust_id, federated_info,
access_token_id) = payload_class.disassemble(payload)
access_token_id,
app_cred_id) = payload_class.disassemble(payload)
break
else:
# If the token_format is not recognized, raise ValidationError.
@ -200,8 +203,8 @@ class TokenFormatter(object):
expires_at = ks_utils.isotime(at=expires_at, subsecond=True)
return (user_id, methods, audit_ids, system, domain_id, project_id,
trust_id, federated_info, access_token_id, issued_at,
expires_at)
trust_id, federated_info, access_token_id, app_cred_id,
issued_at, expires_at)
class BasePayload(object):
@ -222,7 +225,7 @@ class BasePayload(object):
@classmethod
def assemble(cls, user_id, methods, system, project_id, domain_id,
expires_at, audit_ids, trust_id, federated_info,
access_token_id):
access_token_id, app_cred_id):
"""Assemble the payload of a token.
:param user_id: identifier of the user in the token request
@ -237,6 +240,7 @@ class BasePayload(object):
provider ID, protocol ID, and federated domain
ID
:param access_token_id: ID of the secret in OAuth1 authentication
:param app_cred_id: ID of the application credential in effect
:returns: the payload of a token
"""
@ -250,7 +254,7 @@ class BasePayload(object):
(user_id, methods, system, project_id, domain_id,
expires_at_str, audit_ids, trust_id, federated_info,
access_token_id)
access_token_id, app_cred_id)
* ``methods`` are the auth methods.
* federated_info is a dict contains the group IDs, the identity
@ -362,7 +366,7 @@ class UnscopedPayload(BasePayload):
@classmethod
def assemble(cls, user_id, methods, system, project_id, domain_id,
expires_at, audit_ids, trust_id, federated_info,
access_token_id):
access_token_id, app_cred_id):
b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
methods = auth_plugins.convert_method_list_to_integer(methods)
expires_at_int = cls._convert_time_string_to_float(expires_at)
@ -384,9 +388,10 @@ class UnscopedPayload(BasePayload):
trust_id = None
federated_info = None
access_token_id = None
app_cred_id = None
return (user_id, methods, system, project_id, domain_id,
expires_at_str, audit_ids, trust_id, federated_info,
access_token_id)
access_token_id, app_cred_id)
class DomainScopedPayload(BasePayload):
@ -399,7 +404,7 @@ class DomainScopedPayload(BasePayload):
@classmethod
def assemble(cls, user_id, methods, system, project_id, domain_id,
expires_at, audit_ids, trust_id, federated_info,
access_token_id):
access_token_id, app_cred_id):
b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
methods = auth_plugins.convert_method_list_to_integer(methods)
try:
@ -438,9 +443,10 @@ class DomainScopedPayload(BasePayload):
trust_id = None
federated_info = None
access_token_id = None
app_cred_id = None
return (user_id, methods, system, project_id, domain_id,
expires_at_str, audit_ids, trust_id, federated_info,
access_token_id)
access_token_id, app_cred_id)
class ProjectScopedPayload(BasePayload):
@ -453,7 +459,7 @@ class ProjectScopedPayload(BasePayload):
@classmethod
def assemble(cls, user_id, methods, system, project_id, domain_id,
expires_at, audit_ids, trust_id, federated_info,
access_token_id):
access_token_id, app_cred_id):
b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
methods = auth_plugins.convert_method_list_to_integer(methods)
b_project_id = cls.attempt_convert_uuid_hex_to_bytes(project_id)
@ -478,9 +484,10 @@ class ProjectScopedPayload(BasePayload):
trust_id = None
federated_info = None
access_token_id = None
app_cred_id = None
return (user_id, methods, system, project_id, domain_id,
expires_at_str, audit_ids, trust_id, federated_info,
access_token_id)
access_token_id, app_cred_id)
class TrustScopedPayload(BasePayload):
@ -493,7 +500,7 @@ class TrustScopedPayload(BasePayload):
@classmethod
def assemble(cls, user_id, methods, system, project_id, domain_id,
expires_at, audit_ids, trust_id, federated_info,
access_token_id):
access_token_id, app_cred_id):
b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
methods = auth_plugins.convert_method_list_to_integer(methods)
b_project_id = cls.attempt_convert_uuid_hex_to_bytes(project_id)
@ -521,9 +528,10 @@ class TrustScopedPayload(BasePayload):
domain_id = None
federated_info = None
access_token_id = None
app_cred_id = None
return (user_id, methods, system, project_id, domain_id,
expires_at_str, audit_ids, trust_id, federated_info,
access_token_id)
access_token_id, app_cred_id)
class FederatedUnscopedPayload(BasePayload):
@ -547,7 +555,7 @@ class FederatedUnscopedPayload(BasePayload):
@classmethod
def assemble(cls, user_id, methods, system, project_id, domain_id,
expires_at, audit_ids, trust_id, federated_info,
access_token_id):
access_token_id, app_cred_id):
b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
methods = auth_plugins.convert_method_list_to_integer(methods)
b_group_ids = list(map(cls.pack_group_id,
@ -586,9 +594,10 @@ class FederatedUnscopedPayload(BasePayload):
domain_id = None
trust_id = None
access_token_id = None
app_cred_id = None
return (user_id, methods, system, project_id, domain_id,
expires_at_str, audit_ids, trust_id, federated_info,
access_token_id)
access_token_id, app_cred_id)
class FederatedScopedPayload(FederatedUnscopedPayload):
@ -597,7 +606,7 @@ class FederatedScopedPayload(FederatedUnscopedPayload):
@classmethod
def assemble(cls, user_id, methods, system, project_id, domain_id,
expires_at, audit_ids, trust_id, federated_info,
access_token_id):
access_token_id, app_cred_id):
b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
methods = auth_plugins.convert_method_list_to_integer(methods)
b_scope_id = cls.attempt_convert_uuid_hex_to_bytes(
@ -641,9 +650,10 @@ class FederatedScopedPayload(FederatedUnscopedPayload):
system = None
trust_id = None
access_token_id = None
app_cred_id = None
return (user_id, methods, system, project_id, domain_id,
expires_at_str, audit_ids, trust_id, federated_info,
access_token_id)
access_token_id, app_cred_id)
class FederatedProjectScopedPayload(FederatedScopedPayload):
@ -672,7 +682,7 @@ class OauthScopedPayload(BasePayload):
@classmethod
def assemble(cls, user_id, methods, system, project_id, domain_id,
expires_at, audit_ids, trust_id, federated_info,
access_token_id):
access_token_id, app_cred_id):
b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
methods = auth_plugins.convert_method_list_to_integer(methods)
b_project_id = cls.attempt_convert_uuid_hex_to_bytes(project_id)
@ -702,10 +712,11 @@ class OauthScopedPayload(BasePayload):
domain_id = None
trust_id = None
federated_info = None
app_cred_id = None
return (user_id, methods, system, project_id, domain_id,
expires_at_str, audit_ids, trust_id, federated_info,
access_token_id)
access_token_id, app_cred_id)
class SystemScopedPayload(BasePayload):
@ -718,7 +729,7 @@ class SystemScopedPayload(BasePayload):
@classmethod
def assemble(cls, user_id, methods, system, project_id, domain_id,
expires_at, audit_ids, trust_id, federated_info,
access_token_id):
access_token_id, app_cred_id):
b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
methods = auth_plugins.convert_method_list_to_integer(methods)
expires_at_int = cls._convert_time_string_to_float(expires_at)
@ -740,9 +751,55 @@ class SystemScopedPayload(BasePayload):
trust_id = None
federated_info = None
access_token_id = None
app_cred_id = None
return (user_id, methods, system, project_id, domain_id,
expires_at_str, audit_ids, trust_id, federated_info,
access_token_id)
access_token_id, app_cred_id)
class ApplicationCredentialScopedPayload(BasePayload):
version = 9
@classmethod
def create_arguments_apply(cls, **kwargs):
return kwargs['app_cred_id']
@classmethod
def assemble(cls, user_id, methods, system, project_id, domain_id,
expires_at, audit_ids, trust_id, federated_info,
access_token_id, app_cred_id):
b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
methods = auth_plugins.convert_method_list_to_integer(methods)
b_project_id = cls.attempt_convert_uuid_hex_to_bytes(project_id)
expires_at_int = cls._convert_time_string_to_float(expires_at)
b_audit_ids = list(map(cls.random_urlsafe_str_to_bytes,
audit_ids))
b_app_cred_id = cls.attempt_convert_uuid_hex_to_bytes(app_cred_id)
return (b_user_id, methods, b_project_id, expires_at_int, b_audit_ids,
b_app_cred_id)
@classmethod
def disassemble(cls, payload):
(is_stored_as_bytes, user_id) = payload[0]
if is_stored_as_bytes:
user_id = cls.convert_uuid_bytes_to_hex(user_id)
methods = auth_plugins.convert_integer_to_method_list(payload[1])
(is_stored_as_bytes, project_id) = payload[2]
if is_stored_as_bytes:
project_id = cls.convert_uuid_bytes_to_hex(project_id)
expires_at_str = cls._convert_float_to_time_string(payload[3])
audit_ids = list(map(cls.base64_encode, payload[4]))
system = None
domain_id = None
trust_id = None
federated_info = None
access_token_id = None
(is_stored_as_bytes, app_cred_id) = payload[5]
if is_stored_as_bytes:
app_cred_id = cls.convert_uuid_bytes_to_hex(app_cred_id)
return (user_id, methods, system, project_id, domain_id,
expires_at_str, audit_ids, trust_id, federated_info,
access_token_id, app_cred_id)
# For now, the order of the classes in the following list is important. This
@ -758,6 +815,7 @@ PAYLOAD_CLASSES = [
FederatedProjectScopedPayload,
FederatedDomainScopedPayload,
FederatedUnscopedPayload,
ApplicationCredentialScopedPayload,
ProjectScopedPayload,
DomainScopedPayload,
SystemScopedPayload,

View File

@ -103,7 +103,8 @@ class TrustV3(controller.V3Controller):
def _check_unrestricted(self, token):
auth_methods = token['methods']
if 'application_credential' in auth_methods:
if token.token_data['token']['application_credential_restricted']:
td = token.token_data['token']
if td['application_credential']['restricted']:
action = _("Using method 'application_credential' is not "
"allowed for managing trusts.")
raise exception.ForbiddenAction(action=action)

View File

@ -0,0 +1,7 @@
---
fixes:
- |
[`bug 1750415 <https://bugs.launchpad.net/keystone/+bug/1750415>`_]
Fixes an implementation fault in application credentials where the
application credential reference was not populated in the token data,
causing problems with the token validation when caching was disabled.