Django-guardian: Adicionar funções

Criado em 16 jan. 2011  ·  14Comentários  ·  Fonte: django-guardian/django-guardian

Em primeiro lugar, deixe-me explicar por que não adicionamos papéis em primeiro lugar. Resumindo, a política do Django é incluir baterias simples, mas bem feitas. django.contrib.auth é o melhor exemplo. Quantas vezes as pessoas precisam trocar autenticação com implementação própria? Eu acredito que não muitos. guardian tem a intenção de ser uma espécie de "extensão" para o aplicativo de autenticação, com uma interface sã e simples. E é isso. Sem muitas funcionalidades extras incluídas (exceto algumas funções de atalho - mas isso se refere à parte "sã").

Agora, o que acontece com os papéis? Tudo o que eles sempre precisaram? Na verdade, acredito que não. Mesmo com grupos, realmente. O usuário é necessário, pois é uma entidade fundamental para a grande maioria de todos os aplicativos.

Ok, aqui estamos, v1.0 quase lá fora. Portanto, para a v1.1, o suporte a funções seria provavelmente o recurso mais importante. Ainda assim, na minha opinião, deveria ser "opcional" de alguma forma.

API change Enhancement

Todos 14 comentários

+1 Mas eu concordo que papéis são algo que quase nunca é necessário na maioria dos aplicativos escritos em Django.
De vez em quando, alguém como nós tenta implementar um aplicativo (sistema de gerenciamento de documentos personalizado) para o qual django não foi realmente feito, e quando você tem muitos permanentes, começa a pensar em papéis :) papéis e herança são as duas coisas que devem chegar à v1.1

a parte opcional é fácil. assim como usar django não faz com que você use grupos, não faz com que as pessoas tenham que definir papéis.

Obrigado pelo aplicativo btw.
cinza

+1 para funções, dependendo da implementação.

1 para funções e herança

Eu acho que as funções são um "grupo de permissões", não sei se isso é o mesmo para você. Com essa classe de funções, atribuir várias permissões é mais fácil. Sugiro implementar isso usando conjuntos Python para subtrair uma função a outra ou obter a interseção e assim por diante:

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

+1 para funções, herança e opcional

Olá a todos,
Eu sou um dos caras que precisa - ou gostaria de ter - permissões de objeto com base em funções.

Eu entendo desta discussão que por boas razões não é uma prioridade para django-guardian. Então, vou criar meu próprio aplicativo. No entanto, acho que a maioria dos recursos e componentes do django-guardian são bons e, se possível, não quero reinventar a roda, mas reutilizá-los. Então, minha pergunta é, se você está disposto a tornar a proteção do django um pouco mais plugável. Por exemplo (essa é apenas a primeira coisa que surgiu em minha mente, enquanto examinava o código): fazer a configuração a para ObjectPermissionChecker, que permitirá que outros aplicativos substituam a classe Checker por algo que cuida das funções?

O que é que vocês acham?

Obrigado
Juergen

Hej @schacki , essa é uma ideia muito boa. Os testes provavelmente falhariam para a classe de verificador não padrão, mas podemos marcá-los ou garantir que sejam executados apenas com o verificador padrão. Você pode criar uma tarefa específica para isso, por favor?

Eu faria qualquer classe de verificador não padrão não parte do django-guardign, mas do "outro" aplicativo - portanto, é o trabalho do outro aplicativo para as classes de verificador não padrão. Deixe-me fazer uma análise mais completa do que pode precisar ser adaptado e alterado antes de abrir uma tarefa. Com "tarefa", presumo que você se refira a um problema separado? A propósito: pode levar algumas semanas antes que isso avance.

este [1] projeto ajuda com alguma coisa?

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

Texto a seguir copiado por @suriya de # 330:

Houve alguma discussão no passado # 23 sobre a adição de suporte a papéis no django-guardian. No entanto, não foi muito longe. Isso provavelmente ocorre porque adicionar funções a django-guardian parece uma tarefa muito complexa.

Acho que tenho uma proposta muito simples. Eu gostaria de propor e se houver interesse, implementar. No momento, eu tenho meu próprio fork do django-guardian que esta funcionalidade. Seria ótimo ver isso usado por outras pessoas também.

