Primera pregunta
Por favor, ¿podría explicarme cómo se podría implementar la ACL más simple en MVC?
Aquí está el primer enfoque para usar Acl en Controller ...
<?php
class MyController extends Controller {
public function myMethod() {
//It is just abstract code
$acl = new Acl();
$acl->setController('MyController');
$acl->setMethod('myMethod');
$acl->getRole();
if (!$acl->allowed()) die("You're not allowed to do it!");
...
}
}
?>
Es un enfoque muy malo, y el inconveniente es que tenemos que agregar un fragmento de código Acl en el método de cada controlador, ¡pero no necesitamos ninguna dependencia adicional!
El siguiente enfoque es crear todos los métodos del controlador private
y agregar el código ACL al __call
método del controlador .
<?php
class MyController extends Controller {
private function myMethod() {
...
}
public function __call($name, $params) {
//It is just abstract code
$acl = new Acl();
$acl->setController(__CLASS__);
$acl->setMethod($name);
$acl->getRole();
if (!$acl->allowed()) die("You're not allowed to do it!");
...
}
}
?>
Es mejor que el código anterior, pero las principales desventajas son ...
- Todos los métodos del controlador deben ser privados
- Tenemos que agregar el código ACL en el método __call de cada controlador.
El siguiente enfoque es poner el código Acl en el controlador principal, pero aún necesitamos mantener privados todos los métodos del controlador secundario.
¿Cuál es la solución? ¿Y cuál es la mejor práctica? ¿Dónde debo llamar a las funciones de Acl para decidir permitir o no permitir que se ejecute el método?
Segunda pregunta
La segunda pregunta es sobre cómo obtener un rol usando Acl. Imaginemos que tenemos invitados, usuarios y amigos de usuarios. El usuario tiene acceso restringido para ver su perfil que solo sus amigos pueden ver. Todos los invitados no pueden ver el perfil de este usuario. Entonces, aquí está la lógica ...
- tenemos que asegurarnos de que el método que se llama sea perfil
- tenemos que detectar al dueño de este perfil
- tenemos que detectar si el espectador es propietario de este perfil o no
- tenemos que leer las reglas de restricción sobre este perfil
- tenemos que decidir ejecutar o no ejecutar el método de perfil
La pregunta principal es sobre la detección del propietario del perfil. Podemos detectar quién es el propietario del perfil solo ejecutando el método $ model-> getOwner () del modelo, pero Acl no tiene acceso al modelo. ¿Cómo podemos implementar esto?
Espero que mis pensamientos estén claros. Lo siento por mi ingles.
Gracias.
fuente
if($user->hasFriend($other_user) || $other_user->profileIsPublic()) $other_user->renderProfile()
(de lo contrario, mostrar "No tienes acceso al perfil de este usuario" o algo así? No lo entiendo.Respuestas:
Primera parte / respuesta (implementación de ACL)
En mi humilde opinión, la mejor manera de abordar esto sería utilizar patrón de decorador . Básicamente, esto significa que tomas tu objeto y lo colocas dentro de otro objeto, que actuará como un caparazón protector. Esto NO requeriría que extienda la clase original. Aquí hay un ejemplo:
Y así sería como se usa este tipo de estructura:
Como puede notar, esta solución tiene varias ventajas:
Controller
Pero también hay un problema importante con este método: no puede verificar de forma nativa si el objeto protegido se implementa y la interfaz (que también se aplica para buscar métodos existentes) o es parte de alguna cadena de herencia.
Segunda parte / respuesta (RBAC para objetos)
En este caso, la principal diferencia que debe reconocer es que los objetos de dominio (en el ejemplo
Profile
:) contienen detalles sobre el propietario. Esto significa que, para que verifique, si (y en qué nivel) el usuario tiene acceso a él, será necesario que cambie esta línea:Básicamente tienes dos opciones:
Proporcione a la ACL el objeto en cuestión. Pero debes tener cuidado de no violar la Ley de Deméter :
Solicite todos los detalles relevantes y proporcione al ACL solo lo que necesita, lo que también lo hará un poco más amigable para las pruebas unitarias:
Un par de videos que pueden ayudarlo a crear su propia implementación:
Notas al margen
Parece tener una comprensión bastante común (y completamente incorrecta) de lo que es el modelo en MVC. El modelo no es una clase . Si tiene una clase nombrada
FooBarModel
o algo que heredaAbstractModel
entonces lo está haciendo mal.En el MVC adecuado, el modelo es una capa que contiene muchas clases. Gran parte de las clases se pueden separar en dos grupos, según la responsabilidad:
- Lógica empresarial de dominio
( leer más : aquí y aquí ):
Las instancias de este grupo de clases se ocupan del cálculo de valores, verifican diferentes condiciones, implementan reglas de ventas y hacen todo lo demás lo que llamaría "lógica de negocios". No tienen idea de cómo se almacenan los datos, dónde se almacenan o incluso si existe almacenamiento en primer lugar.
El objeto de negocio de dominio no depende de la base de datos. Cuando crea una factura, no importa de dónde provienen los datos. Puede ser desde SQL o desde una API REST remota, o incluso una captura de pantalla de un documento MSWord. La lógica empresarial no cambia.
- Acceso y almacenamiento de datos
Las instancias creadas a partir de este grupo de clases a veces se denominan objetos de acceso a datos. Generalmente estructuras que implementan Data Mapper patrón (no confundir con ORM del mismo nombre ... sin relación). Aquí es donde estarían sus declaraciones SQL (o tal vez su DomDocument, porque lo almacena en XML).
Además de las dos partes principales, hay un grupo más de instancias / clases, que deben mencionarse:
- servicios
Aquí es donde entran en juego sus componentes y los de terceros. Por ejemplo, puede pensar en la "autenticación" como un servicio, que puede proporcionarlo usted mismo o algún código externo. También "remitente de correo" sería un servicio, que podría unir algún objeto de dominio con un PHPMailer o SwiftMailer, o su propio componente de remitente de correo.
Otra fuente de servicios es la abstracción de las capas de acceso a datos y dominio. Se crean para simplificar el código utilizado por los controladores. Por ejemplo: crear una nueva cuenta de usuario puede requerir trabajar con varios objetos de dominio y mapeadores . Pero, al usar un servicio, solo necesitará una o dos líneas en el controlador.
Lo que debe recordar al realizar servicios es que se supone que toda la capa es delgada . No hay lógica empresarial en los servicios. Solo están ahí para hacer malabarismos con objetos de dominio, componentes y mapeadores.
Una de las cosas que todos tienen en común es que los servicios no afectan la capa de Vista de ninguna manera directa, y son autónomos hasta tal punto que pueden ser (y dejar de ser a menudo) utilizados fuera de la estructura MVC. Además, estas estructuras autosostenidas facilitan mucho la migración a un marco / arquitectura diferente, debido al acoplamiento extremadamente bajo entre el servicio y el resto de la aplicación.
fuente
Request
instancia (o algún análogo de ella). El controlador solo extrae datos de laRequest
instancia y pasa la mayor parte a los servicios adecuados (algunos de ellos también se muestran). Los servicios realizan operaciones que usted les ordenó que hicieran. Luego, cuando view genera la respuesta, solicita datos a los servicios y, en base a esa información, genera la respuesta. Dicha respuesta puede ser HTML hecha a partir de múltiples plantillas o simplemente un encabezado de ubicación HTTP. Depende del estado establecido por el controlador.ACL y controladores
En primer lugar: estas son cosas / capas diferentes con mayor frecuencia. A medida que critica el código ejemplar del controlador, une ambos, obviamente demasiado estricto.
tereško ya describió una forma de desacoplar esto más con el patrón de decorador.
Primero retrocedería un paso para buscar el problema original al que se enfrenta y luego lo discutiría un poco.
Por un lado, desea tener controladores que solo hagan el trabajo que se les ordenó (comando o acción, llamémoslo comando).
Por otro lado, desea poder poner ACL en su aplicación. El campo de trabajo de estas ACL debería ser, si entendí bien su pregunta, controlar el acceso a ciertos comandos de sus aplicaciones.
Por lo tanto, este tipo de control de acceso necesita algo más que los una. Según el contexto en el que se ejecuta un comando, ACL se activa y es necesario tomar decisiones sobre si un sujeto específico puede ejecutar o no un comando específico (por ejemplo, el usuario).
Resumamos hasta este punto lo que tenemos:
El componente ACL es fundamental aquí: necesita saber al menos algo sobre el comando (para identificar el comando para ser precisos) y necesita poder identificar al usuario. Normalmente, los usuarios se identifican fácilmente mediante una identificación única. Pero a menudo en las aplicaciones web hay usuarios que no están identificados en absoluto, a menudo llamados invitados, anónimos, todos, etc. Para este ejemplo asumimos que la ACL puede consumir un objeto de usuario y encapsular estos detalles. El objeto de usuario está vinculado al objeto de solicitud de la aplicación y la ACL puede consumirlo.
¿Qué hay de identificar un comando? Su interpretación del patrón MVC sugiere que un comando está compuesto por un nombre de clase y un nombre de método. Si miramos más de cerca, incluso hay argumentos (parámetros) para un comando. Entonces, ¿es válido preguntar qué identifica exactamente un comando? ¿El nombre de la clase, el nombre del método, el número o los nombres de los argumentos, incluso los datos dentro de cualquiera de los argumentos o una mezcla de todo esto?
Dependiendo del nivel de detalle que necesite para identificar un comando en su ACL, esto puede variar mucho. Para el ejemplo, hagámoslo simple y especifiquemos que un comando se identifica por el nombre de la clase y el nombre del método.
Entonces, el contexto de cómo estas tres partes (ACL, Comando y Usuario) se pertenecen entre sí ahora es más claro.
Podríamos decir, con un componente ACL imaginario ya podemos hacer lo siguiente:
Solo vea lo que sucede aquí: al hacer que tanto el comando como el usuario sean identificables, la ACL puede hacer su trabajo. El trabajo de la ACL no está relacionado con el trabajo tanto del objeto de usuario como del comando concreto.
Solo falta una parte, esto no puede vivir en el aire. Y no es así. Por lo tanto, debe ubicar el lugar donde debe activarse el control de acceso. Echemos un vistazo a lo que sucede en una aplicación web estándar:
Para ubicar ese lugar, sabemos que debe ser antes de que se ejecute el comando concreto, por lo que podemos reducir esa lista y solo necesitamos buscar en los siguientes lugares (potenciales):
En algún momento de su aplicación, sabe que un usuario específico ha solicitado realizar un comando concreto. Ya realiza algún tipo de ACL aquí: si un usuario solicita un comando que no existe, no permite que ese comando se ejecute. Entonces, donde sea que suceda en su aplicación, podría ser un buen lugar para agregar las verificaciones de ACL "reales":
El comando ha sido localizado y podemos crear la identificación del mismo para que la ACL pueda manejarlo. En caso de que el comando no esté permitido para un usuario, el comando no se ejecutará (acción). Tal vez en
CommandNotAllowedResponse
lugar deCommandNotFoundResponse
en el caso de que una solicitud no se pueda resolver en un comando concreto.El lugar donde la asignación de una HTTPRequest concreta se asigna a un comando a menudo se llama Enrutamiento . Dado que el enrutamiento ya tiene la tarea de localizar un comando, ¿por qué no extenderlo para verificar si el comando está realmente permitido por ACL? Por ejemplo, mediante la ampliación de la
Router
a un router consciente ACL:RouterACL
. Si su enrutador aún no conoce elUser
, entoncesRouter
no es el lugar correcto, porque para que el ACL funcione no solo el comando sino también el usuario debe estar identificado. Entonces, este lugar puede variar, pero estoy seguro de que puede ubicar fácilmente el lugar que necesita para extender, porque es el lugar que cumple con los requisitos de usuario y comando:El usuario está disponible desde el principio, Comando primero con
Request(Command)
.Entonces, en lugar de poner sus comprobaciones de ACL dentro de cada la implementación concreta de comando, lo coloca antes. No necesita ningún patrón pesado, magia o lo que sea, la ACL hace su trabajo, el usuario hace su trabajo y especialmente el comando hace su trabajo: solo el comando, nada más. El comando no tiene interés en saber si se le aplican roles o no, si está protegido en algún lugar o no.
Así que mantén las cosas separadas que no se pertenecen entre sí. Utilice una nueva redacción leve del principio de responsabilidad única (SRP) : debe haber solo una razón para cambiar un comando: porque el comando ha cambiado. No porque ahora introduzca ACL en su aplicación. No porque cambie el objeto Usuario. No porque migre de una interfaz HTTP / HTML a una interfaz SOAP o de línea de comandos.
La ACL en su caso controla el acceso a un comando, no el comando en sí.
fuente
Una posibilidad es envolver todos sus controladores en otra clase que amplíe Controller y hacer que delegue todas las llamadas de función a la instancia envuelta después de verificar la autorización.
También puede hacerlo más arriba, en el despachador (si su aplicación realmente tiene uno) y buscar los permisos basados en las URL, en lugar de los métodos de control.
editar : Si necesita acceder a una base de datos, un servidor LDAP, etc. es ortogonal a la pregunta. Mi punto fue que podría implementar una autorización basada en URL en lugar de métodos de controlador. Estos son más sólidos porque normalmente no cambiará sus URL (tipo de interfaz pública del área de URL), pero también podría cambiar las implementaciones de sus controladores.
Por lo general, tiene uno o varios archivos de configuración en los que asigna patrones de URL específicos a métodos de autenticación y directivas de autorización específicos. El despachador, antes de enviar la solicitud a los controladores, determina si el usuario está autorizado y aborta el envío si no lo está.
fuente