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

Creado en 3 sept. 2011  ·  28Comentarios  ·  Fuente: django-guardian/django-guardian

Si utilizo el método estándar user.has_perm("perm") , solo devolverá True , si el usuario tiene un permiso global "perm" .
Y si se usa user.has_perm("perm", obj) , devolverá True si el usuario tiene permiso para acceder a este objeto en particular.
Pero devolverá False , incluso si el usuario tiene un permiso global "perm" , lo cual es bastante inesperado para mí, porque asumo que tener un permiso global debería dar acceso al usuario a todos los objetos. ¿Tengo razón?

Enhancement

Comentario más útil

Oye, ¿puedes pegar tu configuración de _AUTHENTICATION_BACKENDS_?

Como ya ha leído la documentación de Django, sabe que el orden de los backends específicos ES importante.

Supongo que tienes algo como lo siguiente en tu aplicación:

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

O esto:

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

Solo asegúrese de que el backend predeterminado se especifique primero.

¿Puedes confirmar que ese es el problema? De lo contrario, agregue más información (¿quizás también use otros backends, o algunos otros parches mono de la aplicación que usan el método _User.has_perm_?).

Todos 28 comentarios

Excavé un poco más y descubrí que cada backend de permisos debería funcionar de forma independiente, por lo que no debería haber una situación como la que describí anteriormente. Pero por alguna razón, al llamar a user.has_perm que proporciona una instancia de objeto, busca verificar solo los permisos de nivel de objeto y omite la verificación de permisos globales. No tengo idea, cuál es la razón de ese comportamiento. Estoy usando Django 1.2.5.

Oye, ¿puedes pegar tu configuración de _AUTHENTICATION_BACKENDS_?

Como ya ha leído la documentación de Django, sabe que el orden de los backends específicos ES importante.

Supongo que tienes algo como lo siguiente en tu aplicación:

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

O esto:

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

Solo asegúrese de que el backend predeterminado se especifique primero.

¿Puedes confirmar que ese es el problema? De lo contrario, agregue más información (¿quizás también use otros backends, o algunos otros parches mono de la aplicación que usan el método _User.has_perm_?).

Ok, parece que estoy demasiado cansado. El orden no debería afectar el resultado _has_perm_. Por lo tanto, agregue más información sobre la configuración de la aplicación que está utilizando. Además, puede asegurarse de que Guardian funcione correctamente ejecutando el conjunto de pruebas (_python manage.py test guardian_).

Oh, ok, he leído tu problema una vez más. El _auth.ModelBackend_ predeterminado NO es compatible con _supports_object_permissions_ (ese atributo es _False_). De acuerdo con los documentos de Django, agregarían ese soporte para el backend predeterminado de 1.4.

Entonces, para su situación, el comportamiento es absolutamente correcto y esperado. El backend predeterminado simplemente se omite.

Debe verificar los permisos globales antes de verificar los permisos de nivel de objeto en su aplicación. Esa es la solución más simple que se me ocurre.

Cerrando como _inválido_, sin embargo, si desea volver a abrirlo con un nuevo comentario, ¡no dude en hacerlo!

Ok, parece que tienes razón. Pero no verificar los permisos globales me trae serias dificultades. Por ejemplo, en guardian.decorators.permission_required , que, como supuse, debería ampliar la funcionalidad de django.contrib.auth.decorators.permission_required ordinario con verificación de permisos de nivel de objeto adicional.
El problema es que tengo una aplicación en funcionamiento que usa permisos globales, y quería agregarle algunos permisos de nivel de objeto adicionales, así que cambié los decoradores permission_required predeterminados a los de nivel de objeto de django-guardian, pero luego, los usuarios habían perdido sus derechos de acceso basados ​​en permisos globales.

Me parece un comportamiento inesperado, ¿podría al menos agregar este caso a la documentación, porque no es obvio sin mirar el código?

En cuanto a mí, esta es una falla de diseño, lo que resulta en una verificación adicional en todo el código donde necesito verificar el permiso tanto globalmente como por objeto.

@coagulant : no sería flexible realmente _extender_ decoradores de la autenticación de Django. Esta aplicación intenta implementar _permisos de objeto_, sin extender el original. Es decir, ¿qué sucede si desea utilizar alguna otra aplicación que utilice únicamente permisos a nivel de guardián y de objeto? ¿Qué sucede si desea usar permisos globales solo como administrador y permisos de nivel de objeto en aplicaciones otorgadas a usuarios regulares? ¿Qué sucede si la aplicación requiere tanto un permiso global como un permiso a nivel de objeto para que el usuario pueda realizar alguna acción en el objeto? Hay muchos casos, el guardián no los cubriría todos.

