¿Dónde deben llevarse a cabo las verificaciones de permisos de usuario en MVC y por quién?

26

¿Deben realizarse verificaciones de permisos de usuario en el modelo o el controlador? ¿Y quién debe encargarse de las verificaciones de permisos, el objeto Usuario o algún ayudante de UserManagement?

¿Dónde debería pasar?

Comprobación en el controlador:

class MyController {
  void performSomeAction() {
    if (user.hasRightPermissions()) {
      model.someAction();
    }
  }
  ...

Tener las comprobaciones en el controlador ayuda a que los modelos sean acciones simples, para que podamos mantener toda la lógica en los controladores.

Comprobación en el modelo:

class MyModel {
  void someAction() {
    if (user.hasRightPermissions()) {
      ...
    }
  }
  ...

Al poner los controles en el Modelo, complicamos el Modelo, pero también nos aseguramos de no permitir accidentalmente que los usuarios hagan cosas que no deben hacer en el Controlador.

¿Y por quien?

Una vez que nos instalamos en el lugar, ¿quién debería hacer los controles? ¿El usuario?

Class User {
  bool hasPermissions(int permissionMask) {
    ...
  }
  ...

Pero en realidad no es responsabilidad del usuario saber a qué puede acceder, así que ¿quizás alguna clase de ayuda?

Class UserManagement {
  bool hasPermissions(User user, int permissionMask) {
    ...
  }
  ...

Sé que es común hacer una sola pregunta, bueno, una pregunta, pero creo que se pueden responder muy bien juntos.

kba
fuente

Respuestas:

20

Como de costumbre, "depende"

  • las verificaciones de permisos funcionarán funcionalmente en cualquier lugar donde sea conveniente colocarlas,
  • pero si está haciendo una pregunta técnica, la respuesta puede ser 'poner las verificaciones en el objeto que posee los datos necesarios para realizar la verificación' (que probablemente sea el controlador).
  • pero si hace una pregunta filosófica, le sugiero una respuesta alternativa: no muestre a los usuarios acciones que no se les permita realizar .

Entonces, en el último caso, es posible que tenga la verificación de permisos en el controlador implementada como una propiedad booleana, y vincule esa propiedad a la propiedad Visible del botón o panel en la interfaz de usuario que controla la acción

como usuario, es frustrante ver botones para acciones que no puedo realizar; se siente como si me dejaran fuera de la diversión;)

Steven A. Lowe
fuente
Nuestra aplicación implementa el tercer escenario con la excepción de que no ocultamos los controles, los deshabilitamos. Desafortunadamente, todo se hace en código subyacente de Winforms, por lo que no es realmente relevante para la pregunta OP.
Dave Nay
11
"Es frustrante ver botones para acciones que no puedo realizar" -> Intenta votar tu propia publicación :)
Rowan Freeman
55
No es suficiente simplemente ocultar botones para acciones que el usuario no puede realizar, el servidor debe verificar cada solicitud de permisos. El tercer elemento de viñeta no es "una respuesta alternativa", es algo que hacer además de verificar los permisos del lado del servidor.
Flimm
@Flimm estuvo de acuerdo, si las solicitudes son manejadas por un servidor; la pregunta específica era sobre la clase Controlador
Steven A. Lowe
7

La seguridad es una preocupación transversal, por lo tanto, debe implementarse en varias capas. Lo que sigue es un ejemplo para MVC, pero el concepto se aplica a otras arquitecturas y / o patrones, solo tiene que identificar los puntos de aplicación.

¿Dónde debería pasar?

Las vistas pueden contener elementos de la interfaz de usuario (widgets, botones, menús, etc.) que deben mostrarse o no para algunos usuarios, según sus permisos. Esto podría ser una responsabilidad del motor de visualización , ya que no desea que cada vista maneje esto por sí solo. Dependiendo del tipo de elementos que esté autorizando, mueva esta responsabilidad a otro lugar. Por ejemplo, piense en un menú en el que algunos elementos deben mostrarse y otros no. Los elementos se pueden implementar como una lista en algún lugar y filtrar esa lista según los permisos y luego reenviarla a la vista.

Los controladores responden a las solicitudes, por lo que si un usuario no tiene permiso para ejecutar una acción, se debe verificar antes de invocar la acción, transfiriendo la responsabilidad al invocador de la acción en lugar de mantenerla en el controlador. Esto tiene la ventaja de mantener limpio su controlador y, si algo cambia en los permisos, no tiene que examinar los controladores para aplicar esos cambios.

Los recursos se muestran según los permisos. Esto normalmente se realiza a nivel de base de datos , ya que no desea extraer todo de la base de datos y luego aplicar permisos.

Como puede ver, dependiendo de lo que quiera autorizar, hay diferentes lugares donde esto debería hacerse. El objetivo es ser lo más discreto posible, de modo que, cuando cambie su política de seguridad, pueda aplicarla fácilmente, preferiblemente sin alterar el código de su aplicación. Esto podría no ser válido para aplicaciones pequeñas, donde el conjunto de permisos es bastante pequeño y no cambia muy a menudo. Sin embargo, en aplicaciones empresariales, la historia es bastante diferente.

¿Quién debería hacerlo?

Claramente no es el modelo. Cada capa debe tener un punto de cumplimiento que maneje la autorización. El texto en cursiva anterior resalta el posible punto de aplicación para cada nivel.

Echa un vistazo a XACML . No tiene que implementarlo tal como está, pero le dará algunas instrucciones que puede seguir.

devnull
fuente
Esta es la mejor respuesta. Por alguna razón, la primera y otras tratan con las diferencias entre el controlador y la vista, o la vista y el modelo, que no es lo que pregunta OP. ¡Gracias!
redFur
1

Yo uso el siguiente esquema. Vale la pena decir que la mayoría de las comprobaciones de permisos de usuario se pueden dividir en dos casos generales:

