From 1e952be2ac233ed4a640c08a4e00469fce2ad392 Mon Sep 17 00:00:00 2001
From: jfwood <john.wood@rackspace.com>
Date: Fri, 15 Nov 2013 09:27:09 -0600
Subject: [PATCH] Add verification REST service to Barbican

Add initial REST resources and simple business logic (i.e. always return
'True' for the is-valid query). Add unit testing and verify works with
REST client.

Change-Id: Iccb3b2babe7be1907acae297ae8b479c3c3a247f
Implements: blueprint api-add-verification
---
 barbican/api/app.py                    |   5 +
 barbican/api/resources.py              | 123 +++++++++++++++++++
 barbican/common/resources.py           |   2 +-
 barbican/common/validators.py          |  38 ++++++
 barbican/common/verifications.py       |  37 ++++++
 barbican/model/models.py               |  66 +++++++++--
 barbican/model/repositories.py         |  62 +++++++++-
 barbican/queue/celery/resources.py     |  18 ++-
 barbican/queue/simple/resources.py     |  16 ++-
 barbican/tasks/resources.py            |  42 +++++++
 barbican/tests/api/test_resources.py   | 157 ++++++++++++++++++++++++-
 barbican/tests/tasks/test_resources.py |  66 +++++++++++
 etc/barbican/policy.json               |   4 +
 13 files changed, 621 insertions(+), 15 deletions(-)
 create mode 100644 barbican/common/verifications.py

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"],