diff --git a/keystone/models/token_model.py b/keystone/models/token_model.py index 7f190286a7..413545b53a 100644 --- a/keystone/models/token_model.py +++ b/keystone/models/token_model.py @@ -13,6 +13,7 @@ """Unified in-memory token model.""" from oslo_log import log +from oslo_serialization import jsonutils from oslo_serialization import msgpackutils from oslo_utils import reflection import six @@ -325,6 +326,21 @@ class TokenModel(object): return roles + def _get_oauth_roles(self): + roles = [] + access_token_roles = self.access_token['role_ids'] + access_token_roles = [ + {'role_id': r} for r in jsonutils.loads(access_token_roles)] + effective_access_token_roles = ( + PROVIDERS.assignment_api.add_implied_roles(access_token_roles) + ) + user_roles = [r['id'] for r in self._get_project_roles()] + for role in effective_access_token_roles: + if role['role_id'] in user_roles: + role = PROVIDERS.role_api.get_role(role['role_id']) + roles.append({'id': role['id'], 'name': role['name']}) + return roles + def _get_federated_roles(self): roles = [] group_ids = [group['id'] for group in self.federated_groups] @@ -428,6 +444,8 @@ class TokenModel(object): roles = self._get_system_roles() elif self.trust_scoped: roles = self._get_trust_roles() + elif self.oauth_scoped: + roles = self._get_oauth_roles() elif self.is_federated and not self.unscoped: roles = self._get_federated_roles() elif self.domain_scoped: diff --git a/keystone/tests/unit/test_v3_oauth1.py b/keystone/tests/unit/test_v3_oauth1.py index 90378214e4..4c648a23ee 100644 --- a/keystone/tests/unit/test_v3_oauth1.py +++ b/keystone/tests/unit/test_v3_oauth1.py @@ -308,6 +308,19 @@ class OAuthFlowTests(OAuth1Tests): self.keystone_token = content.result['token'] self.assertIsNotNone(self.keystone_token_id) + # add a new role assignment to ensure it is ignored in the access token + new_role = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} + PROVIDERS.role_api.create_role(new_role['id'], new_role) + PROVIDERS.assignment_api.add_role_to_user_and_project( + user_id=self.user_id, + project_id=self.project_id, + role_id=new_role['id']) + content = self.post(url, headers=headers, body=body) + token = content.result['token'] + token_roles = [r['id'] for r in token['roles']] + self.assertIn(self.role_id, token_roles) + self.assertNotIn(new_role['id'], token_roles) + class AccessTokenCRUDTests(OAuthFlowTests): def test_delete_access_token_dne(self): diff --git a/releasenotes/notes/bug-1873290-ff7f8e4cee15b75a.yaml b/releasenotes/notes/bug-1873290-ff7f8e4cee15b75a.yaml new file mode 100644 index 0000000000..ad35a30479 --- /dev/null +++ b/releasenotes/notes/bug-1873290-ff7f8e4cee15b75a.yaml @@ -0,0 +1,19 @@ +--- +security: + - | + [`bug 1873290 `_] + [`bug 1872735 `_] + Fixed the token model to respect the roles authorized OAuth1 access tokens. + Previously, the list of roles authorized for an OAuth1 access token were + ignored, so when an access token was used to request a keystone token, the + keystone token would contain every role assignment the creator had for the + project. This also fixed EC2 credentials to respect those roles as well. +fixes: + - | + [`bug 1873290 `_] + [`bug 1872735 `_] + Fixed the token model to respect the roles authorized OAuth1 access tokens. + Previously, the list of roles authorized for an OAuth1 access token were + ignored, so when an access token was used to request a keystone token, the + keystone token would contain every role assignment the creator had for the + project. This also fixed EC2 credentials to respect those roles as well.