Django-guardian: Добавить роли

Созданный на 16 янв. 2011  ·  14Комментарии  ·  Источник: django-guardian/django-guardian

Прежде всего, позвольте мне объяснить, почему мы вообще не добавляли роли. Короче говоря, политика Django состоит в том, чтобы включать простые, но хорошо сделанные батареи. django.contrib.auth - лучший пример. Сколько раз людям нужно обменивать auth на собственную реализацию? Я считаю, что не так уж и много. Guardian призван быть своего рода «расширением» для приложения аутентификации с разумным и простым интерфейсом. Вот и все. Никакой особой дополнительной функциональности не было (кроме некоторых функций быстрого доступа - но это относится к "разумной" части).

А что с ролями? Все, что им всегда было нужно? На самом деле, я верю, что нет. На самом деле то же самое и с группами. Пользователь необходим, поскольку он является фундаментальной сущностью для подавляющего большинства всех приложений.

Хорошо, вот и мы, v1.0 почти готово. Так что для версии 1.1 поддержка ролей, вероятно, будет наиболее важной функцией. Тем не менее, на мой взгляд, это должно быть в некотором роде «необязательным».

API change Enhancement

Все 14 Комментарий

+1 Но я согласен, что роли - это то, что почти никогда не требуется в большинстве приложений, написанных на django.
Время от времени кто-то вроде нас пытается реализовать приложение (настраиваемая система управления документами), для которого на самом деле django не был создан, и когда у вас много перманентных перманентов, вы начинаете думать о ролях :) роли и наследование две вещи, которые должны войти в v1.1

необязательная часть проста. точно так же, как использование django не заставляет вас использовать группы, не заставляет людей определять роли.

спасибо за приложение кстати.
пепел

+1 для ролей, в зависимости от реализации.

+1 для ролей и наследования

Я думаю о ролях как о «группе разрешений», я не знаю, относится ли это к вам так же. С этим классом ролей проще назначать множество разрешений. Я предлагаю реализовать это с помощью наборов Python, чтобы вычесть одну роль другой или получить пересечение, и так далее:

http://docs.python.org/library/stdtypes.html#set -types-set-frozenset
http://en.wikibooks.org/wiki/Python_Programming/Sets

+1 для ролей, наследования и необязательного

Всем привет,
Я один из тех, кому нужны (или хотелось бы) права доступа к объектам на основе ролей.

Из этого обсуждения я понимаю, что по очень веским причинам это не является приоритетом для django-guardian. Итак, я создам собственное приложение. Однако я думаю, что большинство функций и компонентов django-guardian в порядке, и, если возможно, я бы не хотел изобретать колесо заново, а использовал его повторно. Итак, мой вопрос: хотите ли вы сделать защиту django более гибкой. Например (это только первое, что пришло мне в голову при просмотре кода): установить параметр для ObjectPermissionChecker, который позволит другим приложениям заменять класс Checker чем-то, что заботится о ролях?

Что вы ребята думаете?

Спасибо
Юрген

Привет @schacki , это действительно неплохая идея. Скорее всего, тесты не пройдут для класса проверки по умолчанию, но мы можем отметить их или убедиться, что они выполняются только с проверкой по умолчанию. Вы можете создать для него конкретную задачу?

Я бы сделал любые классы проверки, отличные от стандартных, не частью django-guardign, а частью «другого» приложения - так что это задача другого приложения по отношению к классам проверки, отличным от стандартных. Пожалуйста, позвольте мне сделать более тщательный анализ того, что может потребоваться адаптировать и изменить, прежде чем открывать задачу. Под «задачей», я полагаю, вы имеете в виду отдельную проблему? Кстати: это может занять пару недель, прежде чем это продвинется вперед.

этот [1] проект чем-нибудь поможет?

[1] https://github.com/vintasoftware/django-role-permissions

Скопировал следующий текст @suriya из № 330:

В прошлом # 23 было некоторое обсуждение добавления поддержки ролей в django-guardian. Однако далеко не продвинулись. Вероятно, это связано с тем, что добавление ролей в django-guardian кажется очень сложной задачей.

