Removed per ACL operations and added support for PUT method.
Changes has been done as per comments in https://review.openstack.org/#/c/178479/ All of APIs for /secrets/{uuid}/acls/{uuid} resource are removed. ACL resource is changed to /secrets/{uuid}/acl. PUT (complete update) support is added for above ACL resource. PUT is used instead of POST for creating ACL resource as per API-WG recommendation. GET on ACL resource will return response similar to request format. PUT/PATCH will return above ACL resource reference in response. Now as every secret or container has a default acl which is returned on GET call when no explicit ACL is set. That's why PATCH will not return 404 when used on secret/container with no explicit ACL. Similarly DELETE will not return 404 on its invocation even if explicit ACL is not set or called multiple times. All of above changes are made to container ACL resource as well. Change-Id: I5bfccf4cb827845084fd2d5d734f476f2ed16146
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
import pecan
|
import pecan
|
||||||
|
import six
|
||||||
|
|
||||||
from barbican import api
|
from barbican import api
|
||||||
from barbican.api import controllers
|
from barbican.api import controllers
|
||||||
@@ -26,113 +27,21 @@ from barbican.model import repositories as repo
|
|||||||
LOG = utils.getLogger(__name__)
|
LOG = utils.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _acls_not_found(acl_for=None):
|
def _convert_acl_to_response_format(acl, acls_dict):
|
||||||
"""Throw exception indicating no secret or container acls found."""
|
fields = acl.to_dict_fields()
|
||||||
pecan.abort(404, u._('Not Found. Sorry no ACL found for given {0}.').
|
operation = fields['operation']
|
||||||
format(acl_for))
|
|
||||||
|
acl_data = {} # dict for each acl operation data
|
||||||
|
|
||||||
|
acl_data['creator-only'] = fields['creator_only']
|
||||||
|
acl_data['users'] = fields.get('users', [])
|
||||||
|
acl_data['created'] = fields['created']
|
||||||
|
acl_data['updated'] = fields['updated']
|
||||||
|
|
||||||
|
acls_dict[operation] = acl_data
|
||||||
|
|
||||||
|
|
||||||
def _acl_not_found():
|
DEFAULT_ACL = {'read': {'creator-only': False}}
|
||||||
"""Throw exception indicating no secret or container acl found."""
|
|
||||||
pecan.abort(404, u._('Not Found. Sorry no ACL found for given id.'))
|
|
||||||
|
|
||||||
|
|
||||||
def _acls_already_exist():
|
|
||||||
"""Throw exception indicating secret or container acls already exist."""
|
|
||||||
pecan.abort(400, u._('Existing ACL cannot be updated with POST method.'))
|
|
||||||
|
|
||||||
|
|
||||||
def _acl_operation_update_not_allowed():
|
|
||||||
"""Throw exception indicating existing secret or container acl operation.
|
|
||||||
|
|
||||||
Operation cannot be changed for an existing ACL. Allowed change is list of
|
|
||||||
users and/or creator_only flag change.
|
|
||||||
"""
|
|
||||||
pecan.abort(400, u._("Existing ACL's operation cannot be updated."))
|
|
||||||
|
|
||||||
|
|
||||||
class SecretACLController(controllers.ACLMixin):
|
|
||||||
"""Handles a SecretACL entity retrieval and update requests."""
|
|
||||||
|
|
||||||
def __init__(self, acl_id, secret_project_id, secret):
|
|
||||||
self.acl_id = acl_id
|
|
||||||
self.secret_project_id = secret_project_id
|
|
||||||
self.secret = secret
|
|
||||||
self.acl_repo = repo.get_secret_acl_repository()
|
|
||||||
self.validator = validators.ACLValidator()
|
|
||||||
|
|
||||||
def get_acl_tuple(self, req, **kwargs):
|
|
||||||
d = {'project_id': self.secret_project_id}
|
|
||||||
return 'secret', d
|
|
||||||
|
|
||||||
@pecan.expose(generic=True)
|
|
||||||
def index(self):
|
|
||||||
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
|
||||||
|
|
||||||
@index.when(method='GET', template='json')
|
|
||||||
@controllers.handle_exceptions(u._('SecretACL retrieval'))
|
|
||||||
@controllers.enforce_rbac('secret_acl:get')
|
|
||||||
def on_get(self, external_project_id):
|
|
||||||
secret_acl = self.acl_repo.get(
|
|
||||||
entity_id=self.acl_id,
|
|
||||||
suppress_exception=True)
|
|
||||||
|
|
||||||
if not secret_acl:
|
|
||||||
_acl_not_found()
|
|
||||||
|
|
||||||
dict_fields = secret_acl.to_dict_fields()
|
|
||||||
|
|
||||||
return hrefs.convert_acl_to_hrefs(dict_fields)
|
|
||||||
|
|
||||||
@index.when(method='PATCH', template='json')
|
|
||||||
@controllers.handle_exceptions(u._('A SecretACL Update'))
|
|
||||||
@controllers.enforce_rbac('secret_acl:patch')
|
|
||||||
@controllers.enforce_content_types(['application/json'])
|
|
||||||
def on_patch(self, external_project_id, **kwargs):
|
|
||||||
"""Handles existing secret ACL update for given acl id."""
|
|
||||||
|
|
||||||
data = api.load_body(pecan.request, validator=self.validator)
|
|
||||||
LOG.debug('Start SecretACLController on_patch...%s', data)
|
|
||||||
|
|
||||||
secret_acl = self.acl_repo.get(
|
|
||||||
entity_id=self.acl_id,
|
|
||||||
suppress_exception=True)
|
|
||||||
if not secret_acl:
|
|
||||||
_acl_not_found()
|
|
||||||
|
|
||||||
# Make sure request acl operation matches with acl stored in db
|
|
||||||
operation = secret_acl.operation
|
|
||||||
input_acl = data.get(operation)
|
|
||||||
if not input_acl:
|
|
||||||
_acl_operation_update_not_allowed()
|
|
||||||
|
|
||||||
creator_only = input_acl.get('creator-only')
|
|
||||||
user_ids = input_acl.get('users')
|
|
||||||
if creator_only is not None:
|
|
||||||
secret_acl.creator_only = creator_only
|
|
||||||
|
|
||||||
self.acl_repo.create_or_replace_from(self.secret,
|
|
||||||
secret_acl=secret_acl,
|
|
||||||
user_ids=user_ids)
|
|
||||||
acl_ref = hrefs.convert_acl_to_hrefs(secret_acl.to_dict_fields())
|
|
||||||
|
|
||||||
return {'acl_ref': acl_ref['acl_ref']}
|
|
||||||
|
|
||||||
@index.when(method='DELETE', template='json')
|
|
||||||
@controllers.handle_exceptions(u._('SecretACL deletion'))
|
|
||||||
@controllers.enforce_rbac('secret_acl:delete')
|
|
||||||
def on_delete(self, external_project_id, **kwargs):
|
|
||||||
"""Deletes existing ACL by acl_id provided in URI."""
|
|
||||||
|
|
||||||
secret_acl = self.acl_repo.get(
|
|
||||||
entity_id=self.acl_id,
|
|
||||||
suppress_exception=True)
|
|
||||||
|
|
||||||
if not secret_acl:
|
|
||||||
_acl_not_found()
|
|
||||||
|
|
||||||
self.acl_repo.delete_entity_by_id(entity_id=self.acl_id,
|
|
||||||
external_project_id=None)
|
|
||||||
|
|
||||||
|
|
||||||
class SecretACLsController(controllers.ACLMixin):
|
class SecretACLsController(controllers.ACLMixin):
|
||||||
@@ -146,14 +55,10 @@ class SecretACLsController(controllers.ACLMixin):
|
|||||||
self.validator = validators.ACLValidator()
|
self.validator = validators.ACLValidator()
|
||||||
|
|
||||||
def get_acl_tuple(self, req, **kwargs):
|
def get_acl_tuple(self, req, **kwargs):
|
||||||
d = {'project_id': self.secret_project_id}
|
d = {'project_id': self.secret_project_id,
|
||||||
|
'creator_id': self.secret.creator_id}
|
||||||
return 'secret', d
|
return 'secret', d
|
||||||
|
|
||||||
@pecan.expose()
|
|
||||||
def _lookup(self, acl_id, *remainder):
|
|
||||||
return SecretACLController(acl_id, self.secret_project_id,
|
|
||||||
self.secret), remainder
|
|
||||||
|
|
||||||
@pecan.expose(generic=True)
|
@pecan.expose(generic=True)
|
||||||
def index(self, **kwargs):
|
def index(self, **kwargs):
|
||||||
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
||||||
@@ -165,60 +70,11 @@ class SecretACLsController(controllers.ACLMixin):
|
|||||||
LOG.debug('Start secret ACL on_get '
|
LOG.debug('Start secret ACL on_get '
|
||||||
'for secret-ID %s:', self.secret.id)
|
'for secret-ID %s:', self.secret.id)
|
||||||
|
|
||||||
return self._return_acl_hrefs(self.secret.id)
|
return self._return_acl_list_response(self.secret.id)
|
||||||
|
|
||||||
@index.when(method='POST', template='json')
|
|
||||||
@controllers.handle_exceptions(u._('SecretACL(s) creation'))
|
|
||||||
@controllers.enforce_rbac('secret_acls:post')
|
|
||||||
@controllers.enforce_content_types(['application/json'])
|
|
||||||
def on_post(self, external_project_id, **kwargs):
|
|
||||||
"""Handles secret acls creation request.
|
|
||||||
|
|
||||||
Once a set of ACLs exists for a given secret, it can only be updated
|
|
||||||
via PATCH method. In create, multiple operation ACL payload can be
|
|
||||||
specified as mentioned in sample below.
|
|
||||||
|
|
||||||
{
|
|
||||||
"read":{
|
|
||||||
"users":[
|
|
||||||
"5ecb18f341894e94baca9e8c7b6a824a"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"write":{
|
|
||||||
"users":[
|
|
||||||
"20b63d71f90848cf827ee48074f213b7",
|
|
||||||
"5ecb18f341894e94baca9e8c7b6a824a"
|
|
||||||
],
|
|
||||||
"creator-only":false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
count = self.acl_repo.get_count(self.secret.id)
|
|
||||||
LOG.debug('Count of existing ACL on_post is [%s] '
|
|
||||||
' for secret-ID %s:', count, self.secret.id)
|
|
||||||
if count > 0:
|
|
||||||
_acls_already_exist()
|
|
||||||
|
|
||||||
data = api.load_body(pecan.request, validator=self.validator)
|
|
||||||
LOG.debug('Start on_post...%s', data)
|
|
||||||
|
|
||||||
for operation in itertools.ifilter(lambda x: data.get(x),
|
|
||||||
validators.ACL_OPERATIONS):
|
|
||||||
input_cr_only = data[operation].get('creator-only')
|
|
||||||
creator_only = True if input_cr_only else False
|
|
||||||
new_acl = models.SecretACL(self.secret.id, operation=operation,
|
|
||||||
creator_only=creator_only)
|
|
||||||
self.acl_repo.create_or_replace_from(
|
|
||||||
self.secret, secret_acl=new_acl,
|
|
||||||
user_ids=data[operation].get('users'))
|
|
||||||
|
|
||||||
pecan.response.status = 201
|
|
||||||
return self._return_acl_hrefs(self.secret.id)
|
|
||||||
|
|
||||||
@index.when(method='PATCH', template='json')
|
@index.when(method='PATCH', template='json')
|
||||||
@controllers.handle_exceptions(u._('SecretACL(s) Update'))
|
@controllers.handle_exceptions(u._('SecretACL(s) Update'))
|
||||||
@controllers.enforce_rbac('secret_acls:patch')
|
@controllers.enforce_rbac('secret_acls:put_patch')
|
||||||
@controllers.enforce_content_types(['application/json'])
|
@controllers.enforce_content_types(['application/json'])
|
||||||
def on_patch(self, external_project_id, **kwargs):
|
def on_patch(self, external_project_id, **kwargs):
|
||||||
"""Handles update of existing secret acl requests.
|
"""Handles update of existing secret acl requests.
|
||||||
@@ -244,13 +100,6 @@ class SecretACLsController(controllers.ACLMixin):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
count = self.acl_repo.get_count(self.secret.id)
|
|
||||||
LOG.debug('Count of existing ACL on_secret is [%s] '
|
|
||||||
' for secret-ID %s:', count, self.secret.id)
|
|
||||||
if count == 0:
|
|
||||||
_acls_not_found("secret")
|
|
||||||
|
|
||||||
data = api.load_body(pecan.request, validator=self.validator)
|
data = api.load_body(pecan.request, validator=self.validator)
|
||||||
LOG.debug('Start on_patch...%s', data)
|
LOG.debug('Start on_patch...%s', data)
|
||||||
|
|
||||||
@@ -271,7 +120,72 @@ class SecretACLsController(controllers.ACLMixin):
|
|||||||
self.acl_repo.create_or_replace_from(self.secret, secret_acl=s_acl,
|
self.acl_repo.create_or_replace_from(self.secret, secret_acl=s_acl,
|
||||||
user_ids=user_ids)
|
user_ids=user_ids)
|
||||||
|
|
||||||
return self._return_acl_hrefs(self.secret.id)
|
acl_ref = '{0}/acl'.format(
|
||||||
|
hrefs.convert_secret_to_href(self.secret.id))
|
||||||
|
return {'acl_ref': acl_ref}
|
||||||
|
|
||||||
|
@index.when(method='PUT', template='json')
|
||||||
|
@controllers.handle_exceptions(u._('SecretACL(s) Update'))
|
||||||
|
@controllers.enforce_rbac('secret_acls:put_patch')
|
||||||
|
@controllers.enforce_content_types(['application/json'])
|
||||||
|
def on_put(self, external_project_id, **kwargs):
|
||||||
|
"""Handles update of existing secret acl requests.
|
||||||
|
|
||||||
|
Replaces existing secret ACL(s) with input ACL(s) data. Existing
|
||||||
|
ACL operation not specified in input are removed as part of update.
|
||||||
|
For missing creator-only in ACL, false is used as default.
|
||||||
|
In update, multiple operation ACL payload can be specified as
|
||||||
|
mentioned in sample below. A specific ACL can be updated by its
|
||||||
|
own id via SecretACLController patch request.
|
||||||
|
|
||||||
|
{
|
||||||
|
"read":{
|
||||||
|
"users":[
|
||||||
|
"5ecb18f341894e94baca9e8c7b6a824a",
|
||||||
|
"20b63d71f90848cf827ee48074f213b7",
|
||||||
|
"c7753f8da8dc4fbea75730ab0b6e0ef4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"write":{
|
||||||
|
"users":[
|
||||||
|
"5ecb18f341894e94baca9e8c7b6a824a"
|
||||||
|
],
|
||||||
|
"creator-only":true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Every secret, by default, has an implicit ACL in case client has not
|
||||||
|
defined an explicit ACL. That default ACL definition, DEFAULT_ACL,
|
||||||
|
signifies that a secret by default has project based access i.e. client
|
||||||
|
with necessary roles on secret project can access the secret. That's
|
||||||
|
why when ACL is added to a secret, it always returns 200 (and not 201)
|
||||||
|
indicating existence of implicit ACL on a secret.
|
||||||
|
"""
|
||||||
|
data = api.load_body(pecan.request, validator=self.validator)
|
||||||
|
LOG.debug('Start on_put...%s', data)
|
||||||
|
|
||||||
|
existing_acls_map = {acl.operation: acl for acl in
|
||||||
|
self.secret.secret_acls}
|
||||||
|
for operation in itertools.ifilter(lambda x: data.get(x),
|
||||||
|
validators.ACL_OPERATIONS):
|
||||||
|
creator_only = data[operation].get('creator-only', False)
|
||||||
|
user_ids = data[operation].get('users', [])
|
||||||
|
s_acl = None
|
||||||
|
if operation in existing_acls_map: # update if matching acl exists
|
||||||
|
s_acl = existing_acls_map.pop(operation)
|
||||||
|
s_acl.creator_only = creator_only
|
||||||
|
else:
|
||||||
|
s_acl = models.SecretACL(self.secret.id, operation=operation,
|
||||||
|
creator_only=creator_only)
|
||||||
|
self.acl_repo.create_or_replace_from(self.secret, secret_acl=s_acl,
|
||||||
|
user_ids=user_ids)
|
||||||
|
# delete remaining existing acls as they are not present in input.
|
||||||
|
for acl in six.itervalues(existing_acls_map):
|
||||||
|
self.acl_repo.delete_entity_by_id(entity_id=acl.id,
|
||||||
|
external_project_id=None)
|
||||||
|
acl_ref = '{0}/acl'.format(
|
||||||
|
hrefs.convert_secret_to_href(self.secret.id))
|
||||||
|
return {'acl_ref': acl_ref}
|
||||||
|
|
||||||
@index.when(method='DELETE', template='json')
|
@index.when(method='DELETE', template='json')
|
||||||
@controllers.handle_exceptions(u._('SecretACL(s) deletion'))
|
@controllers.handle_exceptions(u._('SecretACL(s) deletion'))
|
||||||
@@ -279,125 +193,37 @@ class SecretACLsController(controllers.ACLMixin):
|
|||||||
def on_delete(self, external_project_id, **kwargs):
|
def on_delete(self, external_project_id, **kwargs):
|
||||||
|
|
||||||
count = self.acl_repo.get_count(self.secret.id)
|
count = self.acl_repo.get_count(self.secret.id)
|
||||||
if count == 0:
|
if count > 0:
|
||||||
_acls_not_found("secret")
|
self.acl_repo.delete_acls_for_secret(self.secret)
|
||||||
self.acl_repo.delete_acls_for_secret(self.secret)
|
|
||||||
|
|
||||||
def _return_acl_hrefs(self, secret_id):
|
def _return_acl_list_response(self, secret_id):
|
||||||
result = self.acl_repo.get_by_secret_id(secret_id)
|
result = self.acl_repo.get_by_secret_id(secret_id)
|
||||||
|
|
||||||
if not result:
|
acls_data = {}
|
||||||
_acls_not_found("secret")
|
if result:
|
||||||
else:
|
for acl in result:
|
||||||
acl_recs = [hrefs.convert_acl_to_hrefs(acl.to_dict_fields())
|
_convert_acl_to_response_format(acl, acls_data)
|
||||||
for acl in result]
|
if not acls_data:
|
||||||
return [{'acl_ref': acl['acl_ref']} for acl in acl_recs]
|
acls_data = DEFAULT_ACL.copy()
|
||||||
|
return acls_data
|
||||||
|
|
||||||
class ContainerACLController(controllers.ACLMixin):
|
|
||||||
"""Handles a ContainerACL entity retrieval and update requests."""
|
|
||||||
|
|
||||||
def __init__(self, acl_id, container_project_id, container):
|
|
||||||
self.acl_id = acl_id
|
|
||||||
self.container_project_id = container_project_id
|
|
||||||
self.container = container
|
|
||||||
self.acl_repo = repo.get_container_acl_repository()
|
|
||||||
self.validator = validators.ACLValidator()
|
|
||||||
|
|
||||||
def get_acl_tuple(self, req, **kwargs):
|
|
||||||
d = {'project_id': self.container_project_id}
|
|
||||||
return 'container', d
|
|
||||||
|
|
||||||
@pecan.expose(generic=True)
|
|
||||||
def index(self):
|
|
||||||
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
|
||||||
|
|
||||||
@index.when(method='GET', template='json')
|
|
||||||
@controllers.handle_exceptions(u._('ContainerACL retrieval'))
|
|
||||||
@controllers.enforce_rbac('container_acl:get')
|
|
||||||
def on_get(self, external_project_id):
|
|
||||||
container_acl = self.acl_repo.get(
|
|
||||||
entity_id=self.acl_id,
|
|
||||||
suppress_exception=True)
|
|
||||||
|
|
||||||
if not container_acl:
|
|
||||||
_acl_not_found()
|
|
||||||
|
|
||||||
dict_fields = container_acl.to_dict_fields()
|
|
||||||
|
|
||||||
return hrefs.convert_acl_to_hrefs(dict_fields)
|
|
||||||
|
|
||||||
@index.when(method='PATCH', template='json')
|
|
||||||
@controllers.handle_exceptions(u._('A ContainerACL Update'))
|
|
||||||
@controllers.enforce_rbac('container_acl:patch')
|
|
||||||
@controllers.enforce_content_types(['application/json'])
|
|
||||||
def on_patch(self, external_project_id, **kwargs):
|
|
||||||
"""Handles existing container ACL update for given acl id."""
|
|
||||||
data = api.load_body(pecan.request, validator=self.validator)
|
|
||||||
LOG.debug('Start ContainerACLController on_patch...%s', data)
|
|
||||||
|
|
||||||
container_acl = self.acl_repo.get(
|
|
||||||
entity_id=self.acl_id,
|
|
||||||
suppress_exception=True)
|
|
||||||
if not container_acl:
|
|
||||||
_acl_not_found()
|
|
||||||
|
|
||||||
# Make sure request acl operation matches with acl stored in db
|
|
||||||
operation = container_acl.operation
|
|
||||||
input_acl = data.get(operation)
|
|
||||||
if not input_acl:
|
|
||||||
_acl_operation_update_not_allowed()
|
|
||||||
|
|
||||||
creator_only = input_acl.get('creator-only')
|
|
||||||
user_ids = input_acl.get('users')
|
|
||||||
if creator_only is not None:
|
|
||||||
container_acl.creator_only = creator_only
|
|
||||||
|
|
||||||
self.acl_repo.create_or_replace_from(self.container,
|
|
||||||
container_acl=container_acl,
|
|
||||||
user_ids=user_ids)
|
|
||||||
acl_ref = hrefs.convert_acl_to_hrefs(container_acl.to_dict_fields())
|
|
||||||
|
|
||||||
return {'acl_ref': acl_ref['acl_ref']}
|
|
||||||
|
|
||||||
@index.when(method='DELETE', template='json')
|
|
||||||
@controllers.handle_exceptions(u._('ContainerACL deletion'))
|
|
||||||
@controllers.enforce_rbac('container_acl:delete')
|
|
||||||
def on_delete(self, external_project_id, **kwargs):
|
|
||||||
"""Deletes existing ACL by acl_id provided in URI."""
|
|
||||||
|
|
||||||
container_acl = self.acl_repo.get(
|
|
||||||
entity_id=self.acl_id,
|
|
||||||
suppress_exception=True)
|
|
||||||
if not container_acl:
|
|
||||||
_acl_not_found()
|
|
||||||
|
|
||||||
self.acl_repo.delete_entity_by_id(entity_id=self.acl_id,
|
|
||||||
external_project_id=None)
|
|
||||||
|
|
||||||
|
|
||||||
class ContainerACLsController(controllers.ACLMixin):
|
class ContainerACLsController(controllers.ACLMixin):
|
||||||
"""Handles ContainerACL requests by a given container id."""
|
"""Handles ContainerACL requests by a given container id."""
|
||||||
|
|
||||||
def __init__(self, container_id):
|
def __init__(self, container):
|
||||||
self.container_id = container_id
|
self.container = container
|
||||||
self.container = None
|
self.container_id = container.id
|
||||||
self.acl_repo = repo.get_container_acl_repository()
|
self.acl_repo = repo.get_container_acl_repository()
|
||||||
self.container_repo = repo.get_container_repository()
|
self.container_repo = repo.get_container_repository()
|
||||||
self.validator = validators.ACLValidator()
|
self.validator = validators.ACLValidator()
|
||||||
self.container_project_id = None
|
self.container_project_id = container.project.external_id
|
||||||
|
|
||||||
def get_acl_tuple(self, req, **kwargs):
|
def get_acl_tuple(self, req, **kwargs):
|
||||||
self._assert_id_and_set_container(suppress_exception=True)
|
d = {'project_id': self.container_project_id,
|
||||||
d = {'project_id': self.container_project_id}
|
'creator_id': self.container.creator_id}
|
||||||
return 'container', d
|
return 'container', d
|
||||||
|
|
||||||
@pecan.expose()
|
|
||||||
def _lookup(self, acl_id, *remainder):
|
|
||||||
self._assert_id_and_set_container()
|
|
||||||
return (ContainerACLController(acl_id, self.container_project_id,
|
|
||||||
self.container), remainder)
|
|
||||||
|
|
||||||
@pecan.expose(generic=True)
|
@pecan.expose(generic=True)
|
||||||
def index(self, **kwargs):
|
def index(self, **kwargs):
|
||||||
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
||||||
@@ -406,68 +232,14 @@ class ContainerACLsController(controllers.ACLMixin):
|
|||||||
@controllers.handle_exceptions(u._('ContainerACL(s) retrieval'))
|
@controllers.handle_exceptions(u._('ContainerACL(s) retrieval'))
|
||||||
@controllers.enforce_rbac('container_acls:get')
|
@controllers.enforce_rbac('container_acls:get')
|
||||||
def on_get(self, external_project_id, **kw):
|
def on_get(self, external_project_id, **kw):
|
||||||
self._assert_id_and_set_container(suppress_exception=True)
|
|
||||||
LOG.debug('Start container ACL on_get '
|
LOG.debug('Start container ACL on_get '
|
||||||
'for container-ID %s:', self.container_id)
|
'for container-ID %s:', self.container_id)
|
||||||
if not self.container:
|
|
||||||
controllers.containers.container_not_found()
|
|
||||||
|
|
||||||
return self._return_acl_hrefs(self.container.id)
|
return self._return_acl_list_response(self.container.id)
|
||||||
|
|
||||||
@index.when(method='POST', template='json')
|
|
||||||
@controllers.handle_exceptions(u._('ContainerACL(s) creation'))
|
|
||||||
@controllers.enforce_rbac('container_acls:post')
|
|
||||||
@controllers.enforce_content_types(['application/json'])
|
|
||||||
def on_post(self, external_project_id, **kwargs):
|
|
||||||
"""Handles container acls creation request.
|
|
||||||
|
|
||||||
Once a set of ACLs exists for a given container, it can only be updated
|
|
||||||
via PATCH method. In create, multiple operation ACL payload can be
|
|
||||||
specified as mentioned in sample below.
|
|
||||||
|
|
||||||
{
|
|
||||||
"read":{
|
|
||||||
"users":[
|
|
||||||
"5ecb18f341894e94baca9e8c7b6a824a"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"write":{
|
|
||||||
"users":[
|
|
||||||
"20b63d71f90848cf827ee48074f213b7",
|
|
||||||
"5ecb18f341894e94baca9e8c7b6a824a"
|
|
||||||
],
|
|
||||||
"creator-only":false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
self._assert_id_and_set_container()
|
|
||||||
|
|
||||||
count = self.acl_repo.get_count(self.container.id)
|
|
||||||
LOG.debug('Count of existing ACL on_post is [%s] '
|
|
||||||
' for container-ID %s:', count, self.container.id)
|
|
||||||
if count > 0:
|
|
||||||
_acls_already_exist()
|
|
||||||
|
|
||||||
data = api.load_body(pecan.request, validator=self.validator)
|
|
||||||
LOG.debug('Start ContainerACLsController on_post...%s', data)
|
|
||||||
|
|
||||||
for operation in itertools.ifilter(lambda x: data.get(x),
|
|
||||||
validators.ACL_OPERATIONS):
|
|
||||||
in_cr_only = data[operation].get('creator-only')
|
|
||||||
creator_only = True if in_cr_only else False
|
|
||||||
new_acl = models.ContainerACL(self.container.id,
|
|
||||||
operation=operation,
|
|
||||||
creator_only=creator_only)
|
|
||||||
self.acl_repo.create_or_replace_from(
|
|
||||||
self.container, container_acl=new_acl,
|
|
||||||
user_ids=data[operation].get('users'))
|
|
||||||
|
|
||||||
pecan.response.status = 201
|
|
||||||
return self._return_acl_hrefs(self.container.id)
|
|
||||||
|
|
||||||
@index.when(method='PATCH', template='json')
|
@index.when(method='PATCH', template='json')
|
||||||
@controllers.handle_exceptions(u._('ContainerACL(s) Update'))
|
@controllers.handle_exceptions(u._('ContainerACL(s) Update'))
|
||||||
@controllers.enforce_rbac('container_acls:patch')
|
@controllers.enforce_rbac('container_acls:put_patch')
|
||||||
@controllers.enforce_content_types(['application/json'])
|
@controllers.enforce_content_types(['application/json'])
|
||||||
def on_patch(self, external_project_id, **kwargs):
|
def on_patch(self, external_project_id, **kwargs):
|
||||||
"""Handles update of existing container acl requests.
|
"""Handles update of existing container acl requests.
|
||||||
@@ -493,13 +265,6 @@ class ContainerACLsController(controllers.ACLMixin):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
self._assert_id_and_set_container()
|
|
||||||
count = self.acl_repo.get_count(self.container.id)
|
|
||||||
LOG.debug('Count of existing ACL on_patch is [%s] '
|
|
||||||
' for container-ID %s:', count, self.container.id)
|
|
||||||
if count == 0:
|
|
||||||
_acls_not_found("container")
|
|
||||||
|
|
||||||
data = api.load_body(pecan.request, validator=self.validator)
|
data = api.load_body(pecan.request, validator=self.validator)
|
||||||
LOG.debug('Start ContainerACLsController on_patch...%s', data)
|
LOG.debug('Start ContainerACLsController on_patch...%s', data)
|
||||||
|
|
||||||
@@ -521,42 +286,91 @@ class ContainerACLsController(controllers.ACLMixin):
|
|||||||
container_acl=c_acl,
|
container_acl=c_acl,
|
||||||
user_ids=user_ids)
|
user_ids=user_ids)
|
||||||
|
|
||||||
return self._return_acl_hrefs(self.container.id)
|
acl_ref = '{0}/acl'.format(
|
||||||
|
hrefs.convert_container_to_href(self.container.id))
|
||||||
|
return {'acl_ref': acl_ref}
|
||||||
|
|
||||||
|
@index.when(method='PUT', template='json')
|
||||||
|
@controllers.handle_exceptions(u._('ContainerACL(s) Update'))
|
||||||
|
@controllers.enforce_rbac('container_acls:put_patch')
|
||||||
|
@controllers.enforce_content_types(['application/json'])
|
||||||
|
def on_put(self, external_project_id, **kwargs):
|
||||||
|
"""Handles update of existing container acl requests.
|
||||||
|
|
||||||
|
Replaces existing container ACL(s) with input ACL(s) data. Existing
|
||||||
|
ACL operation not specified in input are removed as part of update.
|
||||||
|
For missing creator-only in ACL, false is used as default.
|
||||||
|
In update, multiple operation ACL payload can be specified as
|
||||||
|
mentioned in sample below. A specific ACL can be updated by its
|
||||||
|
own id via ContainerACLController patch request.
|
||||||
|
|
||||||
|
{
|
||||||
|
"read":{
|
||||||
|
"users":[
|
||||||
|
"5ecb18f341894e94baca9e8c7b6a824a",
|
||||||
|
"20b63d71f90848cf827ee48074f213b7",
|
||||||
|
"c7753f8da8dc4fbea75730ab0b6e0ef4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"write":{
|
||||||
|
"users":[
|
||||||
|
"5ecb18f341894e94baca9e8c7b6a824a"
|
||||||
|
],
|
||||||
|
"creator-only":true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Every container, by default, has an implicit ACL in case client has not
|
||||||
|
defined an explicit ACL. That default ACL definition, DEFAULT_ACL,
|
||||||
|
signifies that a container by default has project based access i.e.
|
||||||
|
client with necessary roles on container project can access the
|
||||||
|
container. That's why when ACL is added to a container, it always
|
||||||
|
returns 200 (and not 201) indicating existence of implicit ACL on a
|
||||||
|
container.
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = api.load_body(pecan.request, validator=self.validator)
|
||||||
|
LOG.debug('Start ContainerACLsController on_put...%s', data)
|
||||||
|
|
||||||
|
existing_acls_map = {acl.operation: acl for acl in
|
||||||
|
self.container.container_acls}
|
||||||
|
for operation in itertools.ifilter(lambda x: data.get(x),
|
||||||
|
validators.ACL_OPERATIONS):
|
||||||
|
creator_only = data[operation].get('creator-only', False)
|
||||||
|
user_ids = data[operation].get('users', [])
|
||||||
|
if operation in existing_acls_map: # update if matching acl exists
|
||||||
|
c_acl = existing_acls_map.pop(operation)
|
||||||
|
c_acl.creator_only = creator_only
|
||||||
|
else:
|
||||||
|
c_acl = models.ContainerACL(self.container.id,
|
||||||
|
operation=operation,
|
||||||
|
creator_only=creator_only)
|
||||||
|
self.acl_repo.create_or_replace_from(self.container,
|
||||||
|
container_acl=c_acl,
|
||||||
|
user_ids=user_ids)
|
||||||
|
# delete remaining existing acls as they are not present in input.
|
||||||
|
for acl in six.itervalues(existing_acls_map):
|
||||||
|
self.acl_repo.delete_entity_by_id(entity_id=acl.id,
|
||||||
|
external_project_id=None)
|
||||||
|
acl_ref = '{0}/acl'.format(
|
||||||
|
hrefs.convert_container_to_href(self.container.id))
|
||||||
|
return {'acl_ref': acl_ref}
|
||||||
|
|
||||||
@index.when(method='DELETE', template='json')
|
@index.when(method='DELETE', template='json')
|
||||||
@controllers.handle_exceptions(u._('ContainerACL(s) deletion'))
|
@controllers.handle_exceptions(u._('ContainerACL(s) deletion'))
|
||||||
@controllers.enforce_rbac('container_acls:delete')
|
@controllers.enforce_rbac('container_acls:delete')
|
||||||
def on_delete(self, external_project_id, **kwargs):
|
def on_delete(self, external_project_id, **kwargs):
|
||||||
|
|
||||||
self._assert_id_and_set_container()
|
|
||||||
count = self.acl_repo.get_count(self.container_id)
|
count = self.acl_repo.get_count(self.container_id)
|
||||||
if count == 0:
|
if count > 0:
|
||||||
_acls_not_found("container")
|
self.acl_repo.delete_acls_for_container(self.container)
|
||||||
|
|
||||||
self.acl_repo.delete_acls_for_container(self.container)
|
def _return_acl_list_response(self, container_id):
|
||||||
|
|
||||||
def _assert_id_and_set_container(self, suppress_exception=False):
|
|
||||||
"""Checks whether container_id is valid or not.
|
|
||||||
|
|
||||||
Whether container is associated with token's project is not needed as
|
|
||||||
that check is now made via policy rule.
|
|
||||||
"""
|
|
||||||
if self.container:
|
|
||||||
return
|
|
||||||
controllers.assert_is_valid_uuid_from_uri(self.container_id)
|
|
||||||
self.container = self.container_repo.get_container_by_id(
|
|
||||||
entity_id=self.container_id, suppress_exception=True)
|
|
||||||
if not self.container and not suppress_exception:
|
|
||||||
controllers.containers.container_not_found()
|
|
||||||
if self.container:
|
|
||||||
self.container_project_id = self.container.project.external_id
|
|
||||||
|
|
||||||
def _return_acl_hrefs(self, container_id):
|
|
||||||
result = self.acl_repo.get_by_container_id(container_id)
|
result = self.acl_repo.get_by_container_id(container_id)
|
||||||
|
|
||||||
if not result:
|
acls_data = {}
|
||||||
_acls_not_found("container")
|
if result:
|
||||||
else:
|
for acl in result:
|
||||||
acl_recs = [hrefs.convert_acl_to_hrefs(acl.to_dict_fields())
|
_convert_acl_to_response_format(acl, acls_data)
|
||||||
for acl in result]
|
if not acls_data:
|
||||||
return [{'acl_ref': acl['acl_ref']} for acl in acl_recs]
|
acls_data = DEFAULT_ACL.copy()
|
||||||
|
return acls_data
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class ContainerController(controllers.ACLMixin):
|
|||||||
self.validator = validators.ContainerValidator()
|
self.validator = validators.ContainerValidator()
|
||||||
self.consumers = consumers.ContainerConsumersController(
|
self.consumers = consumers.ContainerConsumersController(
|
||||||
self.container_id)
|
self.container_id)
|
||||||
self.acls = acls.ContainerACLsController(self.container_id)
|
self.acl = acls.ContainerACLsController(self.container)
|
||||||
|
|
||||||
def get_acl_tuple(self, req, **kwargs):
|
def get_acl_tuple(self, req, **kwargs):
|
||||||
d = self.get_acl_dict_for_user(req, self.container.container_acls)
|
d = self.get_acl_dict_for_user(req, self.container.container_acls)
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class SecretController(controllers.ACLMixin):
|
|||||||
|
|
||||||
@pecan.expose()
|
@pecan.expose()
|
||||||
def _lookup(self, sub_resource, *remainder):
|
def _lookup(self, sub_resource, *remainder):
|
||||||
if sub_resource == 'acls':
|
if sub_resource == 'acl':
|
||||||
return acls.SecretACLsController(self.secret), remainder
|
return acls.SecretACLsController(self.secret), remainder
|
||||||
else:
|
else:
|
||||||
pecan.abort(405) # only 'acl' as sub-resource is supported
|
pecan.abort(405) # only 'acl' as sub-resource is supported
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from barbican.common import utils
|
from barbican.common import utils
|
||||||
|
|
||||||
|
|
||||||
@@ -52,42 +51,6 @@ def convert_certificate_authority_to_href(ca_id):
|
|||||||
return convert_resource_id_to_href('cas', ca_id)
|
return convert_resource_id_to_href('cas', ca_id)
|
||||||
|
|
||||||
|
|
||||||
def convert_secret_acl_to_href(secret_id, acl_id):
|
|
||||||
"""Convert the secret acl ID to a HATEOS-style href."""
|
|
||||||
secret_href = convert_secret_to_href(secret_id)
|
|
||||||
return secret_href + '/acls/' + acl_id
|
|
||||||
|
|
||||||
|
|
||||||
def convert_container_acl_to_href(container_id, acl_id):
|
|
||||||
"""Convert the container acl ID to a HATEOS-style href."""
|
|
||||||
container_href = convert_container_to_href(container_id)
|
|
||||||
return container_href + '/acls/' + acl_id
|
|
||||||
|
|
||||||
|
|
||||||
def convert_acl_to_hrefs(fields):
|
|
||||||
acl_id = fields['acl_id']
|
|
||||||
if 'secret_id' in fields:
|
|
||||||
fields['acl_ref'] = convert_secret_acl_to_href(fields['secret_id'],
|
|
||||||
acl_id)
|
|
||||||
del fields['acl_id']
|
|
||||||
fields['secret_ref'] = convert_secret_to_href(fields['secret_id'])
|
|
||||||
del fields['secret_id']
|
|
||||||
|
|
||||||
if 'container_id' in fields:
|
|
||||||
fields['acl_ref'] = convert_container_acl_to_href(
|
|
||||||
fields['container_id'], acl_id)
|
|
||||||
del fields['acl_id']
|
|
||||||
fields['container_ref'] = convert_container_to_href(
|
|
||||||
fields['container_id'])
|
|
||||||
del fields['container_id']
|
|
||||||
|
|
||||||
if 'creator_only' in fields:
|
|
||||||
fields['creator-only'] = fields['creator_only']
|
|
||||||
del fields['creator_only']
|
|
||||||
|
|
||||||
return fields
|
|
||||||
|
|
||||||
|
|
||||||
# TODO(hgedikli) handle list of fields in here
|
# TODO(hgedikli) handle list of fields in here
|
||||||
def convert_to_hrefs(fields):
|
def convert_to_hrefs(fields):
|
||||||
"""Convert id's within a fields dict to HATEOS-style hrefs."""
|
"""Convert id's within a fields dict to HATEOS-style hrefs."""
|
||||||
|
|||||||
@@ -1726,6 +1726,7 @@ class SecretACLRepo(BaseRepo):
|
|||||||
session=None):
|
session=None):
|
||||||
session = self.get_session(session)
|
session = self.get_session(session)
|
||||||
secret.updated_at = timeutils.utcnow()
|
secret.updated_at = timeutils.utcnow()
|
||||||
|
secret_acl.updated_at = timeutils.utcnow()
|
||||||
secret.secret_acls.append(secret_acl)
|
secret.secret_acls.append(secret_acl)
|
||||||
secret.save(session=session)
|
secret.save(session=session)
|
||||||
|
|
||||||
@@ -1820,7 +1821,7 @@ class ContainerACLRepo(BaseRepo):
|
|||||||
user_ids=None, session=None):
|
user_ids=None, session=None):
|
||||||
session = self.get_session(session)
|
session = self.get_session(session)
|
||||||
container.updated_at = timeutils.utcnow()
|
container.updated_at = timeutils.utcnow()
|
||||||
|
container_acl.updated_at = timeutils.utcnow()
|
||||||
container.container_acls.append(container_acl)
|
container.container_acls.append(container_acl)
|
||||||
container.save(session=session)
|
container.save(session=session)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -489,7 +489,7 @@ class WhenCreatingStoredKeyOrders(utils.BarbicanAPIBaseTestCase,
|
|||||||
)
|
)
|
||||||
|
|
||||||
if add_acls:
|
if add_acls:
|
||||||
_, _ = test_acls.manage_acls(
|
test_acls.manage_acls(
|
||||||
self.app, 'containers', container_id,
|
self.app, 'containers', container_id,
|
||||||
read_user_ids=['u1', 'u3', 'u4'],
|
read_user_ids=['u1', 'u3', 'u4'],
|
||||||
read_creator_only=read_creator_only,
|
read_creator_only=read_creator_only,
|
||||||
|
|||||||
@@ -20,6 +20,10 @@
|
|||||||
"secret_non_private_read": "rule:all_users and rule:secret_project_match and not rule:secret_private_read",
|
"secret_non_private_read": "rule:all_users and rule:secret_project_match and not rule:secret_private_read",
|
||||||
"secret_decrypt_non_private_read": "rule:all_but_audit and rule:secret_project_match and not rule:secret_private_read",
|
"secret_decrypt_non_private_read": "rule:all_but_audit and rule:secret_project_match and not rule:secret_private_read",
|
||||||
"container_non_private_read": "rule:all_users and rule:container_project_match and not rule:container_private_read",
|
"container_non_private_read": "rule:all_users and rule:container_project_match and not rule:container_private_read",
|
||||||
|
"secret_project_admin": "rule:admin and rule:secret_project_match",
|
||||||
|
"secret_project_creator": "rule:creator_role and rule:secret_project_match and rule:secret_creator_user",
|
||||||
|
"container_project_admin": "rule:admin and rule:container_project_match",
|
||||||
|
"container_project_creator": "rule:creator_role and rule:container_project_match and rule:container_creator_user",
|
||||||
|
|
||||||
"version:get": "@",
|
"version:get": "@",
|
||||||
"secret:decrypt": "rule:secret_decrypt_non_private_read or rule:secret_creator_user or rule:secret_acl_read",
|
"secret:decrypt": "rule:secret_decrypt_non_private_read or rule:secret_creator_user or rule:secret_acl_read",
|
||||||
@@ -57,18 +61,10 @@
|
|||||||
"certificate_authority:unset_global_preferred": "rule:admin",
|
"certificate_authority:unset_global_preferred": "rule:admin",
|
||||||
"certificate_authority:get_global_preferred": "rule:all_users",
|
"certificate_authority:get_global_preferred": "rule:all_users",
|
||||||
"certificate_authority:get_preferred_ca": "rule:all_users",
|
"certificate_authority:get_preferred_ca": "rule:all_users",
|
||||||
"secret_acls:post": "rule:admin_or_creator_role and rule:secret_project_match",
|
"secret_acls:put_patch": "rule:secret_project_admin or rule:secret_project_creator",
|
||||||
"secret_acls:patch": "rule:admin_or_creator_role and rule:secret_project_match",
|
"secret_acls:delete": "rule:secret_project_admin or rule:secret_project_creator",
|
||||||
"secret_acls:delete": "rule:admin_or_creator_role and rule:secret_project_match",
|
|
||||||
"secret_acls:get": "rule:all_but_audit and rule:secret_project_match",
|
"secret_acls:get": "rule:all_but_audit and rule:secret_project_match",
|
||||||
"secret_acl:get": "rule:all_but_audit and rule:secret_project_match",
|
"container_acls:put_patch": "rule:container_project_admin or rule:container_project_creator",
|
||||||
"secret_acl:patch": "rule:admin_or_creator_role and rule:secret_project_match",
|
"container_acls:delete": "rule:container_project_admin or rule:container_project_creator",
|
||||||
"secret_acl:delete": "rule:admin_or_creator_role and rule:secret_project_match",
|
"container_acls:get": "rule:all_but_audit and rule:container_project_match"
|
||||||
"container_acls:post": "rule:admin_or_creator_role and rule:container_project_match",
|
|
||||||
"container_acls:patch": "rule:admin_or_creator_role and rule:container_project_match",
|
|
||||||
"container_acls:delete": "rule:admin_or_creator_role and rule:container_project_match",
|
|
||||||
"container_acls:get": "rule:all_but_audit and rule:container_project_match",
|
|
||||||
"container_acl:get": "rule:all_but_audit and rule:container_project_match",
|
|
||||||
"container_acl:patch": "rule:admin_or_creator_role and rule:container_project_match",
|
|
||||||
"container_acl:delete": "rule:admin_or_creator_role and rule:container_project_match"
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user