Django-guardian: user.has_perm("perm", obj) se comporta de forma inesperada

Criado em 3 set. 2011  ·  28Comentários  ·  Fonte: django-guardian/django-guardian

Se eu usar o método user.has_perm("perm") padrão, ele retornará True apenas, se o usuário tiver uma permissão global "perm" .
E se user.has_perm("perm", obj) for usado, ele retornará True se o usuário tiver permissão para acessar esse objeto específico.
Mas ele retornará False , mesmo que o usuário tenha uma permissão global "perm" , o que é bastante inesperado para mim, porque suponho que ter permissão global deve dar ao usuário acesso a todos os objetos. Estou certo?

Enhancement

Comentários muito úteis

Ei, você pode colar sua configuração de _AUTHENTICATION_BACKENDS_?

Como você já leu a documentação do Django, você sabe que a ordem dos backends especificados É importante.

Suponho que você tenha algo como o seguinte em seu aplicativo:

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

Ou isto:

from django.conf import global_settings
AUTHENTICATION_BACKENDS = (
    'guardian.backends.ObjectPermissionBackend',
) + global_settings.AUTHENTICATION_BACKENDS

Apenas certifique-se de que o back-end padrão seja especificado primeiro.

Você pode confirmar que esse é o problema? Se não, por favor, adicione mais algumas informações (talvez você use alguns outros back-ends também, ou alguns outros patches de macaco de aplicativo que _User.has_perm_ método?).

Todos 28 comentários

Desenterrei um pouco mais e descobri que cada back-end de permissão deve funcionar de forma independente, portanto, não deve haver nenhuma situação como descrevi acima. Mas, por algum motivo, chamar user.has_perm fornecendo instância de objeto, procura apenas verificar as permissões de nível de objeto e ignora a verificação de permissão global. Eu não tenho idéia, qual é a razão desse comportamento. Estou usando o Django 1.2.5.

Ei, você pode colar sua configuração de _AUTHENTICATION_BACKENDS_?

Como você já leu a documentação do Django, você sabe que a ordem dos backends especificados É importante.

Suponho que você tenha algo como o seguinte em seu aplicativo:

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

Ou isto:

from django.conf import global_settings
AUTHENTICATION_BACKENDS = (
    'guardian.backends.ObjectPermissionBackend',
) + global_settings.AUTHENTICATION_BACKENDS

Apenas certifique-se de que o back-end padrão seja especificado primeiro.

Você pode confirmar que esse é o problema? Se não, por favor, adicione mais algumas informações (talvez você use alguns outros back-ends também, ou alguns outros patches de macaco de aplicativo que _User.has_perm_ método?).

Ok, parece que estou muito cansado. A ordem não deve afetar o resultado _has_perm_. Portanto, adicione mais informações sobre as configurações do aplicativo que você está usando. Além disso, você pode garantir que o guardião funcione corretamente executando o conjunto de testes (_python manage.py test guardian_).

Oh, ok, eu li seu problema mais uma vez. O padrão _auth.ModelBackend_ NÃO suporta _supports_object_permissions_ (esse atributo é _False_). De acordo com os documentos do Django, eles adicionariam esse suporte para backend padrão de 1.4.

Então, para sua situação, o comportamento é absolutamente correto e esperado. O back-end padrão é simplesmente omitido.

Você deve verificar as permissões globais antes de verificar as permissões de nível de objeto em seu aplicativo. Essa é a solução mais simples que consigo pensar.

Fechando como _inválido_, no entanto, se você quiser reabri-lo com um novo comentário - sinta-se à vontade para fazê-lo!

Ok, parece que você está certo. Mas não verificar as permissões globais traz algumas dificuldades sérias para mim. Por exemplo, em guardian.decorators.permission_required , que, como presumi, deve estender a funcionalidade do django.contrib.auth.decorators.permission_required comum com verificação de permissão de nível de objeto adicional.
O problema é que eu tenho um aplicativo de trabalho que usa permissões globais e queria adicionar algumas permissões de nível de objeto adicionais a ele, então alterei os decoradores permission_required padrão para os de nível de objeto do django-guardian, mas então os usuários perderam seus direitos de acesso com base em permissões globais.

