diff --git a/stackinabox/services/keystone/backend.py b/stackinabox/services/keystone/backend.py index 16cd304..b6c4baa 100644 --- a/stackinabox/services/keystone/backend.py +++ b/stackinabox/services/keystone/backend.py @@ -707,6 +707,6 @@ def validate_token_admin(self, token): return user_data except Exception as ex: - logger.debug('Error: {0}'.format(ex)) + logger.exception('Error: {0}'.format(ex)) raise KeystoneInvalidTokenError('Invalid Token') diff --git a/stackinabox/services/keystone/v2/__init__.py b/stackinabox/services/keystone/v2/__init__.py index 662c20f..ccbc516 100644 --- a/stackinabox/services/keystone/v2/__init__.py +++ b/stackinabox/services/keystone/v2/__init__.py @@ -121,6 +121,7 @@ def user_data_filter(user): return (200, headers, json.dumps({'users': []})) except Exception as ex: + logger.exception('User List Failure') return (401, headers, 'Not Authorized') else: return (403, headers, 'Forbidden') diff --git a/stackinabox/stack.py b/stackinabox/stack.py index 1f9072d..038a259 100644 --- a/stackinabox/stack.py +++ b/stackinabox/stack.py @@ -152,6 +152,9 @@ def call(self, method, request, uri, headers): service_caller_uri, headers) except Exception as ex: + logger.exception('StackInABox({0}): Service {1} - ' + 'Internal Failure' + .format(self.__id, service.name)) return (500, headers, 'Service Handler had an error: {0}'.format(ex)) diff --git a/stackinabox/util_httpretty.py b/stackinabox/util_httpretty.py index 097cc00..034f38a 100644 --- a/stackinabox/util_httpretty.py +++ b/stackinabox/util_httpretty.py @@ -8,6 +8,7 @@ from httpretty.http import HttpBaseClass from stackinabox.stack import StackInABox +from stackinabox.utils import CaseInsensitiveDict logger = logging.getLogger(__name__) @@ -15,10 +16,15 @@ def httpretty_callback(request, uri, headers): method = request.method + response_headers = CaseInsensitiveDict() + response_headers.update(headers) + request_headers = CaseInsensitiveDict() + request_headers.update(request.headers) + request.headers = request_headers return StackInABox.call_into(method, request, uri, - headers) + response_headers) def httpretty_registration(uri): diff --git a/stackinabox/util_requests_mock.py b/stackinabox/util_requests_mock.py index 0e90683..bb18187 100644 --- a/stackinabox/util_requests_mock.py +++ b/stackinabox/util_requests_mock.py @@ -21,6 +21,7 @@ import six from stackinabox.stack import StackInABox +from stackinabox.utils import CaseInsensitiveDict logger = logging.getLogger(__name__) @@ -65,7 +66,10 @@ def split_status(status): def handle(self, request, uri): method = request.method - headers = request.headers + headers = CaseInsensitiveDict() + request_headers = CaseInsensitiveDict() + request_headers.update(request.headers) + request.headers = request_headers stackinabox_result = StackInABox.call_into(method, request, uri, diff --git a/stackinabox/util_responses.py b/stackinabox/util_responses.py index 0682157..009d741 100644 --- a/stackinabox/util_responses.py +++ b/stackinabox/util_responses.py @@ -7,6 +7,7 @@ import responses from stackinabox.stack import StackInABox +from stackinabox.utils import CaseInsensitiveDict logger = logging.getLogger(__name__) @@ -14,7 +15,10 @@ def responses_callback(request): method = request.method - headers = request.headers + headers = CaseInsensitiveDict() + request_headers = CaseInsensitiveDict() + request_headers.update(request.headers) + request.headers = request_headers uri = request.url return StackInABox.call_into(method, request, diff --git a/stackinabox/utils/__init__.py b/stackinabox/utils/__init__.py new file mode 100644 index 0000000..7ff027e --- /dev/null +++ b/stackinabox/utils/__init__.py @@ -0,0 +1 @@ +from stackinabox.utils.caseinsensitivedict import CaseInsensitiveDict diff --git a/stackinabox/utils/caseinsensitivedict.py b/stackinabox/utils/caseinsensitivedict.py new file mode 100644 index 0000000..992a2c6 --- /dev/null +++ b/stackinabox/utils/caseinsensitivedict.py @@ -0,0 +1,93 @@ +# Copied from the Requests library by Kenneth Reitz et al. + +# Copyright 2013 Kenneth Reitz + +# 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 collections + + +# Compliments of Requests +class CaseInsensitiveDict(collections.MutableMapping): # pragma: no cover + """ + A case-insensitive ``dict``-like object. + + Implements all methods and operations of + ``collections.MutableMapping`` as well as dict's `copy`. Also + provides `lower_items`. + + All keys are expected to be strings. The structure remembers the + case of the last key to be set, and ``iter(instance)``, + ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` + will contain case-sensitive keys. However, querying and contains + testing is case insensitive: + + cid = CaseInsensitiveDict() + cid['Accept'] = 'application/json' + cid['aCCEPT'] == 'application/json' # True + list(cid) == ['Accept'] # True + + For example, ``headers['content-encoding']`` will return the + value of a ``'Content-Encoding'`` response header, regardless + of how the header name was originally stored. + + If the constructor, ``.update``, or equality comparison + operations are given keys that have equal ``.lower()``s, the + behavior is undefined. + + """ + def __init__(self, data=None, **kwargs): + self._store = dict() + if data is None: + data = {} + self.update(data, **kwargs) + + def __setitem__(self, key, value): + # Use the lowercased key for lookups, but store the actual + # key alongside the value. + self._store[key.lower()] = (key, value) + + def __getitem__(self, key): + return self._store[key.lower()][1] + + def __delitem__(self, key): + del self._store[key.lower()] + + def __iter__(self): + return (casedkey for casedkey, mappedvalue in self._store.values()) + + def __len__(self): + return len(self._store) + + def lower_items(self): + """Like iteritems(), but with all lowercase keys.""" + return ( + (lowerkey, keyval[1]) + for (lowerkey, keyval) + in self._store.items() + ) + + def __eq__(self, other): + if isinstance(other, collections.Mapping): + other = CaseInsensitiveDict(other) + else: + return NotImplemented + # Compare insensitively + return dict(self.lower_items()) == dict(other.lower_items()) + + # Copy is required + def copy(self): + return CaseInsensitiveDict(self._store.values()) + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, dict(self.items()))