Merge "Add explicit testing for project users and the user API"

This commit is contained in:
Zuul 2019-03-23 23:04:01 +00:00 committed by Gerrit Code Review
commit ce06e58c33
2 changed files with 337 additions and 0 deletions

View File

@ -300,6 +300,165 @@ class _DomainMemberAndReaderUserTests(object):
)
class _ProjectUserTests(object):
"""Common tests cases for all project users."""
def test_user_cannot_get_users_within_their_domain(self):
user = PROVIDERS.identity_api.create_user(
unit.new_user_ref(domain_id=self.domain_id)
)
with self.test_client() as c:
c.get(
'/v3/users/%s' % user['id'], headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
def test_user_cannot_get_users_in_other_domains(self):
domain = PROVIDERS.resource_api.create_domain(
uuid.uuid4().hex, unit.new_domain_ref()
)
user = PROVIDERS.identity_api.create_user(
unit.new_user_ref(domain_id=domain['id'])
)
with self.test_client() as c:
c.get(
'/v3/users/%s' % user['id'], headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
def test_user_cannot_get_non_existent_user_forbidden(self):
with self.test_client() as c:
c.get(
'/v3/users/%s' % uuid.uuid4().hex, headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
def test_user_cannot_list_users_within_domain(self):
with self.test_client() as c:
c.get(
'/v3/users?domain_id=%s' % self.domain_id,
headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
def test_user_cannot_list_users_in_other_domains(self):
domain = PROVIDERS.resource_api.create_domain(
uuid.uuid4().hex, unit.new_domain_ref()
)
PROVIDERS.identity_api.create_user(
unit.new_user_ref(domain_id=domain['id'])
)
with self.test_client() as c:
c.get(
'/v3/users?domain_id=%s' % domain['id'],
headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
def test_user_cannot_create_users_within_domain(self):
create = {
'user': {
'domain_id': self.domain_id,
'name': uuid.uuid4().hex
}
}
with self.test_client() as c:
c.post(
'/v3/users', json=create, headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
def test_user_cannot_create_users_in_other_domains(self):
domain = PROVIDERS.resource_api.create_domain(
uuid.uuid4().hex, unit.new_domain_ref()
)
create = {
'user': {
'domain_id': domain['id'],
'name': uuid.uuid4().hex
}
}
with self.test_client() as c:
c.post(
'/v3/users', json=create, headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
def test_user_cannot_update_users_within_domain(self):
user = PROVIDERS.identity_api.create_user(
unit.new_user_ref(domain_id=self.domain_id)
)
update = {'user': {'email': uuid.uuid4().hex}}
with self.test_client() as c:
c.patch(
'/v3/users/%s' % user['id'], json=update, headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
def test_user_cannot_update_users_in_other_domain(self):
domain = PROVIDERS.resource_api.create_domain(
uuid.uuid4().hex, unit.new_domain_ref()
)
user = PROVIDERS.identity_api.create_user(
unit.new_user_ref(domain_id=domain['id'])
)
update = {'user': {'email': uuid.uuid4().hex}}
with self.test_client() as c:
c.patch(
'/v3/users/%s' % user['id'], json=update, headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
def test_user_cannot_update_non_existent_user_forbidden(self):
update = {'user': {'email': uuid.uuid4().hex}}
with self.test_client() as c:
c.patch(
'/v3/users/%s' % uuid.uuid4().hex, json=update,
headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
def test_user_cannot_delete_users_within_domain(self):
user = PROVIDERS.identity_api.create_user(
unit.new_user_ref(domain_id=self.domain_id)
)
with self.test_client() as c:
c.delete(
'/v3/users/%s' % user['id'], headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
def test_user_cannot_delete_users_in_other_domains(self):
domain = PROVIDERS.resource_api.create_domain(
uuid.uuid4().hex, unit.new_domain_ref()
)
user = PROVIDERS.identity_api.create_user(
unit.new_user_ref(domain_id=domain['id'])
)
with self.test_client() as c:
c.delete(
'/v3/users/%s' % user['id'], headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
def test_user_cannot_delete_non_existent_user_forbidden(self):
with self.test_client() as c:
c.delete(
'/v3/users/%s' % uuid.uuid4().hex, headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
class SystemReaderTests(base_classes.TestCaseWithBootstrap,
common_auth.AuthTestMixin,
_CommonUserTests,
@ -670,3 +829,142 @@ class DomainAdminTests(base_classes.TestCaseWithBootstrap,
'/v3/users/%s' % uuid.uuid4().hex, headers=self.headers,
expected_status_code=http_client.FORBIDDEN
)
class ProjectReaderTests(base_classes.TestCaseWithBootstrap,
common_auth.AuthTestMixin,
_CommonUserTests,
_ProjectUserTests):
def setUp(self):
super(ProjectReaderTests, self).setUp()
self.loadapp()
self.useFixture(ksfixtures.Policy(self.config_fixture))
self.config_fixture.config(group='oslo_policy', enforce_scope=True)
domain = PROVIDERS.resource_api.create_domain(
uuid.uuid4().hex, unit.new_domain_ref()
)
self.domain_id = domain['id']
project = unit.new_project_ref(domain_id=self.domain_id)
project = PROVIDERS.resource_api.create_project(project['id'], project)
self.project_id = project['id']
project_reader = unit.new_user_ref(domain_id=self.domain_id)
self.user_id = PROVIDERS.identity_api.create_user(project_reader)['id']
PROVIDERS.assignment_api.create_grant(
self.bootstrapper.reader_role_id, user_id=self.user_id,
project_id=self.project_id
)
auth = self.build_authentication_request(
user_id=self.user_id, password=project_reader['password'],
project_id=self.project_id,
)
# 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}
class ProjectMemberTests(base_classes.TestCaseWithBootstrap,
common_auth.AuthTestMixin,
_CommonUserTests,
_ProjectUserTests):
def setUp(self):
super(ProjectMemberTests, self).setUp()
self.loadapp()
self.useFixture(ksfixtures.Policy(self.config_fixture))
self.config_fixture.config(group='oslo_policy', enforce_scope=True)
domain = PROVIDERS.resource_api.create_domain(
uuid.uuid4().hex, unit.new_domain_ref()
)
self.domain_id = domain['id']
project = unit.new_project_ref(domain_id=self.domain_id)
project = PROVIDERS.resource_api.create_project(project['id'], project)
self.project_id = project['id']
project_member = unit.new_user_ref(domain_id=self.domain_id)
self.user_id = PROVIDERS.identity_api.create_user(project_member)['id']
PROVIDERS.assignment_api.create_grant(
self.bootstrapper.member_role_id, user_id=self.user_id,
project_id=self.project_id
)
auth = self.build_authentication_request(
user_id=self.user_id, password=project_member['password'],
project_id=self.project_id,
)
# 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}
class ProjectAdminTests(base_classes.TestCaseWithBootstrap,
common_auth.AuthTestMixin,
_CommonUserTests,
_ProjectUserTests):
def setUp(self):
super(ProjectAdminTests, self).setUp()
self.loadapp()
self.policy_file = self.useFixture(temporaryfile.SecureTempFile())
self.policy_file_name = self.policy_file.file_name
self.useFixture(
ksfixtures.Policy(
self.config_fixture, policy_file=self.policy_file_name
)
)
self._override_policy()
self.config_fixture.config(group='oslo_policy', enforce_scope=True)
domain = PROVIDERS.resource_api.create_domain(
uuid.uuid4().hex, unit.new_domain_ref()
)
self.domain_id = domain['id']
self.user_id = self.bootstrapper.admin_user_id
auth = self.build_authentication_request(
user_id=self.user_id,
password=self.bootstrapper.admin_password,
project_id=self.bootstrapper.project_id
)
# 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 _override_policy(self):
# TODO(lbragstad): Remove this once the deprecated policies in
# keystone.common.policies.users have been removed. This is only
# here to make sure we test the new policies instead of the deprecated
# ones. Oslo.policy will OR deprecated policies with new policies to
# maintain compatibility and give operators a chance to update
# permissions or update policies without breaking users. This will
# cause these specific tests to fail since we're trying to correct this
# broken behavior with better scope checking.
with open(self.policy_file_name, 'w') as f:
overridden_policies = {
'identity:get_user': up.SYSTEM_READER_OR_DOMAIN_READER_OR_USER,
'identity:list_users': up.SYSTEM_READER_OR_DOMAIN_READER,
'identity:create_user': up.SYSTEM_ADMIN_OR_DOMAIN_ADMIN,
'identity:update_user': up.SYSTEM_ADMIN_OR_DOMAIN_ADMIN,
'identity:delete_user': up.SYSTEM_ADMIN_OR_DOMAIN_ADMIN
}
f.write(jsonutils.dumps(overridden_policies))

View File

@ -0,0 +1,39 @@
---
features:
- |
[`bug 1748027 <https://bugs.launchpad.net/keystone/+bug/1748027>`_]
The user API now supports the ``admin``, ``member``, and
``reader`` default roles across system-scope, domain-scope, and
project-scope.
upgrade:
- |
[`bug 1748027 <https://bugs.launchpad.net/keystone/+bug/1748027>`_]
The user API uses new default policies that make it more
accessible to end users and administrators in a secure way. Please
consider these new defaults if your deployment overrides
user policies.
deprecations:
- |
[`bug 1748027 <https://bugs.launchpad.net/keystone/+bug/1748027>`_]
The user policies have been deprecated. The ``identity:get_user``
policy now uses ``(role:reader and system_scope:all) or
(role:reader and token.domain.id:%(target.user.domain_id)s) or
user_id:%(target.user.id)s`` instead of ``rule:admin_or_owner``.
The ``identity:list_users`` policy now uses ``(role:reader and
system_scope:all) or (role:reader and
domain_id:%(target.domain_id)s)`` instead of
``rule:admin_required``. The ``identity:create_user``,
``identity:update_user``, and ``identity:delete_user`` policies
now use ``(role:admin and system_scope:all) or (role:admin and
token.domain.id:%(target.user.domain_id)s)`` instead of
``rule:admin_required``. These new defaults automatically include
support for a read-only role and allow for more granular access to
user APIs, making it easier for system and domain administrators
to delegate authorization, safely. Please consider these new
defaults if your deployment overrides user policies.
security:
- |
[`bug 1748027 <https://bugs.launchpad.net/keystone/+bug/1748027>`_]
The user API now uses system-scope, domain-scope, project-scope and default
roles to provide better accessibility to users in a secure way.