Por otro lado, puedo admitir que este caso de uso particular podría ser más común que los demás. Para cualquier persona interesada en esto, presente otro problema que especifique los requisitos. Creo que esto se puede lograr fácilmente sin incompatibilidad de backwords, es decir, con una nueva configuración.

Sería útil para mí, si se agregara solo otro decorador de permisos requeridos, que verifique los permisos a nivel de objeto y los permisos globales. Eso cubriría mi problema.

@Dzejkob , ¿puede consultar la última confirmación y decir si esto sería suficiente (he agregado un indicador para aceptar permisos globales). Además, avíseme si extender la cadena de documentos en el decorador es suficiente, o ¿debería agregar más ejemplos/documentos más descriptivos?

Nota: la confirmación está en la nueva rama: _feature/add-accept_global_perms-flag-for-decorators_

@lukaszb Sí, instalé una nueva versión de rama de Guardian en mi proyecto, agregué un parámetro en permission_required decoradores accept_global_perms=True y parece funcionar como debería. Así que gracias por hacer esta función. Creo que es buena idea fusionarlo con el maletero.
En cuanto a los documentos, son bastante claros para mí, así que creo que es suficiente.

Esto se solucionó hace mucho tiempo.

Hola, encontré este problema mientras intentaba con jango-guardian y encontré este comportamiento defectuoso/muy confuso.

Agregué un permiso global (llamémoslo view_user ) a un usuario (llamémoslo joe ) y luego verifiqué si Joe puede ver un usuario específico (llamémoslo other_user ) y sorprendentemente devolvió 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?

Ahora, no estoy usando el decorador permission_required explícitamente ya que mis conjuntos de vistas están comprobando "automáticamente" los permisos debido a mi REST_FRAMEWORK/DEFAULT_PERMISSION_CLASSES y AUTHENTICATION_BACKENDS en settings.py . ¿Cómo le digo al guardián que se comporte como se espera entonces?

(Disculpas si me estoy perdiendo algo realmente estúpido, este es el día 2/3 en djangoland)

Perdón por el ruido de hoy, aún más interesante/confuso es que guardian.shortcuts.get_objects_for_user() respeta los permisos de autenticación/globales de forma predeterminada ( accept_global_perms ).

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

Aunque este tema se cerró a favor de otro, hay muchas ideas aquí, así que escribo aquí para reavivar la conversación. Zen de Python dice:

Debe haber una, y preferiblemente solo una, forma obvia de hacerlo.

Y para mí eso es _volver a global cuando no se especifica local (nivel de objeto)_. El orden no cambia el resultado (desde Django returns False if obj is not None ), pero puede cambiar el rendimiento.

Estoy de acuerdo en que tener controles dobles en todo el código no es un buen diseño y, en cierto sentido, también va en contra del principio SECO. Sin embargo, la funcionalidad de implementación de guardián que está disponible dentro del backend predeterminado de Django tampoco es DRY. Que Django permita múltiples backends y verifique uno por uno en orden indica que se supone que los backends deben jugar juntos, no reemplazarse entre sí. Si esto es correcto, entonces Django se niega a verificar los globales cuando un obj is not None es _incorrecto_. Si Django ignoró el obj , podría ser el respaldo global para los verificadores de objetos.

Creo que deberíamos abrir un ticket con Django, preguntando si, y cómo, los backends de autorización interactúan entre sí, y luego continuar desde allí.

Dicho esto, creo que es bastante improbable que Django cambie su comportamiento (aunque sigo pensando que deberíamos preguntar), por lo que el statu quo indica que el guardián debe evolucionar para poder proporcionar ambos dependiendo de cuál se desee (cuál puede establecerse a través de la configuración o un argumento para la función). Pero creo que el comportamiento predeterminado debería ser el _retroceso local a global cuando es falso_ en todos los ámbitos.

Me encantaría que Django prefiriera un sistema de permisos de tres estados: Verdadero, Falso y Ninguno. En ese caso, las fichas locales podrían _anular_ las globales a través False ; y a través None cada backend podría llegar a decir: "No lo sé, pregúntale al siguiente en la fila". En ese caso, Django dejaría de verificar después de obtener un True or False en uno de los backends y dejaría de desperdiciar poder de procesamiento.

Esto le daría a cada backend más poder: puede dar una respuesta definitiva o referirse a otros.

@doganmeh Django implementa un sistema de permisos de tres estados (al menos a partir de 1.10). Las opciones son Verdadero, Ninguno y generar Permiso denegado. Hacer lo último hará que Django deje de verificar y devuelva False.

En cuanto al problema en cuestión, creo que es un problema de Contrib.Auth. Ese es el backend que se ocupa de los 'permisos globales'. El problema es que han establecido una convención indirecta que has_perm con obj=None solo verifica 'permisos de tabla' y has_perm con obj solo verifica 'permisos de fila'. Es poco probable que cambien eso, pero _deberían_ estar abiertos a extender su API para admitir la verificación de ambos.

