diff options
author | Pranali Deore <pdeore@redhat.com> | 2021-07-23 14:47:22 +0000 |
---|---|---|
committer | Abhishek Kekane <akekane@redhat.com> | 2021-09-03 05:37:05 +0000 |
commit | 4473a6053644eec84552780930efe544fd503774 (patch) | |
tree | 79b25d79e769f004f2b69663e2e5f089556a9c9a | |
parent | 9796593f31a4f0bbc62ae6e0541385b573b9ce12 (diff) | |
download | glance-4473a6053644eec84552780930efe544fd503774.tar.gz |
Implement project personas for metadef tags
This commit updates the policies for metadef tags
to use default roles available from keystone.
Change-Id: I2f97c64a72e081ae2f9e558b6fa53dbfd5af2026
-rw-r--r-- | glance/policies/metadef.py | 98 | ||||
-rw-r--r-- | glance/tests/functional/v2/test_metadef_tags.py | 186 |
2 files changed, 267 insertions, 17 deletions
diff --git a/glance/policies/metadef.py b/glance/policies/metadef.py index 92c1749ba..862e12e23 100644 --- a/glance/policies/metadef.py +++ b/glance/policies/metadef.py @@ -269,20 +269,90 @@ metadef_policies = [ ], ), - policy.RuleDefault(name="get_metadef_tag", - check_str="rule:metadef_default"), - policy.RuleDefault(name="get_metadef_tags", - check_str="rule:metadef_default"), - policy.RuleDefault(name="modify_metadef_tag", - check_str="rule:metadef_admin"), - policy.RuleDefault(name="add_metadef_tag", - check_str="rule:metadef_admin"), - policy.RuleDefault(name="add_metadef_tags", - check_str="rule:metadef_admin"), - policy.RuleDefault(name="delete_metadef_tag", - check_str="rule:metadef_admin"), - policy.RuleDefault(name="delete_metadef_tags", - check_str="rule:metadef_admin"), + policy.DocumentedRuleDefault( + name="get_metadef_tag", + check_str=base.ADMIN_OR_PROJECT_READER_GET_NAMESPACE, + scope_types=['system', 'project'], + description="Get tag definition.", + operations=[ + {'path': '/v2/metadefs/namespaces/{namespace_name}/tags' + '/{tag_name}', + 'method': 'GET'} + ], + deprecated_rule=policy.DeprecatedRule( + name="get_metadef_tag", check_str="rule:metadef_default", + deprecated_reason=DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.XENA + ), + ), + policy.DocumentedRuleDefault( + name="get_metadef_tags", + check_str=base.ADMIN_OR_PROJECT_READER_GET_NAMESPACE, + scope_types=['system', 'project'], + description="List tag definitions.", + operations=[ + {'path': '/v2/metadefs/namespaces/{namespace_name}/tags', + 'method': 'GET'} + ], + deprecated_rule=policy.DeprecatedRule( + name="get_metadef_tags", check_str="rule:metadef_default", + deprecated_reason=DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.XENA + ), + ), + policy.DocumentedRuleDefault( + name="modify_metadef_tag", + check_str="rule:metadef_admin", + scope_types=['system', 'project'], + description="Update tag definition.", + operations=[ + {'path': '/v2/metadefs/namespaces/{namespace_name}/tags' + '/{tag_name}', + 'method': 'PUT'} + ], + ), + policy.DocumentedRuleDefault( + name="add_metadef_tag", + check_str="rule:metadef_admin", + scope_types=['system', 'project'], + description="Add tag definition.", + operations=[ + {'path': '/v2/metadefs/namespaces/{namespace_name}/tags' + '/{tag_name}', + 'method': 'POST'} + ], + ), + policy.DocumentedRuleDefault( + name="add_metadef_tags", + check_str="rule:metadef_admin", + scope_types=['system', 'project'], + description="Create tag definitions.", + operations=[ + {'path': '/v2/metadefs/namespaces/{namespace_name}/tags', + 'method': 'POST'} + ], + ), + policy.DocumentedRuleDefault( + name="delete_metadef_tag", + check_str="rule:metadef_admin", + scope_types=['system', 'project'], + description="Delete tag definition.", + operations=[ + {'path': '/v2/metadefs/namespaces/{namespace_name}/tags' + '/{tag_name}', + 'method': 'DELETE'} + ], + ), + policy.DocumentedRuleDefault( + name="delete_metadef_tags", + check_str="rule:metadef_admin", + scope_types=['system', 'project'], + description="Delete tag definitions.", + operations=[ + {'path': '/v2/metadefs/namespaces/{namespace_name}/tags', + 'method': 'DELETE'} + ], + ) ] diff --git a/glance/tests/functional/v2/test_metadef_tags.py b/glance/tests/functional/v2/test_metadef_tags.py index 520a1ce13..cac6b639d 100644 --- a/glance/tests/functional/v2/test_metadef_tags.py +++ b/glance/tests/functional/v2/test_metadef_tags.py @@ -13,15 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -import uuid - from oslo_serialization import jsonutils +from oslo_utils.fixture import uuidsentinel as uuids import requests from six.moves import http_client as http from glance.tests import functional -TENANT1 = str(uuid.uuid4()) +TENANT1 = uuids.owner1 +TENANT2 = uuids.owner2 class TestMetadefTags(functional.FunctionalTest): @@ -176,3 +176,183 @@ class TestMetadefTags(functional.FunctionalTest): self.assertEqual(http.OK, response.status_code) tags = jsonutils.loads(response.text)['tags'] self.assertEqual(3, len(tags)) + + def _create_namespace(self, path, headers, data): + response = requests.post(path, headers=headers, json=data) + self.assertEqual(http.CREATED, response.status_code) + + return response.json() + + def _create_tags(self, namespaces): + tags = [] + for namespace in namespaces: + headers = self._headers({'X-Tenant-Id': namespace['owner']}) + tag_name = "tag_of_%s" % (namespace['namespace']) + path = self._url('/v2/metadefs/namespaces/%s/tags/%s' % + (namespace['namespace'], tag_name)) + response = requests.post(path, headers=headers) + self.assertEqual(http.CREATED, response.status_code) + tag_metadata = response.json() + metadef_tags = dict() + metadef_tags[namespace['namespace']] = tag_metadata['name'] + tags.append(metadef_tags) + + return tags + + def _update_tags(self, path, headers, data): + # The tag should be mutable + response = requests.put(path, headers=headers, json=data) + self.assertEqual(http.OK, response.status_code, response.text) + # Returned metadata_tag should reflect the changes + metadata_tag = response.json() + self.assertEqual(data['name'], metadata_tag['name']) + + # Updates should persist across requests + response = requests.get(path, headers=self._headers()) + self.assertEqual(http.OK, response.status_code) + self.assertEqual(data['name'], metadata_tag['name']) + + def test_role_base_metadata_tags_lifecycle(self): + # Create public and private namespaces for tenant1 and tenant2 + path = self._url('/v2/metadefs/namespaces') + headers = self._headers({'content-type': 'application/json'}) + tenant1_namespaces = [] + tenant2_namespaces = [] + for tenant in [TENANT1, TENANT2]: + headers['X-Tenant-Id'] = tenant + for visibility in ['public', 'private']: + data = { + "namespace": "%s_%s_namespace" % (tenant, visibility), + "display_name": "My User Friendly Namespace", + "description": "My description", + "visibility": visibility, + "owner": tenant + } + namespace = self._create_namespace(path, headers, data) + if tenant == TENANT1: + tenant1_namespaces.append(namespace) + else: + tenant2_namespaces.append(namespace) + + # Create a metadef tags for each namespace created above + tenant1_tags = self._create_tags(tenant1_namespaces) + tenant2_tags = self._create_tags(tenant2_namespaces) + + def _check_tag_access(tags, tenant): + headers = self._headers({'content-type': 'application/json', + 'X-Tenant-Id': tenant, + 'X-Roles': 'reader,member'}) + for tag in tags: + for namespace, tag_name in tag.items(): + path = self._url('/v2/metadefs/namespaces/%s/tags/%s' % + (namespace, tag_name)) + response = requests.get(path, headers=headers) + if namespace.split('_')[1] == 'public': + expected = http.OK + else: + expected = http.NOT_FOUND + + # Make sure we can see all public and own private tags, + # but not the private tags of other tenant's + self.assertEqual(expected, response.status_code) + + # Make sure the same holds for listing + path = self._url( + '/v2/metadefs/namespaces/%s/tags' % namespace) + response = requests.get(path, headers=headers) + self.assertEqual(expected, response.status_code) + if expected == http.OK: + resp_props = response.json()['tags'] + self.assertEqual( + sorted(tag.values()), + sorted([x['name'] + for x in resp_props])) + + # Check Tenant 1 can access tags of all public namespace + # and cannot access tags of private namespace of Tenant 2 + _check_tag_access(tenant2_tags, TENANT1) + + # Check Tenant 2 can access tags of public namespace and + # cannot access tags of private namespace of Tenant 1 + _check_tag_access(tenant1_tags, TENANT2) + + # Update tags with admin and non admin role + total_tags = tenant1_tags + tenant2_tags + for tag in total_tags: + for namespace, tag_name in tag.items(): + data = { + "name": tag_name} + path = self._url('/v2/metadefs/namespaces/%s/tags/%s' % + (namespace, tag_name)) + + # Update tag should fail with non admin role + headers['X-Roles'] = "reader,member" + response = requests.put(path, headers=headers, json=data) + self.assertEqual(http.FORBIDDEN, response.status_code) + + # Should work with admin role + headers = self._headers({ + 'X-Tenant-Id': namespace.split('_')[0]}) + self._update_tags(path, headers, data) + + # Delete tags should not be allowed to non admin role + for tag in total_tags: + for namespace, tag_name in tag.items(): + path = self._url('/v2/metadefs/namespaces/%s/tags/%s' % + (namespace, tag_name)) + response = requests.delete( + path, headers=self._headers({ + 'X-Roles': 'reader,member', + 'X-Tenant-Id': namespace.split('_')[0] + })) + self.assertEqual(http.FORBIDDEN, response.status_code) + + # Delete all metadef tags + headers = self._headers() + for tag in total_tags: + for namespace, tag_name in tag.items(): + path = self._url('/v2/metadefs/namespaces/%s/tags/%s' % + (namespace, tag_name)) + response = requests.delete(path, headers=headers) + self.assertEqual(http.NO_CONTENT, response.status_code) + + # Deleted tags should not be exist + response = requests.get(path, headers=headers) + self.assertEqual(http.NOT_FOUND, response.status_code) + + # Create multiple tags should not be allowed to non admin role + headers = self._headers({'content-type': 'application/json', + 'X-Roles': 'reader,member'}) + data = { + "tags": [{"name": "tag1"}, {"name": "tag2"}, {"name": "tag3"}] + } + for namespace in tenant1_namespaces: + path = self._url('/v2/metadefs/namespaces/%s/tags' % + (namespace['namespace'])) + response = requests.post(path, headers=headers, json=data) + self.assertEqual(http.FORBIDDEN, response.status_code) + + # Create multiple tags. + headers = self._headers({'content-type': 'application/json'}) + for namespace in tenant1_namespaces: + path = self._url('/v2/metadefs/namespaces/%s/tags' % + (namespace['namespace'])) + response = requests.post(path, headers=headers, json=data) + self.assertEqual(http.CREATED, response.status_code) + + # Delete multiple tags should not be allowed with non admin role + headers = self._headers({'content-type': 'application/json', + 'X-Roles': 'reader,member'}) + for namespace in tenant1_namespaces: + path = self._url('/v2/metadefs/namespaces/%s/tags' % + (namespace['namespace'])) + response = requests.delete(path, headers=headers) + self.assertEqual(http.FORBIDDEN, response.status_code) + + # Delete multiple tags created above created tags + headers = self._headers() + for namespace in tenant1_namespaces: + path = self._url('/v2/metadefs/namespaces/%s/tags' % + (namespace['namespace'])) + response = requests.delete(path, headers=headers) + self.assertEqual(http.NO_CONTENT, response.status_code) |