# Copyright 2011 Dustin C. Hatch
#
# 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.
'''Convenient decorators for enforcing authorization on controllers
:Created: Mar 3, 2011
:Author: dustin
'''
from functools import wraps
from milla.auth import permissions
import milla.auth
try:
import pkg_resources
except ImportError:
pkg_resources = None
__all__ = ['auth_required', 'require_perms']
VALIDATOR_EP_GROUP = 'milla.request_validator'
def _find_request(*args, **kwargs):
try:
return kwargs['request']
except KeyError:
for arg in args:
if isinstance(arg, milla.Request):
return arg
def _validate_request(func, requirement, *args, **kwargs):
request = _find_request(*args, **kwargs)
rv = request.config.get('request_validator', 'default')
if hasattr(rv, 'validate'):
# Config specifies a request validator class explicitly instead
# of an entry point name, so use it directly
validator = rv()
elif pkg_resources:
for ep in pkg_resources.iter_entry_points(VALIDATOR_EP_GROUP, rv):
try:
validator = ep.load()()
break
except:
# Ignore errors loading entry points or creating instances
continue
else:
# No entry point loaded or request validator instance
# created, use the default
validator = milla.auth.RequestValidator()
else:
# config does not specify a request validator class, and
# setuptools is not available, use the default
validator = milla.auth.RequestValidator()
try:
validator.validate(request, requirement)
except milla.auth.NotAuthorized as e:
return e(request)
return func(*args, **kwargs)
[docs]def auth_required(func):
'''Simple decorator to enforce authentication for a controller
Example usage::
class SomeController(object):
def __before__(request):
request.user = find_a_user_somehow(request)
@milla.auth_required
def __call__(request):
return 'Hello, world!'
In this example, the ``SomeController`` controller class implements
an ``__before__`` method that adds the ``user`` attribute to the
``request`` instance. This could be done by extracting user
information from the HTTP session, for example. The ``__call__``
method is decorated with ``auth_required``, which will ensure that
the user is successfully authenticated. This is handled by a
*request validator*.
If the request is not authorized, the decorated method will never
be called. Instead, the response is generated by calling the
:py:exc:`~milla.auth.NotAuthorized` exception raised inside
the ``auth_required`` decorator.
'''
@wraps(func)
def wrapper(*args, **kwargs):
return _validate_request(func, None, *args, **kwargs)
return wrapper
[docs]class require_perms(object):
'''Decorator that requires the user have certain permissions
Example usage::
class SomeController(object):
def __before__(request):
request.user = find_a_user_somehow(request)
@milla.require_perms('some_permission', 'and_this_permission')
def __call__(request):
return 'Hello, world!'
In this example, the ``SomeController`` controller class implements
an ``__before__`` method that adds the ``user`` attribute to the
``request`` instance. This could be done by extracting user
information from the HTTP session, for example. The ``__call__``
method is decorated with ``require_perms``, which will ensure that
the user is successfully authenticated and the the user has the
specified permissions. This is handled by a *request validator*.
There are two ways to specify the required permissions:
* By passing the string name of all required permissions as
positional arguments. A complex permission requirement will be
constructed that requires *all* of the given permissions to be
held by the user in order to validate
* By explicitly passing an instance of
:py:class:`~milla.auth.permissions.Permission` or
:py:class:`~milla.auth.permissions.PermissionRequirement`
'''
def __init__(self, *requirements):
requirement = None
for req in requirements:
if not hasattr(req, 'check'):
req = permissions.Permission(req)
if not requirement:
requirement = req
else:
requirement &= req
self.requirement = requirement
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
return _validate_request(func, self.requirement, *args, **kwargs)
return wrapper