(Al menos espero que estén abiertos a agregar comportamiento para verificar ambos. Parece una falla clara en su sistema).

¿Cuáles serían sus sugerencias para la API "verificar ambos"? Lo mejor que se me ocurre es un kwarg.

Estaba hablando de un sistema explícitamente triple donde habría un NullBooleanField en la tabla de Permisos. Cierto, si toma la falta de True (o la falta de permiso) como un None , podría considerarse como tristate. En ese caso, los permisos no especificados por el guardián deben tomarse como None y la decisión debe delegarse al global. Sin embargo, si tuviera que elegir, me quedaría con el diseño explícito.

@airstandley Supongo que te refieres a la adición de un kwarg a user.has_perm como:

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

Creo que eso podría ayudar. En el caso de que fallback=True , Guardian llamaría al backend de Django y devolvería el archivo global. Sin embargo, si tuviera que elegir, preferiría que la alternativa a Django sea manejada naturalmente por el marco, no por el secuestro de sus partes internas.

@doganmeh Lo siento, me expresé mal. Eso debería haber dicho "Verdadero, Falso y aumento de permiso denegado". No:

Las opciones son Verdadero, Ninguno y generar Permiso denegado.

Lo último es cómo pienso en los retornos en mi cabeza...

Creo que también he entendido mal lo que querías decir con

Me encantaría que Django prefiriera un sistema de permisos tri-estatal

Para aclarar, ¿estaba hablando de la implementación del modelo de permisos específicamente y no del sistema en su conjunto?

Mi punto era que el sistema de back-end de autenticación permite el atajo exacto que describiste (al menos para el has_perm , has_module_perms api). Es explícito, si no sencillo:
Cualquier backend puede tomar una decisión por sí mismo (devolver True o PermissionDenied), o delegar la decisión al siguiente respaldado en la cadena (devolver False). Esa es una decisión específica de implementación, y no una cualidad del sistema en sí.

Un ObjectPermissionBackend explícito sería un problema para mi proyecto, por lo que optaría por no ser explícito. ("Lo explícito" hace que la integración con otros backends de verificación de permisos sea engorrosa). Sospecho que, como usted, otros preferirían que fuera explícito. Así que tener la 'explicidad' como escenario tiene sentido para mí.

@doganmeh
Así que sobre los kwarg.

En primer lugar, me sigue preocupando que no estemos en la misma página en cuanto a cómo se pretendía que funcionara el comportamiento del sistema de back-end de autenticación. Así que para ser claro:
Tengo entendido que la intención era que los backends trabajaran juntos. Si una aplicación quisiera usar el sistema de permisos de autenticación (es decir, 'permisos globales', aunque técnicamente serían 'permisos de tabla' pero papa, papa), instalaría el ModelBackend de autenticación en AUTH_BACKENDS. Si esa aplicación quisiera usar el sistema ObjectPermission del guardián (es decir, 'permiso de fila'), instalaría el ObjectPermissionBackend del guardián en AUTH_BACKENDS.
ModelBackend manejaría el permiso global.
ObjectPermissionBackend manejaría los permisos de objetos.
si un usuario solo tuviera un permiso de objeto, ModelBackend nunca devolvería True para has_perm . Si un usuario solo tuviera un permiso global, ObjectPermissionBackend nunca devolvería True para has_perm . En ambos casos, una llamada a user.has_perm(perm, obj) aún debería devolver verdadero, ya que el usuario tiene permiso para _uno_ de los backends instalados. (Aquí es donde encontramos un problema porque ModelBackend falla en esta cuenta)

Ok, dado todo eso.

En un mundo ideal donde la compatibilidad no fuera un problema, yo también preferiría un cambio simple al ModelBackend de contrib.auth. ModelBackend.has_perm(user_obj, perm, obj=None) debería devolver True si el usuario tiene el 'permiso global' especificado por perm; independientemente de si obj es Ninguno o no.

Sin embargo, la compatibilidad es un problema, por lo que le pregunto si tiene una solución al problema.

Las únicas soluciones _compatibles con versiones anteriores que se me ocurren son agregar un kwarg a la API o agregar una configuración de AUTH.

