summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPranali Deore <pdeore@redhat.com>2021-07-23 14:47:22 +0000
committerAbhishek Kekane <akekane@redhat.com>2021-09-03 05:37:05 +0000
commit4473a6053644eec84552780930efe544fd503774 (patch)
tree79b25d79e769f004f2b69663e2e5f089556a9c9a
parent9796593f31a4f0bbc62ae6e0541385b573b9ce12 (diff)
downloadglance-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.py98
-rw-r--r--glance/tests/functional/v2/test_metadef_tags.py186
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)