79 votes

Rails : Accès à current_user à partir d'un modèle en Ruby on Rails

Je dois mettre en œuvre un contrôle d'accès à grain fin dans une application Ruby on Rails. Les permissions des utilisateurs individuels sont sauvegardées dans une table de base de données et j'ai pensé qu'il serait préférable de laisser la ressource respective (c'est-à-dire l'instance d'un modèle) décider si un certain utilisateur est autorisé à lire ou écrire dans cette table. Prendre cette décision dans le contrôleur à chaque fois ne serait certainement pas très DRY.
Le problème est que pour faire cela, le modèle doit avoir accès à l'utilisateur actuel, pour appeler quelque chose comme may_read ?( current_user , attribute_name ). En général, les modèles n'ont cependant pas accès aux données de session.

Il existe de nombreuses suggestions pour sauvegarder une référence à l'utilisateur actuel dans le fil de discussion actuel, par exemple dans cet article de blog . Cela résoudrait certainement le problème.

Les résultats voisins de Google m'ont cependant conseillé de sauvegarder une référence à l'utilisateur actuel dans la classe User, ce qui, je suppose, a été pensé par quelqu'un dont l'application n'a pas à accueillir beaucoup d'utilisateurs à la fois. ;)

Pour faire court, j'ai l'impression que mon souhait d'accéder à l'utilisateur actuel (c'est-à-dire aux données de session) à partir d'un modèle vient de moi le faire mal .

Pouvez-vous me dire en quoi j'ai tort ?

0 votes

Ironiquement, sept ans après que cette question ait été posée, "mal le faire" est 404ing. :)

1 votes

"J'ai pensé qu'il serait préférable de laisser la ressource respective (c'est-à-dire l'instance d'un modèle) décider si un certain utilisateur est autorisé à y lire ou à y écrire." Le modèle n'est peut-être pas le meilleur endroit pour cette logique. Des joyaux comme Pundit et Authority établissent un modèle consistant à avoir une classe "policy" ou "authorizer" distincte désignée pour chaque modèle (et les modèles peuvent les partager, si vous le souhaitez). Ces classes fournissent des moyens de travailler facilement avec l'utilisateur actuel.

48voto

gtd Points 7062

Je dirais que votre instinct vous pousse à garder current_user du modèle sont correctes.

Comme Daniel, je suis pour les contrôleurs maigres et les mannequins obèses, mais il y a aussi un partage clair des responsabilités. Le but du contrôleur est de gérer la demande entrante et la session. Le modèle doit être capable de répondre à la question "L'utilisateur x peut-il faire y à cet objet ?", mais il est absurde qu'il fasse référence à l'objet current_user . Et si vous êtes dans la console ? Et si c'est une tâche cron qui s'exécute ?

Dans de nombreux cas, avec les bonnes API de permissions dans le modèle, cela peut être géré avec une seule ligne before_filters qui s'appliquent à plusieurs actions. Cependant, si les choses deviennent plus complexes, vous voudrez peut-être implémenter une couche séparée (éventuellement dans le module lib/ ) qui encapsule la logique d'autorisation la plus complexe afin d'éviter que votre contrôleur ne devienne trop volumineux et que votre modèle ne soit trop étroitement lié au cycle demande/réponse du Web.

0 votes

Merci, vos préoccupations sont convaincantes. Je vais devoir approfondir mes recherches sur le fonctionnement de certains plugins de contrôle d'accès pour Rails.

4 votes

Les modèles d'autorisation cassent MVC par conception, toute façon de contourner cela finit généralement par être pire que de simplement casser MVC dans ce domaine. Si vous devez utiliser User.current_user dans le registre du fil actuel, alors faites-le, mais faites de votre mieux pour ne pas l'utiliser de manière excessive.

1 votes

Je pense simplement que l'idée selon laquelle les modèles ne devraient pas savoir quel utilisateur y accède n'est qu'un effort de perfection mal conçu. PAS UNE SEULE PERSONNE ICI NE VOUS CONSEILLE DE PASSER LE FUSEAU HORAIRE au modèle dans les paramètres de la méthode. Ce serait stupide. Mais c'est la même chose... Vous avez un objet heure, et la plupart des applications ont le contrôleur d'application qui définit le fuseau horaire, souvent en fonction de la personne connectée, et ensuite les modèles connaissent le fuseau horaire.

36voto

Josh K Points 294

Bien que de nombreuses personnes aient déjà répondu à cette question, je voulais juste ajouter rapidement mon grain de sel.

