Cuando un servicio comprueba la autorización de un usuario para ejecutar una acción utilizando roles suelo encontrarme con la siguiente implementación:
Public Enum Rol ConsultorModulo1 ConsultorModulo2 OperadorModulo1 OperadorModulo2 Administrador End Enum function DoAction() { Rol allowedRoles[] = [OperadorModulo1, Administrador]; if (user.hasAnyRoleIn(allowedRoles)) { DoAction(); } else { throw new SecurityException("Permisos insuficientes"); } }
- ConsultorModuloX es un rol que permite consultar información pero no modificarla para un módulo del sistema en concreto.
- OperadorModuloX es un rol que permite la modificación de información para un módulo del sistema en concreto.
- Administrador es un rol que permite la totalidad de las acciones en el sistema.
No me gusta porque tenemos que encontrar coincidencias de items entre 2 conjuntos (roles que tiene el usuario y roles permitidos para la acción pertinente). Quedaría más eficiente y bonito si encontramos la manera de poder combinar varios roles en una sola variable y comprobar rápidamente si el usuario tiene asignado alguno (o varios o todos) de los roles necesarios.
Máscaras de bits
Para ello vamos a utilizar máscaras de bits. Gracias a las máscaras de bits y los operadores booleanos a nivel de bit podemos montar lo que deseamos de una forma simple y rápida.
Definimos un rol nuevo llamado None. None es ningún rol. Esto NO significa un servicio abierto para todos. Más adelante se verá la utilidad y necesidad de la declaración de este rol.
Definimos un rol nuevo llamado None. None es ningún rol. Esto NO significa un servicio abierto para todos. Más adelante se verá la utilidad y necesidad de la declaración de este rol.
Public Enum Rol None ConsultorModulo1 ConsultorModulo2 OperadorModulo1 OperadorModulo2 Administrador End Enum
Si asignamos una serie de bits diferentes a cada rol podremos utilizar lógica booleana para hacer las comprobaciones:
None = 00000
ConsultorModulo1 = 00001
ConsultorModulo2 = 00010
OperadorModulo1 = 00100
OperadorModulo2 = 01000
Administrador = 10000
Utilizando el operador a nivel de bit AND podemos comprobar que el rol asignado a un usuario es el rol necesario para ejecutar la acción:
RolNecesario 00001 (ConsultorModulo1)
AND
RolUsuario 00001 (OperadorModulo1)
-------
00001 <> 00000 (None) Ejecuta acción
Si el usuario no tiene un rol adecuado el resultado siempre es (00000) None:
RolNecesario 00001 (ConsultorModulo1)
AND
RolUsurio 00100 (OperadorModulo2)
-------
00000 (None) No tiene permisos
RolNecesario 10000 (Admin)
AND
RolUsuario 00101 (OperadorModulo1)
------
00000 (None) No tiene permisos
Combinando varios roles
Es perfectamente posible que un usuario pueda ser Consultor y/o Operador de varios módulos de la aplicación; por lo que debe tener asignado más de un rol. También es posible que una acción pueda ser permitida a varios roles. Para conseguir esto utilizamos el operador OR que permite "sumar" roles. Luego utilizando el operador AND descubrimos si alguno de los roles "sumados" "coincide":
00010 (ConsultorModulo2 )
OR
00100(OperadorModulo1)
OR
00100(OperadorModulo1)
-------
RolUsuario = 00110 (ConsultorModulo2 y OperadorModulo1 a la vez)
RolNecesario 00001 (ConsultorModulo1)
AND
RolUsuario 00110 (ConsultorModulo2 y OperadorModulo1 a la vez)
------
00000 (None) No tiene permisos
Una vez sumados los roles se puede seguir haciendo la comprobación con AND para comprobar si el usuario tiene algun rol de los necesarios para la acción:
RolNecesario 00001 (ConsultorModulo1)
AND
RolUsuario 00110 (ConsultorModulo2 y OperadorModulo1 a la vez)
------
00000 (None) No tiene permisos
Un servicio podría declarar que una acción está permitida para más de un rol:
00010 (ConsultorModulo2 )
OR
00001 (ConsultorModulo1)
-------
RolNecesario = 00011(ConsultorModulo1 y ConsultorModulo2 a la vez)
OR
00001 (ConsultorModulo1)
-------
RolNecesario = 00011(ConsultorModulo1 y ConsultorModulo2 a la vez)
RolNecesario = 00011 (ConsultorModulo1 y ConsultorModulo2 a la vez)
OR
RolUsuario = 00100 (OperadorModulo1)
-------
00000 (None) No tiene permisos
OR
RolUsuario = 00100 (OperadorModulo1)
-------
00000 (None) No tiene permisos
Implementacion en .NET
En .NET un enumerado es en realidad un alias de un entero de 32 bits por lo que cada elemento del enumerado tiene un valor numérico por defecto:
Public Enum Rol None = 1 ConsultorModulo1 = 2 ConsultorModulo2 = 3 OperadorModulo1 = 4 OperadorModulo2 = 5 Administrador = 6 End Enum
Este valor numérico se puede sobrescribir para adaptarse a nuestras necesidades:
Public Enum Rol None = 0 ConsultorModulo1 = 1 ConsultorModulo2 = 2 OperadorModulo1 = 3 OperadorModulo2 = 4 Administrador = 5 End Enum
Y si le aplicamos el atributo FlagsAttribute lo convertimos en un campo de bits que es lo que nos interesa.
<Flags> _ Public Enum Rol None = 0 ConsultorModulo1 = 1 ConsultorModulo2 = 2 OperadorModulo1 = 3 OperadorModulo2 = 4 Administrador = 5 End Enum
None = 00000 (cero en binario)
ConsultorModulo1 = 00001 (uno en binario)
ConsultorModulo2 = 00010 (dos en binario)
OperadorModulo1 = 00011 (tres en binario)
OperadorModulo2 = 00100 (cuatro en binario)
Administrador = 00101 (cinco en binario)
Sabiendo esto; sólo necesitamos asignarle valores cuyos bits no se solapen, básicamente debemos aumentar en potencias de 2:
<Flags> _ Public Enum Rol None = 0 ConsultorModulo1 = 1 ConsultorModulo2 = 2 OperadorModulo1 = 4 OperadorModulo2 = 8 Administrador = 16 End Enum
None = 00000 (cero en binario)
ConsultorModulo1 = 00001 (uno en binario)
ConsultorModulo2 = 00010 (dos en binario)
OperadorModulo1 = 00100 (cuatro en binario)
OperadorModulo2 = 01000 (ocho en binario)
Administrador = 10000 (dieciseis en binario)
Ahora simplemente queda utilizarlos en la autorización del usuario:
Usuario.Roles = Rol.ConsultorModulo1 OR Rol.OperadorModulo2; function DoAction() { Rol minimunAllowedRoles= Rol.OperadorModulo1 OR Rol.ConsultorModulo2; if (allowedRoles AND Usuario.Roles <> Rol.None) { DoAction(); } else { throw new SecurityException("Permisos insuficientes"); } }
No hay comentarios:
Publicar un comentario