a32f67967e
* Allow exceptions.handle to include user-defined exception types. Fixes bug 948536. * Adds a wrapper around the form/table header/table in the base _data_table.html template for easier targeting/styling. * Moves the rest of the main nav templating into a single template for easier customization. Fixes bug 948508. Change-Id: I045c29744ee01f60b080f2c1bbdc79ea5acf1b86
226 lines
7.9 KiB
Python
226 lines
7.9 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2012 Nebula, 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.
|
|
|
|
"""
|
|
Exceptions raised by the Horizon code and the machinery for handling them.
|
|
"""
|
|
|
|
import logging
|
|
import sys
|
|
|
|
from django.conf import settings
|
|
from django.contrib import messages
|
|
from django.utils.translation import ugettext as _
|
|
from cloudfiles import errors as swiftclient
|
|
from glance.common import exception as glanceclient
|
|
from keystoneclient import exceptions as keystoneclient
|
|
from novaclient import exceptions as novaclient
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class HorizonException(Exception):
|
|
""" Base exception class for distinguishing our own exception classes. """
|
|
pass
|
|
|
|
|
|
class Http302(HorizonException):
|
|
"""
|
|
Error class which can be raised from within a handler to cause an
|
|
early bailout and redirect at the middleware level.
|
|
"""
|
|
status_code = 302
|
|
|
|
def __init__(self, location, message=None):
|
|
self.location = location
|
|
self.message = message
|
|
|
|
|
|
class NotAuthorized(HorizonException):
|
|
"""
|
|
Raised whenever a user attempts to access a resource which they do not
|
|
have role-based access to (such as when failing the
|
|
:func:`~horizon.decorators.require_roles` decorator).
|
|
|
|
The included :class:`~horizon.middleware.HorizonMiddleware` catches
|
|
``NotAuthorized`` and handles it gracefully by displaying an error
|
|
message and redirecting the user to a login page.
|
|
"""
|
|
status_code = 401
|
|
|
|
|
|
class NotFound(HorizonException):
|
|
""" Generic error to replace all "Not Found"-type API errors. """
|
|
status_code = 404
|
|
|
|
|
|
class RecoverableError(HorizonException):
|
|
""" Generic error to replace any "Recoverable"-type API errors. """
|
|
status_code = 100 # HTTP status code "Continue"
|
|
|
|
|
|
class ServiceCatalogException(HorizonException):
|
|
"""
|
|
Raised when a requested service is not available in the ``ServiceCatalog``
|
|
returned by Keystone.
|
|
"""
|
|
def __init__(self, service_name):
|
|
message = 'Invalid service catalog service: %s' % service_name
|
|
super(ServiceCatalogException, self).__init__(message)
|
|
|
|
|
|
class AlreadyExists(HorizonException):
|
|
"""
|
|
Exception to be raised when trying to create an API resource which
|
|
already exists.
|
|
"""
|
|
def __init__(self, name, resource_type):
|
|
self.attrs = {"name": name, "resource": resource_type}
|
|
self.msg = 'A %(resource)s with the name "%(name)s" already exists.'
|
|
|
|
def __repr__(self):
|
|
return self.msg % self.attrs
|
|
|
|
def __unicode__(self):
|
|
return _(self.msg) % self.attrs
|
|
|
|
|
|
class HandledException(HorizonException):
|
|
"""
|
|
Used internally to track exceptions that have gone through
|
|
:func:`horizon.exceptions.handle` more than once.
|
|
"""
|
|
def __init__(self, wrapped):
|
|
self.wrapped = wrapped
|
|
|
|
|
|
HORIZON_CONFIG = getattr(settings, "HORIZON_CONFIG", {})
|
|
EXCEPTION_CONFIG = HORIZON_CONFIG.get("exceptions", {})
|
|
|
|
|
|
UNAUTHORIZED = (keystoneclient.Unauthorized,
|
|
keystoneclient.Forbidden,
|
|
novaclient.Unauthorized,
|
|
novaclient.Forbidden,
|
|
glanceclient.AuthorizationFailure,
|
|
glanceclient.NotAuthorized,
|
|
swiftclient.AuthenticationFailed,
|
|
swiftclient.AuthenticationError)
|
|
UNAUTHORIZED += tuple(EXCEPTION_CONFIG.get('unauthorized', []))
|
|
|
|
NOT_FOUND = (keystoneclient.NotFound,
|
|
novaclient.NotFound,
|
|
glanceclient.NotFound,
|
|
swiftclient.NoSuchContainer,
|
|
swiftclient.NoSuchObject)
|
|
NOT_FOUND += tuple(EXCEPTION_CONFIG.get('not_found', []))
|
|
|
|
|
|
# NOTE(gabriel): This is very broad, and may need to be dialed in.
|
|
RECOVERABLE = (keystoneclient.ClientException,
|
|
novaclient.ClientException,
|
|
glanceclient.GlanceException,
|
|
swiftclient.Error,
|
|
AlreadyExists)
|
|
RECOVERABLE += tuple(EXCEPTION_CONFIG.get('recoverable', []))
|
|
|
|
|
|
def handle(request, message=None, redirect=None, ignore=False, escalate=False):
|
|
""" Centralized error handling for Horizon.
|
|
|
|
Because Horizon consumes so many different APIs with completely
|
|
different ``Exception`` types, it's necessary to have a centralized
|
|
place for handling exceptions which may be raised.
|
|
|
|
Exceptions are roughly divided into 3 types:
|
|
|
|
#. ``UNAUTHORIZED``: Errors resulting from authentication or authorization
|
|
problems. These result in being logged out and sent to the login screen.
|
|
#. ``NOT_FOUND``: Errors resulting from objects which could not be
|
|
located via the API. These generally result in a user-facing error
|
|
message, but are otherwise returned to the normal code flow. Optionally
|
|
a redirect value may be passed to the error handler so users are
|
|
returned to a different view than the one requested in addition to the
|
|
error message.
|
|
#. RECOVERABLE: Generic API errors which generate a user-facing message
|
|
but drop directly back to the regular code flow.
|
|
|
|
All other exceptions bubble the stack as normal unless the ``ignore``
|
|
argument is passed in as ``True``, in which case only unrecognized
|
|
errors are bubbled.
|
|
|
|
If the exception is not re-raised, an appropriate wrapper exception
|
|
class indicating the type of exception that was encountered will be
|
|
returned.
|
|
"""
|
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
|
|
# Because the same exception may travel through this method more than
|
|
# once (if it's re-raised) we may want to treat it differently
|
|
# the second time (e.g. no user messages/logging).
|
|
handled = issubclass(exc_type, HandledException)
|
|
wrap = False
|
|
|
|
# Restore our original exception information, but re-wrap it at the end
|
|
if handled:
|
|
exc_type, exc_value, exc_traceback = exc_value.wrapped
|
|
wrap = True
|
|
|
|
# We trust messages from our own exceptions
|
|
if issubclass(exc_type, HorizonException):
|
|
message = exc_value
|
|
# If the message has a placeholder for the exception, fill it in
|
|
elif message and "%(exc)s" in message:
|
|
message = message % {"exc": exc_value}
|
|
|
|
if issubclass(exc_type, UNAUTHORIZED):
|
|
if ignore:
|
|
return NotAuthorized
|
|
request.session.clear()
|
|
if not handled:
|
|
LOG.debug("Unauthorized: %s" % exc_value)
|
|
# We get some pretty useless error messages back from
|
|
# some clients, so let's define our own fallback.
|
|
fallback = _("Unauthorized. Please try logging in again.")
|
|
messages.error(request, message or fallback, extra_tags="login")
|
|
raise NotAuthorized # Redirect handled in middleware
|
|
|
|
if issubclass(exc_type, NOT_FOUND):
|
|
wrap = True
|
|
if not ignore and not handled:
|
|
LOG.debug("Not Found: %s" % exc_value)
|
|
messages.error(request, message or exc_value)
|
|
if redirect:
|
|
raise Http302(redirect)
|
|
if not escalate:
|
|
return NotFound # return to normal code flow
|
|
|
|
if issubclass(exc_type, RECOVERABLE):
|
|
wrap = True
|
|
if not ignore and not handled:
|
|
LOG.debug("Recoverable error: %s" % exc_value)
|
|
messages.error(request, message or exc_value)
|
|
if redirect:
|
|
raise Http302(redirect)
|
|
if not escalate:
|
|
return RecoverableError # return to normal code flow
|
|
|
|
# If we've gotten here, time to wrap and/or raise our exception.
|
|
if wrap:
|
|
raise HandledException([exc_type, exc_value, exc_traceback])
|
|
raise exc_type, exc_value, exc_traceback
|