9

Sto spostando il codice da un'applicazione incorporata in un framework PHP personalizzato non standard in Ruby on Rails (versione 3). Nella versione PHP tutti i controller sono davvero grossi, con modelli sottili, con cui sono sempre stato in disaccordo, quindi mi piace il modo in cui Rails fa la validazione a livello di modello, che è probabilmente il 90% di ciò che accade in questi grassi controller attualmente.Rails 3 Convalida ActiveRecord basata sulle autorizzazioni utente

Un problema che sto affrontando, e incerto su come risolvere comunque, è quello delle diverse regole di validazione basate su chi sta apportando la modifica al modello. Ad esempio, un amministratore o il creatore originale del record dovrebbe essere in grado di fare cose come contrassegnare un record come cancellato (soft delete) mentre tutti gli altri non dovrebbero.

class Something < ActiveRecord::Base 
    ... 
    validates :deleted, :owned_by_active_user => true 
    ... 
end 

class OwnedByActiveUserValidator < ActiveModel::EachValidator 
    validate_each(record, attr_name, attr_value) 
    # Bad idea to have the model know about things such as sessions? 
    unless active_user.admin? || active_user.own?(record) 
     record.errors.add :base, "You do not have permission to delete this record" 
    end 
    end 
end 

Dal momento che il modello stesso è (in teoria) all'oscuro dell'utente che sta facendo il cambiamento, qual è il "modo delle rotaie" per fare questo genere di cose? Devo impostare l'utente attivo come attributo virtuale sul record (non effettivamente salvato nel DB), o devo semplicemente eseguire questi controlli nel controller? Devo ammettere che è strano avere il modello che controlla i permessi sull'utente attivo e aggiunge complessità quando si tratta di testare il modello.

Uno dei motivi per cui sono desideroso di mantenere il più possibile nel modello, è perché voglio fornire sia un'API (accessibile tramite OAuth) sia un sito Web, senza duplicare troppo codice, come questi tipi di controlli delle autorizzazioni.

risposta

10

È proprio compito del controllore gestire l'autorizzazione o delegare l'autorizzazione a un livello di autorizzazione. I modelli non dovrebbero sapere, né doversi preoccupare, chi è attualmente loggato e quali sono i suoi permessi - questo è il lavoro del controller, o qualunque altro livello helper del controller a cui il controller lo assegna.

Si dovrebbe fare :deleted in- attr_accessible di assegnazione di massa tramite new, create o update_attributes. Il controller deve verificare separatamente le autorizzazioni dell'utente autenticato e chiamare lo deleted= separatamente, se l'utente autenticato è autorizzato.

Esistono diverse librerie e framework di autorizzazione per aiutare con l'autorizzazione o per funzionare come un livello di autorizzazione, come ad esempio cancan.

+0

Grazie, devo essere d'accordo con te. I miei modelli dovrebbero comportarsi come gli viene detto (a condizione che la logica aziendale li consenta). I miei controllori dovrebbero decidere chi li dice. Mi limiterò ad astrarre i dettagli cruenti nel miglior modo possibile. – d11wtq

+0

Dogma ... So che questo è il modo in cui è comunemente fatto, ma mi sembra che mettere direttamente il controllo di accesso nei modelli sia una soluzione elegante se si considera che i modelli siano il livello di API di dati/livello di regole aziendali. Evita anche di ripetere le stesse informazioni in più controller che toccano lo stesso modello.Anche l'uso di validatori per lo scopo sembra davvero comodo: può generare errori come "mi dispiace, non hai il permesso di modificare quel campo". Tuttavia, quel campo non avrebbe dovuto essere mostrato in primo luogo. Vorrei che esistesse una soluzione che permettesse l'introspezione per i costruttori di moduli. – odigity

+0

È possibile creare classi del modello di modulo (non supportate dal database) che implementano la convalida e i metodi necessari dal generatore di moduli. – yfeldblum

6

Io risolverei questo con un before_filter nel mio controller, invece che con le convalide nel mio modello.

class SomethingController < ApplicationController 
    before_filter :require_delete_permission, :only => [:destroy] 

    def destroy 
    # delete the record 
    end 

    private 

    def require_delete_permission 
    unless current_user.is_admin || record.owner == current_user 
     flash[:error] = 'You do not have delete permissions' 
     redirect_to somewhere 
    end 
    end 
end 
+0

Anche qui non ho il downvote. Il tuo suggerimento è efficacemente quello che dice la risposta accettata. – d11wtq

+0

Tranne che sta aggiungendo un messaggio al flash ... non invalidando il record salvato – courtsimas

3

Ho riscontrato lo stesso problema in Rails 2.3 e finalmente ho trovato questa soluzione. Nel tuo modello definisci alcuni attributi, a seconda di quale attivazione/disattivazione della convalida. Di quello che il vostro controllo si imposta questo attributo a seconda della data disposizione di controllo (come ad esempio i privilegi di utente nel tuo caso) come segue:

Class Model < ActiveRecord::Base 
    attr_accessor :perform_validation_of_field1 #This is an attribute which controller will use to turn on/off some validation logic depending on the current user 

    validates_presence_of :field1, :if => :perform_validation_of_field1 
    #This validation (or any similar one) will occur only if controller sets model.perform_validation_of_field1 to true. 
end 

Class MyController < ActionController::Base 
    def update 
    @item = Model.find(params[:id]) 
    @item.update_attribute(params[:item]) 

    #The controller decides whether to turn on optional validations depending on current user privileges (without the knowledge of internal implementation of this validation logic) 
    @item.perform_validation_of_field1 = true unless active_user.admin? 

    if @item.save 
     flash[:success] = 'The record has been saved' 
     redirect_to ... 
    else 
     flash.now[:error] = 'The record has not passed validation checks' 
     render :action => :edit 
    end 
    end 

penso che in Rails 3 si può fare in modo simile.

+0

Devo dire, questo suona un po 'casuale sulle prime impressioni, ma se funziona per te allora fantastico;) – d11wtq

+0

Cosa c'è di sbagliato in questo approccio. Non c'è nulla di casuale (scusa non so come rendere l'aggettivo in inglese da questa parola :). Sì, ho digitato un codice ma non l'ho verificato. Speravo che avresti avuto l'idea. L'idea è di usare: if o: a meno che i parametri sulle macro di convalida siano definiti in un modello. Quindi alcune parti della logica di convalida possono essere attivate/disattivate. Il livello controller sa come abilitare/disabilitare una particolare logica di convalida per un modello, ma non sa nulla sull'implementazione della logica di convalida. – cryo28

+0

Ovviamente è possibile combinare le opzioni di convalida in blocchi più grandi (non è necessario definire un attributo per ogni convalida). Quindi, per favore date un'occhiata a http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html e concentratevi su: se e: a meno che i parametri delle macro di convalida in particolare prima di decidere che questo codice funzioni solo per me come una coincidenza :) – cryo28

Problemi correlati