GithubHelp home page GithubHelp logo

dfunckt / django-rules Goto Github PK

View Code? Open in Web Editor NEW
1.8K 36.0 141.0 262 KB

Awesome Django authorization, without the database

License: MIT License

Python 99.79% Shell 0.20% HTML 0.01%
python django permissions rules predicates authorization

django-rules's Issues

Missing decorator for views?

In the documentation it's not specified how to limit a view to users that have object based permissions. Just like normally we can do:

@permission_required('app.action_object')
def my_view(request, ...):
    pass

I think that we should be able to somehow take incoming object IDs arguments and automatically block the request if the user does not meet the requirements.

For example, something along the lines of:

@permission_required('app.view_posts', 'post_id')
def view_post(request, post_id):
    pass

What do you think?

Correct usage of rules py in module

Assuming I have a django project directory like so:

AppA:
-#views
-#models
-#rules.py
etc

AppB
-#settings
-#wsgi
etc

Rules
-compat
-contrib
-templatetags
-#apps
etc

In App A I am defining predicates in the rules.py file, which has the absolute import from future at the top. I want to use these predicates in the App A views file, so I go from 'rules.contrib.views import permission_required'. This however tries to import contrib.views from AppA/rules.py, rather than from the Rules package. What am I doing wrong?

The documentation isn't super clear about this I don't think! I have 'rules.apps.AutodiscoverRulesConfig' in my installed apps.

Does django-rules work with django rest framework viewsets?

Hi,

I am trying to integrate rules with DRF's viewsets but I keep getting an AttributeError: 'OrgViewSet' object has no attribute 'request'

Here is my viewset code:

class OrgViewSet(PermissionRequiredMixin,
                 mixins.ListModelMixin,
                 viewsets.GenericViewSet):
    permission_required = CHANGE_ORG
    queryset = Org.objects.all()
    serializer_class = OrgSerializer

Here is the stack trace

Internal Server Error: /orgs/
Traceback (most recent call last):
  File "/Users/dre/code/treebeard/venv/lib/python3.6/site-packages/django/core/handlers/exception.py", line 41, in inner
    response = get_response(request)
  File "/Users/dre/code/treebeard/venv/lib/python3.6/site-packages/django/core/handlers/base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/Users/dre/code/treebeard/venv/lib/python3.6/site-packages/django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/dre/code/treebeard/venv/lib/python3.6/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File "/Users/dre/code/treebeard/venv/lib/python3.6/site-packages/rest_framework/viewsets.py", line 86, in view
    return self.dispatch(request, *args, **kwargs)
  File "/Users/dre/code/treebeard/venv/lib/python3.6/site-packages/django/contrib/auth/mixins.py", line 90, in dispatch
    if not self.has_permission():
  File "/Users/dre/code/treebeard/venv/lib/python3.6/site-packages/rules/contrib/views.py", line 51, in has_permission
    return self.request.user.has_perms(perms, obj)
AttributeError: 'OrgViewSet' object has no attribute 'request'

Any help would be greatly appreciated.

Autodiscover, how?

So I've read in the documentation that you can have rules autodiscover rule.py files in your applications. That is great, but the documentation doesn't clarify is both rules and rules.apps.AutodiscoverRulesConfig should be in INSTALLED_APPS.

  • If I add only rules, then rules.py files are not discovered
  • If I add both, then I get "ImproperlyConfigured: Application labels aren't unique, duplicates: rules"
  • If I only add rules.apps.AutodiscoverRulesConfig then it says that it cannot import predicate from rules in from rules import predicate.

Is this intended behavior?

Permission Required Decorator always return True

(sorry for my english)

Information:
Django 1.9

Settings:
AUTHENTICATION_BACKENDS = (

'django.contrib.auth.backends.ModelBackend',
'rules.permissions.ObjectPermissionBackend',

)

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rules.apps.AutodiscoverRulesConfig',
'debug_toolbar',

 Autre

]

rules.py files with the predicate and permission(add_perm)

views.py with import rules.py

when I use permission_required, it always return True, when I test the function(predicate) in the views for see the result with print, it's good but permission_required don't work.

ex:

If I have rules.py:
@rules.predicate
def is_author(user, book):
return book.author == user

rules.add_perm('edit_book', is_author)

