El ejemplo utilizado en la pregunta pasa datos mínimos básicos a una función que toca la mejor manera de determinar si el usuario es administrador o no. Una respuesta común fue:
user.isAdmin()
Esto provocó un comentario que se repitió varias veces y se votó muchas veces:
Un usuario no debe decidir si es un administrador o no. El sistema de privilegios o seguridad debería. Algo que esté estrechamente vinculado a una clase no significa que sea una buena idea hacer que forme parte de esa clase.
Respondí,
El usuario no está decidiendo nada. El objeto / tabla Usuario almacena datos sobre cada usuario. Los usuarios reales no pueden cambiar todo sobre ellos mismos.
Pero esto no fue productivo. Claramente, existe una diferencia de perspectiva subyacente que dificulta la comunicación. ¿Alguien puede explicarme por qué user.isAdmin () es malo y pintar un breve boceto de lo que parece hecho "bien"?
Realmente, no veo la ventaja de separar la seguridad del sistema que protege. Cualquier texto de seguridad dirá que la seguridad debe diseñarse en un sistema desde el principio y considerarse en cada etapa del desarrollo, implementación, mantenimiento e incluso el final de la vida útil. No es algo que se pueda atornillar a un lado. Pero 17 votos positivos hasta ahora en este comentario dicen que me falta algo importante.
fuente
User
objeto con unisAdmin()
método (lo que sea que haga detrás de escena)Respuestas:
Piense en el Usuario como una clave y en los permisos como un valor.
Diferentes contextos pueden tener diferentes permisos para cada usuario. Como los permisos son relevantes para el contexto, deberá cambiar el usuario para cada contexto. Por lo tanto, es mejor que coloque esa lógica en otro lugar (por ejemplo, la clase de contexto) y simplemente busque los permisos donde sea necesario.
Un efecto secundario es que puede hacer que su sistema sea más seguro, porque no puede olvidar borrar la bandera de administrador para los lugares donde no es relevante.
fuente
Ese comentario estaba mal redactado.
El punto no es si el objeto de usuario "decide" si es un administrador, un usuario de prueba, un superusuario o cualquier cosa dentro o fuera de lo común. Obviamente, no decide ninguna de esas cosas, el sistema de software en general, tal como lo implementa usted, lo hace. El punto es si la clase "usuario" debe representar los roles del principal que representan sus objetos, o si esto es responsabilidad de otra clase.
fuente
No hubiera elegido redactar el comentario original de la forma en que fue redactado, pero identifica un problema potencialmente legítimo.
Específicamente, las preocupaciones que justifican la separación son autenticación versus autorización .
La autenticación se refiere al proceso de iniciar sesión y obtener una identidad. Es la forma en que los sistemas saben quién es usted y cómo se utilizan para cosas como personalización, propiedad de objetos, etc.
La autorización se refiere a lo que tiene permitido hacer , y esto (generalmente) no está determinado por quién es usted . En cambio, está determinado por alguna política de seguridad como roles o permisos, que no se preocupan por cosas como su nombre o dirección de correo electrónico.
Estos dos pueden cambiar ortogonalmente entre sí. Por ejemplo, puede cambiar el modelo de autenticación agregando proveedores OpenID / OpenAuth. Y puede cambiar la política de seguridad agregando un nuevo rol o cambiando de RBAC a ABAC.
Si todo esto entra en una clase o abstracción, entonces su código de seguridad, que es una de sus herramientas más importantes para la mitigación de riesgos , se convierte, irónicamente, en de alto riesgo.
He trabajado con sistemas en los que la autenticación y la autorización estaban demasiado unidas. En un sistema, había dos bases de datos de usuarios paralelas, cada una para un tipo de "rol". Aparentemente, la persona o el equipo que lo diseñó nunca consideró que un solo usuario físico podría tener ambos roles, o que podría haber ciertas acciones que eran comunes a múltiples roles, o que podría haber problemas con las colisiones de ID de usuario. Este es un ejemplo ciertamente extremo, pero fue / es increíblemente doloroso trabajar con él.
Microsoft y Sun / Oracle (Java) se refieren al conjunto de información de autenticación y autorización como Principal de seguridad . No es perfecto, pero funciona razonablemente bien. En .NET, por ejemplo, tiene
IPrincipal
, que encapsula elIIdentity
- el primero es un objeto de política (autorización) mientras que el segundo es una identidad (autenticación). Podría cuestionar razonablemente la decisión de colocar uno dentro del otro, pero lo importante es que la mayoría del código que escriba será solo para una de las abstracciones, lo que significa que es fácil de probar y refactorizar.No hay nada de malo en un
User.IsAdmin
campo ... a menos que también haya unUser.Name
campo. Esto indicaría que el concepto de "Usuario" no está bien definido y este es, lamentablemente, un error muy común entre los desarrolladores que están un poco mojados detrás de las orejas cuando se trata de seguridad. Por lo general, lo único que debe compartir la identidad y la política es la ID de usuario, que, no por coincidencia, es exactamente cómo se implementa en los modelos de seguridad de Windows y * nix.Es completamente aceptable crear objetos de contenedor que encapsulen tanto la identidad como la política. Por ejemplo, facilitaría la creación de una pantalla de tablero en la que necesita mostrar un mensaje de "saludo" además de varios widgets o enlaces a los que el usuario actual tiene permitido acceder. Siempre que este contenedor solo envuelva la información de identidad y política, y no afirme ser el propietario. En otras palabras, siempre que no se presente como una raíz agregada .
Un modelo de seguridad simplista siempre parece una buena idea cuando diseñas una nueva aplicación por primera vez, debido a YAGNI y todo eso, pero casi siempre termina volviendo a morderte más tarde, porque, sorpresa sorpresa, ¡se agregan nuevas funciones!
Entonces, si sabe lo que es mejor para usted, mantendrá la información de autenticación y autorización por separado. Incluso si la "autorización" en este momento es tan simple como un indicador "IsAdmin", aún así será mejor si no es parte de la misma clase o tabla que la información de autenticación, de modo que si y cuando su política de seguridad necesite cambie, no necesita realizar una cirugía reconstructiva en sus sistemas de autenticación que ya funciona bien.
fuente
isAdmin
es un método? Podría simplemente delegar en un objeto cuya responsabilidad es realizar un seguimiento de los permisos. Lo que es mejor práctica en el diseño de un modelo relacional (qué campos tiene una entidad) no es necesariamente traducible a lo que es mejor práctica en un modelo OO (a qué mensajes puede responder un objeto).User.IsAdmin
muy posible que sea una línea que no hace nada más que llamarPermissionManager.IsAdmin(this.Id)
, pero si otras 30 clases hacen referencia a ella, no puede cambiarla ni eliminarla. Por eso existe el SRP.IsAdmin
campo . Es el tipo de campo que tiende a permanecer mucho tiempo después de que el sistema se ha convertido en RBAC o algo más complejo, y gradualmente se desincroniza con los permisos "reales", y las personas olvidan que ya no deben usarlo, y ... bueno, solo di que no. Incluso si cree que solo tiene dos roles, "admin" y "no admin", no lo almacene como un campo booleano / bit, normalice correctamente y tenga una tabla de permisos.Bueno, al menos viola el principio de responsabilidad única . ¿Debería haber cambios (múltiples niveles de administración, incluso más roles diferentes?) Este tipo de diseño sería bastante desordenado y horrible de mantener.
Considere la ventaja de separar el sistema de seguridad de la clase de usuario, para que ambos puedan tener un rol único y bien definido. Terminas con un diseño más limpio y fácil de mantener en general.
fuente
class User
es un componente central en el triplete de seguridad de Identificación, Autenticación, Autorización . Estos están íntimamente vinculados a nivel de diseño, por lo que no es irrazonable que el código refleje esta interdependencia.Should there be changes
- Bueno, no sabemos si habrá cambios. YAGNI y todo. ¿Esos cambios cuando es necesario, verdad?Creo que el problema, tal vez mal redactado, es que pone demasiado peso en la
User
clase. No, no hay ningún problema de seguridad con tener un métodoUser.isAdmin()
, como se sugirió en esos comentarios. Después de todo, si alguien es lo suficientemente profundo en su código para inyectar código malicioso para una de sus clases principales, tiene serios problemas. Por otro lado, no tiene sentidoUser
decidir si es un administrador para cada contexto. Imagine que agrega diferentes roles: moderadores para su foro, editores que pueden publicar publicaciones en la página principal, escritores que pueden escribir contenido para que los editores publiquen, etc. Con el enfoque de ponerlo en elUser
objeto, terminarás con demasiados métodos y demasiada lógica viviendo enUser
eso que no está directamente relacionado con la clase.En su lugar, podría usar una enumeración de diferentes tipos de permisos y una
PermissionsManager
clase. Quizás podría tener un método comoPermissionsManager.userHasPermission(User, Permission)
devolver un valor booleano. En la práctica, podría verse así (en Java):Es esencialmente equivalente al método del
before_filter
método Rails , que proporciona un ejemplo de este tipo de verificación de permisos en el mundo real.fuente
user.hasPermission(Permissions.EDITOR)
, excepto que es más detallado y procesal en lugar de OO?user.hasPermissions
pone demasiadas responsabilidades en la clase de usuario. Requiere que el usuario conozca los permisos, mientras que un usuario (clase) debería poder existir sin dicho conocimiento. Tampoco desea que una clase de usuario sepa cómo imprimir o mostrar (crear un formulario), lo deja a un procesador / generador de impresoras o muestra una clase de procesador / generador.user.hasPermission(P)
es la API correcta, pero esa es solo la interfaz. Por supuesto, la decisión real debe tomarse en un subsistema de seguridad. Para abordar el comentario de syrion sobre las pruebas, durante las pruebas puede cambiar ese subsistema. Sin embargo, el beneficio de lo directouser.hasPermission(P)
es que no contamina todo el código solo para poder probarloclass User
.Object.isX () se usa para representar una relación clara entre el objeto y X, que se puede expresar como un resultado booleano, por ejemplo:
Water.isBoiling ()
Circuit.isOpen ()
User.isAdmin () asume un significado único de 'Administrador' dentro de cada contexto del sistema , que un usuario es, en cada parte del sistema, un administrador.
Si bien parece bastante simple, un programa del mundo real casi nunca se ajustará a este modelo, sin duda habrá un requisito para que un usuario administre el recurso [X] pero no [Y], o para tipos más distintos de administradores (un [proyecto ] administrador frente a un [sistema] administrador).
Esta situación generalmente requiere una investigación de los requisitos. En raras ocasiones, si alguna vez, un cliente querrá la relación que User.isAdmin () realmente representa, por lo que dudaría en implementar cualquier solución sin aclaración.
fuente
El póster simplemente tenía un diseño diferente y más complicado en mente. En mi opinión,
User.isAdmin()
está bien . Incluso si luego presenta algún modelo de permisos complicado, veo pocas razones para movermeUser.isAdmin()
. Más adelante, es posible que desee dividir la clase Usuario en un objeto que represente un usuario conectado y un objeto estático que represente los datos sobre el usuario. O tal vez no. Ahorra mañana para mañana.fuente
Save tomorrow for tomorrow
. Sí. Cuando descubres que el código estrechamente acoplado que acabas de escribir te ha encasillado y tienes que volver a escribirlo porque no te tomaste 20 minutos adicionales para desacoplarlo ...User.isAdmin
método conectado a un mecanismo de permiso arbitrario sin acoplar la clase de usuario a ese mecanismo específico.Save tomorrow for tomorrow
es un enfoque de diseño terrible porque hace que los problemas que serían triviales si el sistema se implementara correctamente fueran mucho peores. De hecho, esa mentalidad es lo que da como resultado API demasiado complejas, ya que se agrega capa sobre capa para parchear el diseño inicial pobre. Supongo que mi postura esmeasure twice, cut once
.Realmente no es un problema ...
No veo un problema con
User.isAdmin()
. Ciertamente lo prefiero a algo asíPermissionManager.userHasPermission(user, permissions.ADMIN)
, que en el santo nombre de SRP hace que el código sea menos claro y no agrega nada de valor.Creo que SRP está siendo interpretado un poco literalmente por algunos. Creo que está bien, incluso es preferible que una clase tenga una interfaz rica. SRP solo significa que un objeto debe delegar a los colaboradores cualquier cosa que no esté dentro de su responsabilidad. Si la función de administrador de un usuario es algo más complicado que un campo booleano, podría tener sentido que el objeto del usuario delegue en un Administrador de permisos. Pero eso no significa que tampoco sea una buena idea que un usuario conserve su
isAdmin
método. De hecho, significa que cuando su aplicación se gradúa de simple a compleja, debe cambiar el código que usa el objeto Usuario. IOW, su cliente no necesita saber qué se necesita realmente para responder a la pregunta de si un usuario es o no administrador.... pero ¿por qué realmente quieres saber?
Dicho esto, me parece que rara vez necesita saber si un Usuario es un administrador, excepto para poder responder alguna otra pregunta, como si un usuario puede o no realizar alguna acción específica, por ejemplo, como actualizar un Widget. Si ese es el caso, preferiría tener un método en Widget, como
isUpdatableBy(User user)
:De esa manera, un widget es responsable de saber qué criterios deben cumplirse para que un usuario lo actualice, y un usuario es responsable de saber si es un administrador. Este diseño se comunica claramente sobre sus intenciones y hace que sea más fácil graduarse a una lógica empresarial más compleja si llega el momento.
[Editar]
El problema con no tener
User.isAdmin()
y usarPermissionManager.userHasPermission(...)
Pensé en agregar a mi respuesta para explicar por qué prefiero llamar a un método en el objeto Usuario que llamar a un método en un objeto PermissionManager si quiero saber si un usuario es administrador (o tiene un rol de administrador).
Creo que es justo suponer que siempre dependerá de la clase de usuario donde sea que necesite hacer la pregunta: ¿ es este usuario administrador? Esa es una dependencia de la que no puedes deshacerte. Pero si necesita pasar al usuario a un objeto diferente para hacer una pregunta al respecto, eso crea una nueva dependencia de ese objeto además del que ya tiene en el Usuario. Si la pregunta se hace mucho, son muchos los lugares donde se crea una dependencia adicional, y son muchos los lugares que puede tener que cambiar si cualquiera de las dependencias lo exige.
Compare esto con mover la dependencia a la clase Usuario. Ahora, de repente, tiene un sistema en el que el código del cliente (el código que debe hacer la pregunta de si este usuario es un administrador ) no está acoplado a la implementación de cómo se responde esta pregunta. Puede cambiar completamente el sistema de permisos y solo tiene que actualizar un método en una clase para hacerlo. Todo el código del cliente permanece igual.
Insistir en no tener un
isAdmin
método en el Usuario por temor a crear una dependencia en la clase Usuario en el subsistema de permisos, es en mi opinión similar a gastar un dólar para ganar un centavo. Claro, evitas una dependencia en la clase Usuario, pero a costa de crear una en cada lugar donde necesites hacer la pregunta. No es una buena ganga.fuente
Esta discusión me recordó una publicación de blog de Eric Lippert, que me llamó la atención en una discusión similar sobre diseño de clase, seguridad y corrección.
¿Por qué se sellan tantas clases marco?
En particular, Eric plantea el punto:
Esto puede parecer una tangente, pero creo que encaja con el punto que otros carteles han planteado sobre el principio de responsabilidad única SÓLIDO . Considere la siguiente clase maliciosa como una razón para no implementar un método o propiedad.
User.IsAdmin
De acuerdo, es un poco exagerado: nadie sugirió hacer
IsAmdmin
un método virtual, pero podría suceder si su arquitectura de usuario / rol terminara siendo extrañamente compleja. (O tal vez no. Considerepublic class Admin : User { ... }
: tal clase podría tener exactamente ese código arriba). Poner un binario malicioso en su lugar no es un vector de ataque común y tiene mucho más potencial para el caos que una biblioteca de usuario oscura, una vez más, esto podría ser el error de escalada de privilegios que abre la puerta a las travesuras reales. Finalmente, si una instancia binaria u objeto malicioso se abrió paso en el tiempo de ejecución, no es mucho más difícil imaginar que el "Sistema de privilegios o seguridad" sea reemplazado de manera similar.Pero tomando en serio el punto de Eric, si pones tu código de usuario en un tipo particular de sistema vulnerable, tal vez acabas de perder el juego.
Ah, y para ser exactos para el registro, estoy de acuerdo con el postulado en la pregunta: "Un usuario no debe decidir si es un administrador o no". Los privilegios o el sistema de seguridad deberían ". Un
User.IsAdmin
método es una mala idea si está ejecutando el código en el sistema, no tiene el 100% de control del código; en su lugar, debería hacerloPermissions.IsAdmin(User user)
.fuente
System.String
ser heredable porque todo tipo de código de marco crítico hace suposiciones muy específicas sobre cómo funciona. En la práctica, la mayoría del "código de usuario" (incluida la seguridad) es heredable para permitir la duplicación de pruebas.IsAdmin
método para anular / cooptar, no hay un agujero de seguridad potencial. La revisión de los métodos de las interfaces del sistema,IPrincipal
yIIdentity
, está claro que los diseñadores no están de acuerdo conmigo, y por eso me conceden el punto.El problema aquí es:
O bien ha configurado su seguridad correctamente, en cuyo caso el método privilegiado debe verificar si la persona que llama tiene suficiente autoridad, en cuyo caso la verificación es superflua. O no lo ha hecho y está confiando en una clase sin privilegios para tomar sus propias decisiones sobre seguridad.
fuente
User.IsAdmin
específicamente un problema.