2011-12-26 15 views
9

Estoy construyendo una API simple usando django-tastypie. La idea es que tengo dos recursos:Problemas con ForeignKey usando POST en Django-Tastypie

  • Un Nota recurso que representa una nota dejada por un usuario. Solo el usuario que creó una Nota puede editarla.
  • A Comentario recurso. Los comentarios pueden dejarse en cualquier nota de cualquier usuario.

TL; DR: Soy incapaz de limitar la edición de nota al creador de una nota al tiempo que permite a cualquier usuario para comentar en una nota.

estoy usando la siguiente configuración para la autenticación:

class CreatedByEditAuthorization(Authorization): 
    def is_authorized(self, request, object=None, **kwargs): 
     return True 

    def apply_limits(self, request, object_list): 
     if request and request.method != 'GET' and hasattr(request, 'user'): 
      return object_list.filter(created_by=request.user) 
     return object_list 

En pocas palabras, un usuario sólo está autorizado para editar los objetos de los que son iguales a la propiedad created_by (sólo pueden editar los objetos que crearon) .

Esto está relacionado de la siguiente manera:

class NoteResource(ModelResource): 
    comments = fields.ToManyField('myapp.api.resources.CommentResource', 'comments', null=True, blank=True) 
    created_by = fields.ToOneField('account.api.resources.UserResource', 'created_by') 

    def obj_create(self, bundle, request, **kwargs): 
     return super(HapResource, self).obj_create(bundle, request, created_by=request.user) 

    class Meta: 
     queryset = Note.objects.all() 
     allowed_methods = ['get', 'put', 'post'] 
     authorization = CreatedByEditAuthorization() 

así que aquí, cuando se crea un objeto, que se conecte automáticamente, el usuario actual al atributo created_by y vincularlo con la debida autorización.

Un recurso Comment es simple y solo tiene un ForeignKey a un recurso Note.

El problema es este: si el usuario A crea una Nota y el usuario B intenta comentar sobre esa Nota, tastypie envía (o simula) una solicitud POST para editar esa Nota. Ese intento se rechaza ya que el usuario B no creó la Nota, por lo que la creación del comentario falla.

La pregunta es la siguiente: ¿Hay una manera de cualquiera:

  1. Prevenir tastypie el uso de un POST para crear la inversa relación con la nota de recursos o
  2. cambiar el esquema de autorización por lo que las Notas solo pueden ser editadas por su creador, ¿pero los comentarios pueden ser creados en general?

Gracias de antemano por cualquier idea.

Editar: Tengo un gran hack gordo que puede lograr esto. Estoy bastante seguro de que es seguro, pero no estoy seguro; Intentaré construir algunas consultas para asegurarme. En lugar de utilizar fields.ForeignKey en Comment relacionarse con Note, se crea un campo personalizado:

class SafeForeignKey(fields.ForeignKey): 
    def build_related_resource(self, value, request=None, related_obj=None, related_name=None): 
     temp = request.method 
     if isinstance(value, basestring): 
      request.method = 'GET' 
     ret = super(SafeForeignKey, self).build_related_resource(value, request, related_obj, related_name) 
     request.method = temp 
     return ret 

Cada vez que tratamos de construir este recurso relacionado, marcamos la solicitud como una GET (ya que esperamos que se puede adaptar a una consulta SELECT en lugar de UPDATE que coincide con PUT o POST). Esto es realmente feo y potencialmente inseguro si se usa incorrectamente, y espero una mejor solución.

Edit 2: Al leer la fuente de sabrosa, hasta donde puedo decir, no hay forma de filtrar la autorización por la consulta que realmente se enviará.

+0

Un par de preguntas - ¿Estás utilizando contrib.comments? ¿Estás usando autenticación y autorización? Tengo lo que parece ser una configuración muy similar (sin parecer CommentResource) que funciona bien al publicar un nuevo comentario en otro objeto de los usuarios. – JamesO

+0

@JamesO No, nuestros comentarios son algo más ricos que contrib.comments (y hay otros datos asociados con una publicación que está teniendo el mismo problema). Actualmente solo estamos usando la autenticación incorporada() (es decir, todos están autenticados). –

