class Action(SimpleSeparated):
    """Actions are represented by period-separated sequences of elements
    (e.g. ``parcel.edit``, ``admin.assign-role``) with wildcard
    elements indicated by ``*`` (e.g. ``party.*``).  A list of
    *registered* actions is maintained to support permissions set
    queries to find the set of actions that are permitted on a
    specified object pattern.

    """
    separator = '.'

    registered = set()

    def register(action):
        """Action registration is used to support generating lists of
        permitted actions from a permission set and an object pattern.
        Only registered actions will be returned by such queries.

        """
        if isinstance(action, str):
            Action.register(Action(action))
        elif isinstance(action, Action):
            Action.registered.add(action)
        else:
            for a in action:
                Action.register(a) def permissioned_model(cls, perm_type=None, path_fields=None, actions=None):
    """Function to set up a model for permissioning.  Can either be called
    directly, passing a class and suitable values for ``perm_type``,
    ``path_fields`` and ``actions``, or can be used as a class
    decorator, taking values for ``perm_type``, ``path_fields`` and
    ``actions`` from the ``TutelaryMeta`` subclass of the decorated
    class.

    """
    if not issubclass(cls, models.Model):
        raise DecoratorException(
            'permissioned_model',
            "class '" + cls.__name__ + "' is not a Django model"
        )
    added = False
    try:
        if not hasattr(cls, 'TutelaryMeta'):
            if perm_type is None or path_fields is None or actions is None:
                raise DecoratorException(
                    'permissioned_model',
                    ("missing argument: all of perm_type, path_fields and " +
                     "actions must be supplied")
                )
            added = True
            cls.TutelaryMeta = type('TutelaryMeta', (object,),
                                    dict(perm_type=perm_type,
                                         path_fields=path_fields,
                                         actions=actions))
        cls.TutelaryMeta.pfs = ([cls.TutelaryMeta.perm_type] +
                                get_path_fields(cls))
        perms_objs = {}
        for a in cls.TutelaryMeta.actions:
            an = a
            ap = {}
            if isinstance(a, tuple):
                an = a[0]
                ap = a[1]
            Action.register(an)
            if isinstance(ap, dict) and 'permissions_object' in ap:
                po = ap['permissions_object']
                if po is not None:
                    try:
                        t = cls._meta.get_field(po).__class__
                        if t not in [models.ForeignKey,
                                     models.OneToOneField]:
                            raise PermissionObjectException(po)
                    except:
                        raise PermissionObjectException(po)
                perms_objs[an] = po
        if len(perms_objs) == 0:
            cls.get_permissions_object = get_perms_object
        else:
            cls.get_permissions_object = make_get_perms_object(perms_objs)
        return cls
    except:
        if added:
            del cls.TutelaryMeta
        raise def permission_required(*actions, obj=None, raise_exception=False):
    """Permission checking decorator -- works like the
    ``permission_required`` decorator in the default Django
    authentication system, except that it takes a sequence of actions
    to check, an object must be supplied, and the user must have
    permission to perform all of the actions on the given object for
    the permissions test to pass.  *Not actually sure how useful this
    is going to be: in any case where obj is not None, it's going to
    be tricky to get the object into the decorator.  Class-based views
    are definitely best here...*

    """
    def checker(user):
        ok = False
        if user.is_authenticated() and check_perms(user, actions, [obj]):
            ok = True
        if raise_exception and not ok:
            raise PermissionDenied
        else:
            return ok

    def decorator(view_func):
        @wraps(view_func, assigned=available_attrs(view_func))
        def _wrapped_view(request, *args, **kwargs):
            if checker(request.user):
                return view_func(request, *args, **kwargs)
        return _wrapped_view
    return decorator class PermissionRequiredMixin(BasePermissionRequiredMixin):
    """Permission checking mixin -- works just like the
    ``PermissionRequiredMixin`` in the default Django authentication
    system.

    """
    def has_permission(self):
        """Permission checking for "normal" Django."""
        objs = [None]
        if hasattr(self, 'get_perms_objects'):
            objs = self.get_perms_objects()
        else:
            if hasattr(self, 'get_object'):
                try:
                    objs = [self.get_object()]
                except Http404:
                    raise
                except:
                    pass
            if objs == [None]:
                objs = self.get_queryset()

        if (hasattr(self, 'permission_filter_queryset') and
           self.permission_filter_queryset is not False and
           self.request.method == 'GET'):
            if objs != [None]:
                self.perms_filter_queryset(objs)
            return True
        else:
            return check_perms(self.request.user,
                               self.get_permission_required(),
                               objs, self.request.method)

    def handle_no_permission(self):
        msg = self.get_permission_denied_message()
        if hasattr(self, 'raise_exception') and self.raise_exception:
            raise PermissionDenied(*msg)
        return msg

    def dispatch(self, request, *args, **kwargs):
        if not self.has_permission():
            return self.handle_no_permission()
        return super().dispatch(request, *args, **kwargs) class PermissionObjectException(TutelaryException):
    """Exception raised by the ``permissioned_model`` decorator if a
    ``permissions_object`` property in the ``actions`` list refers to
    a non-existent model field, or to a field that is not a foreign
    key or one-to-one relation field.

    """
    def __init__(self, prop):
        super().__init__(
            "invalid permissions_object property '" + prop +
            "' in permissioned_model"
        ) class DecoratorException(TutelaryException):
    """Exception raised if the ``permissioned_model`` decorator is used
    without the required ``TutelaryMeta`` class member being included
    in the model.

    """
    def __init__(self, decorator, msg):
        super().__init__(
            "error expanding decorator '" + decorator + "': " + msg
        ) class InvalidPermissionObjectException(TutelaryException):
    """Exception raised by authentication backend if the object passed to
    backend methods is not either a ``tutelary.engine.Object`` or a
    Django model instance with a ``get_permissions_object`` method.

    """
    def __init__(self):
        super().__init__("invalid object passed to django-tutelary " +
                         "backend method") class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User class PolicyFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Policy

    @classmethod
    def set_directory(cls, dir):
        cls.directory = dir

    @classmethod
    def _adjust_kwargs(cls, **kwargs):
        body_file = os.path.join(cls.directory, kwargs.pop('file', None))
        kwargs['body'] = open(body_file).read()
        return kwargs @pytest.fixture(scope="function")
