variable rails method generate and ruby-on-rails ruby session rails-activerecord

ruby-on-rails - method - routes in ruby on rails



Acceso a current_user desde dentro de un modelo en Ruby on Rails (10)

El controlador debe indicar la instancia del modelo

Trabajar con la base de datos es el trabajo del modelo. La gestión de las solicitudes web, incluido conocer al usuario para la solicitud actual, es el trabajo del controlador.

Por lo tanto, si una instancia modelo necesita conocer al usuario actual, un controlador debe contarla.

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

Esto supone que Item tiene un attr_accessor para current_user .

(Nota: primero publiqué esta respuesta en otra pregunta, pero acabo de notar que la pregunta es un duplicado de esta).

Necesito implementar un control de acceso detallado en la aplicación Ruby on Rails. Los permisos para usuarios individuales se guardan en una tabla de base de datos y pensé que sería mejor dejar que el recurso respectivo (es decir, la instancia de un modelo) decidiera si un determinado usuario puede leer o escribir en él. Tomar esta decisión en el controlador cada vez ciertamente no sería muy SECO.
El problema es que para hacer esto, el modelo necesita acceso al usuario actual, para llamar a algo como may_read ?( current_user , attribute_name ) . Sin embargo, los modelos en general no tienen acceso a los datos de la sesión.

Hay algunas sugerencias para guardar una referencia al usuario actual en el hilo actual, por ejemplo, en esta publicación de blog . Esto ciertamente resolvería el problema.

Los resultados de Google vecinos me aconsejaron que guardara una referencia al usuario actual en la clase de Usuario, lo que supongo fue pensado por alguien cuya aplicación no tiene que acomodar a muchos usuarios a la vez. ;)

Para resumir, me da la sensación de que mi deseo de acceder al usuario actual (es decir, los datos de la sesión) desde dentro de un modelo proviene de que lo estoy haciendo mal .

¿Puedes decirme cómo me equivoco?


Aunque muchos respondieron esta pregunta, solo quería agregar mis dos centavos rápidamente.

El uso del enfoque #current_user en el modelo de usuario debe implementarse con precaución debido a la seguridad del subproceso.

Está bien utilizar un método de clase / singleton en User si recuerda usar Thread.current como una forma de almacenar y recuperar sus valores. Pero no es tan fácil porque también tiene que restablecer Thread.current para que la próxima solicitud no herede los permisos que no debería.

El punto que estoy tratando de plantear es que, si almacena estado en variables de clase o singleton, recuerde que está lanzando seguridad de subprocesos por la ventana.


Bueno, supongo que es que current_user es finalmente una instancia de usuario, entonces, ¿por qué no agregas estos permisos al modelo de User o al modelo de datos? ¿Quieres que se apliquen o consulten los permisos?

Mi suposición es que necesitas reestructurar tu modelo de alguna manera y pasar al usuario actual como un param, como hacer:

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


Diría que tus instintos para mantener current_user fuera del modelo son correctos.

Al igual que Daniel, soy todo para controladores flacos y modelos gordos, pero también hay una clara división de responsabilidades. El propósito del controlador es administrar la solicitud y la sesión entrantes. El modelo debería ser capaz de responder la pregunta "¿Puede el usuario x hacer y para este objeto?", Pero no tiene sentido que haga referencia al current_user . ¿Qué pasa si estás en la consola? ¿Qué pasa si se está ejecutando un trabajo cron?

En muchos casos con la API de permisos correcta en el modelo, esto se puede manejar con una línea de before_filters que se aplican a varias acciones. Sin embargo, si las cosas se vuelven más complejas, quizás desee implementar una capa separada (posiblemente en lib/ ) que encapsule la lógica de autorización más compleja para evitar que su controlador se infle y evite que su modelo se acople demasiado a la solicitud web / ciclo de respuesta


Estoy usando el plugin Declarative Authorization, y hace algo similar a lo que mencionas con current_user Utiliza un before_filter para sacar a current_user y almacenarlo donde la capa del modelo pueda acceder. Se ve como esto:

# 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

Sin embargo, no estoy usando las características del modelo de Autorización declarativa. Estoy a favor del enfoque "Controlador flaco - Modelo de grasa", pero mi sensación es que la autorización (así como la autenticación) es algo que pertenece a la capa de controlador.



Me gustan los modelos flacos de controlador y gordos, y creo que la autenticación no debería romper este principio.

He estado programando con Rails desde hace un año y vengo de la comunidad de PHP. Para mí, es una solución trivial establecer al usuario actual como "global de solicitud larga". Esto se hace de forma predeterminada en algunos marcos, por ejemplo:

En Yii, puede acceder al usuario actual llamando a Yii :: $ app-> user-> identity. Ver http://www.yiiframework.com/doc-2.0/guide-rest-authentication.html

En Lavavel, también puede hacer lo mismo llamando a Auth :: user (). Ver http://laravel.com/docs/4.2/security

¿Por qué si puedo pasar el usuario actual del controlador?

Supongamos que estamos creando una aplicación de blog simple con soporte multiusuario. Estamos creando un sitio público (y los usuarios pueden leer y comentar en publicaciones de blog) y un sitio de administración (los usuarios están conectados y tienen acceso CRUD a su contenido en la base de datos).

