Angular - Как да проверите разрешението въз основа на роля и субекти

В днешно време е много често да се намирате в състояние на автентификация на сгради и упълномощаване във вашето приложение, копането в интернет за библиотеки и техники за оторизация е лесно винаги да намерите решения, които предлагат само разрешение на базата на роли, което не позволява само достъп до страница. почти често се случва да се нуждаете от нещо друго, разрешение на субект-държава.

За TL; DR тук е демонстрацията и кодът, използван в демонстрацията.

Снимки кредити

Какво по дяволите е разрешение на субект-държава?

Е, предполагам, че нямаше нищо по-хубаво в главата ми да го назовавам, докато пиша този пост. Въпреки това повече или по-малко това, което се опитвам да кажа, е свързано със ситуациите, когато трябва да предоставите или да не предоставите на текущия потребител възможността да прилага конкретни действия, ако текущото състояние на субекта разчита на състояние X и различна способност, когато образуванието състояние разчита на състояние Y и най-лошото стана, когато всичко това трябва да бъде на един и същ екран или дори в същия компонент. Всяка библиотека с разрешение за основа на роли ще ви спести за този проблем.
Уморен от четене на различни статии с ролева страница, предотвратяващи решения, аз започнах да мисля и кодирам, без да имам нищо наум все още, и изведнъж беше там, намерих просто, ясно и много гъвкаво решение за решаване на този вид проблеми и неговото съставено от четири компоненти.

  • Услуга за предоставяне на текущата потребителска информация (това, което наистина има значение, е начин за предоставяне на потребителските роли, към които текущият потребител принадлежи или им е предоставено да играе в приложението)
  • Една карта с разрешения за работа (JSON файл)
  • Услуга за разрешения за действителна проверка на разрешението
  • Директива за консумация на услугата за проверка на разрешението

Стъпка 1: Вземете текущи потребителски роли

Внедрете услуга за извличане от страна на сървъра (за първи път) или от сесия или бисквитки, както предпочитате за последващо използване на нея, важното тук е да предоставите на потребителя списък с способности (роли).

// Пример
{
  име: „Джон Доу“,
  имейл: „[email protected]“,
  роли: ['продавач', 'продавач_манеджър'], <- това е важното
  accessToken: „alotofletersrepresentinganaccesstokenhahahaaa!“
  ... още неща
}

В моя случай повече или по-малко изглежда така:

// внос тук ...
@Injectable ()
експорт клас CurrentUserService {
  частен потребителSubject = нов ReplaySubject  (1);
  private hasUser = false;

  конструктор (частни потребителиApi: UserApi) {
  }

  обществен getUser (): наблюдаем <потребител> {
    if (! this.hasUser) {
      this.fetchUser ();
    }
    върнете този.userSubject.asObservable ();
  }

  public fetchUser (): void {
      this.usersApi.getCurrent () // <== http обаждане за получаване на потребителInfo
        .отпишете се (user => {
          // потребителят трябва да съдържа роли е предоставен
          this.hasUser = true;
          this.userSubject.next (употреба);
          this.userSubject.complete ();
        }, (грешка) => {
          this.hasUser = невярно;
          this.userSubject.error (грешка);
        });
  }

}

Втора стъпка: Създайте картата на работния си процес и разрешенията

Това не е нищо друго, освен картографирането на това, което можем да направим и кой можем да направим, като изградим дърво с различните образувания и техните собствени държави. например, нека си представим следния процес на продажби; Приложението ни може да има няколко типа роли. За нашия пример нека да картографираме ролите на ПРОДАВАЧ, решения ARCHITECT и CLIENT.

Най-малко да поговорим за процеса:

  • Първо ПРОДАВАЧът поставя възможност за продажба чрез изпълнение на действието Добавяне на нова възможност, така че състоянието на възможността вероятно е създадено
  • В този момент КЛИЕНТЪТ и ПРОДАВАЧът могат да добавят изискванията към възможността, така че и двамата да прилагат действието Добавяне на изисквания, когато са поставени изискванията, тогава състоянието на възможността може да се промени на изпратено
  • След като изискванията са поставени, ARCHITECT може да иска да добави решение, така че той се нуждае от действие: Качване на решение и вероятно състоянието може да се промени в разрешено
  • След като решението бъде предоставено, КЛИЕНТът може да иска да приеме, така че той се нуждае от действие за одобряване на решение и състоянието ще се промени на одобрено решение

Тук ще намалим процеса, в противен случай това ще нарасне твърде много и не е така за това четене. И така въз основа на този процес картографирането и приемането, че възможността образувание има поле, което проследява състоянието, нашият работен процес ще изглежда така:

{
  "възможност": {
    "addOpportunity": {"разрешени роли": ["ПРОДАВАЧ"]}},
    "създаден": {
      "addRequirement": {"разрешени роли": ["ПРОДАВАЧ", "КЛИЕНТ"]}
    }
    „изпратено“: {
      "addSolution": {"разрешени роли": ["ARCHITECT"]}
    }
    "решен": {
      "approveSolution": {"разрешени роли": ["CLIENT"]}
    }
}

