Fix container consumers rbac policy
This patch modifies the Consumer controller to enable the use of ownership information in policy checks. e.g. policies that use a target container: project_id:%(target.container.project_id) Story: 2009664 Task: 43872 Depends-On: I8698fc7a9ac849b8c24adfe824ca44dd3e42b999 Change-Id: I1724152839f0f5850f8d32d40b36d1670c0ad996
This commit is contained in:
parent
a9d2b133c8
commit
9601593328
@ -173,8 +173,22 @@ def flatten(d, parent_key=''):
|
||||
|
||||
class ACLMixin(object):
|
||||
|
||||
def __init__(self):
|
||||
self.secret = None
|
||||
self.container = None
|
||||
|
||||
def get_acl_tuple(self, req, **kwargs):
|
||||
return None, None
|
||||
if self.secret is not None:
|
||||
entity = 'secret'
|
||||
elif self.container is not None:
|
||||
entity = 'container'
|
||||
else:
|
||||
return None, None
|
||||
entity_acls = getattr(getattr(self, entity), '{}_acls'.format(entity))
|
||||
acl = self.get_acl_dict_for_user(req, entity_acls)
|
||||
acl['project_id'] = getattr(self, entity).project.external_id
|
||||
acl['creator_id'] = getattr(self, entity).creator_id
|
||||
return entity, acl
|
||||
|
||||
def get_acl_dict_for_user(self, req, acl_list):
|
||||
"""Get acl operation found for token user in acl list.
|
||||
@ -237,12 +251,3 @@ class ACLMixin(object):
|
||||
acl_dict.update(co_dict)
|
||||
|
||||
return acl_dict
|
||||
|
||||
|
||||
class SecretACLMixin(ACLMixin):
|
||||
|
||||
def get_acl_tuple(self, req, **kwargs):
|
||||
acl = self.get_acl_dict_for_user(req, self.secret.secret_acls)
|
||||
acl['project_id'] = self.secret.project.external_id
|
||||
acl['creator_id'] = self.secret.creator_id
|
||||
return 'secret', acl
|
||||
|
@ -47,6 +47,7 @@ class SecretACLsController(controllers.ACLMixin):
|
||||
"""Handles SecretACL requests by a given secret id."""
|
||||
|
||||
def __init__(self, secret):
|
||||
super().__init__()
|
||||
self.secret = secret
|
||||
self.secret_project_id = self.secret.project.external_id
|
||||
self.acl_repo = repo.get_secret_acl_repository()
|
||||
@ -210,6 +211,7 @@ class ContainerACLsController(controllers.ACLMixin):
|
||||
"""Handles ContainerACL requests by a given container id."""
|
||||
|
||||
def __init__(self, container):
|
||||
super().__init__()
|
||||
self.container = container
|
||||
self.container_id = container.id
|
||||
self.acl_repo = repo.get_container_acl_repository()
|
||||
|
@ -46,7 +46,9 @@ def _invalid_consumer_id():
|
||||
class ContainerConsumerController(controllers.ACLMixin):
|
||||
"""Handles Container Consumer entity retrieval and deletion requests"""
|
||||
|
||||
def __init__(self, consumer_id):
|
||||
def __init__(self, container, consumer_id):
|
||||
super().__init__()
|
||||
self.container = container
|
||||
self.consumer_id = consumer_id
|
||||
self.consumer_repo = repo.get_container_consumer_repository()
|
||||
self.validator = validators.ContainerConsumerValidator()
|
||||
@ -78,8 +80,10 @@ class ContainerConsumerController(controllers.ACLMixin):
|
||||
class ContainerConsumersController(controllers.ACLMixin):
|
||||
"""Handles Container Consumer creation requests"""
|
||||
|
||||
def __init__(self, container_id):
|
||||
self.container_id = container_id
|
||||
def __init__(self, container):
|
||||
super().__init__()
|
||||
self.container = container
|
||||
self.container_id = self.container.id
|
||||
self.consumer_repo = repo.get_container_consumer_repository()
|
||||
self.container_repo = repo.get_container_repository()
|
||||
self.project_repo = repo.get_project_repository()
|
||||
@ -91,7 +95,8 @@ class ContainerConsumersController(controllers.ACLMixin):
|
||||
def _lookup(self, consumer_id, *remainder):
|
||||
if not utils.validate_id_is_uuid(consumer_id):
|
||||
_invalid_consumer_id()()
|
||||
return ContainerConsumerController(consumer_id), remainder
|
||||
return ContainerConsumerController(self.container, consumer_id), \
|
||||
remainder
|
||||
|
||||
@pecan.expose(generic=True)
|
||||
def index(self, **kwargs):
|
||||
@ -99,7 +104,7 @@ class ContainerConsumersController(controllers.ACLMixin):
|
||||
|
||||
@index.when(method='GET', template='json')
|
||||
@controllers.handle_exceptions(u._('ContainerConsumers(s) retrieval'))
|
||||
@controllers.enforce_rbac('consumers:get')
|
||||
@controllers.enforce_rbac('container_consumers:get')
|
||||
def on_get(self, external_project_id, **kw):
|
||||
LOG.debug('Start consumers on_get '
|
||||
'for container-ID %s:', self.container_id)
|
||||
@ -137,7 +142,7 @@ class ContainerConsumersController(controllers.ACLMixin):
|
||||
|
||||
@index.when(method='POST', template='json')
|
||||
@controllers.handle_exceptions(u._('ContainerConsumer creation'))
|
||||
@controllers.enforce_rbac('consumers:post')
|
||||
@controllers.enforce_rbac('container_consumers:post')
|
||||
@controllers.enforce_content_types(['application/json'])
|
||||
def on_post(self, external_project_id, **kwargs):
|
||||
|
||||
@ -145,14 +150,12 @@ class ContainerConsumersController(controllers.ACLMixin):
|
||||
data = api.load_body(pecan.request, validator=self.validator)
|
||||
LOG.debug('Start on_post...%s', data)
|
||||
|
||||
container = self._get_container(self.container_id)
|
||||
|
||||
self.quota_enforcer.enforce(project)
|
||||
|
||||
new_consumer = models.ContainerConsumerMetadatum(self.container_id,
|
||||
project.id,
|
||||
data)
|
||||
self.consumer_repo.create_or_update_from(new_consumer, container)
|
||||
self.consumer_repo.create_or_update_from(new_consumer, self.container)
|
||||
|
||||
url = hrefs.convert_consumer_to_href(new_consumer.container_id)
|
||||
pecan.response.headers['Location'] = url
|
||||
@ -160,19 +163,16 @@ class ContainerConsumersController(controllers.ACLMixin):
|
||||
LOG.info('Created a container consumer for project: %s',
|
||||
external_project_id)
|
||||
|
||||
return self._return_container_data(self.container_id)
|
||||
return self._return_container_data()
|
||||
|
||||
@index.when(method='DELETE', template='json')
|
||||
@controllers.handle_exceptions(u._('ContainerConsumer deletion'))
|
||||
@controllers.enforce_rbac('consumers:delete')
|
||||
@controllers.enforce_rbac('container_consumers:delete')
|
||||
@controllers.enforce_content_types(['application/json'])
|
||||
def on_delete(self, external_project_id, **kwargs):
|
||||
data = api.load_body(pecan.request, validator=self.validator)
|
||||
LOG.debug('Start on_delete...%s', data)
|
||||
project = self.project_repo.find_by_external_project_id(
|
||||
external_project_id, suppress_exception=True)
|
||||
if not project:
|
||||
_consumer_not_found()
|
||||
project = res.get_or_create_project(external_project_id)
|
||||
|
||||
consumer = self.consumer_repo.get_by_values(
|
||||
self.container_id,
|
||||
@ -184,9 +184,8 @@ class ContainerConsumersController(controllers.ACLMixin):
|
||||
_consumer_not_found()
|
||||
LOG.debug("Found container consumer: %s", consumer)
|
||||
|
||||
container = self._get_container(self.container_id)
|
||||
owner_of_consumer = consumer.project_id == project.id
|
||||
owner_of_container = container.project.external_id \
|
||||
owner_of_container = self.container.project.external_id \
|
||||
== external_project_id
|
||||
if not owner_of_consumer and not owner_of_container:
|
||||
_consumer_ownership_mismatch()
|
||||
@ -198,22 +197,13 @@ class ContainerConsumersController(controllers.ACLMixin):
|
||||
LOG.exception('Problem deleting container consumer')
|
||||
_consumer_not_found()
|
||||
|
||||
ret_data = self._return_container_data(self.container_id)
|
||||
ret_data = self._return_container_data()
|
||||
LOG.info('Deleted a container consumer for project: %s',
|
||||
external_project_id)
|
||||
return ret_data
|
||||
|
||||
def _get_container(self, container_id):
|
||||
container = self.container_repo.get_container_by_id(
|
||||
container_id, suppress_exception=True)
|
||||
if not container:
|
||||
controllers.containers.container_not_found()
|
||||
return container
|
||||
|
||||
def _return_container_data(self, container_id):
|
||||
container = self._get_container(container_id)
|
||||
|
||||
dict_fields = container.to_dict_fields()
|
||||
def _return_container_data(self):
|
||||
dict_fields = self.container.to_dict_fields()
|
||||
|
||||
for secret_ref in dict_fields['secret_refs']:
|
||||
hrefs.convert_to_hrefs(secret_ref)
|
||||
@ -227,7 +217,9 @@ class ContainerConsumersController(controllers.ACLMixin):
|
||||
class SecretConsumerController(controllers.ACLMixin):
|
||||
"""Handles Secret Consumer entity retrieval and deletion requests"""
|
||||
|
||||
def __init__(self, consumer_id):
|
||||
def __init__(self, secret, consumer_id):
|
||||
super().__init__()
|
||||
self.secret = secret
|
||||
self.consumer_id = consumer_id
|
||||
self.consumer_repo = repo.get_secret_consumer_repository()
|
||||
self.validator = validators.SecretConsumerValidator()
|
||||
@ -259,8 +251,10 @@ class SecretConsumerController(controllers.ACLMixin):
|
||||
class SecretConsumersController(controllers.ACLMixin):
|
||||
"""Handles Secret Consumer creation requests"""
|
||||
|
||||
def __init__(self, secret_id):
|
||||
self.secret_id = secret_id
|
||||
def __init__(self, secret):
|
||||
super().__init__()
|
||||
self.secret = secret
|
||||
self.secret_id = secret.id
|
||||
self.consumer_repo = repo.get_secret_consumer_repository()
|
||||
self.secret_repo = repo.get_secret_repository()
|
||||
self.project_repo = repo.get_project_repository()
|
||||
@ -272,7 +266,7 @@ class SecretConsumersController(controllers.ACLMixin):
|
||||
def _lookup(self, consumer_id, *remainder):
|
||||
if not utils.validate_id_is_uuid(consumer_id):
|
||||
_invalid_consumer_id()()
|
||||
return SecretConsumerController(consumer_id), remainder
|
||||
return SecretConsumerController(self.secret, consumer_id), remainder
|
||||
|
||||
@pecan.expose(generic=True)
|
||||
def index(self, **kwargs):
|
||||
@ -280,7 +274,7 @@ class SecretConsumersController(controllers.ACLMixin):
|
||||
|
||||
@index.when(method='GET', template='json')
|
||||
@controllers.handle_exceptions(u._('SecretConsumers(s) retrieval'))
|
||||
@controllers.enforce_rbac('consumers:get')
|
||||
@controllers.enforce_rbac('secret_consumers:get')
|
||||
def on_get(self, external_project_id, **kw):
|
||||
LOG.debug('Start consumers on_get '
|
||||
'for secret-ID %s:', self.secret_id)
|
||||
@ -318,7 +312,7 @@ class SecretConsumersController(controllers.ACLMixin):
|
||||
|
||||
@index.when(method='POST', template='json')
|
||||
@controllers.handle_exceptions(u._('SecretConsumer creation'))
|
||||
@controllers.enforce_rbac('consumers:post')
|
||||
@controllers.enforce_rbac('secret_consumers:post')
|
||||
@controllers.enforce_content_types(['application/json'])
|
||||
def on_post(self, external_project_id, **kwargs):
|
||||
|
||||
@ -326,8 +320,6 @@ class SecretConsumersController(controllers.ACLMixin):
|
||||
data = api.load_body(pecan.request, validator=self.validator)
|
||||
LOG.debug('Start on_post...%s', data)
|
||||
|
||||
secret = self._get_secret(self.secret_id)
|
||||
|
||||
self.quota_enforcer.enforce(project)
|
||||
|
||||
new_consumer = models.SecretConsumerMetadatum(
|
||||
@ -337,7 +329,7 @@ class SecretConsumersController(controllers.ACLMixin):
|
||||
data["resource_type"],
|
||||
data["resource_id"],
|
||||
)
|
||||
self.consumer_repo.create_or_update_from(new_consumer, secret)
|
||||
self.consumer_repo.create_or_update_from(new_consumer, self.secret)
|
||||
|
||||
url = hrefs.convert_consumer_to_href(new_consumer.secret_id)
|
||||
pecan.response.headers['Location'] = url
|
||||
@ -349,7 +341,7 @@ class SecretConsumersController(controllers.ACLMixin):
|
||||
|
||||
@index.when(method='DELETE', template='json')
|
||||
@controllers.handle_exceptions(u._('SecretConsumer deletion'))
|
||||
@controllers.enforce_rbac('consumers:delete')
|
||||
@controllers.enforce_rbac('secret_consumers:delete')
|
||||
@controllers.enforce_content_types(['application/json'])
|
||||
def on_delete(self, external_project_id, **kwargs):
|
||||
data = api.load_body(pecan.request, validator=self.validator)
|
||||
@ -368,9 +360,8 @@ class SecretConsumersController(controllers.ACLMixin):
|
||||
_consumer_not_found()
|
||||
LOG.debug("Found consumer: %s", consumer)
|
||||
|
||||
secret = self._get_secret(self.secret_id)
|
||||
owner_of_consumer = consumer.project_id == project.id
|
||||
owner_of_secret = secret.project.external_id \
|
||||
owner_of_secret = self.secret.project.external_id \
|
||||
== external_project_id
|
||||
if not owner_of_consumer and not owner_of_secret:
|
||||
_consumer_ownership_mismatch()
|
||||
|
@ -45,21 +45,16 @@ class ContainerController(controllers.ACLMixin):
|
||||
"""Handles Container entity retrieval and deletion requests."""
|
||||
|
||||
def __init__(self, container):
|
||||
super().__init__()
|
||||
self.container = container
|
||||
self.container_id = container.id
|
||||
self.consumer_repo = repo.get_container_consumer_repository()
|
||||
self.container_repo = repo.get_container_repository()
|
||||
self.validator = validators.ContainerValidator()
|
||||
self.consumers = consumers.ContainerConsumersController(
|
||||
self.container_id)
|
||||
self.container)
|
||||
self.acl = acls.ContainerACLsController(self.container)
|
||||
|
||||
def get_acl_tuple(self, req, **kwargs):
|
||||
d = self.get_acl_dict_for_user(req, self.container.container_acls)
|
||||
d['project_id'] = self.container.project.external_id
|
||||
d['creator_id'] = self.container.creator_id
|
||||
return 'container', d
|
||||
|
||||
@pecan.expose(generic=True, template='json')
|
||||
def index(self, **kwargs):
|
||||
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
||||
@ -112,6 +107,7 @@ class ContainersController(controllers.ACLMixin):
|
||||
"""Handles Container creation requests."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.consumer_repo = repo.get_container_consumer_repository()
|
||||
self.container_repo = repo.get_container_repository()
|
||||
self.secret_repo = repo.get_secret_repository()
|
||||
@ -230,17 +226,12 @@ class ContainersSecretsController(controllers.ACLMixin):
|
||||
|
||||
def __init__(self, container):
|
||||
LOG.debug('=== Creating ContainerSecretsController ===')
|
||||
super().__init__()
|
||||
self.container = container
|
||||
self.container_secret_repo = repo.get_container_secret_repository()
|
||||
self.secret_repo = repo.get_secret_repository()
|
||||
self.validator = validators.ContainerSecretValidator()
|
||||
|
||||
def get_acl_tuple(self, req, **kwargs):
|
||||
acl = self.get_acl_dict_for_user(req, self.container.container_acls)
|
||||
acl['project_id'] = self.container.project.external_id
|
||||
acl['creator_id'] = self.container.creator_id
|
||||
return ('container', acl)
|
||||
|
||||
@pecan.expose(generic=True)
|
||||
def index(self, **kwargs):
|
||||
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
||||
|
@ -61,6 +61,7 @@ class OrderController(controllers.ACLMixin):
|
||||
"""Handles Order retrieval and deletion requests."""
|
||||
|
||||
def __init__(self, order, queue_resource=None):
|
||||
super().__init__()
|
||||
self.order = order
|
||||
self.order_repo = repo.get_order_repository()
|
||||
self.queue = queue_resource or async_client.TaskClient()
|
||||
@ -96,8 +97,8 @@ class OrdersController(controllers.ACLMixin):
|
||||
"""Handles Order requests for Secret creation."""
|
||||
|
||||
def __init__(self, queue_resource=None):
|
||||
|
||||
LOG.debug('Creating OrdersController')
|
||||
super().__init__()
|
||||
self.order_repo = repo.get_order_repository()
|
||||
self.queue = queue_resource or async_client.TaskClient()
|
||||
self.type_order_validator = validators.TypeOrderValidator()
|
||||
|
@ -37,6 +37,7 @@ class QuotasController(controllers.ACLMixin):
|
||||
|
||||
def __init__(self):
|
||||
LOG.debug('=== Creating QuotasController ===')
|
||||
super().__init__()
|
||||
self.quota_driver = quota.QuotaDriver()
|
||||
|
||||
@pecan.expose(generic=True)
|
||||
@ -59,6 +60,7 @@ class ProjectQuotasController(controllers.ACLMixin):
|
||||
|
||||
def __init__(self, project_id):
|
||||
LOG.debug('=== Creating ProjectQuotasController ===')
|
||||
super().__init__()
|
||||
self.passed_project_id = project_id
|
||||
self.validator = validators.ProjectQuotaValidator()
|
||||
self.quota_driver = quota.QuotaDriver()
|
||||
@ -114,6 +116,7 @@ class ProjectsQuotasController(controllers.ACLMixin):
|
||||
|
||||
def __init__(self):
|
||||
LOG.debug('=== Creating ProjectsQuotaController ===')
|
||||
super().__init__()
|
||||
self.quota_driver = quota.QuotaDriver()
|
||||
|
||||
@pecan.expose()
|
||||
|
@ -28,11 +28,12 @@ def _secret_metadata_not_found():
|
||||
pecan.abort(404, u._('Secret metadata not found.'))
|
||||
|
||||
|
||||
class SecretMetadataController(controllers.SecretACLMixin):
|
||||
class SecretMetadataController(controllers.ACLMixin):
|
||||
"""Handles SecretMetadata requests by a given secret id."""
|
||||
|
||||
def __init__(self, secret):
|
||||
LOG.debug('=== Creating SecretMetadataController ===')
|
||||
super().__init__()
|
||||
self.secret = secret
|
||||
self.secret_project_id = self.secret.project.external_id
|
||||
self.secret_repo = repo.get_secret_repository()
|
||||
@ -106,10 +107,11 @@ class SecretMetadataController(controllers.SecretACLMixin):
|
||||
return {'key': key, 'value': value}
|
||||
|
||||
|
||||
class SecretMetadatumController(controllers.SecretACLMixin):
|
||||
class SecretMetadatumController(controllers.ACLMixin):
|
||||
|
||||
def __init__(self, secret):
|
||||
LOG.debug('=== Creating SecretMetadatumController ===')
|
||||
super().__init__()
|
||||
self.user_meta_repo = repo.get_secret_user_meta_repository()
|
||||
self.secret = secret
|
||||
self.metadatum_validator = validators.NewSecretMetadatumValidator()
|
||||
|
@ -71,13 +71,14 @@ def _request_has_twsk_but_no_transport_key_id():
|
||||
'transport key id has not been provided.'))
|
||||
|
||||
|
||||
class SecretController(controllers.SecretACLMixin):
|
||||
class SecretController(controllers.ACLMixin):
|
||||
"""Handles Secret retrieval and deletion requests."""
|
||||
|
||||
def __init__(self, secret):
|
||||
LOG.debug('=== Creating SecretController ===')
|
||||
super().__init__()
|
||||
self.secret = secret
|
||||
self.consumers = consumers.SecretConsumersController(secret.id)
|
||||
self.consumers = consumers.SecretConsumersController(secret)
|
||||
self.consumer_repo = repo.get_secret_consumer_repository()
|
||||
self.transport_key_repo = repo.get_transport_key_repository()
|
||||
|
||||
@ -276,6 +277,7 @@ class SecretsController(controllers.ACLMixin):
|
||||
|
||||
def __init__(self):
|
||||
LOG.debug('Creating SecretsController')
|
||||
super().__init__()
|
||||
self.validator = validators.NewSecretValidator()
|
||||
self.secret_repo = repo.get_secret_repository()
|
||||
self.quota_enforcer = quota.QuotaEnforcer('secrets', self.secret_repo)
|
||||
|
@ -57,6 +57,7 @@ class PreferredSecretStoreController(controllers.ACLMixin):
|
||||
|
||||
def __init__(self, secret_store):
|
||||
LOG.debug('=== Creating PreferredSecretStoreController ===')
|
||||
super().__init__()
|
||||
self.secret_store = secret_store
|
||||
self.proj_store_repo = repo.get_project_secret_store_repository()
|
||||
|
||||
@ -103,6 +104,7 @@ class SecretStoreController(controllers.ACLMixin):
|
||||
|
||||
def __init__(self, secret_store):
|
||||
LOG.debug('=== Creating SecretStoreController ===')
|
||||
super().__init__()
|
||||
self.secret_store = secret_store
|
||||
|
||||
@pecan.expose()
|
||||
@ -129,6 +131,7 @@ class SecretStoresController(controllers.ACLMixin):
|
||||
|
||||
def __init__(self):
|
||||
LOG.debug('Creating SecretStoresController')
|
||||
super().__init__()
|
||||
self.secret_stores_repo = repo.get_secret_stores_repository()
|
||||
self.proj_store_repo = repo.get_project_secret_store_repository()
|
||||
|
||||
|
@ -43,6 +43,7 @@ class TransportKeyController(controllers.ACLMixin):
|
||||
|
||||
def __init__(self, transport_key_id, transport_key_repo=None):
|
||||
LOG.debug('=== Creating TransportKeyController ===')
|
||||
super().__init__()
|
||||
self.transport_key_id = transport_key_id
|
||||
self.repo = transport_key_repo or repo.TransportKeyRepo()
|
||||
|
||||
@ -83,6 +84,7 @@ class TransportKeysController(controllers.ACLMixin):
|
||||
|
||||
def __init__(self, transport_key_repo=None):
|
||||
LOG.debug('Creating TransportKeyController')
|
||||
super().__init__()
|
||||
self.repo = transport_key_repo or repo.TransportKeyRepo()
|
||||
self.validator = validators.NewTransportKeyValidator()
|
||||
|
||||
|
@ -21,10 +21,20 @@ _READER = "role:reader"
|
||||
_MEMBER = "role:member"
|
||||
_ADMIN = "role:admin"
|
||||
_SYSTEM_ADMIN = "role:admin and system_scope:all"
|
||||
_PROJECT_MEMBER = f"{_MEMBER} and project_id:%(target.container.project_id)s"
|
||||
_PROJECT_ADMIN = f"{_ADMIN} and project_id:%(target.container.project_id)s"
|
||||
|
||||
_SECRET_CREATOR = "user_id:%(target.secret.creator_id)s"
|
||||
_SECRET_PROJECT = "project_id:%(target.secret.project_id)s"
|
||||
_SECRET_MEMBER = f"{_MEMBER} and {_SECRET_PROJECT}"
|
||||
_SECRET_ADMIN = f"{_ADMIN} and {_SECRET_PROJECT}"
|
||||
_SECRET_ACCESS = (f"{_SECRET_CREATOR} or ({_SECRET_MEMBER} and "
|
||||
f"True:%(target.secret.read_project_access)s)")
|
||||
|
||||
_CONTAINER_CREATOR = "user_id:%(target.container.creator_id)s"
|
||||
_CONTAINER_IS_NOT_PRIVATE = "True:%(target.container.read_project_access)s"
|
||||
_CONTAINER_PROJECT = "project_id:%(target.container.project_id)s"
|
||||
_CONTAINER_MEMBER = f"{_MEMBER} and {_CONTAINER_PROJECT}"
|
||||
_CONTAINER_ADMIN = f"{_ADMIN} and {_CONTAINER_PROJECT}"
|
||||
_CONTAINER_ACCESS = (f"{_CONTAINER_CREATOR} or ({_CONTAINER_MEMBER} and "
|
||||
f"True:%(target.container.read_project_access)s)")
|
||||
|
||||
rules = [
|
||||
policy.DocumentedRuleDefault(
|
||||
@ -33,27 +43,23 @@ rules = [
|
||||
'rule:audit or rule:container_non_private_read or ' +
|
||||
'rule:container_project_creator or ' +
|
||||
'rule:container_project_admin or rule:container_acl_read' +
|
||||
f" or ({_PROJECT_MEMBER} and ({_CONTAINER_CREATOR} or " +
|
||||
f"{_CONTAINER_IS_NOT_PRIVATE})) or {_PROJECT_ADMIN} or " +
|
||||
f" or {_CONTAINER_ACCESS} or {_CONTAINER_ADMIN} or " +
|
||||
f"{_SYSTEM_ADMIN}",
|
||||
scope_types=['project', 'system'],
|
||||
description='List a specific consumer for a given container.',
|
||||
operations=[
|
||||
{
|
||||
'path': '/v1/containers/{container-id}/consumers/' +
|
||||
'{consumer-id}',
|
||||
'method': 'GET'
|
||||
}
|
||||
]
|
||||
# This API is unusable. There is no way for a user to get
|
||||
# the consumer-id they would need to send a request.
|
||||
description='DEPRECATED: show information for a specific consumer',
|
||||
operations=[{
|
||||
'path': '/v1/containers/{container-id}/consumers/{consumer-id}',
|
||||
'method': 'GET'
|
||||
}]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='consumers:get',
|
||||
check_str='rule:admin or rule:observer or rule:creator or ' +
|
||||
'rule:audit or rule:container_non_private_read or ' +
|
||||
name='container_consumers:get',
|
||||
check_str='rule:container_non_private_read or ' +
|
||||
'rule:container_project_creator or ' +
|
||||
'rule:container_project_admin or rule:container_acl_read' +
|
||||
f" or ({_PROJECT_MEMBER} and ({_CONTAINER_CREATOR} or " +
|
||||
f"{_CONTAINER_IS_NOT_PRIVATE})) or {_PROJECT_ADMIN} or " +
|
||||
'rule:container_project_admin or rule:container_acl_read ' +
|
||||
f" or {_CONTAINER_ACCESS} or {_CONTAINER_ADMIN} or " +
|
||||
f"{_SYSTEM_ADMIN}",
|
||||
scope_types=['project', 'system'],
|
||||
description='List a containers consumers.',
|
||||
@ -65,12 +71,11 @@ rules = [
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='consumers:post',
|
||||
check_str='rule:admin or rule:container_non_private_read or ' +
|
||||
name='container_consumers:post',
|
||||
check_str='rule:container_non_private_read or ' +
|
||||
'rule:container_project_creator or ' +
|
||||
'rule:container_project_admin or rule:container_acl_read' +
|
||||
f" or ({_PROJECT_MEMBER} and ({_CONTAINER_CREATOR} or " +
|
||||
f"{_CONTAINER_IS_NOT_PRIVATE})) or {_PROJECT_ADMIN} or " +
|
||||
'rule:container_project_admin or rule:container_acl_read ' +
|
||||
f" or {_CONTAINER_ACCESS} or {_CONTAINER_ADMIN} or " +
|
||||
f"{_SYSTEM_ADMIN}",
|
||||
scope_types=['project', 'system'],
|
||||
description='Creates a consumer.',
|
||||
@ -82,19 +87,65 @@ rules = [
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='consumers:delete',
|
||||
check_str='rule:admin or rule:container_non_private_read or ' +
|
||||
name='container_consumers:delete',
|
||||
check_str='rule:container_non_private_read or ' +
|
||||
'rule:container_project_creator or ' +
|
||||
'rule:container_project_admin or rule:container_acl_read' +
|
||||
f" or ({_PROJECT_MEMBER} and ({_CONTAINER_CREATOR} or " +
|
||||
f"{_CONTAINER_IS_NOT_PRIVATE})) or {_PROJECT_ADMIN} or " +
|
||||
'rule:container_project_admin or rule:container_acl_read ' +
|
||||
f" or {_CONTAINER_ACCESS} or {_CONTAINER_ADMIN} or " +
|
||||
f"{_SYSTEM_ADMIN}",
|
||||
scope_types=['project', 'system'],
|
||||
description='Deletes a consumer.',
|
||||
operations=[
|
||||
{
|
||||
'path': '/v1/containers/{container-id}/consumers/' +
|
||||
'{consumer-id}',
|
||||
'path': '/v1/containers/{container-id}/consumers',
|
||||
'method': 'DELETE'
|
||||
}
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='secret_consumers:get',
|
||||
check_str='rule:secret_non_private_read or ' +
|
||||
'rule:secret_project_creator or ' +
|
||||
'rule:secret_project_admin or rule:secret_acl_read ' +
|
||||
f" or {_SECRET_ACCESS} or {_SECRET_ADMIN} or " +
|
||||
f"{_SYSTEM_ADMIN}",
|
||||
scope_types=['project', 'system'],
|
||||
description='List consumers for a secret.',
|
||||
operations=[
|
||||
{
|
||||
'path': '/v1/secrets/{secret-id}/consumers',
|
||||
'method': 'GET'
|
||||
}
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='secret_consumers:post',
|
||||
check_str='rule:secret_non_private_read or ' +
|
||||
'rule:secret_project_creator or ' +
|
||||
'rule:secret_project_admin or rule:secret_acl_read ' +
|
||||
f" or {_SECRET_ACCESS} or {_SECRET_ADMIN} or " +
|
||||
f"{_SYSTEM_ADMIN}",
|
||||
scope_types=['project', 'system'],
|
||||
description='Creates a consumer.',
|
||||
operations=[
|
||||
{
|
||||
'path': '/v1/secrets/{secrets-id}/consumers',
|
||||
'method': 'POST'
|
||||
}
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='secret_consumers:delete',
|
||||
check_str='rule:secret_non_private_read or ' +
|
||||
'rule:secret_project_creator or ' +
|
||||
'rule:secret_project_admin or rule:secret_acl_read ' +
|
||||
f" or {_SECRET_ACCESS} or {_SECRET_ADMIN} or " +
|
||||
f"{_SYSTEM_ADMIN}",
|
||||
scope_types=['project', 'system'],
|
||||
description='Deletes a consumer.',
|
||||
operations=[
|
||||
{
|
||||
'path': '/v1/secrets/{secrets-id}/consumers',
|
||||
'method': 'DELETE'
|
||||
}
|
||||
]
|
||||
|
@ -1059,10 +1059,12 @@ class WhenTestingConsumersResource(BaseTestCase):
|
||||
|
||||
self.external_project_id = '12345project'
|
||||
self.container_id = '12345container'
|
||||
self.creator_user_id = '123456CreatorUser'
|
||||
|
||||
# Force an error on GET calls that pass RBAC, as we are not testing
|
||||
# such flows in this test module.
|
||||
self.consumer_repo = mock.MagicMock()
|
||||
self.container_repo = mock.MagicMock()
|
||||
get_by_container_id = mock.MagicMock(return_value=None,
|
||||
side_effect=self
|
||||
._generate_get_error())
|
||||
@ -1072,23 +1074,34 @@ class WhenTestingConsumersResource(BaseTestCase):
|
||||
self.setup_container_consumer_repository_mock(self.consumer_repo)
|
||||
self.setup_container_repository_mock()
|
||||
|
||||
self.resource = ConsumersResource(container_id=self.container_id)
|
||||
container = mock.MagicMock()
|
||||
container.id = self.container_id
|
||||
container.project.external_id = self.external_project_id
|
||||
container.creator_id = self.creator_user_id
|
||||
self.container_repo.get_container_by_id.return_value = container
|
||||
self.setup_container_repository_mock(self.container_repo)
|
||||
|
||||
self.resource = ConsumersResource(container)
|
||||
|
||||
def test_rules_should_be_loaded(self):
|
||||
self.assertIsNotNone(self.policy_enforcer.rules)
|
||||
|
||||
def test_should_pass_create_consumer(self):
|
||||
self._assert_pass_rbac(['admin'], self._invoke_on_post,
|
||||
content_type='application/json')
|
||||
content_type='application/json',
|
||||
project_id=self.external_project_id)
|
||||
|
||||
def test_should_raise_create_consumer(self):
|
||||
self._assert_fail_rbac([None, 'audit', 'observer', 'creator', 'bogus'],
|
||||
self._invoke_on_post,
|
||||
content_type='application/json')
|
||||
content_type='application/json',
|
||||
user_id='some_other_user',
|
||||
project_id='some_other_project')
|
||||
|
||||
def test_should_pass_delete_consumer(self):
|
||||
self._assert_pass_rbac(['admin'], self._invoke_on_delete,
|
||||
content_type='application/json')
|
||||
content_type='application/json',
|
||||
project_id=self.external_project_id)
|
||||
|
||||
def test_should_raise_delete_consumer(self):
|
||||
self._assert_fail_rbac([None, 'audit', 'observer', 'creator', 'bogus'],
|
||||
@ -1097,7 +1110,8 @@ class WhenTestingConsumersResource(BaseTestCase):
|
||||
def test_should_pass_get_consumers(self):
|
||||
self._assert_pass_rbac(['admin', 'observer', 'creator', 'audit'],
|
||||
self._invoke_on_get,
|
||||
content_type='application/json')
|
||||
content_type='application/json',
|
||||
project_id=self.external_project_id)
|
||||
|
||||
def test_should_raise_get_consumers(self):
|
||||
self._assert_fail_rbac([None, 'bogus'],
|
||||
@ -1121,17 +1135,28 @@ class WhenTestingConsumerResource(BaseTestCase):
|
||||
|
||||
self.external_project_id = '12345project'
|
||||
self.consumer_id = '12345consumer'
|
||||
self.container_id = '12345container'
|
||||
self.creator_user_id = '123456CreatorUser'
|
||||
|
||||
# Force an error on GET calls that pass RBAC, as we are not testing
|
||||
# such flows in this test module.
|
||||
self.consumer_repo = mock.MagicMock()
|
||||
self.container_repo = mock.MagicMock()
|
||||
fail_method = mock.MagicMock(return_value=None,
|
||||
side_effect=self._generate_get_error())
|
||||
self.consumer_repo.get = fail_method
|
||||
|
||||
self.setup_project_repository_mock()
|
||||
self.setup_container_consumer_repository_mock(self.consumer_repo)
|
||||
self.resource = ConsumerResource(consumer_id=self.consumer_id)
|
||||
|
||||
container = mock.MagicMock()
|
||||
container.id = self.container_id
|
||||
container.project.external_id = self.external_project_id
|
||||
container.creator_id = self.creator_user_id
|
||||
self.container_repo.get_container_by_id.return_value = container
|
||||
self.setup_container_repository_mock(self.container_repo)
|
||||
|
||||
self.resource = ConsumerResource(container, self.consumer_id)
|
||||
|
||||
def test_rules_should_be_loaded(self):
|
||||
self.assertIsNotNone(self.policy_enforcer.rules)
|
||||
@ -1292,6 +1317,7 @@ class WhenTestingSecretConsumersResource(BaseTestCase):
|
||||
|
||||
self.external_project_id = '12345project'
|
||||
self.secret_id = '12345secret'
|
||||
self.creator_user_id = '123456CreatorUser'
|
||||
|
||||
# Force an error on GET calls that pass RBAC, as we are not testing
|
||||
# such flows in this test module.
|
||||
@ -1301,27 +1327,35 @@ class WhenTestingSecretConsumersResource(BaseTestCase):
|
||||
._generate_get_error())
|
||||
self.consumer_repo.get_by_secret_id = get_by_secret_id
|
||||
|
||||
secret = mock.MagicMock()
|
||||
secret.id = self.secret_id
|
||||
secret.project.external_id = self.external_project_id
|
||||
secret.creator_id = self.creator_user_id
|
||||
|
||||
self.setup_project_repository_mock()
|
||||
self.setup_secret_consumer_repository_mock(self.consumer_repo)
|
||||
self.setup_secret_repository_mock()
|
||||
|
||||
self.resource = SecretConsumersResource(secret_id=self.secret_id)
|
||||
self.resource = SecretConsumersResource(secret)
|
||||
|
||||
def test_rules_should_be_loaded(self):
|
||||
self.assertIsNotNone(self.policy_enforcer.rules)
|
||||
|
||||
def test_should_pass_create_consumer(self):
|
||||
self._assert_pass_rbac(['admin'], self._invoke_on_post,
|
||||
content_type='application/json')
|
||||
content_type='application/json',
|
||||
project_id=self.external_project_id)
|
||||
|
||||
def test_should_raise_create_consumer(self):
|
||||
self._assert_fail_rbac([None, 'audit', 'observer', 'creator', 'bogus'],
|
||||
self._invoke_on_post,
|
||||
content_type='application/json')
|
||||
content_type='application/json',
|
||||
project_id='some_other_id')
|
||||
|
||||
def test_should_pass_delete_consumer(self):
|
||||
self._assert_pass_rbac(['admin'], self._invoke_on_delete,
|
||||
content_type='application/json')
|
||||
content_type='application/json',
|
||||
project_id=self.external_project_id)
|
||||
|
||||
def test_should_raise_delete_consumer(self):
|
||||
self._assert_fail_rbac([None, 'audit', 'observer', 'creator', 'bogus'],
|
||||
@ -1330,12 +1364,14 @@ class WhenTestingSecretConsumersResource(BaseTestCase):
|
||||
def test_should_pass_get_consumers(self):
|
||||
self._assert_pass_rbac(['admin', 'observer', 'creator', 'audit'],
|
||||
self._invoke_on_get,
|
||||
content_type='application/json')
|
||||
content_type='application/json',
|
||||
project_id=self.external_project_id)
|
||||
|
||||
def test_should_raise_get_consumers(self):
|
||||
self._assert_fail_rbac([None, 'bogus'],
|
||||
self._invoke_on_get,
|
||||
content_type='application/json')
|
||||
content_type='application/json',
|
||||
project_id='some_other_id')
|
||||
|
||||
def _invoke_on_post(self):
|
||||
self.resource.on_post(self.req, self.resp)
|
||||
@ -1354,6 +1390,7 @@ class WhenTestingSecretConsumerResource(BaseTestCase):
|
||||
|
||||
self.external_project_id = '12345project'
|
||||
self.consumer_id = '12345consumer'
|
||||
self.creator_user_id = '123456CreatorUser'
|
||||
|
||||
# Force an error on GET calls that pass RBAC, as we are not testing
|
||||
# such flows in this test module.
|
||||
@ -1362,9 +1399,13 @@ class WhenTestingSecretConsumerResource(BaseTestCase):
|
||||
side_effect=self._generate_get_error())
|
||||
self.consumer_repo.get = fail_method
|
||||
|
||||
secret = mock.MagicMock()
|
||||
secret.project.external_id = self.external_project_id
|
||||
secret.creator_id = self.creator_user_id
|
||||
|
||||
self.setup_project_repository_mock()
|
||||
self.setup_secret_consumer_repository_mock(self.consumer_repo)
|
||||
self.resource = SecretConsumerResource(consumer_id=self.consumer_id)
|
||||
self.resource = SecretConsumerResource(secret, self.consumer_id)
|
||||
|
||||
def test_rules_should_be_loaded(self):
|
||||
self.assertIsNotNone(self.policy_enforcer.rules)
|
||||
|
@ -136,20 +136,22 @@ test_data_read_container_consumer_acl_only = {
|
||||
|
||||
test_data_delete_container_consumer_acl_only = {
|
||||
'with_admin_a': {'user': admin_a, 'expected_return': 200},
|
||||
'with_creator_a': {'user': creator_a, 'expected_return': 403},
|
||||
'with_observer_a': {'user': observer_a, 'expected_return': 403},
|
||||
'with_auditor_a': {'user': auditor_a, 'expected_return': 403},
|
||||
'with_creator_a': {'user': creator_a, 'expected_return': 200},
|
||||
'with_observer_a': {'user': observer_a, 'expected_return': 200},
|
||||
'with_auditor_a': {'user': auditor_a, 'expected_return': 200},
|
||||
# the consumer being deleted is owned by project a, so attempts
|
||||
# to remove it with users from project b below should fail
|
||||
'with_admin_b': {'user': admin_b, 'expected_return': 403},
|
||||
'with_observer_b': {'user': observer_b, 'expected_return': 403},
|
||||
}
|
||||
|
||||
test_data_create_container_consumer_acl_only = {
|
||||
'with_admin_a': {'user': admin_a, 'expected_return': 200},
|
||||
'with_creator_a': {'user': creator_a, 'expected_return': 403},
|
||||
'with_observer_a': {'user': observer_a, 'expected_return': 403},
|
||||
'with_auditor_a': {'user': auditor_a, 'expected_return': 403},
|
||||
'with_creator_a': {'user': creator_a, 'expected_return': 200},
|
||||
'with_observer_a': {'user': observer_a, 'expected_return': 200},
|
||||
'with_auditor_a': {'user': auditor_a, 'expected_return': 200},
|
||||
'with_admin_b': {'user': admin_b, 'expected_return': 200},
|
||||
'with_observer_b': {'user': observer_b, 'expected_return': 403},
|
||||
'with_observer_b': {'user': observer_b, 'expected_return': 200},
|
||||
}
|
||||
|
||||
|
||||
@ -272,7 +274,14 @@ class AclTestCase(base.TestCase):
|
||||
|
||||
@utils.parameterized_dataset(test_data_delete_container_consumer_acl_only)
|
||||
def test_container_acl_remove_consumer(self, user, expected_return):
|
||||
"""Acl access will not allow you to delete a consumer"""
|
||||
"""Test DELETE /v1/containers/{container-id}/consumers
|
||||
|
||||
Test default policy for deleting a consumer set by admin_a
|
||||
from a private container owned by creator_a.
|
||||
|
||||
Each user in the data set is added to the ACL and then used
|
||||
to delete the consumer set by admin_a.
|
||||
"""
|
||||
container_ref = self.store_container(user_name=creator_a,
|
||||
admin=admin_a)
|
||||
consumer_model = get_consumer_model()
|
||||
@ -298,7 +307,14 @@ class AclTestCase(base.TestCase):
|
||||
|
||||
@utils.parameterized_dataset(test_data_create_container_consumer_acl_only)
|
||||
def test_container_acl_create_consumer(self, user, expected_return):
|
||||
"""Acl access will not allow you to add a consumer"""
|
||||
"""Test POST /v1/containers/{container_id}/consumers
|
||||
|
||||
Test default policy for adding consumers to a container owned by
|
||||
creator_a and set to private.
|
||||
|
||||
Each user in the data set is added to the ACL and then used
|
||||
to POST a new consumer.
|
||||
"""
|
||||
container_ref = self.store_container(user_name=creator_a,
|
||||
admin=admin_a)
|
||||
|
||||
|
13
releasenotes/notes/fix-story-2009664-042ef282c0dd6b6a.yaml
Normal file
13
releasenotes/notes/fix-story-2009664-042ef282c0dd6b6a.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
Fixed Story 2009664 - Fixed the Consumer controller to be able to use the
|
||||
associated Container's ownership information in policy checks.
|
||||
security:
|
||||
- |
|
||||
Part of the fix for Story 2009664 required renaming the policy for
|
||||
Container Consumers from "consumers:get" to "container_consumers:get",
|
||||
"consumers:post" to "container_consumers:post", and "consumers:delete"
|
||||
to "container_consumers:delete". If you are using custom policies to
|
||||
override the default policies you will need to update them to use the
|
||||
new names.
|
Loading…
x
Reference in New Issue
Block a user