summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLance Bragstad <lbragstad@gmail.com>2018-08-21 20:41:38 +0000
committerLance Bragstad <lbragstad@gmail.com>2018-10-29 15:01:29 +0000
commit239bed09a922d6076711ca5c112be6299fa0f0bb (patch)
tree9249be9b19dcaf26a09847a4ff3abb1e11f6888f
parentb5338c38a440f22cacaf5089bbbabec9892585da (diff)
Implement scope_type checking for credentials
This change adds tests cases for the default roles keystone supports at install time. It also modifies the policies for the credentials API to be more self-service by properly checking for various scopes. Closes-Bug: 1788415 Partial-Bug: 968696 Change-Id: Ifedb7798c96930b6cc0f91159a14a21ac4b02f9f
Notes
Notes (review): Code-Review+2: Morgan Fainberg <morgan.fainberg@gmail.com> Code-Review+1: Vishakha Agarwal <agarwalvishakha18@gmail.com> Code-Review+2: wangxiyuan <wangxiyuan@huawei.com> Workflow+1: wangxiyuan <wangxiyuan@huawei.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Tue, 30 Oct 2018 08:58:23 +0000 Reviewed-on: https://review.openstack.org/594547 Project: openstack/keystone Branch: refs/heads/master
-rw-r--r--keystone/api/credentials.py61
-rw-r--r--keystone/common/policies/credential.py103
-rw-r--r--keystone/tests/unit/base_classes.py1
-rw-r--r--keystone/tests/unit/protection/__init__.py0
-rw-r--r--keystone/tests/unit/protection/v3/__init__.py0
-rw-r--r--keystone/tests/unit/protection/v3/test_credentials.py1137
-rw-r--r--keystone/tests/unit/test_v3_credential.py18
-rw-r--r--releasenotes/notes/bug-1788415-3190279e9c900f76.yaml25
8 files changed, 1309 insertions, 36 deletions
diff --git a/keystone/api/credentials.py b/keystone/api/credentials.py
index 8f6fda3..f258e19 100644
--- a/keystone/api/credentials.py
+++ b/keystone/api/credentials.py
@@ -21,16 +21,31 @@ from six.moves import http_client
21from keystone.common import provider_api 21from keystone.common import provider_api
22from keystone.common import rbac_enforcer 22from keystone.common import rbac_enforcer
23from keystone.common import validation 23from keystone.common import validation
24import keystone.conf
24from keystone.credential import schema 25from keystone.credential import schema
25from keystone import exception 26from keystone import exception
26from keystone.i18n import _ 27from keystone.i18n import _
27from keystone.server import flask as ks_flask 28from keystone.server import flask as ks_flask
28 29
29 30CONF = keystone.conf.CONF
30PROVIDERS = provider_api.ProviderAPIs 31PROVIDERS = provider_api.ProviderAPIs
31ENFORCER = rbac_enforcer.RBACEnforcer 32ENFORCER = rbac_enforcer.RBACEnforcer
32 33
33 34
35def _build_target_enforcement():
36 target = {}
37 try:
38 target['credential'] = PROVIDERS.credential_api.get_credential(
39 flask.request.view_args.get('credential_id')
40 )
41 except exception.NotFound: # nosec
42 # Defer existance in the event the credential doesn't exist, we'll
43 # check this later anyway.
44 pass
45
46 return target
47
48
34class CredentialResource(ks_flask.ResourceBase): 49class CredentialResource(ks_flask.ResourceBase):
35 collection_key = 'credentials' 50 collection_key = 'credentials'
36 member_key = 'credential' 51 member_key = 'credential'
@@ -75,17 +90,34 @@ class CredentialResource(ks_flask.ResourceBase):
75 90
76 def _list_credentials(self): 91 def _list_credentials(self):
77 filters = ['user_id', 'type'] 92 filters = ['user_id', 'type']
93 if not self.oslo_context.system_scope:
94 target = {'credential': {'user_id': self.oslo_context.user_id}}
95 else:
96 target = None
78 ENFORCER.enforce_call(action='identity:list_credentials', 97 ENFORCER.enforce_call(action='identity:list_credentials',
79 filters=filters) 98 filters=filters, target_attr=target)
80 hints = self.build_driver_hints(filters) 99 hints = self.build_driver_hints(filters)
81 refs = PROVIDERS.credential_api.list_credentials(hints) 100 refs = PROVIDERS.credential_api.list_credentials(hints)
101 # If the request was filtered, make sure to return only the
102 # credentials specific to that user. This makes it so that users with
103 # roles on projects can't see credentials that aren't theirs.
104 if (not self.oslo_context.system_scope and
105 CONF.oslo_policy.enforce_scope):
106 filtered_refs = []
107 for ref in refs:
108 if ref['user_id'] == target['credential']['user_id']:
109 filtered_refs.append(ref)
110 refs = filtered_refs
82 refs = [self._blob_to_json(r) for r in refs] 111 refs = [self._blob_to_json(r) for r in refs]
83 return self.wrap_collection(refs, hints=hints) 112 return self.wrap_collection(refs, hints=hints)
84 113
85 def _get_credential(self, credential_id): 114 def _get_credential(self, credential_id):
86 ENFORCER.enforce_call(action='identity:get_credential') 115 ENFORCER.enforce_call(
87 ref = PROVIDERS.credential_api.get_credential(credential_id) 116 action='identity:get_credential',
88 return self.wrap_member(self._blob_to_json(ref)) 117 build_target=_build_target_enforcement
118 )
119 credential = PROVIDERS.credential_api.get_credential(credential_id)
120 return self.wrap_member(self._blob_to_json(credential))
89 121
90 def get(self, credential_id=None): 122 def get(self, credential_id=None):
91 # Get Credential or List of credentials. 123 # Get Credential or List of credentials.
@@ -97,8 +129,12 @@ class CredentialResource(ks_flask.ResourceBase):
97 129
98 def post(self): 130 def post(self):
99 # Create a new credential 131 # Create a new credential
100 ENFORCER.enforce_call(action='identity:create_credential')
101 credential = flask.request.json.get('credential', {}) 132 credential = flask.request.json.get('credential', {})
133 target = {}
134 target['credential'] = credential
135 ENFORCER.enforce_call(
136 action='identity:create_credential', target_attr=target
137 )
102 validation.lazy_validate(schema.credential_create, credential) 138 validation.lazy_validate(schema.credential_create, credential)
103 trust_id = getattr(self.oslo_context, 'trust_id', None) 139 trust_id = getattr(self.oslo_context, 'trust_id', None)
104 ref = self._assign_unique_id( 140 ref = self._assign_unique_id(
@@ -108,7 +144,12 @@ class CredentialResource(ks_flask.ResourceBase):
108 144
109 def patch(self, credential_id): 145 def patch(self, credential_id):
110 # Update Credential 146 # Update Credential
111 ENFORCER.enforce_call(action='identity:update_credential') 147 ENFORCER.enforce_call(
148 action='identity:update_credential',
149 build_target=_build_target_enforcement
150 )
151 PROVIDERS.credential_api.get_credential(credential_id)
152
112 credential = flask.request.json.get('credential', {}) 153 credential = flask.request.json.get('credential', {})
113 validation.lazy_validate(schema.credential_update, credential) 154 validation.lazy_validate(schema.credential_update, credential)
114 self._require_matching_id(credential) 155 self._require_matching_id(credential)
@@ -118,7 +159,11 @@ class CredentialResource(ks_flask.ResourceBase):
118 159
119 def delete(self, credential_id): 160 def delete(self, credential_id):
120 # Delete credentials 161 # Delete credentials
121 ENFORCER.enforce_call(action='identity:delete_credential') 162 ENFORCER.enforce_call(
163 action='identity:delete_credential',
164 build_target=_build_target_enforcement
165 )
166
122 return (PROVIDERS.credential_api.delete_credential(credential_id), 167 return (PROVIDERS.credential_api.delete_credential(credential_id),
123 http_client.NO_CONTENT) 168 http_client.NO_CONTENT)
124 169
diff --git a/keystone/common/policies/credential.py b/keystone/common/policies/credential.py
index 147f31c..f04ba54 100644
--- a/keystone/common/policies/credential.py
+++ b/keystone/common/policies/credential.py
@@ -10,56 +10,109 @@
10# License for the specific language governing permissions and limitations 10# License for the specific language governing permissions and limitations
11# under the License. 11# under the License.
12 12
13from oslo_log import versionutils
13from oslo_policy import policy 14from oslo_policy import policy
14 15
15from keystone.common.policies import base 16from keystone.common.policies import base
16 17
18SYSTEM_READER_OR_CRED_OWNER = (
19 '(role:reader and system_scope:all) '
20 'or user_id:%(target.credential.user_id)s'
21)
22SYSTEM_MEMBER_OR_CRED_OWNER = (
23 '(role:member and system_scope:all) '
24 'or user_id:%(target.credential.user_id)s'
25)
26SYSTEM_ADMIN_OR_CRED_OWNER = (
27 '(role:admin and system_scope:all) '
28 'or user_id:%(target.credential.user_id)s'
29)
30
31DEPRECATED_REASON = (
32 'As of the Stein release, the credential API now understands how to '
33 'handle system-scoped tokens in addition to project-scoped tokens, making '
34 'the API more accessible to users without compromising security or '
35 'manageability for administrators. The new default policies for this API '
36 'account for these changes automatically.'
37)
38deprecated_get_credential = policy.DeprecatedRule(
39 name=base.IDENTITY % 'get_credential',
40 check_str=base.RULE_ADMIN_REQUIRED
41)
42deprecated_list_credentials = policy.DeprecatedRule(
43 name=base.IDENTITY % 'list_credentials',
44 check_str=base.RULE_ADMIN_REQUIRED
45)
46deprecated_create_credential = policy.DeprecatedRule(
47 name=base.IDENTITY % 'create_credential',
48 check_str=base.RULE_ADMIN_REQUIRED
49)
50deprecated_update_credential = policy.DeprecatedRule(
51 name=base.IDENTITY % 'update_credential',
52 check_str=base.RULE_ADMIN_REQUIRED
53)
54deprecated_delete_credential = policy.DeprecatedRule(
55 name=base.IDENTITY % 'delete_credential',
56 check_str=base.RULE_ADMIN_REQUIRED
57)
58
59
17credential_policies = [ 60credential_policies = [
18 policy.DocumentedRuleDefault( 61 policy.DocumentedRuleDefault(
19 name=base.IDENTITY % 'get_credential', 62 name=base.IDENTITY % 'get_credential',
20 check_str=base.RULE_ADMIN_REQUIRED, 63 check_str=SYSTEM_READER_OR_CRED_OWNER,
21 # FIXME(lbragstad): Credentials aren't really project-scoped or 64 scope_types=['system', 'project'],
22 # system-scoped. Instead, they are tied to a user. If this API is
23 # called with a system-scoped token, it's a system-administrator and
24 # they should be able to get any credential for management reasons. If
25 # this API is called with a project-scoped token, then extra
26 # enforcement needs to happen based on who created the credential, what
27 # projects they are members of, and the project the token is scoped to.
28 # When we fully support the second case, we can add `project` to the
29 # list of scope_types. This comment applies to the rest of the policies
30 # in this module.
31 # scope_types=['system', 'project'],
32 description='Show credentials details.', 65 description='Show credentials details.',
33 operations=[{'path': '/v3/credentials/{credential_id}', 66 operations=[{'path': '/v3/credentials/{credential_id}',
34 'method': 'GET'}]), 67 'method': 'GET'}],
68 deprecated_rule=deprecated_get_credential,
69 deprecated_reason=DEPRECATED_REASON,
70 deprecated_since=versionutils.deprecated.STEIN
71 ),
35 policy.DocumentedRuleDefault( 72 policy.DocumentedRuleDefault(
36 name=base.IDENTITY % 'list_credentials', 73 name=base.IDENTITY % 'list_credentials',
37 check_str=base.RULE_ADMIN_REQUIRED, 74 check_str=SYSTEM_READER_OR_CRED_OWNER,
38 # scope_types=['system', 'project'], 75 scope_types=['system', 'project'],
39 description='List credentials.', 76 description='List credentials.',
40 operations=[{'path': '/v3/credentials', 77 operations=[{'path': '/v3/credentials',
41 'method': 'GET'}]), 78 'method': 'GET'}],
79 deprecated_rule=deprecated_list_credentials,
80 deprecated_reason=DEPRECATED_REASON,
81 deprecated_since=versionutils.deprecated.STEIN
82 ),
42 policy.DocumentedRuleDefault( 83 policy.DocumentedRuleDefault(
43 name=base.IDENTITY % 'create_credential', 84 name=base.IDENTITY % 'create_credential',
44 check_str=base.RULE_ADMIN_REQUIRED, 85 check_str=SYSTEM_ADMIN_OR_CRED_OWNER,
45 # scope_types=['system', 'project'], 86 scope_types=['system', 'project'],
46 description='Create credential.', 87 description='Create credential.',
47 operations=[{'path': '/v3/credentials', 88 operations=[{'path': '/v3/credentials',
48 'method': 'POST'}]), 89 'method': 'POST'}],
90 deprecated_rule=deprecated_create_credential,
91 deprecated_reason=DEPRECATED_REASON,
92 deprecated_since=versionutils.deprecated.STEIN
93 ),
49 policy.DocumentedRuleDefault( 94 policy.DocumentedRuleDefault(
50 name=base.IDENTITY % 'update_credential', 95 name=base.IDENTITY % 'update_credential',
51 check_str=base.RULE_ADMIN_REQUIRED, 96 check_str=SYSTEM_MEMBER_OR_CRED_OWNER,
52 # scope_types=['system', 'project'], 97 scope_types=['system', 'project'],
53 description='Update credential.', 98 description='Update credential.',
54 operations=[{'path': '/v3/credentials/{credential_id}', 99 operations=[{'path': '/v3/credentials/{credential_id}',
55 'method': 'PATCH'}]), 100 'method': 'PATCH'}],
101 deprecated_rule=deprecated_update_credential,
102 deprecated_reason=DEPRECATED_REASON,
103 deprecated_since=versionutils.deprecated.STEIN
104 ),
56 policy.DocumentedRuleDefault( 105 policy.DocumentedRuleDefault(
57 name=base.IDENTITY % 'delete_credential', 106 name=base.IDENTITY % 'delete_credential',
58 check_str=base.RULE_ADMIN_REQUIRED, 107 check_str=SYSTEM_ADMIN_OR_CRED_OWNER,
59 # scope_types=['system', 'project'], 108 scope_types=['system', 'project'],
60 description='Delete credential.', 109 description='Delete credential.',
61 operations=[{'path': '/v3/credentials/{credential_id}', 110 operations=[{'path': '/v3/credentials/{credential_id}',
62 'method': 'DELETE'}]) 111 'method': 'DELETE'}],
112 deprecated_rule=deprecated_delete_credential,
113 deprecated_reason=DEPRECATED_REASON,
114 deprecated_since=versionutils.deprecated.STEIN
115 )
63] 116]
64 117
65 118
diff --git a/keystone/tests/unit/base_classes.py b/keystone/tests/unit/base_classes.py
index fb6e3af..39001d7 100644
--- a/keystone/tests/unit/base_classes.py
+++ b/keystone/tests/unit/base_classes.py
@@ -41,6 +41,7 @@ class TestCaseWithBootstrap(core.BaseTestCase):
41 self.useFixture(database.Database()) 41 self.useFixture(database.Database())
42 super(TestCaseWithBootstrap, self).setUp() 42 super(TestCaseWithBootstrap, self).setUp()
43 self.config_fixture = self.useFixture(config_fixture.Config(CONF)) 43 self.config_fixture = self.useFixture(config_fixture.Config(CONF))
44 CONF(args=[], project='keystone')
44 self.useFixture( 45 self.useFixture(
45 ksfixtures.KeyRepository( 46 ksfixtures.KeyRepository(
46 self.config_fixture, 47 self.config_fixture,
diff --git a/keystone/tests/unit/protection/__init__.py b/keystone/tests/unit/protection/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/keystone/tests/unit/protection/__init__.py
diff --git a/keystone/tests/unit/protection/v3/__init__.py b/keystone/tests/unit/protection/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/keystone/tests/unit/protection/v3/__init__.py
diff --git a/keystone/tests/unit/protection/v3/test_credentials.py b/keystone/tests/unit/protection/v3/test_credentials.py
new file mode 100644
index 0000000..21a2651
--- /dev/null
+++ b/keystone/tests/unit/protection/v3/test_credentials.py
@@ -0,0 +1,1137 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13import uuid
14
15from oslo_serialization import jsonutils
16from six.moves import http_client
17
18from keystone.common.policies import credential as cp
19from keystone.common import provider_api
20import keystone.conf
21from keystone.tests.common import auth as common_auth
22from keystone.tests import unit
23from keystone.tests.unit import base_classes
24from keystone.tests.unit import ksfixtures
25from keystone.tests.unit.ksfixtures import temporaryfile
26
27CONF = keystone.conf.CONF
28PROVIDERS = provider_api.ProviderAPIs
29
30
31class _UserCredentialTests(object):
32 """Test cases for anyone that has a valid user token."""
33
34 def test_user_can_create_credentials_for_themselves(self):
35 create = {
36 'credential': {
37 'blob': uuid.uuid4().hex,
38 'user_id': self.user_id,
39 'type': uuid.uuid4().hex
40 }
41 }
42 with self.test_client() as c:
43 c.post('/v3/credentials', json=create, headers=self.headers)
44
45 def test_user_can_get_their_credentials(self):
46 with self.test_client() as c:
47 create = {
48 'credential': {
49 'blob': uuid.uuid4().hex,
50 'type': uuid.uuid4().hex,
51 'user_id': self.user_id
52 }
53 }
54 r = c.post('/v3/credentials', json=create, headers=self.headers)
55 credential_id = r.json['credential']['id']
56
57 path = '/v3/credentials/%s' % credential_id
58 r = c.get(path, headers=self.headers)
59 self.assertEqual(
60 self.user_id, r.json['credential']['user_id']
61 )
62
63 def test_user_can_list_their_credentials(self):
64 with self.test_client() as c:
65 expected = []
66 for _ in range(2):
67 create = {
68 'credential': {
69 'blob': uuid.uuid4().hex,
70 'type': uuid.uuid4().hex,
71 'user_id': self.user_id
72 }
73 }
74 r = c.post(
75 '/v3/credentials', json=create, headers=self.headers
76 )
77 expected.append(r.json['credential'])
78
79 r = c.get('/v3/credentials', headers=self.headers)
80 for credential in expected:
81 self.assertIn(credential, r.json['credentials'])
82
83 def test_user_can_filter_their_credentials_by_type_and_user(self):
84 with self.test_client() as c:
85 credential_type = uuid.uuid4().hex
86 create = {
87 'credential': {
88 'blob': uuid.uuid4().hex,
89 'type': credential_type,
90 'user_id': self.user_id
91 }
92 }
93 r = c.post(
94 '/v3/credentials', json=create, headers=self.headers
95 )
96 expected_credential_id = r.json['credential']['id']
97
98 create = {
99 'credential': {
100 'blob': uuid.uuid4().hex,
101 'type': uuid.uuid4().hex,
102 'user_id': self.user_id
103 }
104 }
105 r = c.post(
106 '/v3/credentials', json=create, headers=self.headers
107 )
108
109 path = '/v3/credentials?type=%s' % credential_type
110 r = c.get(path, headers=self.headers)
111 self.assertEqual(
112 expected_credential_id, r.json['credentials'][0]['id']
113 )
114
115 path = '/v3/credentials?user=%s' % self.user_id
116 r = c.get(path, headers=self.headers)
117 self.assertEqual(
118 expected_credential_id, r.json['credentials'][0]['id']
119 )
120
121 def test_user_can_update_their_credential(self):
122 with self.test_client() as c:
123 create = {
124 'credential': {
125 'blob': uuid.uuid4().hex,
126 'type': uuid.uuid4().hex,
127 'user_id': self.user_id
128 }
129 }
130
131 r = c.post('/v3/credentials', json=create, headers=self.headers)
132 credential_id = r.json['credential']['id']
133
134 updated_blob = uuid.uuid4().hex
135 update = {'credential': {'blob': updated_blob}}
136 path = '/v3/credentials/%s' % credential_id
137 r = c.patch(path, json=update, headers=self.headers)
138 self.assertEqual(updated_blob, r.json['credential']['blob'])
139
140 def test_user_can_delete_their_credentials(self):
141 with self.test_client() as c:
142 create = {
143 'credential': {
144 'blob': uuid.uuid4().hex,
145 'type': uuid.uuid4().hex,
146 'user_id': self.user_id
147 }
148 }
149 r = c.post('/v3/credentials', json=create, headers=self.headers)
150 credential_id = r.json['credential']['id']
151
152 path = '/v3/credentials/%s' % credential_id
153 c.delete(path, headers=self.headers)
154
155
156class _ProjectUsersTests(object):
157 """Users who have project role authorization observe the same behavior."""
158
159 def test_user_cannot_get_credentials_for_other_users(self):
160 user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
161 user_password = user['password']
162 user = PROVIDERS.identity_api.create_user(user)
163 project = unit.new_project_ref(
164 domain_id=CONF.identity.default_domain_id
165 )
166 project = PROVIDERS.resource_api.create_project(project['id'], project)
167 PROVIDERS.assignment_api.create_grant(
168 self.bootstrapper.member_role_id, user_id=user['id'],
169 project_id=project['id']
170 )
171 user_auth = self.build_authentication_request(
172 user_id=user['id'], password=user_password,
173 project_id=project['id']
174 )
175
176 with self.test_client() as c:
177 r = c.post('/v3/auth/tokens', json=user_auth)
178 token_id = r.headers['X-Subject-Token']
179 headers = {'X-Auth-Token': token_id}
180
181 create = {
182 'credential': {
183 'blob': uuid.uuid4().hex,
184 'type': uuid.uuid4().hex,
185 'user_id': user['id']
186 }
187 }
188 r = c.post('/v3/credentials', json=create, headers=headers)
189 credential_id = r.json['credential']['id']
190
191 with self.test_client() as c:
192 path = '/v3/credentials/%s' % credential_id
193 c.get(
194 path, headers=self.headers,
195 expected_status_code=http_client.FORBIDDEN
196 )
197
198 def test_user_cannot_get_non_existant_credential_forbidden(self):
199 with self.test_client() as c:
200 c.get(
201 '/v3/credentials/%s' % uuid.uuid4().hex, headers=self.headers,
202 expected_status_code=http_client.FORBIDDEN
203 )
204
205 def test_user_cannot_list_credentials_for_other_users(self):
206 user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
207 user_password = user['password']
208 user = PROVIDERS.identity_api.create_user(user)
209 project = unit.new_project_ref(
210 domain_id=CONF.identity.default_domain_id
211 )
212 project = PROVIDERS.resource_api.create_project(project['id'], project)
213 PROVIDERS.assignment_api.create_grant(
214 self.bootstrapper.member_role_id, user_id=user['id'],
215 project_id=project['id']
216 )
217 user_auth = self.build_authentication_request(
218 user_id=user['id'], password=user_password,
219 project_id=project['id']
220 )
221
222 with self.test_client() as c:
223 r = c.post('/v3/auth/tokens', json=user_auth)
224 token_id = r.headers['X-Subject-Token']
225 headers = {'X-Auth-Token': token_id}
226
227 create = {
228 'credential': {
229 'blob': uuid.uuid4().hex,
230 'type': uuid.uuid4().hex,
231 'user_id': user['id']
232 }
233 }
234 c.post('/v3/credentials', json=create, headers=headers)
235
236 with self.test_client() as c:
237 path = '/v3/credentials?user_id=%s' % user['id']
238 r = c.get(path, headers=self.headers)
239 self.assertEqual([], r.json['credentials'])
240
241 def test_user_cannot_filter_credentials_by_type_for_others(self):
242 user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
243 user_password = user['password']
244 user = PROVIDERS.identity_api.create_user(user)
245 project = unit.new_project_ref(
246 domain_id=CONF.identity.default_domain_id
247 )
248 project = PROVIDERS.resource_api.create_project(project['id'], project)
249 PROVIDERS.assignment_api.create_grant(
250 self.bootstrapper.member_role_id, user_id=user['id'],
251 project_id=project['id']
252 )
253 user_auth = self.build_authentication_request(
254 user_id=user['id'], password=user_password,
255 project_id=project['id']
256 )
257
258 credential_type = uuid.uuid4().hex
259 with self.test_client() as c:
260 r = c.post('/v3/auth/tokens', json=user_auth)
261 token_id = r.headers['X-Subject-Token']
262 headers = {'X-Auth-Token': token_id}
263
264 create = {
265 'credential': {
266 'blob': uuid.uuid4().hex,
267 'type': credential_type,
268 'user_id': user['id']
269 }
270 }
271 c.post('/v3/credentials', json=create, headers=headers)
272
273 with self.test_client() as c:
274 path = '/v3/credentials?type=%s' % credential_type
275 r = c.get(path, headers=self.headers)
276 self.assertEqual(0, len(r.json['credentials']))
277
278 def test_user_cannot_filter_credentials_by_user_for_others(self):
279 user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
280 user_password = user['password']
281 user = PROVIDERS.identity_api.create_user(user)
282 project = unit.new_project_ref(
283 domain_id=CONF.identity.default_domain_id
284 )
285 project = PROVIDERS.resource_api.create_project(project['id'], project)
286 PROVIDERS.assignment_api.create_grant(
287 self.bootstrapper.member_role_id, user_id=user['id'],
288 project_id=project['id']
289 )
290 user_auth = self.build_authentication_request(
291 user_id=user['id'], password=user_password,
292 project_id=project['id']
293 )
294
295 with self.test_client() as c:
296 r = c.post('/v3/auth/tokens', json=user_auth)
297 token_id = r.headers['X-Subject-Token']
298 headers = {'X-Auth-Token': token_id}
299
300 expected_cred_ids = []
301 for _ in range(2):
302 create = {
303 'credential': {
304 'blob': uuid.uuid4().hex,
305 'type': uuid.uuid4().hex,
306 'user_id': user['id']
307 }
308 }
309 r = c.post('/v3/credentials', json=create, headers=headers)
310 expected_cred_ids.append(r.json['credential']['id'])
311
312 with self.test_client() as c:
313 path = '/v3/credentials?user_id=%s' % user['id']
314 r = c.get(path, headers=self.headers)
315 self.assertEqual([], r.json['credentials'])
316
317 def test_user_cannot_update_credentials_for_others(self):
318 user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
319 user_password = user['password']
320 user = PROVIDERS.identity_api.create_user(user)
321 project = unit.new_project_ref(
322 domain_id=CONF.identity.default_domain_id
323 )
324 project = PROVIDERS.resource_api.create_project(project['id'], project)
325 PROVIDERS.assignment_api.create_grant(
326 self.bootstrapper.member_role_id, user_id=user['id'],
327 project_id=project['id']
328 )
329 user_auth = self.build_authentication_request(
330 user_id=user['id'], password=user_password,
331 project_id=project['id']
332 )
333
334 with self.test_client() as c:
335 r = c.post('/v3/auth/tokens', json=user_auth)
336 token_id = r.headers['X-Subject-Token']
337 headers = {'X-Auth-Token': token_id}
338
339 create = {
340 'credential': {
341 'blob': uuid.uuid4().hex,
342 'type': uuid.uuid4().hex,
343 'user_id': user['id']
344 }
345 }
346 r = c.post('/v3/credentials', json=create, headers=headers)
347 credential_id = r.json['credential']['id']
348
349 with self.test_client() as c:
350 update = {'credential': {'blob': uuid.uuid4().hex}}
351 path = '/v3/credentials/%s' % credential_id
352 c.patch(
353 path, json=update, headers=self.headers,
354 expected_status_code=http_client.FORBIDDEN
355 )
356
357 def test_user_cannot_update_non_existant_credential_forbidden(self):
358 with self.test_client() as c:
359 update = {'credential': {'blob': uuid.uuid4().hex}}
360
361 c.patch(
362 '/v3/credentials/%s' % uuid.uuid4().hex, json=update,
363 headers=self.headers,
364 expected_status_code=http_client.FORBIDDEN
365 )
366
367 def test_user_cannot_create_credentials_for_other_users(self):
368 user = PROVIDERS.identity_api.create_user(
369 unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
370 )
371
372 with self.test_client() as c:
373 create = {
374 'credential': {
375 'blob': uuid.uuid4().hex,
376 'type': uuid.uuid4().hex,
377 'user_id': user['id']
378 }
379 }
380 c.post(
381 '/v3/credentials', json=create, headers=self.headers,
382 expected_status_code=http_client.FORBIDDEN
383 )
384
385 def test_user_cannot_delete_credentials_for_others(self):
386 user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
387 user_password = user['password']
388 user = PROVIDERS.identity_api.create_user(user)
389 project = unit.new_project_ref(
390 domain_id=CONF.identity.default_domain_id
391 )
392 project = PROVIDERS.resource_api.create_project(project['id'], project)
393 PROVIDERS.assignment_api.create_grant(
394 self.bootstrapper.member_role_id, user_id=user['id'],
395 project_id=project['id']
396 )
397 user_auth = self.build_authentication_request(
398 user_id=user['id'], password=user_password,
399 project_id=project['id']
400 )
401
402 with self.test_client() as c:
403 r = c.post('/v3/auth/tokens', json=user_auth)
404 token_id = r.headers['X-Subject-Token']
405 headers = {'X-Auth-Token': token_id}
406
407 create = {
408 'credential': {
409 'blob': uuid.uuid4().hex,
410 'type': uuid.uuid4().hex,
411 'user_id': user['id']
412 }
413 }
414 r = c.post('/v3/credentials', json=create, headers=headers)
415 credential_id = r.json['credential']['id']
416
417 with self.test_client() as c:
418 path = '/v3/credentials/%s' % credential_id
419 c.delete(
420 path, headers=self.headers,
421 expected_status_code=http_client.FORBIDDEN
422 )
423
424 def test_user_cannot_delete_non_existant_credential_forbidden(self):
425 with self.test_client() as c:
426 c.delete(
427 '/v3/credentials/%s' % uuid.uuid4().hex, headers=self.headers,
428 expected_status_code=http_client.FORBIDDEN
429 )
430
431
432class _SystemUserCredentialTests(object):
433 """Tests that are common across all system users."""
434
435 def test_user_can_list_credentials_for_other_users(self):
436 user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
437 user_password = user['password']
438 user = PROVIDERS.identity_api.create_user(user)
439 project = unit.new_project_ref(
440 domain_id=CONF.identity.default_domain_id
441 )
442 project = PROVIDERS.resource_api.create_project(project['id'], project)
443 PROVIDERS.assignment_api.create_grant(
444 self.bootstrapper.member_role_id, user_id=user['id'],
445 project_id=project['id']
446 )
447 user_auth = self.build_authentication_request(
448 user_id=user['id'], password=user_password,
449 project_id=project['id']
450 )
451
452 with self.test_client() as c:
453 r = c.post('/v3/auth/tokens', json=user_auth)
454 token_id = r.headers['X-Subject-Token']
455 headers = {'X-Auth-Token': token_id}
456
457 create = {
458 'credential': {
459 'blob': uuid.uuid4().hex,
460 'type': uuid.uuid4().hex,
461 'user_id': user['id']
462 }
463 }
464 r = c.post('/v3/credentials', json=create, headers=headers)
465 credential_id = r.json['credential']['id']
466
467 with self.test_client() as c:
468 r = c.get('/v3/credentials', headers=self.headers)
469 self.assertEqual(1, len(r.json['credentials']))
470 self.assertEqual(credential_id, r.json['credentials'][0]['id'])
471 self.assertEqual(user['id'], r.json['credentials'][0]['user_id'])
472
473 def test_user_cannot_get_non_existant_credential_not_found(self):
474 with self.test_client() as c:
475 c.get(
476 '/v3/credentials/%s' % uuid.uuid4().hex, headers=self.headers,
477 expected_status_code=http_client.NOT_FOUND
478 )
479
480 def test_user_can_filter_credentials_by_type_for_others(self):
481 user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
482 user_password = user['password']
483 user = PROVIDERS.identity_api.create_user(user)
484 project = unit.new_project_ref(
485 domain_id=CONF.identity.default_domain_id
486 )
487 project = PROVIDERS.resource_api.create_project(project['id'], project)
488 PROVIDERS.assignment_api.create_grant(
489 self.bootstrapper.member_role_id, user_id=user['id'],
490 project_id=project['id']
491 )
492 user_auth = self.build_authentication_request(
493 user_id=user['id'], password=user_password,
494 project_id=project['id']
495 )
496
497 credential_type = uuid.uuid4().hex
498 with self.test_client() as c:
499 r = c.post('/v3/auth/tokens', json=user_auth)
500 token_id = r.headers['X-Subject-Token']
501 headers = {'X-Auth-Token': token_id}
502
503 create = {
504 'credential': {
505 'blob': uuid.uuid4().hex,
506 'type': credential_type,
507 'user_id': user['id']
508 }
509 }
510 r = c.post('/v3/credentials', json=create, headers=headers)
511 credential_id = r.json['credential']['id']
512
513 create = {
514 'credential': {
515 'blob': uuid.uuid4().hex,
516 'type': uuid.uuid4().hex,
517 'user_id': user['id']
518 }
519 }
520 c.post('/v3/credentials', json=create, headers=headers)
521
522 with self.test_client() as c:
523 path = '/v3/credentials?type=%s' % credential_type
524 r = c.get(path, headers=self.headers)
525 self.assertEqual(1, len(r.json['credentials']))
526 self.assertEqual(credential_id, r.json['credentials'][0]['id'])
527 self.assertEqual(user['id'], r.json['credentials'][0]['user_id'])
528
529 def test_user_can_filter_credentials_by_user_for_others(self):
530 user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
531 user_password = user['password']
532 user = PROVIDERS.identity_api.create_user(user)
533 project = unit.new_project_ref(
534 domain_id=CONF.identity.default_domain_id
535 )
536 project = PROVIDERS.resource_api.create_project(project['id'], project)
537 PROVIDERS.assignment_api.create_grant(
538 self.bootstrapper.member_role_id, user_id=user['id'],
539 project_id=project['id']
540 )
541 user_auth = self.build_authentication_request(
542 user_id=user['id'], password=user_password,
543 project_id=project['id']
544 )
545
546 with self.test_client() as c:
547 r = c.post('/v3/auth/tokens', json=user_auth)
548 token_id = r.headers['X-Subject-Token']
549 headers = {'X-Auth-Token': token_id}
550
551 expected_cred_ids = []
552 for _ in range(2):
553 create = {
554 'credential': {
555 'blob': uuid.uuid4().hex,
556 'type': uuid.uuid4().hex,
557 'user_id': user['id']
558 }
559 }
560 r = c.post('/v3/credentials', json=create, headers=headers)
561 expected_cred_ids.append(r.json['credential']['id'])
562
563 with self.test_client() as c:
564 path = '/v3/credentials?user_id=%s' % user['id']
565 r = c.get(path, headers=self.headers)
566 self.assertEqual(2, len(r.json['credentials']))
567 for credential in r.json['credentials']:
568 self.assertIn(credential['id'], expected_cred_ids)
569 self.assertEqual(user['id'], credential['user_id'])
570
571
572class SystemReaderTests(base_classes.TestCaseWithBootstrap,
573 common_auth.AuthTestMixin,
574 _UserCredentialTests,
575 _SystemUserCredentialTests):
576
577 def setUp(self):
578 super(SystemReaderTests, self).setUp()
579 self.loadapp()
580 self.useFixture(ksfixtures.Policy(self.config_fixture))
581 self.config_fixture.config(group='oslo_policy', enforce_scope=True)
582
583 system_reader = unit.new_user_ref(
584 domain_id=CONF.identity.default_domain_id
585 )
586 self.user_id = PROVIDERS.identity_api.create_user(
587 system_reader
588 )['id']
589 PROVIDERS.assignment_api.create_system_grant_for_user(
590 self.user_id, self.bootstrapper.reader_role_id
591 )
592
593 auth = self.build_authentication_request(
594 user_id=self.user_id, password=system_reader['password'],
595 system=True
596 )
597
598 # Grab a token using the persona we're testing and prepare headers
599 # for requests we'll be making in the tests.
600 with self.test_client() as c:
601 r = c.post('/v3/auth/tokens', json=auth)
602 self.token_id = r.headers['X-Subject-Token']
603 self.headers = {'X-Auth-Token': self.token_id}
604
605 def test_user_cannot_create_credentials_for_other_users(self):
606 user = PROVIDERS.identity_api.create_user(
607 unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
608 )
609
610 with self.test_client() as c:
611 create = {
612 'credential': {
613 'blob': uuid.uuid4().hex,
614 'type': uuid.uuid4().hex,
615 'user_id': user['id']
616 }
617 }
618 c.post(
619 '/v3/credentials', json=create, headers=self.headers,
620 expected_status_code=http_client.FORBIDDEN
621 )
622
623 def test_user_cannot_update_credentials_for_others(self):
624 user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
625 user_password = user['password']
626 user = PROVIDERS.identity_api.create_user(user)
627 project = unit.new_project_ref(
628 domain_id=CONF.identity.default_domain_id
629 )
630 project = PROVIDERS.resource_api.create_project(project['id'], project)
631 PROVIDERS.assignment_api.create_grant(
632 self.bootstrapper.member_role_id, user_id=user['id'],
633 project_id=project['id']
634 )
635 user_auth = self.build_authentication_request(
636 user_id=user['id'], password=user_password,
637 project_id=project['id']
638 )
639
640 with self.test_client() as c:
641 r = c.post('/v3/auth/tokens', json=user_auth)
642 token_id = r.headers['X-Subject-Token']
643 headers = {'X-Auth-Token': token_id}
644
645 create = {
646 'credential': {
647 'blob': uuid.uuid4().hex,
648 'type': uuid.uuid4().hex,
649 'user_id': user['id']
650 }
651 }
652 r = c.post('/v3/credentials', json=create, headers=headers)
653 credential_id = r.json['credential']['id']
654
655 with self.test_client() as c:
656 update = {'credential': {'blob': uuid.uuid4().hex}}
657 path = '/v3/credentials/%s' % credential_id
658 c.patch(
659 path, json=update, headers=self.headers,
660 expected_status_code=http_client.FORBIDDEN
661 )
662
663 def test_user_cannot_update_non_existant_credential_forbidden(self):
664 with self.test_client() as c:
665 update = {'credential': {'blob': uuid.uuid4().hex}}
666
667 c.patch(
668 '/v3/credentials/%s' % uuid.uuid4().hex, json=update,
669 headers=self.headers,
670 expected_status_code=http_client.FORBIDDEN
671 )
672
673 def test_user_cannot_delete_credentials_for_others(self):
674 user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
675 user_password = user['password']
676 user = PROVIDERS.identity_api.create_user(user)
677 project = unit.new_project_ref(
678 domain_id=CONF.identity.default_domain_id
679 )
680 project = PROVIDERS.resource_api.create_project(project['id'], project)
681 PROVIDERS.assignment_api.create_grant(
682 self.bootstrapper.member_role_id, user_id=user['id'],
683 project_id=project['id']
684 )
685 user_auth = self.build_authentication_request(
686 user_id=user['id'], password=user_password,
687 project_id=project['id']
688 )
689
690 with self.test_client() as c:
691 r = c.post('/v3/auth/tokens', json=user_auth)
692 token_id = r.headers['X-Subject-Token']
693 headers = {'X-Auth-Token': token_id}
694
695 create = {
696 'credential': {
697 'blob': uuid.uuid4().hex,
698 'type': uuid.uuid4().hex,
699 'user_id': user['id']
700 }
701 }
702 r = c.post('/v3/credentials', json=create, headers=headers)
703 credential_id = r.json['credential']['id']
704
705 with self.test_client() as c:
706 path = '/v3/credentials/%s' % credential_id
707 c.delete(
708 path, headers=self.headers,
709 expected_status_code=http_client.FORBIDDEN
710 )
711
712 def test_user_cannot_delete_non_existant_credential_forbidden(self):
713 with self.test_client() as c:
714 c.delete(
715 '/v3/credentials/%s' % uuid.uuid4().hex, headers=self.headers,
716 expected_status_code=http_client.FORBIDDEN
717 )
718
719
720class SystemMemberTests(base_classes.TestCaseWithBootstrap,
721 common_auth.AuthTestMixin,
722 _UserCredentialTests,
723 _SystemUserCredentialTests):
724
725 def setUp(self):
726 super(SystemMemberTests, self).setUp()
727 self.loadapp()
728 self.useFixture(ksfixtures.Policy(self.config_fixture))
729 self.config_fixture.config(group='oslo_policy', enforce_scope=True)
730
731 system_member = unit.new_user_ref(
732 domain_id=CONF.identity.default_domain_id
733 )
734 self.user_id = PROVIDERS.identity_api.create_user(
735 system_member
736 )['id']
737 PROVIDERS.assignment_api.create_system_grant_for_user(
738 self.user_id, self.bootstrapper.member_role_id
739 )
740
741 auth = self.build_authentication_request(
742 user_id=self.user_id, password=system_member['password'],
743 system=True
744 )
745
746 # Grab a token using the persona we're testing and prepare headers
747 # for requests we'll be making in the tests.
748 with self.test_client() as c:
749 r = c.post('/v3/auth/tokens', json=auth)
750 self.token_id = r.headers['X-Subject-Token']
751 self.headers = {'X-Auth-Token': self.token_id}
752
753 def test_user_cannot_create_credentials_for_other_users(self):
754 user = PROVIDERS.identity_api.create_user(
755 unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
756 )
757
758 with self.test_client() as c:
759 create = {
760 'credential': {
761 'blob': uuid.uuid4().hex,
762 'type': uuid.uuid4().hex,
763 'user_id': user['id']
764 }
765 }
766 c.post(
767 '/v3/credentials', json=create, headers=self.headers,
768 expected_status_code=http_client.FORBIDDEN
769 )
770
771 def test_user_can_update_credentials_for_others(self):
772 user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
773 user_password = user['password']
774 user = PROVIDERS.identity_api.create_user(user)
775 project = unit.new_project_ref(
776 domain_id=CONF.identity.default_domain_id
777 )
778 project = PROVIDERS.resource_api.create_project(project['id'], project)
779 PROVIDERS.assignment_api.create_grant(
780 self.bootstrapper.member_role_id, user_id=user['id'],
781 project_id=project['id']
782 )
783 user_auth = self.build_authentication_request(
784 user_id=user['id'], password=user_password,
785 project_id=project['id']
786 )
787
788 with self.test_client() as c:
789 r = c.post('/v3/auth/tokens', json=user_auth)
790 token_id = r.headers['X-Subject-Token']
791 headers = {'X-Auth-Token': token_id}
792
793 create = {
794 'credential': {
795 'blob': uuid.uuid4().hex,
796 'type': uuid.uuid4().hex,
797 'user_id': user['id']
798 }
799 }
800 r = c.post('/v3/credentials', json=create, headers=headers)
801 credential_id = r.json['credential']['id']
802
803 with self.test_client() as c:
804 update = {'credential': {'blob': uuid.uuid4().hex}}
805 path = '/v3/credentials/%s' % credential_id
806 c.patch(path, json=update, headers=self.headers)
807
808 def test_user_cannot_update_non_existant_credential_not_found(self):
809 with self.test_client() as c:
810 update = {'credential': {'blob': uuid.uuid4().hex}}
811
812 c.patch(
813 '/v3/credentials/%s' % uuid.uuid4().hex, json=update,
814 headers=self.headers,
815 expected_status_code=http_client.NOT_FOUND
816 )
817
818 def test_user_cannot_delete_credentials_for_others(self):
819 user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
820 user_password = user['password']
821 user = PROVIDERS.identity_api.create_user(user)
822 project = unit.new_project_ref(
823 domain_id=CONF.identity.default_domain_id
824 )
825 project = PROVIDERS.resource_api.create_project(project['id'], project)
826 PROVIDERS.assignment_api.create_grant(
827 self.bootstrapper.member_role_id, user_id=user['id'],
828 project_id=project['id']
829 )
830 user_auth = self.build_authentication_request(
831 user_id=user['id'], password=user_password,
832 project_id=project['id']
833 )
834
835 with self.test_client() as c:
836 r = c.post('/v3/auth/tokens', json=user_auth)
837 token_id = r.headers['X-Subject-Token']
838 headers = {'X-Auth-Token': token_id}
839
840 create = {
841 'credential': {
842 'blob': uuid.uuid4().hex,
843 'type': uuid.uuid4().hex,
844 'user_id': user['id']
845 }
846 }
847 r = c.post('/v3/credentials', json=create, headers=headers)
848 credential_id = r.json['credential']['id']
849
850 with self.test_client() as c:
851 path = '/v3/credentials/%s' % credential_id
852 c.delete(
853 path, headers=self.headers,
854 expected_status_code=http_client.FORBIDDEN
855 )
856
857 def test_user_cannot_delete_non_existant_credential_forbidden(self):
858 with self.test_client() as c:
859 c.delete(
860 '/v3/credentials/%s' % uuid.uuid4().hex, headers=self.headers,
861 expected_status_code=http_client.FORBIDDEN
862 )
863
864
865class SystemAdminTests(base_classes.TestCaseWithBootstrap,
866 common_auth.AuthTestMixin,
867 _UserCredentialTests,
868 _SystemUserCredentialTests):
869
870 def setUp(self):
871 super(SystemAdminTests, self).setUp()
872 self.loadapp()
873 self.useFixture(ksfixtures.Policy(self.config_fixture))
874 self.config_fixture.config(group='oslo_policy', enforce_scope=True)
875
876 # Reuse the system administrator account created during
877 # ``keystone-manage bootstrap``
878 self.user_id = self.bootstrapper.admin_user_id
879 auth = self.build_authentication_request(
880 user_id=self.user_id,
881 password=self.bootstrapper.admin_password,
882 system=True
883 )
884
885 # Grab a token using the persona we're testing and prepare headers
886 # for requests we'll be making in the tests.
887 with self.test_client() as c:
888 r = c.post('/v3/auth/tokens', json=auth)
889 self.token_id = r.headers['X-Subject-Token']
890 self.headers = {'X-Auth-Token': self.token_id}
891
892 def test_user_can_create_credentials_for_other_users(self):
893 user = PROVIDERS.identity_api.create_user(
894 unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
895 )
896
897 with self.test_client() as c:
898 create = {
899 'credential': {
900 'blob': uuid.uuid4().hex,
901 'type': uuid.uuid4().hex,
902 'user_id': user['id']
903 }
904 }
905 c.post('/v3/credentials', json=create, headers=self.headers)
906
907 def test_user_can_update_credentials_for_others(self):
908 user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
909 user_password = user['password']
910 user = PROVIDERS.identity_api.create_user(user)
911 project = unit.new_project_ref(
912 domain_id=CONF.identity.default_domain_id
913 )
914 project = PROVIDERS.resource_api.create_project(project['id'], project)
915 PROVIDERS.assignment_api.create_grant(
916 self.bootstrapper.member_role_id, user_id=user['id'],
917 project_id=project['id']
918 )
919 user_auth = self.build_authentication_request(
920 user_id=user['id'], password=user_password,
921 project_id=project['id']
922 )
923
924 with self.test_client() as c:
925 r = c.post('/v3/auth/tokens', json=user_auth)
926 token_id = r.headers['X-Subject-Token']
927 headers = {'X-Auth-Token': token_id}
928
929 create = {
930 'credential': {
931 'blob': uuid.uuid4().hex,
932 'type': uuid.uuid4().hex,
933 'user_id': user['id']
934 }
935 }
936 r = c.post('/v3/credentials', json=create, headers=headers)
937 credential_id = r.json['credential']['id']
938
939 with self.test_client() as c:
940 path = '/v3/credentials/%s' % credential_id
941 updated_blob = uuid.uuid4().hex
942 update = {'credential': {'blob': updated_blob}}
943 r = c.patch(path, json=update, headers=self.headers)
944 self.assertEqual(updated_blob, r.json['credential']['blob'])
945 self.assertEqual(user['id'], r.json['credential']['user_id'])
946
947 def test_user_cannot_update_non_existant_credential_not_found(self):
948 with self.test_client() as c:
949 update = {'credential': {'blob': uuid.uuid4().hex}}
950
951 c.patch(
952 '/v3/credentials/%s' % uuid.uuid4().hex, json=update,
953 headers=self.headers,
954 expected_status_code=http_client.NOT_FOUND
955 )
956
957 def test_user_can_delete_credentials_for_others(self):
958 user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
959 user_password = user['password']
960 user = PROVIDERS.identity_api.create_user(user)
961 project = unit.new_project_ref(
962 domain_id=CONF.identity.default_domain_id
963 )
964 project = PROVIDERS.resource_api.create_project(project['id'], project)
965 PROVIDERS.assignment_api.create_grant(
966 self.bootstrapper.member_role_id, user_id=user['id'],
967 project_id=project['id']
968 )
969 user_auth = self.build_authentication_request(
970 user_id=user['id'], password=user_password,
971 project_id=project['id']
972 )
973
974 with self.test_client() as c:
975 r = c.post('/v3/auth/tokens', json=user_auth)
976 token_id = r.headers['X-Subject-Token']
977 headers = {'X-Auth-Token': token_id}
978
979 create = {
980 'credential': {
981 'blob': uuid.uuid4().hex,
982 'type': uuid.uuid4().hex,
983 'user_id': user['id']
984 }
985 }
986 r = c.post('/v3/credentials', json=create, headers=headers)
987 credential_id = r.json['credential']['id']
988
989 with self.test_client() as c:
990 path = '/v3/credentials/%s' % credential_id
991 c.delete(path, headers=self.headers)
992
993 def test_user_cannot_delete_non_existant_credential_not_found(self):
994 with self.test_client() as c:
995 c.delete(
996 '/v3/credentials/%s' % uuid.uuid4().hex, headers=self.headers,
997 expected_status_code=http_client.NOT_FOUND
998 )
999
1000
1001class ProjectReaderTests(base_classes.TestCaseWithBootstrap,
1002 common_auth.AuthTestMixin,
1003 _UserCredentialTests,
1004 _ProjectUsersTests):
1005
1006 def setUp(self):
1007 super(ProjectReaderTests, self).setUp()
1008 self.loadapp()
1009 self.useFixture(ksfixtures.Policy(self.config_fixture))
1010 self.config_fixture.config(group='oslo_policy', enforce_scope=True)
1011
1012 project_reader = unit.new_user_ref(
1013 domain_id=CONF.identity.default_domain_id
1014 )
1015 self.user_id = PROVIDERS.identity_api.create_user(
1016 project_reader
1017 )['id']
1018 project = unit.new_project_ref(
1019 domain_id=CONF.identity.default_domain_id
1020 )
1021 self.project_id = PROVIDERS.resource_api.create_project(
1022 project['id'], project
1023 )['id']
1024 PROVIDERS.assignment_api.create_grant(
1025 self.bootstrapper.reader_role_id, user_id=self.user_id,
1026 project_id=self.project_id
1027 )
1028
1029 auth = self.build_authentication_request(
1030 user_id=self.user_id,
1031 password=project_reader['password'],
1032 project_id=self.project_id
1033 )
1034
1035 # Grab a token using the persona we're testing and prepare headers
1036 # for requests we'll be making in the tests.
1037 with self.test_client() as c:
1038 r = c.post('/v3/auth/tokens', json=auth)
1039 self.token_id = r.headers['X-Subject-Token']
1040 self.headers = {'X-Auth-Token': self.token_id}
1041
1042
1043class ProjectMemberTests(base_classes.TestCaseWithBootstrap,
1044 common_auth.AuthTestMixin,
1045 _UserCredentialTests,
1046 _ProjectUsersTests):
1047
1048 def setUp(self):
1049 super(ProjectMemberTests, self).setUp()
1050 self.loadapp()
1051 self.useFixture(ksfixtures.Policy(self.config_fixture))
1052 self.config_fixture.config(group='oslo_policy', enforce_scope=True)
1053
1054 project_member = unit.new_user_ref(
1055 domain_id=CONF.identity.default_domain_id
1056 )
1057 self.user_id = PROVIDERS.identity_api.create_user(
1058 project_member
1059 )['id']
1060 project = unit.new_project_ref(
1061 domain_id=CONF.identity.default_domain_id
1062 )
1063 self.project_id = PROVIDERS.resource_api.create_project(
1064 project['id'], project
1065 )['id']
1066 PROVIDERS.assignment_api.create_grant(
1067 self.bootstrapper.member_role_id, user_id=self.user_id,
1068 project_id=self.project_id
1069 )
1070
1071 auth = self.build_authentication_request(
1072 user_id=self.user_id,
1073 password=project_member['password'],
1074 project_id=self.project_id
1075 )
1076
1077 # Grab a token using the persona we're testing and prepare headers
1078 # for requests we'll be making in the tests.
1079 with self.test_client() as c:
1080 r = c.post('/v3/auth/tokens', json=auth)
1081 self.token_id = r.headers['X-Subject-Token']
1082 self.headers = {'X-Auth-Token': self.token_id}
1083
1084
1085class ProjectAdminTests(base_classes.TestCaseWithBootstrap,
1086 common_auth.AuthTestMixin,
1087 _UserCredentialTests,
1088 _ProjectUsersTests):
1089
1090 def setUp(self):
1091 super(ProjectAdminTests, self).setUp()
1092 self.loadapp()
1093
1094 self.policy_file = self.useFixture(temporaryfile.SecureTempFile())
1095 self.policy_file_name = self.policy_file.file_name
1096 self.useFixture(
1097 ksfixtures.Policy(
1098 self.config_fixture, policy_file=self.policy_file_name
1099 )
1100 )
1101 self._override_policy()
1102 self.config_fixture.config(group='oslo_policy', enforce_scope=True)
1103
1104 # Reuse the system administrator account created during
1105 # ``keystone-manage bootstrap``
1106 self.user_id = self.bootstrapper.admin_user_id
1107 auth = self.build_authentication_request(
1108 user_id=self.user_id,
1109 password=self.bootstrapper.admin_password,
1110 project_id=self.bootstrapper.project_id
1111 )
1112
1113 # Grab a token using the persona we're testing and prepare headers
1114 # for requests we'll be making in the tests.
1115 with self.test_client() as c:
1116 r = c.post('/v3/auth/tokens', json=auth)
1117 self.token_id = r.headers['X-Subject-Token']
1118 self.headers = {'X-Auth-Token': self.token_id}
1119
1120 def _override_policy(self):
1121 # TODO(lbragstad): Remove this once the deprecated policies in
1122 # keystone.common.policies.credentials have been removed. This is only
1123 # here to make sure we test the new policies instead of the deprecated
1124 # ones. Oslo.policy will OR deprecated policies with new policies to
1125 # maintain compatibility and give operators a chance to update
1126 # permissions or update policies without breaking users. This will
1127 # cause these specific tests to fail since we're trying to correct this
1128 # broken behavior with better scope checking.
1129 with open(self.policy_file_name, 'w') as f:
1130 overridden_policies = {
1131 'identity:get_credential': cp.SYSTEM_READER_OR_CRED_OWNER,
1132 'identity:list_credentials': cp.SYSTEM_READER_OR_CRED_OWNER,
1133 'identity:create_credential': cp.SYSTEM_ADMIN_OR_CRED_OWNER,
1134 'identity:update_credential': cp.SYSTEM_MEMBER_OR_CRED_OWNER,
1135 'identity:delete_credential': cp.SYSTEM_ADMIN_OR_CRED_OWNER
1136 }
1137 f.write(jsonutils.dumps(overridden_policies))
diff --git a/keystone/tests/unit/test_v3_credential.py b/keystone/tests/unit/test_v3_credential.py
index 1907c20..b5e0824 100644
--- a/keystone/tests/unit/test_v3_credential.py
+++ b/keystone/tests/unit/test_v3_credential.py
@@ -113,6 +113,11 @@ class CredentialTestCase(CredentialBaseTestCase):
113 113
114 def test_list_credentials_filtered_by_type(self): 114 def test_list_credentials_filtered_by_type(self):
115 """Call ``GET /credentials?type={type}``.""" 115 """Call ``GET /credentials?type={type}``."""
116 PROVIDERS.assignment_api.create_system_grant_for_user(
117 self.user_id, self.role_id
118 )
119 token = self.get_system_scoped_token()
120
116 # The type ec2 was chosen, instead of a random string, 121 # The type ec2 was chosen, instead of a random string,
117 # because the type must be in the list of supported types 122 # because the type must be in the list of supported types
118 ec2_credential = unit.new_credential_ref(user_id=uuid.uuid4().hex, 123 ec2_credential = unit.new_credential_ref(user_id=uuid.uuid4().hex,
@@ -123,14 +128,14 @@ class CredentialTestCase(CredentialBaseTestCase):
123 ec2_credential['id'], ec2_credential) 128 ec2_credential['id'], ec2_credential)
124 129
125 # The type cert was chosen for the same reason as ec2 130 # The type cert was chosen for the same reason as ec2
126 r = self.get('/credentials?type=cert') 131 r = self.get('/credentials?type=cert', token=token)
127 132
128 # Testing the filter for two different types 133 # Testing the filter for two different types
129 self.assertValidCredentialListResponse(r, ref=self.credential) 134 self.assertValidCredentialListResponse(r, ref=self.credential)
130 for cred in r.result['credentials']: 135 for cred in r.result['credentials']:
131 self.assertEqual('cert', cred['type']) 136 self.assertEqual('cert', cred['type'])
132 137
133 r_ec2 = self.get('/credentials?type=ec2') 138 r_ec2 = self.get('/credentials?type=ec2', token=token)
134 self.assertThat(r_ec2.result['credentials'], matchers.HasLength(1)) 139 self.assertThat(r_ec2.result['credentials'], matchers.HasLength(1))
135 cred_ec2 = r_ec2.result['credentials'][0] 140 cred_ec2 = r_ec2.result['credentials'][0]
136 141
@@ -143,6 +148,11 @@ class CredentialTestCase(CredentialBaseTestCase):
143 user1_id = uuid.uuid4().hex 148 user1_id = uuid.uuid4().hex
144 user2_id = uuid.uuid4().hex 149 user2_id = uuid.uuid4().hex
145 150
151 PROVIDERS.assignment_api.create_system_grant_for_user(
152 self.user_id, self.role_id
153 )
154 token = self.get_system_scoped_token()
155
146 # Creating credentials for two different users 156 # Creating credentials for two different users
147 credential_user1_ec2 = unit.new_credential_ref(user_id=user1_id, 157 credential_user1_ec2 = unit.new_credential_ref(user_id=user1_id,
148 type=CRED_TYPE_EC2) 158 type=CRED_TYPE_EC2)
@@ -156,7 +166,9 @@ class CredentialTestCase(CredentialBaseTestCase):
156 PROVIDERS.credential_api.create_credential( 166 PROVIDERS.credential_api.create_credential(
157 credential_user2_cert['id'], credential_user2_cert) 167 credential_user2_cert['id'], credential_user2_cert)
158 168
159 r = self.get('/credentials?user_id=%s&type=ec2' % user1_id) 169 r = self.get(
170 '/credentials?user_id=%s&type=ec2' % user1_id, token=token
171 )
160 self.assertValidCredentialListResponse(r, ref=credential_user1_ec2) 172 self.assertValidCredentialListResponse(r, ref=credential_user1_ec2)
161 self.assertThat(r.result['credentials'], matchers.HasLength(1)) 173 self.assertThat(r.result['credentials'], matchers.HasLength(1))
162 cred = r.result['credentials'][0] 174 cred = r.result['credentials'][0]
diff --git a/releasenotes/notes/bug-1788415-3190279e9c900f76.yaml b/releasenotes/notes/bug-1788415-3190279e9c900f76.yaml
new file mode 100644
index 0000000..9f9f2a9
--- /dev/null
+++ b/releasenotes/notes/bug-1788415-3190279e9c900f76.yaml
@@ -0,0 +1,25 @@
1---
2upgrade:
3 - |
4 [`bug 1788415 <https://bugs.launchpad.net/keystone/+bug/1788415>`_]
5 [`bug 968696 <https://bugs.launchpad.net/keystone/+bug/968696>`_]
6 Policies protecting the ``/v3/credentials`` API have changed defaults in
7 order to make the credentials API more accessible for all users and not
8 just operators or system administrator. Please consider these updates when
9 using this version of keystone since it could affect API behavior in your
10 deployment, especially if you're using a customized policy file.
11security:
12 - |
13 [`bug 1788415 <https://bugs.launchpad.net/keystone/+bug/1788415>`_]
14 [`bug 968696 <https://bugs.launchpad.net/keystone/+bug/968696>`_]
15 More granular policy checks have been applied to the credential API in
16 order to make it more self-service for users. By default, end users will
17 now have the ability to manage their credentials.
18fixes:
19 - |
20 [`bug 1788415 <https://bugs.launchpad.net/keystone/+bug/1788415>`_]
21 [`bug 968696 <https://bugs.launchpad.net/keystone/+bug/968696>`_]
22 Improved self-service support has been implemented in the credential API.
23 This means that end users have the ability to manage their own credentials
24 as opposed to filing tickets to have deployment administrators manage
25 credentials for users.