9

Ich verschiebe Code von einer Anwendung, die in einem nicht standardmäßigen benutzerdefinierten PHP-Framework in Ruby on Rails (Version 3) erstellt wurde. In der PHP-Version sind alle Controller wirklich fett, mit dünnen Modellen, mit denen ich immer nicht einverstanden war, also genieße ich die Art und Weise, wie Rails die Validierung auf Modellebene durchführt, was wahrscheinlich 90% von dem ist, was in diesen fetten Controllern passiert zur Zeit.Rails 3 ActiveRecord Validierung basierend auf Benutzerberechtigungen

Ein Problem, mit dem ich konfrontiert bin, und unsicher, wie man es lösen kann, sind unterschiedliche Validierungsregeln, die darauf basieren, wer die Änderung am Modell vornimmt. Zum Beispiel sollte ein Administrator oder der ursprüngliche Ersteller des Datensatzes in der Lage sein, Dinge zu tun, wie einen Datensatz als gelöscht markieren (soft delete), während dies für alle anderen nicht gilt.

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 

Da das Modell selbst (in der Theorie) weiß nichts von dem Benutzer, der die Änderung macht, was ist die „Schienen Weg“ diese Art der Sache zu tun? Sollte ich den aktiven Benutzer als virtuelles Attribut für den Datensatz festlegen (nicht tatsächlich in DB gespeichert), oder sollte ich diese Prüfungen nur im Controller durchführen? Ich muss zugeben, dass es merkwürdig ist, wenn das Modell Berechtigungen für den aktiven Benutzer überprüft, und es erhöht die Komplexität, wenn es darum geht, das Modell zu testen.

Ein Grund, warum ich so viel wie möglich im Modell behalten möchte, ist, weil ich sowohl eine API (Zugriff über OAuth) als auch eine Website bereitstellen möchte, ohne zu viel Code wie diese zu duplizieren Arten von Berechtigungen überprüft.

Antwort

10

Es ist die Aufgabe des Controllers, die Autorisierung zu übernehmen oder die Autorisierung an eine Autorisierungsschicht zu delegieren. Die Modelle sollten nicht wissen, müssen sich nicht darum kümmern, wer gerade eingeloggt ist und welche Berechtigungen sie haben - das ist die Aufgabe des Controllers oder der Auth-Helfer-Ebene, an die der Controller das delegiert.

sollten Sie machen :deleted in- attr_accessible zu Massenzuordnung über new, create oder update_attributes. Der Controller sollte die Berechtigungen des authentifizierten Benutzers getrennt prüfen und separat deleted= aufrufen, wenn der authentifizierte Benutzer autorisiert ist.

Es gibt mehrere Autorisierungsbibliotheken und Frameworks, die bei der Autorisierung helfen oder als Autorisierungsschicht fungieren, z. B. cancan.

+0

Danke, ich muss dir zustimmen. Meine Modelle sollten tun, was ihnen gesagt wird (sofern die Geschäftslogik dies zulässt). Meine Controller sollten entscheiden, wer es ihnen sagt. Ich werde nur sicherstellen, dass ich die blutigen Details so gut wie möglich abstrahiere. – d11wtq

+0

Dogma ... Ich weiß, dass es so üblich ist, aber es scheint mir, dass die direkte Integration von Access Control in die Modelle eine elegante Lösung ist, wenn man die Modelle als Daten-API/Business-Rule-Layer betrachtet. Es vermeidet auch, dieselben Informationen in mehreren Controllern zu wiederholen, die dasselbe Modell berühren.Und die Verwendung von Validatoren für den Zweck scheint auch sehr praktisch - kann Fehler wie "sorry, Sie haben keine Berechtigung, dieses Feld zu bearbeiten" generieren. Dieses Feld sollte jedoch nicht an erster Stelle stehen. Ich wünschte, es gäbe eine Lösung, die es erlaubt, Form Builder zu introspizieren. – odigity

+0

Sie können Formularmodell-Klassen (nicht datenbankgestützt) erstellen, die die Validierung implementieren, und die vom Formular-Generator benötigten Methoden. – yfeldblum

6

Ich würde dies mit einem before_filter in meinem Controller lösen, anstatt mit Validierungen in meinem Modell.

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

Ich bekomme den Downvote auch hier nicht. Ihr Vorschlag ist effektiv, was die angenommene Antwort sagt. – d11wtq

+0

Außer er fügt dem Flash eine Nachricht hinzu ... den Datensatz nicht ungültig speichern – courtsimas

3

Ich bin auf das gleiche Problem in Rails 2.3 gestoßen und schließlich mit dieser Lösung gekommen. In Ihrem Modell definieren Sie ein Attribut, abhängig davon, welche Validierung Sie aktivieren/deaktivieren. Als Sie Ihre Steuerung eingestellt Sie dieses Attribut je nach dem Zeitpunkt zur Verfügung Controller (wie Benutzerrechte in Ihrem Fall) wie folgt:

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 

Ich denke, dass in Rails 3 es in ähnlicher Weise durchgeführt werden kann.

+0

Ich muss sagen, das klingt ein bisschen willkürlich beim ersten Eindruck, aber wenn es für dich funktioniert dann toll;) – d11wtq

+0

Was ist los mit diesem Ansatz? Es gibt nichts über Zufall (sorry, weiß nicht, wie man Adjektiv in Englisch von diesem Wort macht :). Ja, ich habe einen Code eingegeben, aber nicht überprüft. Ich habe gehofft, dass du auf die Idee kommst. Die Idee ist zu verwenden: Wenn oder: außer Parameter auf Validierungsmakros in einem Modell definiert. So können einige Teile der Validierungslogik ein- und ausgeschaltet werden. Die Controller-Schicht weiß, wie eine bestimmte Validierungslogik für ein Modell aktiviert/deaktiviert wird, weiß jedoch nichts über die Implementierung der Validierungslogik. – cryo28

+0

Natürlich können Sie die Validierungsoptionen zu größeren Blöcken kombinieren (es ist nicht notwendig, für jede Validierung ein Attribut zu definieren). Werfen Sie einen Blick auf http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html und konzentrieren Sie sich auf: if und: sofern Parameter von Validierungsmakros im Besonderen, bevor Sie entscheiden, dass dieser Code nur für mich funktioniert als Zufall :) – cryo28