Giter Club home page Giter Club logo

django-guardian's Introduction

django-guardian

image

image

image

django-guardian is an implementation of per object permissions1 on top of Django's authorization backend

Documentation

Online documentation is available at https://django-guardian.readthedocs.io/.

Requirements

  • Python 3.5+
  • A supported version of Django (currently 2.2+)

GitHub Actions run tests against Django versions 2.2, 3.0, 3.1, 3.2, 4.0, and main.

Installation

To install django-guardian simply run:

pip install django-guardian

Configuration

We need to hook django-guardian into our project.

  1. Put guardian into your INSTALLED_APPS at settings module:
INSTALLED_APPS = (
 ...
 'guardian',
)
  1. Add extra authorization backend to your settings.py:
AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend', # default
    'guardian.backends.ObjectPermissionBackend',
)
  1. Create guardian database tables by running:

    python manage.py migrate

Usage

After installation and project hooks we can finally use object permissions with Django.

Lets start really quickly:

>>> from django.contrib.auth.models import User, Group
>>> jack = User.objects.create_user('jack', '[email protected]', 'topsecretagentjack')
>>> admins = Group.objects.create(name='admins')
>>> jack.has_perm('change_group', admins)
False
>>> from guardian.models import UserObjectPermission
>>> UserObjectPermission.objects.assign_perm('change_group', jack, obj=admins)
<UserObjectPermission: admins | jack | change_group>
>>> jack.has_perm('change_group', admins)
True

Of course our agent jack here would not be able to change_group globally:

>>> jack.has_perm('change_group')
False

Admin integration

Replace admin.ModelAdmin with GuardedModelAdmin for those models which should have object permissions support within admin panel.

For example:

from django.contrib import admin
from myapp.models import Author
from guardian.admin import GuardedModelAdmin

# Old way:
#class AuthorAdmin(admin.ModelAdmin):
#    pass

# With object permissions support
class AuthorAdmin(GuardedModelAdmin):
    pass

admin.site.register(Author, AuthorAdmin)

  1. Great paper about this feature is available at djangoadvent articles.โ†ฉ

django-guardian's People

Contributors

1vank1n avatar ad-m avatar aramgutang avatar bmihelac avatar brianmay avatar bsvetchine avatar crosbymichael avatar emperorcezar avatar ggreer avatar ghinch avatar johnthagen avatar jonnyarnold avatar kaesemeister avatar keattang avatar lnagel avatar lukaszb avatar michael-k avatar mitar avatar partimer avatar qeternity avatar rafaponieman avatar rhblind avatar shanx avatar sjdemartini avatar tdruez avatar thedrow avatar troygrosfield avatar wtower avatar wvolz avatar xordoquy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-guardian's Issues

Problems with orphaned object permission entries

We need to add management command for removing orphaned object permission entries.

Additionally, documentation must be extended with the topic as it is not so obvious but in some circumstances may eventually create security breach (i.e. after obj perms assignment for one target, which is removed and then new object with same content type and primary key created).

(This was proposed and pointed out by Elrond - thanks!)

Provide a way to show only per object permissions in permission assignment form

Right now you can see both class based and per object permissions when you go to the permission assignment form (the one you see when you click on the edit link for a user row in object permission list). Since the assignments of class based permissions is impossible through this form, I think the best thing is to just not show the class based permissions there or at least provide a way to show only per object permissions in permission assignment form.

Not being able of assign class based permissions in this form is not a big deal, since you already can do it through the standard user edition form which allows to assign permissions to a particular user.

Fix testsuite

I've made some bad decisions while preparing test suite for guardian. Namely,
there are some specific codes for guardian to be tested, and i.e. you most
probably cannot run "python manage.py test guardian" within real-world project.

  1. Need to replace all those Keycard, KeyValue models with one that is going to
    be there in a project anyway (ContentType would be good replacer as
    django.contrib.contenttypes are required by django.contrib.auth)
  2. Same for Flatpage model
  3. Test runner should run for simplest possible settings (i.e. with only
    admin+prereqs + guardian apps, and required custom configuration, only one
    exists currently if I remember correctly: ANONYMOUS_USER_ID)

Pointed out by Ramanan Sivaranjan - thanks!

Users with permission set by Group should not be displayed within admin page

First of all, thank you very much for the code you wrote. It saved me a lot of $ and time for implementing customized object permissions within Django.

Okay, here is the situation.

