Merge pull request #69 from jfwood/master
Added methods to the crypto plugin manager to handle key gen and is-supports API flows; Added more code to deal with corner cases in API flow;
This commit is contained in:
@@ -21,6 +21,9 @@ import falcon
|
||||
from barbican.openstack.common import jsonutils as json
|
||||
|
||||
|
||||
MAX_SIZE_REQUEST_INPUT_ACCEPTED_IN_BYTES = 1000000
|
||||
|
||||
|
||||
class ApiResource(object):
|
||||
"""
|
||||
Base class for API resources
|
||||
@@ -42,7 +45,7 @@ def load_body(req):
|
||||
Python dictionary
|
||||
"""
|
||||
try:
|
||||
raw_json = req.stream.read()
|
||||
raw_json = req.stream.read(MAX_SIZE_REQUEST_INPUT_ACCEPTED_IN_BYTES)
|
||||
except IOError:
|
||||
abort(falcon.HTTP_500, 'Read Error')
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ from barbican.openstack.common import log
|
||||
def create_main_app(global_config, **local_conf):
|
||||
"""uWSGI factory method for the Barbican-API application"""
|
||||
|
||||
# Configure oslo logging and configuration services.
|
||||
config.parse_args()
|
||||
log.setup('barbican')
|
||||
|
||||
|
||||
+78
-31
@@ -30,6 +30,7 @@ from barbican.model.models import (Tenant, Secret, TenantSecret,
|
||||
from barbican.model.repositories import (TenantRepo, SecretRepo,
|
||||
OrderRepo, TenantSecretRepo,
|
||||
EncryptedDatumRepo)
|
||||
from barbican.common import exception
|
||||
from barbican.crypto import extension_manager as em
|
||||
from barbican.openstack.common.gettextutils import _
|
||||
from barbican.openstack.common import jsonutils as json
|
||||
@@ -47,12 +48,12 @@ def _general_failure(message):
|
||||
|
||||
def _secret_not_found():
|
||||
"""Throw exception indicating secret not found."""
|
||||
abort(falcon.HTTP_400, _('Unable to locate secret.'))
|
||||
abort(falcon.HTTP_404, _('Unable to locate secret.'))
|
||||
|
||||
|
||||
def _order_not_found():
|
||||
"""Throw exception indicating order not found."""
|
||||
abort(falcon.HTTP_400, _('Unable to locate order.'))
|
||||
abort(falcon.HTTP_404, _('Unable to locate order.'))
|
||||
|
||||
|
||||
def _put_accept_incorrect(ct):
|
||||
@@ -67,6 +68,12 @@ def _get_accept_not_supported(accept):
|
||||
"is not supported.").format(accept))
|
||||
|
||||
|
||||
def _get_secret_info_not_found(mime_type):
|
||||
"""Throw exception indicating request's accept is not supported."""
|
||||
abort(falcon.HTTP_404, _("Secret information of type '{0}' not available "
|
||||
"for decryption.").format(mime_type))
|
||||
|
||||
|
||||
def _secret_mime_type_not_supported(mt, exception=None):
|
||||
"""Throw exception indicating secret mime-type is not supported."""
|
||||
abort(falcon.HTTP_400, _("Mime-type of '{0}' "
|
||||
@@ -82,6 +89,16 @@ def _client_content_mismatch_to_secret(expected, actual):
|
||||
"secret's of '{1}'.").format(actual, expected))
|
||||
|
||||
|
||||
def _secret_data_too_large():
|
||||
"""Throw exception indicating plain-text was too big."""
|
||||
abort(falcon.HTTP_413, _("Could not add secret data as it was too large"))
|
||||
|
||||
|
||||
def _secret_plain_text_empty():
|
||||
"""Throw exception indicating empty plain-text was supplied."""
|
||||
abort(falcon.HTTP_400, _("Could not add secret with empty 'plain_text'"))
|
||||
|
||||
|
||||
def _failed_to_create_encrypted_datum():
|
||||
"""
|
||||
Throw exception we could not create an EncryptedDatum
|
||||
@@ -90,6 +107,11 @@ def _failed_to_create_encrypted_datum():
|
||||
abort(falcon.HTTP_400, _("Could not add secret data to Barbican."))
|
||||
|
||||
|
||||
def _failed_to_decrypt_data():
|
||||
"""Throw exception if failed to decrypt secret information."""
|
||||
abort(falcon.HTTP_500, _("Problem decrypting secret information."))
|
||||
|
||||
|
||||
def _secret_already_has_data():
|
||||
"""
|
||||
Throw exception that the secret already has data.
|
||||
@@ -177,16 +199,17 @@ def next_href(resources_name, tenant_id, offset, limit):
|
||||
return convert_list_to_href(resources_name, tenant_id, offset, limit)
|
||||
|
||||
|
||||
def add_nav_hrefs(resources_name, tenant_id, offset, limit, data):
|
||||
def add_nav_hrefs(resources_name, tenant_id, offset, limit, num_elements, data):
|
||||
if offset > 0:
|
||||
data.update({'previous': previous_href(resources_name,
|
||||
tenant_id,
|
||||
offset,
|
||||
limit)})
|
||||
data.update({'next': next_href(resources_name,
|
||||
tenant_id,
|
||||
offset,
|
||||
limit)})
|
||||
if num_elements >= limit:
|
||||
data.update({'next': next_href(resources_name,
|
||||
tenant_id,
|
||||
offset,
|
||||
limit)})
|
||||
return data
|
||||
|
||||
|
||||
@@ -232,6 +255,12 @@ class SecretsResource(ApiResource):
|
||||
except em.CryptoMimeTypeNotSupportedException as cmtnse:
|
||||
LOG.exception('Secret creation failed - mime-type not supported')
|
||||
_secret_mime_type_not_supported(cmtnse.mime_type)
|
||||
except exception.NoDataToProcess:
|
||||
LOG.exception('No secret data to process')
|
||||
_secret_plain_text_empty()
|
||||
except exception.LimitExceeded:
|
||||
LOG.exception('Secret data too big to process')
|
||||
_secret_data_too_large()
|
||||
except Exception as e:
|
||||
LOG.exception('Secret creation failed - unknown')
|
||||
_general_failure('Secret creation failed - unknown')
|
||||
@@ -248,21 +277,23 @@ class SecretsResource(ApiResource):
|
||||
|
||||
params = req._params
|
||||
|
||||
result = self.secret_repo.get_by_create_date(
|
||||
offset_arg=params.get('offset', None),
|
||||
limit_arg=params.get('limit', None),
|
||||
suppress_exception=True)
|
||||
result = self.secret_repo \
|
||||
.get_by_create_date(offset_arg=params.get('offset',
|
||||
None),
|
||||
limit_arg=params.get('limit',
|
||||
None),
|
||||
suppress_exception=True)
|
||||
secrets, offset, limit = result
|
||||
|
||||
if not secrets:
|
||||
_secret_not_found()
|
||||
else:
|
||||
secrets_resp = [convert_to_hrefs(tenant_id,
|
||||
augment_fields_with_content_types(s)) for s in
|
||||
secrets]
|
||||
secrets_resp_overall = add_nav_hrefs('secrets',
|
||||
tenant_id, offset, limit,
|
||||
{'secrets': secrets_resp})
|
||||
secret_fields = lambda s: augment_fields_with_content_types(s)
|
||||
secrets_resp = [convert_to_hrefs(tenant_id, secret_fields(s)) for
|
||||
s in secrets]
|
||||
secrets_resp_overall = add_nav_hrefs('secrets', tenant_id,
|
||||
offset, limit, len(secrets),
|
||||
{'secrets': secrets_resp})
|
||||
resp.body = json.dumps(secrets_resp_overall,
|
||||
default=json_handler)
|
||||
|
||||
@@ -292,10 +323,10 @@ class SecretResource(ApiResource):
|
||||
if not req.accept or req.accept == 'application/json':
|
||||
# Metadata-only response, no decryption necessary.
|
||||
resp.set_header('Content-Type', 'application/json')
|
||||
resp.body = json.dumps(
|
||||
convert_to_hrefs(tenant_id,
|
||||
augment_fields_with_content_types(secret)),
|
||||
default=json_handler)
|
||||
secret_fields = augment_fields_with_content_types(secret)
|
||||
resp.body = json.dumps(convert_to_hrefs(tenant_id,
|
||||
secret_fields),
|
||||
default=json_handler)
|
||||
else:
|
||||
tenant = get_or_create_tenant(tenant_id, self.tenant_repo)
|
||||
resp.set_header('Content-Type', req.accept)
|
||||
@@ -306,6 +337,10 @@ class SecretResource(ApiResource):
|
||||
LOG.exception('Secret decryption failed - '
|
||||
'accept not supported')
|
||||
_get_accept_not_supported(canse.accept)
|
||||
except em.CryptoNoSecretOrDataException as cnsode:
|
||||
LOG.exception('Secret information of type {0} not '
|
||||
'found for decryption.'.format(cnsode.mime_type))
|
||||
_get_secret_info_not_found(cnsode.mime_type)
|
||||
except Exception as e:
|
||||
LOG.exception('Secret decryption failed - unknown')
|
||||
_failed_to_decrypt_data()
|
||||
@@ -341,14 +376,23 @@ class SecretResource(ApiResource):
|
||||
except em.CryptoMimeTypeNotSupportedException as cmtnse:
|
||||
LOG.exception('Secret creation failed - mime-type not supported')
|
||||
_secret_mime_type_not_supported(cmtnse.mime_type)
|
||||
except exception.NoDataToProcess:
|
||||
LOG.exception('No secret data to process')
|
||||
_secret_plain_text_empty()
|
||||
except exception.LimitExceeded:
|
||||
LOG.exception('Secret data too big to process')
|
||||
_secret_data_too_large()
|
||||
except Exception as e:
|
||||
LOG.exception('Secret creation failed - unknown')
|
||||
_failed_to_create_encrypted_datum()
|
||||
|
||||
def on_delete(self, req, resp, tenant_id, secret_id):
|
||||
secret = self.repo.get(entity_id=secret_id)
|
||||
|
||||
self.repo.delete_entity(secret)
|
||||
try:
|
||||
self.repo.delete_entity_by_id(entity_id=secret_id)
|
||||
except exception.NotFound:
|
||||
LOG.exception('Problem deleting secret')
|
||||
_secret_not_found()
|
||||
|
||||
resp.status = falcon.HTTP_200
|
||||
|
||||
@@ -419,10 +463,10 @@ class OrdersResource(ApiResource):
|
||||
|
||||
params = req._params
|
||||
|
||||
result = self.order_repo.get_by_create_date(
|
||||
offset_arg=params.get('offset', None),
|
||||
limit_arg=params.get('limit', None),
|
||||
suppress_exception=True)
|
||||
result = self.order_repo \
|
||||
.get_by_create_date(offset_arg=params.get('offset', None),
|
||||
limit_arg=params.get('limit', None),
|
||||
suppress_exception=True)
|
||||
orders, offset, limit = result
|
||||
|
||||
if not orders:
|
||||
@@ -430,8 +474,8 @@ class OrdersResource(ApiResource):
|
||||
else:
|
||||
orders_resp = [convert_to_hrefs(tenant_id, o.to_dict_fields())
|
||||
for o in orders]
|
||||
orders_resp_overall = add_nav_hrefs('orders', tenant_id, offset,
|
||||
limit,
|
||||
orders_resp_overall = add_nav_hrefs('orders', tenant_id,
|
||||
offset, limit, len(orders),
|
||||
{'orders': orders_resp})
|
||||
resp.body = json.dumps(orders_resp_overall,
|
||||
default=json_handler)
|
||||
@@ -455,8 +499,11 @@ class OrderResource(ApiResource):
|
||||
default=json_handler)
|
||||
|
||||
def on_delete(self, req, resp, tenant_id, order_id):
|
||||
order = self.repo.get(entity_id=order_id)
|
||||
|
||||
self.repo.delete_entity(order)
|
||||
try:
|
||||
self.repo.delete_entity_by_id(entity_id=order_id)
|
||||
except exception.NotFound:
|
||||
LOG.exception('Problem deleting order')
|
||||
_order_not_found()
|
||||
|
||||
resp.status = falcon.HTTP_200
|
||||
|
||||
@@ -128,6 +128,10 @@ class Invalid(BarbicanException):
|
||||
message = _("Data supplied was not valid.")
|
||||
|
||||
|
||||
class NoDataToProcess(BarbicanException):
|
||||
message = _("No data supplied to process.")
|
||||
|
||||
|
||||
class InvalidSortKey(Invalid):
|
||||
message = _("Sort key supplied was not valid.")
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
"""
|
||||
Shared business logic.
|
||||
"""
|
||||
from sys import getsizeof
|
||||
from oslo.config import cfg
|
||||
from barbican.common import exception
|
||||
from barbican.crypto.extension_manager import (
|
||||
CryptoMimeTypeNotSupportedException
|
||||
)
|
||||
@@ -25,6 +28,15 @@ from barbican.common import utils
|
||||
LOG = utils.getLogger(__name__)
|
||||
|
||||
|
||||
DEFAULT_MAX_SECRET_BYTES = 10000
|
||||
common_opts = [
|
||||
cfg.IntOpt('max_allowed_secret_in_bytes', default=DEFAULT_MAX_SECRET_BYTES),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(common_opts)
|
||||
|
||||
|
||||
def get_or_create_tenant(tenant_id, tenant_repo):
|
||||
"""Returns tenant with matching tenant_id. Creates it if it does
|
||||
not exist."""
|
||||
@@ -67,6 +79,15 @@ def create_secret(data, tenant, crypto_manager,
|
||||
tenant_secret_repo.create_from(new_assoc)
|
||||
|
||||
if 'plain_text' in data:
|
||||
|
||||
plain_text = data['plain_text']
|
||||
|
||||
if not plain_text:
|
||||
raise exception.NoDataToProcess()
|
||||
|
||||
if getsizeof(plain_text) > CONF.max_allowed_secret_in_bytes:
|
||||
raise exception.LimitExceeded()
|
||||
|
||||
LOG.debug('Encrypting plain_text secret...')
|
||||
new_datum = crypto_manager.encrypt(data['plain_text'],
|
||||
new_secret,
|
||||
@@ -74,14 +95,15 @@ def create_secret(data, tenant, crypto_manager,
|
||||
datum_repo.create_from(new_datum)
|
||||
elif ok_to_generate:
|
||||
LOG.debug('Generating new secret...')
|
||||
|
||||
|
||||
# TODO: Generate a good key
|
||||
new_datum = crypto_manager.encrypt('generated_plain_text_key',
|
||||
new_secret,
|
||||
tenant)
|
||||
new_datum = crypto_manager.generate_data_encryption_key(new_secret,
|
||||
tenant)
|
||||
datum_repo.create_from(new_datum)
|
||||
else:
|
||||
LOG.debug('Only creating metadata for the new secret.')
|
||||
LOG.debug('Creating metadata only for the new secret. '
|
||||
'A subsequent PUT is required')
|
||||
crypto_manager.supports(new_secret, tenant)
|
||||
|
||||
return new_secret
|
||||
|
||||
@@ -100,7 +122,10 @@ def create_encrypted_datum(secret, plain_text, tenant, crypto_manager,
|
||||
:retval The response body, None if N/A
|
||||
"""
|
||||
if not plain_text:
|
||||
raise ValueError('Must provide plain-text to encrypt.')
|
||||
raise exception.NoDataToProcess()
|
||||
|
||||
if getsizeof(plain_text) > CONF.max_allowed_secret_in_bytes:
|
||||
raise exception.LimitExceeded()
|
||||
|
||||
if secret.encrypted_data:
|
||||
raise ValueError('Secret already has encrypted data stored for it.')
|
||||
|
||||
@@ -45,7 +45,7 @@ class CryptoMimeTypeNotSupportedException(BarbicanException):
|
||||
not available in any active plugin."""
|
||||
def __init__(self, mime_type):
|
||||
super(CryptoMimeTypeNotSupportedException, self).__init__(
|
||||
_('Crypto Mime Type not supported {0}'.format(mime_type))
|
||||
_("Crypto Mime Type of '{0}' not supported").format(mime_type)
|
||||
)
|
||||
self.mime_type = mime_type
|
||||
|
||||
@@ -55,11 +55,22 @@ class CryptoAcceptNotSupportedException(BarbicanException):
|
||||
available in any active plugin."""
|
||||
def __init__(self, accept):
|
||||
super(CryptoAcceptNotSupportedException, self).__init__(
|
||||
_('Crypto Accept not supported {0}'.format(accept))
|
||||
_("Crypto Accept of '{0}' not supported").format(accept)
|
||||
)
|
||||
self.accept = accept
|
||||
|
||||
|
||||
class CryptoNoSecretOrDataException(BarbicanException):
|
||||
"""Raised when secret information is not available for the specified
|
||||
secret mime-type."""
|
||||
def __init__(self, mime_type):
|
||||
super(CryptoNoSecretOrDataException, self).__init__(
|
||||
_('No secret information available for '
|
||||
'Mime Type of {0}').format(mime_type)
|
||||
)
|
||||
self.mime_type = mime_type
|
||||
|
||||
|
||||
class CryptoExtensionManager(named.NamedExtensionManager):
|
||||
def __init__(self, conf=CONF, invoke_on_load=True,
|
||||
invoke_args=(), invoke_kwargs={}):
|
||||
@@ -81,8 +92,46 @@ class CryptoExtensionManager(named.NamedExtensionManager):
|
||||
|
||||
def decrypt(self, accept, secret, tenant):
|
||||
"""Delegates decryption to active plugins."""
|
||||
if not secret or not secret.encrypted_data:
|
||||
raise CryptoNoSecretOrDataException(accept)
|
||||
|
||||
plain_text = None
|
||||
for ext in self.extensions:
|
||||
if ext.obj.supports(accept):
|
||||
return ext.obj.decrypt(accept, secret, tenant)
|
||||
plain_text = ext.obj.decrypt(accept, secret, tenant)
|
||||
break
|
||||
else:
|
||||
raise CryptoAcceptNotSupportedException(accept)
|
||||
|
||||
if not plain_text:
|
||||
raise CryptoNoSecretOrDataException(accept)
|
||||
|
||||
return plain_text
|
||||
|
||||
def generate_data_encryption_key(self, secret, tenant):
|
||||
"""
|
||||
Delegates generating a data-encryption key to active plugins.
|
||||
|
||||
Note that this key can be used by clients for their encryption
|
||||
processes. This generated key is then be encrypted via
|
||||
the plug-in key encryption process, and that encrypted datum
|
||||
is then returned from this method.
|
||||
"""
|
||||
for ext in self.extensions:
|
||||
if ext.obj.supports(secret.mime_type):
|
||||
# TODO: Call plugin's key generation processes.
|
||||
# Note: It could be the *data* key to generate (for the
|
||||
# secret algo type) uses a different plug in than that
|
||||
# used to encrypted the key.
|
||||
data_key = ext.obj.create(secret.mime_type)
|
||||
return ext.obj.encrypt(data_key, secret, tenant)
|
||||
else:
|
||||
raise CryptoMimeTypeNotSupportedException(secret.mime_type)
|
||||
|
||||
def supports(self, secret, tenant):
|
||||
"""Tests if at least one plug-in supports the secret type."""
|
||||
for ext in self.extensions:
|
||||
if ext.obj.supports(secret.mime_type):
|
||||
return True
|
||||
else:
|
||||
raise CryptoMimeTypeNotSupportedException(secret.mime_type)
|
||||
|
||||
@@ -20,8 +20,10 @@ Barbican defined mime-types
|
||||
# Maps mime-types used to specify secret data formats to the types that can
|
||||
# be requested for secrets via GET calls.
|
||||
CTYPES_PLAIN = {'default': 'text/plain'}
|
||||
CTYPES_BINARY = {'default': 'application/octet-stream'}
|
||||
CTYPES_AES = {'default': 'application/aes'}
|
||||
CTYPES_MAPPINGS = {'text/plain': CTYPES_PLAIN,
|
||||
'application/octet-stream': CTYPES_BINARY,
|
||||
'application/aes': CTYPES_AES}
|
||||
|
||||
|
||||
@@ -38,5 +40,6 @@ def augment_fields_with_content_types(secret):
|
||||
for datum in secret.encrypted_data:
|
||||
if datum.mime_type in CTYPES_MAPPINGS:
|
||||
fields.update({'content_types': CTYPES_MAPPINGS[datum.mime_type]})
|
||||
break
|
||||
|
||||
return fields
|
||||
|
||||
@@ -48,16 +48,19 @@ class SimpleCryptoPlugin(CryptoPluginBase):
|
||||
#TODO: Use PyCrypto to aes encode secrets
|
||||
|
||||
def __init__(self):
|
||||
self.supported_types = ['application/aes-256-cbc', 'text/plain']
|
||||
self.supported_types = ['text/plain', 'application/octet-stream']
|
||||
|
||||
def encrypt(self, unencrypted, secret, tenant):
|
||||
encrypted_datum = EncryptedDatum(secret)
|
||||
encrypted_datum.cypher_text = 'encrypted-data'
|
||||
encrypted_datum.cypher_text = '[ENcrypt this:{0}]'.format(unencrypted)
|
||||
encrypted_datum.kek_metadata = "kek_metadata here"
|
||||
return encrypted_datum
|
||||
|
||||
def decrypt(self, secret_type, secret, tenant):
|
||||
encrypted_datum = secret.encrypted_data
|
||||
return 'plain-data'
|
||||
for encrypted_datum in secret.encrypted_data:
|
||||
if secret_type == encrypted_datum.mime_type:
|
||||
return '[DEcrypt this:{0}]'.format(encrypted_datum.cypher_text)
|
||||
return None
|
||||
|
||||
def create(self, secret_type):
|
||||
return "insecure_key"
|
||||
|
||||
@@ -38,6 +38,11 @@ class States(object):
|
||||
PENDING = 'PENDING'
|
||||
ACTIVE = 'ACTIVE'
|
||||
|
||||
@classmethod
|
||||
def is_valid(self, state_to_test):
|
||||
"""Tests if a state is a valid one."""
|
||||
return state_to_test in self.__dict__
|
||||
|
||||
|
||||
@compiles(BigInteger, 'sqlite')
|
||||
def compile_big_int_sqlite(type_, compiler, **kw):
|
||||
@@ -74,12 +79,17 @@ class ModelBase(object):
|
||||
"""Delete this object"""
|
||||
import barbican.model.repositories
|
||||
session = session or barbican.model.repositories.get_session()
|
||||
session.delete(self)
|
||||
self.deleted = True
|
||||
self.deleted_at = timeutils.utcnow()
|
||||
self.save(session=session)
|
||||
|
||||
#TODO: Soft delete instead?
|
||||
# self.deleted = True
|
||||
# self.deleted_at = timeutils.utcnow()
|
||||
# self.save(session=session)
|
||||
self._do_delete_children(session)
|
||||
|
||||
def _do_delete_children(self, session):
|
||||
"""
|
||||
Sub-class hook: delete children relationships.
|
||||
"""
|
||||
pass
|
||||
|
||||
def update(self, values):
|
||||
"""dict.update() behaviour."""
|
||||
@@ -206,6 +216,13 @@ class Secret(BASE, ModelBase):
|
||||
|
||||
self.status = States.ACTIVE
|
||||
|
||||
def _do_delete_children(self, session):
|
||||
"""
|
||||
Sub-class hook: delete children relationships.
|
||||
"""
|
||||
for datum in self.encrypted_data:
|
||||
datam.delete(session)
|
||||
|
||||
def _do_extra_dict_fields(self):
|
||||
"""Sub-class hook method: return dict of fields."""
|
||||
return {'secret_id': self.id,
|
||||
|
||||
@@ -49,9 +49,6 @@ BASE = models.BASE
|
||||
sa_logger = None
|
||||
|
||||
|
||||
STATUSES = ['active', 'saving', 'queued', 'killed', 'pending_delete',
|
||||
'deleted']
|
||||
|
||||
db_opts = [
|
||||
cfg.IntOpt('sql_idle_timeout', default=3600),
|
||||
cfg.IntOpt('sql_max_retries', default=60),
|
||||
@@ -205,6 +202,17 @@ def wrap_db_error(f):
|
||||
return _wrap
|
||||
|
||||
|
||||
def clean_paging_values(offset_arg=None, limit_arg=None):
|
||||
"""Cleans and safely limits raw paging offset/limit values."""
|
||||
offset = int(offset_arg) if offset_arg else 0
|
||||
offset = offset if offset >= 0 else 0
|
||||
|
||||
limit = int(limit_arg) if limit_arg else CONF.max_limit_paging
|
||||
limit = limit if limit >= 2 else 2
|
||||
|
||||
return (offset, limit)
|
||||
|
||||
|
||||
class BaseRepo(object):
|
||||
"""
|
||||
Base repository for the barbican entities.
|
||||
@@ -330,13 +338,20 @@ class BaseRepo(object):
|
||||
"""
|
||||
return self._update(entity_id, values, purge_props)
|
||||
|
||||
def delete_entity(self, entity):
|
||||
"""Remove the entity"""
|
||||
def delete_entity_by_id(self, entity_id):
|
||||
"""Remove the entity by its ID"""
|
||||
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
entity.deleted_at = timeutils.utcnow()
|
||||
entity.delete(session=session)
|
||||
|
||||
entity = self.get(entity_id=entity_id, session=session)
|
||||
|
||||
try:
|
||||
entity.delete(session=session)
|
||||
except sqlalchemy.exc.IntegrityError:
|
||||
LOG.exception('Problem finding entity to delete')
|
||||
raise exception.NotFound("Entity ID %s not found"
|
||||
% entity_id)
|
||||
|
||||
def _do_entity_name(self):
|
||||
"""Sub-class hook: return entity name, such as for debugging."""
|
||||
@@ -378,7 +393,7 @@ class BaseRepo(object):
|
||||
msg = "{0} status is required.".format(self._do_entity_name())
|
||||
raise exception.Invalid(msg)
|
||||
|
||||
if status not in STATUSES:
|
||||
if not models.States.is_valid(status):
|
||||
msg = "Invalid status '{0}' for {1}.".format(
|
||||
status, self._do_entity_name())
|
||||
raise exception.Invalid(msg)
|
||||
@@ -455,10 +470,6 @@ class TenantRepo(BaseRepo):
|
||||
"""Sub-class hook: build a retrieve query."""
|
||||
return session.query(models.Tenant).filter_by(id=entity_id)
|
||||
|
||||
def _do_validate(self, values):
|
||||
"""Sub-class hook: validate values."""
|
||||
pass
|
||||
|
||||
def find_by_keystone_id(self, keystone_id, suppress_exception=False,
|
||||
session=None):
|
||||
session = self.get_session(session)
|
||||
@@ -491,19 +502,16 @@ class SecretRepo(BaseRepo):
|
||||
and paged based on the offset and limit fields.
|
||||
"""
|
||||
|
||||
offset = int(offset_arg) if offset_arg else 0
|
||||
offset = offset if offset >= 0 else 0
|
||||
|
||||
limit = int(limit_arg) if limit_arg else CONF.max_limit_paging
|
||||
limit = limit if limit >= 2 else 2
|
||||
offset, limit = clean_paging_values(offset_arg, limit_arg)
|
||||
|
||||
session = self.get_session(session)
|
||||
utcnow = timeutils.utcnow()
|
||||
|
||||
try:
|
||||
query = session.query(models.Secret).order_by(
|
||||
models.Secret.created_at).filter_by(deleted=False)
|
||||
|
||||
query = session.query(models.Secret) \
|
||||
.order_by(models.Secret.created_at) \
|
||||
.filter_by(deleted=False)
|
||||
|
||||
# Note: Must use '== None' below, not 'is None'.
|
||||
query = query.filter(or_(models.Secret.expiration == None,
|
||||
models.Secret.expiration > utcnow))
|
||||
@@ -528,20 +536,20 @@ class SecretRepo(BaseRepo):
|
||||
def _do_build_query_by_name(self, name, session):
|
||||
"""Sub-class hook: find entity by name."""
|
||||
utcnow = timeutils.utcnow()
|
||||
|
||||
|
||||
# Note: Must use '== None' below, not 'is None'.
|
||||
return session.query(models.Secret).filter_by(name=name).filter(
|
||||
or_(models.Secret.expiration == None,
|
||||
models.Secret.expiration > utcnow))
|
||||
return session.query(models.Secret).filter_by(name=name) \
|
||||
.filter(or_(models.Secret.expiration == None,
|
||||
models.Secret.expiration > utcnow))
|
||||
|
||||
def _do_build_get_query(self, entity_id, session):
|
||||
"""Sub-class hook: build a retrieve query."""
|
||||
utcnow = timeutils.utcnow()
|
||||
|
||||
|
||||
# Note: Must use '== None' below, not 'is None'.
|
||||
return session.query(models.Secret).filter_by(id=entity_id).filter(
|
||||
or_(models.Secret.expiration == None,
|
||||
models.Secret.expiration > utcnow))
|
||||
return session.query(models.Secret).filter_by(id=entity_id) \
|
||||
.filter(or_(models.Secret.expiration == None,
|
||||
models.Secret.expiration > utcnow))
|
||||
|
||||
def _do_validate(self, values):
|
||||
"""Sub-class hook: validate values."""
|
||||
@@ -609,17 +617,13 @@ class OrderRepo(BaseRepo):
|
||||
and paged based on the offset and limit fields.
|
||||
"""
|
||||
|
||||
offset = int(offset_arg) if offset_arg else 0
|
||||
offset = offset if offset >= 0 else 0
|
||||
|
||||
limit = int(limit_arg) if limit_arg else CONF.max_limit_paging
|
||||
limit = limit if limit >= 2 else 2
|
||||
offset, limit = clean_paging_values(offset_arg, limit_arg)
|
||||
|
||||
session = self.get_session(session)
|
||||
|
||||
try:
|
||||
query = session.query(models.Order).order_by(
|
||||
models.Order.created_at)
|
||||
query = session.query(models.Order) \
|
||||
.order_by(models.Order.created_at)
|
||||
query = query.filter_by(deleted=False)
|
||||
|
||||
entities = query[offset:(offset + limit)]
|
||||
|
||||
@@ -26,7 +26,8 @@ from barbican.crypto.extension_manager import CryptoExtensionManager
|
||||
from barbican.model.models import (Secret, Tenant, TenantSecret,
|
||||
Order, EncryptedDatum)
|
||||
from barbican.common import config
|
||||
from barbican.common import exception
|
||||
from barbican.common import exception as excep
|
||||
from barbican.common.resources import DEFAULT_MAX_SECRET_BYTES
|
||||
from barbican.openstack.common import jsonutils
|
||||
|
||||
|
||||
@@ -59,8 +60,8 @@ def create_secret(mime_type, id="id", name="name",
|
||||
|
||||
|
||||
def create_order(mime_type, id="id", name="name",
|
||||
algorithm=None, bit_length=None,
|
||||
cypher_type=None):
|
||||
algorithm=None, bit_length=None,
|
||||
cypher_type=None):
|
||||
"""Generate an Order entity instance."""
|
||||
order = Order()
|
||||
order.id = id
|
||||
@@ -156,18 +157,18 @@ class WhenCreatingSecretsUsingSecretsResource(unittest.TestCase):
|
||||
|
||||
args, kwargs = self.secret_repo.create_from.call_args
|
||||
secret = args[0]
|
||||
assert isinstance(secret, Secret)
|
||||
assert secret.name == self.name
|
||||
assert secret.algorithm == self.secret_algorithm
|
||||
assert secret.bit_length == self.secret_bit_length
|
||||
assert secret.cypher_type == self.secret_cypher_type
|
||||
assert secret.mime_type == self.mime_type
|
||||
self.assertTrue(isinstance(secret, Secret))
|
||||
self.assertEqual(secret.name, self.name)
|
||||
self.assertEqual(secret.algorithm, self.secret_algorithm)
|
||||
self.assertEqual(secret.bit_length, self.secret_bit_length)
|
||||
self.assertEqual(secret.cypher_type, self.secret_cypher_type)
|
||||
self.assertEqual(secret.mime_type, self.mime_type)
|
||||
|
||||
args, kwargs = self.tenant_secret_repo.create_from.call_args
|
||||
tenant_secret = args[0]
|
||||
assert isinstance(tenant_secret, TenantSecret)
|
||||
assert tenant_secret.tenant_id == self.tenant_id
|
||||
assert tenant_secret.secret_id == secret.id
|
||||
self.assertTrue(isinstance(tenant_secret, TenantSecret))
|
||||
self.assertEqual(tenant_secret.tenant_id, self.tenant_id)
|
||||
self.assertEqual(tenant_secret.secret_id, secret.id)
|
||||
|
||||
args, kwargs = self.datum_repo.create_from.call_args
|
||||
datum = args[0]
|
||||
@@ -183,21 +184,21 @@ class WhenCreatingSecretsUsingSecretsResource(unittest.TestCase):
|
||||
|
||||
args, kwargs = self.secret_repo.create_from.call_args
|
||||
secret = args[0]
|
||||
assert isinstance(secret, Secret)
|
||||
assert secret.name == self.name
|
||||
self.assertTrue(isinstance(secret, Secret))
|
||||
self.assertEqual(secret.name, self.name)
|
||||
|
||||
args, kwargs = self.tenant_secret_repo.create_from.call_args
|
||||
tenant_secret = args[0]
|
||||
assert isinstance(tenant_secret, TenantSecret)
|
||||
assert not tenant_secret.tenant_id
|
||||
assert tenant_secret.secret_id == secret.id
|
||||
self.assertTrue(isinstance(tenant_secret, TenantSecret))
|
||||
self.assertIsNone(tenant_secret.tenant_id)
|
||||
self.assertEqual(tenant_secret.secret_id, secret.id)
|
||||
|
||||
args, kwargs = self.datum_repo.create_from.call_args
|
||||
datum = args[0]
|
||||
assert isinstance(datum, EncryptedDatum)
|
||||
self.assertTrue(isinstance(datum, EncryptedDatum))
|
||||
self.assertEqual('cypher_text', datum.cypher_text)
|
||||
assert self.mime_type == datum.mime_type
|
||||
assert datum.kek_metadata is not None
|
||||
self.assertEqual(self.mime_type, datum.mime_type)
|
||||
self.assertIsNotNone(datum.kek_metadata)
|
||||
|
||||
def test_should_add_new_secret_no_plain_text(self):
|
||||
json_template = u'{{"name":"{0}", "mime_type":"{1}"}}'
|
||||
@@ -208,16 +209,48 @@ class WhenCreatingSecretsUsingSecretsResource(unittest.TestCase):
|
||||
|
||||
args, kwargs = self.secret_repo.create_from.call_args
|
||||
secret = args[0]
|
||||
assert isinstance(secret, Secret)
|
||||
assert secret.name == self.name
|
||||
self.assertTrue(isinstance(secret, Secret))
|
||||
self.assertEqual(secret.name, self.name)
|
||||
|
||||
args, kwargs = self.tenant_secret_repo.create_from.call_args
|
||||
tenant_secret = args[0]
|
||||
assert isinstance(tenant_secret, TenantSecret)
|
||||
assert tenant_secret.tenant_id == self.tenant_id
|
||||
assert tenant_secret.secret_id == secret.id
|
||||
self.assertTrue(isinstance(tenant_secret, TenantSecret))
|
||||
self.assertEqual(tenant_secret.tenant_id, self.tenant_id)
|
||||
self.assertEqual(tenant_secret.secret_id, secret.id)
|
||||
|
||||
assert not self.datum_repo.create_from.called
|
||||
self.assertFalse(self.datum_repo.create_from.called)
|
||||
|
||||
def test_should_fail_due_to_empty_plain_text(self):
|
||||
self.secret_req = {'name': self.name,
|
||||
'mime_type': self.mime_type,
|
||||
'algorithm': self.secret_algorithm,
|
||||
'bit_length': self.secret_bit_length,
|
||||
'cypher_type': self.secret_cypher_type,
|
||||
'plain_text': ''}
|
||||
self.stream.read.return_value = json.dumps(self.secret_req)
|
||||
|
||||
with self.assertRaises(falcon.HTTPError) as cm:
|
||||
self.resource.on_post(self.req, self.resp, self.tenant_id)
|
||||
|
||||
exception = cm.exception
|
||||
self.assertEqual(falcon.HTTP_400, exception.status)
|
||||
|
||||
def test_should_fail_due_to_plain_text_too_large(self):
|
||||
big_text = ['A' for x in xrange(2 * DEFAULT_MAX_SECRET_BYTES)]
|
||||
|
||||
self.secret_req = {'name': self.name,
|
||||
'mime_type': self.mime_type,
|
||||
'algorithm': self.secret_algorithm,
|
||||
'bit_length': self.secret_bit_length,
|
||||
'cypher_type': self.secret_cypher_type,
|
||||
'plain_text': big_text}
|
||||
self.stream.read.return_value = json.dumps(self.secret_req)
|
||||
|
||||
with self.assertRaises(falcon.HTTPError) as cm:
|
||||
self.resource.on_post(self.req, self.resp, self.tenant_id)
|
||||
|
||||
exception = cm.exception
|
||||
self.assertEqual(falcon.HTTP_413, exception.status)
|
||||
|
||||
def test_should_fail_due_to_unsupported_mime(self):
|
||||
self.secret_req = {'name': self.name,
|
||||
@@ -232,7 +265,7 @@ class WhenCreatingSecretsUsingSecretsResource(unittest.TestCase):
|
||||
self.resource.on_post(self.req, self.resp, self.tenant_id)
|
||||
|
||||
exception = cm.exception
|
||||
assert falcon.HTTP_400 == exception.status
|
||||
self.assertEqual(falcon.HTTP_400, exception.status)
|
||||
|
||||
|
||||
class WhenGettingSecretsListUsingSecretsResource(unittest.TestCase):
|
||||
@@ -293,12 +326,12 @@ class WhenGettingSecretsListUsingSecretsResource(unittest.TestCase):
|
||||
def test_should_get_list_secrets(self):
|
||||
self.resource.on_get(self.req, self.resp, self.tenant_id)
|
||||
|
||||
self.secret_repo.get_by_create_date.assert_called_once_with(
|
||||
offset_arg=self.params.get('offset',
|
||||
self.offset),
|
||||
limit_arg=self.params.get('limit',
|
||||
self.limit),
|
||||
suppress_exception=True)
|
||||
self.secret_repo.get_by_create_date \
|
||||
.assert_called_once_with(offset_arg=self.params.get('offset',
|
||||
self.offset),
|
||||
limit_arg=self.params.get('limit',
|
||||
self.limit),
|
||||
suppress_exception=True)
|
||||
|
||||
resp_body = jsonutils.loads(self.resp.body)
|
||||
self.assertTrue('previous' in resp_body)
|
||||
@@ -324,16 +357,15 @@ class WhenGettingSecretsListUsingSecretsResource(unittest.TestCase):
|
||||
self.resource.on_get(self.req, self.resp, self.tenant_id)
|
||||
|
||||
exception = cm.exception
|
||||
assert falcon.HTTP_400 == exception.status
|
||||
self.assertEqual(falcon.HTTP_404, exception.status)
|
||||
|
||||
def _create_url(self, tenant_id, offset_arg=None, limit_arg=None):
|
||||
if limit_arg:
|
||||
offset = int(offset_arg)
|
||||
limit = int(limit_arg)
|
||||
return '/v1/{0}/secrets?limit={1}&offset={2}'.format(
|
||||
tenant_id,
|
||||
limit,
|
||||
offset)
|
||||
return '/v1/{0}/secrets?limit={1}&offset={2}'.format(tenant_id,
|
||||
limit,
|
||||
offset)
|
||||
else:
|
||||
return '/v1/{0}/secrets'.format(self.tenant_id)
|
||||
|
||||
@@ -374,7 +406,7 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(
|
||||
|
||||
self.secret_repo = MagicMock()
|
||||
self.secret_repo.get.return_value = self.secret
|
||||
self.secret_repo.delete_entity.return_value = None
|
||||
self.secret_repo.delete_entity_by_id.return_value = None
|
||||
|
||||
self.tenant_secret_repo = MagicMock()
|
||||
self.tenant_secret_repo.create_from.return_value = None
|
||||
@@ -426,7 +458,7 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(
|
||||
self.assertEquals(self.resp.status, falcon.HTTP_200)
|
||||
|
||||
resp_body = self.resp.body
|
||||
assert resp_body
|
||||
self.assertIsNotNone(resp_body)
|
||||
|
||||
def test_should_throw_exception_for_get_when_secret_not_found(self):
|
||||
self.secret_repo.get.return_value = None
|
||||
@@ -436,8 +468,7 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(
|
||||
self.secret.id)
|
||||
|
||||
exception = cm.exception
|
||||
assert falcon.HTTP_400 == exception.status
|
||||
|
||||
self.assertEqual(falcon.HTTP_404, exception.status)
|
||||
|
||||
def test_should_throw_exception_for_get_when_accept_not_supported(self):
|
||||
|
||||
@@ -448,8 +479,19 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(
|
||||
self.secret.id)
|
||||
|
||||
exception = cm.exception
|
||||
assert falcon.HTTP_406 == exception.status
|
||||
self.assertEqual(falcon.HTTP_406, exception.status)
|
||||
|
||||
def test_should_throw_exception_for_get_when_datum_not_available(self):
|
||||
|
||||
self.req.accept = 'text/plain'
|
||||
self.secret.encrypted_data = []
|
||||
|
||||
with self.assertRaises(falcon.HTTPError) as cm:
|
||||
self.resource.on_get(self.req, self.resp, self.tenant_id,
|
||||
self.secret.id)
|
||||
|
||||
exception = cm.exception
|
||||
self.assertEqual(falcon.HTTP_404, exception.status)
|
||||
|
||||
def test_should_put_secret_as_plain(self):
|
||||
self._setup_for_puts()
|
||||
@@ -459,10 +501,10 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(
|
||||
|
||||
args, kwargs = self.datum_repo.create_from.call_args
|
||||
datum = args[0]
|
||||
assert isinstance(datum, EncryptedDatum)
|
||||
self.assertTrue(isinstance(datum, EncryptedDatum))
|
||||
self.assertEqual('cypher_text', datum.cypher_text)
|
||||
assert self.mime_type == datum.mime_type
|
||||
assert datum.kek_metadata is not None
|
||||
self.assertEqual(self.mime_type, datum.mime_type)
|
||||
self.assertIsNotNone(datum.kek_metadata)
|
||||
|
||||
def test_should_fail_put_secret_as_json(self):
|
||||
self._setup_for_puts()
|
||||
@@ -476,7 +518,7 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(
|
||||
self.secret.id)
|
||||
|
||||
exception = cm.exception
|
||||
assert falcon.HTTP_415 == exception.status
|
||||
self.assertEqual(falcon.HTTP_415, exception.status)
|
||||
|
||||
def test_should_fail_put_secret_not_found(self):
|
||||
self._setup_for_puts()
|
||||
@@ -489,7 +531,7 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(
|
||||
self.secret.id)
|
||||
|
||||
exception = cm.exception
|
||||
assert falcon.HTTP_400 == exception.status
|
||||
self.assertEqual(falcon.HTTP_404, exception.status)
|
||||
|
||||
def test_should_fail_put_secret_no_plain_text(self):
|
||||
self._setup_for_puts()
|
||||
@@ -502,7 +544,7 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(
|
||||
self.secret.id)
|
||||
|
||||
exception = cm.exception
|
||||
assert falcon.HTTP_400 == exception.status
|
||||
self.assertEqual(falcon.HTTP_400, exception.status)
|
||||
|
||||
def test_should_fail_put_secret_with_existing_datum(self):
|
||||
self._setup_for_puts()
|
||||
@@ -515,23 +557,51 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(
|
||||
self.secret.id)
|
||||
|
||||
exception = cm.exception
|
||||
assert falcon.HTTP_409 == exception.status
|
||||
self.assertEqual(falcon.HTTP_409, exception.status)
|
||||
|
||||
def test_should_fail_due_to_empty_plain_text(self):
|
||||
self._setup_for_puts()
|
||||
|
||||
self.stream.read.return_value = ''
|
||||
|
||||
with self.assertRaises(falcon.HTTPError) as cm:
|
||||
self.resource.on_put(self.req, self.resp, self.tenant_id,
|
||||
self.secret.id)
|
||||
|
||||
exception = cm.exception
|
||||
self.assertEqual(falcon.HTTP_400, exception.status)
|
||||
|
||||
def test_should_fail_due_to_plain_text_too_large(self):
|
||||
self._setup_for_puts()
|
||||
|
||||
big_text = ['A' for x in xrange(2 * DEFAULT_MAX_SECRET_BYTES)]
|
||||
self.stream.read.return_value = big_text
|
||||
|
||||
with self.assertRaises(falcon.HTTPError) as cm:
|
||||
self.resource.on_put(self.req, self.resp, self.tenant_id,
|
||||
self.secret.id)
|
||||
|
||||
exception = cm.exception
|
||||
self.assertEqual(falcon.HTTP_413, exception.status)
|
||||
|
||||
def test_should_delete_secret(self):
|
||||
self.resource.on_delete(self.req, self.resp, self.tenant_id,
|
||||
self.secret.id)
|
||||
|
||||
self.secret_repo.get.assert_called_once_with(entity_id=self.secret.id)
|
||||
self.secret_repo.delete_entity.assert_called_once_with(self.secret)
|
||||
self.secret_repo.delete_entity_by_id \
|
||||
.assert_called_once_with(entity_id=self.secret.id)
|
||||
|
||||
def test_should_throw_exception_for_delete_when_secret_not_found(self):
|
||||
self.secret_repo.get.side_effect = exception.NotFound(
|
||||
self.secret_repo.delete_entity_by_id.side_effect = excep.NotFound(
|
||||
"Test not found exception")
|
||||
|
||||
with self.assertRaises(exception.NotFound):
|
||||
with self.assertRaises(falcon.HTTPError) as cm:
|
||||
self.resource.on_delete(self.req, self.resp, self.tenant_id,
|
||||
self.secret.id)
|
||||
|
||||
exception = cm.exception
|
||||
self.assertEqual(falcon.HTTP_404, exception.status)
|
||||
|
||||
def _setup_for_puts(self):
|
||||
self.plain_text = "plain_text"
|
||||
self.req.accept = self.mime_type
|
||||
@@ -599,7 +669,7 @@ class WhenCreatingOrdersUsingOrdersResource(unittest.TestCase):
|
||||
None)
|
||||
|
||||
args, kwargs = self.order_repo.create_from.call_args
|
||||
assert isinstance(args[0], Order)
|
||||
self.assertTrue(isinstance(args[0], Order))
|
||||
|
||||
|
||||
class WhenGettingOrdersListUsingOrdersResource(unittest.TestCase):
|
||||
@@ -649,12 +719,12 @@ class WhenGettingOrdersListUsingOrdersResource(unittest.TestCase):
|
||||
def test_should_get_list_orders(self):
|
||||
self.resource.on_get(self.req, self.resp, self.tenant_id)
|
||||
|
||||
self.order_repo.get_by_create_date.assert_called_once_with(
|
||||
offset_arg=self.params.get('offset',
|
||||
self.offset),
|
||||
limit_arg=self.params.get('limit',
|
||||
self.limit),
|
||||
suppress_exception=True)
|
||||
self.order_repo.get_by_create_date \
|
||||
.assert_called_once_with(offset_arg=self.params.get('offset',
|
||||
self.offset),
|
||||
limit_arg=self.params.get('limit',
|
||||
self.limit),
|
||||
suppress_exception=True)
|
||||
|
||||
resp_body = jsonutils.loads(self.resp.body)
|
||||
self.assertTrue('previous' in resp_body)
|
||||
@@ -680,7 +750,7 @@ class WhenGettingOrdersListUsingOrdersResource(unittest.TestCase):
|
||||
self.resource.on_get(self.req, self.resp, self.tenant_id)
|
||||
|
||||
exception = cm.exception
|
||||
assert falcon.HTTP_400 == exception.status
|
||||
self.assertEqual(falcon.HTTP_404, exception.status)
|
||||
|
||||
def _create_url(self, tenant_id, offset_arg=None, limit_arg=None):
|
||||
if limit_arg:
|
||||
@@ -704,7 +774,7 @@ class WhenGettingOrDeletingOrderUsingOrderResource(unittest.TestCase):
|
||||
|
||||
self.order_repo = MagicMock()
|
||||
self.order_repo.get.return_value = self.order
|
||||
self.order_repo.delete_entity.return_value = None
|
||||
self.order_repo.delete_entity_by_id.return_value = None
|
||||
|
||||
self.policy = MagicMock()
|
||||
|
||||
@@ -726,26 +796,31 @@ class WhenGettingOrDeletingOrderUsingOrderResource(unittest.TestCase):
|
||||
self.resource.on_delete(self.req, self.resp, self.tenant_keystone_id,
|
||||
self.order.id)
|
||||
|
||||
self.order_repo.get.assert_called_once_with(entity_id=self.order.id)
|
||||
self.order_repo.delete_entity.assert_called_once_with(self.order)
|
||||
self.order_repo.delete_entity_by_id \
|
||||
.assert_called_once_with(entity_id=self.order.id)
|
||||
|
||||
def test_should_throw_exception_for_get_when_order_not_found(self):
|
||||
self.order_repo.get.side_effect = exception.NotFound(
|
||||
"Test not found exception")
|
||||
self.order_repo.get.return_value = None
|
||||
|
||||
with self.assertRaises(exception.NotFound):
|
||||
with self.assertRaises(falcon.HTTPError) as cm:
|
||||
self.resource.on_get(self.req, self.resp, self.tenant_keystone_id,
|
||||
self.order.id)
|
||||
|
||||
exception = cm.exception
|
||||
self.assertEqual(falcon.HTTP_404, exception.status)
|
||||
|
||||
def test_should_throw_exception_for_delete_when_order_not_found(self):
|
||||
self.order_repo.get.side_effect = exception.NotFound(
|
||||
self.order_repo.delete_entity_by_id.side_effect = excep.NotFound(
|
||||
"Test not found exception")
|
||||
|
||||
with self.assertRaises(exception.NotFound):
|
||||
with self.assertRaises(falcon.HTTPError) as cm:
|
||||
self.resource.on_delete(self.req, self.resp,
|
||||
self.tenant_keystone_id,
|
||||
self.order.id)
|
||||
|
||||
exception = cm.exception
|
||||
self.assertEqual(falcon.HTTP_404, exception.status)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -24,7 +24,6 @@ class TestCryptoPlugin(CryptoPluginBase):
|
||||
def encrypt(self, unencrypted, secret, tenant):
|
||||
datum = EncryptedDatum(secret)
|
||||
datum.cypher_text = 'cypher_text'
|
||||
datum.mime_type = 'text/plain'
|
||||
datum.kek_metadata = json.dumps({'plugin': 'TestCryptoPlugin'})
|
||||
return datum
|
||||
|
||||
|
||||
Reference in New Issue
Block a user