# 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
:Updated: $Date$
:Updater: $Author$
'''

from functools import wraps
from milla.auth import RequestValidator, NotAuthorized, permissions
import milla
import pkg_resources

__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)
    ep_name = request.config.get('request_validator', 'default')

    # Override the RequestVariable name with a class from the specified
    # entry point, if one is available. Otherwise, the default is used.
    for ep in pkg_resources.iter_entry_points(VALIDATOR_EP_GROUP, ep_name):
        try:
            RequestValidator = ep.load()
        except:
            continue

    try:
        validator = RequestValidator()
        validator.validate(request, requirement)
    except NotAuthorized as e:
        return e(request)
    return func(*args, **kwargs)

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

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 isinstance(req, basestring):
                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