Стъпка 3: Услугата за разрешаване на проверка за консумация на работния процес и картата на разрешенията

Сега, след като процесът е картографиран в карта на работния процес и разрешенията, трябва да създадем услуга, за да я консумираме и да проверим дали потребителят е упълномощен или не и може да изглежда така:

// Импортиране на декларации тук
// между другото в angular-cli можем да поставим JSON файла във
// enviroment.ts
@Injectable ()
експорт клас WorkflowEvents {
  частно само за четене WORKFLOW_EVENTS = среда ['работен процес'];
  частни потребителски роли: Задайте ;
  // помниш ли стъпката 1? той се използва тук
  конструктор (частен currentUserService: CurrentUserService) {
  }
  // връща булева наблюдаема
  обществена проверкаАвторизация (път: който и да е): наблюдаем  {
    // зареждаме ролите само веднъж
   ако (! this.userRoles) {
      върнете това.currentUserService.getUser ()
        .map (currentUser => currentUser.roles)
        .do (роли => {
          const role = role.map (role => role.name);
          this.userRoles = нов комплект (роли);
        })
        .map (роли => this.doCheckAuthorization (path));
    }
    върнете Observable.of (this.doCheckAuthorization (path));
  }

  private doCheckAuthorization (path: string []): boolean {
    ако (path.length) {
      const entry = this.findEntry (this.WORKFLOW_EVENTS, path);
      ако (влизане и вписване [„позволени роли“]
             && this.userRoles.size) {
        връщане entry.permittedRoles
        .some (позволеноRole => this.userRoles.has (разрешенRole));
      }
      върнете невярно;
    }
    върнете невярно;
  }
/ **
 * Рекурсивно намерете запис на работния процес на картата въз основа на низовете на пътя
 * /
частни findEntry (currentObject: произволен, ключове: string [], index = 0) {
    const ключ = клавиши [индекс];
    ако (currentObject [ключ] & & индекс 

По същество това, което прави е да изглежда валиден запис и да проверява дали текущите потребителски роли са включени в разрешените Роли.

Файл 4: Директивата

След като имаме текущите потребителски роли, дърво на разрешения на работния поток и услуга за проверка на разрешение за текущи ролеве на потребителя, сега ни е необходим начин да го оживим и най-добрият начин в ъглова 2/4 е директива. Първоначално директивата, която написах, беше директива за атрибути, която превключваше атрибута на дисплея на CSS, но това може да доведе до проблеми с производителността, тъй като децедентните компоненти все още се зареждат в DOM, така че е по-добре да използвате структурни директиви (Благодаря на моя колега Петьо Чолаков за този добър улов , вижте разликата тук), тъй като можем да модифицираме DOM на целевия елемент и това е спад, така че да избегнем зареждането на неизползвани елементи.

@Directive ({
  селектор: „[appCanAccess]“
})
клас на износ CanAccessDirective внедрява OnInit, OnDestroy {
  @ Input ('appCanAccess') appCanAccess: string | низ [];
  частно разрешение $: Абонамент;

  конструктор (частен templateRef: TemplateRef ,
              частен изгледКонтейнер: ViewContainerRef,
              private workflowEvents: WorkflowEvents) {
  }

  ngOnInit (): void {
    this.applyPermission ();
  }

  private applyPermission (): void {
    this.permission $ = this.workflowEvents
                        .checkAuthorization (this.appCanAccess)
      . Абонирайте се (разрешено => {
        ако (разрешен) {
          this.viewContainer.createEmbeddedView (this.templateRef);
        } else {
          this.viewContainer.clear ();
        }
      });
  }

  ngOnDestroy (): void {
    this.permission $ .unsubscribe ();
  }

}

Накрая резултатната работа

Сега всичко, от което имаме нужда, е време да ги приведем в действие. така че в нашия HTML шаблон, единственото, което трябва да направим, е нещо като следния код

Да приемем, че имаме примерен компонент, който включва обекта на възможността:

@Компонент({
  селектор: „панел с ценообразуване“,
  шаблон: `
<Същи проби компонент>
    
<добавяне-изискване-компонент * appCanAccess = "['възможност", prilikuObject.state,' addRequirement '] "> 
<добавка-решение-компонент * appCanAccess = "['възможност", prilikuObject.state,' addSolution '] "> 
<одобри-решение-компонент * appCanAccess = "['възможност", prilikuObject.state,' одобри Решение '] "> 
 `
})
експортният клас SampleComponent внедрява OnInit {

  @Input () възможностObject: всяка;
  конструктор () {
  }

  ngOnInit () {
  }
}

И това е всичко, можем да имаме прост компонент, представящ поведение в зависимост от ролите на потребителя и състоянието на субекта.

Благодаря на моите колеги Петьо и Говинд за уловката и критиците към лошото ми кодиране, бихме могли да намерим това решение, което може да работи перфектно за нашите нужди, надявам се и това да ви помогне.

ЮНИ 2018 г., малка извадка работеща => https://emsedano.github.io/ng-entity-state/