Implement system reader role for trusts API
Currently, the trusts API only allows the "project" scope type, and
moreover inconsistently enforces different actions based on admin status
or trustor/trustee relationship: for example, an "admin" can list all
trusts but not filter by trustor or trustee and cannot get details for a
single trust, not can they list or get trust roles. This patch changes
the behavior of the trusts API to allow a system reader to list and get
details for trusts and trust roles, where previously only a trustor or
trustee could do so. This helps make the different actions in the trusts
API consistent with one another and makes the API more useful to a
deployment auditor. A subsequent patch will add system admin
functionality.
This change does not use the oslo.policy deprecation feature for the
'identity:list_trusts_for_trustor' or 'identity:list_trusts_for_trustee'
policies as those are new policies introduced in 7717ed3
.
Change-Id: I4e1482643e18fd46e937ffae8b3623cea2d2dd62
Partial-bug: #1818850
Partial-bug: #1818846
Related-Bug: #968696
This commit is contained in:
parent
09e699baba
commit
ea7acd8036
@ -10,12 +10,43 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_log import versionutils
|
||||||
from oslo_policy import policy
|
from oslo_policy import policy
|
||||||
|
|
||||||
from keystone.common.policies import base
|
from keystone.common.policies import base
|
||||||
|
|
||||||
RULE_TRUSTOR = 'user_id:%(target.trust.trustor_user_id)s'
|
RULE_TRUSTOR = 'user_id:%(target.trust.trustor_user_id)s'
|
||||||
RULE_TRUSTEE = 'user_id:%(target.trust.trustee_user_id)s'
|
RULE_TRUSTEE = 'user_id:%(target.trust.trustee_user_id)s'
|
||||||
|
SYSTEM_READER_OR_TRUSTOR_OR_TRUSTEE = (
|
||||||
|
base.SYSTEM_READER + ' or ' + RULE_TRUSTOR + ' or ' + RULE_TRUSTEE
|
||||||
|
)
|
||||||
|
SYSTEM_READER_OR_TRUSTOR = base.SYSTEM_READER + ' or ' + RULE_TRUSTOR
|
||||||
|
SYSTEM_READER_OR_TRUSTEE = base.SYSTEM_READER + ' or ' + RULE_TRUSTEE
|
||||||
|
|
||||||
|
deprecated_list_trusts = policy.DeprecatedRule(
|
||||||
|
name=base.IDENTITY % 'list_trusts',
|
||||||
|
check_str=base.RULE_ADMIN_REQUIRED
|
||||||
|
)
|
||||||
|
deprecated_list_roles_for_trust = policy.DeprecatedRule(
|
||||||
|
name=base.IDENTITY % 'list_roles_for_trust',
|
||||||
|
check_str=RULE_TRUSTOR + ' or ' + RULE_TRUSTEE
|
||||||
|
)
|
||||||
|
deprecated_get_role_for_trust = policy.DeprecatedRule(
|
||||||
|
name=base.IDENTITY % 'get_role_for_trust',
|
||||||
|
check_str=RULE_TRUSTOR + ' or ' + RULE_TRUSTEE
|
||||||
|
)
|
||||||
|
deprecated_get_trust = policy.DeprecatedRule(
|
||||||
|
name=base.IDENTITY % 'get_trust',
|
||||||
|
check_str=RULE_TRUSTOR + ' or ' + RULE_TRUSTEE
|
||||||
|
)
|
||||||
|
|
||||||
|
DEPRECATED_REASON = """
|
||||||
|
As of the Train release, the trust API now understands default roles and
|
||||||
|
system-scoped tokens, making the API more granular by default without
|
||||||
|
compromising security. The new policy defaults account for these changes
|
||||||
|
automatically. Be sure to take these new defaults into consideration if you are
|
||||||
|
relying on overrides in your deployment for the service API.
|
||||||
|
"""
|
||||||
|
|
||||||
trust_policies = [
|
trust_policies = [
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
@ -30,17 +61,20 @@ trust_policies = [
|
|||||||
'method': 'POST'}]),
|
'method': 'POST'}]),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=base.IDENTITY % 'list_trusts',
|
name=base.IDENTITY % 'list_trusts',
|
||||||
check_str=base.RULE_ADMIN_REQUIRED,
|
check_str=base.SYSTEM_READER,
|
||||||
scope_types=['project'],
|
scope_types=['system'],
|
||||||
description='List trusts.',
|
description='List trusts.',
|
||||||
operations=[{'path': '/v3/OS-TRUST/trusts',
|
operations=[{'path': '/v3/OS-TRUST/trusts',
|
||||||
'method': 'GET'},
|
'method': 'GET'},
|
||||||
{'path': '/v3/OS-TRUST/trusts',
|
{'path': '/v3/OS-TRUST/trusts',
|
||||||
'method': 'HEAD'}]),
|
'method': 'HEAD'}],
|
||||||
|
deprecated_rule=deprecated_list_trusts,
|
||||||
|
deprecated_reason=DEPRECATED_REASON,
|
||||||
|
deprecated_since=versionutils.deprecated.TRAIN),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=base.IDENTITY % 'list_trusts_for_trustor',
|
name=base.IDENTITY % 'list_trusts_for_trustor',
|
||||||
check_str=RULE_TRUSTOR,
|
check_str=SYSTEM_READER_OR_TRUSTOR,
|
||||||
scope_types=['project'],
|
scope_types=['system', 'project'],
|
||||||
description='List trusts for trustor.',
|
description='List trusts for trustor.',
|
||||||
operations=[{'path': '/v3/OS-TRUST/trusts?trustor_user_id={trustor_user_id}',
|
operations=[{'path': '/v3/OS-TRUST/trusts?trustor_user_id={trustor_user_id}',
|
||||||
'method': 'GET'},
|
'method': 'GET'},
|
||||||
@ -48,8 +82,8 @@ trust_policies = [
|
|||||||
'method': 'HEAD'}]),
|
'method': 'HEAD'}]),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=base.IDENTITY % 'list_trusts_for_trustee',
|
name=base.IDENTITY % 'list_trusts_for_trustee',
|
||||||
check_str=RULE_TRUSTEE,
|
check_str=SYSTEM_READER_OR_TRUSTEE,
|
||||||
scope_types=['project'],
|
scope_types=['system', 'project'],
|
||||||
description='List trusts for trustee.',
|
description='List trusts for trustee.',
|
||||||
operations=[{'path': '/v3/OS-TRUST/trusts?trustee_user_id={trustee_user_id}',
|
operations=[{'path': '/v3/OS-TRUST/trusts?trustee_user_id={trustee_user_id}',
|
||||||
'method': 'GET'},
|
'method': 'GET'},
|
||||||
@ -57,22 +91,28 @@ trust_policies = [
|
|||||||
'method': 'HEAD'}]),
|
'method': 'HEAD'}]),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=base.IDENTITY % 'list_roles_for_trust',
|
name=base.IDENTITY % 'list_roles_for_trust',
|
||||||
check_str=RULE_TRUSTOR + ' or ' + RULE_TRUSTEE,
|
check_str=SYSTEM_READER_OR_TRUSTOR_OR_TRUSTEE,
|
||||||
scope_types=['project'],
|
scope_types=['system', 'project'],
|
||||||
description='List roles delegated by a trust.',
|
description='List roles delegated by a trust.',
|
||||||
operations=[{'path': '/v3/OS-TRUST/trusts/{trust_id}/roles',
|
operations=[{'path': '/v3/OS-TRUST/trusts/{trust_id}/roles',
|
||||||
'method': 'GET'},
|
'method': 'GET'},
|
||||||
{'path': '/v3/OS-TRUST/trusts/{trust_id}/roles',
|
{'path': '/v3/OS-TRUST/trusts/{trust_id}/roles',
|
||||||
'method': 'HEAD'}]),
|
'method': 'HEAD'}],
|
||||||
|
deprecated_rule=deprecated_list_roles_for_trust,
|
||||||
|
deprecated_reason=DEPRECATED_REASON,
|
||||||
|
deprecated_since=versionutils.deprecated.TRAIN),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=base.IDENTITY % 'get_role_for_trust',
|
name=base.IDENTITY % 'get_role_for_trust',
|
||||||
check_str=RULE_TRUSTOR + ' or ' + RULE_TRUSTEE,
|
check_str=SYSTEM_READER_OR_TRUSTOR_OR_TRUSTEE,
|
||||||
scope_types=['project'],
|
scope_types=['system', 'project'],
|
||||||
description='Check if trust delegates a particular role.',
|
description='Check if trust delegates a particular role.',
|
||||||
operations=[{'path': '/v3/OS-TRUST/trusts/{trust_id}/roles/{role_id}',
|
operations=[{'path': '/v3/OS-TRUST/trusts/{trust_id}/roles/{role_id}',
|
||||||
'method': 'GET'},
|
'method': 'GET'},
|
||||||
{'path': '/v3/OS-TRUST/trusts/{trust_id}/roles/{role_id}',
|
{'path': '/v3/OS-TRUST/trusts/{trust_id}/roles/{role_id}',
|
||||||
'method': 'HEAD'}]),
|
'method': 'HEAD'}],
|
||||||
|
deprecated_rule=deprecated_get_role_for_trust,
|
||||||
|
deprecated_reason=DEPRECATED_REASON,
|
||||||
|
deprecated_since=versionutils.deprecated.TRAIN),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=base.IDENTITY % 'delete_trust',
|
name=base.IDENTITY % 'delete_trust',
|
||||||
check_str=RULE_TRUSTOR,
|
check_str=RULE_TRUSTOR,
|
||||||
@ -82,13 +122,16 @@ trust_policies = [
|
|||||||
'method': 'DELETE'}]),
|
'method': 'DELETE'}]),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=base.IDENTITY % 'get_trust',
|
name=base.IDENTITY % 'get_trust',
|
||||||
check_str=RULE_TRUSTOR + ' or ' + RULE_TRUSTEE,
|
check_str=SYSTEM_READER_OR_TRUSTOR_OR_TRUSTEE,
|
||||||
scope_types=['project'],
|
scope_types=['system', 'project'],
|
||||||
description='Get trust.',
|
description='Get trust.',
|
||||||
operations=[{'path': '/v3/OS-TRUST/trusts/{trust_id}',
|
operations=[{'path': '/v3/OS-TRUST/trusts/{trust_id}',
|
||||||
'method': 'GET'},
|
'method': 'GET'},
|
||||||
{'path': '/v3/OS-TRUST/trusts/{trust_id}',
|
{'path': '/v3/OS-TRUST/trusts/{trust_id}',
|
||||||
'method': 'HEAD'}])
|
'method': 'HEAD'}],
|
||||||
|
deprecated_rule=deprecated_get_trust,
|
||||||
|
deprecated_reason=DEPRECATED_REASON,
|
||||||
|
deprecated_since=versionutils.deprecated.TRAIN)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -147,15 +147,28 @@ class _AdminTestsMixin(object):
|
|||||||
)
|
)
|
||||||
self.assertEqual(1, len(r.json['trusts']))
|
self.assertEqual(1, len(r.json['trusts']))
|
||||||
|
|
||||||
def test_admin_cannot_get_trust_for_other_user(self):
|
|
||||||
PROVIDERS.trust_api.create_trust(
|
class AdminTokenTests(TrustTests, _AdminTestsMixin):
|
||||||
|
"""Tests for the is_admin user.
|
||||||
|
|
||||||
|
The Trusts API has hardcoded is_admin checks that we need to ensure are
|
||||||
|
preserved through the system-scope transition.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(AdminTokenTests, self).setUp()
|
||||||
|
self.config_fixture.config(admin_token='ADMIN')
|
||||||
|
self.headers = {'X-Auth-Token': 'ADMIN'}
|
||||||
|
|
||||||
|
def test_admin_can_delete_trust_for_other_user(self):
|
||||||
|
ref = PROVIDERS.trust_api.create_trust(
|
||||||
self.trust_id, **self.trust_data)
|
self.trust_id, **self.trust_data)
|
||||||
|
|
||||||
with self.test_client() as c:
|
with self.test_client() as c:
|
||||||
c.get(
|
c.delete(
|
||||||
'/v3/OS-TRUST/trusts/%s' % self.trust_id,
|
'/v3/OS-TRUST/trusts/%s' % ref['id'],
|
||||||
headers=self.headers,
|
headers=self.headers,
|
||||||
expected_status_code=http_client.FORBIDDEN
|
expected_status_code=http_client.NO_CONTENT
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_admin_can_get_non_existent_trust_not_found(self):
|
def test_admin_can_get_non_existent_trust_not_found(self):
|
||||||
@ -167,6 +180,17 @@ class _AdminTestsMixin(object):
|
|||||||
expected_status_code=http_client.NOT_FOUND
|
expected_status_code=http_client.NOT_FOUND
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_admin_cannot_get_trust_for_other_user(self):
|
||||||
|
PROVIDERS.trust_api.create_trust(
|
||||||
|
self.trust_id, **self.trust_data)
|
||||||
|
|
||||||
|
with self.test_client() as c:
|
||||||
|
c.get(
|
||||||
|
'/v3/OS-TRUST/trusts/%s' % self.trust_id,
|
||||||
|
headers=self.headers,
|
||||||
|
expected_status_code=http_client.FORBIDDEN
|
||||||
|
)
|
||||||
|
|
||||||
def test_admin_cannot_list_trust_roles_for_other_user(self):
|
def test_admin_cannot_list_trust_roles_for_other_user(self):
|
||||||
PROVIDERS.trust_api.create_trust(
|
PROVIDERS.trust_api.create_trust(
|
||||||
self.trust_id, **self.trust_data)
|
self.trust_id, **self.trust_data)
|
||||||
@ -191,19 +215,118 @@ class _AdminTestsMixin(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AdminTokenTests(TrustTests, _AdminTestsMixin):
|
class _SystemUserTests(object):
|
||||||
"""Tests for the is_admin user.
|
"""Tests for system admin, member, and reader."""
|
||||||
|
|
||||||
The Trusts API has hardcoded is_admin checks that we need to ensure are
|
def test_user_can_get_non_existent_trust(self):
|
||||||
preserved through the system-scope transition.
|
trust_id = uuid.uuid4().hex
|
||||||
"""
|
with self.test_client() as c:
|
||||||
|
c.get(
|
||||||
|
'/v3/OS-TRUST/trusts/%s' % trust_id,
|
||||||
|
headers=self.headers,
|
||||||
|
expected_status_code=http_client.NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_user_can_get_trust_for_other_user(self):
|
||||||
|
PROVIDERS.trust_api.create_trust(
|
||||||
|
self.trust_id, **self.trust_data)
|
||||||
|
|
||||||
|
with self.test_client() as c:
|
||||||
|
r = c.get(
|
||||||
|
'/v3/OS-TRUST/trusts/%s' % self.trust_id,
|
||||||
|
headers=self.headers
|
||||||
|
)
|
||||||
|
self.assertEqual(r.json['trust']['id'], self.trust_id)
|
||||||
|
|
||||||
|
def test_user_can_list_trusts_for_trustee(self):
|
||||||
|
PROVIDERS.trust_api.create_trust(
|
||||||
|
self.trust_id, **self.trust_data)
|
||||||
|
|
||||||
|
with self.test_client() as c:
|
||||||
|
c.get(
|
||||||
|
('/v3/OS-TRUST/trusts?trustee_user_id=%s' %
|
||||||
|
self.trustee_user_id),
|
||||||
|
headers=self.headers
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_user_can_list_trusts_for_trustor(self):
|
||||||
|
PROVIDERS.trust_api.create_trust(
|
||||||
|
self.trust_id, **self.trust_data)
|
||||||
|
|
||||||
|
with self.test_client() as c:
|
||||||
|
c.get(
|
||||||
|
('/v3/OS-TRUST/trusts?trustor_user_id=%s' %
|
||||||
|
self.trustor_user_id),
|
||||||
|
headers=self.headers
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_user_can_list_trust_roles_for_other_user(self):
|
||||||
|
PROVIDERS.trust_api.create_trust(
|
||||||
|
self.trust_id, **self.trust_data)
|
||||||
|
|
||||||
|
with self.test_client() as c:
|
||||||
|
r = c.get(
|
||||||
|
'/v3/OS-TRUST/trusts/%s/roles' % self.trust_id,
|
||||||
|
headers=self.headers
|
||||||
|
)
|
||||||
|
self.assertEqual(r.json['roles'][0]['id'],
|
||||||
|
self.bootstrapper.member_role_id)
|
||||||
|
|
||||||
|
def test_user_can_get_trust_role_for_other_user(self):
|
||||||
|
PROVIDERS.trust_api.create_trust(
|
||||||
|
self.trust_id, **self.trust_data)
|
||||||
|
|
||||||
|
with self.test_client() as c:
|
||||||
|
c.get(
|
||||||
|
('/v3/OS-TRUST/trusts/%s/roles/%s' %
|
||||||
|
(self.trust_id, self.bootstrapper.member_role_id)),
|
||||||
|
headers=self.headers
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SystemReaderTests(TrustTests, _SystemUserTests):
|
||||||
|
"""Tests for system reader users."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(AdminTokenTests, self).setUp()
|
super(SystemReaderTests, self).setUp()
|
||||||
self.config_fixture.config(admin_token='ADMIN')
|
self.config_fixture.config(group='oslo_policy', enforce_scope=True)
|
||||||
self.headers = {'X-Auth-Token': 'ADMIN'}
|
|
||||||
|
|
||||||
def test_admin_can_delete_trust_for_other_user(self):
|
system_reader = unit.new_user_ref(
|
||||||
|
domain_id=CONF.identity.default_domain_id
|
||||||
|
)
|
||||||
|
self.user_id = PROVIDERS.identity_api.create_user(
|
||||||
|
system_reader
|
||||||
|
)['id']
|
||||||
|
PROVIDERS.assignment_api.create_system_grant_for_user(
|
||||||
|
self.user_id, self.bootstrapper.reader_role_id
|
||||||
|
)
|
||||||
|
|
||||||
|
auth = self.build_authentication_request(
|
||||||
|
user_id=self.user_id,
|
||||||
|
password=system_reader['password'],
|
||||||
|
system=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Grab a token using the persona we're testing and prepare headers
|
||||||
|
# for requests we'll be making in the tests.
|
||||||
|
with self.test_client() as c:
|
||||||
|
r = c.post('/v3/auth/tokens', json=auth)
|
||||||
|
self.token_id = r.headers['X-Subject-Token']
|
||||||
|
self.headers = {'X-Auth-Token': self.token_id}
|
||||||
|
|
||||||
|
def test_user_cannot_create_trust(self):
|
||||||
|
json = {'trust': self.trust_data['trust']}
|
||||||
|
json['trust']['roles'] = self.trust_data['roles']
|
||||||
|
|
||||||
|
with self.test_client() as c:
|
||||||
|
c.post(
|
||||||
|
'/v3/OS-TRUST/trusts',
|
||||||
|
json=json,
|
||||||
|
headers=self.headers,
|
||||||
|
expected_status_code=http_client.FORBIDDEN
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_user_cannot_delete_trust(self):
|
||||||
ref = PROVIDERS.trust_api.create_trust(
|
ref = PROVIDERS.trust_api.create_trust(
|
||||||
self.trust_id, **self.trust_data)
|
self.trust_id, **self.trust_data)
|
||||||
|
|
||||||
@ -211,11 +334,11 @@ class AdminTokenTests(TrustTests, _AdminTestsMixin):
|
|||||||
c.delete(
|
c.delete(
|
||||||
'/v3/OS-TRUST/trusts/%s' % ref['id'],
|
'/v3/OS-TRUST/trusts/%s' % ref['id'],
|
||||||
headers=self.headers,
|
headers=self.headers,
|
||||||
expected_status_code=http_client.NO_CONTENT
|
expected_status_code=http_client.FORBIDDEN
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SystemAdminTests(TrustTests, _AdminTestsMixin):
|
class SystemAdminTests(TrustTests, _AdminTestsMixin, _SystemUserTests):
|
||||||
"""Tests for system admin users."""
|
"""Tests for system admin users."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -250,18 +373,6 @@ class SystemAdminTests(TrustTests, _AdminTestsMixin):
|
|||||||
expected_status_code=http_client.FORBIDDEN
|
expected_status_code=http_client.FORBIDDEN
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_admin_list_all_trusts_overridden_defaults(self):
|
|
||||||
self._override_policy_old_defaults()
|
|
||||||
PROVIDERS.trust_api.create_trust(
|
|
||||||
self.trust_id, **self.trust_data)
|
|
||||||
|
|
||||||
with self.test_client() as c:
|
|
||||||
r = c.get(
|
|
||||||
'/v3/OS-TRUST/trusts',
|
|
||||||
headers=self.headers
|
|
||||||
)
|
|
||||||
self.assertEqual(1, len(r.json['trusts']))
|
|
||||||
|
|
||||||
def test_admin_cannot_delete_trust_for_user_overridden_defaults(self):
|
def test_admin_cannot_delete_trust_for_user_overridden_defaults(self):
|
||||||
# only the is_admin admin can do this
|
# only the is_admin admin can do this
|
||||||
self._override_policy_old_defaults()
|
self._override_policy_old_defaults()
|
||||||
@ -312,6 +423,18 @@ class SystemAdminTests(TrustTests, _AdminTestsMixin):
|
|||||||
expected_status_code=http_client.FORBIDDEN
|
expected_status_code=http_client.FORBIDDEN
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_user_list_all_trusts_overridden_defaults(self):
|
||||||
|
self._override_policy_old_defaults()
|
||||||
|
PROVIDERS.trust_api.create_trust(
|
||||||
|
self.trust_id, **self.trust_data)
|
||||||
|
|
||||||
|
with self.test_client() as c:
|
||||||
|
r = c.get(
|
||||||
|
'/v3/OS-TRUST/trusts',
|
||||||
|
headers=self.headers
|
||||||
|
)
|
||||||
|
self.assertEqual(1, len(r.json['trusts']))
|
||||||
|
|
||||||
|
|
||||||
class ProjectUserTests(TrustTests):
|
class ProjectUserTests(TrustTests):
|
||||||
"""Tests for all project users."""
|
"""Tests for all project users."""
|
||||||
|
Loading…
Reference in New Issue
Block a user