Parece um comportamento inesperado para mim, você poderia pelo menos adicionar este caso à documentação, porque não é óbvio sem olhar para o código.

Quanto a mim, isso é uma falha de design, resultando em uma verificação extra em todo o código, onde preciso verificar a permissão global e por objeto.

@coagulant : não seria flexível realmente _extend_ decorators da autenticação do Django. Este aplicativo pretende implementar _object permissions_, não estendendo o original. Ou seja, e se você quiser usar algum outro aplicativo que use apenas permissões de nível de guardião e objeto? E se você quiser usar permissões globais apenas no administrador e permissões em nível de objeto em aplicativos fornecidos a usuários comuns? E se o aplicativo exigir permissão global e permissão de nível de objeto para que o usuário possa executar alguma ação no objeto? Há muitos casos, guardião não cobriria todos eles.

Por outro lado, posso admitir que esse caso de uso específico pode ser mais comum que os outros. Para qualquer pessoa interessada nisso - envie outro problema especificando os requisitos. Eu acredito que isso pode ser facilmente alcançado sem incompatibilidade de backwords - ou seja, com nova configuração.

Seria útil para mim, se fosse adicionado apenas outro decorador permission_required, que verificasse as permissões no nível do objeto e as globais. Isso cobriria meu problema.

@Dzejkob , você pode verificar o último commit e dizer se isso seria suficiente (adicionei o sinalizador para aceitar permissões globais). Além disso, deixe-me saber se estender docstring no próprio decorador é suficiente ou devo adicionar mais exemplos/documentos mais descritivos?

Nota: o commit está no novo branch: _feature/add-accept_global_perms-flag-for-decorators_

@lukaszb Sim, instalei uma nova versão de branch do Guardian no meu projeto, adicionei o parâmetro em permission_required decoradores accept_global_perms=True e parece funcionar como deveria. Então, obrigado por fazer esse recurso. Eu acho que é uma boa ideia fundi-lo no tronco.
Sobre os documentos, eles são bem claros para mim, então acho que é o suficiente.

Isso foi corrigido há muito tempo.

Olá, encontrei esse problema porque estou tentando jango-guardian e achei esse comportamento com erros/muito confuso.

Eu adicionei uma permissão global (vamos chamá-la de view_user ) a um usuário (vamos chamá-la de joe ) e então verifiquei se Joe pode visualizar um usuário específico (vamos chamá-la de other_user ) e surpreendentemente retornou False .

joe = User.objects.get(username="joe")
other_user = User.objects.get(username="other_user")
assign_perm("myapp.view_user", joe)
joe.has_perm("myapp.view_user") # True as expected
joe.has_perm("myapp.view_user", other_user) # False, whaaaat?

Agora, não estou usando o decorador permission_required explicitamente, pois meus conjuntos de exibição estão "automaticamente" verificando as permissões por causa do meu REST_FRAMEWORK/DEFAULT_PERMISSION_CLASSES e AUTHENTICATION_BACKENDS em settings.py . Como eu digo ao guardião para se comportar como esperado então?

(Desculpe se estou perdendo algo realmente estúpido, isso é meio que dia 2/3 em djangoland)

Desculpe pelo barulho hoje, ainda mais interessante/confuso que guardian.shortcuts.get_objects_for_user() honra as permissões auth/global por padrão ( accept_global_perms ).

accept_global_perms: if True takes global permissions into account. 
[...]
Default is True.

Embora essa questão tenha sido encerrada em favor de outra, há muitos insights aqui, então estou escrevendo aqui para reacender a conversa. Zen do Python disse:

Precisa haver uma - e de preferência apenas uma - maneira óbvia de fazer isso.