if I used @permission_required('edit_book', fn=objectgetter(Book, 'id') it doesn't work but if I use in the views: is_author(object_user, object_book) it's work.

Some points about documentation

Thanks a lot for a great package, dfunkct!

The documentation is great, and I'm trying to get onboard. But even though a lot of things are pretty clear, certain things are still a bit confusing, and I think making them clearer in the documentation would really make this more approachable.

Namely;

  • You write rules.predicate and rules.add_rule, but at no point is there an import rules statement. For the decorator, this is kinda understandable, as it's common to import decorators from modules. But for add_rule, it wasn't clear what that variable was. Are these running on the module itself?
  • You mention that there are two rule sets, one shared one, and the other for Django. How do you access these? When I do rules.add_rule, which one does it get added to? Is the Django one implicit?
  • Where do we add these rules? Doing so in the view function is the most obvious choice. You mention adding a rule.py module in the app, which makes sense. What is the content of this module then? Just import rules, and then add the rules? And then do another import rules in views.py, and start using the decorators?

In general, I imagine many people will use rules from a Django app. A minimal example would really help. Also, a lot of info about settings things up is spread around the file. Installing using pip is a no-brainer. But there should also be a Django configuration section for things like:

  • Adding the app to INSTALLED_APPS
  • Adding rules to AUTHENTICATION_BACKENDS
  • Adding a rules.py file.

Sorry if this is long, but it's by no means a rant. On the contrary, I'm really excited about using this library, and I think it would really benefit from a few tweaks to the documentation!

Global superuser override?

I need to create a lot of rulesets in a lot of models, and it appears that I will need to add an is_superuser predicate to every single one, which is not very DRY. Is there a way (or a best practice) to grant full access to superusers on everything touched by rules?

rules.contrib.views.objectgetter() does not work with default view argument values

When using the permission_required decorator as follows:
@permission_required('perm_name', fn=objectgetter(model_name, view_arg)),
it is not possible to leverage default argument values as specified in the view declaration.

If for example, I declare my view as follows:
def edit_circle(request, cid=0):
...

When the view is called without the 'cid' argument, the following exception is raised:
ImproperlyConfigured: Argument cid is not available. Given arguments: []

Problem using template tags

I am having difficulty using template tags in my templates. For example this works

In the view:

from __future__                 import absolute_import
import rules
...
def detail(request, slug):
    obj = get_object_or_404(NewsStory, slug=slug)
    return render(request, 'news/detail.html', {
        'story': obj,
        'can_publish_newsstory': rules.has_perm('can_publish_newsstory', request.user)
        })

In the template

       {% if can_publish_newsstory %}
        <li><a href="{% url 'news:edit' story.slug %}" class="button expand secondary">Edit Story</a></li>
        <li><a href="{% url 'news:delete' story.slug %}" class="button expand secondary">Delete Story</a></li>
        {% endif %}

If I change this to remove 'can_publish_newsstory' from the view and include it in the template, nothing shows.

Revised Template

{% load rules %}
{% has_perm 'can_publish_newsstory' user as can_publish_newsstory %}
       {% if can_publish_newsstory %}
        <li><a href="{% url 'news:edit' story.slug %}" class="button expand secondary">Edit Story</a></li>
        <li><a href="{% url 'news:delete' story.slug %}" class="button expand secondary">Delete Story</a></li>
        {% endif %}

I am using Python 2.

Predicates do not include the tested permission in the context

I'm implementing a custom rule system where a user can have different answers to a permission depending on the obj that is supplied to the predicates. In order to check these permissions fully, I need access to the name of the rule being checked in my predicates.

Is this possible without making obj in my predicates a tuple containing the name as well as the object I want to check on?

is_group_member() factory has undesirable caching side effect

Hey. Rules is proving quite useful, so thanks!, but a head scratcher in a test suite for a django app I am building led me to this issue.

In your built in factory is_group_member() you have the following code just before returning:

if not hasattr(user, '_group_names_cache'):  # pragma: no cover
    user._group_names_cache = set(user.groups.values_list('name', flat=True))

I'm not sure why you are caching the user's groups other than to save possible DB look ups in the future? A side effect is that if a user is removed from a group while your _group_names_cache attribute is in existence rules based on group membership will miss that change, as you've replaced a callable with a cached attribute. This results in unexpected rule failure. I'd suggest removing the attribute creation but you may other reasons for its existence?

To confirm:

  1. give a user membership in a group
  2. use is_group_member() to create a predicate for membership in that group
  3. test the new predicate - it returns True
  4. remove the user from the group
  5. test the predicate again - it still returns True, but should return False
  6. verify inconsistency by comparing user.groups.all() with user._group_names_cache

django-rules does not work with DjangoObjectPermissions from django rest framework

My problem is accurately described by the comment I found here https://groups.google.com/forum/#!topic/django-rest-framework/M5q6pI8vcZQ:

I have been using DjangoObjectPermissions which inherits from DjangoModelPermissions. DjangoModelPermissions has a method has_permission which checks for Model level permissions. However, if you are working with object level permissions, usually the model level permissions evaluate to false. Consequently DjangoObjectPermissions raises a permission denied.

I have easily fixed that issue be overwriting the has_permission method of DjangoObjectPermissions, but again I am wondering, have I missed anything or should that not be the default behavior of DjangoObjectPermissions.

I have a code like this:

# rules.py

import rules


############################
# Predicates
############################

@rules.predicate
def is_tweet_owner(user, tweet):
    if not tweet:
        return False
    return tweet.owner == user


############################
# Permissions
############################

rules.add_perm('tweets.add_tweet', rules.is_authenticated)
rules.add_perm('tweets.change_tweet', is_tweet_owner)
rules.add_perm('tweets.delete_tweet', is_tweet_owner)


# views.py


from rest_framework import viewsets, mixins

from .models import Tweet
from .permissions import TweetPermission
from .serializers import TweetSerializer


class TweetViewSet(mixins.CreateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   viewsets.GenericViewSet):
    queryset = Tweet.objects.all()
    serializer_class = TweetSerializer
    permission_classes = (TweetPermission, )

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)


