376 lines
15 KiB
Python
376 lines
15 KiB
Python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
|
|
import pecan
|
|
|
|
from barbican import api
|
|
from barbican.api import controllers
|
|
from barbican.common import hrefs
|
|
from barbican.common import utils
|
|
from barbican.common import validators
|
|
from barbican import i18n as u
|
|
from barbican.model import models
|
|
from barbican.model import repositories as repo
|
|
|
|
LOG = utils.getLogger(__name__)
|
|
|
|
|
|
def _convert_acl_to_response_format(acl, acls_dict):
|
|
fields = acl.to_dict_fields()
|
|
operation = fields['operation']
|
|
|
|
acl_data = {} # dict for each acl operation data
|
|
|
|
acl_data['project-access'] = fields['project_access']
|
|
acl_data['users'] = fields.get('users', [])
|
|
acl_data['created'] = fields['created']
|
|
acl_data['updated'] = fields['updated']
|
|
|
|
acls_dict[operation] = acl_data
|
|
|
|
|
|
DEFAULT_ACL = {'read': {'project-access': True}}
|
|
|
|
|
|
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()
|
|
self.validator = validators.ACLValidator()
|
|
|
|
def get_acl_tuple(self, req, **kwargs):
|
|
d = {'project_id': self.secret_project_id,
|
|
'creator_id': self.secret.creator_id}
|
|
return 'secret', d
|
|
|
|
@pecan.expose(generic=True)
|
|
def index(self, **kwargs):
|
|
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
|
|
|
@index.when(method='GET', template='json')
|
|
@controllers.handle_exceptions(u._('SecretACL(s) retrieval'))
|
|
@controllers.enforce_rbac('secret_acls:get')
|
|
def on_get(self, external_project_id, **kw):
|
|
LOG.debug('Start secret ACL on_get '
|
|
'for secret-ID %s:', self.secret.id)
|
|
|
|
return self._return_acl_list_response(self.secret.id)
|
|
|
|
@index.when(method='PATCH', template='json')
|
|
@controllers.handle_exceptions(u._('SecretACL(s) Update'))
|
|
@controllers.enforce_rbac('secret_acls:put_patch')
|
|
@controllers.enforce_content_types(['application/json'])
|
|
def on_patch(self, external_project_id, **kwargs):
|
|
"""Handles update of existing secret acl requests.
|
|
|
|
At least one secret ACL needs to exist for update to proceed.
|
|
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"
|
|
],
|
|
"project-access":true
|
|
}
|
|
}
|
|
"""
|
|
data = api.load_body(pecan.request, validator=self.validator)
|
|
LOG.debug('Start on_patch...%s', data)
|
|
|
|
existing_acls_map = {acl.operation: acl for acl in
|
|
self.secret.secret_acls}
|
|
for operation in filter(lambda x: data.get(x),
|
|
validators.ACL_OPERATIONS):
|
|
project_access = data[operation].get('project-access')
|
|
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[operation]
|
|
if project_access is not None:
|
|
s_acl.project_access = project_access
|
|
else:
|
|
s_acl = models.SecretACL(self.secret.id, operation=operation,
|
|
project_access=project_access)
|
|
self.acl_repo.create_or_replace_from(self.secret, secret_acl=s_acl,
|
|
user_ids=user_ids)
|
|
|
|
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 project-access in ACL, true 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"
|
|
],
|
|
"project-access":false
|
|
}
|
|
}
|
|
|
|
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 filter(lambda x: data.get(x),
|
|
validators.ACL_OPERATIONS):
|
|
project_access = data[operation].get('project-access', True)
|
|
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.project_access = project_access
|
|
else:
|
|
s_acl = models.SecretACL(self.secret.id, operation=operation,
|
|
project_access=project_access)
|
|
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 existing_acls_map.values():
|
|
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')
|
|
@controllers.handle_exceptions(u._('SecretACL(s) deletion'))
|
|
@controllers.enforce_rbac('secret_acls:delete')
|
|
def on_delete(self, external_project_id, **kwargs):
|
|
|
|
count = self.acl_repo.get_count(self.secret.id)
|
|
if count > 0:
|
|
self.acl_repo.delete_acls_for_secret(self.secret)
|
|
|
|
def _return_acl_list_response(self, secret_id):
|
|
result = self.acl_repo.get_by_secret_id(secret_id)
|
|
|
|
acls_data = {}
|
|
if result:
|
|
for acl in result:
|
|
_convert_acl_to_response_format(acl, acls_data)
|
|
if not acls_data:
|
|
acls_data = DEFAULT_ACL.copy()
|
|
return acls_data
|
|
|
|
|
|
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()
|
|
self.container_repo = repo.get_container_repository()
|
|
self.validator = validators.ACLValidator()
|
|
self.container_project_id = container.project.external_id
|
|
|
|
def get_acl_tuple(self, req, **kwargs):
|
|
d = {'project_id': self.container_project_id,
|
|
'creator_id': self.container.creator_id}
|
|
return 'container', d
|
|
|
|
@pecan.expose(generic=True)
|
|
def index(self, **kwargs):
|
|
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
|
|
|
@index.when(method='GET', template='json')
|
|
@controllers.handle_exceptions(u._('ContainerACL(s) retrieval'))
|
|
@controllers.enforce_rbac('container_acls:get')
|
|
def on_get(self, external_project_id, **kw):
|
|
LOG.debug('Start container ACL on_get '
|
|
'for container-ID %s:', self.container_id)
|
|
|
|
return self._return_acl_list_response(self.container.id)
|
|
|
|
@index.when(method='PATCH', template='json')
|
|
@controllers.handle_exceptions(u._('ContainerACL(s) Update'))
|
|
@controllers.enforce_rbac('container_acls:put_patch')
|
|
@controllers.enforce_content_types(['application/json'])
|
|
def on_patch(self, external_project_id, **kwargs):
|
|
"""Handles update of existing container acl requests.
|
|
|
|
At least one container ACL needs to exist for update to proceed.
|
|
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"
|
|
],
|
|
"project-access":false
|
|
}
|
|
}
|
|
"""
|
|
data = api.load_body(pecan.request, validator=self.validator)
|
|
LOG.debug('Start ContainerACLsController on_patch...%s', data)
|
|
|
|
existing_acls_map = {acl.operation: acl for acl in
|
|
self.container.container_acls}
|
|
for operation in filter(lambda x: data.get(x),
|
|
validators.ACL_OPERATIONS):
|
|
project_access = data[operation].get('project-access')
|
|
user_ids = data[operation].get('users')
|
|
if operation in existing_acls_map: # update if matching acl exists
|
|
c_acl = existing_acls_map[operation]
|
|
if project_access is not None:
|
|
c_acl.project_access = project_access
|
|
else:
|
|
c_acl = models.ContainerACL(self.container.id,
|
|
operation=operation,
|
|
project_access=project_access)
|
|
self.acl_repo.create_or_replace_from(self.container,
|
|
container_acl=c_acl,
|
|
user_ids=user_ids)
|
|
|
|
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 project-access in ACL, true 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"
|
|
],
|
|
"project-access":false
|
|
}
|
|
}
|
|
|
|
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 filter(lambda x: data.get(x),
|
|
validators.ACL_OPERATIONS):
|
|
project_access = data[operation].get('project-access', True)
|
|
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.project_access = project_access
|
|
else:
|
|
c_acl = models.ContainerACL(self.container.id,
|
|
operation=operation,
|
|
project_access=project_access)
|
|
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 existing_acls_map.values():
|
|
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')
|
|
@controllers.handle_exceptions(u._('ContainerACL(s) deletion'))
|
|
@controllers.enforce_rbac('container_acls:delete')
|
|
def on_delete(self, external_project_id, **kwargs):
|
|
count = self.acl_repo.get_count(self.container_id)
|
|
if count > 0:
|
|
self.acl_repo.delete_acls_for_container(self.container)
|
|
|
|
def _return_acl_list_response(self, container_id):
|
|
result = self.acl_repo.get_by_container_id(container_id)
|
|
|
|
acls_data = {}
|
|
if result:
|
|
for acl in result:
|
|
_convert_acl_to_response_format(acl, acls_data)
|
|
if not acls_data:
|
|
acls_data = DEFAULT_ACL.copy()
|
|
return acls_data
|