
Uno de los mayores retos al construir aplicaciones Angular es el control y la gestión de permisos de usuario.
A menudo debemos mostrar componentes o permitir acciones según el tipo de usuario.
En este artículo se explica una solución propuesta:
Glosario:
- Definir roles y permisos
- Construir clases para gestionar los permisos de cada usuario
- Servicio global para la gestión de permisos
- Directiva para usar el gestor de permisos con facilidad
- Demo en StackBlitz_
1. Definir roles y permisos
Lo primero es definir los tipos de roles que manejará la aplicación:
export enum Role {
SUPERUSER = 'su',
ADMIN = 'admin',
USER = 'user',
UNKNOWN = 'unknown'
}
El siguiente paso es definir los distintos permisos que queremos conceder:
export enum PermissionType {
CREATE = 'CREATE',
READ = 'READ',
UPDATE = 'UPDATE',
DELETE = 'DELETE',
OTHER = 'OTHER'
}
2. Clases para gestionar los permisos de cada usuario
Una vez definida la estructura de roles y permisos, implementamos la clase base de permisos para cada tipo de usuario.
import { PermissionType } from '../permission-type';
export abstract class PermissionBase {
public permissions: PermissionType[];
constructor() {}
}
El siguiente paso es extender la clase base para cada tipo de usuario.
Por ejemplo, para el rol superusuario, al que se le conceden las acciones CREATE, READ, UPDATE, DELETE y OTHER:
import { PermissionType } from '../permission-type';
import { PermissionBase } from './base.permissions';
export class SuperuserPermission extends PermissionBase {
constructor() {
super();
this.permissions = [
PermissionType.CREATE, PermissionType.READ,
PermissionType.UPDATE, PermissionType.DELETE,
PermissionType.OTHER
];
}
}
Tras definir los permisos de cada tipo de usuario, construimos una factoría que crea la instancia de la clase de permisos. Con el patrón singleton limitamos a una sola instancia para evitar múltiples manejadores.
import { PermissionBase } from './base.permissions';
import { Role } from '../role';
import { SuperuserPermission } from './superuser.permissions';
import { AdminPermission } from './admin.permissions';
import { UserPermission } from './user.permissions';
import { UnknownPermission } from './unknown.permissions';
export class PermissionsFactory {
public static instance: PermissionBase;
private constructor() {}
public static getInstance() {
if (this.instance) {
return this.instance;
} else {
const role = localStorage.getItem('role');
switch(role) {
case Role.SUPERUSER:
this.instance = new SuperuserPermission();
break;
case Role.UNKNOWN:
this.instance = new UnknownPermission();
break;
. . .
default:
this.instance = new UnknownPermission();
break;
}
}
}
}
3. Servicio para la gestión de permisos
Por ahora tenemos los permisos por usuario y la factoría para obtener la instancia. Necesitamos una forma accesible de saber si un usuario puede realizar una acción concreta.
Para ello implementamos un servicio global que use la instancia de permisos. Así será accesible desde cualquier componente o directiva.
import { Injectable } from '@angular/core';
import { PermissionType } from './permission-type';
import { Role } from './role';
import { EnumValues } from 'enum-values';
import { PermissionBase } from './permissions/base.permissions';
import { PermissionsFactory } from './permissions/factory.permissions';
@Injectable(
provideIn: 'root'
)
export class PermissionManagerService {
private permissions: PermissionBase;
constructor() { }
isGranted (permission: PermissionType) {
const permissions = PermissionsFactory.getInstance().permissions;
for (let perm of permissions) {
if (perm === permission){
return true;
}
}
return false;
}
authAs (role: Role) {
localStorage.setItem('role',
(role === null)
? Role.UNKNOWN
: role
);
this.permissions = PermissionsFactory.getInstance();
}
}
- El método
isGrantedrecibe un tipo de permiso, recorre los permisos del usuario y devuelvetruesi lo encuentra. - El método
authAsexige una instancia del gestor de permisos.
4. Directiva para facilitar el uso del gestor de permisos
PermissionManagerService ya se puede consumir con facilidad. Aún así, podemos simplificar su uso en plantillas con una directiva:
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
import { PermissionType } from './permission-type';
import { PermissionManagerService } from './permission-manager.service';
@Directive({
selector: '[appIsGranted]'
})
export class IsGrantedDirective {
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
private permissionManagerS: PermissionManagerService
) { }
@Input() set appIsGranted(permission: PermissionType) {
this.isGranted(permission);
}
private isGranted(permission: PermissionType) {
if (this.permissionManagerS.isGranted(permission)) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}
Nota: la directiva appIsGranted es una variante de la implementación de ngIf.
Uso de la directiva:
<div appIsGranted="'CREATE'">
// This block will only be shown to granted users
</div>
<my-component appIsGranted="'DELETE'">
// This component will only be shown to granted users
</my-component>
5. Demo en StackBlitz
rjlopez-angular-permissions-part1 - StackBlitz
Nota: no es una implementación para producción; incluye código y datos simulados.
Consulta Angular permissions based on roles | Part 2. Permissions for multiple resources
Publicado originalmente en dev.to.