Библиотеки
Permissions

Permissions

Разграничения прав доступа к элементам пользовательского интерфейса

В сложных веб-приложениях часто бывает необходимо ограничивать доступ к кнопкам, полям ввода или даже целым маршрутам определённым группам пользователей. К примеру, только администраторы могут иметь доступ к разделу настроек приложения.

Библиотека @diasoft/qpalette-permissions создана для упрощения работы с ограничениями доступа.

Установка

Установите библиотеку

npm i @diasoft/qpalette-permissions

Выполните настройку модуля для режимов RBAC или ABAC

⚠️

Начиная с версии 6.5.5 флаг enable не требуется. Запрос прав в модели ABAC или RBAC выполняется только тогда, когда это реально требуется (в меню, в коде приложения).

⚠️

Начиная с версии 7.2.17 флаг enable снова в строю.

⚠️

Обратите внимание: если флаг enable выставлен в false или не задан, проверка прав выполняться не будет! В этом случае считается, что у пользователя есть все права, все пайпы возвращают true.

Выполните настройку базового URL-адреса с доступом к API QAccessPolicyUI через конфигурацию для permissions:

{
  "permissions": {
    "enable": true
  }
}

Подключите модуль QPermissionModule

Подключите модуль QPermissionModule с использованием forRoot() в AppModule продукта:

⚠️

Начиная с версии 6.5.5 флаги, передаваемые вторым аргументом в метод QPermissionsModule.forRoot ({ rbac: true/false, abac: true/false }), более не используются. Запрос прав в модели ABAC или RBAC выполняется только тогда, когда это реально требуется (в меню, в коде приложения).

import {QPermissionsModule} from '@diasoft/qpalette-permissions';
import {QCoreService} from "@diasoft/qpalette-core";
import {NgModule} from "@angular/core";
 
@NgModule({
    imports: [
        //...
        QPermissionsModule.forRoot(QCoreService.config?.permissions), // Если нужен режим RBAC
        QPermissionsModule.forRoot(QCoreService.config?.permissions, { rbac: false, abac: true }), // Если нужен режим ABAC
        QPermissionsModule.forRoot(QCoreService.config?.permissions, { rbac: true, abac: true }) // Если нужны оба режима
    ]
})
export class AppModule {
    //...
}

Также необходимо подключить модуль QPermissionModule (без использования forRoot()) в модуле, внутри которого планируется использовать пайпы из @diasoft/qpalette-permissions:

import {QPermissionsModule} from '@diasoft/qpalette-permissions';
import {NgModule} from "@angular/core";
 
@NgModule({
  imports: [
    //...
    QPermissionsModule
  ]
})
export class FeatureModule {
    //...
}

Всё готово. Осталось лишь разобраться с возможностями модуля.

Разграничение прав по атрибутам пользователя

Для разграничения доступа к элементам UI на основе атрибутов пользователя необходимо использовать пайп qOnlyFor. В основе своей работы он использует объект пользователя из QAuthService.getUserInfo(), интерфейс которого выглядит так:

interface QUserInfo {
  userName?: string;
  fullName?: string;
  roles?: string[];
  id?: string;
}

При использовании qOnlyFor необходимо первым параметром передать значение (или массив значений) проверяемого атрибута пользователя. Вторым параметром передаётся название этого атрибута (по умолчанию roles). Например:

<label>
  <input *ngIf="`admreference` | qOnlyFor">
  Только пользователь с ролью admreference увидит этот элемент
</label>
 
<label>
  <input [disabled]="[`admreference`, `guest`] | qOnlyFor: `roles`">
  Только пользователи, принадлежащие ролям admreference или guest, увидят этот элемент
</label>
 
<label>
  <input [disabled]="`guest` | qOnlyFor: `userName`">
  Только пользователь guest увидит этот элемент
</label>

Разграничение прав по модели ABAC или RBAC

Для проверки прав доступа в модели ABAC или RBAC модуль предоставляет следующий набор пайпов:

  • qCan
  • qCanRead,
  • qCanWrite,
  • qCanCreate
  • qCanUpdate,
  • qCanDelete.

