Mime Type Revamp
Implements: blueprint mime-type-revamp Change-Id: I43f8ec16a3b2839174abb0989c950a6bb8a5b685
This commit is contained in:
parent
719da9246d
commit
1bb6adc8fd
@ -16,6 +16,7 @@
|
|||||||
"""
|
"""
|
||||||
API-facing resource controllers.
|
API-facing resource controllers.
|
||||||
"""
|
"""
|
||||||
|
import base64
|
||||||
|
|
||||||
import falcon
|
import falcon
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ from barbican.model import models
|
|||||||
from barbican.model import repositories as repo
|
from barbican.model import repositories as repo
|
||||||
from barbican.common import exception
|
from barbican.common import exception
|
||||||
from barbican.crypto import extension_manager as em
|
from barbican.crypto import extension_manager as em
|
||||||
|
from barbican.crypto import mime_types
|
||||||
from barbican.openstack.common.gettextutils import _
|
from barbican.openstack.common.gettextutils import _
|
||||||
from barbican.openstack.common import jsonutils as json
|
from barbican.openstack.common import jsonutils as json
|
||||||
from barbican.queue import get_queue_api
|
from barbican.queue import get_queue_api
|
||||||
@ -80,16 +82,6 @@ def _secret_mime_type_not_supported(mt, req, resp):
|
|||||||
_("Mime-type of '{0}' is not supported.").format(mt), req, resp)
|
_("Mime-type of '{0}' is not supported.").format(mt), req, resp)
|
||||||
|
|
||||||
|
|
||||||
def _client_content_mismatch_to_secret(expected, actual, req, res):
|
|
||||||
"""
|
|
||||||
Throw exception indicating client content-type doesn't match
|
|
||||||
secret's mime-type.
|
|
||||||
"""
|
|
||||||
api.abort(falcon.HTTP_400,
|
|
||||||
_("Request content-type of '{0}' doesn't match "
|
|
||||||
"secret's of '{1}'.").format(actual, expected), req, res)
|
|
||||||
|
|
||||||
|
|
||||||
def _secret_data_too_large(req, resp):
|
def _secret_data_too_large(req, resp):
|
||||||
"""Throw exception indicating plain-text was too big."""
|
"""Throw exception indicating plain-text was too big."""
|
||||||
api.abort(falcon.HTTP_413,
|
api.abort(falcon.HTTP_413,
|
||||||
@ -322,15 +314,13 @@ class SecretsResource(api.ApiResource):
|
|||||||
LOG.debug('Start secrets on_get '
|
LOG.debug('Start secrets on_get '
|
||||||
'for tenant-ID {0}:'.format(keystone_id))
|
'for tenant-ID {0}:'.format(keystone_id))
|
||||||
|
|
||||||
params = req._params
|
result = self.secret_repo.get_by_create_date(
|
||||||
|
keystone_id,
|
||||||
|
offset_arg=req.get_param('offset'),
|
||||||
|
limit_arg=req.get_param('limit'),
|
||||||
|
suppress_exception=True
|
||||||
|
)
|
||||||
|
|
||||||
result = self.secret_repo \
|
|
||||||
.get_by_create_date(keystone_id,
|
|
||||||
offset_arg=params.get('offset',
|
|
||||||
None),
|
|
||||||
limit_arg=params.get('limit',
|
|
||||||
None),
|
|
||||||
suppress_exception=True)
|
|
||||||
secrets, offset, limit = result
|
secrets, offset, limit = result
|
||||||
|
|
||||||
if not secrets:
|
if not secrets:
|
||||||
@ -383,6 +373,7 @@ class SecretResource(api.ApiResource):
|
|||||||
else:
|
else:
|
||||||
tenant = res.get_or_create_tenant(keystone_id, self.tenant_repo)
|
tenant = res.get_or_create_tenant(keystone_id, self.tenant_repo)
|
||||||
resp.set_header('Content-Type', req.accept)
|
resp.set_header('Content-Type', req.accept)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp.body = self.crypto_manager.decrypt(req.accept, secret,
|
resp.body = self.crypto_manager.decrypt(req.accept, secret,
|
||||||
tenant)
|
tenant)
|
||||||
@ -390,14 +381,34 @@ class SecretResource(api.ApiResource):
|
|||||||
LOG.exception('Secret decryption failed - '
|
LOG.exception('Secret decryption failed - '
|
||||||
'accept not supported')
|
'accept not supported')
|
||||||
_get_accept_not_supported(canse.accept, req, resp)
|
_get_accept_not_supported(canse.accept, req, resp)
|
||||||
except em.CryptoNoSecretOrDataException as cnsode:
|
except em.CryptoNoSecretOrDataException:
|
||||||
LOG.exception('Secret information of type {0} not '
|
LOG.exception('Secret information of type not '
|
||||||
'found for decryption.'.format(cnsode.mime_type))
|
'found for decryption.')
|
||||||
_get_secret_info_not_found(cnsode.mime_type, req, resp)
|
_get_secret_info_not_found(req.accept, req, resp)
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.exception('Secret decryption failed - unknown')
|
LOG.exception('Secret decryption failed - unknown')
|
||||||
_failed_to_decrypt_data(req, resp)
|
_failed_to_decrypt_data(req, resp)
|
||||||
|
|
||||||
|
acceptable = utils.get_accepted_encodings(req)
|
||||||
|
LOG.debug('Acceptable: {0}'.format(acceptable))
|
||||||
|
if acceptable:
|
||||||
|
encodings = [enc for enc in acceptable if
|
||||||
|
enc in mime_types.ENCODINGS]
|
||||||
|
if encodings:
|
||||||
|
if 'base64' in encodings:
|
||||||
|
|
||||||
|
resp.body = base64.b64encode(resp.body)
|
||||||
|
else:
|
||||||
|
# encoding not supported
|
||||||
|
LOG.exception('Accept-Encoding not supported:'
|
||||||
|
' {0}'.format(str(encodings)))
|
||||||
|
_get_accept_not_supported(str(encodings), req, resp)
|
||||||
|
else:
|
||||||
|
# encoding not supported
|
||||||
|
LOG.exception('Accept-Encoding not supported:'
|
||||||
|
' {0}'.format(str(encodings)))
|
||||||
|
_get_accept_not_supported(str(encodings), req, resp)
|
||||||
|
|
||||||
@handle_exceptions(_('Secret update'))
|
@handle_exceptions(_('Secret update'))
|
||||||
def on_put(self, req, resp, keystone_id, secret_id):
|
def on_put(self, req, resp, keystone_id, secret_id):
|
||||||
|
|
||||||
@ -408,24 +419,35 @@ class SecretResource(api.ApiResource):
|
|||||||
suppress_exception=True)
|
suppress_exception=True)
|
||||||
if not secret:
|
if not secret:
|
||||||
_secret_not_found(req, resp)
|
_secret_not_found(req, resp)
|
||||||
if secret.mime_type != req.content_type:
|
|
||||||
_client_content_mismatch_to_secret(secret.mime_type,
|
|
||||||
req.content_type, req, resp)
|
|
||||||
if secret.encrypted_data:
|
if secret.encrypted_data:
|
||||||
_secret_already_has_data(req, resp)
|
_secret_already_has_data(req, resp)
|
||||||
|
|
||||||
tenant = res.get_or_create_tenant(keystone_id, self.tenant_repo)
|
tenant = res.get_or_create_tenant(keystone_id, self.tenant_repo)
|
||||||
|
payload = None
|
||||||
|
content_type = req.content_type
|
||||||
|
content_encoding = req.get_header('Content-Encoding')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
plain_text = req.stream.read(api.MAX_BYTES_REQUEST_INPUT_ACCEPTED)
|
payload = req.stream.read(api.MAX_BYTES_REQUEST_INPUT_ACCEPTED)
|
||||||
except IOError:
|
except IOError:
|
||||||
api.abort(falcon.HTTP_500, 'Read Error')
|
api.abort(falcon.HTTP_500, 'Read Error')
|
||||||
|
|
||||||
resp.status = falcon.HTTP_200
|
if content_type in mime_types.BINARY and \
|
||||||
|
content_encoding in mime_types.ENCODINGS:
|
||||||
|
if content_encoding == 'base64':
|
||||||
|
payload = base64.b64decode(payload)
|
||||||
|
elif content_encoding is not None:
|
||||||
|
LOG.exception(
|
||||||
|
'Content-Encoding not supported {0}'.format(content_encoding)
|
||||||
|
)
|
||||||
|
_put_accept_incorrect(content_encoding, req, resp)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
res.create_encrypted_datum(secret,
|
res.create_encrypted_datum(secret,
|
||||||
plain_text,
|
payload,
|
||||||
|
content_type,
|
||||||
|
content_encoding,
|
||||||
tenant,
|
tenant,
|
||||||
self.crypto_manager,
|
self.crypto_manager,
|
||||||
self.tenant_secret_repo,
|
self.tenant_secret_repo,
|
||||||
@ -443,6 +465,8 @@ class SecretResource(api.ApiResource):
|
|||||||
LOG.exception('Secret creation failed - unknown')
|
LOG.exception('Secret creation failed - unknown')
|
||||||
_failed_to_create_encrypted_datum(req, resp)
|
_failed_to_create_encrypted_datum(req, resp)
|
||||||
|
|
||||||
|
resp.status = falcon.HTTP_200
|
||||||
|
|
||||||
@handle_exceptions(_('Secret deletion'))
|
@handle_exceptions(_('Secret deletion'))
|
||||||
def on_delete(self, req, resp, keystone_id, secret_id):
|
def on_delete(self, req, resp, keystone_id, secret_id):
|
||||||
|
|
||||||
|
@ -209,6 +209,14 @@ class InvalidContentType(BarbicanException):
|
|||||||
message = _("Invalid content type %(content_type)s")
|
message = _("Invalid content type %(content_type)s")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidContentEncoding(BarbicanException):
|
||||||
|
message = _("Invalid content encoding %(content_encoding)s")
|
||||||
|
|
||||||
|
|
||||||
|
class PayloadDecodingError(BarbicanException):
|
||||||
|
message = _("Error while attempting to decode payload.")
|
||||||
|
|
||||||
|
|
||||||
class BadRegistryConnectionConfiguration(BarbicanException):
|
class BadRegistryConnectionConfiguration(BarbicanException):
|
||||||
message = _("Registry was not configured correctly on API server. "
|
message = _("Registry was not configured correctly on API server. "
|
||||||
"Reason: %(reason)s")
|
"Reason: %(reason)s")
|
||||||
|
@ -16,9 +16,12 @@
|
|||||||
"""
|
"""
|
||||||
Shared business logic.
|
Shared business logic.
|
||||||
"""
|
"""
|
||||||
|
import base64
|
||||||
|
|
||||||
from barbican.common import exception, validators
|
from barbican.common import exception, validators
|
||||||
from barbican.model import models
|
from barbican.model import models
|
||||||
from barbican.common import utils
|
from barbican.common import utils
|
||||||
|
from barbican.crypto import mime_types
|
||||||
|
|
||||||
|
|
||||||
LOG = utils.getLogger(__name__)
|
LOG = utils.getLogger(__name__)
|
||||||
@ -51,15 +54,32 @@ def create_secret(data, tenant, crypto_manager,
|
|||||||
time_keeper.mark('after Secret model create')
|
time_keeper.mark('after Secret model create')
|
||||||
new_datum = None
|
new_datum = None
|
||||||
|
|
||||||
if 'plain_text' in data:
|
if 'payload' in data:
|
||||||
|
payload = data.get('payload')
|
||||||
plain_text = data['plain_text']
|
content_type = data.get('payload_content_type')
|
||||||
|
content_encoding = data.get('payload_content_encoding')
|
||||||
if not plain_text:
|
if not payload:
|
||||||
raise exception.NoDataToProcess()
|
raise exception.NoDataToProcess()
|
||||||
|
|
||||||
LOG.debug('Encrypting plain_text secret...')
|
LOG.debug('Pre-processing payload...')
|
||||||
new_datum = crypto_manager.encrypt(data['plain_text'],
|
if content_type in mime_types.PLAIN_TEXT:
|
||||||
|
LOG.debug('Plain text payload. No pre-processing needed.')
|
||||||
|
elif content_type in mime_types.BINARY:
|
||||||
|
# payload has to be decoded
|
||||||
|
if content_encoding not in ['base64']:
|
||||||
|
raise exception.InvalidContentEncoding(content_encoding)
|
||||||
|
try:
|
||||||
|
payload = base64.b64decode(payload)
|
||||||
|
except TypeError:
|
||||||
|
raise exception.PayloadDecodingError()
|
||||||
|
else:
|
||||||
|
raise exception.InvalidContentType()
|
||||||
|
|
||||||
|
time_keeper.mark('after pre-processing')
|
||||||
|
|
||||||
|
LOG.debug('Encrypting payload...')
|
||||||
|
new_datum = crypto_manager.encrypt(payload,
|
||||||
|
content_type,
|
||||||
new_secret,
|
new_secret,
|
||||||
tenant)
|
tenant)
|
||||||
time_keeper.mark('after encrypt')
|
time_keeper.mark('after encrypt')
|
||||||
@ -73,8 +93,6 @@ def create_secret(data, tenant, crypto_manager,
|
|||||||
else:
|
else:
|
||||||
LOG.debug('Creating metadata only for the new secret. '
|
LOG.debug('Creating metadata only for the new secret. '
|
||||||
'A subsequent PUT is required')
|
'A subsequent PUT is required')
|
||||||
crypto_manager.supports(new_secret, tenant)
|
|
||||||
time_keeper.mark('after supports check')
|
|
||||||
|
|
||||||
# Create Secret entities in datastore.
|
# Create Secret entities in datastore.
|
||||||
secret_repo.create_from(new_secret)
|
secret_repo.create_from(new_secret)
|
||||||
@ -97,34 +115,39 @@ def create_secret(data, tenant, crypto_manager,
|
|||||||
return new_secret
|
return new_secret
|
||||||
|
|
||||||
|
|
||||||
def create_encrypted_datum(secret, plain_text, tenant, crypto_manager,
|
def create_encrypted_datum(secret, payload,
|
||||||
|
content_type, content_encoding,
|
||||||
|
tenant, crypto_manager,
|
||||||
tenant_secret_repo, datum_repo):
|
tenant_secret_repo, datum_repo):
|
||||||
"""
|
"""
|
||||||
Modifies the secret to add the plain_text secret information.
|
Modifies the secret to add the plain_text secret information.
|
||||||
|
|
||||||
:param secret: the secret entity to associate the secret data to
|
:param secret: the secret entity to associate the secret data to
|
||||||
:param plain_text: plain-text of the secret data to store
|
:param payload: secret data to store
|
||||||
|
:param content_type: payload content mime type
|
||||||
|
:param content_encoding: payload content encoding
|
||||||
:param tenant: the tenant (entity) who owns the secret
|
:param tenant: the tenant (entity) who owns the secret
|
||||||
:param crypto_manager: the crypto plugin manager
|
:param crypto_manager: the crypto plugin manager
|
||||||
:param tenant_secret_repo: the tenant/secret association repository
|
:param tenant_secret_repo: the tenant/secret association repository
|
||||||
:param datum_repo: the encrypted datum repository
|
:param datum_repo: the encrypted datum repository
|
||||||
:retval The response body, None if N/A
|
:retval The response body, None if N/A
|
||||||
"""
|
"""
|
||||||
if not plain_text:
|
if not payload:
|
||||||
raise exception.NoDataToProcess()
|
raise exception.NoDataToProcess()
|
||||||
|
|
||||||
if validators.secret_too_big(plain_text):
|
if validators.secret_too_big(payload):
|
||||||
raise exception.LimitExceeded()
|
raise exception.LimitExceeded()
|
||||||
|
|
||||||
if secret.encrypted_data:
|
if secret.encrypted_data:
|
||||||
raise ValueError('Secret already has encrypted data stored for it.')
|
raise ValueError('Secret already has encrypted data stored for it.')
|
||||||
|
|
||||||
fields = secret.to_dict_fields()
|
fields = secret.to_dict_fields()
|
||||||
fields['plain_text'] = plain_text
|
fields['payload'] = payload
|
||||||
|
|
||||||
# Encrypt plain_text
|
# Encrypt payload
|
||||||
LOG.debug('Encrypting plain_text secret')
|
LOG.debug('Encrypting secret payload...')
|
||||||
new_datum = crypto_manager.encrypt(plain_text,
|
new_datum = crypto_manager.encrypt(payload,
|
||||||
|
content_type,
|
||||||
secret,
|
secret,
|
||||||
tenant)
|
tenant)
|
||||||
datum_repo.create_from(new_datum)
|
datum_repo.create_from(new_datum)
|
||||||
|
@ -18,7 +18,9 @@ Common utilities for Barbican.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
import barbican.openstack.common.log as logging
|
import barbican.openstack.common.log as logging
|
||||||
|
|
||||||
|
|
||||||
@ -54,6 +56,40 @@ def getLogger(name):
|
|||||||
return logging.getLogger(name)
|
return logging.getLogger(name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_accepted_encodings(req):
|
||||||
|
"""Returns a list of client acceptable encodings sorted by q value.
|
||||||
|
|
||||||
|
For details see: http://tools.ietf.org/html/rfc2616#section-14.3
|
||||||
|
|
||||||
|
:param req: request object
|
||||||
|
:returns: list of client acceptable encodings sorted by q value.
|
||||||
|
"""
|
||||||
|
header = req.get_header('Accept-Encoding')
|
||||||
|
if header is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
encodings = list()
|
||||||
|
for enc in header.split(','):
|
||||||
|
if ';' in enc:
|
||||||
|
encoding, q = enc.split(';')
|
||||||
|
try:
|
||||||
|
q = q.split('=')[1]
|
||||||
|
quality = float(q.strip())
|
||||||
|
except ValueError:
|
||||||
|
# can't convert quality to float
|
||||||
|
return None
|
||||||
|
if quality > 1.0 or quality < 0.0:
|
||||||
|
# quality is outside valid range
|
||||||
|
return None
|
||||||
|
if quality > 0.0:
|
||||||
|
encodings.append((encoding.strip(), quality))
|
||||||
|
else:
|
||||||
|
encodings.append((enc.strip(), 1))
|
||||||
|
|
||||||
|
return [enc[0] for enc in sorted(encodings,
|
||||||
|
cmp=lambda a, b: cmp(b[1], a[1]))]
|
||||||
|
|
||||||
|
|
||||||
class TimeKeeper(object):
|
class TimeKeeper(object):
|
||||||
"""
|
"""
|
||||||
Keeps track of elapsed times and then allows for dumping a smmary to
|
Keeps track of elapsed times and then allows for dumping a smmary to
|
||||||
|
@ -3,11 +3,14 @@ API JSON validators.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
|
||||||
import jsonschema as schema
|
import jsonschema as schema
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from barbican.common import exception
|
from barbican.common import exception
|
||||||
from barbican.openstack.common import timeutils
|
|
||||||
from barbican.common import utils
|
from barbican.common import utils
|
||||||
|
from barbican.crypto import mime_types
|
||||||
|
from barbican.openstack.common import timeutils
|
||||||
from barbican.openstack.common.gettextutils import _
|
from barbican.openstack.common.gettextutils import _
|
||||||
|
|
||||||
|
|
||||||
@ -71,16 +74,18 @@ class NewSecretValidator(ValidatorBase):
|
|||||||
"cypher_type": {"type": "string"},
|
"cypher_type": {"type": "string"},
|
||||||
"bit_length": {"type": "integer", "minimum": 0},
|
"bit_length": {"type": "integer", "minimum": 0},
|
||||||
"expiration": {"type": "string"},
|
"expiration": {"type": "string"},
|
||||||
"plain_text": {"type": "string"},
|
"payload": {"type": "string"},
|
||||||
"mime_type": {
|
"payload_content_type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
'enum': [
|
"enum": mime_types.SUPPORTED
|
||||||
'text/plain',
|
},
|
||||||
'application/octet-stream'
|
"payload_content_encoding": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"base64"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"required": ["mime_type"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def validate(self, json_data, parent_schema=None):
|
def validate(self, json_data, parent_schema=None):
|
||||||
@ -108,27 +113,46 @@ class NewSecretValidator(ValidatorBase):
|
|||||||
"before current time"))
|
"before current time"))
|
||||||
json_data['expiration'] = expiration
|
json_data['expiration'] = expiration
|
||||||
|
|
||||||
# Validate/convert 'plain_text' if provided.
|
# Validate/convert 'payload' if provided.
|
||||||
if 'plain_text' in json_data:
|
if 'payload' in json_data:
|
||||||
if json_data['mime_type'] != 'text/plain':
|
content_type = json_data.get('payload_content_type')
|
||||||
raise exception.InvalidObject(schema=schema_name,
|
if content_type is None:
|
||||||
reason=_("If 'plain_text' is "
|
raise exception.InvalidObject(
|
||||||
"supplied, 'mime_type' "
|
schema=schema_name,
|
||||||
"must be 'text/plain'"))
|
reason=_("If 'payload' is supplied, 'payload_content_type'"
|
||||||
|
" must also be supplied.")
|
||||||
|
)
|
||||||
|
|
||||||
plain_text = json_data['plain_text']
|
content_encoding = json_data.get('payload_content_encoding')
|
||||||
if secret_too_big(plain_text):
|
if content_type == 'application/octet-stream' and \
|
||||||
|
content_encoding is None:
|
||||||
|
raise exception.InvalidObject(
|
||||||
|
schema=schema_name,
|
||||||
|
reason=_("payload_content_encoding must be specified "
|
||||||
|
"when payload_content_type is application/"
|
||||||
|
"octet-stream.")
|
||||||
|
)
|
||||||
|
|
||||||
|
if content_type.startswith('text/plain') and \
|
||||||
|
content_encoding is not None:
|
||||||
|
raise exception.InvalidObject(
|
||||||
|
schema=schema_name,
|
||||||
|
reason=_("payload_content_encoding must not be specified "
|
||||||
|
"when payload_content_type is text/plain")
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = json_data['payload']
|
||||||
|
if secret_too_big(payload):
|
||||||
raise exception.LimitExceeded()
|
raise exception.LimitExceeded()
|
||||||
|
|
||||||
plain_text = plain_text.strip()
|
payload = payload.strip()
|
||||||
if not plain_text:
|
if not payload:
|
||||||
raise exception.InvalidObject(schema=schema_name,
|
raise exception.InvalidObject(schema=schema_name,
|
||||||
reason=_("If 'plain_text' "
|
reason=_("If 'payload' "
|
||||||
"specified, must be "
|
"specified, must be "
|
||||||
"non empty"))
|
"non empty"))
|
||||||
json_data['plain_text'] = plain_text
|
|
||||||
|
|
||||||
# TODO: Add validation of 'mime_type' based on loaded plugins.
|
json_data['payload'] = payload
|
||||||
|
|
||||||
return json_data
|
return json_data
|
||||||
|
|
||||||
@ -169,35 +193,29 @@ class NewOrderValidator(ValidatorBase):
|
|||||||
except schema.ValidationError as e:
|
except schema.ValidationError as e:
|
||||||
raise exception.InvalidObject(schema=schema_name, reason=str(e))
|
raise exception.InvalidObject(schema=schema_name, reason=str(e))
|
||||||
|
|
||||||
# If secret group is provided, validate it now.
|
secret = json_data.get('secret')
|
||||||
if 'secret' in json_data:
|
if secret is None:
|
||||||
secret = json_data['secret']
|
|
||||||
self.secret_validator.validate(secret, parent_schema=self.name)
|
|
||||||
if 'plain_text' in secret:
|
|
||||||
raise exception.InvalidObject(schema=schema_name,
|
|
||||||
reason=_("'plain_text' not "
|
|
||||||
"allowed for secret "
|
|
||||||
"generation"))
|
|
||||||
else:
|
|
||||||
raise exception.InvalidObject(schema=schema_name,
|
raise exception.InvalidObject(schema=schema_name,
|
||||||
reason=_("'secret' attributes "
|
reason=_("'secret' attributes "
|
||||||
"are required"))
|
"are required"))
|
||||||
|
# If secret group is provided, validate it now.
|
||||||
|
self.secret_validator.validate(secret, parent_schema=self.name)
|
||||||
|
if 'payload' in secret:
|
||||||
|
raise exception.InvalidObject(schema=schema_name,
|
||||||
|
reason=_("'payload' not "
|
||||||
|
"allowed for secret "
|
||||||
|
"generation"))
|
||||||
|
|
||||||
# Validation secret generation related fields.
|
# Validation secret generation related fields.
|
||||||
# TODO: Invoke the crypto plugin for this purpose
|
# TODO: Invoke the crypto plugin for this purpose
|
||||||
if secret.get('mime_type') != 'application/octet-stream':
|
|
||||||
raise exception.UnsupportedField(field="mime_type",
|
|
||||||
schema=schema_name,
|
|
||||||
reason=_("Only 'application/octe"
|
|
||||||
"t-stream' supported"))
|
|
||||||
|
|
||||||
if secret.get('cypher_type') != 'cbc':
|
if secret.get('cypher_type').lower() != 'cbc':
|
||||||
raise exception.UnsupportedField(field="cypher_type",
|
raise exception.UnsupportedField(field="cypher_type",
|
||||||
schema=schema_name,
|
schema=schema_name,
|
||||||
reason=_("Only 'cbc' "
|
reason=_("Only 'cbc' "
|
||||||
"supported"))
|
"supported"))
|
||||||
|
|
||||||
if secret.get('algorithm') != 'aes':
|
if secret.get('algorithm').lower() != 'aes':
|
||||||
raise exception.UnsupportedField(field="algorithm",
|
raise exception.UnsupportedField(field="algorithm",
|
||||||
schema=schema_name,
|
schema=schema_name,
|
||||||
reason=_("Only 'aes' "
|
reason=_("Only 'aes' "
|
||||||
|
@ -17,6 +17,7 @@ from oslo.config import cfg
|
|||||||
from stevedore import named
|
from stevedore import named
|
||||||
|
|
||||||
from barbican.common.exception import BarbicanException
|
from barbican.common.exception import BarbicanException
|
||||||
|
from barbican.crypto import mime_types
|
||||||
from barbican.model.models import EncryptedDatum
|
from barbican.model.models import EncryptedDatum
|
||||||
from barbican.openstack.common.gettextutils import _
|
from barbican.openstack.common.gettextutils import _
|
||||||
|
|
||||||
@ -64,12 +65,17 @@ class CryptoAcceptNotSupportedException(BarbicanException):
|
|||||||
class CryptoNoSecretOrDataException(BarbicanException):
|
class CryptoNoSecretOrDataException(BarbicanException):
|
||||||
"""Raised when secret information is not available for the specified
|
"""Raised when secret information is not available for the specified
|
||||||
secret mime-type."""
|
secret mime-type."""
|
||||||
def __init__(self, mime_type):
|
def __init__(self, secret_id):
|
||||||
super(CryptoNoSecretOrDataException, self).__init__(
|
super(CryptoNoSecretOrDataException, self).__init__(
|
||||||
_('No secret information available for '
|
_('No secret information available for '
|
||||||
'Mime Type of {0}').format(mime_type)
|
'secret {0}').format(secret_id)
|
||||||
)
|
)
|
||||||
self.mime_type = mime_type
|
self.secret_id = secret_id
|
||||||
|
|
||||||
|
|
||||||
|
class CryptoPluginNotFound(BarbicanException):
|
||||||
|
"""Raised when no plugins are installed."""
|
||||||
|
message = "Crypto plugin not found."
|
||||||
|
|
||||||
|
|
||||||
class CryptoExtensionManager(named.NamedExtensionManager):
|
class CryptoExtensionManager(named.NamedExtensionManager):
|
||||||
@ -83,68 +89,67 @@ class CryptoExtensionManager(named.NamedExtensionManager):
|
|||||||
invoke_kwds=invoke_kwargs
|
invoke_kwds=invoke_kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
def encrypt(self, unencrypted, secret, tenant):
|
def encrypt(self, unencrypted, content_type, secret, tenant):
|
||||||
"""Delegates encryption to active plugins."""
|
"""Delegates encryption to first active plugin."""
|
||||||
if secret.mime_type == 'text/plain':
|
if len(self.extensions) < 1:
|
||||||
|
raise CryptoPluginNotFound()
|
||||||
|
encrypting_plugin = self.extensions[0].obj
|
||||||
|
|
||||||
|
if content_type in mime_types.PLAIN_TEXT:
|
||||||
|
# normalize text to binary string
|
||||||
unencrypted = unencrypted.encode('utf-8')
|
unencrypted = unencrypted.encode('utf-8')
|
||||||
|
|
||||||
for ext in self.extensions:
|
datum = EncryptedDatum(secret)
|
||||||
if ext.obj.supports(secret.mime_type):
|
datum.content_type = content_type
|
||||||
datum = EncryptedDatum(secret)
|
datum.cypher_text, datum.kek_metadata = encrypting_plugin.encrypt(
|
||||||
datum.cypher_text, datum.kek_metadata = ext.obj.encrypt(
|
unencrypted, tenant
|
||||||
unencrypted, tenant
|
)
|
||||||
)
|
return datum
|
||||||
return datum
|
|
||||||
else:
|
|
||||||
raise CryptoMimeTypeNotSupportedException(secret.mime_type)
|
|
||||||
|
|
||||||
def decrypt(self, accept, secret, tenant):
|
def decrypt(self, accept, secret, tenant):
|
||||||
"""Delegates decryption to active plugins."""
|
"""Delegates decryption to active plugins."""
|
||||||
|
|
||||||
if not secret or not secret.encrypted_data:
|
if not secret or not secret.encrypted_data:
|
||||||
raise CryptoNoSecretOrDataException(accept)
|
raise CryptoNoSecretOrDataException(secret.id)
|
||||||
|
|
||||||
|
if not mime_types.is_supported(accept):
|
||||||
|
raise CryptoAcceptNotSupportedException(accept)
|
||||||
|
|
||||||
for ext in self.extensions:
|
for ext in self.extensions:
|
||||||
if ext.obj.supports(accept):
|
plugin = ext.obj
|
||||||
for datum in secret.encrypted_data:
|
for datum in secret.encrypted_data:
|
||||||
if accept == datum.mime_type:
|
if plugin.supports(datum.kek_metadata):
|
||||||
unencrypted = ext.obj.decrypt(datum.cypher_text,
|
unencrypted = plugin.decrypt(datum.cypher_text,
|
||||||
datum.kek_metadata,
|
datum.kek_metadata,
|
||||||
tenant)
|
tenant)
|
||||||
if accept == 'text/plain':
|
if datum.content_type in mime_types.PLAIN_TEXT:
|
||||||
unencrypted = unencrypted.decode('utf-8')
|
unencrypted = unencrypted.decode('utf-8')
|
||||||
return unencrypted
|
return unencrypted
|
||||||
else:
|
else:
|
||||||
raise CryptoAcceptNotSupportedException(accept)
|
raise CryptoPluginNotFound()
|
||||||
|
|
||||||
def generate_data_encryption_key(self, secret, tenant):
|
def generate_data_encryption_key(self, secret, tenant):
|
||||||
"""
|
"""
|
||||||
Delegates generating a data-encryption key to active plugins.
|
Delegates generating a data-encryption key to first active plugin.
|
||||||
|
|
||||||
Note that this key can be used by clients for their encryption
|
Note that this key can be used by clients for their encryption
|
||||||
processes. This generated key is then be encrypted via
|
processes. This generated key is then be encrypted via
|
||||||
the plug-in key encryption process, and that encrypted datum
|
the plug-in key encryption process, and that encrypted datum
|
||||||
is then returned from this method.
|
is then returned from this method.
|
||||||
"""
|
"""
|
||||||
for ext in self.extensions:
|
if len(self.extensions) < 1:
|
||||||
if ext.obj.supports(secret.mime_type):
|
raise CryptoPluginNotFound()
|
||||||
# TODO: Call plugin's key generation processes.
|
encrypting_plugin = self.extensions[0].obj
|
||||||
# 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.algorithm, secret.bit_length)
|
|
||||||
datum = EncryptedDatum(secret)
|
|
||||||
datum.cypher_text, datum.kek_metadata = ext.obj.encrypt(
|
|
||||||
data_key, tenant
|
|
||||||
)
|
|
||||||
return datum
|
|
||||||
else:
|
|
||||||
raise CryptoMimeTypeNotSupportedException(secret.mime_type)
|
|
||||||
|
|
||||||
def supports(self, secret, tenant):
|
# TODO: Call plugin's key generation processes.
|
||||||
"""Tests if at least one plug-in supports the secret type."""
|
# Note: It could be the *data* key to generate (for the
|
||||||
for ext in self.extensions:
|
# secret algo type) uses a different plug in than that
|
||||||
if ext.obj.supports(secret.mime_type):
|
# used to encrypted the key.
|
||||||
return True
|
|
||||||
else:
|
data_key = encrypting_plugin.create(secret.algorithm,
|
||||||
raise CryptoMimeTypeNotSupportedException(secret.mime_type)
|
secret.bit_length)
|
||||||
|
datum = EncryptedDatum(secret)
|
||||||
|
datum.cypher_text, datum.kek_metadata = encrypting_plugin.encrypt(
|
||||||
|
data_key, tenant
|
||||||
|
)
|
||||||
|
return datum
|
||||||
|
@ -17,6 +17,14 @@
|
|||||||
Barbican defined mime-types
|
Barbican defined mime-types
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Supported mime types
|
||||||
|
PLAIN_TEXT = ['text/plain',
|
||||||
|
'text/plain;charset=utf-8',
|
||||||
|
'text/plain; charset=utf-8']
|
||||||
|
BINARY = ['application/octet-stream']
|
||||||
|
ENCODINGS = ['base64']
|
||||||
|
SUPPORTED = PLAIN_TEXT + BINARY
|
||||||
|
|
||||||
# Maps mime-types used to specify secret data formats to the types that can
|
# Maps mime-types used to specify secret data formats to the types that can
|
||||||
# be requested for secrets via GET calls.
|
# be requested for secrets via GET calls.
|
||||||
CTYPES_PLAIN = {'default': 'text/plain'}
|
CTYPES_PLAIN = {'default': 'text/plain'}
|
||||||
@ -27,6 +35,10 @@ CTYPES_MAPPINGS = {'text/plain': CTYPES_PLAIN,
|
|||||||
'application/aes': CTYPES_AES}
|
'application/aes': CTYPES_AES}
|
||||||
|
|
||||||
|
|
||||||
|
def is_supported(mime_type):
|
||||||
|
return mime_type in SUPPORTED
|
||||||
|
|
||||||
|
|
||||||
def augment_fields_with_content_types(secret):
|
def augment_fields_with_content_types(secret):
|
||||||
"""Generate a dict of content types based on the data associated
|
"""Generate a dict of content types based on the data associated
|
||||||
with the specified secret."""
|
with the specified secret."""
|
||||||
@ -38,8 +50,10 @@ def augment_fields_with_content_types(secret):
|
|||||||
|
|
||||||
# TODO: How deal with merging more than one datum instance?
|
# TODO: How deal with merging more than one datum instance?
|
||||||
for datum in secret.encrypted_data:
|
for datum in secret.encrypted_data:
|
||||||
if datum.mime_type in CTYPES_MAPPINGS:
|
if datum.content_type in CTYPES_MAPPINGS:
|
||||||
fields.update({'content_types': CTYPES_MAPPINGS[datum.mime_type]})
|
fields.update(
|
||||||
|
{'content_types': CTYPES_MAPPINGS[datum.content_type]}
|
||||||
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
return fields
|
return fields
|
||||||
|
@ -68,15 +68,14 @@ class CryptoPluginBase(object):
|
|||||||
"""Create a new key."""
|
"""Create a new key."""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def supports(self, secret_type):
|
def supports(self, kek_metadata):
|
||||||
"""Whether the plugin supports the specified secret type."""
|
"""Used to decide whether the plugin supports decoding the secret."""
|
||||||
|
|
||||||
|
|
||||||
class SimpleCryptoPlugin(CryptoPluginBase):
|
class SimpleCryptoPlugin(CryptoPluginBase):
|
||||||
"""Insecure implementation of the crypto plugin."""
|
"""Insecure implementation of the crypto plugin."""
|
||||||
|
|
||||||
def __init__(self, conf=CONF):
|
def __init__(self, conf=CONF):
|
||||||
self.supported_types = ['text/plain', 'application/octet-stream']
|
|
||||||
self.kek = conf.simple_crypto_plugin.kek
|
self.kek = conf.simple_crypto_plugin.kek
|
||||||
self.block_size = AES.block_size
|
self.block_size = AES.block_size
|
||||||
|
|
||||||
@ -131,5 +130,6 @@ class SimpleCryptoPlugin(CryptoPluginBase):
|
|||||||
raise ValueError('At this time you must supply 128/192/256 as'
|
raise ValueError('At this time you must supply 128/192/256 as'
|
||||||
'bit length')
|
'bit length')
|
||||||
|
|
||||||
def supports(self, secret_type):
|
def supports(self, kek_metadata):
|
||||||
return secret_type in self.supported_types
|
metadata = json.loads(kek_metadata)
|
||||||
|
return metadata['plugin'] == 'SimpleCryptoPlugin'
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
"""bp/mime-type-revamp
|
||||||
|
|
||||||
|
Revision ID: b4ba6a2a663
|
||||||
|
Revises: 53c2ae2df15d
|
||||||
|
Create Date: 2013-07-26 15:25:44.193889
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'b4ba6a2a663'
|
||||||
|
down_revision = '53c2ae2df15d'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('encrypted_data', sa.Column('content_type',
|
||||||
|
sa.String(length=255),
|
||||||
|
nullable=True))
|
||||||
|
op.alter_column('secrets', u'mime_type',
|
||||||
|
existing_type=sa.VARCHAR(length=255),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('orders', u'secret_mime_type',
|
||||||
|
existing_type=sa.VARCHAR(length=255),
|
||||||
|
nullable=True)
|
||||||
|
### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('secrets', u'mime_type',
|
||||||
|
existing_type=sa.VARCHAR(length=255),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('orders', u'secret_mime_type',
|
||||||
|
existing_type=sa.VARCHAR(length=255),
|
||||||
|
nullable=True)
|
||||||
|
op.drop_column('encrypted_data', 'content_type')
|
||||||
|
### end Alembic commands ###
|
@ -191,7 +191,7 @@ class Secret(BASE, ModelBase):
|
|||||||
|
|
||||||
name = Column(String(255))
|
name = Column(String(255))
|
||||||
expiration = Column(DateTime, default=None)
|
expiration = Column(DateTime, default=None)
|
||||||
mime_type = Column(String(255), nullable=False)
|
mime_type = Column(String(255), nullable=True)
|
||||||
algorithm = Column(String(255))
|
algorithm = Column(String(255))
|
||||||
bit_length = Column(Integer)
|
bit_length = Column(Integer)
|
||||||
cypher_type = Column(String(255))
|
cypher_type = Column(String(255))
|
||||||
@ -204,15 +204,13 @@ class Secret(BASE, ModelBase):
|
|||||||
"""Creates secret from a dict."""
|
"""Creates secret from a dict."""
|
||||||
super(Secret, self).__init__()
|
super(Secret, self).__init__()
|
||||||
|
|
||||||
self.name = parsed_request.get('name', None)
|
self.name = parsed_request.get('name')
|
||||||
self.mime_type = parsed_request['mime_type']
|
self.expiration = parsed_request.get('expiration')
|
||||||
|
self.algorithm = parsed_request.get('algorithm')
|
||||||
self.expiration = parsed_request.get('expiration', None)
|
self.bit_length = parsed_request.get('bit_length')
|
||||||
self.algorithm = parsed_request.get('algorithm', None)
|
self.cypher_type = parsed_request.get('cypher_type')
|
||||||
self.bit_length = parsed_request.get('bit_length', None)
|
|
||||||
self.cypher_type = parsed_request.get('cypher_type', None)
|
|
||||||
|
|
||||||
self.status = States.ACTIVE
|
self.status = States.ACTIVE
|
||||||
|
self.mime_type = None
|
||||||
|
|
||||||
def _do_delete_children(self, session):
|
def _do_delete_children(self, session):
|
||||||
"""
|
"""
|
||||||
@ -226,7 +224,6 @@ class Secret(BASE, ModelBase):
|
|||||||
return {'secret_id': self.id,
|
return {'secret_id': self.id,
|
||||||
'name': self.name or self.id,
|
'name': self.name or self.id,
|
||||||
'expiration': self.expiration,
|
'expiration': self.expiration,
|
||||||
'mime_type': self.mime_type,
|
|
||||||
'algorithm': self.algorithm,
|
'algorithm': self.algorithm,
|
||||||
'bit_length': self.bit_length,
|
'bit_length': self.bit_length,
|
||||||
'cypher_type': self.cypher_type}
|
'cypher_type': self.cypher_type}
|
||||||
@ -244,7 +241,7 @@ class EncryptedDatum(BASE, ModelBase):
|
|||||||
|
|
||||||
secret_id = Column(String(36), ForeignKey('secrets.id'),
|
secret_id = Column(String(36), ForeignKey('secrets.id'),
|
||||||
nullable=False)
|
nullable=False)
|
||||||
|
content_type = Column(String(255))
|
||||||
mime_type = Column(String(255))
|
mime_type = Column(String(255))
|
||||||
cypher_text = Column(LargeBinary)
|
cypher_text = Column(LargeBinary)
|
||||||
kek_metadata = Column(Text)
|
kek_metadata = Column(Text)
|
||||||
@ -255,15 +252,14 @@ class EncryptedDatum(BASE, ModelBase):
|
|||||||
|
|
||||||
if secret:
|
if secret:
|
||||||
self.secret_id = secret.id
|
self.secret_id = secret.id
|
||||||
self.mime_type = secret.mime_type
|
|
||||||
|
|
||||||
self.status = States.ACTIVE
|
self.status = States.ACTIVE
|
||||||
|
|
||||||
def _do_extra_dict_fields(self):
|
def _do_extra_dict_fields(self):
|
||||||
"""Sub-class hook method: return dict of fields."""
|
"""Sub-class hook method: return dict of fields."""
|
||||||
return {'name': self.name,
|
return {'name': self.name,
|
||||||
'mime_type': self.mime_type,
|
|
||||||
'cypher_text': self.secret,
|
'cypher_text': self.secret,
|
||||||
|
'content_type': self.content_type,
|
||||||
'kek_metadata': self.kek_metadata}
|
'kek_metadata': self.kek_metadata}
|
||||||
|
|
||||||
|
|
||||||
@ -285,7 +281,7 @@ class Order(BASE, ModelBase):
|
|||||||
secret_algorithm = Column(String(255))
|
secret_algorithm = Column(String(255))
|
||||||
secret_bit_length = Column(Integer)
|
secret_bit_length = Column(Integer)
|
||||||
secret_cypher_type = Column(String(255))
|
secret_cypher_type = Column(String(255))
|
||||||
secret_mime_type = Column(String(255), nullable=False)
|
secret_mime_type = Column(String(255), nullable=True)
|
||||||
secret_expiration = Column(DateTime, default=None)
|
secret_expiration = Column(DateTime, default=None)
|
||||||
|
|
||||||
secret_id = Column(String(36), ForeignKey('secrets.id'),
|
secret_id = Column(String(36), ForeignKey('secrets.id'),
|
||||||
@ -294,7 +290,6 @@ class Order(BASE, ModelBase):
|
|||||||
def _do_extra_dict_fields(self):
|
def _do_extra_dict_fields(self):
|
||||||
"""Sub-class hook method: return dict of fields."""
|
"""Sub-class hook method: return dict of fields."""
|
||||||
return {'secret': {'name': self.secret_name or self.secret_id,
|
return {'secret': {'name': self.secret_name or self.secret_id,
|
||||||
'mime_type': self.secret_mime_type,
|
|
||||||
'algorithm': self.secret_algorithm,
|
'algorithm': self.secret_algorithm,
|
||||||
'bit_length': self.secret_bit_length,
|
'bit_length': self.secret_bit_length,
|
||||||
'cypher_type': self.secret_cypher_type,
|
'cypher_type': self.secret_cypher_type,
|
||||||
|
@ -13,16 +13,19 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from mock import MagicMock
|
import base64
|
||||||
import falcon
|
|
||||||
import json
|
import json
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
import falcon
|
||||||
|
import mock
|
||||||
|
from mock import MagicMock
|
||||||
|
|
||||||
from barbican.api import resources as res
|
from barbican.api import resources as res
|
||||||
from barbican.crypto.extension_manager import CryptoExtensionManager
|
|
||||||
from barbican.model import models
|
|
||||||
from barbican.common import exception as excep
|
from barbican.common import exception as excep
|
||||||
from barbican.common.validators import DEFAULT_MAX_SECRET_BYTES
|
from barbican.common.validators import DEFAULT_MAX_SECRET_BYTES
|
||||||
|
from barbican.crypto.extension_manager import CryptoExtensionManager
|
||||||
|
from barbican.model import models
|
||||||
from barbican.openstack.common import jsonutils
|
from barbican.openstack.common import jsonutils
|
||||||
|
|
||||||
|
|
||||||
@ -34,19 +37,18 @@ def suite():
|
|||||||
suite.addTest(WhenGettingSecretsListUsingSecretsResource())
|
suite.addTest(WhenGettingSecretsListUsingSecretsResource())
|
||||||
suite.addTest(WhenGettingPuttingOrDeletingSecretUsingSecretResource())
|
suite.addTest(WhenGettingPuttingOrDeletingSecretUsingSecretResource())
|
||||||
suite.addTest(WhenCreatingOrdersUsingOrdersResource())
|
suite.addTest(WhenCreatingOrdersUsingOrdersResource())
|
||||||
suite.addText(WhenGettingOrdersListUsingOrdersResource())
|
suite.addTest(WhenGettingOrdersListUsingOrdersResource())
|
||||||
suite.addTest(WhenGettingOrDeletingOrderUsingOrderResource())
|
suite.addTest(WhenGettingOrDeletingOrderUsingOrderResource())
|
||||||
|
|
||||||
return suite
|
return suite
|
||||||
|
|
||||||
|
|
||||||
def create_secret(mime_type, id="id", name="name",
|
def create_secret(id="id", name="name",
|
||||||
algorithm=None, bit_length=None,
|
algorithm=None, bit_length=None,
|
||||||
cypher_type=None, encrypted_datum=None):
|
cypher_type=None, encrypted_datum=None):
|
||||||
"""Generate a Secret entity instance."""
|
"""Generate a Secret entity instance."""
|
||||||
info = {'id': id,
|
info = {'id': id,
|
||||||
'name': name,
|
'name': name,
|
||||||
'mime_type': mime_type,
|
|
||||||
'algorithm': algorithm,
|
'algorithm': algorithm,
|
||||||
'bit_length': bit_length,
|
'bit_length': bit_length,
|
||||||
'cypher_type': cypher_type}
|
'cypher_type': cypher_type}
|
||||||
@ -56,14 +58,15 @@ def create_secret(mime_type, id="id", name="name",
|
|||||||
return secret
|
return secret
|
||||||
|
|
||||||
|
|
||||||
def create_order(mime_type, id="id", name="name",
|
def create_order(id="id",
|
||||||
algorithm=None, bit_length=None,
|
name="name",
|
||||||
|
algorithm=None,
|
||||||
|
bit_length=None,
|
||||||
cypher_type=None):
|
cypher_type=None):
|
||||||
"""Generate an Order entity instance."""
|
"""Generate an Order entity instance."""
|
||||||
order = models.Order()
|
order = models.Order()
|
||||||
order.id = id
|
order.id = id
|
||||||
order.secret_name = name
|
order.secret_name = name
|
||||||
order.secret_mime_type = mime_type
|
|
||||||
order.secret_algorithm = algorithm
|
order.secret_algorithm = algorithm
|
||||||
order.secret_bit_length = bit_length
|
order.secret_bit_length = bit_length
|
||||||
order.secret_cypher_type = cypher_type
|
order.secret_cypher_type = cypher_type
|
||||||
@ -95,18 +98,17 @@ class WhenTestingVersionResource(unittest.TestCase):
|
|||||||
class WhenCreatingSecretsUsingSecretsResource(unittest.TestCase):
|
class WhenCreatingSecretsUsingSecretsResource(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.name = 'name'
|
self.name = 'name'
|
||||||
self.plain_text = 'not-encrypted'.decode('utf-8')
|
self.payload = b'not-encrypted'
|
||||||
self.mime_type = 'text/plain'
|
self.payload_content_type = 'text/plain'
|
||||||
self.secret_algorithm = 'algo'
|
self.secret_algorithm = 'AES'
|
||||||
self.secret_bit_length = 512
|
self.secret_bit_length = 256
|
||||||
self.secret_cypher_type = 'cytype'
|
self.secret_cypher_type = 'CBC'
|
||||||
|
|
||||||
self.secret_req = {'name': self.name,
|
self.secret_req = {'name': self.name,
|
||||||
'mime_type': self.mime_type,
|
'payload': self.payload,
|
||||||
|
'payload_content_type': self.payload_content_type,
|
||||||
'algorithm': self.secret_algorithm,
|
'algorithm': self.secret_algorithm,
|
||||||
'bit_length': self.secret_bit_length,
|
'bit_length': self.secret_bit_length,
|
||||||
'cypher_type': self.secret_cypher_type,
|
'cypher_type': self.secret_cypher_type}
|
||||||
'plain_text': self.plain_text}
|
|
||||||
self.json = json.dumps(self.secret_req)
|
self.json = json.dumps(self.secret_req)
|
||||||
|
|
||||||
self.keystone_id = 'keystone1234'
|
self.keystone_id = 'keystone1234'
|
||||||
@ -153,16 +155,15 @@ class WhenCreatingSecretsUsingSecretsResource(unittest.TestCase):
|
|||||||
|
|
||||||
args, kwargs = self.secret_repo.create_from.call_args
|
args, kwargs = self.secret_repo.create_from.call_args
|
||||||
secret = args[0]
|
secret = args[0]
|
||||||
self.assertTrue(isinstance(secret, models.Secret))
|
self.assertIsInstance(secret, models.Secret)
|
||||||
self.assertEqual(secret.name, self.name)
|
self.assertEqual(secret.name, self.name)
|
||||||
self.assertEqual(secret.algorithm, self.secret_algorithm)
|
self.assertEqual(secret.algorithm, self.secret_algorithm)
|
||||||
self.assertEqual(secret.bit_length, self.secret_bit_length)
|
self.assertEqual(secret.bit_length, self.secret_bit_length)
|
||||||
self.assertEqual(secret.cypher_type, self.secret_cypher_type)
|
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
|
args, kwargs = self.tenant_secret_repo.create_from.call_args
|
||||||
tenant_secret = args[0]
|
tenant_secret = args[0]
|
||||||
self.assertTrue(isinstance(tenant_secret, models.TenantSecret))
|
self.assertIsInstance(tenant_secret, models.TenantSecret)
|
||||||
self.assertEqual(tenant_secret.tenant_id, self.tenant_entity_id)
|
self.assertEqual(tenant_secret.tenant_id, self.tenant_entity_id)
|
||||||
self.assertEqual(tenant_secret.secret_id, secret.id)
|
self.assertEqual(tenant_secret.secret_id, secret.id)
|
||||||
|
|
||||||
@ -170,7 +171,7 @@ class WhenCreatingSecretsUsingSecretsResource(unittest.TestCase):
|
|||||||
datum = args[0]
|
datum = args[0]
|
||||||
self.assertIsInstance(datum, models.EncryptedDatum)
|
self.assertIsInstance(datum, models.EncryptedDatum)
|
||||||
self.assertEqual('cypher_text', datum.cypher_text)
|
self.assertEqual('cypher_text', datum.cypher_text)
|
||||||
self.assertEqual(self.mime_type, datum.mime_type)
|
self.assertEqual(self.payload_content_type, datum.content_type)
|
||||||
self.assertIsNotNone(datum.kek_metadata)
|
self.assertIsNotNone(datum.kek_metadata)
|
||||||
|
|
||||||
def test_should_add_new_secret_with_expiration(self):
|
def test_should_add_new_secret_with_expiration(self):
|
||||||
@ -187,19 +188,19 @@ class WhenCreatingSecretsUsingSecretsResource(unittest.TestCase):
|
|||||||
expected = expiration[:-6].replace('12', '17', 1)
|
expected = expiration[:-6].replace('12', '17', 1)
|
||||||
self.assertEqual(expected, str(secret.expiration))
|
self.assertEqual(expected, str(secret.expiration))
|
||||||
|
|
||||||
def test_should_add_new_secret_tenant_not_exist(self):
|
def test_should_add_new_secret_if_tenant_does_not_exist(self):
|
||||||
self.tenant_repo.get.return_value = None
|
self.tenant_repo.get.return_value = None
|
||||||
|
|
||||||
self.resource.on_post(self.req, self.resp, self.keystone_id)
|
self.resource.on_post(self.req, self.resp, self.keystone_id)
|
||||||
|
|
||||||
args, kwargs = self.secret_repo.create_from.call_args
|
args, kwargs = self.secret_repo.create_from.call_args
|
||||||
secret = args[0]
|
secret = args[0]
|
||||||
self.assertTrue(isinstance(secret, models.Secret))
|
self.assertIsInstance(secret, models.Secret)
|
||||||
self.assertEqual(secret.name, self.name)
|
self.assertEqual(secret.name, self.name)
|
||||||
|
|
||||||
args, kwargs = self.tenant_secret_repo.create_from.call_args
|
args, kwargs = self.tenant_secret_repo.create_from.call_args
|
||||||
tenant_secret = args[0]
|
tenant_secret = args[0]
|
||||||
self.assertTrue(isinstance(tenant_secret, models.TenantSecret))
|
self.assertIsInstance(tenant_secret, models.TenantSecret)
|
||||||
self.assertEqual(self.tenant_entity_id, tenant_secret.tenant_id)
|
self.assertEqual(self.tenant_entity_id, tenant_secret.tenant_id)
|
||||||
self.assertEqual(secret.id, tenant_secret.secret_id)
|
self.assertEqual(secret.id, tenant_secret.secret_id)
|
||||||
|
|
||||||
@ -207,54 +208,52 @@ class WhenCreatingSecretsUsingSecretsResource(unittest.TestCase):
|
|||||||
datum = args[0]
|
datum = args[0]
|
||||||
self.assertTrue(isinstance(datum, models.EncryptedDatum))
|
self.assertTrue(isinstance(datum, models.EncryptedDatum))
|
||||||
self.assertEqual('cypher_text', datum.cypher_text)
|
self.assertEqual('cypher_text', datum.cypher_text)
|
||||||
self.assertEqual(self.mime_type, datum.mime_type)
|
self.assertEqual(self.payload_content_type, datum.content_type)
|
||||||
self.assertIsNotNone(datum.kek_metadata)
|
self.assertIsNotNone(datum.kek_metadata)
|
||||||
|
|
||||||
def test_should_add_new_secret_no_plain_text(self):
|
def test_should_add_new_secret_metadata_without_payload(self):
|
||||||
json_template = u'{{"name":"{0}", "mime_type":"{1}"}}'
|
self.stream.read.return_value = json.dumps({'name': self.name})
|
||||||
json = json_template.format(self.name, self.mime_type)
|
|
||||||
self.stream.read.return_value = json
|
|
||||||
|
|
||||||
self.resource.on_post(self.req, self.resp, self.keystone_id)
|
self.resource.on_post(self.req, self.resp, self.keystone_id)
|
||||||
|
|
||||||
args, kwargs = self.secret_repo.create_from.call_args
|
args, kwargs = self.secret_repo.create_from.call_args
|
||||||
secret = args[0]
|
secret = args[0]
|
||||||
self.assertTrue(isinstance(secret, models.Secret))
|
self.assertIsInstance(secret, models.Secret)
|
||||||
self.assertEqual(secret.name, self.name)
|
self.assertEqual(secret.name, self.name)
|
||||||
|
|
||||||
args, kwargs = self.tenant_secret_repo.create_from.call_args
|
args, kwargs = self.tenant_secret_repo.create_from.call_args
|
||||||
tenant_secret = args[0]
|
tenant_secret = args[0]
|
||||||
self.assertTrue(isinstance(tenant_secret, models.TenantSecret))
|
self.assertIsInstance(tenant_secret, models.TenantSecret)
|
||||||
self.assertEqual(tenant_secret.tenant_id, self.tenant_entity_id)
|
self.assertEqual(tenant_secret.tenant_id, self.tenant_entity_id)
|
||||||
self.assertEqual(tenant_secret.secret_id, secret.id)
|
self.assertEqual(tenant_secret.secret_id, secret.id)
|
||||||
|
|
||||||
self.assertFalse(self.datum_repo.create_from.called)
|
self.assertFalse(self.datum_repo.create_from.called)
|
||||||
|
|
||||||
def test_should_add_new_secret_plain_text_almost_too_large(self):
|
def test_should_add_new_secret_payload_almost_too_large(self):
|
||||||
big_text = ''.join(['A' for x
|
big_text = ''.join(['A' for x
|
||||||
in xrange(DEFAULT_MAX_SECRET_BYTES - 10)])
|
in xrange(DEFAULT_MAX_SECRET_BYTES - 10)])
|
||||||
|
|
||||||
self.secret_req = {'name': self.name,
|
secret_req = {'name': self.name,
|
||||||
'mime_type': self.mime_type,
|
'algorithm': self.secret_algorithm,
|
||||||
'algorithm': self.secret_algorithm,
|
'bit_length': self.secret_bit_length,
|
||||||
'bit_length': self.secret_bit_length,
|
'cypher_type': self.secret_cypher_type,
|
||||||
'cypher_type': self.secret_cypher_type,
|
'payload': big_text,
|
||||||
'plain_text': big_text}
|
'payload_content_type': 'text/plain'}
|
||||||
self.stream.read.return_value = json.dumps(self.secret_req)
|
self.stream.read.return_value = json.dumps(secret_req)
|
||||||
|
|
||||||
self.resource.on_post(self.req, self.resp, self.keystone_id)
|
self.resource.on_post(self.req, self.resp, self.keystone_id)
|
||||||
|
|
||||||
def test_should_fail_due_to_plain_text_too_large(self):
|
def test_should_fail_due_to_payload_too_large(self):
|
||||||
big_text = ''.join(['A' for x
|
big_text = ''.join(['A' for x
|
||||||
in xrange(DEFAULT_MAX_SECRET_BYTES + 10)])
|
in xrange(DEFAULT_MAX_SECRET_BYTES + 10)])
|
||||||
|
|
||||||
self.secret_req = {'name': self.name,
|
secret_req = {'name': self.name,
|
||||||
'mime_type': self.mime_type,
|
'algorithm': self.secret_algorithm,
|
||||||
'algorithm': self.secret_algorithm,
|
'bit_length': self.secret_bit_length,
|
||||||
'bit_length': self.secret_bit_length,
|
'cypher_type': self.secret_cypher_type,
|
||||||
'cypher_type': self.secret_cypher_type,
|
'payload': big_text,
|
||||||
'plain_text': big_text}
|
'payload_content_type': 'text/plain'}
|
||||||
self.stream.read.return_value = json.dumps(self.secret_req)
|
self.stream.read.return_value = json.dumps(secret_req)
|
||||||
|
|
||||||
with self.assertRaises(falcon.HTTPError) as cm:
|
with self.assertRaises(falcon.HTTPError) as cm:
|
||||||
self.resource.on_post(self.req, self.resp, self.keystone_id)
|
self.resource.on_post(self.req, self.resp, self.keystone_id)
|
||||||
@ -262,13 +261,28 @@ class WhenCreatingSecretsUsingSecretsResource(unittest.TestCase):
|
|||||||
exception = cm.exception
|
exception = cm.exception
|
||||||
self.assertEqual(falcon.HTTP_413, exception.status)
|
self.assertEqual(falcon.HTTP_413, exception.status)
|
||||||
|
|
||||||
def test_should_fail_due_to_empty_plain_text(self):
|
def test_should_fail_due_to_empty_payload(self):
|
||||||
|
secret_req = {'name': self.name,
|
||||||
|
'algorithm': self.secret_algorithm,
|
||||||
|
'bit_length': self.secret_bit_length,
|
||||||
|
'cypher_type': self.secret_cypher_type,
|
||||||
|
'payload': '',
|
||||||
|
'payload_content_type': 'text/plain'}
|
||||||
|
self.stream.read.return_value = json.dumps(secret_req)
|
||||||
|
|
||||||
|
with self.assertRaises(falcon.HTTPError) as cm:
|
||||||
|
self.resource.on_post(self.req, self.resp, self.keystone_id)
|
||||||
|
|
||||||
|
exception = cm.exception
|
||||||
|
self.assertEqual(falcon.HTTP_400, exception.status)
|
||||||
|
|
||||||
|
def test_should_fail_due_to_unsupported_payload_content_type(self):
|
||||||
self.secret_req = {'name': self.name,
|
self.secret_req = {'name': self.name,
|
||||||
'mime_type': self.mime_type,
|
'payload_content_type': 'somethingbogushere',
|
||||||
'algorithm': self.secret_algorithm,
|
'algorithm': self.secret_algorithm,
|
||||||
'bit_length': self.secret_bit_length,
|
'bit_length': self.secret_bit_length,
|
||||||
'cypher_type': self.secret_cypher_type,
|
'cypher_type': self.secret_cypher_type,
|
||||||
'plain_text': ''}
|
'payload': self.payload}
|
||||||
self.stream.read.return_value = json.dumps(self.secret_req)
|
self.stream.read.return_value = json.dumps(self.secret_req)
|
||||||
|
|
||||||
with self.assertRaises(falcon.HTTPError) as cm:
|
with self.assertRaises(falcon.HTTPError) as cm:
|
||||||
@ -277,20 +291,67 @@ class WhenCreatingSecretsUsingSecretsResource(unittest.TestCase):
|
|||||||
exception = cm.exception
|
exception = cm.exception
|
||||||
self.assertEqual(falcon.HTTP_400, exception.status)
|
self.assertEqual(falcon.HTTP_400, exception.status)
|
||||||
|
|
||||||
def test_should_fail_due_to_unsupported_mime(self):
|
def test_create_secret_with_encoded_binary_payload(self):
|
||||||
self.secret_req = {'name': self.name,
|
self.stream.read.return_value = json.dumps(
|
||||||
'mime_type': 'somethingbogushere',
|
{'name': self.name,
|
||||||
'algorithm': self.secret_algorithm,
|
'algorithm': self.secret_algorithm,
|
||||||
'bit_length': self.secret_bit_length,
|
'bit_length': self.secret_bit_length,
|
||||||
'cypher_type': self.secret_cypher_type,
|
'cypher_type': self.secret_cypher_type,
|
||||||
'plain_text': self.plain_text}
|
'payload': 'lOtfqHaUUpe6NqLABgquYQ==',
|
||||||
self.stream.read.return_value = json.dumps(self.secret_req)
|
'payload_content_type': 'application/octet-stream',
|
||||||
|
'payload_content_encoding': 'base64'}
|
||||||
|
)
|
||||||
|
|
||||||
with self.assertRaises(falcon.HTTPError) as cm:
|
self.resource.on_post(self.req, self.resp, self.keystone_id)
|
||||||
|
|
||||||
|
self.assertEqual(falcon.HTTP_201, self.resp.status)
|
||||||
|
|
||||||
|
args, kwargs = self.datum_repo.create_from.call_args
|
||||||
|
datum = args[0]
|
||||||
|
self.assertIsInstance(datum, models.EncryptedDatum)
|
||||||
|
self.assertEqual('cypher_text', datum.cypher_text)
|
||||||
|
self.assertEqual('application/octet-stream', datum.content_type)
|
||||||
|
self.assertIsNotNone(datum.kek_metadata)
|
||||||
|
|
||||||
|
def test_create_secret_fails_with_binary_payload_no_encoding(self):
|
||||||
|
self.stream.read.return_value = json.dumps(
|
||||||
|
{'name': self.name,
|
||||||
|
'algorithm': self.secret_algorithm,
|
||||||
|
'bit_length': self.secret_bit_length,
|
||||||
|
'cypher_type': self.secret_cypher_type,
|
||||||
|
'payload': 'lOtfqHaUUpe6NqLABgquYQ==',
|
||||||
|
'payload_content_type': 'application/octet-stream'}
|
||||||
|
)
|
||||||
|
|
||||||
|
with self.assertRaises(falcon.HTTPError):
|
||||||
self.resource.on_post(self.req, self.resp, self.keystone_id)
|
self.resource.on_post(self.req, self.resp, self.keystone_id)
|
||||||
|
|
||||||
exception = cm.exception
|
def test_create_secret_fails_with_binary_payload_bad_encoding(self):
|
||||||
self.assertEqual(falcon.HTTP_400, exception.status)
|
self.stream.read.return_value = json.dumps(
|
||||||
|
{'name': self.name,
|
||||||
|
'algorithm': self.secret_algorithm,
|
||||||
|
'bit_length': self.secret_bit_length,
|
||||||
|
'cypher_type': self.secret_cypher_type,
|
||||||
|
'payload': 'lOtfqHaUUpe6NqLABgquYQ==',
|
||||||
|
'payload_content_type': 'application/octet-stream',
|
||||||
|
'payload_content_encoding': 'bogus64'}
|
||||||
|
)
|
||||||
|
|
||||||
|
with self.assertRaises(falcon.HTTPError):
|
||||||
|
self.resource.on_post(self.req, self.resp, self.keystone_id)
|
||||||
|
|
||||||
|
def test_create_secret_fails_with_binary_payload_no_content_type(self):
|
||||||
|
self.stream.read.return_value = json.dumps(
|
||||||
|
{'name': self.name,
|
||||||
|
'algorithm': self.secret_algorithm,
|
||||||
|
'bit_length': self.secret_bit_length,
|
||||||
|
'cypher_type': self.secret_cypher_type,
|
||||||
|
'payload': 'lOtfqHaUUpe6NqLABgquYQ==',
|
||||||
|
'payload_content_encoding': 'base64'}
|
||||||
|
)
|
||||||
|
|
||||||
|
with self.assertRaises(falcon.HTTPError):
|
||||||
|
self.resource.on_post(self.req, self.resp, self.keystone_id)
|
||||||
|
|
||||||
|
|
||||||
class WhenGettingSecretsListUsingSecretsResource(unittest.TestCase):
|
class WhenGettingSecretsListUsingSecretsResource(unittest.TestCase):
|
||||||
@ -298,18 +359,15 @@ class WhenGettingSecretsListUsingSecretsResource(unittest.TestCase):
|
|||||||
self.tenant_id = 'tenant1234'
|
self.tenant_id = 'tenant1234'
|
||||||
self.keystone_id = 'keystone1234'
|
self.keystone_id = 'keystone1234'
|
||||||
self.name = 'name1234'
|
self.name = 'name1234'
|
||||||
self.mime_type = 'text/plain'
|
self.secret_algorithm = "AES"
|
||||||
self.secret_algorithm = "algo"
|
self.secret_bit_length = 256
|
||||||
self.secret_bit_length = 512
|
self.secret_cypher_type = "CBC"
|
||||||
self.secret_cypher_type = "cytype"
|
|
||||||
self.params = {'offset': 2, 'limit': 2}
|
|
||||||
|
|
||||||
self.num_secrets = 10
|
self.num_secrets = 10
|
||||||
self.offset = 2
|
self.offset = 2
|
||||||
self.limit = 2
|
self.limit = 2
|
||||||
|
|
||||||
secret_params = {'mime_type': self.mime_type,
|
secret_params = {'name': self.name,
|
||||||
'name': self.name,
|
|
||||||
'algorithm': self.secret_algorithm,
|
'algorithm': self.secret_algorithm,
|
||||||
'bit_length': self.secret_bit_length,
|
'bit_length': self.secret_bit_length,
|
||||||
'cypher_type': self.secret_cypher_type,
|
'cypher_type': self.secret_cypher_type,
|
||||||
@ -340,7 +398,8 @@ class WhenGettingSecretsListUsingSecretsResource(unittest.TestCase):
|
|||||||
|
|
||||||
self.req = MagicMock()
|
self.req = MagicMock()
|
||||||
self.req.accept = 'application/json'
|
self.req.accept = 'application/json'
|
||||||
self.req._params = self.params
|
self.req.get_param = mock.Mock()
|
||||||
|
self.req.get_param.side_effect = [self.offset, self.limit]
|
||||||
self.resp = MagicMock()
|
self.resp = MagicMock()
|
||||||
self.resource = res.SecretsResource(self.crypto_mgr, self.tenant_repo,
|
self.resource = res.SecretsResource(self.crypto_mgr, self.tenant_repo,
|
||||||
self.secret_repo,
|
self.secret_repo,
|
||||||
@ -352,10 +411,8 @@ class WhenGettingSecretsListUsingSecretsResource(unittest.TestCase):
|
|||||||
|
|
||||||
self.secret_repo.get_by_create_date \
|
self.secret_repo.get_by_create_date \
|
||||||
.assert_called_once_with(self.keystone_id,
|
.assert_called_once_with(self.keystone_id,
|
||||||
offset_arg=self.params.get('offset',
|
offset_arg=self.offset,
|
||||||
self.offset),
|
limit_arg=self.limit,
|
||||||
limit_arg=self.params.get('limit',
|
|
||||||
self.limit),
|
|
||||||
suppress_exception=True)
|
suppress_exception=True)
|
||||||
|
|
||||||
resp_body = jsonutils.loads(self.resp.body)
|
resp_body = jsonutils.loads(self.resp.body)
|
||||||
@ -382,10 +439,8 @@ class WhenGettingSecretsListUsingSecretsResource(unittest.TestCase):
|
|||||||
|
|
||||||
self.secret_repo.get_by_create_date \
|
self.secret_repo.get_by_create_date \
|
||||||
.assert_called_once_with(self.keystone_id,
|
.assert_called_once_with(self.keystone_id,
|
||||||
offset_arg=self.params.get('offset',
|
offset_arg=self.offset,
|
||||||
self.offset),
|
limit_arg=self.limit,
|
||||||
limit_arg=self.params.get('limit',
|
|
||||||
self.limit),
|
|
||||||
suppress_exception=True)
|
suppress_exception=True)
|
||||||
|
|
||||||
resp_body = jsonutils.loads(self.resp.body)
|
resp_body = jsonutils.loads(self.resp.body)
|
||||||
@ -408,22 +463,22 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(unittest.TestCase):
|
|||||||
self.tenant_id = 'tenantid1234'
|
self.tenant_id = 'tenantid1234'
|
||||||
self.keystone_id = 'keystone1234'
|
self.keystone_id = 'keystone1234'
|
||||||
self.name = 'name1234'
|
self.name = 'name1234'
|
||||||
self.mime_type = 'text/plain'
|
|
||||||
secret_id = "idsecret1"
|
secret_id = "idsecret1"
|
||||||
datum_id = "iddatum1"
|
datum_id = "iddatum1"
|
||||||
self.secret_algorithm = "algo"
|
|
||||||
self.secret_bit_length = 512
|
self.secret_algorithm = "AES"
|
||||||
self.secret_cypher_type = "cytype"
|
self.secret_bit_length = 256
|
||||||
|
self.secret_cypher_type = "CBC"
|
||||||
|
|
||||||
self.datum = models.EncryptedDatum()
|
self.datum = models.EncryptedDatum()
|
||||||
self.datum.id = datum_id
|
self.datum.id = datum_id
|
||||||
self.datum.secret_id = secret_id
|
self.datum.secret_id = secret_id
|
||||||
self.datum.mime_type = self.mime_type
|
self.datum.content_type = "text/plain"
|
||||||
self.datum.cypher_text = "cypher_text"
|
self.datum.cypher_text = "cypher_text"
|
||||||
self.datum.kek_metadata = "kekedata"
|
self.datum.kek_metadata = json.dumps({'plugin': 'TestCryptoPlugin'})
|
||||||
|
|
||||||
self.secret = create_secret(self.mime_type,
|
self.secret = create_secret(id=secret_id,
|
||||||
id=secret_id,
|
|
||||||
name=self.name,
|
name=self.name,
|
||||||
algorithm=self.secret_algorithm,
|
algorithm=self.secret_algorithm,
|
||||||
bit_length=self.secret_bit_length,
|
bit_length=self.secret_bit_length,
|
||||||
@ -476,9 +531,10 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(unittest.TestCase):
|
|||||||
self.assertEquals(self.resp.status, falcon.HTTP_200)
|
self.assertEquals(self.resp.status, falcon.HTTP_200)
|
||||||
|
|
||||||
resp_body = jsonutils.loads(self.resp.body)
|
resp_body = jsonutils.loads(self.resp.body)
|
||||||
self.assertTrue('content_types' in resp_body)
|
self.assertIn('content_types', resp_body)
|
||||||
self.assertTrue(self.datum.mime_type in
|
self.assertIn(self.datum.content_type,
|
||||||
resp_body['content_types'].itervalues())
|
resp_body['content_types'].itervalues())
|
||||||
|
self.assertNotIn('mime_type', resp_body)
|
||||||
|
|
||||||
def test_should_get_secret_as_plain(self):
|
def test_should_get_secret_as_plain(self):
|
||||||
self.req.accept = 'text/plain'
|
self.req.accept = 'text/plain'
|
||||||
@ -496,6 +552,29 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(unittest.TestCase):
|
|||||||
resp_body = self.resp.body
|
resp_body = self.resp.body
|
||||||
self.assertIsNotNone(resp_body)
|
self.assertIsNotNone(resp_body)
|
||||||
|
|
||||||
|
def test_should_get_secret_as_binary(self):
|
||||||
|
self.req.accept = 'application/octet-stream'
|
||||||
|
# mock Content-Encoding header
|
||||||
|
self.req.get_header.return_value = None
|
||||||
|
self.datum.content_type = 'application/octet-stream'
|
||||||
|
|
||||||
|
self.resource.on_get(self.req, self.resp, self.keystone_id,
|
||||||
|
self.secret.id)
|
||||||
|
|
||||||
|
self.assertEqual(self.resp.body, 'unencrypted_data')
|
||||||
|
|
||||||
|
def test_should_get_secret_as_encoded_binary(self):
|
||||||
|
encoded_data = base64.b64encode(b'unencrypted_data')
|
||||||
|
self.req.accept = 'application/octet-stream'
|
||||||
|
# mock Content-Encoding header
|
||||||
|
self.req.get_header.return_value = 'base64'
|
||||||
|
self.datum.content_type = 'application/octet-stream'
|
||||||
|
|
||||||
|
self.resource.on_get(self.req, self.resp, self.keystone_id,
|
||||||
|
self.secret.id)
|
||||||
|
|
||||||
|
self.assertEqual(self.resp.body, encoded_data)
|
||||||
|
|
||||||
def test_should_throw_exception_for_get_when_secret_not_found(self):
|
def test_should_throw_exception_for_get_when_secret_not_found(self):
|
||||||
self.secret_repo.get.return_value = None
|
self.secret_repo.get.return_value = None
|
||||||
|
|
||||||
@ -516,6 +595,17 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(unittest.TestCase):
|
|||||||
exception = cm.exception
|
exception = cm.exception
|
||||||
self.assertEqual(falcon.HTTP_406, exception.status)
|
self.assertEqual(falcon.HTTP_406, exception.status)
|
||||||
|
|
||||||
|
def test_should_throw_exeption_for_get_with_wrong_accept_encoding(self):
|
||||||
|
self.req.accept = 'application/octet-stream'
|
||||||
|
# mock Content-Encoding header
|
||||||
|
self.req.get_header.return_value = 'bogusencoding'
|
||||||
|
|
||||||
|
with self.assertRaises(falcon.HTTPError) as e:
|
||||||
|
self.resource.on_get(self.req, self.resp, self.keystone_id,
|
||||||
|
self.secret.id)
|
||||||
|
|
||||||
|
self.assertEqual(falcon.HTTP_406, e.exception.status)
|
||||||
|
|
||||||
def test_should_throw_exception_for_get_when_datum_not_available(self):
|
def test_should_throw_exception_for_get_when_datum_not_available(self):
|
||||||
self.req.accept = 'text/plain'
|
self.req.accept = 'text/plain'
|
||||||
self.secret.encrypted_data = []
|
self.secret.encrypted_data = []
|
||||||
@ -533,20 +623,52 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(unittest.TestCase):
|
|||||||
self.resource.on_put(self.req, self.resp, self.keystone_id,
|
self.resource.on_put(self.req, self.resp, self.keystone_id,
|
||||||
self.secret.id)
|
self.secret.id)
|
||||||
|
|
||||||
self.assertEquals(self.resp.status, falcon.HTTP_200)
|
self.assertEqual(self.resp.status, falcon.HTTP_200)
|
||||||
|
|
||||||
args, kwargs = self.datum_repo.create_from.call_args
|
args, kwargs = self.datum_repo.create_from.call_args
|
||||||
datum = args[0]
|
datum = args[0]
|
||||||
self.assertTrue(isinstance(datum, models.EncryptedDatum))
|
self.assertIsInstance(datum, models.EncryptedDatum)
|
||||||
self.assertEqual('cypher_text', datum.cypher_text)
|
self.assertEqual('cypher_text', datum.cypher_text)
|
||||||
self.assertEqual(self.mime_type, datum.mime_type)
|
|
||||||
self.assertIsNotNone(datum.kek_metadata)
|
self.assertIsNotNone(datum.kek_metadata)
|
||||||
|
|
||||||
|
def test_should_put_secret_as_binary(self):
|
||||||
|
self._setup_for_puts()
|
||||||
|
self.req.content_type = 'application/octet-stream'
|
||||||
|
|
||||||
|
self.resource.on_put(self.req, self.resp, self.keystone_id,
|
||||||
|
self.secret.id)
|
||||||
|
|
||||||
|
self.assertEqual(self.resp.status, falcon.HTTP_200)
|
||||||
|
|
||||||
|
args, kwargs = self.datum_repo.create_from.call_args
|
||||||
|
datum = args[0]
|
||||||
|
self.assertIsInstance(datum, models.EncryptedDatum)
|
||||||
|
|
||||||
|
def test_should_put_encoded_secret_as_binary(self):
|
||||||
|
self._setup_for_puts()
|
||||||
|
self.stream.read.return_value = base64.b64encode(self.payload)
|
||||||
|
self.req.content_type = 'application/octet-stream'
|
||||||
|
# mock Content-Encoding header
|
||||||
|
self.req.get_header.return_value = 'base64'
|
||||||
|
|
||||||
|
self.resource.on_put(self.req, self.resp, self.keystone_id,
|
||||||
|
self.secret.id)
|
||||||
|
|
||||||
|
self.assertEqual(self.resp.status, falcon.HTTP_200)
|
||||||
|
|
||||||
|
def test_should_fail_to_put_secret_with_unsupported_encoding(self):
|
||||||
|
self._setup_for_puts()
|
||||||
|
self.req.content_type = 'application/octet-stream'
|
||||||
|
# mock Content-Encoding header
|
||||||
|
self.req.get_header.return_value = 'bogusencoding'
|
||||||
|
|
||||||
|
with self.assertRaises(falcon.HTTPError):
|
||||||
|
self.resource.on_put(self.req, self.resp, self.keystone_id,
|
||||||
|
self.secret.id)
|
||||||
|
|
||||||
def test_should_fail_put_secret_as_json(self):
|
def test_should_fail_put_secret_as_json(self):
|
||||||
self._setup_for_puts()
|
self._setup_for_puts()
|
||||||
|
|
||||||
# Force error, as content_type of PUT doesn't match
|
|
||||||
# the secret's mime-type.
|
|
||||||
self.req.content_type = 'application/json'
|
self.req.content_type = 'application/json'
|
||||||
|
|
||||||
with self.assertRaises(falcon.HTTPError) as cm:
|
with self.assertRaises(falcon.HTTPError) as cm:
|
||||||
@ -569,7 +691,7 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(unittest.TestCase):
|
|||||||
exception = cm.exception
|
exception = cm.exception
|
||||||
self.assertEqual(falcon.HTTP_404, exception.status)
|
self.assertEqual(falcon.HTTP_404, exception.status)
|
||||||
|
|
||||||
def test_should_fail_put_secret_no_plain_text(self):
|
def test_should_fail_put_secret_no_payload(self):
|
||||||
self._setup_for_puts()
|
self._setup_for_puts()
|
||||||
|
|
||||||
# Force error due to no data passed in the request.
|
# Force error due to no data passed in the request.
|
||||||
@ -595,7 +717,7 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(unittest.TestCase):
|
|||||||
exception = cm.exception
|
exception = cm.exception
|
||||||
self.assertEqual(falcon.HTTP_409, exception.status)
|
self.assertEqual(falcon.HTTP_409, exception.status)
|
||||||
|
|
||||||
def test_should_fail_due_to_empty_plain_text(self):
|
def test_should_fail_due_to_empty_payload(self):
|
||||||
self._setup_for_puts()
|
self._setup_for_puts()
|
||||||
|
|
||||||
self.stream.read.return_value = ''
|
self.stream.read.return_value = ''
|
||||||
@ -640,9 +762,11 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(unittest.TestCase):
|
|||||||
self.assertEqual(falcon.HTTP_404, exception.status)
|
self.assertEqual(falcon.HTTP_404, exception.status)
|
||||||
|
|
||||||
def _setup_for_puts(self):
|
def _setup_for_puts(self):
|
||||||
self.plain_text = "plain_text"
|
self.payload = "plain_text"
|
||||||
self.req.accept = self.mime_type
|
self.req.accept = "text/plain"
|
||||||
self.req.content_type = self.mime_type
|
self.req.content_type = "text/plain"
|
||||||
|
# mock Content-Encoding header
|
||||||
|
self.req.get_header.return_value = None
|
||||||
|
|
||||||
self.secret.encrypted_data = []
|
self.secret.encrypted_data = []
|
||||||
|
|
||||||
@ -650,7 +774,7 @@ class WhenGettingPuttingOrDeletingSecretUsingSecretResource(unittest.TestCase):
|
|||||||
self.tenant_secret_repo.create_from.return_value = None
|
self.tenant_secret_repo.create_from.return_value = None
|
||||||
|
|
||||||
self.stream = MagicMock()
|
self.stream = MagicMock()
|
||||||
self.stream.read.return_value = self.plain_text
|
self.stream.read.return_value = self.payload
|
||||||
self.req.stream = self.stream
|
self.req.stream = self.stream
|
||||||
|
|
||||||
|
|
||||||
@ -745,8 +869,7 @@ class WhenGettingOrdersListUsingOrdersResource(unittest.TestCase):
|
|||||||
self.offset = 2
|
self.offset = 2
|
||||||
self.limit = 2
|
self.limit = 2
|
||||||
|
|
||||||
order_params = {'mime_type': self.mime_type,
|
order_params = {'name': self.name,
|
||||||
'name': self.name,
|
|
||||||
'algorithm': self.secret_algorithm,
|
'algorithm': self.secret_algorithm,
|
||||||
'bit_length': self.secret_bit_length,
|
'bit_length': self.secret_bit_length,
|
||||||
'cypher_type': self.secret_cypher_type}
|
'cypher_type': self.secret_cypher_type}
|
||||||
@ -834,8 +957,7 @@ class WhenGettingOrDeletingOrderUsingOrderResource(unittest.TestCase):
|
|||||||
self.tenant_keystone_id = 'keystoneid1234'
|
self.tenant_keystone_id = 'keystoneid1234'
|
||||||
self.requestor = 'requestor1234'
|
self.requestor = 'requestor1234'
|
||||||
|
|
||||||
self.order = create_order(id="id1", name="name",
|
self.order = create_order(id="id1", name="name")
|
||||||
mime_type="name")
|
|
||||||
|
|
||||||
self.order_repo = MagicMock()
|
self.order_repo = MagicMock()
|
||||||
self.order_repo.get.return_value = self.order
|
self.order_repo.get.return_value = self.order
|
||||||
|
0
barbican/tests/common/__init__.py
Normal file
0
barbican/tests/common/__init__.py
Normal file
52
barbican/tests/common/test_utils.py
Normal file
52
barbican/tests/common/test_utils.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from barbican.common import utils
|
||||||
|
|
||||||
|
|
||||||
|
class WhenTestingAcceptEncodingGetter(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.req = mock.Mock()
|
||||||
|
|
||||||
|
def test_parses_accept_encoding_header(self):
|
||||||
|
self.req.get_header.return_value = '*'
|
||||||
|
ae = utils.get_accepted_encodings(self.req)
|
||||||
|
self.req.get_header.assert_called_once_with('Accept-Encoding')
|
||||||
|
self.assertEqual(ae, ['*'])
|
||||||
|
|
||||||
|
def test_returns_none_for_empty_encoding(self):
|
||||||
|
self.req.get_header.return_value = None
|
||||||
|
ae = utils.get_accepted_encodings(self.req)
|
||||||
|
self.assertIsNone(ae)
|
||||||
|
|
||||||
|
def test_parses_single_accept_with_quality_value(self):
|
||||||
|
self.req.get_header.return_value = 'base64;q=0.7'
|
||||||
|
ae = utils.get_accepted_encodings(self.req)
|
||||||
|
self.assertEqual(ae, ['base64'])
|
||||||
|
|
||||||
|
def test_parses_more_than_one_encoding(self):
|
||||||
|
self.req.get_header.return_value = 'base64, gzip'
|
||||||
|
ae = utils.get_accepted_encodings(self.req)
|
||||||
|
self.assertEqual(ae, ['base64', 'gzip'])
|
||||||
|
|
||||||
|
def test_can_sort_by_quality_value(self):
|
||||||
|
self.req.get_header.return_value = 'base64;q=0.5, gzip;q=0.6, compress'
|
||||||
|
ae = utils.get_accepted_encodings(self.req)
|
||||||
|
self.assertEqual(ae, ['compress', 'gzip', 'base64'])
|
@ -31,18 +31,18 @@ class WhenTestingSecretValidator(unittest.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.name = 'name'
|
self.name = 'name'
|
||||||
self.plain_text = 'not-encrypted'.decode('utf-8')
|
self.payload = b'not-encrypted'
|
||||||
self.mime_type = 'text/plain'
|
self.payload_content_type = 'text/plain'
|
||||||
self.secret_algorithm = 'algo'
|
self.secret_algorithm = 'algo'
|
||||||
self.secret_bit_length = 512
|
self.secret_bit_length = 512
|
||||||
self.secret_cypher_type = 'cytype'
|
self.secret_cypher_type = 'cytype'
|
||||||
|
|
||||||
self.secret_req = {'name': self.name,
|
self.secret_req = {'name': self.name,
|
||||||
'mime_type': self.mime_type,
|
'payload_content_type': self.payload_content_type,
|
||||||
'algorithm': self.secret_algorithm,
|
'algorithm': self.secret_algorithm,
|
||||||
'bit_length': self.secret_bit_length,
|
'bit_length': self.secret_bit_length,
|
||||||
'cypher_type': self.secret_cypher_type,
|
'cypher_type': self.secret_cypher_type,
|
||||||
'plain_text': self.plain_text}
|
'payload': self.payload}
|
||||||
|
|
||||||
self.validator = validators.NewSecretValidator()
|
self.validator = validators.NewSecretValidator()
|
||||||
|
|
||||||
@ -57,17 +57,17 @@ class WhenTestingSecretValidator(unittest.TestCase):
|
|||||||
self.secret_req['name'] = ' '
|
self.secret_req['name'] = ' '
|
||||||
self.validator.validate(self.secret_req)
|
self.validator.validate(self.secret_req)
|
||||||
|
|
||||||
def test_should_validate_no_plain_text(self):
|
def test_should_validate_no_payload(self):
|
||||||
del self.secret_req['plain_text']
|
del self.secret_req['payload']
|
||||||
result = self.validator.validate(self.secret_req)
|
result = self.validator.validate(self.secret_req)
|
||||||
|
|
||||||
self.assertFalse('plain_text' in result)
|
self.assertFalse('payload' in result)
|
||||||
|
|
||||||
def test_should_validate_plain_text_with_whitespace(self):
|
def test_should_validate_payload_with_whitespace(self):
|
||||||
self.secret_req['plain_text'] = ' ' + self.plain_text + ' '
|
self.secret_req['payload'] = ' ' + self.payload + ' '
|
||||||
result = self.validator.validate(self.secret_req)
|
result = self.validator.validate(self.secret_req)
|
||||||
|
|
||||||
self.assertEqual(self.plain_text, result['plain_text'])
|
self.assertEqual(self.payload, result['payload'])
|
||||||
|
|
||||||
def test_should_validate_future_expiration(self):
|
def test_should_validate_future_expiration(self):
|
||||||
self.secret_req['expiration'] = '2114-02-28T19:14:44.180394'
|
self.secret_req['expiration'] = '2114-02-28T19:14:44.180394'
|
||||||
@ -128,24 +128,6 @@ class WhenTestingSecretValidator(unittest.TestCase):
|
|||||||
exception = e.exception
|
exception = e.exception
|
||||||
self.assertTrue('name' in str(exception))
|
self.assertTrue('name' in str(exception))
|
||||||
|
|
||||||
def test_should_fail_no_mime(self):
|
|
||||||
del self.secret_req['mime_type']
|
|
||||||
|
|
||||||
with self.assertRaises(excep.InvalidObject) as e:
|
|
||||||
self.validator.validate(self.secret_req)
|
|
||||||
|
|
||||||
exception = e.exception
|
|
||||||
self.assertTrue('mime_type' in str(exception))
|
|
||||||
|
|
||||||
def test_should_fail_invalid_mime(self):
|
|
||||||
self.secret_req['mime_type'] = 'application/octet-stream'
|
|
||||||
|
|
||||||
with self.assertRaises(excep.InvalidObject) as e:
|
|
||||||
self.validator.validate(self.secret_req)
|
|
||||||
|
|
||||||
exception = e.exception
|
|
||||||
self.assertTrue('plain_text' in str(exception))
|
|
||||||
|
|
||||||
def test_should_fail_negative_bit_length(self):
|
def test_should_fail_negative_bit_length(self):
|
||||||
self.secret_req['bit_length'] = -23
|
self.secret_req['bit_length'] = -23
|
||||||
|
|
||||||
@ -164,23 +146,14 @@ class WhenTestingSecretValidator(unittest.TestCase):
|
|||||||
exception = e.exception
|
exception = e.exception
|
||||||
self.assertTrue('bit_length' in str(exception))
|
self.assertTrue('bit_length' in str(exception))
|
||||||
|
|
||||||
def test_should_fail_empty_plain_text(self):
|
def test_validation_should_fail_with_empty_payload(self):
|
||||||
self.secret_req['plain_text'] = ' '
|
self.secret_req['payload'] = ' '
|
||||||
|
|
||||||
with self.assertRaises(excep.InvalidObject) as e:
|
with self.assertRaises(excep.InvalidObject) as e:
|
||||||
self.validator.validate(self.secret_req)
|
self.validator.validate(self.secret_req)
|
||||||
|
|
||||||
exception = e.exception
|
exception = e.exception
|
||||||
self.assertTrue('plain_text' in str(exception))
|
self.assertTrue('payload' in str(exception))
|
||||||
|
|
||||||
def test_should_fail_bad_mime(self):
|
|
||||||
self.secret_req['mime_type'] = 'badmime'
|
|
||||||
|
|
||||||
with self.assertRaises(excep.InvalidObject) as e:
|
|
||||||
self.validator.validate(self.secret_req)
|
|
||||||
|
|
||||||
exception = e.exception
|
|
||||||
self.assertTrue('mime_type' in str(exception))
|
|
||||||
|
|
||||||
def test_should_fail_already_expired(self):
|
def test_should_fail_already_expired(self):
|
||||||
self.secret_req['expiration'] = '2004-02-28T19:14:44.180394'
|
self.secret_req['expiration'] = '2004-02-28T19:14:44.180394'
|
||||||
@ -206,36 +179,53 @@ class WhenTestingSecretValidator(unittest.TestCase):
|
|||||||
'bit_length': None,
|
'bit_length': None,
|
||||||
'cypher_type': None}
|
'cypher_type': None}
|
||||||
|
|
||||||
with self.assertRaises(excep.InvalidObject) as e:
|
with self.assertRaises(excep.InvalidObject):
|
||||||
self.validator.validate(self.secret_req)
|
self.validator.validate(self.secret_req)
|
||||||
|
|
||||||
exception = e.exception
|
|
||||||
self.assertTrue('mime_type' in str(exception))
|
|
||||||
|
|
||||||
def test_should_fail_all_empties(self):
|
def test_should_fail_all_empties(self):
|
||||||
self.secret_req = {'name': '',
|
self.secret_req = {'name': '',
|
||||||
'algorithm': '',
|
'algorithm': '',
|
||||||
'bit_length': '',
|
'bit_length': '',
|
||||||
'cypher_type': ''}
|
'cypher_type': ''}
|
||||||
|
|
||||||
with self.assertRaises(excep.InvalidObject) as e:
|
with self.assertRaises(excep.InvalidObject):
|
||||||
self.validator.validate(self.secret_req)
|
self.validator.validate(self.secret_req)
|
||||||
|
|
||||||
exception = e.exception
|
def test_should_fail_no_payload_content_type(self):
|
||||||
self.assertTrue('mime_type' in str(exception))
|
del self.secret_req['payload_content_type']
|
||||||
|
|
||||||
|
with self.assertRaises(excep.InvalidObject):
|
||||||
|
self.validator.validate(self.secret_req)
|
||||||
|
|
||||||
|
def test_should_fail_with_plain_text_and_encoding(self):
|
||||||
|
self.secret_req['payload_content_encoding'] = 'base64'
|
||||||
|
|
||||||
|
with self.assertRaises(excep.InvalidObject):
|
||||||
|
self.validator.validate(self.secret_req)
|
||||||
|
|
||||||
|
def test_should_fail_with_wrong_encoding(self):
|
||||||
|
self.secret_req['payload_content_type'] = 'application/octet-stream'
|
||||||
|
self.secret_req['payload_content_encoding'] = 'unsupported'
|
||||||
|
|
||||||
|
with self.assertRaises(excep.InvalidObject):
|
||||||
|
self.validator.validate(self.secret_req)
|
||||||
|
|
||||||
|
def test_should_validate_with_wrong_encoding(self):
|
||||||
|
self.secret_req['payload_content_type'] = 'application/octet-stream'
|
||||||
|
self.secret_req['payload_content_encoding'] = 'base64'
|
||||||
|
|
||||||
|
self.validator.validate(self.secret_req)
|
||||||
|
|
||||||
|
|
||||||
class WhenTestingOrderValidator(unittest.TestCase):
|
class WhenTestingOrderValidator(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.name = 'name'
|
self.name = 'name'
|
||||||
self.mime_type = 'application/octet-stream'
|
|
||||||
self.secret_algorithm = 'aes'
|
self.secret_algorithm = 'aes'
|
||||||
self.secret_bit_length = 128
|
self.secret_bit_length = 128
|
||||||
self.secret_cypher_type = 'cbc'
|
self.secret_cypher_type = 'cbc'
|
||||||
|
|
||||||
self.secret_req = {'name': self.name,
|
self.secret_req = {'name': self.name,
|
||||||
'mime_type': self.mime_type,
|
|
||||||
'algorithm': self.secret_algorithm,
|
'algorithm': self.secret_algorithm,
|
||||||
'bit_length': self.secret_bit_length,
|
'bit_length': self.secret_bit_length,
|
||||||
'cypher_type': self.secret_cypher_type}
|
'cypher_type': self.secret_cypher_type}
|
||||||
@ -294,51 +284,6 @@ class WhenTestingOrderValidator(unittest.TestCase):
|
|||||||
exception = e.exception
|
exception = e.exception
|
||||||
self.assertTrue('cypher_type' in str(exception))
|
self.assertTrue('cypher_type' in str(exception))
|
||||||
|
|
||||||
def test_should_fail_no_mime(self):
|
|
||||||
del self.secret_req['mime_type']
|
|
||||||
|
|
||||||
with self.assertRaises(excep.InvalidObject) as e:
|
|
||||||
self.validator.validate(self.order_req)
|
|
||||||
|
|
||||||
exception = e.exception
|
|
||||||
self.assertTrue('mime_type' in str(exception))
|
|
||||||
|
|
||||||
def test_should_fail_bad_mime(self):
|
|
||||||
self.secret_req['mime_type'] = 'badmimehere'
|
|
||||||
|
|
||||||
with self.assertRaises(excep.InvalidObject) as e:
|
|
||||||
self.validator.validate(self.order_req)
|
|
||||||
|
|
||||||
exception = e.exception
|
|
||||||
self.assertTrue('mime_type' in str(exception))
|
|
||||||
|
|
||||||
def test_should_fail_wrong_mime(self):
|
|
||||||
self.secret_req['mime_type'] = 'text/plain'
|
|
||||||
|
|
||||||
with self.assertRaises(excep.UnsupportedField) as e:
|
|
||||||
self.validator.validate(self.order_req)
|
|
||||||
|
|
||||||
exception = e.exception
|
|
||||||
self.assertTrue('mime_type' in str(exception))
|
|
||||||
|
|
||||||
def test_should_fail_bad_mime_empty(self):
|
|
||||||
self.secret_req['mime_type'] = ''
|
|
||||||
|
|
||||||
with self.assertRaises(excep.InvalidObject) as e:
|
|
||||||
self.validator.validate(self.order_req)
|
|
||||||
|
|
||||||
exception = e.exception
|
|
||||||
self.assertTrue('mime_type' in str(exception))
|
|
||||||
|
|
||||||
def test_should_fail_bad_mime_whitespace(self):
|
|
||||||
self.secret_req['mime_type'] = ' '
|
|
||||||
|
|
||||||
with self.assertRaises(excep.InvalidObject) as e:
|
|
||||||
self.validator.validate(self.order_req)
|
|
||||||
|
|
||||||
exception = e.exception
|
|
||||||
self.assertTrue('mime_type' in str(exception))
|
|
||||||
|
|
||||||
def test_should_fail_negative_bit_length(self):
|
def test_should_fail_negative_bit_length(self):
|
||||||
self.secret_req['bit_length'] = -23
|
self.secret_req['bit_length'] = -23
|
||||||
|
|
||||||
@ -364,14 +309,14 @@ class WhenTestingOrderValidator(unittest.TestCase):
|
|||||||
exception = e.exception
|
exception = e.exception
|
||||||
self.assertTrue('secret' in str(exception))
|
self.assertTrue('secret' in str(exception))
|
||||||
|
|
||||||
def test_should_fail_plain_text_provided(self):
|
def test_should_fail_payload_provided(self):
|
||||||
self.secret_req['plain_text'] = ' '
|
self.secret_req['payload'] = ' '
|
||||||
|
|
||||||
with self.assertRaises(excep.InvalidObject) as e:
|
with self.assertRaises(excep.InvalidObject) as e:
|
||||||
self.validator.validate(self.order_req)
|
self.validator.validate(self.order_req)
|
||||||
|
|
||||||
exception = e.exception
|
exception = e.exception
|
||||||
self.assertTrue('plain_text' in str(exception))
|
self.assertTrue('payload' in str(exception))
|
||||||
|
|
||||||
def test_should_fail_already_expired(self):
|
def test_should_fail_already_expired(self):
|
||||||
self.secret_req['expiration'] = '2004-02-28T19:14:44.180394'
|
self.secret_req['expiration'] = '2004-02-28T19:14:44.180394'
|
||||||
@ -398,12 +343,9 @@ class WhenTestingOrderValidator(unittest.TestCase):
|
|||||||
'cypher_type': None}
|
'cypher_type': None}
|
||||||
self.order_req = {'secret': self.secret_req}
|
self.order_req = {'secret': self.secret_req}
|
||||||
|
|
||||||
with self.assertRaises(excep.InvalidObject) as e:
|
with self.assertRaises(excep.InvalidObject):
|
||||||
self.validator.validate(self.order_req)
|
self.validator.validate(self.order_req)
|
||||||
|
|
||||||
exception = e.exception
|
|
||||||
self.assertTrue('mime_type' in str(exception))
|
|
||||||
|
|
||||||
def test_should_fail_all_empties(self):
|
def test_should_fail_all_empties(self):
|
||||||
self.secret_req = {'name': '',
|
self.secret_req = {'name': '',
|
||||||
'algorithm': '',
|
'algorithm': '',
|
||||||
@ -411,12 +353,9 @@ class WhenTestingOrderValidator(unittest.TestCase):
|
|||||||
'cypher_type': ''}
|
'cypher_type': ''}
|
||||||
self.order_req = {'secret': self.secret_req}
|
self.order_req = {'secret': self.secret_req}
|
||||||
|
|
||||||
with self.assertRaises(excep.InvalidObject) as e:
|
with self.assertRaises(excep.InvalidObject):
|
||||||
self.validator.validate(self.order_req)
|
self.validator.validate(self.order_req)
|
||||||
|
|
||||||
exception = e.exception
|
|
||||||
self.assertTrue('mime_type' in str(exception))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
@ -30,13 +30,14 @@ class TestCryptoPlugin(CryptoPluginBase):
|
|||||||
return cypher_text, kek_metadata
|
return cypher_text, kek_metadata
|
||||||
|
|
||||||
def decrypt(self, encrypted, kek_metadata, tenant):
|
def decrypt(self, encrypted, kek_metadata, tenant):
|
||||||
return 'plain-data'
|
return b'unencrypted_data'
|
||||||
|
|
||||||
def create(self, algorithm, bit_length):
|
def create(self, algorithm, bit_length):
|
||||||
return "insecure_key"
|
return "insecure_key"
|
||||||
|
|
||||||
def supports(self, secret_type):
|
def supports(self, kek_metadata):
|
||||||
return secret_type == 'text/plain'
|
metadata = json.loads(kek_metadata)
|
||||||
|
return metadata['plugin'] == 'TestCryptoPlugin'
|
||||||
|
|
||||||
|
|
||||||
class WhenTestingSimpleCryptoPlugin(unittest.TestCase):
|
class WhenTestingSimpleCryptoPlugin(unittest.TestCase):
|
||||||
@ -104,3 +105,19 @@ class WhenTestingSimpleCryptoPlugin(unittest.TestCase):
|
|||||||
def test_create_unsupported_bit_key(self):
|
def test_create_unsupported_bit_key(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
self.plugin.create("aes", 129)
|
self.plugin.create("aes", 129)
|
||||||
|
|
||||||
|
def test_supports_decoding_metadata(self):
|
||||||
|
kek_metadata = json.dumps({
|
||||||
|
'plugin': 'SimpleCryptoPlugin',
|
||||||
|
'encryption': 'aes-128-cbc',
|
||||||
|
'kek': 'kek_id'
|
||||||
|
})
|
||||||
|
self.assertTrue(self.plugin.supports(kek_metadata))
|
||||||
|
|
||||||
|
def test_does_not_support_decoding_metadata(self):
|
||||||
|
kek_metadata = json.dumps({
|
||||||
|
'plugin': 'MuchFancierPlugin',
|
||||||
|
'encryption': 'aes-128-cbc',
|
||||||
|
'kek': 'kek_id'
|
||||||
|
})
|
||||||
|
self.assertFalse(self.plugin.supports(kek_metadata))
|
||||||
|
@ -15,22 +15,22 @@
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from barbican.model.models import Secret
|
from barbican.model import models
|
||||||
|
|
||||||
|
|
||||||
class WhenCreatingNewSecret(unittest.TestCase):
|
class WhenCreatingNewSecret(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.parsed_body = {'name': 'name',
|
self.parsed_secret = {'name': 'name',
|
||||||
'mime_type': 'text/plain',
|
'algorithm': 'algorithm',
|
||||||
'algorithm': 'algorithm',
|
'bit_length': 512,
|
||||||
'bit_length': 512,
|
'cypher_type': 'cypher_type',
|
||||||
'cypher_type': 'cypher_type',
|
'plain_text': 'not-encrypted'}
|
||||||
'plain_text': 'not-encrypted'}
|
|
||||||
|
self.parsed_order = {'secret': self.parsed_secret}
|
||||||
|
|
||||||
def test_new_secret_is_created_from_dict(self):
|
def test_new_secret_is_created_from_dict(self):
|
||||||
secret = Secret(self.parsed_body)
|
secret = models.Secret(self.parsed_secret)
|
||||||
self.assertEqual(secret.name, self.parsed_body['name'])
|
self.assertEqual(secret.name, self.parsed_secret['name'])
|
||||||
self.assertEqual(secret.mime_type, self.parsed_body['mime_type'])
|
self.assertEqual(secret.algorithm, self.parsed_secret['algorithm'])
|
||||||
self.assertEqual(secret.algorithm, self.parsed_body['algorithm'])
|
self.assertEqual(secret.bit_length, self.parsed_secret['bit_length'])
|
||||||
self.assertEqual(secret.bit_length, self.parsed_body['bit_length'])
|
self.assertEqual(secret.cypher_type, self.parsed_secret['cypher_type'])
|
||||||
self.assertEqual(secret.cypher_type, self.parsed_body['cypher_type'])
|
|
||||||
|
@ -13,9 +13,10 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from mock import MagicMock
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from mock import MagicMock
|
||||||
|
|
||||||
from barbican.crypto.extension_manager import CryptoExtensionManager
|
from barbican.crypto.extension_manager import CryptoExtensionManager
|
||||||
from barbican.tasks.resources import BeginOrder
|
from barbican.tasks.resources import BeginOrder
|
||||||
from barbican.model.models import (Tenant, Secret, TenantSecret,
|
from barbican.model.models import (Tenant, Secret, TenantSecret,
|
||||||
@ -39,10 +40,9 @@ class WhenBeginningOrder(unittest.TestCase):
|
|||||||
self.order.requestor = self.requestor
|
self.order.requestor = self.requestor
|
||||||
|
|
||||||
self.secret_name = "name"
|
self.secret_name = "name"
|
||||||
self.secret_algorithm = "algo"
|
self.secret_algorithm = "AES"
|
||||||
self.secret_bit_length = 512
|
self.secret_bit_length = 256
|
||||||
self.secret_cypher_type = "cytype"
|
self.secret_cypher_type = "CBC"
|
||||||
self.secret_mime_type = "text/plain"
|
|
||||||
self.secret_expiration = timeutils.utcnow()
|
self.secret_expiration = timeutils.utcnow()
|
||||||
|
|
||||||
self.keystone_id = 'keystone1234'
|
self.keystone_id = 'keystone1234'
|
||||||
@ -60,7 +60,6 @@ class WhenBeginningOrder(unittest.TestCase):
|
|||||||
self.order.secret_bit_length = self.secret_bit_length
|
self.order.secret_bit_length = self.secret_bit_length
|
||||||
self.order.secret_cypher_type = self.secret_cypher_type
|
self.order.secret_cypher_type = self.secret_cypher_type
|
||||||
self.order.secret_expiration = self.secret_expiration
|
self.order.secret_expiration = self.secret_expiration
|
||||||
self.order.secret_mime_type = self.secret_mime_type
|
|
||||||
|
|
||||||
self.order_repo = MagicMock()
|
self.order_repo = MagicMock()
|
||||||
self.order_repo.get.return_value = self.order
|
self.order_repo.get.return_value = self.order
|
||||||
@ -90,26 +89,25 @@ class WhenBeginningOrder(unittest.TestCase):
|
|||||||
self.order_repo.get \
|
self.order_repo.get \
|
||||||
.assert_called_once_with(entity_id=self.order.id,
|
.assert_called_once_with(entity_id=self.order.id,
|
||||||
keystone_id=self.keystone_id)
|
keystone_id=self.keystone_id)
|
||||||
assert self.order.status == States.ACTIVE
|
self.assertEqual(self.order.status, States.ACTIVE)
|
||||||
|
|
||||||
args, kwargs = self.secret_repo.create_from.call_args
|
args, kwargs = self.secret_repo.create_from.call_args
|
||||||
secret = args[0]
|
secret = args[0]
|
||||||
assert isinstance(secret, Secret)
|
self.assertIsInstance(secret, Secret)
|
||||||
assert secret.name == self.secret_name
|
self.assertEqual(secret.name, self.secret_name)
|
||||||
assert secret.expiration == self.secret_expiration
|
self.assertEqual(secret.expiration, self.secret_expiration)
|
||||||
|
|
||||||
args, kwargs = self.tenant_secret_repo.create_from.call_args
|
args, kwargs = self.tenant_secret_repo.create_from.call_args
|
||||||
tenant_secret = args[0]
|
tenant_secret = args[0]
|
||||||
assert isinstance(tenant_secret, TenantSecret)
|
self.assertIsInstance(tenant_secret, TenantSecret)
|
||||||
assert tenant_secret.tenant_id == self.tenant_id
|
self.assertEqual(tenant_secret.tenant_id, self.tenant_id)
|
||||||
assert tenant_secret.secret_id == secret.id
|
self.assertEqual(tenant_secret.secret_id, secret.id)
|
||||||
|
|
||||||
args, kwargs = self.datum_repo.create_from.call_args
|
args, kwargs = self.datum_repo.create_from.call_args
|
||||||
datum = args[0]
|
datum = args[0]
|
||||||
self.assertIsInstance(datum, EncryptedDatum)
|
self.assertIsInstance(datum, EncryptedDatum)
|
||||||
self.assertEqual(self.secret_mime_type, datum.mime_type)
|
self.assertIsNotNone(datum.cypher_text)
|
||||||
assert datum.cypher_text is not None
|
self.assertIsNotNone(datum.kek_metadata)
|
||||||
assert datum.kek_metadata is not None
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
Loading…
Reference in New Issue
Block a user