Fix trust redelegation and associated test
Currently keystone does not allow for redelegation of trust. The only way for creating a new trust with the currect implementation using a trusted token was with impersonation. This patch fixes the workflow for redelegating a trust. Change-Id: If8f537b7525bfa26604dbce891dc193169aa21e2 Partial-Bug: 1534834
This commit is contained in:
parent
aa640e1e35
commit
5196237c82
|
@ -424,7 +424,8 @@ def new_policy_ref(**kwargs):
|
||||||
def new_trust_ref(trustor_user_id, trustee_user_id, project_id=None,
|
def new_trust_ref(trustor_user_id, trustee_user_id, project_id=None,
|
||||||
impersonation=None, expires=None, role_ids=None,
|
impersonation=None, expires=None, role_ids=None,
|
||||||
role_names=None, remaining_uses=None,
|
role_names=None, remaining_uses=None,
|
||||||
allow_redelegation=False, **kwargs):
|
allow_redelegation=False, redelegation_count=None,
|
||||||
|
redelegated_trust_id=None, **kwargs):
|
||||||
ref = {
|
ref = {
|
||||||
'id': uuid.uuid4().hex,
|
'id': uuid.uuid4().hex,
|
||||||
'trustor_user_id': trustor_user_id,
|
'trustor_user_id': trustor_user_id,
|
||||||
|
@ -433,8 +434,12 @@ def new_trust_ref(trustor_user_id, trustee_user_id, project_id=None,
|
||||||
'project_id': project_id,
|
'project_id': project_id,
|
||||||
'remaining_uses': remaining_uses,
|
'remaining_uses': remaining_uses,
|
||||||
'allow_redelegation': allow_redelegation,
|
'allow_redelegation': allow_redelegation,
|
||||||
|
'redelegated_trust_id': redelegated_trust_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isinstance(redelegation_count, int):
|
||||||
|
ref.update(redelegation_count=redelegation_count)
|
||||||
|
|
||||||
if isinstance(expires, six.string_types):
|
if isinstance(expires, six.string_types):
|
||||||
ref['expires_at'] = expires
|
ref['expires_at'] = expires
|
||||||
elif isinstance(expires, dict):
|
elif isinstance(expires, dict):
|
||||||
|
|
|
@ -3165,27 +3165,43 @@ class TestTrustChain(test_v3.RestfulTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestTrustChain, self).setUp()
|
super(TestTrustChain, self).setUp()
|
||||||
# Create trust chain
|
"""Create a trust chain using redelegation.
|
||||||
self.user_chain = list()
|
|
||||||
|
A trust chain is a series of trusts that are redelegated. For example,
|
||||||
|
self.user_list consists of userA, userB, and userC. The first trust in
|
||||||
|
the trust chain is going to be established between self.user and userA,
|
||||||
|
call it trustA. Then, userA is going to obtain a trust scoped token
|
||||||
|
using trustA, and with that token create a trust between userA and
|
||||||
|
userB called trustB. This pattern will continue with userB creating a
|
||||||
|
trust with userC.
|
||||||
|
So the trust chain should look something like:
|
||||||
|
trustA -> trustB -> trustC
|
||||||
|
Where:
|
||||||
|
self.user is trusting userA with trustA
|
||||||
|
userA is trusting userB with trustB
|
||||||
|
userB is trusting userC with trustC
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.user_list = list()
|
||||||
self.trust_chain = list()
|
self.trust_chain = list()
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
user = unit.create_user(self.identity_api,
|
user = unit.create_user(self.identity_api,
|
||||||
domain_id=self.domain_id)
|
domain_id=self.domain_id)
|
||||||
self.user_chain.append(user)
|
self.user_list.append(user)
|
||||||
|
|
||||||
# trustor->trustee
|
# trustor->trustee redelegation without impersonation
|
||||||
trustee = self.user_chain[0]
|
trustee = self.user_list[0]
|
||||||
trust_ref = unit.new_trust_ref(
|
trust_ref = unit.new_trust_ref(
|
||||||
trustor_user_id=self.user_id,
|
trustor_user_id=self.user_id,
|
||||||
trustee_user_id=trustee['id'],
|
trustee_user_id=trustee['id'],
|
||||||
project_id=self.project_id,
|
project_id=self.project_id,
|
||||||
impersonation=True,
|
impersonation=False,
|
||||||
expires=dict(minutes=1),
|
expires=dict(minutes=1),
|
||||||
role_ids=[self.role_id])
|
role_ids=[self.role_id],
|
||||||
trust_ref.update(
|
|
||||||
allow_redelegation=True,
|
allow_redelegation=True,
|
||||||
redelegation_count=3)
|
redelegation_count=3)
|
||||||
|
|
||||||
|
# Create a trust between self.user and the first user in the list
|
||||||
r = self.post('/OS-TRUST/trusts',
|
r = self.post('/OS-TRUST/trusts',
|
||||||
body={'trust': trust_ref})
|
body={'trust': trust_ref})
|
||||||
|
|
||||||
|
@ -3194,30 +3210,39 @@ class TestTrustChain(test_v3.RestfulTestCase):
|
||||||
user_id=trustee['id'],
|
user_id=trustee['id'],
|
||||||
password=trustee['password'],
|
password=trustee['password'],
|
||||||
trust_id=trust['id'])
|
trust_id=trust['id'])
|
||||||
|
|
||||||
|
# Generate a trusted token for the first user
|
||||||
trust_token = self.get_requested_token(auth_data)
|
trust_token = self.get_requested_token(auth_data)
|
||||||
self.trust_chain.append(trust)
|
self.trust_chain.append(trust)
|
||||||
|
|
||||||
for trustee in self.user_chain[1:]:
|
# Set trustor and redelegated_trust_id for next trust in the chain
|
||||||
|
next_trustor_id = trustee['id']
|
||||||
|
redelegated_trust_id = trust['id']
|
||||||
|
|
||||||
|
# Loop through the user to create a chain of redelegated trust.
|
||||||
|
for next_trustee in self.user_list[1:]:
|
||||||
trust_ref = unit.new_trust_ref(
|
trust_ref = unit.new_trust_ref(
|
||||||
trustor_user_id=self.user_id,
|
trustor_user_id=next_trustor_id,
|
||||||
trustee_user_id=trustee['id'],
|
trustee_user_id=next_trustee['id'],
|
||||||
project_id=self.project_id,
|
project_id=self.project_id,
|
||||||
impersonation=True,
|
impersonation=False,
|
||||||
role_ids=[self.role_id])
|
role_ids=[self.role_id],
|
||||||
trust_ref.update(
|
allow_redelegation=True,
|
||||||
allow_redelegation=True)
|
redelegated_trust_id=redelegated_trust_id)
|
||||||
r = self.post('/OS-TRUST/trusts',
|
r = self.post('/OS-TRUST/trusts',
|
||||||
body={'trust': trust_ref},
|
body={'trust': trust_ref},
|
||||||
token=trust_token)
|
token=trust_token)
|
||||||
trust = self.assertValidTrustResponse(r)
|
trust = self.assertValidTrustResponse(r)
|
||||||
auth_data = self.build_authentication_request(
|
auth_data = self.build_authentication_request(
|
||||||
user_id=trustee['id'],
|
user_id=next_trustee['id'],
|
||||||
password=trustee['password'],
|
password=next_trustee['password'],
|
||||||
trust_id=trust['id'])
|
trust_id=trust['id'])
|
||||||
trust_token = self.get_requested_token(auth_data)
|
trust_token = self.get_requested_token(auth_data)
|
||||||
self.trust_chain.append(trust)
|
self.trust_chain.append(trust)
|
||||||
|
next_trustor_id = next_trustee['id']
|
||||||
|
redelegated_trust_id = trust['id']
|
||||||
|
|
||||||
trustee = self.user_chain[-1]
|
trustee = self.user_list[-1]
|
||||||
trust = self.trust_chain[-1]
|
trust = self.trust_chain[-1]
|
||||||
auth_data = self.build_authentication_request(
|
auth_data = self.build_authentication_request(
|
||||||
user_id=trustee['id'],
|
user_id=trustee['id'],
|
||||||
|
@ -3235,7 +3260,7 @@ class TestTrustChain(test_v3.RestfulTestCase):
|
||||||
self.assertValidTokenResponse(r)
|
self.assertValidTokenResponse(r)
|
||||||
|
|
||||||
def assert_trust_tokens_revoked(self, trust_id):
|
def assert_trust_tokens_revoked(self, trust_id):
|
||||||
trustee = self.user_chain[0]
|
trustee = self.user_list[0]
|
||||||
auth_data = self.build_authentication_request(
|
auth_data = self.build_authentication_request(
|
||||||
user_id=trustee['id'],
|
user_id=trustee['id'],
|
||||||
password=trustee['password']
|
password=trustee['password']
|
||||||
|
@ -3253,7 +3278,7 @@ class TestTrustChain(test_v3.RestfulTestCase):
|
||||||
trust_id)
|
trust_id)
|
||||||
|
|
||||||
def test_delete_trust_cascade(self):
|
def test_delete_trust_cascade(self):
|
||||||
self.assert_user_authenticate(self.user_chain[0])
|
self.assert_user_authenticate(self.user_list[0])
|
||||||
self.delete('/OS-TRUST/trusts/%(trust_id)s' % {
|
self.delete('/OS-TRUST/trusts/%(trust_id)s' % {
|
||||||
'trust_id': self.trust_chain[0]['id']})
|
'trust_id': self.trust_chain[0]['id']})
|
||||||
|
|
||||||
|
@ -3263,30 +3288,51 @@ class TestTrustChain(test_v3.RestfulTestCase):
|
||||||
self.assert_trust_tokens_revoked(self.trust_chain[0]['id'])
|
self.assert_trust_tokens_revoked(self.trust_chain[0]['id'])
|
||||||
|
|
||||||
def test_delete_broken_chain(self):
|
def test_delete_broken_chain(self):
|
||||||
self.assert_user_authenticate(self.user_chain[0])
|
self.assert_user_authenticate(self.user_list[0])
|
||||||
self.delete('/OS-TRUST/trusts/%(trust_id)s' % {
|
|
||||||
'trust_id': self.trust_chain[1]['id']})
|
|
||||||
|
|
||||||
self.delete('/OS-TRUST/trusts/%(trust_id)s' % {
|
self.delete('/OS-TRUST/trusts/%(trust_id)s' % {
|
||||||
'trust_id': self.trust_chain[0]['id']})
|
'trust_id': self.trust_chain[0]['id']})
|
||||||
|
|
||||||
|
# Verify the two remaining trust have been deleted
|
||||||
|
for i in xrange(len(self.user_list) - 1):
|
||||||
|
auth_data = self.build_authentication_request(
|
||||||
|
user_id=self.user_list[i]['id'],
|
||||||
|
password=self.user_list[i]['password'])
|
||||||
|
|
||||||
|
auth_token = self.get_requested_token(auth_data)
|
||||||
|
|
||||||
|
self.delete('/OS-TRUST/trusts/%(trust_id)s' % {
|
||||||
|
'trust_id': self.trust_chain[i + 1]['id']},
|
||||||
|
token=auth_token,
|
||||||
|
expected_status=http_client.NOT_FOUND)
|
||||||
|
|
||||||
def test_trustor_roles_revoked(self):
|
def test_trustor_roles_revoked(self):
|
||||||
self.assert_user_authenticate(self.user_chain[0])
|
self.assert_user_authenticate(self.user_list[0])
|
||||||
|
|
||||||
self.assignment_api.remove_role_from_user_and_project(
|
self.assignment_api.remove_role_from_user_and_project(
|
||||||
self.user_id, self.project_id, self.role_id
|
self.user_id, self.project_id, self.role_id
|
||||||
)
|
)
|
||||||
|
|
||||||
auth_data = self.build_authentication_request(
|
# Verify that users are not allowed to authenticate with trust
|
||||||
token=self.last_token,
|
for i in xrange(len(self.user_list[1:])):
|
||||||
trust_id=self.trust_chain[-1]['id'])
|
trustee = self.user_list[i]
|
||||||
self.v3_create_token(auth_data,
|
auth_data = self.build_authentication_request(
|
||||||
expected_status=http_client.NOT_FOUND)
|
user_id=trustee['id'],
|
||||||
|
password=trustee['password'])
|
||||||
|
|
||||||
|
# Attempt to authenticate with trust
|
||||||
|
token = self.get_requested_token(auth_data)
|
||||||
|
auth_data = self.build_authentication_request(
|
||||||
|
token=token,
|
||||||
|
trust_id=self.trust_chain[i - 1]['id'])
|
||||||
|
|
||||||
|
# Trustee has no delegated roles
|
||||||
|
self.v3_create_token(auth_data,
|
||||||
|
expected_status=http_client.FORBIDDEN)
|
||||||
|
|
||||||
def test_intermediate_user_disabled(self):
|
def test_intermediate_user_disabled(self):
|
||||||
self.assert_user_authenticate(self.user_chain[0])
|
self.assert_user_authenticate(self.user_list[0])
|
||||||
|
|
||||||
disabled = self.user_chain[0]
|
disabled = self.user_list[0]
|
||||||
disabled['enabled'] = False
|
disabled['enabled'] = False
|
||||||
self.identity_api.update_user(disabled['id'], disabled)
|
self.identity_api.update_user(disabled['id'], disabled)
|
||||||
|
|
||||||
|
@ -3297,9 +3343,9 @@ class TestTrustChain(test_v3.RestfulTestCase):
|
||||||
expected_status=http_client.FORBIDDEN)
|
expected_status=http_client.FORBIDDEN)
|
||||||
|
|
||||||
def test_intermediate_user_deleted(self):
|
def test_intermediate_user_deleted(self):
|
||||||
self.assert_user_authenticate(self.user_chain[0])
|
self.assert_user_authenticate(self.user_list[0])
|
||||||
|
|
||||||
self.identity_api.delete_user(self.user_chain[0]['id'])
|
self.identity_api.delete_user(self.user_list[0]['id'])
|
||||||
|
|
||||||
# Bypass policy enforcement
|
# Bypass policy enforcement
|
||||||
with mock.patch.object(rules, 'enforce', return_value=True):
|
with mock.patch.object(rules, 'enforce', return_value=True):
|
||||||
|
|
|
@ -357,7 +357,16 @@ class V3TokenDataHelper(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
if CONF.trust.enabled and trust:
|
if CONF.trust.enabled and trust:
|
||||||
token_user_id = trust['trustor_user_id']
|
# If redelegated_trust_id is set, then we must traverse the
|
||||||
|
# trust_chain in order to determine who the original trustor is. We
|
||||||
|
# need to do this because the user ID of the original trustor helps
|
||||||
|
# us determine scope in the redelegated context.
|
||||||
|
if trust.get('redelegated_trust_id'):
|
||||||
|
trust_chain = self.trust_api.get_trust_pedigree(trust['id'])
|
||||||
|
token_user_id = trust_chain[-1]['trustor_user_id']
|
||||||
|
else:
|
||||||
|
token_user_id = trust['trustor_user_id']
|
||||||
|
|
||||||
token_project_id = trust['project_id']
|
token_project_id = trust['project_id']
|
||||||
# trusts do not support domains yet
|
# trusts do not support domains yet
|
||||||
token_domain_id = None
|
token_domain_id = None
|
||||||
|
|
|
@ -172,17 +172,23 @@ class TrustV3(controller.V3Controller):
|
||||||
raise exception.Forbidden(
|
raise exception.Forbidden(
|
||||||
_('At least one role should be specified.'))
|
_('At least one role should be specified.'))
|
||||||
|
|
||||||
def _get_user_role(self, trust):
|
def _get_trustor_roles(self, trust):
|
||||||
|
original_trust = trust.copy()
|
||||||
|
while original_trust.get('redelegated_trust_id'):
|
||||||
|
original_trust = self.trust_api.get_trust(
|
||||||
|
original_trust['redelegated_trust_id'])
|
||||||
|
|
||||||
if not self._attribute_is_empty(trust, 'project_id'):
|
if not self._attribute_is_empty(trust, 'project_id'):
|
||||||
return self.assignment_api.get_roles_for_user_and_project(
|
return self.assignment_api.get_roles_for_user_and_project(
|
||||||
trust['trustor_user_id'], trust['project_id'])
|
original_trust['trustor_user_id'],
|
||||||
|
original_trust['project_id'])
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _require_trustor_has_role_in_project(self, trust):
|
def _require_trustor_has_role_in_project(self, trust):
|
||||||
user_roles = self._get_user_role(trust)
|
trustor_roles = self._get_trustor_roles(trust)
|
||||||
for trust_role in trust['roles']:
|
for trust_role in trust['roles']:
|
||||||
matching_roles = [x for x in user_roles
|
matching_roles = [x for x in trustor_roles
|
||||||
if x == trust_role['id']]
|
if x == trust_role['id']]
|
||||||
if not matching_roles:
|
if not matching_roles:
|
||||||
raise exception.RoleNotFound(role_id=trust_role['id'])
|
raise exception.RoleNotFound(role_id=trust_role['id'])
|
||||||
|
|
|
@ -90,14 +90,9 @@ class Manager(manager.Manager):
|
||||||
def get_trust_pedigree(self, trust_id):
|
def get_trust_pedigree(self, trust_id):
|
||||||
trust = self.driver.get_trust(trust_id)
|
trust = self.driver.get_trust(trust_id)
|
||||||
trust_chain = [trust]
|
trust_chain = [trust]
|
||||||
if trust and trust.get('redelegated_trust_id'):
|
while trust and trust.get('redelegated_trust_id'):
|
||||||
trusts = self.driver.list_trusts_for_trustor(
|
trust = self.driver.get_trust(trust['redelegated_trust_id'])
|
||||||
trust['trustor_user_id'])
|
trust_chain.append(trust)
|
||||||
while trust_chain[-1].get('redelegated_trust_id'):
|
|
||||||
for t in trusts:
|
|
||||||
if t['id'] == trust_chain[-1]['redelegated_trust_id']:
|
|
||||||
trust_chain.append(t)
|
|
||||||
break
|
|
||||||
|
|
||||||
return trust_chain
|
return trust_chain
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue