So passen Sie UX / UI unter Berechtigungen an

Viele Projekte haben Authentifizierungsprozesse (bis zu dem einen oder anderen Grad). Viele Best Practices wurden in allen bekannten Technologien usw. geschrieben. usw.


Aber der Benutzer hat sich angemeldet und? Immerhin kann er nicht alles machen. Wie man feststellt, was er sehen kann und was nicht. Welche Schaltflächen es zu drücken hat, welche können ändern, was erstellt oder gelöscht werden soll.


In diesem Artikel möchte ich einen Ansatz zur Lösung dieser Probleme in Webanwendungen betrachten.


Bild


Eine echte / effektive Autorisierung kann zunächst nur auf dem Server erfolgen. Im Front End können wir nur die Benutzeroberfläche verbessern (hmm, die russische Sprache ist mächtig, aber nicht immer ...), es ist auch die Benutzererfahrung jenseits von UX. Wir können die Schaltflächen ausblenden, auf die der Benutzer nicht klicken darf, oder ihn daran hindern, auf die Seite zuzugreifen, oder eine Meldung anzeigen, dass er keine Rechte an dieser oder jener Aktion hat.


Und hier stellt sich die Frage, wie man es so richtig wie möglich macht.


Beginnen wir mit der Definition des Problems.


Wir haben die Todo App erstellt und sie hat verschiedene Benutzertypen:


USER - kann alle Aufgaben anzeigen und ändern (V setzen und entfernen), aber nicht löschen oder erstellen und keine Statistiken anzeigen.


ADMIN - kann alle Aufgaben anzeigen und neue erstellen, sieht jedoch keine Statistiken.


SUPER_ADMIN - kann alle Aufgaben anzeigen , neue erstellen und löschen, kann auch Statistiken anzeigen .


Aufgabe anzeigenAufgabe erstellenAufgabe aktivieren / deaktivieren (Update)Aufgabe löschenStatistiken anzeigen
USERV.X.V.X.X.
AdminV.V.V.X.X.
SUPER_ADMINV.V.V.V.V.

In einer solchen Situation können wir mit den Rollen leicht auskommen. Die Situation kann sich jedoch stark ändern. Stellen Sie sich vor, es gibt einen Benutzer, der die gleichen Rechte wie ADMIN haben und Aufgaben löschen muss. Oder einfach USER mit der Fähigkeit, Statistiken zu sehen.


Eine einfache Lösung besteht darin, neue Rollen zu erstellen.
Auf großen Systemen mit einer relativ großen Anzahl von Benutzern verlieren wir uns jedoch schnell in einer großen Anzahl von Rollen ... Und hier erinnern wir uns an die Berechtigungen für Benutzerrechte. Zur einfacheren Verwaltung können wir Gruppen mit mehreren Berechtigungen erstellen und diese an den Benutzer anhängen. Es besteht immer die Möglichkeit, einem bestimmten Benutzer eine bestimmte Berechtigung hinzuzufügen.


Ähnliche Lösungen finden sich in vielen großartigen Diensten. AWS, Google Cloud, SalesForce usw.
Ähnliche Lösungen sind bereits in vielen Frameworks implementiert, beispielsweise in Django (Python).


Ich möchte ein Beispiel für die Implementierung für Angular-Anwendungen geben. (Am Beispiel derselben ToDo-App).


Zuerst müssen Sie alle möglichen Berechtigungen ermitteln.


In Funktionen unterteilt


  1. Wir haben Aufgaben und Statistiken.
  2. Wir bestimmen die möglichen Aktionen mit jedem von ihnen.
    • Aufgabe: erstellen, lesen, aktualisieren, löschen
    • Statistik: Lesen (in unserem Beispiel nur Ansicht)
  3. Erstellen Sie eine Karte (MAP) mit Rollen und Berechtigungen

export const permissionsMap = { todos:{ create:'*', read:'*', update:'*', delete:'*' }, stats:'*' } 

Meiner Meinung nach ist dies die beste Option, aber in den meisten Fällen gibt der Server Folgendes zurück:


 export permissions = [ 'todos_create', 'todos_read', 'todos_update', 'todos_delete', 'stas' ] 

Weniger lesbar, aber auch sehr gut.


So sieht unsere Anwendung aus, wenn der Benutzer über alle möglichen Berechtigungen verfügt.


Bild


Beginnen wir mit USER. So sehen die Berechtigungen aus:


 export const USERpermissionsMap = { todos:{ read:'*', update:'*', } } 

1) Der BENUTZER kann keine Statistiken sehen, dh er kann im Prinzip nicht zur Statistikseite gehen.


In solchen Situationen verfügt Angular über Guards, die auf Routenebene registriert sind ( Dokumentation ).


In unserem Fall sieht es so aus:


 const routes: Routes = [ // {...Other routes}, { path: 'stats', component: TodoStatisticsComponent, canActivate: [ PermissionsGuardService ], data: { permission: 'stats' }, } ]; 

Sie sollten auf das Objekt in den Daten achten.


peremissions = 'stats', dies sind die Berechtigungen, über die der Benutzer verfügen muss, um auf diese Seite zugreifen zu können.


Basierend auf den erforderlichen data.permissions und den Berechtigungen, die der PermissionsGuardService-Server uns erteilt hat, wird entschieden, ob USER auf die Seite '/ stats' gehen soll oder nicht.


 @Injectable({ providedIn: 'root' }) export class PermissionsGuardService implements CanActivate { constructor( private store: Store<any>) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean { // Required permission to continue navigation const required = route.data.permission; // User permissions that we got from server const userPerms = this.getPermissions(); // verification const isPermitted = checkPermissions(required, userPerms); if (!isPermitted) { alert('ROUTE GUARD SAYS: \n You don\'t have permissions to see this page'); } return isPermitted; } getPermissions() { return localStorage.get('userPermissions') } } 

Diese Funktion enthält die Entscheidungslogik - Gibt es eine unersetzliche Berechtigung (erforderlich) für alle Berechtigungen des USER (userPerms)?


 export function checkPermissions(required: string, userPerms) { // 1) Separate feature and action const [feature, action] = required.split('_'); // 2) Check if user have any type of access to the feature if (!userPerms.hasOwnProperty(feature)) { return false; } // 3) Check if user have permission for required action if (!userPerms[feature].hasOwnProperty(action)) { return false; } return true; } 

Bild


Und so. Unser BENUTZER kann nicht zur Statistikseite gelangen. Er kann jedoch weiterhin Aufgaben erstellen und löschen.


Damit USER die Aufgabe nicht entfernen kann, reicht es aus, das rote (X) aus der Aufgabenzeile zu entfernen.
Zu diesem Zweck verwenden wir die Strukturrichtlinie .


 <!-- Similar to other Structural Directives in angular (*ngIF, *ngFor..) we have '*' in directive name Input for directive is a required permission to see this btn. --> <button *appPermissions="'todos_delete'" class="delete-button" (click)="removeSingleTodo()"> X </button> 

 @Directive({ selector: '[appPermissions]' }) export class PermissionsDirective { private _required: string; private _viewRef: EmbeddedViewRef<any> | null = null; private _templateRef: TemplateRef<any> | null = null; @Input() set appPermissions(permission: string) { this._required = permission; this._viewRef = null; this.init(); } constructor(private templateRef: TemplateRef<any>, private viewContainerRef: ViewContainerRef) { this._templateRef = templateRef; } init() { const isPermitted = checkPermissions(this._required, this.getPermissions()); if (isPermitted) { this._viewRef = this.viewContainerRef.createEmbeddedView(this.templateRef); } else { console.log('PERMISSIONS DIRECTIVE says \n You don\'t have permissions to see it'); } } getPermissions() { localStorage.get('userPermissions') } } 

Bild


Jetzt sieht USER die Schaltfläche LÖSCHEN nicht, kann aber dennoch neue Aufgaben hinzufügen.


Ein Eingabefeld zu entfernen - wird alle Arten unserer Anwendung verderben. Die richtige Lösung in dieser Situation besteht darin disable Eingabefelder zu disable .


Wir werden Rohr verwenden.


 <!-- as a value `permissions` pipe will get required permissions `permissions` pipe return true or false, that's why we have !('todos_create' | permissions) to set disable=true if pipe returns false --> <input class="centered-block" [disabled]="!('todos_create' | permissions)" placeholder="What needs to be done?" autofocus/> 

 @Pipe({ name: 'permissions' }) export class PermissionsPipe implements PipeTransform { constructor(private store: Store<any>) { } transform(required: any, args?: any): any { const isPermitted = checkPermissions(required, this.getPermissions()); if (isPermitted) { return true; } else { console.log('[PERMISSIONS PIPE] You don\'t have permissions'); return false; } } getPermissions() { return localStorage.get('userPermissions') } } 

Und jetzt kann USER nur Aufgaben sehen und ändern (V / X). Wir haben jedoch noch eine weitere Schaltfläche mit dem Namen "Löschen abgeschlossen".


Angenommen, wir haben die folgenden Anforderungen an den Produktmanager:


  1. Die Schaltfläche "Abgeschlossen löschen" sollte für alle und immer sichtbar sein.
  2. Es sollte auch anklickbar sein.
  3. Wenn USER ohne die entsprechenden Berechtigungen die Schaltfläche drückt, sollte eine Meldung angezeigt werden.

Bauanweisung wird uns nicht helfen, ebenso wie Rohr.
Es ist auch nicht sehr bequem, Berechtigungsprüfungen in Komponentenfunktionen zu schreiben.


Wir müssen lediglich eine Berechtigungsprüfung zwischen dem Klick und der Ausführung der gebundenen Funktion durchführen.


Meiner Meinung nach lohnt es sich, Dekorateure einzusetzen.


 export function Permissions(required) { return (classProto, propertyKey, descriptor) => { const originalFunction = descriptor.value; descriptor.value = function (...args: any[]) { const userPerms = localStorage.get('userPermissions') const isPermitted = checkPermissions(required, userPerms); if (isPermitted) { originalFunction.apply(this, args); } else { alert('you have no permissions \n [PERMISSIONS DECORATOR]'); } }; return descriptor; }; } 

 @Component({ selector: 'app-actions', templateUrl: './actions.component.html', styleUrls: ['./actions.component.css'] }) export class ActionsComponent implements OnInit { @Output() deleteCompleted = new EventEmitter(); constructor() { } @Permissions('todos_delete') public deleteCompleted() { this.deleteCompleted.emit(); } } 

Die Redner verdienen einen separaten Artikel.


Unser Endergebnis:


Bild


Das Ergebnis:


Dieser Ansatz ermöglicht es uns, unsere UX einfach und dynamisch an die Berechtigungen des Benutzers anzupassen.


Und am wichtigsten ist, dass wir dafür nicht alle Komponenten in Services verschieben oder die '* ngIf'-Vorlagen einschlagen müssen.


Der gesamte Antrag ist vollständig .


Ich werde gerne einen Kommentar abgeben.

Source: https://habr.com/ru/post/de439100/


All Articles