Cómo implementar de forma limpia el acceso a las funciones basado en permisos

7

Se me ha asignado la tarea de escribir un control de activación / desactivación para las funciones de nuestro producto en función de quién haya iniciado sesión, en principio, con un indicador de activación / desactivación para cada función. En pocas palabras, este es un acceso a funciones basado en permisos que se puede activar a nivel de usuario.

El problema es que puedo ver que esto se convierte en una pesadilla de complejidad. Por ejemplo, la persona A es aparte del grupo G pero tiene misiones especiales para las misiones 1, 2, 3, pero A es completamente diferente a la persona B, que es parte del grupo H que tiene permisos especiales 2, 3 y 4, etc. etc.

Administrar los permisos no va a ser la parte difícil: eso es simplemente establecer un valor en la base de datos. La parte difícil será permitir que la persona A pueda usar la función F si tiene permiso P. No quiero que esto se convierta en cientos de sentencias dispersas en la base del código cuyas combinaciones son exponenciales.

Una solución que tengo en mente es separar la característica del permiso al tener una capa de control justo encima de la característica que todo lo demás usa, similar a escribir una biblioteca y usar un lenguaje de scripting para las estructuras de control. Esto separaría las declaraciones if / else de la característica real, pero el problema es que todavía existen y sus combinaciones aún están allí.

¿Conoces alguna solución elegante para este problema o alguna similar?

    
pregunta zzelman 28.03.2017 - 19:03

3 respuestas

3

1. Organice los permisos y su control

De hecho, si define permisos de acceso a funciones individuales, tendrá, como en características alternas , un if -clause en cada característica para verificar si el usuario tiene el permiso correcto.

Si retira la función del permiso, por ejemplo, mediante un nivel de direccionamiento indirecto, como un grupo de características (por ejemplo, una característica pertenece a un grupo configurable) o un perfil de autorización (por ejemplo, varios usuarios pertenecen a un perfil, que tiene varios permisos de funciones), aún necesitará una cláusula if para cada función.

Para que esto sea fácil de manejar, debe encapsular la verificación de permisos en un objeto de permiso. Esto le permitiría implementar el enlace entre la función y el usuario con cualquiera de las estrategias mencionadas anteriormente, sin tener que cambiar el código:

user.permission("feature_X").mandatory_check(); // if needed, throws a permission exception 
                                                // to be catched at right level. 

o

if (user.permission("feature_Y").optional_check() ) {
   ...                                         // execute optional feature 
}                                              // do nothing if access is not granted

2. Las funciones de control acceden por adelantado

Si las funciones son funciones independientes, puede controlar el acceso por adelantado.

Esto significa que la IU se asegurará de que el usuario solo pueda invocar funciones y comandos para los que el usuario tenga permiso. Esto permite aislar los controles de permisos en su arquitectura. Las diversas características no tendrán que implementar verificaciones adicionales.

Lamentablemente, esto no funcionará si la función es opcional, es decir, el usuario puede invocar una función, y la función se ejecuta de manera diferente si la función está ahí o no.

Administrar características como una estrategia

Este enfoque utiliza estrategias en tiempo de ejecución. La estrategia invoca una característica o nada. El Context sería en este caso un tipo de objeto de perfil de usuario que se inicializa al iniciar sesión. Para cada característica habría una estrategia (esto podría seguir siendo flexible, mediante el uso de un contenedor asociativo como un mapa de características). Las estrategias serían inicializadas dependiendo de los permisos del usuario.

Más adelante, cuando se ejecuten las diferentes funciones del software, utilizarán la estrategia relevante (por ejemplo, un objeto que ejecutará la función correcta o un objeto de estrategia vacío que no hace nada).

Este enfoque parece bastante elegante. Sin embargo, en lugar de agregar un if en el código de cada característica, se requeriría implementar cada característica como una estrategia independiente, lo que podría ser extremadamente desafiante.

¿Y qué?

Personalmente, incluso si el segundo parece más elegante, optaría por la opción 1: esto es mucho más flexible. La encapsulación adecuada del permiso puede reducir el control de permisos en la característica a una sola declaración.

    
respondido por el Christophe 28.03.2017 - 20:38
2

Hay muchas formas diferentes de hacer permisos, pero aquí hay algunos consejos.

Tenga en cuenta que hay dos programas aquí: la interfaz del administrador, que permite a un administrador configurar usuarios, grupos y permisos; y el programa principal, que necesita saber quién tiene qué permisos.

Comience con el programa principal

Los permisos se configuran solo ocasionalmente. Pero se leen una y otra vez. La lectura es mucho más importante en este sentido.