Думаю, у меня есть очень простое предложение. Я хотел бы его выдвинуть и, если есть интерес, реализовать. В настоящее время у меня есть собственный форк django-guardian, в котором реализована эта функциональность. Было бы здорово, если бы это использовали и другие.

Ядро проверки разрешений django-guardian для представления реализовано в функции get_403_or_None() . Эта функция проверяет, есть ли у пользователя все необходимые разрешения. https://github.com/lukaszb/django-guardian/blob/112c373f213a19d93baa81fa4a941a41333115b5/guardian/utils.py#L98

has_permissions = all(request.user.has_perm(perm, obj) for perm in perms)

Вспомогательные роли - небольшое изменение. Вместо проверки того, что у пользователя есть разрешения all , мы просто должны проверить, удовлетворяет ли пользователь хотя бы одному разрешению. Что-то вроде

if require_all_permissions:
    has_permissions = all(request.user.has_perm(perm, obj) for perm in perms)
else:
    has_permissions = any(request.user.has_perm(perm, obj) for perm in perms)

должен это сделать. Чтобы добавить эту поддержку, все, что нам нужно, это определить новый флаг в get_403_or_None() и его вызывающих объектах, который указывает, хотим ли мы удовлетворить все или только некоторые разрешения.

Я открыл №386 и понимаю, что это, вероятно, то же самое. Что мне нужно, так это то, что в коде, где я проверяю разрешение, я могу сделать «есть ли у этого пользователя права на редактирование этого объекта», но когда я назначаю пользователям разрешения, я назначаю им разрешение «модератора», которое является надмножество нескольких разрешений, включая разрешение на «редактирование». Это позволяет мне позже добавлять дополнительные разрешения «модератору» без необходимости назначать их всем пользователям вручную.

Мы можем назвать эту иерархию разрешений, ролей.

+1 для ролей, наследования и необязательного

Я реализовал концепцию ролей, как показано ниже:

настроить get_40x_or_None

from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.http import HttpResponseForbidden, HttpResponseNotFound
from django.shortcuts import render
from guardian.conf import settings as guardian_settings


def get_40x_or_None(request, perms, global_perms, obj=None, login_url=None,
                    redirect_field_name=None, return_403=False,
                    return_404=False):
    """check if the given perms and global_perms are both granted by the user in combination"""
    login_url = login_url or settings.LOGIN_URL
    redirect_field_name = redirect_field_name or REDIRECT_FIELD_NAME

    # Handles both original and with object provided permission check
    # as ``obj`` defaults to None

    has_permissions = False
    if not has_permissions:
        has_permissions = all(request.user.has_perm(perm, obj) for perm in perms) and \
                          all(request.user.has_perm(perm) for perm in global_perms)

    if not has_permissions:
        if return_403:
            if guardian_settings.RENDER_403:
                response = render(request, guardian_settings.TEMPLATE_403)
                response.status_code = 403
                return response
            elif guardian_settings.RAISE_403:
                raise PermissionDenied
            return HttpResponseForbidden()
        if return_404:
            if guardian_settings.RENDER_404:
                response = render(request, guardian_settings.TEMPLATE_404)
                response.status_code = 404
                return response
            elif guardian_settings.RAISE_404:
                raise ObjectDoesNotExist
            return HttpResponseNotFound()
        else:
            from django.contrib.auth.views import redirect_to_login
            return redirect_to_login(request.get_full_path(),
                                     login_url,
                                     redirect_field_name)

реализовать пользовательский GlobalPermissionRequiredMixin

from django.core.exceptions import PermissionDenied, ImproperlyConfigured
from guardian.mixins import PermissionRequiredMixin
from collections.abc import Iterable

from permission.utils import get_40x_or_None