# permissions.py


from rest_framework import permissions


class TweetPermission(permissions.DjangoObjectPermissions):
    authenticated_users_only = False

    def has_permission(self, request, view):
        """
        WTF?!
        """
        return True

I want the tweet can be updated or deleted only by the user that owns this tweet. But user.has_perms method will always return false, because django calls has_permission method before has_object_permission.

So, django will call this:

>>> user.has_perms(['tweets.change_tweet'])
False

But I want this:

>>> user.has_perms(['tweets.change_tweet'], tweet)
True

rules.compat not in the setup packages

pip install git+https://github.com/dfunckt/django-rules.git@master won't install the compat package.
As a result, doing something like from rules.contrib.views import LoginRequiredMixin will fail.

Support permission-based queryset filters

I really like how rules works for individual object permissions, but it doesn't really cover permission-based queryset filtering (which comes naturally for database-centric permission systems like django-guardian). While thinking about how to compensate for that, I thought of an extension to the API that could help fill that gap. Rather than rushing off to write a pull request, I figured I'd outline it here for feedback first. I'm envisioning something like this:

from django.db.models import Q

@rules.filter
def is_book_author(user):
    return Q(author=user)

is_book_author_or_superuser = is_book_author | rules.predicates.is_superuser

rules.add_filter('books.view_book', is_book_author_or_superuser)

Book.objects.filter(rules.q(user, 'books.view_book')

Filters would have to be defined separately from the object permission predicates, but would work very similarly; Q objects can be combined in ways that are pretty compatible with the predicate combinations already supported in rules. Existing predicates which only depend on properties of the user could be combined with Q-based filters, with predicate outcomes being represented as always-True (like Q(pk__isnull=False)) or always-False (like Q(pk__isnull=True)) Q objects.

This would also make it pretty straightforward to create a Django REST Framework filter that would use the filter associated with the correct permission:

from rest_framework.compat import get_model_name

class DjangoPermissionRulesFilter(BaseFilterBackend):

    perm_format = '%(app_label)s.view_%(model_name)s'

    def filter_queryset(self, request, queryset, view):
        user = request.user
        model_cls = queryset.model
        kwargs = {
            'app_label': model_cls._meta.app_label,
            'model_name': get_model_name(model_cls)
        }
        permission = self.perm_format % kwargs
        return queryset.filter(rules.q(user, permission))

Some of the things I like about this design:

  • Keeps implementation of the permissions out of the models and model managers, so they can be grouped together with the predicate definitions
  • Allows reuse of some basic filtering operations (at least within permissions on the same model or other ones with the same lookup path for the fields to compare)
  • Consistency with implementing predicates for the object-based permissions
  • Ability to reuse predicates that don't depend on the object in filters (this part just occurred to me and hasn't been as carefully thought through as the rest, but it seems like it should work)
  • Very simple to support in Django REST Framework with only one custom filter class that can be reused for many views

Some downsides that I don't see good ways to work around yet:

  • One permission can have 2 different implementations: a predicate function for a single object, or a Q object for a queryset. I really don't see any way around this without really limiting and complicating the case where you don't even need a filter for the permission (which is pretty common).
  • Models with different lookup paths to the user (or related models) generally can't share filter functions; one may need Q(author=user) while another has Q(owner=user), Q(status__user=user), or even Q(creator__organization=user.organization).
  • I'd kind of prefer a query filtering syntax like Book.objects.has_perm(user, 'books.view_book'), but it doesn't seem worth the effort to create a model manager mixin for it that would need to be explicitly included in all relevant models.

Thoughts? Do you think something like this would fit in rules or should go into a separate app which depends on it? And can you think of any good improvements on the API?

Admin page: model instance is None

I've gotten the Django permission tests to work as per the docs, but when attempting admin integration, I'm unable to get the model instance to be accepted by the predicate. The user is passed appropriately, but the model instance (book) in the example) is always None. I'm following the tutorials exactly, and am on Django 1.11. What could cause this?