  • El acceso del usuario a la acción del controlador en función del rol del usuario sin verificar los parámetros se llama a la acción con,
  • acceso del usuario al modelo basado en cualquier lógica o relación entre un usuario en particular y un modelo en particular.

El acceso a la acción del controlador sin verificar los atributos generalmente se implementa en marcos MVC. Esto es simple en absoluto: usted define reglas, sus usuarios tienen un rol. Simplemente verifica que el usuario tenga permiso para buscar su papel en las reglas.

El acceso del usuario a un modelo particular debe definirse en el modelo. (El actor es la clase de usuario base. Suponga que puede ser cliente, vendedor o invitado).

interface ICheckAccess
{
    public function checkAccess(Actor $actor, $role);
}

class SomeModel implements ICheckAccess
{
    public function checkAccess(Actor $actor, $role)
    {
        // Your permissions logic can be as sophisticated as you want.
    }
}

Colocar esa lógica en el modelo trae algunas ganancias. El método de verificación de acceso se puede heredar, no necesita crear clases adicionales, puede usar las ventajas generales de OOP.

Luego, para simplificar la verificación de acceso, tomamos algunas suposiciones que casi siempre se implementan por simplicidad y buen estilo:

  • generalmente los controladores están relacionados con alguna clase de modelo;
  • las acciones que se verifican para el acceso toman la identificación del modelo único como parámetro;
  • siempre se puede acceder a este parámetro de manera uniforme desde el método de la clase de controlador base;
  • La acción se coloca en el controlador correspondiente al modelo que toma la acción id.

Con estos supuestos, las acciones que usan la identificación del modelo pueden asociarse con una instancia de modelo particular. De hecho, la mayoría de las acciones se pueden transformar y mover fácilmente para ajustarse a los supuestos establecidos anteriormente.

Luego, se debe definir y heredar alguna clase de controlador abstracto base.

abstract class ModelController
{
    // Retrieve model from database using id from action parameter.
    public abstract function loadModel($id);

    // Returns rules for user role to pass to SomeModel::checkAccess()
    // Something like array('view' => 'viewer', 'delete' => 'owner', 'update' => 'owner')
    public abstract function modelRules();

    public abstract fucntion getIdParameter();

    public function filterModelAccess()
    {
        $id = $this->getIdParameter();
        if(!$this->checkModelAccess($id))
            throw new HttpException(403);
    }

    public function checkModelAccess($id)
    {
        $model = $this->loadModel($id);
        $actor = My::app()->getActor();
        $rules = $this->modelRules();
        $role = $rules[My::app()->getActionName()];
        return $model->chechAccess($actor, $role);
    }
}

Puede llamar al método SomeController :: checkModelAccess ($ id) cuando construya sus menús y decida si desea mostrar algún enlace.

George Sovetov
fuente
Lo siento por PHP.
George Sovetov
1

Tanto en modelo como en vista

En la vista , porque la interfaz de usuario no debe mostrar los elementos de la interfaz de usuario que están restringidos al usuario actual

(como, digamos, el botón "Eliminar" debe mostrarse a las personas con los permisos adecuados)

En el modelo , porque su aplicación probablemente tenga algún tipo de API, ¿verdad? La API también tiene que verificar los permisos y probablemente reutiliza el Modelo.

(por ejemplo, tiene el botón "Eliminar" en la interfaz de usuario y el método de API "http: / server / API / DeleteEntry / 123" al mismo tiempo

jitbit
fuente
¿Por qué elegiste el modelo sobre el controlador?
Flimm
No estoy seguro de por qué ver, modelar y no en el controlador, donde se realiza la mayor parte del tiempo.
VP.
@VP el controlador no tiene poder para mostrar / ocultar elementos de la interfaz de usuario (aparte de pasar un bool-var A LA VISTA)
jitbit
No sé, en todas partes normalmente se hace en la capa del controlador, por eso tenía curiosidad.
VP.
0

MVC es un patrón de presentación. Como tal, la vista y el controlador solo deben tener responsabilidades con respecto a la presentación. Algunos permisos se aplican a la presentación, como un modo experto, características experimentales de IU o diseños diferentes. Esos pueden ser manejados por el controlador MVC.

Muchos otros tipos de permisos son relevantes en varias capas de la aplicación. Por ejemplo, si desea tener usuarios que solo puedan ver datos y no cambiar las cosas:

  • la capa de presentación tiene que ocultar las funciones de edición
  • Si se llama a una función de edición de todos modos, esto podría / debería ser detectado (por las partes específicas de la aplicación de la capa empresarial, no por la parte específica del dominio: TrainEditor, no Train) y probablemente cause una excepción
  • La capa de acceso a datos también puede verificar escrituras, pero para tipos de permisos más complejos que rápidamente requieren demasiado conocimiento de la capa empresarial para ser una buena idea.

Hay alguna duplicación en este enfoque. Pero como la presentación es usualmente volátil, uno puede hacer un buen caso para verificar el permiso en la parte generalmente más estable de la aplicación, incluso si eso significa algunas verificaciones redundantes en caso de que la capa de presentación funcione según lo previsto.

Patricio
fuente