Skip to content

Commit

Permalink
Implement project personas for metadef tags
Browse files Browse the repository at this point in the history
This commit updates the policies for metadef tags
to use default roles available from keystone.

Change-Id: I2f97c64a72e081ae2f9e558b6fa53dbfd5af2026
  • Loading branch information
PranaliDeore authored and konan-abhi committed Sep 3, 2021
1 parent 9796593 commit 4473a60
Show file tree
Hide file tree
Showing 2 changed files with 267 additions and 17 deletions.
98 changes: 84 additions & 14 deletions glance/policies/metadef.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,20 +269,90 @@
],
),

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'}
],
)
]


Expand Down
186 changes: 183 additions & 3 deletions glance/tests/functional/v2/test_metadef_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -176,3 +176,183 @@ def test_metadata_tags_lifecycle(self):
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)

0 comments on commit 4473a60

Please sign in to comment.