I have a site with more than 10,000 users(approx.) and about 1,300 users are in the group named 'Staff'. So, I gave this 'Staff' group some object permissions using your GuardedModelAdmin interface /admin/some/model/permissions/group-manage/1/. (Let's say 'Staff'.id = 1.)

After giving permissions, I tried to reload the object permission page /admin/some/model/permissions to see whether the changes are successfully applied. And bomb! The page took almost 3 minutes to load within my development environment, 2 minutes to load within production environment.

I tried to find out why this happens and figured out that the page /admin/some/model/permissions/ tries to show all the users with permissions, not only with the user specific permission but also with the user with permission inherited from Group permission. As my 'Group' had more than 1,300 users, django-guardian tried to load all those users and that took tons of time to render the page.

I think, as the groups with permission are displayed within 'Group Permission' section on bottom of the admin page, that this behavior of showing all the users in the group again is unnecessary.

I included a patch which fixes this behavior by adding kwarg named with_group_users, which acts just like with_superusers, with test codes. All the tests pass without problem. I hope this patch could help you a lot.

Patch::

diff --git a/guardian/admin.py b/guardian/admin.py
index a2fed83..ae38266 100644
--- a/guardian/admin.py
+++ b/guardian/admin.py
@@ -145,7 +145,8 @@ class GuardedModelAdmin(admin.ModelAdmin):
         """
         obj = get_object_or_404(self.queryset(request), pk=object_pk)
         users_perms = SortedDict(
-            get_users_with_perms(obj, attach_perms=True))
+            get_users_with_perms(obj, attach_perms=True,
+                with_group_users=False))
         users_perms.keyOrder.sort(key=lambda user: user.username)
         groups_perms = SortedDict(
             get_groups_with_perms(obj, attach_perms=True))
diff --git a/guardian/shortcuts.py b/guardian/shortcuts.py
index 0964d91..8c561cd 100644
--- a/guardian/shortcuts.py
+++ b/guardian/shortcuts.py
@@ -140,7 +140,8 @@ def get_perms_for_model(cls):
     ctype = ContentType.objects.get_for_model(model)
     return Permission.objects.filter(content_type=ctype)

-def get_users_with_perms(obj, attach_perms=False, with_superusers=False):
+def get_users_with_perms(obj, attach_perms=False, with_superusers=False,
+        with_group_users=True):
     """
     Returns queryset of all ``User`` objects with *any* object permissions for
     the given ``obj``.
@@ -178,17 +179,19 @@ def get_users_with_perms(obj, attach_perms=False, with_superusers=False):
         qset = Q(
             userobjectpermission__content_type=ctype,
             userobjectpermission__object_pk=obj.pk)
-        qset = qset | Q(
-            groups__groupobjectpermission__content_type=ctype,
-            groups__groupobjectpermission__object_pk=obj.pk,
-        )
+        if with_group_users:
+            qset = qset | Q(
+                groups__groupobjectpermission__content_type=ctype,
+                groups__groupobjectpermission__object_pk=obj.pk,
+            )
         if with_superusers:
             qset = qset | Q(is_superuser=True)
         return User.objects.filter(qset).distinct()
     else:
         # TODO: Do not hit db for each user!
         users = {}
-        for user in get_users_with_perms(obj):
+        for user in get_users_with_perms(obj,
+                with_group_users=with_group_users):
             users[user] = get_perms(user, obj)
         return users

diff --git a/guardian/tests/shortcuts_test.py b/guardian/tests/shortcuts_test.py
index ebde9c9..bd36b6c 100644
--- a/guardian/tests/shortcuts_test.py
+++ b/guardian/tests/shortcuts_test.py
@@ -266,6 +266,14 @@ class GetUsersWithPermsTest(TestCase):
             set([self.user1, admin]),
         )

