201 lines
6.7 KiB
Python
201 lines
6.7 KiB
Python
# Copyright (c) 2013-2014 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.
|
|
|
|
"""
|
|
API handler for Cloudkeep's Barbican
|
|
"""
|
|
import pecan
|
|
from webob import exc
|
|
from oslo.config import cfg
|
|
from pkgutil import simplegeneric
|
|
|
|
from barbican.common import exception
|
|
from barbican.common import utils
|
|
from barbican.crypto import extension_manager as em
|
|
from barbican.openstack.common import gettextutils as u
|
|
from barbican.openstack.common import jsonutils as json
|
|
from barbican.openstack.common import policy
|
|
|
|
|
|
LOG = utils.getLogger(__name__)
|
|
MAX_BYTES_REQUEST_INPUT_ACCEPTED = 15000
|
|
common_opts = [
|
|
cfg.IntOpt('max_allowed_request_size_in_bytes',
|
|
default=MAX_BYTES_REQUEST_INPUT_ACCEPTED),
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(common_opts)
|
|
|
|
|
|
class ApiResource(object):
|
|
"""Base class for API resources."""
|
|
pass
|
|
|
|
|
|
def load_body(req, resp=None, validator=None):
|
|
"""Helper function for loading an HTTP request body from JSON.
|
|
This body is placed into into a Python dictionary.
|
|
|
|
:param req: The HTTP request instance to load the body from.
|
|
:param resp: The HTTP response instance.
|
|
:param validator: The JSON validator to enforce.
|
|
:return: A dict of values from the JSON request.
|
|
"""
|
|
try:
|
|
body = req.body_file.read(CONF.max_allowed_request_size_in_bytes)
|
|
except IOError:
|
|
LOG.exception("Problem reading request JSON stream.")
|
|
pecan.abort(500, 'Read Error')
|
|
|
|
try:
|
|
#TODO(jwood): Investigate how to get UTF8 format via openstack
|
|
# jsonutils:
|
|
# parsed_body = json.loads(raw_json, 'utf-8')
|
|
parsed_body = json.loads(body)
|
|
strip_whitespace(parsed_body)
|
|
except ValueError:
|
|
LOG.exception("Problem loading request JSON.")
|
|
pecan.abort(400, 'Malformed JSON')
|
|
|
|
if validator:
|
|
try:
|
|
parsed_body = validator.validate(parsed_body)
|
|
except exception.InvalidObject as e:
|
|
LOG.exception("Failed to validate JSON information")
|
|
pecan.abort(400, str(e))
|
|
except exception.UnsupportedField as e:
|
|
LOG.exception("Provided field value is not supported")
|
|
pecan.abort(400, str(e))
|
|
except exception.LimitExceeded as e:
|
|
LOG.exception("Data limit exceeded")
|
|
pecan.abort(413, str(e))
|
|
|
|
return parsed_body
|
|
|
|
|
|
def generate_safe_exception_message(operation_name, excep):
|
|
"""Generates an exception message that is 'safe' for clients to consume.
|
|
|
|
A 'safe' message is one that doesn't contain sensitive information that
|
|
could be used for (say) cryptographic attacks on Barbican. That generally
|
|
means that em.CryptoXxxx should be captured here and with a simple
|
|
message created on behalf of them.
|
|
|
|
:param operation_name: Name of attempted operation, with a 'Verb noun'
|
|
format (e.g. 'Create Secret).
|
|
:param excep: The Exception instance that halted the operation.
|
|
:return: (status, message) where 'status' is one of the webob.exc.HTTP_xxx
|
|
codes, and 'message' is the sanitized message
|
|
associated with the error.
|
|
"""
|
|
message = None
|
|
reason = None
|
|
status = 500
|
|
|
|
try:
|
|
raise excep
|
|
except exc.HTTPError as f:
|
|
message = f.title
|
|
status = f.status
|
|
except policy.PolicyNotAuthorized:
|
|
message = u._('{0} attempt not allowed - '
|
|
'please review your '
|
|
'user/tenant privileges').format(operation_name)
|
|
status = 403
|
|
except em.CryptoContentTypeNotSupportedException as cctnse:
|
|
reason = u._("content-type of '{0}' not "
|
|
"supported").format(cctnse.content_type)
|
|
status = 400
|
|
except em.CryptoContentEncodingNotSupportedException as cc:
|
|
reason = u._("content-encoding of '{0}' not "
|
|
"supported").format(cc.content_encoding)
|
|
status = 400
|
|
except em.CryptoAcceptNotSupportedException as canse:
|
|
reason = u._("accept of '{0}' not "
|
|
"supported").format(canse.accept)
|
|
status = 406
|
|
except em.CryptoNoPayloadProvidedException:
|
|
reason = u._("No payload provided")
|
|
status = 400
|
|
except em.CryptoNoSecretOrDataFoundException:
|
|
reason = u._("Not Found. Sorry but your secret is in "
|
|
"another castle")
|
|
status = 404
|
|
except em.CryptoPayloadDecodingError:
|
|
reason = u._("Problem decoding payload")
|
|
status = 400
|
|
except em.CryptoContentEncodingMustBeBase64:
|
|
reason = u._("Text-based binary secret payloads must "
|
|
"specify a content-encoding of 'base64'")
|
|
status = 400
|
|
except em.CryptoAlgorithmNotSupportedException:
|
|
reason = u._("No plugin was found that supports the "
|
|
"requested algorithm")
|
|
status = 400
|
|
except em.CryptoSupportedPluginNotFound:
|
|
reason = u._("No plugin was found that could support "
|
|
"your request")
|
|
status = 400
|
|
except exception.NoDataToProcess:
|
|
reason = u._("No information provided to process")
|
|
status = 400
|
|
except exception.LimitExceeded:
|
|
reason = u._("Provided information too large "
|
|
"to process")
|
|
status = 413
|
|
except Exception:
|
|
message = u._('{0} failure seen - please contact site '
|
|
'administrator.').format(operation_name)
|
|
|
|
if reason:
|
|
message = u._('{0} issue seen - {1}.').format(operation_name,
|
|
reason)
|
|
|
|
return status, message
|
|
|
|
|
|
@simplegeneric
|
|
def get_items(obj):
|
|
"""This is used to get items from either
|
|
a list or a dictionary. While false
|
|
generator is need to process scalar object
|
|
"""
|
|
|
|
while False:
|
|
yield None
|
|
|
|
|
|
@get_items.register(dict)
|
|
def _json_object(obj):
|
|
return obj.iteritems()
|
|
|
|
|
|
@get_items.register(list)
|
|
def _json_array(obj):
|
|
return enumerate(obj)
|
|
|
|
|
|
def strip_whitespace(json_data):
|
|
"""This function will recursively trim values from the
|
|
object passed in using the get_items
|
|
"""
|
|
|
|
for key, value in get_items(json_data):
|
|
if hasattr(value, 'strip'):
|
|
json_data[key] = value.strip()
|
|
else:
|
|
strip_whitespace(value)
|