@rules.predicate
def in_section(user, person_instance):
    #This always produces an exception, because person_instance is always None.
    return user.person.section == person_instance.section

rules.add_perm('myapp', rules.always_allow)
rules.add_perm('myapp.add_person', in_section) 

Enhancement - Human readable name for rules/permissions

Hi, I've been a fan of this lib for a long time, so now I'm implementing it on a bigger scale than usual in a project and ran into an enhancement.

The context is that we want to connect some custom rules/permissions with roles, and build an UI around this. We like the format of appname:permission_name to name a permission, but this is not very readable for the end-user assigning permissions to roles. Similar to Django's permission system, we'd like to be able to provide a human readable name to the permission.

API wise, I suggest extending the API from

rules.add_perm('appname:permission_name', some_predicate)

to

rules.add_perm('appname:permission_name', some_predicate, verbose_name=_("A translatable string") )

Looking at the source code, this would make the RuleSet a bit more complex object than a simple dict, a single rule within a RuleSet would probably need to be an object itself where the verbose_name is also kept.

Thoughts? Comments? I'm willing to contribute on this with a PR!

Django 1.10.4 autodiscover for rules.py modules failed

Replaced 'rules', with 'rules.apps.AutodiscoverRulesConfig', on INSTALLED_APPS settings to enable auto discover mode of rules.py modules as docs said and got this error:

ImportError: No module named 'rules.apps.AutodiscoverRulesConfig'; 'rules.apps' is not a package

P.S: I'm using Python 3.5.2 inside venv

Is predicates invocation too magic ?

Hi,
I'm raising a question here before submitting any pull request.
I think there is a design issue with Predicate.test(), because if I defined my predicate to accept only
two positional arguments and the caller gives only one argument, my predicate will be called with None for the second argument.

@rules.predicate
def are_equal(a, b):
  return a == b

rules.add_rule('test_this', are_equal)
rules.test_rule('test_this', 'a', 'a') # OK there is two positional argument
rules.test_rule('test_this', 'a')  # why b is None in this case ??

This behaviour troubles me, I do not know yet how it should behave, but I would like, first, to hear from you.
Do you think there is room for improvement ?

persistence

I'm new to django-rules. I like its simplicity and it's always helpful when there's more documentation than code. But I'm wondering how I should handle an app where I spin up a bunch of objects, each with its own set of permissions (and these are added to by users as time goes by) and, well, my server goes down. How do I rebuild that permission structure?

I (really) like that django-rules doesn't require a database hit each time I make a permission check, but its also important to record those permissions.

Thanks.

help w/ example for docs

Hi @dfunckt, I've been building a test application w/ rules that I ultimately want to submit as a pull request. You can look at it in https://github.com/highpost/rules-testapp. It's a simple blog app with a number of users with different privilege levels.

I'm also writing an article that you can either use in your documentation or we can find another home. The first chunk (in explore.txt) is an overview of basic Django permissions. I'll get to rules after that.

My problem now is that my app works ... except with rules. If you look at views.py, you'll see that I'm using CBVs. If you remove PermissionRequiredMixin from each view, you can use the various test URLs in the README.txt file without any problem. But if you include them, then the buttons that access the views will fail with "127.0.0.1 redirected you too many times." I haven't had any luck tracking this down. I will note that the CreateView seems to work, and DetailView, UpdateView and DeleteView (with all take pk arguments) fail.

Thanks for your help.

autodiscovery

