summaryrefslogtreecommitdiff
path: root/glance
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2021-09-03 02:08:24 +0000
committerGerrit Code Review <review@openstack.org>2021-09-03 02:08:24 +0000
commitcddc3f68f07d371acbe73c8f0ad6b07db1d6c238 (patch)
treed754afea950f6e620310c6a046b7ec16065579ce /glance
parent4b754c1590afdc22ed8bcf03387c4d3496617a37 (diff)
parent035e714f83effde844425db30423117dced50688 (diff)
downloadglance-cddc3f68f07d371acbe73c8f0ad6b07db1d6c238.tar.gz
Merge "Implement project personas for metadef objects"
Diffstat (limited to 'glance')
-rw-r--r--glance/policies/metadef.py73
-rw-r--r--glance/tests/functional/v2/test_metadef_objects.py183
2 files changed, 243 insertions, 13 deletions
diff --git a/glance/policies/metadef.py b/glance/policies/metadef.py
index 97200a730..0bc1498b7 100644
--- a/glance/policies/metadef.py
+++ b/glance/policies/metadef.py
@@ -85,16 +85,69 @@ metadef_policies = [
],
),
- policy.RuleDefault(name="get_metadef_object",
- check_str="rule:metadef_default"),
- policy.RuleDefault(name="get_metadef_objects",
- check_str="rule:metadef_default"),
- policy.RuleDefault(name="modify_metadef_object",
- check_str="rule:metadef_admin"),
- policy.RuleDefault(name="add_metadef_object",
- check_str="rule:metadef_admin"),
- policy.RuleDefault(name="delete_metadef_object",
- check_str="rule:metadef_admin"),
+ policy.DocumentedRuleDefault(
+ name="get_metadef_object",
+ check_str=base.ADMIN_OR_PROJECT_READER_GET_NAMESPACE,
+ scope_types=['system', 'project'],
+ description="Get a specific object from a namespace.",
+ operations=[
+ {'path': '/v2/metadefs/namespaces/{namespace_name}/objects'
+ '/{object_name}',
+ 'method': 'GET'}
+ ],
+ deprecated_rule=policy.DeprecatedRule(
+ name="get_metadef_object", check_str="rule:metadef_default",
+ deprecated_reason=DEPRECATED_REASON,
+ deprecated_since=versionutils.deprecated.XENA
+ ),
+ ),
+ policy.DocumentedRuleDefault(
+ name="get_metadef_objects",
+ check_str=base.ADMIN_OR_PROJECT_READER_GET_NAMESPACE,
+ scope_types=['system', 'project'],
+ description="Get objects from a namespace.",
+ operations=[
+ {'path': '/v2/metadefs/namespaces/{namespace_name}/objects',
+ 'method': 'GET'}
+ ],
+ deprecated_rule=policy.DeprecatedRule(
+ name="get_metadef_objects", check_str="rule:metadef_default",
+ deprecated_reason=DEPRECATED_REASON,
+ deprecated_since=versionutils.deprecated.XENA
+ ),
+ ),
+ policy.DocumentedRuleDefault(
+ name="modify_metadef_object",
+ check_str="rule:metadef_admin",
+ scope_types=['system', 'project'],
+ description="Update an object within a namespace.",
+ operations=[
+ {'path': '/v2/metadefs/namespaces/{namespace_name}/objects'
+ '/{object_name}',
+ 'method': 'PUT'}
+ ],
+ ),
+ policy.DocumentedRuleDefault(
+ name="add_metadef_object",
+ check_str="rule:metadef_admin",
+ scope_types=['system', 'project'],
+ description="Create an object within a namespace.",
+ operations=[
+ {'path': '/v2/metadefs/namespaces/{namespace_name}/objects',
+ 'method': 'POST'}
+ ],
+ ),
+ policy.DocumentedRuleDefault(
+ name="delete_metadef_object",
+ check_str="rule:metadef_admin",
+ scope_types=['system', 'project'],
+ description="Delete an object within a namespace.",
+ operations=[
+ {'path': '/v2/metadefs/namespaces/{namespace_name}/objects'
+ '/{object_name}',
+ 'method': 'DELETE'}
+ ],
+ ),
policy.RuleDefault(name="list_metadef_resource_types",
check_str="rule:metadef_default"),
diff --git a/glance/tests/functional/v2/test_metadef_objects.py b/glance/tests/functional/v2/test_metadef_objects.py
index 98a94cb6a..71dda1de5 100644
--- a/glance/tests/functional/v2/test_metadef_objects.py
+++ b/glance/tests/functional/v2/test_metadef_objects.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 TestMetadefObjects(functional.FunctionalTest):
@@ -271,3 +271,180 @@ class TestMetadefObjects(functional.FunctionalTest):
(namespace_name, metadata_object_name))
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
+
+ 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_object(self, namespaces):
+ objects = []
+ for namespace in namespaces:
+ headers = self._headers({'X-Tenant-Id': namespace['owner']})
+ data = {
+ "name": "object_of_%s" % (namespace['namespace']),
+ "description": "object description.",
+ "required": [
+ "property1"
+ ],
+ "properties": {
+ "property1": {
+ "type": "integer",
+ "title": "property1",
+ "description": "property1 description",
+ },
+ }
+ }
+ path = self._url('/v2/metadefs/namespaces/%s/objects' %
+ namespace['namespace'])
+ response = requests.post(path, headers=headers, json=data)
+ self.assertEqual(http.CREATED, response.status_code)
+ obj_metadata = response.json()
+ metadef_objects = dict()
+ metadef_objects[namespace['namespace']] = obj_metadata['name']
+ objects.append(metadef_objects)
+
+ return objects
+
+ def _update_object(self, path, headers, data, namespace):
+ response = requests.put(path, headers=headers, json=data)
+ self.assertEqual(http.OK, response.status_code, response.text)
+
+ expected_object = {
+ 'description': data['description'],
+ 'name': data['name'],
+ 'properties': data['properties'],
+ 'required': data['required'],
+ 'schema': '/v2/schemas/metadefs/object',
+ 'self': '/v2/metadefs/namespaces/%s/objects/%s' % (namespace,
+ data['name'])
+ }
+ # Returned metadata_object should reflect the changes
+ metadata_object = response.json()
+ metadata_object.pop('created_at')
+ metadata_object.pop('updated_at')
+ self.assertEqual(metadata_object, expected_object)
+
+ # Updates should persist across requests
+ response = requests.get(path, headers=self._headers())
+ metadata_object = response.json()
+ metadata_object.pop('created_at')
+ metadata_object.pop('updated_at')
+ self.assertEqual(metadata_object, expected_object)
+
+ def test_role_base_metadata_objects_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 object for each namespace created above
+ tenant1_objects = self._create_object(tenant1_namespaces)
+ tenant2_objects = self._create_object(tenant2_namespaces)
+
+ def _check_object_access(objects, tenant):
+ headers = self._headers({'content-type': 'application/json',
+ 'X-Tenant-Id': tenant,
+ 'X-Roles': 'reader,member'})
+ for obj in objects:
+ for namespace, object_name in obj.items():
+ path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
+ (namespace, object_name))
+ headers = headers
+ response = requests.get(path, headers=headers)
+ if namespace.split('_')[1] == 'public':
+ expected = http.OK
+ else:
+ expected = http.NOT_FOUND
+
+ self.assertEqual(expected, response.status_code)
+
+ path = self._url(
+ '/v2/metadefs/namespaces/%s/objects' % namespace)
+ response = requests.get(path, headers=headers)
+ self.assertEqual(expected, response.status_code)
+ if expected == http.OK:
+ resp_objs = response.json()['objects']
+ self.assertEqual(
+ sorted(obj.values()),
+ sorted([x['name'] for x in resp_objs]))
+
+ # Check Tenant 1 can access objects of all public namespace
+ # and cannot access object of private namespace of Tenant 2
+ _check_object_access(tenant2_objects, TENANT1)
+
+ # Check Tenant 2 can access objects of public namespace and
+ # cannot access objects of private namespace of Tenant 1
+ _check_object_access(tenant1_objects, TENANT2)
+
+ # Update objects with admin and non admin role
+ total_objects = tenant1_objects + tenant2_objects
+ for obj in total_objects:
+ for namespace, object_name in obj.items():
+ data = {
+ "name": object_name,
+ "description": "desc-UPDATED",
+ "required": [
+ "property1"
+ ],
+ "properties": {
+ 'property1': {
+ 'type': 'integer',
+ "title": "property1",
+ 'description': 'p1 desc-UPDATED',
+ }
+ }
+ }
+ # Update object should fail with non admin role
+ path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
+ (namespace, object_name))
+ 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_object(path, headers, data, namespace)
+
+ # Delete object should not be allowed to non admin role
+ for obj in total_objects:
+ for namespace, object_name in obj.items():
+ path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
+ (namespace, object_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 objects
+ headers = self._headers()
+ for obj in total_objects:
+ for namespace, object_name in obj.items():
+ path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
+ (namespace, object_name))
+ response = requests.delete(path, headers=headers)
+ self.assertEqual(http.NO_CONTENT, response.status_code)
+
+ # Deleted objects should not be exist
+ response = requests.get(path, headers=headers)
+ self.assertEqual(http.NOT_FOUND, response.status_code)