Seguridad del uso Thread.current [] en rieles

81

Sigo recibiendo opiniones contradictorias sobre la práctica de almacenar información en el Thread.currenthash (por ejemplo, el actual_usuario, el subdominio actual, etc.). La técnica se ha propuesto como una forma de simplificar el procesamiento posterior dentro de la capa del modelo (alcance de consultas, auditoría, etc.).

Muchos consideran que la práctica es inaceptable porque rompe el patrón MVC. Otros expresan preocupaciones sobre la confiabilidad / seguridad del enfoque, y mi pregunta de dos partes se enfoca en el último aspecto.

  1. ¿Se Thread.currentgarantiza que el hash esté disponible y sea privado para una sola respuesta, durante todo su ciclo?

  2. Entiendo que un hilo, al final de una respuesta, bien puede ser entregado a otras solicitudes entrantes, filtrando así cualquier información almacenada en Thread.current. ¿Sería suficiente borrar dicha información antes del final de la respuesta (por ejemplo, ejecutando Thread.current[:user] = nildesde un controlador after_filter) para prevenir tal violación de seguridad?

¡Gracias! Giuseppe

Giuseppe
fuente
9
Consulte la sección "Ensuciarse con Thread.current" aquí. m.onkey.org/thread-safety-for-your-rails . Eso fue escrito por uno de los autores de Jruby. El código ROR # 1 en sí mismo usa Thread.current para I18N y time_zone. ¿Eso habla de su garantía? # 2. Si el # 1 es cierto, entonces es suficiente.
so_mv
Gracias, agregué la referencia.
Giuseppe
3
En la publicación a la que se vincula, los ejemplos tratan exclusivamente con la capa del controlador, y la solución propuesta es obviamente apropiada. Sin embargo, sospecho que lo que a la mayoría de la gente le interesaría es una forma limpia de dar acceso a los modelos a 1 o 2 piezas de información que normalmente se les excluyen, sin agregar parámetros adicionales a cada llamada a los modelos. En este sentido, todas esas grandes y aterradoras señales de advertencia de "manténgase alejado de Thread.current" sin razones específicas por las que hasta ahora me han dejado inseguro. Gracias
Giuseppe

Respuestas:

48

No hay una razón específica para mantenerse alejado de las variables locales del hilo, los problemas principales son:

  • es más difícil probarlos, ya que tendrá que recordar configurar las variables locales del hilo cuando esté probando el código que lo usa
  • las clases que usan subprocesos locales necesitarán saber que estos objetos no están disponibles para ellos, pero dentro de una variable subproceso local y este tipo de indirección generalmente rompe la ley de demeter
  • no limpiar los subprocesos locales podría ser un problema si su marco reutiliza los subprocesos (la variable subproceso local ya estaría iniciada y el código que se basa en || = las llamadas para inicializar variables podrían fallar

Entonces, aunque no está completamente fuera de lugar su uso, el mejor enfoque es no usarlos, pero de vez en cuando te topas con una pared donde un hilo local será la solución más simple posible sin cambiar mucho código y tendrá que comprometerse, tener un modelo orientado a objetos menos que perfecto con el hilo local o cambiar bastante código para hacer lo mismo.

Por lo tanto, es principalmente una cuestión de pensar cuál será la mejor solución para su caso y si realmente va por el camino local del hilo, seguramente le aconsejaría que lo haga con bloques que recuerden limpiar después están hechos, como los siguientes:

around_filter :do_with_current_user

def do_with_current_user
    Thread.current[:current_user] = self.current_user
    begin
        yield
    ensure
        Thread.current[:current_user] = nil
    end      
end

Esto asegura que la variable local del subproceso se limpie antes de usarse si este subproceso se recicla.

Maurício Linhares
fuente
1
El uso de un filtro de alrededor con un bloque de aseguramiento afectará al manejo / registro de excepciones si no tiene mucho cuidado. Consulte RequestStore en la respuesta de Dejan para obtener una solución más limpia basada en middleware.
Irongaze.com
Puede haber sido mi enfoque del problema en el sentido de que estaba rescatando la excepción primero (en lugar de simplemente asegurarme como se indicó anteriormente). La pila de llamadas de la excepción se restablecía a la ubicación donde volví a plantear, estropeando el registro de errores de Rails y en otros lugares. Terminé volcando manualmente la excepción capturada en el archivo de registro para preservarlo, pero era feo. El uso de un enfoque basado en middleware es mucho más limpio en mi humilde opinión.
Irongaze.com
1
Es por eso que el código anterior solo tiene un ensurebloque y no intenta capturar la excepción.
Maurício Linhares
Simple cuando lo ves :)
244an
4

La respuesta aceptada es técnicamente precisa, pero como se señala en la respuesta con suavidad, y en http://m.onkey.org/thread-safety-for-your-rails no tan suavemente:

No use el almacenamiento local de subprocesos, Thread.currentsi no es absolutamente necesario

La gema request_storees otra solución (mejor), pero solo lea el archivo Léame allí para obtener más razones para mantenerse alejado del almacenamiento local de subprocesos.

Casi siempre hay una forma mejor.

Tom Harrison
fuente
3
Estoy usando la aplicación Unicorn with Rails Multi-tenant. ¿Puede sugerir un ejemplo de "mejor manera"? El enlace que publicó arroja un error de aplicación. Gracias.
lacostenycoder