I thought I understood autodiscovery (see #35), but I don't. So I've built a very simple Django project with a set of permissions stored in perms.py and loaded and used in views.py. The relevant line is probably

module = import_module('myapp.perms')

in myapp/views.py. This project app completely works: I can run it with app-setup.sh and (importantly) I can run the related tests with pytest. But if I try to move this app to another environment, say something built with Cookiecutter Django, then I have problems. Modifying import_module() to give it a relative path (.perms) also fails. In addition, note that my permissions file is named perms.py. If I rename it to rules.py and change the above line, it fails.

So can you show me an explicit way of loading a permissions file?

Thanks.

How to use PermissionRequiredMixin

I have start implementing django-rules to my application and I would like to know how can I integrate the permission backend with CBVs in django.

My settings are:

AUTHENTICATION_BACKENDS = ( 'account.auth_backends.EmailAuthenticationBackend',
                            'rules.permissions.ObjectPermissionBackend',
                            'django.contrib.auth.backends.ModelBackend')
INSTALLED_APPS = (
    ....,
    'rules.apps.AutodiscoverRulesConfig',
    ...,)

views.py

class BookEditView(SuccessMessageMixin, PermissionRequiredMixin, UpdateView):
    model = Book
    template_name = 'book.html'
    form_class = BookFormSettings

    success_message = "%(title)s was created successfully"

    ### PermissionRequiredMixin settings
    permission_required = 'books.change_course'

rules.py

@predicate
def is_author(user, course):
    return book.author == user

add_perm('books.change_book', is_author)

If this does not work, what are the best practices to integrate django-rules through views.

Thanks.

in python 3.6 the `inspect.getargspec()` used in the Predicate class is removed

In python 3.6 the getargspec is to be removed.

Django already provides some implementations to overcome this deprecation: see https://github.com/django/django/pull/4846/files (especially in here: https://github.com/django/django/pull/4846/files#diff-661c241427e347ab93c204317d4f68dc)

....
  File ".../_venv35/lib/python3.5/site-packages/rules/rulesets.py", line 1, in <module>
    from .predicates import predicate
  File ".../_venv35/lib/python3.5/site-packages/rules/predicates.py", line 260, in <module>
    always_true = predicate(lambda: True, name='always_true')
  File ".../_venv35/lib/python3.5/site-packages/rules/predicates.py", line 253, in predicate
    return inner(fn)
  File ".../_venv35/lib/python3.5/site-packages/rules/predicates.py", line 248, in inner
    p = Predicate(fn, name, **options)
  File ".../_venv35/lib/python3.5/site-packages/rules/predicates.py", line 70, in __init__
    argspec = inspect.getargspec(fn)
  File "/usr/lib/python3.5/inspect.py", line 1040, in getargspec
    stacklevel=2)
DeprecationWarning: inspect.getargspec() is deprecated, use inspect.signature() instead

Admin Integration

Very noob in Python/Django:

Reading the docs, I get the basic idea of applying django-rules in a django template.

However, I feel completely overwhelmed when it comes to applying it in django-admin.

Could you please provide a hint?

Autodiscovering does not work

Hello!
I am using django 1.7 and trying to enable autodiscovering of rules.py files as it shown in the README file:

INSTALLED_APPS = (
    # ...
    'rules',
    'rules.apps.AutodiscoverRulesConfig',
)

But I am getting the following error:

Traceback (most recent call last):
  File "./manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/lib/python3.4/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
  File "/usr/lib/python3.4/site-packages/django/core/management/__init__.py", line 354, in execute
    django.setup()
  File "/usr/lib/python3.4/site-packages/django/__init__.py", line 21, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/usr/lib/python3.4/site-packages/django/apps/registry.py", line 89, in populate
    "duplicates: %s" % app_config.label)
django.core.exceptions.ImproperlyConfigured: Application labels aren't unique, duplicates: rules

When I am disabling rules application:

INSTALLED_APPS = (
    # ...
    #'rules',
    'rules.apps.AutodiscoverRulesConfig',
)

I am getting another error (when I am trying to load any page):

ImportError at /
No module named 'rules.apps.AutodiscoverRulesConfig'; 'rules.apps' is not a package
Request Method: GET
Request URL:    http://localhost:8000/
Django Version: 1.7
Exception Type: ImportError
Exception Value:    
No module named 'rules.apps.AutodiscoverRulesConfig'; 'rules.apps' is not a package
Exception Location: /usr/lib/python3.4/site-packages/authority/__init__.py in autodiscover, line 21
Python Executable:  /usr/bin/python
Python Version: 3.4.1

What is the correct way of using autodiscover?

'rules.apps' is not a package

I'm not able to get django-rules working. I know that there is other issues that are similar #10 , #8 , and #16 ; but I don't have django-authority installed and I'm using python 3.4 and Django 1.7.6

This is the error I'm receiving:

ImportError at /
No module named 'rules.apps.AutodiscoverRulesConfig'; 'rules.apps' is not a package
Request Method: GET
Request URL:    http://localhost:8080/
Django Version: 1.7.6
Exception Type: ImportError
Exception Value:    
No module named 'rules.apps.AutodiscoverRulesConfig'; 'rules.apps' is not a package
Exception Location: /usr/lib/python3.4/importlib/__init__.py in import_module, line 109
Python Executable:  /usr/bin/python3.4
Python Version: 3.4.1

Thanks for the help.

rules.contrib.views.PermissionRequiredMixin hides coding errors from the developer

        try:
            # Requires SingleObjectMixin or equivalent ``get_object`` method
            return self.get_object()
        except AttributeError:  # pragma: no cover
            return None

If the get_object() implementation has a bug that ends up causing AttributeError, the mixin gobbles it, which can lead to a very confusing result and debugging experience :)

Instead of looking at side effects of calling a missing method, what about using hasattr?

assignment_tag is removed in django-2.0

Quoting the release docs:

Django 1.4 added the assignment_tag helper to ease the creation of template tags that store results in a template variable. The simple_tag() helper has gained this same ability, making the assignment_tag obsolete. Tags that use assignment_tag should be updated to use simple_tag.