Aquí están "los AR estándar":

class Post < ActiveRecord::Base has_many :comments belongs_to :author, class_name: ''User'', primary_key: author_id end class User < ActiveRecord::Base has_many: :posts end class Comment < ActiveRecord::Base belongs_to :post end

Ahora, en el sitio público:

class PostsController < ActionController::Base def index # Nothing special here, show latest posts on index page. @posts = Post.includes(:comments).latest(10) end end

Eso fue limpio y simple. En el sitio de administración, sin embargo, se necesita algo más. Esta es la implementación básica para todos los controladores de administración:

class Admin::BaseController < ActionController::Base before_action: :auth, :set_current_user after_action: :unset_current_user private def auth # The actual auth is missing for brievery @user = login_or_redirect end def set_current_user # User.current needs to use Thread.current! User.current = @user end def unset_current_user # User.current needs to use Thread.current! User.current = nil end end

Así que se agregó la funcionalidad de inicio de sesión y el usuario actual se guarda en un global. Ahora el modelo de usuario se ve así:

# Let''s extend the common User model to include current user method. class Admin::User < User def self.current=(user) Thread.current[:current_user] = user end def self.current Thread.current[:current_user] end end

User.current ahora es thread-safe

Extendamos otros modelos para aprovechar esto:

class Admin::Post < Post before_save: :assign_author def default_scope where(author: User.current) end def assign_author self.author = User.current end end

El modelo de publicación se amplió para que parezca que solo hay entradas del usuario actualmente conectadas. ¡Cuan genial es eso!

El controlador de la publicación Admin podría verse más o menos así:

class Admin::PostsController < Admin::BaseController def index # Shows all posts (for the current user, of course!) @posts = Post.all end def new # Finds the post by id (if it belongs to the current user, of course!) @post = Post.find_by_id(params[:id]) # Updates & saves the new post (for the current user, of course!) @post.attributes = params.require(:post).permit() if @post.save # ... else # ... end end end

Para el modelo de comentarios, la versión de administrador podría verse así:

class Admin::Comment < Comment validate: :check_posts_author private def check_posts_author unless post.author == User.current errors.add(:blog, ''Blog must be yours!'') end end end

En mi humilde opinión: esta es una manera poderosa y segura de asegurarse de que los usuarios puedan acceder / modificar solo sus datos, todo de una vez. Piense en cuánto necesita el desarrollador escribir el código de prueba si cada consulta necesita comenzar con "current_user.posts.whatever_method (...)"? Mucho.

Corrígeme si estoy equivocado pero creo:

Se trata de la separación de preocupaciones. Incluso cuando está claro que solo el controlador debe manejar las comprobaciones de autenticación, de ninguna manera el usuario actualmente conectado debe permanecer en la capa de controlador.

Lo único que debe recordar: ¡NO lo use en exceso! Recuerde que puede haber trabajadores de correo electrónico que no estén utilizando User.current o que puedan acceder a la aplicación desde una consola, etc.


Mi sensación es que el usuario actual es parte del "contexto" de su modelo MVC, piense en el usuario actual como la hora actual, la corriente de registro actual, el nivel de depuración actual, la transacción actual, etc. Podría pasar todos estos " modalidades "como argumentos en sus funciones. O lo hace disponible por variables en un contexto fuera del cuerpo de la función actual. El contexto local del subproceso es la mejor opción que las variables globales o de otro tipo debido a la seguridad de subprocesos más fácil. Como dijo Josh K, el peligro con los locales locales es que deben ser eliminados después de la tarea, algo que un marco de inyección de dependencia puede hacer por usted. MVC es una imagen un tanto simplificada de la realidad de la aplicación y no todo está cubierto por ella.


Siempre me sorprenden las respuestas de "simplemente no hagas eso" por parte de personas que no conocen la necesidad empresarial subyacente de la persona que pregunta. Sí, generalmente esto debería evitarse. Pero hay circunstancias en las que es apropiado y muy útil. Solo tuve uno yo mismo.

Aquí estaba mi solución:

def find_current_user (1..Kernel.caller.length).each do |n| RubyVM::DebugInspector.open do |i| current_user = eval "current_user rescue nil", i.frame_binding(n) return current_user unless current_user.nil? end end return nil end

Esto recorre la pila hacia atrás buscando un marco que responda a current_user . Si no se encuentra ninguno, devuelve nil. Se podría hacer más robusto al confirmar el tipo de devolución esperada, y posiblemente al confirmar que el propietario del marco es un tipo de controlador, pero generalmente funciona de maravilla.


Tengo esto en una aplicación mía. Simplemente busca la sesión actual de controladores [: usuario] y la establece en una variable de clase User.current_user. Este código funciona en producción y es bastante simple. Desearía poder decir que se me ocurrió, pero creo que lo tomé prestado de un genio de Internet en otro lado.

class ApplicationController < ActionController::Base before_filter do |c| User.current_user = User.find(c.session[:user]) unless c.session[:user].nil? end end class User < ActiveRecord::Base cattr_accessor :current_user end