summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPranali Deore <pdeore@redhat.com>2021-07-23 14:19:40 +0000
committerDan Smith <dansmith@redhat.com>2021-09-02 16:06:12 -0700
commit9796593f31a4f0bbc62ae6e0541385b573b9ce12 (patch)
treea3f0d919d0ce5607e861abe338c1eb798ea46437
parentc64083badcf6261c55f01fd8098b1e47f19fbb5b (diff)
downloadglance-9796593f31a4f0bbc62ae6e0541385b573b9ce12.tar.gz
Implement project personas for metadef properties
This commit updates the policies for metadef properties to use default roles available from keystone. Implements: blueprint secure-rbac Change-Id: Ic93c2a8a7017e6eac16d23fb5a9224d2c11e7211
-rw-r--r--glance/policies/metadef.py76
-rw-r--r--glance/tests/functional/v2/test_metadef_properties.py162
2 files changed, 224 insertions, 14 deletions
diff --git a/glance/policies/metadef.py b/glance/policies/metadef.py
index 35575d017..92c1749ba 100644
--- a/glance/policies/metadef.py
+++ b/glance/policies/metadef.py
@@ -203,17 +203,71 @@ metadef_policies = [
],
),
-
- policy.RuleDefault(name="get_metadef_property",
- check_str="rule:metadef_default"),
- policy.RuleDefault(name="get_metadef_properties",
- check_str="rule:metadef_default"),
- policy.RuleDefault(name="modify_metadef_property",
- check_str="rule:metadef_admin"),
- policy.RuleDefault(name="add_metadef_property",
- check_str="rule:metadef_admin"),
- policy.RuleDefault(name="remove_metadef_property",
- check_str="rule:metadef_admin"),
+ policy.DocumentedRuleDefault(
+ name="get_metadef_property",
+ check_str=base.ADMIN_OR_PROJECT_READER_GET_NAMESPACE,
+ scope_types=['system', 'project'],
+ description="Get a specific meta definition property.",
+ operations=[
+ {'path': '/v2/metadefs/namespaces/{namespace_name}/properties'
+ '/{property_name}',
+ 'method': 'GET'}
+ ],
+ deprecated_rule=policy.DeprecatedRule(
+ name="get_metadef_property",
+ check_str="rule:metadef_default",
+ deprecated_reason=DEPRECATED_REASON,
+ deprecated_since=versionutils.deprecated.XENA
+ ),
+ ),
+ policy.DocumentedRuleDefault(
+ name="get_metadef_properties",
+ check_str=base.ADMIN_OR_PROJECT_READER_GET_NAMESPACE,
+ scope_types=['system', 'project'],
+ description="List meta definition properties.",
+ operations=[
+ {'path': '/v2/metadefs/namespaces/{namespace_name}/properties',
+ 'method': 'GET'}
+ ],
+ deprecated_rule=policy.DeprecatedRule(
+ name="get_metadef_properties",
+ check_str="rule:metadef_default",
+ deprecated_reason=DEPRECATED_REASON,
+ deprecated_since=versionutils.deprecated.XENA
+ ),
+ ),
+ policy.DocumentedRuleDefault(
+ name="modify_metadef_property",
+ check_str="rule:metadef_admin",
+ scope_types=['system', 'project'],
+ description="Update meta definition property.",
+ operations=[
+ {'path': '/v2/metadefs/namespaces/{namespace_name}/properties'
+ '/{property_name}',
+ 'method': 'GET'}
+ ],
+ ),
+ policy.DocumentedRuleDefault(
+ name="add_metadef_property",
+ check_str="rule:metadef_admin",
+ scope_types=['system', 'project'],
+ description="Create meta definition property.",
+ operations=[
+ {'path': '/v2/metadefs/namespaces/{namespace_name}/properties',
+ 'method': 'POST'}
+ ],
+ ),
+ policy.DocumentedRuleDefault(
+ name="remove_metadef_property",
+ check_str="rule:metadef_admin",
+ scope_types=['system', 'project'],
+ description="Delete meta definition property.",
+ operations=[
+ {'path': '/v2/metadefs/namespaces/{namespace_name}/properties'
+ '/{property_name}',
+ 'method': 'DELETE'}
+ ],
+ ),
policy.RuleDefault(name="get_metadef_tag",
check_str="rule:metadef_default"),
diff --git a/glance/tests/functional/v2/test_metadef_properties.py b/glance/tests/functional/v2/test_metadef_properties.py
index 3d55eaebd..593f201ca 100644
--- a/glance/tests/functional/v2/test_metadef_properties.py
+++ b/glance/tests/functional/v2/test_metadef_properties.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 TestNamespaceProperties(functional.FunctionalTest):
@@ -223,3 +223,159 @@ class TestNamespaceProperties(functional.FunctionalTest):
(namespace_name, property_name))
response = requests.get(path, headers=self._headers())
self.assertEqual(http.NOT_FOUND, response.status_code)
+
+ def _create_namespace(self, path, headers, data):
+ json_data = jsonutils.dumps(data)
+ response = requests.post(path, headers=headers, data=json_data)
+ self.assertEqual(http.CREATED, response.status_code)
+
+ return response.json()
+
+ def _create_properties(self, namespaces):
+ properties = []
+ for namespace in namespaces:
+ headers = self._headers({'X-Tenant-Id': namespace['owner']})
+ data = {
+ "name": "property_of_%s" % (namespace['namespace']),
+ "type": "integer",
+ "title": "property",
+ "description": "property description",
+ }
+ path = self._url('/v2/metadefs/namespaces/%s/properties' %
+ namespace['namespace'])
+ response = requests.post(path, headers=headers, json=data)
+ self.assertEqual(http.CREATED, response.status_code)
+ prop_metadata = response.json()
+ metadef_property = dict()
+ metadef_property[namespace['namespace']] = prop_metadata['name']
+ properties.append(metadef_property)
+
+ return properties
+
+ def _update_property(self, path, headers, data):
+ # The property should be mutable
+ response = requests.put(path, headers=headers, json=data)
+ self.assertEqual(http.OK, response.status_code, response.text)
+
+ # Returned property should reflect the changes
+ property_object = response.json()
+ self.assertEqual('string', property_object['type'])
+ self.assertEqual(data['description'], property_object['description'])
+
+ # Updates should persist across requests
+ response = requests.get(path, headers=self._headers())
+ self.assertEqual('string', property_object['type'])
+ self.assertEqual(data['description'], property_object['description'])
+
+ def test_role_base_metadata_properties_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 property for each namespace created above
+ tenant1_properties = self._create_properties(tenant1_namespaces)
+ tenant2_properties = self._create_properties(tenant2_namespaces)
+
+ def _check_properties_access(properties, tenant):
+ headers = self._headers({'content-type': 'application/json',
+ 'X-Tenant-Id': tenant,
+ 'X-Roles': 'reader,member'})
+ for prop in properties:
+ for namespace, property_name in prop.items():
+ path = \
+ self._url('/v2/metadefs/namespaces/%s/properties/%s' %
+ (namespace, property_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 our and public properties, but not
+ # the other tenant's
+ self.assertEqual(expected, response.status_code)
+
+ # Make sure the same holds for listing
+ path = self._url(
+ '/v2/metadefs/namespaces/%s/properties' % namespace)
+ response = requests.get(path, headers=headers)
+ self.assertEqual(expected, response.status_code)
+ if expected == http.OK:
+ resp_props = response.json()['properties'].values()
+ self.assertEqual(
+ sorted(prop.values()),
+ sorted([x['name']
+ for x in resp_props]))
+
+ # Check Tenant 1 can access properties of all public namespace
+ # and cannot access properties of private namespace of Tenant 2
+ _check_properties_access(tenant2_properties, TENANT1)
+
+ # Check Tenant 2 can access properties of public namespace and
+ # cannot access properties of private namespace of Tenant 1
+ _check_properties_access(tenant1_properties, TENANT2)
+
+ # Update properties with admin and non admin role
+ total_properties = tenant1_properties + tenant2_properties
+ for prop in total_properties:
+ for namespace, property_name in prop.items():
+ data = {
+ "name": property_name,
+ "type": "string",
+ "title": "string property",
+ "description": "desc-UPDATED",
+ }
+ path = self._url('/v2/metadefs/namespaces/%s/properties/%s' %
+ (namespace, property_name))
+
+ # Update property 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_property(path, headers, data)
+
+ # Delete property should not be allowed to non admin role
+ for prop in total_properties:
+ for namespace, property_name in prop.items():
+ path = self._url('/v2/metadefs/namespaces/%s/properties/%s' %
+ (namespace, property_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 properties
+ headers = self._headers()
+ for prop in total_properties:
+ for namespace, property_name in prop.items():
+ path = self._url('/v2/metadefs/namespaces/%s/properties/%s' %
+ (namespace, property_name))
+ response = requests.delete(path, headers=headers)
+ self.assertEqual(http.NO_CONTENT, response.status_code)
+
+ # Deleted property should not be exist
+ response = requests.get(path, headers=headers)
+ self.assertEqual(http.NOT_FOUND, response.status_code)