def datadir(request, tmpdir):
    """
    Fixture responsible for searching a folder with the same name of test
    module and, if available, moving all contents to a temporary directory so
    tests can use them freely.

    Adapted from http://stackoverflow.com/questions/29627341
    """
    test_dir = py.path.local(request.module.__file__).new(ext='')
    try:
        for f in test_dir.listdir():
            f.copy(tmpdir)
    except:
        pass
    return tmpdir class CheckModel1(models.Model):
    name = models.CharField(max_length=100)

    class TutelaryMeta:
        perm_type = 'check'
        path_fields = ('name',)
        actions = [('check.list', {'permissions_object': None}),
                   ('check.create', {'permissions_object': None}),
                   ('check.detail',
                    {'error_message': 'detail view not allowed'}),
                   'check.delete']

    def __str__(self):
        return self.name class CheckModel2(models.Model):
    name = models.CharField(max_length=100)
    container = models.ForeignKey(CheckModel1)

    def __str__(self):
        return self.name class CheckModel3(models.Model):
    name = models.CharField(max_length=100)
    container = models.ForeignKey(CheckModel1)

    def __str__(self):
        return self.name class CheckModel4(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

    class TutelaryMeta:
        perm_type = 'check4'
        path_fields = ('name',)
        actions = [('check4.list', {'permissions_object': None}),
                   ('check4.create', {'permissions_object': None}),
                   'check4.detail',
                   'check4.delete'] class CheckModel5(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

    class TutelaryMeta:
        perm_type = 'check5'
        path_fields = ('name',)
        actions = ['check5.detail', 'check5.delete'] class CheckModel1Broken(models.Model):
    name = models.CharField(max_length=100)

    class TutelaryMeta:
        perm_type = 'check'
        path_fields = ('name',)
        actions = [('check.list', {'permissions_object': None}),
                   ('check.create', {'permissions_object': None}),
                   ('check.detail',
                    {'error_message': 'detail view not allowed'}),
                   'check.delete']

    def __str__(self):
        return self.name
from django.core.exceptions import PermissionDenied, ImproperlyConfigured
from django.contrib.auth import get_backends
from django.db import models
from django.http import HttpResponse
from django.test import RequestFactory
from tutelary.engine import Action
from tutelary.decorators import permissioned_model, permission_required
from tutelary.mixins import PermissionRequiredMixin
from tutelary.exceptions import (
    PermissionObjectException, DecoratorException,
    InvalidPermissionObjectException
)
from .factories import UserFactory, PolicyFactory
from .datadir import datadir  # noqa
from .check_models import (
    CheckModel1, CheckModel2, CheckModel3, CheckModel4, CheckModel5,
    CheckModel1Broken
)
import django.views.generic as generic
import django.views.generic.edit as edit
import pytest


def test_permission_object_exceptions(datadir, setup):  # noqa
    with pytest.raises(PermissionObjectException):
        permissioned_model(
            CheckModel3, perm_type='check3',
            path_fields=('container', 'pk'),
            actions=[('check3.list', {'permissions_object': 'not-there'}),
                     ('check3.create', {'permissions_object': 'container'}),
                     'check3.detail',
                     'check3.delete']
        )

    with pytest.raises(PermissionObjectException):
        permissioned_model(
            CheckModel3, perm_type='check3',
            path_fields=('container', 'pk'),
            actions=[('check3.list', {'permissions_object': 'name'}),
                     ('check3.create', {'permissions_object': 'container'}),
                     'check3.detail',
                     'check3.delete']
        )

    with pytest.raises(DecoratorException):
        class Dummy(models.Model):
            pass
        permissioned_model(
            Dummy, perm_type='dummy',
            path_fields=('name',)
        )

    with pytest.raises(DecoratorException):
        class Dummy:
            pass
        permissioned_model(
            Dummy, perm_type='dummy',
            path_fields=('name',),
            actions=['dummy.list']
        )


def test_mixin_null_perms_obj(datadir, setup):  # noqa
    class Check4ListView(PermissionRequiredMixin, CheckViewBase,
                         generic.ListView):
        permission_required = 'check4.list'

        def get_object(self):
            return self.obj

    class Check4CreateView(PermissionRequiredMixin, edit.CreateView):
        permission_required = 'check4.create'

        def __init__(self, obj, user):
            self.model = obj
            self.obj = obj
            self.request = RequestFactory().post('/check')
            self.request.user = user

        def get_object(self):
            return self.obj

    user1, user2 = setup
    obj = CheckModel4(name='visible')
    new_obj = CheckModel4(name='new-one')

    assert Check4ListView(obj, user1).has_permission()
    assert not Check4CreateView(new_obj, user1).has_permission()
    assert Check4ListView(obj, user2).has_permission()
    assert Check4CreateView(new_obj, user2).has_permission()


def test_mixin_no_permissions_object(datadir, setup):  # noqa
    class Check5DetailView(PermissionRequiredMixin, CheckViewBase,
                           generic.DetailView):
        permission_required = 'check5.detail'

        def get_object(self):
            return self.obj

    user1, user2 = setup
    obj = CheckModel5(name='check')

    assert not Check5DetailView(obj, user1).has_permission()
    assert Check5DetailView(obj, user2).has_permission()


@permission_required('check.list')
def func_view_ok(request):
    return HttpResponse("<html><body>Dummy response.</body></html>")


@permission_required('check3.create', raise_exception=True)
def func_view_bad(request):
    return HttpResponse("<html><body>Dummy response.</body></html>")


def test_function_view(datadir, setup):  # noqa
    user1, user2 = setup

    request = RequestFactory().get('/check')
    request.user = user1
    assert (func_view_ok(request).content ==
            b'<html><body>Dummy response.</body></html>')
    with pytest.raises(PermissionDenied):
        assert (func_view_bad(request).content ==
                b'<html><body>Dummy response.</body></html>')


def test_backend_exceptions(datadir, setup):  # noqa
    user1, user2 = setup
    ok_obj = CheckModel1(name='secret')
    broken_obj = CheckModel1Broken(name='broken')

    assert not user1.has_perm('check.detail', ok_obj)
    assert user2.has_perm('check.detail', ok_obj)
    with pytest.raises(InvalidPermissionObjectException):
        assert not user1.has_perm('check.detail', broken_obj)
    with pytest.raises(InvalidPermissionObjectException):
        assert user2.has_perm('check.detail', broken_obj)


    with pytest.raises(InvalidPermissionObjectException):