As this prevents a timely upgrade to Django 2.0 for me, I'd ask you to consider merging a PR and releasing a new version soon-ish.

AttributeError: 'ObjectPermissionBackend' object has no attribute 'get_user'

I am trying to use Django's Client.force_login during tests, which appears to trigger an AttributeError when using ObjectPermissionBackend in settings.AUTHENTICATION_BACKENDS:

[25]   …/app/tests/test_middleware.py(23)test_ddt_middleware_normal()
-> response = client.get('/api/', HTTP_ACCEPT='text/html')
[26]   …/Vcs/django/django/test/client.py(531)get()
-> **extra)
[27]   …/Vcs/django/django/test/client.py(333)get()
-> return self.generic('GET', path, secure=secure, **r)
[28]   …/Vcs/django/django/test/client.py(409)generic()
-> return self.request(**r)
[29]   …/Vcs/django/django/test/client.py(478)request()
-> response = self.handler(environ)
[30]   …/Vcs/django/django/utils/six.py(686)reraise()
-> raise value
[31]   …/Vcs/django/django/core/handlers/exception.py(39)inner()
-> response = get_response(request)
[32]   …/app/middleware.py(37)__call__()
-> if not (request.user and
[33]   …/Vcs/django/django/utils/functional.py(234)inner()
-> self._setup()
[34]   …/Vcs/django/django/utils/functional.py(380)_setup()
-> self._wrapped = self._setupfunc()
[35]   …/Vcs/django/django/contrib/auth/middleware.py(24)<lambda>()
-> request.user = SimpleLazyObject(lambda: get_user(request))
[36]   …/Vcs/django/django/contrib/auth/middleware.py(12)get_user()
-> request._cached_user = auth.get_user(request)
[37] > …/Vcs/django/django/contrib/auth/__init__.py(187)get_user()
-> user = backend.get_user(user_id)

The pytest test looks like this:

def test_foobar(db, client, some_user, some_group):
    some_user.groups.add(some_group)
    some_user.save()
    client.force_login(some_user)

Setting a password and using login works:

def test_foobar(db, client, some_user, some_group):
    some_user.groups.add(some_group)
    some_user.set_password('password')
    some_user.save()
    assert client.login(username=some_user.username,
                        password='password')

According to the documentation the get_user method is required:
https://docs.djangoproject.com/en/1.10/topics/auth/customizing/#writing-an-authentication-backend.

Optimize rule evaluation

Assume a rule such as:

my_rule = first_condition | second_condition

I would assume that second_condition would not be evaluated if first_condition evaluated to True. It appears that django-rules doesn't doesn't apply this optimization.

Is there a way to handle this with the library? If not, what would be involved in adding it? (This is particularly valuable for my use-case, as I am abusing the predicates to mutate data when the predicates are called.)

Thanks for a great library!

Rules 2.0

Hey all, I'm thinking of releasing Rules 2.0 soon and was wondering if there are ideas for things that can be implemented now but couldn't due to having to keep backwards compatibility. I intend to include (breaking) changes related to #44 and #52. It would also be nice if we tackled #32 too (I remember there were some compatibility issues when I looked at it back when it was reported).

1.2.X will still be supported for some time, back-porting fixes for bugs that are severe enough to warrant it but no new features will be implemented.

What do you all think? I'll go ahead and release 2.0 in a couple of weeks if nothing comes up.

Redirection loop with CBV

If user already logged in, and have no specific permission for accessing View - redirect loop happen, here:

            # Check for permissions and return a response
            if not user.has_perms(perms, obj):
                # User does not have a required permission
                if raise_exception:
                    raise PermissionDenied()
                else:
                    return _redirect_to_login(request, view_func.__name__,
                                              login_url, redirect_field_name)

since raise_exception is always set to False (default value) with CBV.

Is it time for a new release ?

Hi @dfunckt,
I'm a happy user of the master branch for several month now.
I think django-rules is stable and mature feature wised to be released and pushed to pypi.

Let me know if you need help.

Please consider using user._group_names_cache again

Hi,

You've removed using the group_names_cache here because of #43.

As far as I can tell, that cache was only valid for the lifetime of one request and saves many duplicate DB queries. It wasn't 'undesired', in fact it was very desirable, imo. I have multiple {% has_perm %} on most pages, all based on group membership. Another example: I have a list of objects which many groups can view, but one part of determining whether an 'edit' link should be displayed for an object from that list is based on group membership.
What used to be 9 queries for this page is now over 60, all of them are fetching the group names for the same user again and again.

I'm not exactly sure what ljsjl's use case was, but I think that changing group membership and then checking it again for that same user within one single request is not the typical use case. Even the Django docs regarding Permission Caching say that it's fine to cache them by default.

If you decide against using the cache by default again, what would be the best way forward for people who want to use it? Right now I've basically copied the predefined is_group_member predicate and modified it. Is that the best solution? Or would you be willing to add another predicate to your package, something like cached_is_group_member? Or change the signature to

def is_group_member(*groups, cache=False):
    ...
    @predicate(name)
    def fn(user):
        if cache:
            # Use cache.
        else:
            # Refetch them every time.

and use it like this is_admin = is_group_member('admin', cache=True)? Not a big fan of the last one, but it would work.

Can't import rules

with django-rules imported from pip:

....
Django==1.8.5
django-rules==0.2
django-social-auth==0.7.28
django-wysiwyg-redactor==0.4.9.1
djangorestframework==3.4.7
httplib2==0.9.2
...

and rules included in my INSTALLED_APPS

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rules.apps.AutodiscoverRulesConfig',
    'rest_framework.authtoken',
    'rest_framework',
    'accounts',
    'bms',
    'cms',
    'socialmedia',
    # 'v1_api',
)