Terminará agregando este tipo de código a casi todas las páginas / formularios:

bool ok = HasPermission(user, permissionCode);

y algunas veces

AssertHasPermission(user, permissionCode);

o tal vez

AssertHasPermission(user, permissionCode, Action callbackIfFailed);

este último podría ser útil si se llama así:

AssertHasPermission(context.User, "108", () => context.Redirect("~/Error.html"));

Desea que este código se ejecute de manera muy eficiente. Cualquiera que sea la estructura de datos que utilice, debe ser simple; tomar un usuario y permiso y ver si existe. Sugiero una estructura plana / desnormalizada. En tiempo de ejecución, no debería ser necesario recorrer un árbol, por ejemplo, o aplicar reglas en cascada.

Si sus requisitos incluyen membresía de grupo y permisos de grupo, implemente eso más adelante ... vea a continuación.

Una función podría no ser una función

Para un usuario final, una "característica" puede estar compuesta de varias funciones en el código. Por este motivo, necesita al menos una capa de indirección entre un permiso y una base de código. Es por eso que en el ejemplo anterior estoy pasando un código de permiso y no, digamos, un nombre de página. Necesitará esta abstracción en caso de que necesite agregar más páginas a una característica, o si una página tiene que dividirse en varias características (por ejemplo, leer y escribir para el mismo objeto de dominio).

Grupos, permisos en cascada y reglas de adición / resta

Parece que sus requisitos incluyen permisos de grupo. Este tipo de cosas son casi estéticas: es principalmente para facilitar las cosas a un usuario final, es decir, administrar a varios usuarios en forma de cerradura al ponerlos en un grupo. Pero entonces hay excepciones; quizás necesite la capacidad de anular los permisos de grupo para ciertos miembros, por ejemplo, o necesita dos permisos diferentes para acceder a una sola función. Puedes volverte loco con las opciones.

Lo importante es: mantener estas opciones fuera de la base del código principal. A tu código principal no debería importarle si un usuario está en un grupo; solo importa si tiene el código de permiso correcto después de que se apliquen todas las reglas del grupo.

Sugeriría que, después de aceptar las aportaciones del administrador, el programa de administración debería mantener una estructura separada para grupos y usuarios, luego recorrer la estructura y componer la estructura plana que utilizará el programa principal. Puede pensar en esto como "compilar" o desnormalizar.

Si haces esto correctamente, te ahorrarás mucho trabajo. Más adelante, si sus conceptos de membresía de grupo y permisos de grupo cambian (por ejemplo, si agrega listas negras y listas blancas, o si se integra con grupos de Active Directory, etc.), la lista de permisos calculada aún debe seguir siendo la misma estructura plana anterior al final . De esa manera, puede ajustar la IU de administración al contenido de su corazón y su base de código principal no requerirá ninguna modificación.

YAGNI

En mi experiencia, las personas se vuelven muy creativas cuando encuentran posibilidades para administrar permisos. En casi todos los casos que he visto, la implementación resultante es mucho más compleja de lo que realmente se necesita. Mantén las cosas simples. No intentes incluir todos los casos de uso imaginables. En su lugar, concéntrese en la extensibilidad y recuerde que siempre puede mejorar las estructuras del grupo sin afectar el programa principal.

    
respondido por el John Wu 28.03.2017 - 23:38
1

Un enfoque muy común que generalmente requiere una pequeña cantidad de combinaciones es control de acceso basado en roles . En RBAC, le daría un nombre a cada recurso, posiblemente en una jerarquía. Luego creas roles con permisos para realizar ciertos verbos (como "crear", "leer", "lista", "ver", "eliminar", "ejecutar") en esos recursos. Luego asignas sujetos a esos roles.

Por ejemplo, digamos que sus permisos se asignaron a:

  1. Crear nuevo cliente
  2. Lea la información del cliente
  3. leer registros
  4. Ejecutar autoprueba

Puede crear un rol de "Servicio al cliente" con los permisos 1-3 y un rol de "Técnico" con los permisos 2-4, y luego asignar esos roles a los sujetos según corresponda. Puede asignar ambos roles a un administrador.

Su código para leer los registros simplemente debe verificar si el sujeto actual está asignado a un rol con permiso de "lectura" en el recurso "registro".

Esto permite una asignación flexible de roles de usuario a grupos de permisos que no tiene que adivinar desde el principio y no tiene que ser el mismo para cada cliente.

    
respondido por el Karl Bielefeldt 29.03.2017 - 14:53

Lea otras preguntas en las etiquetas