2011-12-26 3 views
9

Sto costruendo una semplice API usando django-tastypie. L'idea è che ho due risorse:Problemi con ForeignKey usando POST in Django-Tastypie

  • Un Nota risorsa che rappresenta una nota lasciato da un utente. Solo l'utente che ha creato una nota può modificarlo.
  • A Commento risorsa. I commenti possono essere lasciati su qualsiasi nota da qualsiasi utente.

TL; DR: Non sono in grado di limitare Nota editing per il creatore di una nota, pur consentendo a qualsiasi utente di commentare una nota.

Sto usando la seguente configurazione per l'autenticazione:

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 

Insomma, un utente è autorizzato solo per modificare gli oggetti di cui sono uguale alla proprietà created_by (possono modificare solo gli oggetti che hanno creato) .

Questo è legato come segue:

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

ecco, quando si crea un oggetto, allego automaticamente l'utente corrente all'attributo created_by e collegarlo al l'autorizzazione appropriata.

Una risorsa Comment A è una semplice e ha una risorsa ForeignKey in una risorsa Note.

Il problema è questo: se l'utente A crea una nota e l'utente B tenta di commentare che la nota, tastypie invia (o simula) una richiesta POST per modificare che la nota. Tale tentativo viene rifiutato poiché l'utente B non ha creato la nota, pertanto la creazione del commento non riesce.

La domanda è questa: c'è un modo per entrambi:

  1. Impedire tastypie dall'utilizzo di un POST per creare il reverse-relazione alla risorsa Nota o
  2. Modifica del regime di autorizzazione quindi Notes può essere modificato solo dal loro creatore, ma i commenti possono essere generati in generale?

Grazie in anticipo per eventuali approfondimenti.

Modifica: Ho una grossa hack grassa che può realizzare questo. Sono abbastanza sicuro che sia sicuro, ma non sono positivo; Cercherò di costruire alcune query per essere sicuro. Invece di usare fields.ForeignKey in Comment di relazionarsi con Note, crea un campo personalizzato:

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 

Ogni volta che cerchiamo di costruire questa risorsa correlata, vi segnalo la richiesta in via GET (in quanto ci aspettiamo da abbinare a una query SELECT anziché UPDATE che corrisponde a PUT o POST). Questo è davvero brutto e potenzialmente pericoloso se usato in modo errato, e spero in una soluzione migliore.

Modifica 2: Dalla lettura della fonte tastypie, per quanto posso dire non c'è modo di filtrare l'autorizzazione dalla query che verrà effettivamente inviata.

+0

Un paio di domande - Stai usando contrib.comments? Stai usando l'autenticazione e l'autorizzazione? Ho quello che sembra essere un setup molto simile (senza sembrare CommentResource) che funziona bene quando si pubblica un nuovo commento su un altro oggetto utente. – JamesO

+0

@JamesO No, i nostri commenti sono in qualche modo più ricchi di quanto offerto da contrib.comments (e vi sono altri dati associati a un post che presenta lo stesso problema). Al momento utilizziamo solo l'autenticazione integrata() (ad esempio, tutti sono autenticati). –

+0

Hai postato questo come un problema su django-tastypie: https://github.com/toastdriven/django-tastypie/issues? Se tenta sinceramente di aggiornare un record genitore ogni volta che crei qualcosa relativo ad esso, è più vicino a un bug che a una funzionalità. –

risposta

4

Come per la discussione sulla https://github.com/toastdriven/django-tastypie/issues/480#issuecomment-5561036:

Il metodo che determina se un Resource può essere aggiornato è can_update. Pertanto, per fare questo lavoro nel modo "corretto", è necessario creare una sottoclasse di 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' 

poi lasciare CommentResource collegamento alle note nel modo standard: note = fields.ForeignKey(SafeNoteResource, 'note').

1

una soluzione semplice dovrebbe essere quello di controllare all'interno della apply_limits se la richiesta è per un Nota risorsa o un commento risorsa. per esempio. qualcosa come

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 

Poi si sta limitando l'accesso solo a Notes allo stesso utente, quando l'utente accede a una Nota risorsa direttamente, e non attraverso altre risorse correlate, come ad esempio Commenti.

aggiornamento: o un'opzione di un po 'più sicuro sarebbe quello di controllare che la richiesta non non inizio con 'api/v1/commento' - così si sta solo whitelist il commento di accesso, piuttosto che qualcosa di diverso nota . Lo stesso principio vale comunque. Fai attenzione a questo confronto basato sul testo del percorso della richiesta, per evitare casi in cui qualcuno aggiunge semplicemente/fa precedere la stringa all'URL per ignorare la tua autorizzazione. Speriamo che la preparazione anticipata sia più limitata, dal momento che è necessario inserire l'url corretto in urls.py, quindi perché ho usato startswith qui. Ovviamente dovresti regolare la stringa del percorso in modo che corrisponda ai tuoi URL di tasteypie.

+0

sembra essere un attacco più grande di quello che ho proposto; i permessi a livello di tabella devono essere gestiti in 'Meta' per la corrispondente' Risorsa'. Inoltre, come dici tu, il confronto basato sul testo può essere pericoloso (e infatti, potrebbe anche causare bug se, ad esempio, dovessi avere una relazione con un commento con 'full = True' - nel qual caso l'URL potrebbe non includere/commento /).Nel senso di ciò che viene restituito dal pacchetto, hai ragione nel fatto che il percorso è davvero l'unico dato rilevante che hai. –

+0

non sono sicuro di come il mio suggerimento sia una modifica più grande rispetto al cambio del metodo di richiesta da POST a GET (per non parlare delle righe di codice aggiunte). Sulla soluzione che ho proposto, l'autorizzazione è gestita nel posto giusto (che è apply_limits), e il controllo dell'URL può essere eseguito indipendentemente dal fatto che sia pieno = vero. Stiamo esaminando l'URL della richiesta e non tutti i dati del pacchetto. La mia soluzione può forse essere leggermente migliorata aggiungendo un qualche tipo di flag all'oggetto request all'interno della risorsa Commenti obj_create e quindi controllandola nel metodo apply_limits di Notes. – gingerlime

+0

È un grosso problema perché rompe la modularità. L'autorizzazione a livello di tabella deve essere implementata a livello della risorsa, non elaborata nello schema di autenticazione. Nel suddetto hack, si modifica il metodo di richiesta, ma solo quando si segue la relazione con un 'Note' da un' Comment', in modo da mantenere il cambiamento dello schema localizzato su una determinata Risorsa. Prima di postare l'hack POST-GET, ho preso in considerazione l'utilizzo del percorso (in realtà in un modo più solido/coerente modificando il modo in cui i percorsi sono stati costruiti) ma ho deciso contro di esso perché ha rotto la modularità. –