E para mim isso é _fallback para global quando local (nível de objeto) não é especificado_. A ordem não altera o resultado (desde Django returns False if obj is not None ), mas pode alterar o desempenho.

Eu concordo que ter verificações duplas em todo o código não é um bom design e, em certo sentido, também contra o princípio DRY. No entanto, a funcionalidade de implementação do guardião que está disponível no backend padrão do Django também não é DRY. O fato de o Django permitir vários back-ends e verificar um por um em ordem indica que os back-ends devem funcionar juntos, não substituir um ao outro. Se isso estiver certo, então o Django se recusa a verificar os globais quando um obj is not None é _errado_. Se o Django ignorou o obj , pode ser o fallback global para os verificadores de objetos.

Acho que devemos abrir um ticket com o Django, perguntando se, e como, os backends de autorização jogam uns com os outros, e então partir daí.

Com isso dito, acho que o Django mudar seu comportamento é bastante improvável (embora eu ainda ache que devemos perguntar), então o status quo indica que, o guardião precisa evoluir para poder fornecer ambos, dependendo de qual é desejado (o que pode ser definido por meio de configurações ou um argumento para a função). Mas o comportamento padrão eu acho que deve ser o _retorno local para global quando false_ em toda a placa.

Eu adoraria se o Django preferisse um sistema de permissão tri-state: True, False e None. Nesse caso, os verificadores locais poderiam _substituir_ os globais via False ; e via None cada backend poderia dizer: "Não sei, pergunte ao próximo da fila". Nesse caso, o Django pararia de verificar depois de obter um True or False em um dos backends e pararia de desperdiçar poder de processamento.

Isso daria a cada backend mais poder: pode dar uma resposta definitiva ou se referir a outros.

@doganmeh O Django implementa um sistema de permissões de três estados (pelo menos a partir de 1.10). As opções são True, None e aumentando PermissionDenied. Fazer o mais tarde fará com que o Django pare de verificar e retorne False.

Quanto ao problema em questão, acredito que seja um problema do Contrib.Auth. Esse é o backend que lida com 'permissões globais'. O problema é que eles estabeleceram uma convenção indireta que has_perm com obj=None verifica apenas 'permissões de tabela' e has_perm com obj verifica apenas 'permissões de linha'. É improvável que eles mudem isso, mas _devem_ estar abertos a estender sua API para dar suporte à verificação de ambos.

(Pelo menos eu espero que eles estejam abertos a adicionar comportamento para verificar ambos. Parece uma falha clara em seu sistema.)

Quais seriam suas sugestões para a API 'verificar ambos'? O melhor que consigo pensar é um kwarg.

Eu estava falando sobre um sistema explicitamente tristate onde haveria um NullBooleanField na tabela Permission. É verdade que se você considerar a falta de True (ou a falta de permissão) como None , pode ser considerado tristate. Nesse caso, as permissões não especificadas pelo guardião devem ser consideradas None e a decisão deve ser delegada ao global. Se eu tivesse uma escolha, porém, eu ficaria com o design explícito.

@airstandley , acho que você quer dizer uma adição de um kwarg a user.has_perm , como:

def has_perm(self, perm, obj=None, fallback=False)

Acho que isso ajudaria. No caso fallback=True , o guardião chamaria o backend do Django e retornaria o global. Se eu tivesse escolha, no entanto, preferiria que o fallback para o Django fosse tratado naturalmente pelo framework, não pelo seqüestro de seus internos.

@doganmeh Desculpe, eu falei errado. Isso deveria ter lido "True, False e aumentando PermissionDenied". Não:

As opções são True, None e aumentando PermissionDenied

Quanto mais tarde é como eu penso nos retornos na minha cabeça...

Acho que também posso ter entendido mal o que você quis dizer com

Eu adoraria se o Django preferisse um sistema de permissão tri-state

Para esclarecer, você estava falando especificamente da implementação do Modelo de Permissão e não do sistema como um todo?