Entonces kwargs:
Llamémoslo obj_shortcut porque es lo mejor que se me ocurre. object_shortcut por defecto sería Verdadero. Si object_shortcut es Verdadero, los backends deberían comportarse como lo hace ModelBackend ahora: deberían _solo_ verificar los permisos de 'tabla/global' si obj es Ninguno, de lo contrario, _solo_ deberían verificar los permisos de 'fila/objeto'. Sin embargo, si object_shortcut es False, los backends deberían comportarse como ambos preferiríamos: verificarían _tanto_ los permisos globales como los de objeto cuando el objeto no fuera Ninguno. Luego, los complementos como Guardian siempre podrían proporcionar una mezcla con un método has_perm con object_shortcut=False como predeterminado. user.has_perm(perm, obj, object_shortcut=False) devolvería correctamente Verdadero si el Permiso representado por perm se hubiera asignado al Usuario, mientras que las llamadas heredadas a user.has_perm(perm, obj) continuarían dando Falso.

Una configuración tendría el mismo resultado pero básicamente resultaría en un cambio en ModelBackend._get_permissions .

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

se convertiría en 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()

No estoy seguro de cuál es mejor. Creo que una configuración es más limpia, pero estoy seguro de que los desarrolladores de Django estarían más calificados para determinar eso.

El punto es que es posible solucionar el problema, y ​​creo firmemente que el problema es un error de Django, no de Guardian.

@airstandley Tienes razón, False o None no importa. Ambos significan que no lo sé . Eventualmente, si nadie lo sabe, no se otorga el permiso.

En general, estoy en la misma página contigo, aunque django obliga a los backends a comportarse de una forma u otra parece degradar la falta de acoplamiento. Creo que debería tener mecanismos para permitir que cada backend obtenga la información que necesita para hacer su trabajo. Sin embargo, estoy con usted en los argumentos de palabras clave, pero no solo forzando una forma u otra.

Esta conversación parece una pérdida de tiempo, pero me ayudó a aclararme a mí mismo que no hay un permiso otorgado en Guardian, no es una negación, es solo falta de información.

De todos modos, hice una solicitud de extracción #546. Por favor, échale un vistazo y déjame saber lo que piensas.

Creé un ticket con django: https://code.djangoproject.com/ticket/29012 y me pregunto qué dirían.

Parece que hubo otros boletos con django: https://code.djangoproject.com/ticket/20218

Cerré el mío.

@doganmeh
Me gusta la dirección en la que te diriges con el #546. Si fuera de ayuda, debería tener tiempo para revisar el resto de las unidades y escribir algunas pruebas para estas alternativas este fin de semana.

En una tangente. Tu comentario sobre "Django obligando a los backends" a comportarse de una manera específica me confunde, pero también me hizo pensar. Los backends pueden comportarse como les gustaría; mi preocupación inicial sobre ObjectPermissionBackend de Guardian proviene de la documentación que sugiere que fue diseñado para ejecutarse junto con ModelBackend de Auth. Guardian podría ofrecer múltiples backends, uno diseñado para funcionar con ModelBackend y otro diseñado para funcionar solo. (Es decir, uno que solo verifica las tablas UserObjectPermission/GroupObjectPermission de Guardian, el otro verifica las tablas UserObjectPermission/GroupObjectPermission y la tabla de permisos de Auth).
Personalmente prefiero el enfoque actual con ambientación y kwargs. Creo que la principal desventaja de un enfoque de back-end múltiple sería que no queda claro cómo deben comportarse los atajos y las funciones de conveniencia.

Vi la publicación en la lista de correo de desarrolladores de Django. Espero que acepten eso, o estén de acuerdo en cambiar el comportamiento de una manera incompatible. La limitación actual de la API es simplemente torpe.

¿Puedes apoyarlo allí? 😊

Enviado desde Mail para Windows 10

De: airstandley
Enviado: viernes, 12 de enero de 2018 12:06 p. m.
Para: django-guardián/django-guardián
CC: Mehmet Dogan; Mencionar
Asunto: Re: [django-guardian/django-guardian] user.has_perm("perm", obj) se comporta de forma inesperada (#49)

Vi la publicación en la lista de correo de desarrolladores de Django. Espero que acepten eso, o estén de acuerdo en cambiar el comportamiento de una manera incompatible. La limitación actual de la API es simplemente torpe.

Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub o silencie el hilo.

Después de lidiar con este problema durante aproximadamente una semana, llegué a creer más firmemente que cada backend debe hacer solo una cosa, que son los permisos de objetos para Guardian. Para que eso suceda, Django debe tratar a obj s más amablemente.

Está el parche que le envié a Django para eso: https://github.com/django/django/pull/9581 (comenta si puedes). Los que lo hacen, podemos limpiar la recuperación de los permisos del modelo donde sea que haya en Guardian, y simplemente hacer una llamada al backend predeterminado.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

lukaszb picture lukaszb  ·  14Comentarios

brianmay picture brianmay  ·  16Comentarios

BenDevelopment picture BenDevelopment  ·  5Comentarios

johnthagen picture johnthagen  ·  9Comentarios

Allan-Nava picture Allan-Nava  ·  35Comentarios