viernes, 21 de marzo de 2014

Roles para autorizaciones en el sistema.

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.

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)
            -------
RolUsuario = 00110 (ConsultorModulo2 y OperadorModulo1 a la vez)

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)

RolNecesario = 00011  (ConsultorModulo1 y ConsultorModulo2 a la vez)
                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