+    def test_with_group_users(self):
+        self.user1.groups.add(self.group1)
+        assign("change_contenttype", self.group1, self.obj1)
+        result = get_users_with_perms(self.obj1, attach_perms=True,
+                with_group_users=False)
+        expected = {}
+        self.assertEqual(result, expected)
+

 class GetGroupsWithPerms(TestCase):
     """

Thank you again for your wonderful django-guardian code.

Sincerely,
Woosuk Suh.

Update to django-grappelli 2.3.3

One of recent changes at grappelli codebase breaks guardian's grappelli support:

diff --git a/grappelli/templates/admin/change_form.html b/grappelli/templates/admin/change_form.html
index 6720eeb..abe8c27 100644
--- a/grappelli/templates/admin/change_form.html
+++ b/grappelli/templates/admin/change_form.html
@@ -31,9 +31,20 @@
                         }
                     }
                 });
-                $("input.vForeignKeyRawIdAdminField").grp_related_fk({lookup_url:"{% url grp_related_lookup %}"});
-                $("input.vManyToManyRawIdAdminField").grp_related_m2m({lookup_url:"{% url grp_m2m_lookup %}"});
-                $("input.vIntegerField[name*='object_id']").grp_related_generic({lookup_url:"{% url grp_related_lookup %}"});
+                var related_lookup_fields_fk = {% get_related_lookup_fields_fk adminform.model_admin %};
+                var related_lookup_fields_m2m = {% get_related_lookup_fields_m2m adminform.model_admin %};
+                var related_lookup_fields_generic = {% get_related_lookup_fields_generic adminform.model_admin %};
+                $.each(related_lookup_fields_fk, function() {
+                    $("#id_" + this).grp_related_fk({lookup_url:"{% url grp_related_lookup %}"});
+                });
+                $.each(related_lookup_fields_m2m, function() {
+                    $("#id_" + this).grp_related_m2m({lookup_url:"{% url grp_m2m_lookup %}"});
+                });
+                $.each(related_lookup_fields_generic, function() {
+                    var content_type = "#id_" + this[0],
+                        object_id = "#id_" + this[1];
+                    $(object_id).grp_related_generic({content_type:content_type, object_id:object_id, lookup_url:"{% url grp_related_lookup %}"});
+                });

Seems like they dropped url based lookups in favor of using AdminModel instance and specific template tag.

Quick fix I can think of: add adminform dictionary with model_admin field being AdminModel instance.

Access control based on wildcards

This is an enhancement suggestion.

Guardian supports object based permissions, which allows a finer-grained control compared to what django does out-of-the-box. What I'd like to suggest is to support wildcards when choosing which objects to apply the permissions.

For example, instead of saying:
"User 1 has read access to object a-42 of the model MyClassA"
"User 1 has read access to object a-43 of the model MyClassA"
...

We could instead say:
"User 1 has read access to objects of the model MyClassA that match the condition first_name = 'Jhon'"
...

I.e., let "User 1" read all objects with a "first_name" attribute with the value "Jhon".


Edit: Removed reference to Attribute-based access control, which is not what I meant at all.

Add roles

First of all, let me explain why we didn't add roles in the first place. In short, Django's policy is to include simple but well done batteries. django.contrib.auth is best example. How many times people need to exchange auth with own implementation? I believe not so many. guardian is intended to be kind of "extension" to auth app, with sane & simple interface. And thats it. No much of extra functionality included (except some shortcut functions - but this refer to "sane" part).

Now, whats with roles? All they always needed? In fact, I believe not. Same with groups, really. User is needed as it is a fundamental entity for vast majority of all applications.

Ok, here we are, v1.0 almost out there. So for v1.1, roles support would be probably most important feature. Still, in my view, it should be "optional" in some way.

get_users_with_perms Doesn't Respect `superuser` Status (Bug)

Hi there. I'm having some issues with the get_users_with_perms shortcut.

Basically, it seems to NOT respect superuser status when returning users who have permissions. By definition, a superuser should always have permissions to everything. I think that this shortcut should be re-defined to include superuser accounts in its return values, in order to generate a 'true' list of people with permissions to the specified object.

Here's an example (using django 1.2.5 and guardian 1.0.0):

>>> from apps.rooms.models import Room
>>> from apps.partylines.models import Partyline
>>> from django.contrib.auth.models import User
>>> from guardian.shortcuts import get_users_with_perms, assign
>>> 
>>> u = User.objects.get(id=1)
>>> u.is_superuser
True
>>> 
>>> r = Room(number=2, partyline=Partyline.objects.get(id=1))
>>> r.save()
>>> 
>>> get_users_with_perms(r)
[]
>>> 
>>> assign('change_room', u, r)
>>> get_users_with_perms(r)
[<User: rdegges>]
>>>

As you can see, get_users_with_perms should have returned the account when I ran it the first time, without explicitly assigning my superuser account the permission.

Proposal: action for multiple permission assign

With many objects could be a very long work to assign object permissions to any object. A used thing to have could be an Action (in the combo box, like delete one) that allow you to select multiple objects and then set permissions for all of them.

Action I suppose is the fastest way to do it (and maybe the cleanest way too), but any other method to multiple assign perms to object is welcome.

Thanks
Marco

Requirenments in example project

Please, verify and add django-coverage, django-registration to requirements.txt. Also add pysqlite, cuz this is default database in settings.py

permission_required_or_403 doesn't raise PermissionDenied

The permission_required_or_403 decorator returns a HttpResponseForbidden if the user doesn't have permission, but Django provides an exception called PermissionDenied that gets converted into an equivalen response (that has <h1>Permission denied</h1> as its content), and if you raise the exception it's easy for others to catch it with a middleware and convert it into a nice, themed error page.

primary_key limitation

It seems that guardian only works on models where the default Django "id" field / primary_key is present.

../guardian/core.py", line 59, in get_perms
key = (ctype.id, obj.id)
AttributeError: 'MyClass' object has no attribute 'id'

Is this a known limitation?

Documentation cleanup

Recently we've added possibility to generate documentation as PDF (using rst2pdf builder).
Unfortunately, generated pdf is badly "sectioned" and we should replace some stuff (mainly within index.rst after first glance).

Exception when calling clean_orphan_obj_perms on django 1.3

Traceback (most recent call last):

  File "E:\Dev\IDE\PyCharm\helpers\pycharm\django_manage.py", line 19, in 
    run_module(manage_file, None, '__main__')
  File "C:\Python27\lib\runpy.py", line 180, in run_module
    fname, loader, pkg_name)
  File "C:\Python27\lib\runpy.py", line 72, in _run_code
    exec code in run_globals
  File "D:\idea\webadmin\webadmin\manage.py", line 11, in 
    execute_manager(settings)
  File "C:\Python27\Lib\site-packages\django\core\management\__init__.py", line 438, in execute_manager
    utility.execute()
  File "C:\Python27\Lib\site-packages\django\core\management\__init__.py", line 379, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "C:\Python27\Lib\site-packages\django\core\management\base.py", line 191, in run_from_argv
    self.execute(*args, **options.__dict__)
  File "C:\Python27\Lib\site-packages\django\core\management\base.py", line 220, in execute
    output = self.handle(*args, **options)
  File "C:\Python27\Lib\site-packages\django\core\management\base.py", line 351, in handle
    return self.handle_noargs(**options)
  File "C:\Python27\lib\site-packages\django_guardian-1.0.0.rc1-py2.7.egg\guardian\management\commands\clean_orphan_obj_perms.py", line 20, in handle_noargs
    removed = clean_orphan_obj_perms()
  File "C:\Python27\lib\site-packages\django_guardian-1.0.0.rc1-py2.7.egg\guardian\utils.py", line 80, in clean_orphan_obj_perms
    if perm.content_object is None:
  File "C:\Python27\Lib\site-packages\django\contrib\contenttypes\generic.py", line 80, in __get__
    ct = self.get_content_type(id=ct_id, using=instance._state.db)
  File "C:\Python27\Lib\site-packages\django\contrib\contenttypes\generic.py", line 59, in get_content_type
    return ContentType.objects.db_manager(using).get_for_id(id)
  File "C:\Python27\Lib\site-packages\django\contrib\contenttypes\models.py", line 55, in get_for_id
    self._add_to_cache(self.db, ct)
  File "C:\Python27\Lib\site-packages\django\contrib\contenttypes\models.py", line 70, in _add_to_cache
    key = (model._meta.app_label, model._meta.object_name.lower())
AttributeError: 'NoneType' object has no attribute '_meta'

LoginRequired and PermissionRequired view mixins

Using the django's or guardian's 'loginrequired' and 'permissionrequired' decorators with the new class based views is tricky and cumbersome - django documetations shows some pointers here: https://docs.djangoproject.com/en/dev/topics/class-based-views/#decorating-class-based-views

A different approach would to use view mixins which in my opinion is the easiest and most readable solution. An example class based view with a permission required check could look like this:

class FitterEditView(PermissionRequiredMixin, UpdateView):
    """
    ...
    """

    ### PermissionRequiredMixin settings
    permission_required = 'fitters.change_fitter'

    ### UpdateView settings
    context_object_name="fitter"
    queryset = Fitter.objects.all()
    form_class = FitterForm
    ...

Below is my first attempt at these mixins created for a project I am currently working on. These mixins are designed to work both with vanilla django or with django-guradian. If this is something you would consider including in the future I would love to assist.

On the final note I want to say thank you for this fantastic app which I have been using it since ver 0.2.

class LoginRequiredMixin(object):
    """ 
    A login required mixin for use with class based views. This Class is a light wrapper around the
    `login_required` decorator and hence function parameters are just attributes defined on the class.

    Due to parent class order traversal this mixin must be added as the left most 
    mixin of a view.

    The mixin has exaclty the same flow as `login_required` decorator:

        If the user isn't logged in, redirect to settings.LOGIN_URL, passing the current 
        absolute path in the query string. Example: /accounts/login/?next=/polls/3/.

        If the user is logged in, execute the view normally. The view code is free to 
        assume the user is logged in.

    **Class Settings**
        `redirect_field_name - defaults to "next"
        `login_url` - the login url of your site

    """
    redirect_field_name = REDIRECT_FIELD_NAME
    login_url = None

    @method_decorator(login_required(redirect_field_name=redirect_field_name, login_url=login_url))
    def dispatch(self, request, *args, **kwargs):
        return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)

class PermissionRequiredMixin(object):
    """ 
    A view mixin that verifies if the current logged in user has the specified permission 
    by wrapping the ``request.user.has_perm(..)`` method.

    If a `get_object()` method is defined either manually or by including another mixin (for example
    ``SingleObjectMixin``) or ``self.object`` is defiend then the permission will be tested against 
    that specific instance.

    .. NOTE: Testing of a permission against a specific object instance requires an authentication backend
             that supports. Please see ``django-guardian`` to add object level permissions to your project.  

    The mixin does the following:  

        If the user isn't logged in, redirect to settings.LOGIN_URL, passing the current 
        absolute path in the query string. Example: /accounts/login/?next=/polls/3/.

        If the `raise_exception` is set to True than rather than redirect to login page
        a `PermisionDenied` (403) is raised.

        If the user is logged in, and passes the permission check than the view is executed
        normally.

    **Example Usage**

        class FitterEditView(PermissionRequiredMixin, UpdateView):
            ...
            ### PermissionRequiredMixin settings
            permission_required = 'fitters.change_fitter'

            ### other view settings
            context_object_name="fitter"
            queryset = Fitter.objects.all()
            form_class = FitterForm
            ...

    **Class Settings**
        `permission_required` - the permission to check of form "<app_label>.<permission codename>"
                                i.e. 'polls.can_vote' for a permission on a model in the polls application.

        `login_url` - the login url of your site
        `redirect_field_name - defaults to "next"
        `raise_exception` - defaults to False - raise PermisionDenied (403) if set to True

    """
    ### default class view settings
    login_url = settings.LOGIN_URL
    raise_exception = False
    permission_required = None
    redirect_field_name=REDIRECT_FIELD_NAME

    def dispatch(self, request, *args, **kwargs):
        # call the parent dispatch first to pre-populate few things before we check for permissions
        original_return_value = super(PermissionRequiredMixin, self).dispatch(request, *args, **kwargs)

        # verify class settings
        if self.permission_required == None or len(self.permission_required.split('.')) != 2:
            raise ImproperlyConfigured("'PermissionRequiredMixin' requires 'permission_required' attribute to be set to '<app_label>.<permission codename>' but is set to '%s' instead" % self.permission_required)

        # verify permission on object instance if needed
        has_permission = False
        if hasattr(self, 'object')  and self.object is not None: 
            has_permission = request.user.has_perm(self.permission_required, self.object)
        elif hasattr(self, 'get_object') and callable(self.get_object):
            has_permission = request.user.has_perm(self.permission_required, self.get_object())
        else:
            has_permission = request.user.has_perm(self.permission_required)

        # user failed permission
        if not has_permission:
            if self.raise_exception:
                return HttpResponseForbidden()
            else:
                path = urlquote(request.get_full_path())
                tup = self.login_url, self.redirect_field_name, path
                return HttpResponseRedirect("%s?%s=%s" % tup)

        # user passed permission check so just return the result of calling .dispatch()
        return original_return_value

Add get_all_per_object_permissions(object) shortcut

get_perms() shortcut returns all the permissions without distinction, but for some cases it might be necessary to make a distinction between the "per object" permissions and the "class based ones".

I am trying to delete all "per object" permissions that will be orphaned after deleting an instance by overriding the delete_model() ModelAdmin method. I also want it to filter out the "class based" permissions in the "object permissions" form for each glossary instance, and show only the "per object" permissions there by overriding one of the GuardedModelAdmin methods (get_obj_perms_field_initial() if I recall correctly) since it uses get_perms() to get all the permissions. For these reasons I think this new shortcut is useful.

Grappelli not removed from INSTALLED_APPS list by GrappelliGuardedModelAdminTests tearDown method.

I've commented on the line in question, which is in a very old commit.

We just hit on this bug today, through some weird confluence of bad luck. It was causing some later tests of ours to fail, because they ended up searching for templates in all INSTALLED_APPS. We don't have Grapelli installed, so they failed.

I've suggested an easier way to restore the INSTALLED_APPS state in my comment. Am I missing something there?

if user_obj.is_active is not True: can fail

hi

in backends, there is the following statement :

Do not check any further if user is not active

if user_obj.is_active is not True:
return False

It can fail because in mysql, boolean is stored with values 0 or 1, so for example is_active can be stetted to 1 and thus, In this case, it is not considered as True
I change it by "if not user_obj.is_active:"

But I don't know if there is a specific and good reason to do check with True....

Problem creating table guardian_userobjectpermission

Using mysql backend, syncdb on a project using guardian raises the following mysql exception :

_mysql_exceptions.OperationalError: (1170, "BLOB/TEXT column 'object_pk' used in key specification without a key length")

coming from UserObjectPermission class Meta, with
unique_together = ['user', 'permission', 'content_type', 'object_pk']

using object_pk (TextField) as a blob/text key needs a length (see http://code.djangoproject.com/ticket/2495

As a workaround, i used a CharField for object_pk (pk are most frequently slugs or integers, not large (> 255 chars for mysql) texts...

Migrating from 0.10 to 1.00

Hello Lukasz,

I attempted to upgrade from 0.10 to 1.00 but looks like the base model uses object_pk rather than object_id for starters.

Can you suggest how I can proceed ? What you would do in my situation? I do have over 1000+ object permission entries.

Thank you and keep up the good work.

Include example_project in source tarball

Building the documentation from the source tarball throws a lot of warnings about the missing example_project. Could you change MANIFEST.in to:
recursive-include example_project

Build RPM

There is an error parsing the Spec file during rpm creation with:

python setup.py bdist_rpm

I fixed the problem removing the description part inside the setup.py

description = ' ', #guardian.doc,

Built is tested on Centos 5 / Centos 6 / RHEL 5 / RHEL 6 / Fedora 15

GuardedModelAdmin Enhancement

Hi Lukasz,

I have modified all my 'GuradedModelAdmin' to include the following code:

    ### additional admin actions for this adminmodel
    def auth_group(obj): #, request, queryset):
        model_level_perms = Group.objects.filter(permissions__content_type=ContentType.objects.get_for_model(obj)).distinct()
        instance_level_perms = get_groups_with_perms(obj)
        if 'guardian' in settings.INSTALLED_APPS:
            return (model_level_perms | instance_level_perms).distinct()

This lets one add 'auth_group' to the 'list_display' settings and have a way to see group access at a glance in the list. What do you think of adding something like the above into the 'GuradedModelAdmin' class?.

permissions don't seem to be inherited

I have a model GridTask with a "view_task" permission. I then have a subclass WSMRTask(GridTask), but it does not seem to have the "view_task" permission defined in the superclass's Meta.permissions list. Is this intentional? Is there any easy work around? Can this be fixed in a future release?

Cheers,

Ian

PS - is there a better forum or list for discussing django-guardian?

Admin.site.urls necessary

This is kind of a stupid little caveat, but if you happen to have forgotten to update your admin url config from the deprecated:

(r'^admin/(.*)', admin.site.root)

to the proper:

(r'^admin/', admin.site.urls)

then you will get a 404 in the admin when accessing object permissions.

Allow shortcut functions to work with table-level permissions

Currently, shortcuts provided by guardian.shortcuts module work for object-level permissions only.
They should work with global permissions as well - or at least lets propose that functionality.

I'm only worried about easy mistakes:

assign('flatpages.change_flatpage', joe, Flatpage) wouldn't raise exception if someone had 'flatpage' (instance) on mind and not Flatpage (a model).

On the other hand, this is only logical way to implement this and eventually something similar would land into Django core around 1.3/1.4 (according to documentation and schedule info).

This have been originally proposed by Alban Tiberghien - thanks!

GuardedModelAdmin as mixin

This is quite easy to implement and really useful.

The idea is to separate the logic currently defined in GuardedModelAdmin so generic properties and methods are defined in a new mixin class.
So GuardedModelAdminMixin will define get_obj_perms_base_context, obj_perms_manage_view, get_obj_perms_manage_template etc with relative properties and GuardedModelAdmin will use the Mixin and will define get_urls and queryset.

In this way if a django app extends ModelAdmin using a custom one, you can still include the mixin and rewrite the get_urls method yourself to include the guardian ones.

Please let me know your thoughts, I might implement it myself if is sounds good and pull request the change.

Equivalent to user_can_access_owned_objects_only for "group"

I was delighted to see the user_can_access_owned_objects_only Flag in the last update! This way it is easy to implement per row permissions also for the admin interface.

It would be nice to have also the possibility to get something like this for a specified group. An object would be connected to a group. (Chosen by the creator of the object.) Than a filter will be applied which let only user which are in the group see this object.

Table names violate conventions

  • guardian_groupobjectpermission should be guardian_group_object_permission
  • guardian_userobjectpermission should be guardian_user_object_permission

Improves readability and consistency.

Unexpected crash when checking for permission

Hi,

While my users' permissions are correct, I get the following traceback when checking a permission for a given user. I can't reproduce it in the shell, but it always happen when run through a django view :

Traceback (most recent call last):

File "/home/www/virtualenvs/imaginationforpeople.org/lib/python2.6/site-packages/django/core/handlers/base.py", line 111, in get_response
response = callback(request, _callback_args, *_callback_kwargs)

File "/home/www/virtualenvs/imaginationforpeople.org/lib/python2.6/site-packages/django/contrib/auth/decorators.py", line 23, in _wrapped_view
return view_func(request, _args, *_kwargs)

File "/home/www/virtualenvs/imaginationforpeople.org/imaginationforpeople/apps/member/views.py", line 143, in profile_edit
if not current_user.has_perm('change_profile', profile):

File "/home/www/virtualenvs/imaginationforpeople.org/lib/python2.6/site-packages/django/contrib/auth/models.py", line 324, in has_perm
return _user_has_perm(self, perm, obj)

File "/home/www/virtualenvs/imaginationforpeople.org/lib/python2.6/site-packages/django/contrib/auth/models.py", line 181, in _user_has_perm
backend.has_perm(user, perm, obj)):

File "/home/www/virtualenvs/imaginationforpeople.org/lib/python2.6/site-packages/guardian/backends.py", line 59, in has_perm
return check.has_perm(perm, obj)

File "/home/www/virtualenvs/imaginationforpeople.org/lib/python2.6/site-packages/guardian/core.py", line 49, in has_perm
return perm in self.get_perms(obj)

File "/home/www/virtualenvs/imaginationforpeople.org/lib/python2.6/site-packages/guardian/core.py", line 77, in get_perms
.values_list("codename"))))

File "/home/www/virtualenvs/imaginationforpeople.org/lib/python2.6/site-packages/django/db/models/query.py", line 514, in values_list
_fields=fields)

File "/home/www/virtualenvs/imaginationforpeople.org/lib/python2.6/site-packages/django/db/models/query.py", line 754, in _clone
query = self.query.clone()

File "/home/www/virtualenvs/imaginationforpeople.org/lib/python2.6/site-packages/django/db/models/sql/query.py", line 247, in clone
obj.where = deepcopy(self.where, memo=memo)

File "/usr/lib/python2.6/copy.py", line 173, in deepcopy
y = copier(memo)

File "/home/www/virtualenvs/imaginationforpeople.org/lib/python2.6/site-packages/django/utils/tree.py", line 61, in deepcopy
obj.children = deepcopy(self.children, memodict)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 228, in _deepcopy_list
y.append(deepcopy(a, memo))

File "/usr/lib/python2.6/copy.py", line 173, in deepcopy
y = copier(memo)

File "/home/www/virtualenvs/imaginationforpeople.org/lib/python2.6/site-packages/django/utils/tree.py", line 61, in deepcopy
obj.children = deepcopy(self.children, memodict)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 228, in _deepcopy_list
y.append(deepcopy(a, memo))

File "/usr/lib/python2.6/copy.py", line 173, in deepcopy
y = copier(memo)

File "/home/www/virtualenvs/imaginationforpeople.org/lib/python2.6/site-packages/django/utils/tree.py", line 61, in deepcopy
obj.children = deepcopy(self.children, memodict)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 228, in _deepcopy_list
y.append(deepcopy(a, memo))

File "/usr/lib/python2.6/copy.py", line 173, in deepcopy
y = copier(memo)

File "/home/www/virtualenvs/imaginationforpeople.org/lib/python2.6/site-packages/django/utils/tree.py", line 61, in deepcopy
obj.children = deepcopy(self.children, memodict)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 228, in _deepcopy_list
y.append(deepcopy(a, memo))

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 235, in _deepcopy_tuple
y.append(deepcopy(a, memo))

File "/usr/lib/python2.6/copy.py", line 189, in deepcopy
y = _reconstruct(x, rv, 1, memo)

File "/usr/lib/python2.6/copy.py", line 338, in _reconstruct
state = deepcopy(state, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 189, in deepcopy
y = _reconstruct(x, rv, 1, memo)

File "/usr/lib/python2.6/copy.py", line 338, in _reconstruct
state = deepcopy(state, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 235, in _deepcopy_tuple
y.append(deepcopy(a, memo))

File "/usr/lib/python2.6/copy.py", line 189, in deepcopy
y = _reconstruct(x, rv, 1, memo)

File "/usr/lib/python2.6/copy.py", line 338, in _reconstruct
state = deepcopy(state, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 189, in deepcopy
y = _reconstruct(x, rv, 1, memo)

File "/usr/lib/python2.6/copy.py", line 338, in _reconstruct
state = deepcopy(state, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 235, in _deepcopy_tuple
y.append(deepcopy(a, memo))

File "/usr/lib/python2.6/copy.py", line 189, in deepcopy
y = _reconstruct(x, rv, 1, memo)

File "/usr/lib/python2.6/copy.py", line 338, in _reconstruct
state = deepcopy(state, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 235, in _deepcopy_tuple
y.append(deepcopy(a, memo))

File "/usr/lib/python2.6/copy.py", line 189, in deepcopy
y = _reconstruct(x, rv, 1, memo)

File "/usr/lib/python2.6/copy.py", line 338, in _reconstruct
state = deepcopy(state, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 189, in deepcopy
y = _reconstruct(x, rv, 1, memo)

File "/usr/lib/python2.6/copy.py", line 338, in _reconstruct
state = deepcopy(state, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 235, in _deepcopy_tuple
y.append(deepcopy(a, memo))

File "/usr/lib/python2.6/copy.py", line 189, in deepcopy
y = _reconstruct(x, rv, 1, memo)

File "/usr/lib/python2.6/copy.py", line 338, in _reconstruct
state = deepcopy(state, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 189, in deepcopy
y = _reconstruct(x, rv, 1, memo)

File "/usr/lib/python2.6/copy.py", line 338, in _reconstruct
state = deepcopy(state, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 235, in _deepcopy_tuple
y.append(deepcopy(a, memo))

File "/usr/lib/python2.6/copy.py", line 189, in deepcopy
y = _reconstruct(x, rv, 1, memo)

File "/usr/lib/python2.6/copy.py", line 338, in _reconstruct
state = deepcopy(state, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 235, in _deepcopy_tuple
y.append(deepcopy(a, memo))

File "/usr/lib/python2.6/copy.py", line 189, in deepcopy
y = _reconstruct(x, rv, 1, memo)

File "/usr/lib/python2.6/copy.py", line 338, in _reconstruct
state = deepcopy(state, memo)

File "/usr/lib/python2.6/copy.py", line 162, in deepcopy
y = copier(x, memo)

File "/usr/lib/python2.6/copy.py", line 255, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)

File "/usr/lib/python2.6/copy.py", line 189, in deepcopy
y = _reconstruct(x, rv, 1, memo)

File "/usr/lib/python2.6/copy.py", line 323, in _reconstruct
y = callable(*args)

File "/usr/lib/python2.6/copy_reg.py", line 93, in newobj
return cls.new(cls, *args)

TypeError: instancemethod expected at least 2 arguments, got 0

My backends are the following:

AUTHENTICATION_BACKENDS = (
'userena.backends.UserenaAuthenticationBackend',
'guardian.backends.ObjectPermissionBackend',
'django.contrib.auth.backends.ModelBackend',
)

And my versions:
Django 1.3.1, Guardian 1.0.3

Thank you !

Guillaume

South don't recognize if permissions are changed

For what I have understand the permissions are set when the syncdb command is called, if we change the permissions of a model, and try to call South to find that changes it won't find none, the only way I found to make it work, is of course to remove the migration folder.
I don't label this as a bug, is just a warning, to people that spend some hours trying to understand what's wrong.
By the way I was debugging django-userena, not this particular app.

Admin panel integration

So, this is becoming something most desirable at the moment and I would like to start public discussion on the topic.

  1. Tests are necessary - django comes with great tests support and we should use it. Yes, with admin too. There is a script at guardian's root directory called ~ 'run_test_and_report.sh' which shows code coverage of the test suite. Output is not 100%? Too bad, we need 100%. It's just a must. Test first, implement just after tests are written.
  2. ObjectPermission models (both UserObjectPermission and GroupObjectPermission) are not necessary required. They doesn't really helps a lot in my view. In fact, only reason I see those models to be registered is to present object permissions by user or group.
    Feel free to present other point of view.
  3. My proposal.

My concept would be to add a view for AdminModels, something called 'permissions' near history button. At that view, we could draw a table with names of permissions as headers (vertically? horizontally could be a problem if we define a lot of permissions). Users/groups would be rows in that table with simple yes/no icons for each permission. That would be something like "object permissions matrix for specific instance". Queries required to draw such table could be a problem. Am not sure if it is possible to write sane code with < O(n) queries (n = len(users+groups). On the other hand, am not sure if this could be a big problem - we can always (and should most probably) paginate results.

Editing inline should be allowed.

Main points are:

  • do not allow unexpected errors flying around
  • make user not to panic each time he/she have to edit or review permissions for specific object
  • make user not to panic each time he/she have to edit or review permissions for specific user and/or group

I try to implement things as fast as it is possible but would really could use help with this one (both, by discussion and coding) as it is probably most crucial and desirable part of application (after the core of course).

Make permission checks at templates easier

Currently django-guardian offers only one template tag (get_obj_perms) which works for within single context block. Better solution would be to provide something similar to ifhasperm tag from django-authority or other solution making permission checks easier.

Generic FK is NOT needed

ObjectPermission models have fk pointing at Permission model which already defines content_type. We don't really need this to be duplicated. This would involve changes in
queries but should be rather painless with 100% tests coverage.

user.has_perm("perm", obj) behaves unexpectedly

If I use standard user.has_perm("perm") method, then it will return True only, if user has a global permission "perm".
And if user.has_perm("perm", obj) is used, it'll teturn True if user have permission to access this particular object.
But it will return False, even if user has a global permission "perm", which is quite unexpected for me, because I assume, that having global permission should give user access to all objects. Am I right?

shortcut get_objects_for... doesnt work with multiple perms

get_objects_for_group and
get_objects_for_user

return an empty list of object if the call is made with a perms parameter containing a list of perms ( eg ['perm1','perm2'] )

this comes from a bug at the end of those functions.
line 360
if codenames.issubset(obj_codenames):

should be replaced by:
if obj_codenames.issubset(codenames):

same on line 469.

the obj_codenames is (possibly) a subset of the requested codenames, not the other way around!

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.