O núcleo da verificação de permissão do django-guardian para uma visão é implementado na função get_403_or_None() . Esta função verifica se um usuário possui todas as permissões necessárias. 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)

Funções de apoio são uma pequena mudança. Em vez de verificar se um usuário possui all permissões, simplesmente temos que verificar se o usuário satisfaz pelo menos uma permissão. Algo como

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)

deve fazer isso. Para adicionar esse suporte, tudo o que precisamos é definir um novo sinalizador em get_403_or_None() e seus chamadores que especifica se queremos que todas ou apenas algumas permissões sejam satisfeitas.

Abri # 386 e estou percebendo que provavelmente é o mesmo. O que eu precisaria é que no código em que estou verificando a permissão, eu possa fazer "este usuário tem permissões de edição neste objeto", mas quando estou atribuindo permissões aos usuários, atribuo a eles a permissão de "moderador", que é um superconjunto de múltiplas permissões, incluindo permissão para "editar". Isso me permite mais tarde adicionar mais permissões ao "moderador" sem ter que atribuí-las manualmente a todos os usuários.

Podemos nomear essa hierarquia de permissões, de papéis.

+1 para funções, herança e opcional

Implementei um conceito de função como abaixo:

personalizar 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)

implementar 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

No django construído em visualizações crud, agora é possível definir global_required_permissions e required_permissions :

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

Conclusão

Esta solução é baseada no fato de que User tem permissão global para deletar recursos, o que poderia ser concedido por Group dos modelos de autenticação django (o papel no contexto deste problema). Mas ele só pode deletar recursos para os quais ele tem o objeto baseado em view_resource Permission dos modelos de autenticação django. Eu acho que essa pode ser uma maneira comum de implementar funções com guardião django.

Se eu obtiver alguns polegares para cima, irei abrir uma solicitação de pull com uma versão de código livre de conflito do meu código acima.

Abordagem realmente interessante @jokiefer. Eu gosto disso, originalmente tentei implementar guardian nesse sentido. Os usuários precisariam da permissão global view ou edit , mas para acessar um objeto específico, uma permissão individual para view ou edit era necessária. A permissão global iria pedir mais uma permissão de habilitação, sem a qual as permissões do objeto não importavam. Obviamente, isso não funcionou, então reestruturei as coisas.

Sua amostra permitiria isso? isto é, a permissão global delete é necessária, mas para realmente delete um objeto, a permissão baseada em objeto delete também é necessária. Com base no seu exemplo, o delete global é necessário e, em seguida, um objeto específico view ? Isso é um pouco incompatível?

@dwasyl

Sua amostra permitiria isso?
Sim, isso funcionaria para sua abordagem.

Mas não me concentrei na amostra acima. Em nosso projeto, implementamos o mais simples possível acl app (AccessControlList). Neste aplicativo, há um modelo principal chamado acl . Este modelo é uma abstração das permissões de guardião atomic . Ele armazena um conjunto de usuários, conjunto de permissões, conjunto (s) de objetos acessíveis. A coisa mágica por trás desse modelo é implementada em alguns sinais. Por exemplo, ownable_models_handling.py é um sinal genérico que anexa sinais dinamicamente a todos os outros modelos que devem ser acessíveis com permissões de guardião. SE quisermos proteger mais modelos, simplesmente precisamos implementar um novo campo m2m no modelo AccessControlList , que deve ser chamado com um accessible_MODELNAME para funcionar automaticamente. No acl_handling.py existe a implementação que adiciona / remove as permissões do guardião com base no dado AccessControlList . Com isso, não precisamos modificar o núcleo guardião.

Talvez este aplicativo acl ajude você mais do que o exemplo acima. Com algum trabalho extra no aplicativo acl talvez ele possa ser terceirizado de nosso código para uma maneira genérica de adicionar funções ao guadian (uma solução de aplicativo on-top para guardião).

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

brianmay picture brianmay  ·  16Comentários

g-as picture g-as  ·  10Comentários

Allan-Nava picture Allan-Nava  ·  4Comentários

johnthagen picture johnthagen  ·  9Comentários

Allan-Nava picture Allan-Nava  ·  35Comentários