# 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 exception from barbican.common import hrefs from barbican.common import quota from barbican.common import resources as res 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 _consumer_not_found(): """Throw exception indicating consumer not found.""" pecan.abort(404, u._('Consumer not found.')) def _consumer_ownership_mismatch(): """Throw exception indicating the user does not own this consumer.""" pecan.abort(403, u._('Not Allowed. Sorry, only the creator of a consumer ' 'can delete it.')) def _invalid_consumer_id(): """Throw exception indicating consumer id is invalid.""" pecan.abort(404, u._('Not Found. Provided consumer id is invalid.')) class ContainerConsumerController(controllers.ACLMixin): """Handles Container Consumer entity retrieval and deletion requests""" def __init__(self, consumer_id): self.consumer_id = consumer_id self.consumer_repo = repo.get_container_consumer_repository() self.validator = validators.ContainerConsumerValidator() @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._('ContainerConsumer retrieval')) @controllers.enforce_rbac('consumer:get') def on_get(self, external_project_id): consumer = self.consumer_repo.get( entity_id=self.consumer_id, suppress_exception=True) if not consumer: _consumer_not_found() dict_fields = consumer.to_dict_fields() LOG.info('Retrieved a consumer for project: %s', external_project_id) return hrefs.convert_to_hrefs( hrefs.convert_to_hrefs(dict_fields) ) class ContainerConsumersController(controllers.ACLMixin): """Handles Container Consumer creation requests""" def __init__(self, container_id): self.container_id = container_id self.consumer_repo = repo.get_container_consumer_repository() self.container_repo = repo.get_container_repository() self.project_repo = repo.get_project_repository() self.validator = validators.ContainerConsumerValidator() self.quota_enforcer = quota.QuotaEnforcer('consumers', self.consumer_repo) @pecan.expose() def _lookup(self, consumer_id, *remainder): if not utils.validate_id_is_uuid(consumer_id): _invalid_consumer_id()() return ContainerConsumerController(consumer_id), remainder @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._('ContainerConsumers(s) retrieval')) @controllers.enforce_rbac('consumers:get') def on_get(self, external_project_id, **kw): LOG.debug('Start consumers on_get ' 'for container-ID %s:', self.container_id) result = self.consumer_repo.get_by_container_id( self.container_id, offset_arg=kw.get('offset', 0), limit_arg=kw.get('limit'), suppress_exception=True ) consumers, offset, limit, total = result if not consumers: resp_ctrs_overall = {'consumers': [], 'total': total} else: resp_ctrs = [ hrefs.convert_to_hrefs(c.to_dict_fields()) for c in consumers ] consumer_path = "containers/{container_id}/consumers".format( container_id=self.container_id) resp_ctrs_overall = hrefs.add_nav_hrefs( consumer_path, offset, limit, total, {'consumers': resp_ctrs} ) resp_ctrs_overall.update({'total': total}) LOG.info('Retrieved a container consumer list for project: %s', external_project_id) return resp_ctrs_overall @index.when(method='POST', template='json') @controllers.handle_exceptions(u._('ContainerConsumer creation')) @controllers.enforce_rbac('consumers:post') @controllers.enforce_content_types(['application/json']) def on_post(self, external_project_id, **kwargs): project = res.get_or_create_project(external_project_id) 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) url = hrefs.convert_consumer_to_href(new_consumer.container_id) pecan.response.headers['Location'] = url LOG.info('Created a container consumer for project: %s', external_project_id) return self._return_container_data(self.container_id) @index.when(method='DELETE', template='json') @controllers.handle_exceptions(u._('ContainerConsumer deletion')) @controllers.enforce_rbac('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() consumer = self.consumer_repo.get_by_values( self.container_id, data["name"], data["URL"], suppress_exception=True ) if not consumer: _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 \ == external_project_id if not owner_of_consumer and not owner_of_container: _consumer_ownership_mismatch() try: self.consumer_repo.delete_entity_by_id(consumer.id, external_project_id) except exception.NotFound: LOG.exception('Problem deleting container consumer') _consumer_not_found() ret_data = self._return_container_data(self.container_id) 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() for secret_ref in dict_fields['secret_refs']: hrefs.convert_to_hrefs(secret_ref) # TODO(john-wood-w) Why two calls to convert_to_hrefs()? return hrefs.convert_to_hrefs( hrefs.convert_to_hrefs(dict_fields) ) class SecretConsumerController(controllers.ACLMixin): """Handles Secret Consumer entity retrieval and deletion requests""" def __init__(self, consumer_id): self.consumer_id = consumer_id self.consumer_repo = repo.get_secret_consumer_repository() self.validator = validators.SecretConsumerValidator() @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._('SecretConsumer retrieval')) @controllers.enforce_rbac('consumer:get') def on_get(self, external_project_id): consumer = self.consumer_repo.get( entity_id=self.consumer_id, suppress_exception=True) if not consumer: _consumer_not_found() dict_fields = consumer.to_dict_fields() LOG.info('Retrieved a secret consumer for project: %s', external_project_id) return hrefs.convert_to_hrefs( hrefs.convert_to_hrefs(dict_fields) ) class SecretConsumersController(controllers.ACLMixin): """Handles Secret Consumer creation requests""" def __init__(self, secret_id): 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() self.validator = validators.SecretConsumerValidator() self.quota_enforcer = quota.QuotaEnforcer('consumers', self.consumer_repo) @pecan.expose() def _lookup(self, consumer_id, *remainder): if not utils.validate_id_is_uuid(consumer_id): _invalid_consumer_id()() return SecretConsumerController(consumer_id), remainder @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._('SecretConsumers(s) retrieval')) @controllers.enforce_rbac('consumers:get') def on_get(self, external_project_id, **kw): LOG.debug('Start consumers on_get ' 'for secret-ID %s:', self.secret_id) result = self.consumer_repo.get_by_secret_id( self.secret_id, offset_arg=kw.get('offset', 0), limit_arg=kw.get('limit'), suppress_exception=True ) consumers, offset, limit, total = result if not consumers: resp_ctrs_overall = {'consumers': [], 'total': total} else: resp_ctrs = [ hrefs.convert_to_hrefs(c.to_dict_fields()) for c in consumers ] consumer_path = "secrets/{secret_id}/consumers".format( secret_id=self.secret_id) resp_ctrs_overall = hrefs.add_nav_hrefs( consumer_path, offset, limit, total, {'consumers': resp_ctrs} ) resp_ctrs_overall.update({'total': total}) LOG.info('Retrieved a consumer list for project: %s', external_project_id) return resp_ctrs_overall @index.when(method='POST', template='json') @controllers.handle_exceptions(u._('SecretConsumer creation')) @controllers.enforce_rbac('consumers:post') @controllers.enforce_content_types(['application/json']) def on_post(self, external_project_id, **kwargs): project = res.get_or_create_project(external_project_id) 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( self.secret_id, project.id, data["service"], data["resource_type"], data["resource_id"], ) self.consumer_repo.create_or_update_from(new_consumer, secret) url = hrefs.convert_consumer_to_href(new_consumer.secret_id) pecan.response.headers['Location'] = url LOG.info('Created a consumer for project: %s', external_project_id) return self._return_secret_data(self.secret_id) @index.when(method='DELETE', template='json') @controllers.handle_exceptions(u._('SecretConsumer deletion')) @controllers.enforce_rbac('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() consumer = self.consumer_repo.get_by_values( self.secret_id, data["resource_id"], suppress_exception=True ) if not consumer: _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 \ == external_project_id if not owner_of_consumer and not owner_of_secret: _consumer_ownership_mismatch() try: self.consumer_repo.delete_entity_by_id(consumer.id, external_project_id) except exception.NotFound: LOG.exception('Problem deleting consumer') _consumer_not_found() ret_data = self._return_secret_data(self.secret_id) LOG.info('Deleted a consumer for project: %s', external_project_id) return ret_data def _get_secret(self, secret_id): secret = self.secret_repo.get_secret_by_id( secret_id, suppress_exception=True) if not secret: controllers.secrets.secret_not_found() return secret def _return_secret_data(self, secret_id): secret = self._get_secret(secret_id) dict_fields = secret.to_dict_fields() return hrefs.convert_to_hrefs( hrefs.convert_to_hrefs(dict_fields) )