Пайп qCan первым аргументом принимает либо имя сервиса (в случае использования режима RBAC), либо массив [имя объекта, имя сервиса] (в случае использования режима ABAC). Вторым аргументом принимается действие. Если пользователь может выполнять это действие над указанным объектом, пайп возвращает true.

В пайпы qCan* передается только первый аргумент аналогично qCan.

Примеры использования смотрите в следующих разделах: RBAC и ABAC.

RBAC

⚠️

Начиная с версии 6.5.5 этот раздел считается устаревшим. Логика работы пайпов, описанная ниже, будет работать только для сервисов QAccessPolicy, установленных на стенде до апреля 2023 года! Для сервисов, установленных после этой даты, логика, описанная ниже, работать не будет! Ждите выпуска QPalette с поддержкой новой версии QAccessPolicy.

Если в продукте включен режим RBAC (см. раздел Установка), то будут доступны функции, описанные в этом разделе. В данном режиме проверяется пара: URL сервиса и действие, доступные текущему пользователю.

Инициализация

Перед проверкой прав в данном режиме необходимо инициализировать сервис QPermissionsRbacService, подписавшись на его метод init().

Например, можно выполнить инициализацию непосредственно в том компоненте, где нужна проверка прав:

<ng-container *ngIf="rightsInitialized$ | async">
    <div *ngIf="'/mdpaudit/**' | qCanRead">
        Этот элемент будет отображён только в том случае, если пользователь имеет права на ЧТЕНИЕ объекта /mdpaudit/**
    </div>
</ng-container>

Но если в проекте часто делаются проверки прав, то целесообразнее выполнить инициализацию единожды при инициализации главного компонента приложения:

<main *ngIf="rightsInitialized$ | async; else loading">
    <router-outlet></router-outlet>
</main>
 
<ng-template #loading>
    <q-spinner [fullScreen]="true"></q-spinner>
</ng-template>

Существует несколько способов ограничения доступа к элементам UI.

Сокрытие элементов

<div *ngIf="'/mdpaudit/**' | qCanRead">
  Этот элемент будет отображён только в том случае, если пользователь имеет права на ЧТЕНИЕ объекта /mdpaudit/**
</div>
 
<div *ngIf="'/mdpaudit/**' | qCan: 'read'">
  Идентично первому примеру
</div>

В коде выше приведено 4 примера, позволяющих ограничивать видимость элемента для текущего пользователя. Если пользователь имеет право на доступ к определённому объекту (в данном случае, /mdpaudit/**), блок будет отображён.

Отключение возможности взаимодействия с элементом

<label>
  <input [disabled]="'/mdpaudit/**' | qCanRead">
  Пользователь сможет взаимодействовать с этим элементом только в том случае, если он имеет право на ЧТЕНИЕ объекта /mdpaudit/**
</label>
 
<label>
  <input [disabled]="'/mdpaudit/**' | qCan: 'read'">
  Идентично первому примеру
</label>

Примеры выше показывают возможность ограничения взаимодействия с элементами. Директивы могут быть использованы для изменения значений атрибутов disabled, readonly, style, и других.

Запрет вызова метода класса

import {QCanPipe} from '@diasoft/qpalette-permissions';
 
class SomeComponent {
    constructor(private readonly can: QCanPipe) {}
 
    myMethod(): void {
        // Если текущий пользователь имеет прав на запись объекта /mdpaudit/**
        if (this.can.transform('/mdpaudit/**', 'write')) {
            // Выполнить какое-то действие...
        }
    }
}

В примере выше показан способ запрета вызова метода в случае, когда текущий пользователь не имеет прав на выполнение этой функции.

Запрет активации маршрута

Что если необходимо ограничить доступ к маршруту? Angular для этого предлагает использовать Guards (opens in a new tab).

Разберём на примере.

Первое, что необходимо сделать — сгенерировать Guard:

ng generate guard router-guard

Должен создаться класс. Подключим к нему пайп qCan и реализуем метод canActivate:

import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree} from '@angular/router';
import {Observable} from 'rxjs';
import {QCanPipe} from '@diasoft/qpalette-permissions';
 
@Injectable()
export class RouterGuard implements CanActivate {
  constructor(private readonly can: QCanPipe) {}
 
  canActivate(
      route: ActivatedRouteSnapshot,
      state: RouterStateSnapshot
  ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
 
    const canNavigate = this.can.transform('/mdpaudit/**', 'read');
 
    if (!canNavigate) {
      alert('У вас нет доступа к этому ресурсу');
    }
 
    return canNavigate;
  }
 
}

Подключим его к необходимому модулю и установим его на необходимый маршрут:

@NgModule({
  imports: [
    RouterModule.forChild([
      {
        path: '',
        component: SomeComponent,
        canActivate: [RouterGuard]
      }
    ]),
    QPermissionsModule
  ],
  providers: [RouterGuard]
})
export class FeatureModule {
    //...
}

Таким образом, если текущий пользователь не будет иметь доступ на чтение определённого объекта, он увидит уведомление.

ABAC

Если в продукте включен режим ABAC (см. раздел Установка), то будут доступны все функции, описанные в разделе RBAC:

  • сокрытие элемента;
  • отключение возможности взаимодействия с элементом;
  • запрет вызова метода;
  • запрет активации маршрута.

Однако есть отличия:

  • Перед использованием пайпов необходимо запросить права на объекты, к которым планируется проверять доступ с помощью пайпов – для этого есть два способа:
    • Запрос прав внутри шаблона с помощью пайпа qCanInit (см. примеры ниже).
    • Запрос прав с помощью сервиса QPermissionsAbacService (см. примеры ниже).
  • В качестве объекта, к которому проверяется доступ, необходимо указать массив, где первый элемент – это системное наименование бизнес-объекта, а второй – системное наименование сервиса. Например: ['ACCOUNT', 'mdpaccount'] | qCanRead.

Запрос прав внутри шаблона

В шаблоне компонента используйте qCanInit перед тем, как использовать пайпы проверки прав:

<ng-container *ngIf="[['Publish', 'qpdmpersonaldesk'], ['PageWidgetRelations', 'qpdmpersonaldesk']] | qCanInit | async">
    <p>['Publish', 'qpdmpersonaldesk'] | qCanDelete => {{['Publish', 'qpdmpersonaldesk'] | qCanDelete}}</p>
    <p>['PageWidgetRelations', 'qpdmpersonaldesk'] | qCan: 'update' => {{['PageWidgetRelations', 'qpdmpersonaldesk'] | qCan: 'update'}}</p>
    <p>['Publish', 'qpdmpersonaldesk'] | qCan: 'cancel' => {{['Publish', 'qpdmpersonaldesk'] | qCan: 'cancel'}}</p>
</ng-container>

Запрос прав с помощью сервиса

В шаблоне компонента используйте async пайп для проверки инициализации прав:

<ng-container *ngIf="rightsInitialized$ | async">
    <p>['Publish', 'qpdmpersonaldesk'] | qCanDelete => {{['Publish', 'qpdmpersonaldesk'] | qCanDelete}}</p>
    <p>['PageWidgetRelations', 'qpdmpersonaldesk'] | qCan: 'update' => {{['PageWidgetRelations', 'qpdmpersonaldesk'] | qCan: 'update'}}</p>
</ng-container>

В коде компонента используйте метод init сервиса QPermissionsAbacService библиотеки @diasoft/qpalette-permissions:

import { QPermissionsAbacService } from '@diasoft/qpalette-permissions';
 
export class AbacComponent implements OnInit {
    rightsInitialized$ = this.abacService.init([
        ['Publish', 'qpdmpersonaldesk'],
        ['PageWidgetRelations', 'qpdmpersonaldesk'],
    ]);
 
    constructor(private readonly abacService: QPermissionsAbacService) {}
}