barbican/barbican/api/__init__.py
Ryan Petrello daa0517b63 Implement the REST API with pecan.
Change-Id: I60665f8273a61303be98912e249b153d784a221b
2014-05-15 12:50:55 -05:00

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)