+0

¿Ha publicado esto como un problema en django-tastypie: https://github.com/toastdriven/django-tastypie/issues? Si realmente intenta actualizar un registro principal cada vez que crea algo relacionado con él, eso está más cerca de un error que de una característica. –

Respuesta

4

Según discusión sobre https://github.com/toastdriven/django-tastypie/issues/480#issuecomment-5561036:

El método que determina si un Resource puede ser actualizado es can_update. Por lo tanto, para que esto funcione de la manera "correcta", es necesario crear una subclase de NoteResource:

class SafeNoteResource(NoteResource): 
    def can_update(self): 
     return False 
    class Meta: 
     queryset = Note.objects.all() 
     allowed_methods = ['get'] 
     authorization = Authorization() 
     # You MUST set this to the same resource_name as NoteResource 
     resource_name = 'note' 

entonces vamos CommentResource enlace a las notas de la manera estándar: note = fields.ForeignKey(SafeNoteResource, 'note').

1

una solución sencilla debe ser de revisar el interior del apply_limits si la solicitud es para un Nota recurso o un comentario recursos. p.ej. algo así como

def apply_limits(self, request, object_list): 
    if request and request.method != 'GET' and hasattr(request, 'user') and getattr(request, 'path','').startswith('/api/v1/note'): 
     return object_list.filter(created_by=request.user) 
    return object_list 

Entonces sólo está limitando el acceso a las notas al mismo usuario, cuando el usuario está accediendo a un Nota sitio directamente, y no a través de otros recursos relacionados, tales como Comentarios.

actualización o una opción un poco más segura sería la de comprobar que la solicitud no hace comienzo con 'api/v1/comentario' - por lo que sólo estamos listas blancas el comentario de acceso en lugar de nada que no sea la nota . El mismo principio sin embargo se aplica. Tenga cuidado con esta comparación basada en texto de la ruta de solicitud, para evitar casos en los que alguien simplemente agregue/preceda cadena a su url para eludir su autorización. Afortunadamente, el anteponer es más limitado, ya que debe llegar a la url correcta en urls.py, de ahí que use startswith aquí. Por supuesto, tendrías que ajustar la cadena de ruta para que coincida con tus urls sabrosas.

+0

This parece ser un hack más grande que lo que he propuesto; los permisos de nivel de tabla se deben manejar en 'Meta' para el 'Recurso' correspondiente. Además, como dices, la comparación basada en texto puede ser peligrosa (y de hecho, también causaría errores si, por ejemplo, tuvieras una relación con un comentario con 'completo = Verdadero '- en cuyo caso la URL podría no incluir/comentar /).En el sentido de lo que se transfiere del paquete, tiene razón en que el camino es realmente el único dato relevante que tiene. –

+0

no estoy seguro de cómo mi sugerencia es un hack más grande que cambiar el método de solicitud de POST a GET (sin mencionar las líneas de código agregadas). En la solución que propuse, la autorización se maneja en el lugar correcto (que es apply_limits), y la verificación de URL puede realizarse ya sea completa o incompleta = Verdadero. Estamos examinando la URL de solicitud y no los datos de paquete. Mi solución quizás pueda mejorarse un poco agregando algún tipo de indicador al objeto de solicitud dentro del recurso Comentarios obj_create y luego verificándolo en el método Notes apply_limits. – gingerlime

+0

Es un hack más grande porque rompe la modularidad. La autorización a nivel de tabla debe implementarse a nivel del Recurso, no integrada en el esquema de autenticación. En el hack anterior, cambias el método de solicitud, pero solo cuando sigues la relación con una 'Nota' de un' Comentario', por lo que mantienes el cambio de esquema localizado en un Recurso determinado. Antes de publicar el truco POST-GET, consideré usar la ruta (en realidad de una manera más robusta/consistente al cambiar la forma en que se construyeron las rutas) pero decidí no hacerlo porque rompió la modularidad. –

Cuestiones relacionadas