Meu ponto era que o sistema de back-end de autenticação permite o atalho exato que você descreveu (pelo menos para o has_perm , has_module_perms api). É explícito, se não direto:
Qualquer back-end pode tomar uma decisão por conta própria (retornar True ou PermissionDenied) ou delegar a decisão ao próximo apoiado na cadeia (retornar False). Essa é uma decisão específica da implementação, e não uma qualidade do próprio sistema.

Um ObjectPermissionBackend explícito seria um problema para o meu projeto, então eu escolheria não ser explícito. ("Explicitness" torna a integração com outros backends de verificação de permissão complicada.) Suspeito que, como você, outros prefeririam que fosse explícito. Portanto, ter a 'explicidade' como cenário faz sentido para mim.

@doganmeh
Então, sobre o kwarg.

Em primeiro lugar, continuo me preocupando se não estamos na mesma página sobre como o comportamento do sistema de back-end do auth deveria funcionar. Então para ficar claro:
Meu entendimento é que a intenção era que os backends trabalhassem juntos. Se um aplicativo quisesse usar o sistema de permissão de autenticação (ou seja, 'permissões globais', embora tecnicamente fossem 'permissões de tabela', mas batata, batata), ele instalaria o auth ModelBackend em AUTH_BACKENDS. Se esse aplicativo quisesse usar o sistema ObjectPermission do guardião (ou seja, 'permissão de linha'), não seria o ObjectPermissionBackend do guardião em AUTH_BACKENDS.
ModelBackend lidaria com a permissão global.
ObjectPermissionBackend lidaria com permissões de objeto.
se um usuário tivesse apenas uma permissão de objeto, ModelBackend nunca retornaria True para has_perm . Se um usuário tivesse apenas uma permissão global, ObjectPermissionBackend nunca retornaria True para has_perm . Em ambos os casos, uma chamada para user.has_perm(perm, obj) ainda deve retornar true, pois o usuário tem permissão para _one_ dos backends instalados. (É aqui que encontramos um problema porque o ModelBackend falha nesta conta)

Ok, dado tudo isso.

Em um mundo ideal onde a compatibilidade não fosse um problema, eu também preferiria uma simples mudança no ModelBackend do contrib.auth. ModelBackend.has_perm(user_obj, perm, obj=None) deve retornar True se o usuário tiver a 'permissão global' especificada por perm; independentemente de obj ser None ou não.

No entanto, a compatibilidade é um problema, e é por isso que pergunto se você tem uma solução para o problema.

As únicas soluções _compatíveis com versões anteriores_ que consigo pensar são adicionar um kwarg à API ou adicionar uma configuração AUTH.

Então kwargs:
Vamos chamá-lo de obj_shortcut porque é o melhor que consigo pensar. object_shortcut por padrão seria True. Se object_shortcut for True, os backends devem se comportar como o ModelBackend faz agora: eles devem _only_ verificar as permissões 'table/global' se obj for None, caso contrário, eles devem _only_ verificar as permissões 'row/object'. No entanto, se object_shortcut for False, os backends devem se comportar como nós dois preferiríamos: eles verificariam _both_ permissões globais e de objeto quando o objeto não fosse Nenhum. Então add-ons como o Guardian sempre poderiam fornecer um mixin com um método has_perm com object_shortcut=False como padrão. user.has_perm(perm, obj, object_shortcut=False) retornaria corretamente True se a Permissão representada por perm fosse atribuída ao Usuário, enquanto as chamadas legadas para user.has_perm(perm, obj) continuariam a retornar False.

Uma configuração teria o mesmo resultado, mas basicamente resultaria em uma troca em ModelBackend._get_permissions .

if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
    return set()

se tornaria algo como:

if not user_obj.is_active or user_obj.is_anonymous or (obj is not None and legacy_behaviour_setting_is _on):
    return set()

