From 718d122fe1595d59b4eab99c3a744bfe34941369 Mon Sep 17 00:00:00 2001 From: Lance Bragstad Date: Mon, 7 Jan 2019 20:48:11 +0000 Subject: [PATCH] Implement system admin role in project API This commit introduces the system admin role to the projects API, making it consistent with other system-admin policy definitions. Subsequent patches will build on this work to expose more functionality to domain users: - domain reader functionality - domain member test coverage - domain admin functionality - project user test coverage Change-Id: Iceed65d34a8a7cff8841000d7703b1a48e95bb24 Closes-Bug: 1805403 Related-Bug: 1750660 Related-Bug: 1806762 --- keystone/common/policies/project.py | 33 +++++- .../tests/unit/protection/v3/test_projects.py | 104 ++++++++++++++++++ .../notes/bug-1805403-c003627a64768716.yaml | 37 +++++++ 3 files changed, 168 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/bug-1805403-c003627a64768716.yaml diff --git a/keystone/common/policies/project.py b/keystone/common/policies/project.py index 3bc70cf427..a09a2ab0a4 100644 --- a/keystone/common/policies/project.py +++ b/keystone/common/policies/project.py @@ -35,6 +35,18 @@ deprecated_list_user_projects = policy.DeprecatedRule( name=base.IDENTITY % 'list_user_projects', check_str=base.RULE_ADMIN_OR_OWNER ) +deprecated_create_project = policy.DeprecatedRule( + name=base.IDENTITY % 'create_project', + check_str=base.RULE_ADMIN_REQUIRED +) +deprecated_update_project = policy.DeprecatedRule( + name=base.IDENTITY % 'update_project', + check_str=base.RULE_ADMIN_REQUIRED +) +deprecated_delete_project = policy.DeprecatedRule( + name=base.IDENTITY % 'delete_project', + check_str=base.RULE_ADMIN_REQUIRED +) DEPRECATED_REASON = """ As of the Stein release, the project API understands how to handle @@ -104,7 +116,7 @@ project_policies = [ deprecated_since=versionutils.deprecated.STEIN), policy.DocumentedRuleDefault( name=base.IDENTITY % 'create_project', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=base.SYSTEM_ADMIN, # FIXME(lbragstad): System administrators should be able to create # projects anywhere in the deployment. Domain administrators should # only be able to create projects within their domain. Project @@ -115,25 +127,34 @@ project_policies = [ scope_types=['system'], description='Create project.', operations=[{'path': '/v3/projects', - 'method': 'POST'}]), + 'method': 'POST'}], + deprecated_rule=deprecated_create_project, + deprecated_reason=DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.STEIN), policy.DocumentedRuleDefault( name=base.IDENTITY % 'update_project', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=base.SYSTEM_ADMIN, # FIXME(lbragstad): See the above comment for create_project as to why # this is limited to only system-scope. scope_types=['system'], description='Update project.', operations=[{'path': '/v3/projects/{project_id}', - 'method': 'PATCH'}]), + 'method': 'PATCH'}], + deprecated_rule=deprecated_update_project, + deprecated_reason=DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.STEIN), policy.DocumentedRuleDefault( name=base.IDENTITY % 'delete_project', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=base.SYSTEM_ADMIN, # FIXME(lbragstad): See the above comment for create_project as to why # this is limited to only system-scope. scope_types=['system'], description='Delete project.', operations=[{'path': '/v3/projects/{project_id}', - 'method': 'DELETE'}]), + 'method': 'DELETE'}], + deprecated_rule=deprecated_delete_project, + deprecated_reason=DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.STEIN), policy.DocumentedRuleDefault( name=base.IDENTITY % 'list_project_tags', check_str=base.RULE_ADMIN_OR_TARGET_PROJECT, diff --git a/keystone/tests/unit/protection/v3/test_projects.py b/keystone/tests/unit/protection/v3/test_projects.py index ba2a25f43c..db61616a4e 100644 --- a/keystone/tests/unit/protection/v3/test_projects.py +++ b/keystone/tests/unit/protection/v3/test_projects.py @@ -214,6 +214,110 @@ class SystemMemberTests(base_classes.TestCaseWithBootstrap, self.headers = {'X-Auth-Token': self.token_id} +class SystemAdminTests(base_classes.TestCaseWithBootstrap, + common_auth.AuthTestMixin, + _SystemUserTests): + + def setUp(self): + super(SystemAdminTests, self).setUp() + self.loadapp() + self.useFixture(ksfixtures.Policy(self.config_fixture)) + self.config_fixture.config(group='oslo_policy', enforce_scope=True) + + self.user_id = self.bootstrapper.admin_user_id + auth = self.build_authentication_request( + user_id=self.user_id, + password=self.bootstrapper.admin_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_can_create_projects(self): + create = { + 'project': unit.new_project_ref( + domain_id=CONF.identity.default_domain_id + ) + } + + with self.test_client() as c: + c.post('/v3/projects', json=create, headers=self.headers) + + def test_user_can_update_projects(self): + project = PROVIDERS.resource_api.create_project( + uuid.uuid4().hex, + unit.new_project_ref(domain_id=CONF.identity.default_domain_id) + ) + + update = {'project': {'description': uuid.uuid4().hex}} + + with self.test_client() as c: + c.patch( + '/v3/projects/%s' % project['id'], json=update, + headers=self.headers + ) + + def test_user_can_update_non_existant_project_not_found(self): + update = {'project': {'description': uuid.uuid4().hex}} + + with self.test_client() as c: + c.patch( + '/v3/projects/%s' % uuid.uuid4().hex, json=update, + headers=self.headers, + expected_status_code=http_client.NOT_FOUND + ) + + def test_user_can_delete_projects(self): + project = PROVIDERS.resource_api.create_project( + uuid.uuid4().hex, + unit.new_project_ref(domain_id=CONF.identity.default_domain_id) + ) + + with self.test_client() as c: + c.delete('/v3/projects/%s' % project['id'], headers=self.headers) + + def test_user_can_delete_non_existant_project_not_found(self): + with self.test_client() as c: + c.delete( + '/v3/projects/%s' % uuid.uuid4().hex, headers=self.headers, + expected_status_code=http_client.NOT_FOUND + ) + + def test_user_can_list_their_projects(self): + other_project = PROVIDERS.resource_api.create_project( + uuid.uuid4().hex, + unit.new_project_ref(domain_id=CONF.identity.default_domain_id) + ) + + user_project = PROVIDERS.resource_api.create_project( + uuid.uuid4().hex, + unit.new_project_ref(domain_id=CONF.identity.default_domain_id) + ) + + PROVIDERS.assignment_api.create_grant( + self.bootstrapper.reader_role_id, user_id=self.user_id, + project_id=user_project['id'] + ) + + with self.test_client() as c: + r = c.get( + '/v3/users/%s/projects' % self.user_id, headers=self.headers, + ) + self.assertEqual(2, len(r.json['projects'])) + project_ids = [] + for project in r.json['projects']: + project_ids.append(project['id']) + + self.assertIn(user_project['id'], project_ids) + self.assertIn(self.bootstrapper.project_id, project_ids) + self.assertNotIn(other_project['id'], project_ids) + + class ProjectUserTests(base_classes.TestCaseWithBootstrap, common_auth.AuthTestMixin): diff --git a/releasenotes/notes/bug-1805403-c003627a64768716.yaml b/releasenotes/notes/bug-1805403-c003627a64768716.yaml new file mode 100644 index 0000000000..37fa0e0c25 --- /dev/null +++ b/releasenotes/notes/bug-1805403-c003627a64768716.yaml @@ -0,0 +1,37 @@ +--- +features: + - | + [`bug 1805403 `_] + The project API now supports the ``admin``, ``member``, and + ``reader`` default roles. +upgrade: + - | + [`bug 1805403 `_] + The project 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 + project policies. +deprecations: + - | + [`bug 1805403 `_] + The project policies have been deprecated. The + ``identity:get_project`` policy now uses ``(role:reader and + system_scope:all) or project_id:%(target.project.id)s`` instead of + ``rule:admin_required or project_id:%(target.project.id)s``. The + ``identity:list_projects`` policy now uses ``role:reader and + system_scope:all`` instead of ``rule:admin_required``. The + ``identity:create_project``, ``identity:update_project``, and + ``identity:delete_project`` policies now use ``role:admin and + system_scope:all`` instead of ``rule:admin_required``. The + ``identity:list_user_projects`` policy now uses ``(role:admin and + system_scope:all) or user_id:%(target.user.id)s`` instead of + ``rule:admin_or_owner``. These new defaults automatically account + for system-scope and support a read-only role, making it easier + for system administrators to delegate subsets of responsibility + without compromising security. Please consider these new defaults + if your deployment overrides the project policies. +security: + - | + [`bug 1805403 `_] + The project API now uses system-scope and default roles to + provide better accessibility to users in a secure way.