Resumen:
Este documento describe un sistema minimalista pero potente de control de acceso granular en un entorno SaaS, donde los usuarios tienen acceso controlado a entidades y operaciones específicas, con la capacidad adicional de gestionar múltiples bases de datos. Se implementa un sistema de roles y permisos que permite definir jerarquías de acceso y control sobre entidades, operaciones y datos relacionados. Además, se incluye la capacidad de limitar el acceso a una base de datos a la vez por usuario, asegurando un entorno seguro y flexible.
1. Introducción
El control de acceso granular es un aspecto crucial en el desarrollo de aplicaciones SaaS. Este paper describe un sistema minimalista que utiliza una estructura de tipos y campos en GraphQL para gestionar los permisos y el acceso a operaciones y entidades, asegurando la flexibilidad y simplicidad en su administración. Este sistema también permite la segmentación de acceso a múltiples bases de datos específicas para los usuarios, garantizando que cada usuario solo pueda operar en una base de datos a la vez.
2. Modelo Estructural
El sistema se basa en los siguientes tipos clave:
2.1. Entidad (Entity)
Define las entidades que pueden ser gestionadas en el sistema, como Store o Product. El campo inheritsAccess indica si el acceso a esta entidad requiere un control adicional a nivel de registros específicos.
type Entity {
id: ID!
name: String!
inheritsAccess: Boolean!
}
| id | name | inheritsAccess |
|---|---|---|
| 949494 | Store | true |
| 123456 | Product | false |
2.2. Tipo Operation
Define las operaciones que se pueden realizar sobre las entidades, como obtener o listar tiendas.
type Operation {
id: ID!
entityId: ID!
operationName: String!
}
| id | entityId | operationName |
|---|---|---|
| 7373 | 949494 | getStores |
| 8383 | 123456 | listProducts |
2.3. Tipo EntityInherit
Este tipo se utiliza cuando una entidad requiere acceso granular. Relaciona la entidad con el tipo que define los permisos de acceso, en este caso, UserStore.
type EntityInherit {
id: ID!
entityId: ID!
inheritType: String!
}
| id | entityId | inheritType |
|---|---|---|
| 49 | 949494 | userStore |
2.4. Tipo UserStore
Define qué usuarios tienen acceso a tiendas específicas. Esto asegura que el acceso a los registros de tiendas esté restringido a nivel de usuario.
type UserStore {
id: ID!
storeId: ID!
userId: ID!
}
| id | storeId | userId |
|---|---|---|
| 84 | 5 | 6 |
| 85 | 8 | 6 |
2.5. Tipo Role
Define los diferentes roles que un usuario puede tener, incluyendo el tipo de rol (por ejemplo, guest, operator, supervisor, director), lo que simplifica la gestión del acceso y las responsabilidades.
enum RoleType {
GUEST
OPERATOR
SUPERVISOR
DIRECTOR
}
type Role {
id: ID!
name: String!
roleType: RoleType!
}
| id | name | roleType |
|---|---|---|
| 5 | Manejo de tiendas | SUPERVISOR |
| 6 | Visor de productos | GUEST |
2.6. Tipo RoleOperation
Relaciona los roles con las operaciones que pueden ejecutar. Cada rol puede tener acceso a múltiples operaciones sobre distintas entidades.
type RoleOperation {
id: ID!
roleId: ID!
operationId: ID!
}
| id | roleId | operationId |
|---|---|---|
| 7 | 5 | 7373 |
2.7. Tipo UserRole
Relaciona a los usuarios con los roles que tienen asignados. Además, incluye el campo datastoreId, que indica a qué base de datos (definida en el tipo Datastore) tiene acceso el usuario.
type UserRole {
id: ID!
userId: ID!
roleId: ID!
datastoreId: ID!
}
| id | userId | roleId | datastoreId |
|---|---|---|---|
| 5 | 6 | 5 | 1 |
2.8. Tipo Datastore
Define las diferentes bases de datos a las que los usuarios pueden tener acceso. Esto asegura que el usuario solo interactúe con los datos de una base de datos a la vez.
type Datastore {
id: ID!
name: String!
}
| id | name |
|---|---|
| 1 | corpdb1 |
| 2 | corpdb2 |
3. Lógica de Acceso
El sistema sigue una lógica clara para determinar si un usuario puede realizar una operación:
Verificación del Rol:
Cuando un usuario intenta realizar una operación, se verifica si el rol asignado al usuario permite esa operación, consultando el tipo RoleOperation.
Control de Acceso Granular:
Si la entidad sobre la que se realiza la operación tiene inheritsAccess = true, se consulta el tipo EntityInherit para determinar qué registros específicos puede acceder el usuario. Por ejemplo, si la operación es getStores, se filtran las tiendas a las que el usuario tiene acceso, basado en el tipo UserStore.
Acceso a Base de Datos:
Cada operación también verifica la base de datos a la que tiene acceso el usuario mediante el campo datastoreId en UserRole. El usuario solo puede operar en una base de datos a la vez, lo que asegura segmentación y control adecuado de los datos.
4. Ejemplo Práctico de Acceso
Imaginemos que el usuario 6 quiere ejecutar la operación getStores. El flujo de acceso sería el siguiente:
- Operación y Rol: Se consulta en el tipo
RoleOperationque el rol asignado al usuario (en este caso, SUPERVISOR) permite ejecutar la operacióngetStores. - Control de Acceso Granular: Se verifica si la entidad
StoretieneinheritsAccess = true. Al ser afirmativo, se consulta el tipoUserStorepara filtrar las tiendas a las que el usuario puede acceder. El resultado sería algo como:WHERE storeId IN (5, 8). - Acceso a la Base de Datos: Se verifica en el tipo
UserRoleque el usuario tiene acceso a la base de datoscorpdb1(mediante el valordatastoreId = 1). Solo se permite ejecutar la operación en esta base de datos.
5. Conclusión
El sistema descrito ofrece un enfoque minimalista y escalable para gestionar el acceso a entidades y operaciones en un entorno SaaS. Al combinar un control de acceso granular, roles jerárquicos y la capacidad de gestionar múltiples bases de datos, se asegura que el sistema sea flexible y seguro, sin complicar innecesariamente la lógica de administración. Esto permite que tanto administradores como usuarios interactúen con el sistema de manera intuitiva, asegurando un alto nivel de control y gobernanza sobre los datos.
6. Anexos: Esquema GraphQL
type Entity { id: ID! name: String! inheritsAccess: Boolean! } type Operation { id: ID! entityId: ID! operationName: String! } type EntityInherit { id: ID! entityId: ID! inheritType: String! } type UserStore { id: ID! storeId: ID! userId: ID! } enum RoleType { GUEST OPERATOR SUPERVISOR DIRECTOR } type Role { id: ID! name: String! roleType: RoleType! } type RoleOperation { id: ID! roleId: ID! operationId: ID! } type UserRole { id: ID! userId: ID! roleId: ID! datastoreId: ID! } type Datastore { id: ID! name: String! }name: String! }