class GlobalPermissionRequiredMixin(PermissionRequiredMixin):
    global_permission_required = None

    def get_global_required_permissions(self, request=None):
        """
        Returns list of permissions in format *<app_label>.<codename>* that
        should be checked against *request.user* and *object*. By default, it
        returns list from ``global_permission_required`` attribute.

        :param request: Original request.
        """
        if isinstance(self.global_permission_required, str):
            perms = [self.global_permission_required]
        elif isinstance(self.global_permission_required, Iterable):
            perms = [p for p in self.global_permission_required]
        else:
            raise ImproperlyConfigured("'GlobalPermissionRequiredMixin' requires "
                                       "'global_permission_required' attribute to be set to "
                                       "'<app_label>.<permission codename>' but is set to '%s' instead"
                                       % self.global_permission_required)
        return perms

    def check_permissions(self, request):
        """
        Checks if *request.user* has all permissions returned by
        *get_required_permissions* method.

        :param request: Original request.
        """
        obj = self.get_permission_object()

        forbidden = get_40x_or_None(request,
                                    perms=self.get_required_permissions(request),
                                    global_perms=self.get_global_required_permissions(request),
                                    obj=obj,
                                    login_url=self.login_url,
                                    redirect_field_name=self.redirect_field_name,
                                    return_403=self.return_403,
                                    return_404=self.return_404,
                                    )
        if forbidden:
            self.on_permission_check_fail(request, forbidden, obj=obj)
        if forbidden and self.raise_exception:
            raise PermissionDenied()
        return forbidden

В django, встроенном в crud views, теперь можно определять global_required_permissions и required_permissions :

class ResourceDeleteView(GlobalPermissionRequiredMixin, DeleteView):
    ...
    global_permission_required = [app.delete_resource]
    permission_required = [app.view_resource]
    ...

Заключение

Это решение основано на том, что User имеет глобальное разрешение на удаление ресурсов, которое может быть предоставлено Group из моделей аутентификации django (роль в контексте этой проблемы). Но он может удалить ресурсы только потому, что у него есть объект на основе view_resource Permission из моделей авторизации django. Я думаю, что это может быть обычным способом реализации ролей с помощью django guardian.

Если я получу несколько одобрений, я открою пулреквест с версией моего кода без конфликтов, приведенной выше.

Действительно интересный подход @jokiefer. Мне это нравится, я изначально пытался реализовать guardian этими принципами. Пользователям потребуется глобальное разрешение view или edit , но тогда для доступа к конкретному объекту требуется индивидуальное разрешение для view или edit . Глобальное разрешение должно было попросить действовать скорее как разрешающее разрешение, без которого разрешения объекта не имели значения. Очевидно, на самом деле это не сработало, поэтому я реструктурировал все.

Допустит ли ваш образец это? т.е. требуется глобальное разрешение delete , но для фактического delete объекта также требуется разрешение delete на основе объекта. Основываясь на вашем примере, требуется глобальный delete , а затем специфичный для объекта view ? Это немного несоответствие?

@dwasyl

Допустит ли ваш образец это?
Да, это сработает для вашего подхода.

Но я не акцентировал внимание на приведенном выше образце. В нашем проекте мы реализуем максимально простое приложение acl (AccessControlList). В этом приложении есть одна основная модель, которая называется acl . Эта модель является абстракцией разрешений хранителя atomic . В нем хранится набор пользователей, набор разрешений, набор (и) доступных объектов. Магия, стоящая за этой моделью, реализована в некоторых сигналах. Например, ownable_models_handling.py - это общий сигнал, который динамически добавляет сигналы ко всем другим моделям, которые должны быть доступны с разрешениями хранителя. ЕСЛИ мы хотим защитить больше моделей, нам просто нужно реализовать новое поле m2m в модели AccessControlList , которое должно вызываться с начальным accessible_MODELNAME для автоматической работы. В acl_handling.py есть реализация, которая добавляет / удаляет разрешения хранителя на основе заданного AccessControlList . При этом нам не нужно изменять ядро ​​хранителя.

Возможно, это приложение acl поможет вам больше, чем приведенный выше пример. При некоторой дополнительной работе над приложением acl , возможно, его можно было бы передать на аутсорсинг из нашего кода для универсального способа добавления ролей в гуадианскую (верхнее приложение для опекуна).

Была ли эта страница полезной?
0 / 5 - 0 рейтинги