Attempting to start the django shell, I get the following error

Traceback (most recent call last):
  File "manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 351, in execute_from_command_line
    utility.execute()
  File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 325, in execute
    django.setup()
  File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/__init__.py", line 18, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/apps/registry.py", line 85, in populate
    app_config = AppConfig.create(entry)
  File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/apps/config.py", line 112, in create
    mod = import_module(mod_path)
  File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
ImportError: No module named rules.apps

I can't think of what might be wrong..

Making rules work with CreateView

Not sure this is really an issue, but good for your documentation:
By default I have found rules don't work with CreateView because there is no object instance yet, so it throws a PK error. However, I have defined rules as such that will accept just the user object and will return the appropriate True/False. I really needed some security rules on the CreateView, so I started messing with the get_object function (unused for CreateView) and I made this work by adding the following function 'get_object' to the CreateView class:

class CustomCreateView(PermissionRequiredMixin, CreateView): """ other stuff """ def get_object(self): pass

Now I'm trying to get this working with ListView, which is proving to be a bit more difficult...

ImportError on python 2.7.10

Hello,

I'm getting the following error (I didn't install the other package, django-rules, 🤐 ) on Django 1.9.

Unhandled exception in thread started by <function wrapper at 0x104001758>
Traceback (most recent call last):
  File "/Users/dacian/.virtualenvs/venv/lib/python2.7/site-packages/django/utils/autoreload.py", line 226, in wrapper
    fn(*args, **kwargs)
  File "/Users/dacian/.virtualenvs/venv/lib/python2.7/site-packages/django/core/management/commands/runserver.py", line 109, in inner_run
    autoreload.raise_last_exception()
  File "/Users/dacian/.virtualenvs/venv/lib/python2.7/site-packages/django/utils/autoreload.py", line 249, in raise_last_exception
    six.reraise(*_exception)
  File "/Users/dacian/.virtualenvs/venv/lib/python2.7/site-packages/django/utils/autoreload.py", line 226, in wrapper
    fn(*args, **kwargs)
  File "/Users/dacian/.virtualenvs/venv/lib/python2.7/site-packages/django/__init__.py", line 18, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/Users/dacian/.virtualenvs/venv/lib/python2.7/site-packages/django/apps/registry.py", line 85, in populate
    app_config = AppConfig.create(entry)
  File "/Users/dacian/.virtualenvs/venv/lib/python2.7/site-packages/django/apps/config.py", line 116, in create
    mod = import_module(mod_path)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
  File "/Users/dacian/Desktop/dev/project_name/extras/dashboard/rules.py", line 6, in <module>
    from rules import predicate
ImportError: cannot import name predicate
INSTALLED_APPS = [
    'modeltranslation',
    'django.contrib.sites',
    'jet',
    'django.contrib.admin',
    'django.contrib.auth',
    'polymorphic',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.humanize',
    # 'rules',
    'rules.apps.AutodiscoverRulesConfig',
    'django_extensions',
    'rest_framework',
]

extras/dashboard/rules.py

# -*- coding: utf-8 -*-

from __future__ import absolute_import
from __future__ import unicode_literals

from rules import predicate


@predicate(name='has_menu_perm')
def has_menu_perm(user, menu_item):
    return any([user.has_perm(perm) for perm in menu_item.perms])

Support for PEP 484 annotations

Using function annotations in rules will cause this error (Python 3.5):

ValueError: Function has keyword-only arguments or annotations, use getfullargspec() API which can support them