Não tenho certeza qual é melhor. Eu acho que uma configuração é mais limpa, mas tenho certeza que os desenvolvedores do Django seriam mais qualificados para determinar isso.

A questão é que é possível corrigir o problema, e acredito fortemente que o problema é um bug do Django, não do Guardian.

@airstandley Você está certo, False ou None não importa. Ambos significam que eu não sei . Eventualmente, se ninguém souber, a permissão não é concedida.

Geralmente estou na mesma página com você, embora o django forçando os back-ends a se comportarem de uma maneira ou de outra pareça degradar o acoplamento fraco. Acho que deveria ter mecanismos para permitir que cada back-end obtenha as informações necessárias para realizar seus trabalhos. Estou com você nos argumentos de palavra-chave, mas não apenas forçando de uma maneira ou de outra.

Esta conversa parece uma perda de tempo, mas me ajudou a esclarecer para mim mesmo que não há permissão concedida em tutor não é uma negação, é apenas falta de informação.

De qualquer forma, fiz um pull request #546. Por favor, confira e deixe-me saber o que você pensa.

Eu criei um ticket com o django: https://code.djangoproject.com/ticket/29012 , e me pergunto o que eles diriam.

Parece que havia alguns outros tickets com o django: https://code.djangoproject.com/ticket/20218

Eu fechei o meu.

@doganmeh
Eu gosto da direção que você está indo com # 546. Se isso ajudar, eu deveria ter tempo para examinar o resto das unidades e escrever alguns testes para esses fallbacks neste fim de semana.

Na tangente. Seu comentário sobre "Django forçando backends" a se comportarem de uma maneira específica me confunde, mas também me fez pensar. Os back-ends podem se comportar como quiserem; minha preocupação inicial com o ObjectPermissionBackend do Guardian decorre da documentação sugerindo que ele foi projetado para ser executado ao lado do ModelBackend do Auth. O Guardian pode oferecer vários back-ends, um projetado para funcionar com ModelBackend e outro projetado para funcionar sozinho. (IE um que apenas verifica as tabelas UserObjectPermission/GroupObjectPermission do Guardian, o outro verifica as tabelas UserObjectPermission/GroupObjectPermission e a tabela de permissões do Auth.)
Pessoalmente, prefiro a abordagem atual com uma configuração e kwargs. Acho que a principal desvantagem de uma abordagem de back-end múltiplo seria que não fica claro como os atalhos e as funções de conveniência devem se comportar.

Vi o post na lista de discussão do Django dev. Espero que aceitem isso, ou concordem em mudar o comportamento de forma incompatível. A limitação atual na API é apenas desajeitada.

Você pode apoiá-lo lá? 😊

Enviado do Mail para Windows 10

De: airstandley
Enviado: sexta-feira, 12 de janeiro de 2018 12:06
Para: django-guardian/django-guardian
CC: Mehmet Dogan; Menção
Assunto: Re: [django-guardian/django-guardian] user.has_perm("perm", obj) se comporta inesperadamente (#49)

Vi o post na lista de discussão do Django dev. Espero que aceitem isso, ou concordem em mudar o comportamento de forma incompatível. A limitação atual na API é apenas desajeitada.

Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub ou silencie a conversa.

Depois de lidar com esse problema por cerca de uma semana, passei a acreditar mais firmemente que cada back-end precisa fazer apenas uma coisa, que são permissões de objeto para o Guardian. Para que isso aconteça, o Django deve tratar obj s mais bem.

Tem o patch que enviei para o Django para isso: https://github.com/django/django/pull/9581 (comente se puder). Aqueles que conseguem, podemos limpar a recuperação de permissões de modelo onde quer que haja no Guardian e apenas fazer uma chamada para o back-end padrão.

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

Questões relacionadas

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

ad-m picture ad-m  ·  13Comentários

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

lukaszb picture lukaszb  ·  14Comentários

BenDevelopment picture BenDevelopment  ·  5Comentários