L'utilisation de l'approche #current_user sur le modèle utilisateur doit être mise en œuvre avec prudence en raison de la sécurité des fils.

Il est possible d'utiliser une méthode de classe/singleton sur User si vous n'oubliez pas d'utiliser Thread.current pour stocker et récupérer vos valeurs. Mais ce n'est pas aussi simple que cela car vous devez également réinitialiser Thread.current afin que la prochaine requête n'hérite pas des permissions qu'elle ne devrait pas avoir.

Ce que je veux dire, c'est que si vous stockez l'état dans des variables de classe ou singleton, n'oubliez pas que vous jetez la sécurité des fils par la fenêtre.

15 votes

+10 si je pouvais pour cette remarque. Sauvegarder l'état de la requête dans les méthodes/variables d'une classe singleton est une très mauvaise idée.

36voto

Nathan Long Points 30303

Le contrôleur doit indiquer à l'instance du modèle

Travailler avec la base de données est le travail du modèle. La gestion des requêtes web, y compris la connaissance de l'utilisateur pour la requête en cours, est le travail du contrôleur.

Par conséquent, si une instance de modèle a besoin de connaître l'utilisateur actuel, un contrôleur doit le lui indiquer.

def create
  @item = Item.new
  @item.current_user = current_user # or whatever your controller method is
  ...
end

Cela suppose que Item a un attr_accessor pour current_user .

(Note - J'ai d'abord posté cette réponse sur une autre question, mais je viens de remarquer que cette question est un doublon de celle-ci).

3 votes

Vous avez un argument de principe, mais d'un point de vue pratique, cela peut entraîner beaucoup de code supplémentaire définissant l'utilisateur actuel à de nombreux endroits. A mon avis, parfois le thread-local User.current_user permet de raccourcir le reste du code et de le rendre plus facile à comprendre, même s'il ne respecte pas la norme MVC.

0 votes

Étonnant ! J'ai plusieurs objets imbriqués, donc ce que j'ai ajouté les ids dans les vues (cachées) et l'attr_accessor dans le modèle.

1 votes

@antinome Le vrai problème, dans le cas de l'OP, est peut-être de faire en sorte que le modèle gère les contrôles d'autorisation. Une classe "policy" ou "authorizer" séparée peut recevoir le modèle et l'utilisateur actuel et autoriser à partir de là. Cette approche signifie que le modèle ne vérifie pas les permissions dans des contextes où vous ne voulez pas les vérifier, comme les tâches Rake.

9voto

khelll Points 12222

Eh bien, je pense que current_user est finalement une instance d'utilisateur, alors, pourquoi ne pas ajouter ces permissions à l'instance d'utilisateur ? User ou au modèle de données auquel vous voulez que les permissions soient appliquées ou interrogées ?

Je pense que vous devez restructurer votre modèle d'une manière ou d'une autre et passer l'utilisateur actuel comme un paramètre, comme faire :

class Node < ActiveRecord
  belongs_to :user

  def authorized?(user)
    user && ( user.admin? or self.user_id == user.id )
  end
end

# inside controllers or helpers
node.authorized? current_user

4voto

Daniel Kristensen Points 1106

J'utilise le plugin Declarative Authorization, et il fait quelque chose de similaire à ce que vous mentionnez avec current_user Il utilise un before_filter pour tirer current_user et le stocker là où la couche modèle peut l'atteindre. Ça ressemble à ça :

# set_current_user sets the global current user for this request.  This
# is used by model security that does not have access to the
# controller#current_user method.  It is called as a before_filter.
def set_current_user
  Authorization.current_user = current_user
end

Je n'utilise pas les fonctions de modèle de l'autorisation déclarative. Je suis partisan de l'approche "Skinny Controller - Fat Model", mais j'ai le sentiment que l'autorisation (ainsi que l'authentification) est quelque chose qui appartient à la couche contrôleur.

0 votes

L'autorisation (ainsi que l'authentification) appartient à la fois à la couche contrôleur (où vous autorisez l'accès à une action) et au modèle (où vous autorisez l'accès à une ressource).

2 votes

N'utilisez pas de variables de classe ! Utilisez Thread.current['current_user'] ou quelque chose de similaire à la place. C'est threadsafe ! N'oubliez pas de réinitialiser le current_user à la fin de la requête !

1 votes

Cela peut ressembler à une variable de classe, mais il s'agit en fait d'une méthode qui définit le Thread.current. github.com/stffn/declarative_authorization/blob/master/lib/

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X