File "…/app/rules.py", line 3, in <module>
  from .predicates import (
File "…/app/predicates.py", line 13, in <module>
  def is_superuser(user: User):
File "…/django-rules/rules/predicates.py", line 246, in predicate
  return inner(fn)
File "…/django-rules/rules/predicates.py", line 241, in inner
  p = Predicate(fn, name, **options)
File "…/django-rules/rules/predicates.py", line 67, in __init__
  argspec = inspect.getargspec(fn)
File "/usr/lib64/python3.5/inspect.py", line 1045, in getargspec
  raise ValueError("Function has keyword-only arguments or annotations"

Ref: https://docs.python.org/3/library/inspect.html#inspect.getfullargspec

Django 1.11 missing in tests

There is no Django 1.11 in the test matrix. Is this project still alive and being kept up to date with new Django releases?

Document usage of simple group membership on views

Documentation of the decorator for permissions on views is good, but focuses on views with passed-in objects and a user's permissions to access them. The simpler case, where you have set up group membership rules with django-rules and want to protect an entire view based on group membership, is not obvious.

I finally figured out that django-rules can be used in conjunction with Django's user_passes_test. I recommend adding to that section of the documentation something like this:

If you want to protect an entire view with a rules decorator, irrespective of any particular object, django-rules can be used in conjunction with Django's user_passes_test decorator. Rather than using the permission_required, use something like:

from django.contrib.auth.decorators import user_passes_test
import rules

is_participant = rules.is_group_member('Participants')

@user_passes_test(is_participant)
def participant_sample(request):
   ....

Updating permissions does not work

I couldn't find any solution online to this issue so I'm hoping you could help me out. So here's the scenario. I first created the permission below weeks ago. Back then, I wanted to display the list of companies only to admin users:
add_perm('mining.list_company', is_admin_level)

The above code worked perfectly. However, couple of days ago, we have made some changes and we want the list of companies to be available to all users. So I have this now:
add_perm('mining.list_company', is_authenticated)

However, when I go to the company list page using a regular user, I still get a permission denied error even though I have already changed the permission. I went into a little bit of digging and it looks like the add_perm method gets executed only ONCE, that's why the is_authenticated does not get recognized. I was able to confirm this because when I tried adding pdb debugging inside the predicate itself, it does not get executed and just proceeds to permission denied error.

Am I missing anything here? It seems I'm the only one having this issue. Below are the necessary codes:

predicates.py

from __future__ import unicode_literals, absolute_import

from rules import predicate


@predicate()
def is_authenticated(user):
    return user.is_authenticated()

@predicate()
def is_admin_level(user):
    return user.is_admin_level

rules.py

from __future__ import unicode_literals, absolute_import
from rules import add_perm
from .predicates import is_authenticated

add_perm('mining.list_company', is_authenticated)

views.py

class CompanyList(LoginRequiredMixin, PermissionMixin, ListView):
    logger = logging.getLogger(__name__)
    context_object_name = 'companies'
    permission_required = 'mining.list_company'
    template_name = 'mining/company/list.html'
    paginate_by = 10

Again, everything is working perfectly fine before. The error appeared when I changed is_admin_level to is_authenticated. I would really appreciate it if you could point me to the right direction here. Thanks in advance!

Return rule denial reasons (and improved integration with rest framework)

Thanks for the great library! I love the philosophy to use rules written in simple python functions over database tables.

I ran into a few troubles trying to integrate it with a django rest framework project - we don't use django permissions, which means we can't use DjangoObjectPermissions easily.

I thought to connect django-rules directly to drf permissions - ends up similar-ish to dry-rest-permissions but using django-rules.

I also wanted to be able to return custom error messages explaining why a permission was denied.

I managed to implement this by subclassing stuff in django-rules so that you can use it like this:

# in rules.py

@predicate(messages=('Team is archived', 'Team is not archived'))
def is_team_archived(_, team):
  return team.status = 'archived'

can_archive_team = ~is_team_archived

# standalone use

result, message = can_archive_team.test_with_message(user, team)
if result:
  print('yes')
else:
  print('no:', message) # prints "no: Team is archived"

# use in a @detail_route

# creates a drf compatible permission class
CanArchiveTeam = create_permission_class('CanArchiveTeam', can_archive_team)

@detail_route(
    methods=['POST'],
    permission_classes=(IsAuthenticated, CanArchiveTeam)
)
def archive(self, request, pk=None):
  # do archiving

If the CanArchiveTeam does not pass, then it returns a permission denied error with the message Team is archived.

There is a bit more information in our project discussion.

I wanted to avoid magic naming conventions (the approach taken by dry-rest-permissions) or referring to permissions using string names - explicit imports are much clearer to me.

Actually, the only API change for rules is changing/adding a method that returns (result, message) instead of result, and accepting messages as predicate args.

My questions are:

  • would you be interested in having something this inside django-rules?
  • if not, would you be interested in making django-rules officially extendable so I don't need to use private methods/variables which might break in future versions?

The alternative is a fork, but I don't want to contribute to the ever fragmenting django permissions ecosystem. Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.