diff --git a/barbican/api/app.py b/barbican/api/app.py index 407b08f8d..afa10605f 100644 --- a/barbican/api/app.py +++ b/barbican/api/app.py @@ -51,6 +51,8 @@ def create_main_app(global_config, **local_conf): secret = res.SecretResource(crypto_mgr) orders = res.OrdersResource() order = res.OrderResource() + verifications = res.VerificationsResource() + verification = res.VerificationResource() # For performance testing only performance = res.PerformanceResource() @@ -65,6 +67,9 @@ def create_main_app(global_config, **local_conf): api.add_route('/v1/{keystone_id}/secrets/{secret_id}', secret) api.add_route('/v1/{keystone_id}/orders', orders) api.add_route('/v1/{keystone_id}/orders/{order_id}', order) + api.add_route('/v1/{keystone_id}/verifications', verifications) + api.add_route('/v1/{keystone_id}/verifications/{verification_id}', + verification) # For performance testing only api.add_route('/{0}'.format(performance_uri), performance) diff --git a/barbican/api/resources.py b/barbican/api/resources.py index 91604fe40..dd05b5056 100644 --- a/barbican/api/resources.py +++ b/barbican/api/resources.py @@ -52,6 +52,13 @@ def _order_not_found(req, resp): 'another castle.'), req, resp) +def _verification_not_found(req, resp): + """Throw exception indicating verification not found.""" + api.abort(falcon.HTTP_404, u._('Not Found. Sorry but your verification ' + 'result is in ' + 'another castle.'), req, resp) + + def _put_accept_incorrect(ct, req, resp): """Throw exception indicating request content-type is not supported.""" api.abort(falcon.HTTP_415, @@ -86,6 +93,15 @@ def convert_secret_to_href(keystone_id, secret_id): return utils.hostname_for_refs(keystone_id=keystone_id, resource=resource) +def convert_verification_to_href(keystone_id, verification_id): + """Convert the tenant/verification IDs to a HATEOS-style href.""" + if verification_id: + resource = 'verifications/' + verification_id + else: + resource = 'verifications/????' + return utils.hostname_for_refs(keystone_id=keystone_id, resource=resource) + + def convert_order_to_href(keystone_id, order_id): """Convert the tenant/order IDs to a HATEOS-style href.""" if order_id: @@ -105,6 +121,11 @@ def convert_to_hrefs(keystone_id, fields): fields['order_ref'] = convert_order_to_href(keystone_id, fields['order_id']) del fields['order_id'] + if 'verification_id' in fields: + fields['verification_ref'] = \ + convert_verification_to_href(keystone_id, + fields['verification_id']) + del fields['verification_id'] return fields @@ -537,3 +558,105 @@ class OrderResource(api.ApiResource): _order_not_found(req, resp) resp.status = falcon.HTTP_200 + + +class VerificationsResource(api.ApiResource): + """Handles Verification creation requests. + + Creating a verification entity initiates verification processing + on a target resource. The results of this verification processing + can be monitored via this entity. + """ + + def __init__(self, tenant_repo=None, verification_repo=None, + queue_resource=None): + self.tenant_repo = tenant_repo or repo.TenantRepo() + self.verification_repo = verification_repo or repo.VerificationRepo() + self.validator = validators.VerificationValidator() + self.queue = queue_resource or queue.get_queue_api() + + @handle_exceptions(u._('Verification creation')) + @handle_rbac('verifications:post') + def on_post(self, req, resp, keystone_id): + LOG.debug('Start on_post for tenant-ID {0}:...'.format(keystone_id)) + + data = api.load_body(req, resp, self.validator) + tenant = res.get_or_create_tenant(keystone_id, self.tenant_repo) + + new_verification = models.Verification(data) + new_verification.tenant_id = tenant.id + self.verification_repo.create_from(new_verification) + + # Send to workers to process. + self.queue.process_verification(verification_id=new_verification.id, + keystone_id=keystone_id) + + resp.status = falcon.HTTP_202 + resp.set_header('Location', + '/{0}/verifications/{1}'.format(keystone_id, + new_verification.id)) + url = convert_verification_to_href(keystone_id, new_verification.id) + LOG.debug('URI to verification is {0}'.format(url)) + resp.body = json.dumps({'verification_ref': url}) + + @handle_exceptions(u._('Verification(s) retrieval')) + @handle_rbac('verifications:get') + def on_get(self, req, resp, keystone_id): + LOG.debug('Start verifications on_get ' + 'for tenant-ID {0}:'.format(keystone_id)) + + result = self.verification_repo.get_by_create_date( + keystone_id, + offset_arg=req.get_param('offset'), + limit_arg=req.get_param('limit'), + suppress_exception=True + ) + + verifications, offset, limit, total = result + + if not verifications: + resp_overall = {'verifications': [], 'total': total} + else: + resp = [convert_to_hrefs(keystone_id, v.to_dict_fields()) for + v in verifications] + resp_overall = add_nav_hrefs('verifications', keystone_id, + offset, limit, total, + {'verifications': resp}) + resp_overall.update({'total': total}) + + resp.status = falcon.HTTP_200 + resp.body = json.dumps(resp_overall, + default=json_handler) + + +class VerificationResource(api.ApiResource): + """Handles Verification entity retrieval and deletion requests.""" + + def __init__(self, verification_repo=None): + self.repo = verification_repo or repo.VerificationRepo() + + @handle_exceptions(u._('Verification retrieval')) + @handle_rbac('verification:get') + def on_get(self, req, resp, keystone_id, verification_id): + verif = self.repo.get(entity_id=verification_id, + keystone_id=keystone_id, + suppress_exception=True) + if not verif: + _verification_not_found(req, resp) + + resp.status = falcon.HTTP_200 + resp.body = json.dumps(convert_to_hrefs(keystone_id, + verif.to_dict_fields()), + default=json_handler) + + @handle_exceptions(u._('Verification deletion')) + @handle_rbac('verification:delete') + def on_delete(self, req, resp, keystone_id, verification_id): + + try: + self.repo.delete_entity_by_id(entity_id=verification_id, + keystone_id=keystone_id) + except exception.NotFound: + _verification_not_found(req, resp) + + resp.status = falcon.HTTP_200 diff --git a/barbican/common/resources.py b/barbican/common/resources.py index b9195a558..e8ac63918 100644 --- a/barbican/common/resources.py +++ b/barbican/common/resources.py @@ -53,7 +53,7 @@ def create_secret(data, tenant, crypto_manager, time_keeper.mark('after Secret model create') new_datum = None content_type = data.get('payload_content_type', - 'application/octet-stream') # TODO: Add to Order! + 'application/octet-stream') if 'payload' in data: payload = data.get('payload') diff --git a/barbican/common/validators.py b/barbican/common/validators.py index a335acfc1..2c1af7994 100644 --- a/barbican/common/validators.py +++ b/barbican/common/validators.py @@ -244,3 +244,41 @@ class NewOrderValidator(ValidatorBase): "multiple of 8")) return json_data + + +class VerificationValidator(ValidatorBase): + """Validate a verification resource request.""" + + def __init__(self): + self.name = 'Verification' + self.schema = { + "type": "object", + "required": ["resource_type", "resource_ref", + "resource_action", "impersonation_allowed"], + "properties": { + "resource_type": { + "type": "string", + "enum": [ + "image" + ] + }, + "resource_ref": {"type": "string"}, + "resource_action": { + "type": "string", + "enum": [ + "vm_attach" + ] + }, + "impersonation_allowed": {"type": "boolean"}, + }, + } + + def validate(self, json_data, parent_schema=None): + schema_name = self._full_name(parent_schema) + + try: + schema.validate(json_data, self.schema) + except schema.ValidationError as e: + raise exception.InvalidObject(schema=schema_name, reason=str(e)) + + return json_data diff --git a/barbican/common/verifications.py b/barbican/common/verifications.py new file mode 100644 index 000000000..0c53d0936 --- /dev/null +++ b/barbican/common/verifications.py @@ -0,0 +1,37 @@ +# Copyright (c) 2013 Rackspace, Inc. +# +# 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. + +""" +Resource verification business logic. +""" +from barbican.common import utils + + +LOG = utils.getLogger(__name__) + + +def verify(verification): + """Verifies if a resource is 'valid' for an action or not. + + Based on the target resource information in the supplied verification + entity this function determines if it is valid to use for the specified + action. The supplied entity is then updated with the processing result. + + :param verification: A Verification entity + """ + if 'image' == verification.resource_type: + #TODO(jfwood) Add rules or else consider a plugin approach similar to + # barbican/crypto/plugin.py. + verification.is_verified = True diff --git a/barbican/model/models.py b/barbican/model/models.py index adbbfee4b..3fc509edc 100644 --- a/barbican/model/models.py +++ b/barbican/model/models.py @@ -164,6 +164,7 @@ class Tenant(BASE, ModelBase): keystone_id = sa.Column(sa.String(255), unique=True) orders = orm.relationship("Order", backref="tenant") + verifications = orm.relationship("Verification", backref="tenant") secrets = orm.relationship("TenantSecret", backref="tenants") keks = orm.relationship("KEKDatum", backref="tenant") @@ -196,15 +197,16 @@ class Secret(BASE, ModelBase): # See barbican.api.resources.py::SecretsResource.on_get() encrypted_data = orm.relationship("EncryptedDatum", lazy='joined') - def __init__(self, parsed_request): + def __init__(self, parsed_request=None): """Creates secret from a dict.""" super(Secret, self).__init__() - self.name = parsed_request.get('name') - self.expiration = parsed_request.get('expiration') - self.algorithm = parsed_request.get('algorithm') - self.bit_length = parsed_request.get('bit_length') - self.mode = parsed_request.get('mode') + if parsed_request: + self.name = parsed_request.get('name') + self.expiration = parsed_request.get('expiration') + self.algorithm = parsed_request.get('algorithm') + self.bit_length = parsed_request.get('bit_length') + self.mode = parsed_request.get('mode') self.status = States.ACTIVE @@ -346,8 +348,58 @@ class Order(BASE, ModelBase): return ret +class Verification(BASE, ModelBase): + """Represents a Verification result in the datastore. + + Verification represent that status of resource verification requests + made by Tenants. + """ + + __tablename__ = 'verifications' + + tenant_id = sa.Column(sa.String(36), sa.ForeignKey('tenants.id'), + nullable=False) + + error_status_code = sa.Column(sa.String(16)) + error_reason = sa.Column(sa.String(255)) + + resource_type = sa.Column(sa.String(255), nullable=False) + resource_ref = sa.Column(sa.String(255), nullable=False) + resource_action = sa.Column(sa.String(255), nullable=False) + impersonation_allowed = sa.Column(sa.Boolean, nullable=False, + default=True) + is_verified = sa.Column(sa.Boolean, nullable=False, + default=False) + + def __init__(self, parsed_request=None): + """Creates a Verification entity from a dict.""" + super(Verification, self).__init__() + + if parsed_request: + self.resource_type = parsed_request.get('resource_type') + self.resource_ref = parsed_request.get('resource_ref') + self.resource_action = parsed_request.get('resource_action') + self.impersonation_allowed = parsed_request.get('impersonation_' + 'allowed') + + self.status = States.PENDING + + def _do_extra_dict_fields(self): + """Sub-class hook method: return dict of fields.""" + ret = {'verification_id': self.id, + 'resource_type': self.resource_type, + 'resource_ref': self.resource_ref, + 'resource_action': self.resource_action, + 'impersonation_allowed': self.impersonation_allowed, + 'is_verified': self.is_verified} + if self.error_status_code: + ret['error_status_code'] = self.error_status_code + if self.error_reason: + ret['error_reason'] = self.error_reason + return ret + # Keep this tuple synchronized with the models in the file -MODELS = [TenantSecret, Tenant, Secret, EncryptedDatum, Order] +MODELS = [TenantSecret, Tenant, Secret, EncryptedDatum, Order, Verification] def register_models(engine): diff --git a/barbican/model/repositories.py b/barbican/model/repositories.py index 6b36096f6..800583797 100644 --- a/barbican/model/repositories.py +++ b/barbican/model/repositories.py @@ -228,7 +228,7 @@ def clean_paging_values(offset_arg=0, limit_arg=CONF.default_limit_paging): limit, offset )) - return (offset, limit) + return offset, limit class BaseRepo(object): @@ -720,3 +720,63 @@ class OrderRepo(BaseRepo): def _do_validate(self, values): """Sub-class hook: validate values.""" pass + + +class VerificationRepo(BaseRepo): + """Repository for the Verification entity.""" + + def get_by_create_date(self, keystone_id, offset_arg=None, limit_arg=None, + suppress_exception=False, session=None): + """ + Returns a list of verifications, ordered by the date they were + created at and paged based on the offset and limit fields. The + keystone_id is external-to-Barbican value assigned to the tenant + by Keystone. + """ + + offset, limit = clean_paging_values(offset_arg, limit_arg) + + session = self.get_session(session) + + try: + query = session.query(models.Verification) \ + .order_by(models.Verification.created_at) + query = query.filter_by(deleted=False) \ + .join(models.Tenant, models.Verification.tenant) \ + .filter(models.Tenant.keystone_id == keystone_id) + + start = offset + end = offset + limit + LOG.debug('Retrieving from {0} to {1}'.format(start, end)) + total = query.count() + entities = query[start:end] + LOG.debug('Number entities retrieved: {0} out of {1}'.format( + len(entities), total + )) + + except sa_orm.exc.NoResultFound: + entities = None + total = 0 + if not suppress_exception: + raise exception.NotFound("No %s's found" + % (self._do_entity_name())) + + return entities, offset, limit, total + + def _do_entity_name(self): + """Sub-class hook: return entity name, such as for debugging.""" + return "Verification" + + def _do_create_instance(self): + return models.Verification() + + def _do_build_get_query(self, entity_id, keystone_id, session): + """Sub-class hook: build a retrieve query.""" + return session.query(models.Verification).filter_by(id=entity_id) \ + .filter_by(deleted=False) \ + .join(models.Tenant, models.Verification.tenant) \ + .filter(models.Tenant.keystone_id == keystone_id) + + def _do_validate(self, values): + """Sub-class hook: validate values.""" + pass diff --git a/barbican/queue/celery/resources.py b/barbican/queue/celery/resources.py index dbbb758a0..d00031a2d 100644 --- a/barbican/queue/celery/resources.py +++ b/barbican/queue/celery/resources.py @@ -19,7 +19,7 @@ Celery Queue Resources related objects and functions. from celery import Celery from oslo.config import cfg -from barbican.tasks.resources import BeginOrder +from barbican.tasks import resources from barbican.common import utils @@ -44,7 +44,6 @@ CONF.import_opt('debug', 'barbican.openstack.common.log') # the bin/barbican-worker to boot up a Celery worker server instance. celery = Celery(CONF.celery.project, broker=CONF.celery.broker, - # backend='amqp://', include=[CONF.celery.include]) @@ -53,9 +52,22 @@ def process_order(order_id, keystone_id): return process_order_wrapper.delay(order_id, keystone_id) +def process_verification(verification_id, keystone_id): + """Process Verification.""" + return process_verification_wrapper.delay(verification_id, keystone_id) + + @celery.task def process_order_wrapper(order_id, keystone_id): """(Celery wrapped task) Process Order.""" LOG.debug('Order id is {0}'.format(order_id)) - task = BeginOrder() + task = resources.BeginOrder() return task.process(order_id, keystone_id) + + +@celery.task +def process_verification_wrapper(verification_id, keystone_id): + """(Celery wrapped task) Process Verification.""" + LOG.debug('Verification id is {0}'.format(verification_id)) + task = resources.PerformVerification() + return task.process(verification_id, keystone_id) diff --git a/barbican/queue/simple/resources.py b/barbican/queue/simple/resources.py index 5ac78366b..2efe8b739 100644 --- a/barbican/queue/simple/resources.py +++ b/barbican/queue/simple/resources.py @@ -18,7 +18,7 @@ Simple Queue Resources related objects and functions, making direct calls to the worker tasks. """ from oslo.config import cfg -from barbican.tasks.resources import BeginOrder +from barbican.tasks import resources from barbican.common import utils LOG = utils.getLogger(__name__) @@ -29,9 +29,21 @@ CONF = cfg.CONF def process_order(order_id, keystone_id): """Process Order.""" LOG.debug('Order id is {0}'.format(order_id)) - task = BeginOrder() + task = resources.BeginOrder() try: task.process(order_id, keystone_id) except Exception: LOG.exception(">>>>> Task exception seen, but simulating async " "reporting via the Orders entity on the worker side.") + + +def process_verification(verification_id, keystone_id): + """Process Verification.""" + LOG.debug('Verification id is {0}'.format(verification_id)) + task = resources.PerformVerification() + try: + task.process(verification_id, keystone_id) + except Exception: + LOG.exception(">>>>> Task exception seen, but simulating async " + "reporting via the Verification entity on the " + "worker side.") diff --git a/barbican/tasks/resources.py b/barbican/tasks/resources.py index 28da37b08..da2764b8f 100644 --- a/barbican/tasks/resources.py +++ b/barbican/tasks/resources.py @@ -21,6 +21,7 @@ import abc from barbican import api from barbican.common import resources as res from barbican.common import utils +from barbican.common import verifications as ver from barbican.crypto import extension_manager as em from barbican.model import models from barbican.model import repositories as rep @@ -195,3 +196,44 @@ class BeginOrder(BaseTask): order.secret_id = new_secret.id LOG.debug("...done creating order's secret.") + + +class PerformVerification(BaseTask): + """Handles beginning processing a Verification request.""" + + def get_name(self): + return u._('Perform Verification') + + def __init__(self, verification_repo=None): + LOG.debug('Creating PerformVerification task processor') + self.verification_repo = verification_repo or rep.VerificationRepo() + + def retrieve_entity(self, verification_id, keystone_id): + return self.verification_repo.get(entity_id=verification_id, + keystone_id=keystone_id) + + def handle_processing(self, verification, *args, **kwargs): + self.handle_verification(verification) + + def handle_error(self, verification, status, message, exception, + *args, **kwargs): + verification.status = models.States.ERROR + verification.error_status_code = status + verification.error_reason = message + self.verification_repo.save(verification) + + def handle_success(self, verification, *args, **kwargs): + verification.status = models.States.ACTIVE + self.verification_repo.save(verification) + + def handle_verification(self, verification): + """Handle performing a verification. + + Performs a verification process on a reference. + + :param verification: Verification to process on behalf of. + """ + # Perform the verification. + ver.verify(verification) + + LOG.debug("...done verifying resource.") diff --git a/barbican/tests/api/test_resources.py b/barbican/tests/api/test_resources.py index 2bff9502c..7516ef721 100644 --- a/barbican/tests/api/test_resources.py +++ b/barbican/tests/api/test_resources.py @@ -913,13 +913,15 @@ class WhenCreatingOrdersUsingOrdersResource(unittest.TestCase): def test_should_add_new_order(self): self.resource.on_post(self.req, self.resp, self.tenant_keystone_id) + self.assertEquals(falcon.HTTP_202, self.resp.status) + self.queue_resource.process_order \ .assert_called_once_with(order_id=None, keystone_id=self.tenant_keystone_id) args, kwargs = self.order_repo.create_from.call_args order = args[0] - self.assertTrue(isinstance(order, models.Order)) + self.assertIsInstance(order, models.Order) def test_should_fail_add_new_order_no_secret(self): self.stream.read.return_value = '{}' @@ -1142,3 +1144,156 @@ class WhenAddingNavigationHrefs(unittest.TestCase): self.assertIn('previous', data_with_hrefs) self.assertNotIn('next', data_with_hrefs) + + +class WhenCreatingVerificationsUsingVerificationsResource(unittest.TestCase): + def setUp(self): + self.resource_type = 'image' + self.resource_ref = 'http://www.images.com/v1/images/12345' + self.resource_action = 'vm_attach' + self.impersonation = True + + self.tenant_internal_id = 'tenantid1234' + self.tenant_keystone_id = 'keystoneid1234' + + self.tenant = models.Tenant() + self.tenant.id = self.tenant_internal_id + self.tenant.keystone_id = self.tenant_keystone_id + + self.tenant_repo = mock.MagicMock() + self.tenant_repo.get.return_value = self.tenant + + self.verification_repo = mock.MagicMock() + self.verification_repo.create_from.return_value = None + + self.queue_resource = mock.MagicMock() + self.queue_resource.process_verification.return_value = None + + self.stream = mock.MagicMock() + + self.verify_req = {'resource_type': self.resource_type, + 'resource_ref': self.resource_ref, + 'resource_action': self.resource_action, + 'impersonation_allowed': self.impersonation} + self.json = json.dumps(self.verify_req) + self.stream.read.return_value = self.json + + self.req = mock.MagicMock() + self.req.stream = self.stream + + self.resp = mock.MagicMock() + self.resource = res.VerificationsResource(self.tenant_repo, + self.verification_repo, + self.queue_resource) + + def test_should_add_new_verification(self): + self.resource.on_post(self.req, self.resp, self.tenant_keystone_id) + + self.assertEquals(falcon.HTTP_202, self.resp.status) + + self.queue_resource.process_verification \ + .assert_called_once_with(verification_id=None, + keystone_id=self.tenant_keystone_id) + + args, kwargs = self.verification_repo.create_from.call_args + verification = args[0] + self.assertIsInstance(verification, models.Verification) + + def test_should_fail_add_new_verification_no_resource_ref(self): + self.verify_req.pop('resource_ref') + self.json = json.dumps(self.verify_req) + self.stream.read.return_value = self.json + + with self.assertRaises(falcon.HTTPError) as cm: + self.resource.on_post(self.req, self.resp, + self.tenant_keystone_id) + exception = cm.exception + self.assertEqual(falcon.HTTP_400, exception.status) + + def test_should_fail_verification_unsupported_resource_type(self): + self.verify_req['resource_type'] = 'not-a-valid-type' + self.json = json.dumps(self.verify_req) + self.stream.read.return_value = self.json + + with self.assertRaises(falcon.HTTPError) as cm: + self.resource.on_post(self.req, self.resp, + self.tenant_keystone_id) + exception = cm.exception + self.assertEqual(falcon.HTTP_400, exception.status) + + def test_should_fail_verification_bad_json(self): + self.stream.read.return_value = '' + + with self.assertRaises(falcon.HTTPError) as cm: + self.resource.on_post(self.req, self.resp, + self.tenant_keystone_id) + exception = cm.exception + self.assertEqual(falcon.HTTP_400, exception.status) + + +class WhenGettingOrDeletingVerificationUsingVerifyResource(unittest.TestCase): + def setUp(self): + self.tenant_keystone_id = 'keystoneid1234' + self.requestor = 'requestor1234' + + self.verification = self._create_verification(id="id1") + + self.verify_repo = mock.MagicMock() + self.verify_repo.get.return_value = self.verification + self.verify_repo.delete_entity_by_id.return_value = None + + self.req = mock.MagicMock() + self.resp = mock.MagicMock() + + self.resource = res.VerificationResource(self.verify_repo) + + def test_should_get_verification(self): + self.resource.on_get(self.req, self.resp, self.tenant_keystone_id, + self.verification.id) + + self.verify_repo.get \ + .assert_called_once_with(entity_id=self.verification.id, + keystone_id=self.tenant_keystone_id, + suppress_exception=True) + + def test_should_delete_verification(self): + self.resource.on_delete(self.req, self.resp, self.tenant_keystone_id, + self.verification.id) + + self.verify_repo.delete_entity_by_id \ + .assert_called_once_with(entity_id=self.verification.id, + keystone_id=self.tenant_keystone_id) + + def test_should_throw_exception_for_get_when_verify_not_found(self): + self.verify_repo.get.return_value = None + + with self.assertRaises(falcon.HTTPError) as cm: + self.resource.on_get(self.req, self.resp, self.tenant_keystone_id, + self.verification.id) + exception = cm.exception + self.assertEqual(falcon.HTTP_404, exception.status) + + def test_should_throw_exception_for_delete_when_verify_not_found(self): + self.verify_repo.delete_entity_by_id.side_effect = excep.NotFound( + "Test not found exception") + + with self.assertRaises(falcon.HTTPError) as cm: + self.resource.on_delete(self.req, self.resp, + self.tenant_keystone_id, + self.verification.id) + exception = cm.exception + self.assertEqual(falcon.HTTP_404, exception.status) + + def _create_verification(self, id="id", + resource_type='image', + resource_ref='http://www.images.com/images/123', + resource_action='vm_attach', + impersonation_allowed=True): + """Generate a Verification entity instance.""" + verification = models.Verification() + verification.id = id + verification.resource_type = resource_type + verification.resource_ref = resource_ref + verification.resource_action = resource_action + verification.impersonation_allowed = impersonation_allowed + return verification diff --git a/barbican/tests/tasks/test_resources.py b/barbican/tests/tasks/test_resources.py index f7302eb66..ce79124a8 100644 --- a/barbican/tests/tasks/test_resources.py +++ b/barbican/tests/tasks/test_resources.py @@ -162,3 +162,69 @@ class WhenBeginningOrder(unittest.TestCase): # secondary one (ValueError). with self.assertRaises(TypeError): self.resource.process(self.order.id, self.keystone_id) + + +class WhenPerformingVerification(unittest.TestCase): + + def setUp(self): + self.verif = models.Verification() + self.verif.id = "id1" + + self.resource_type = 'image', + self.resource_ref = 'http://www.images.com/images/123', + self.resource_action = 'vm_attach', + self.impersonation_allowed = True + + self.keystone_id = 'keystone1234' + self.tenant_id = 'tenantid1234' + self.tenant = models.Tenant() + self.tenant.id = self.tenant_id + self.tenant.keystone_id = self.keystone_id + self.tenant_repo = mock.MagicMock() + self.tenant_repo.get.return_value = self.tenant + + self.verif.status = models.States.PENDING + self.verif.tenant_id = self.tenant_id + self.verif.resource_type = self.resource_type + self.verif.resource_ref = self.resource_ref + self.verif.resource_action = self.resource_action + self.verif.impersonation_allowed = self.impersonation_allowed + + self.verif_repo = mock.MagicMock() + self.verif_repo.get.return_value = self.verif + + self.resource = resources.PerformVerification(self.verif_repo) + + def test_should_process_verification(self): + self.resource.process(self.verif.id, self.keystone_id) + + self.verif_repo.get \ + .assert_called_once_with(entity_id=self.verif.id, + keystone_id=self.keystone_id) + self.assertEqual(self.verif.status, models.States.ACTIVE) + + args, kwargs = self.verif_repo.save.call_args + verif = args[0] + self.assertIsInstance(verif, models.Verification) + self.assertEqual(verif.resource_type, self.resource_type) + self.assertEqual(verif.resource_action, self.resource_action) + + def test_should_fail_during_retrieval(self): + # Force an error during the verification retrieval phase. + self.verif_repo.get = mock.MagicMock(return_value=None, + side_effect=ValueError()) + + with self.assertRaises(ValueError): + self.resource.process(self.verif.id, self.keystone_id) + + # Verification state doesn't change because can't retrieve + # it to change it. + self.assertEqual(models.States.PENDING, self.verif.status) + + def test_should_fail_during_success_report_fail(self): + # Force an error during the processing handler phase. + self.verif_repo.save = mock.MagicMock(return_value=None, + side_effect=ValueError()) + + with self.assertRaises(ValueError): + self.resource.process(self.verif.id, self.keystone_id) diff --git a/etc/barbican/policy.json b/etc/barbican/policy.json index 76122bb07..acc192dc1 100644 --- a/etc/barbican/policy.json +++ b/etc/barbican/policy.json @@ -10,6 +10,10 @@ "orders:get": "rule:all_but_audit", "order:get": "rule:all_users", "order:delete": "rule:admin", + "verifications:post": "rule:admin_or_creator", + "verifications:get": "rule:all_but_audit", + "verification:get": "rule:all_users", + "verification:delete": "rule:admin", "admin": ["role:admin"], "observer": ["role:observer"], "creator": ["role:creator"],