Úhlová - Jak zkontrolovat autorizaci na základě role a stavů entit

V dnešní době je velmi běžné, že vás najdete ve stavu ověřování a autorizace budovy ve vaší aplikaci. Kopání na webu pro autorizační knihovny a techniky je vždy snadné najít řešení, která nabízejí pouze autorizaci na základě rolí, která zabraňuje pouze přístupu na stránku, nicméně téměř často se stane, že potřebujete něco jiného, ​​autorizaci stavu entity.

Pro TL; DR je zde demo a kód použitý v demo.

Obrazové kredity

Co to sakra je autorizace státu?

No, myslím, že při psaní tohoto příspěvku v mé hlavě nebylo nic lepšího. To, co se snažím říci, však více či méně souvisí se situacemi, kdy musíte současnému uživateli udělit nebo neudělit schopnost použít konkrétní akci, pokud se aktuální stav entity spoléhá na stav X a jinou schopnost, když se entita stav spoléhá na stav Y a stalo se to nejhorší, když by to všechno mělo být na stejné obrazovce nebo dokonce ve stejné součásti. Všechny knihovny s autorizací založenou na rolích vás za tento problém uloží.
Už vás nebaví číst různé články s řešením pro prevenci stránek založených na rolích, začal jsem myslet a kódovat, aniž bych cokoli myslel, a najednou tam bylo, našel jsem jednoduché, přímé a velmi flexibilní řešení pro řešení tohoto druhu problémů a jeho složení ze čtyř komponenty.

  • Služba poskytující aktuální informace o uživateli (na čem záleží, je způsob, jak poskytnout uživatelům role, které aktuální uživatel patří nebo bylo uděleno pro hraní v aplikaci)
  • Jedna mapa oprávnění pro pracovní postup (soubor JSON)
  • Služba oprávnění ke skutečné kontrole autorizace
  • Směrnice pro spotřebování kontroly autorizační služby

Krok 1: získejte aktuální uživatelské role

Implementujte službu pro načtení ze strany serveru (poprvé) nebo z relace nebo cookies, jak dáváte přednost jejich následnému použití, důležité je zde poskytnout seznam uživatelů schopností (rolí).

// Příklad
{
  jméno: 'John Doe',
  email: '[email protected]',
  role: ['prodejce', 'seller_manager'], <- na tom záleží
  accessToken: 'alotofletersrepresentinganaccesstokenhahahaaa!'
  ... další věci
}

V mém případě to víceméně vypadá:

// importuje se zde ...
@Injectable ()
exportní třída CurrentUserService {
  private userSubject = new ReplaySubject  (1);
  private hasUser = false;

  constructor (private usersApi: UserApi) {
  }

  public getUser (): Pozorovatelné  {
    if (! this.hasUser) {
      this.fetchUser ();
    }
    return this.userSubject.asObservable ();
  }

  public fetchUser (): void {
      this.usersApi.getCurrent () // <== http volání pro načtení userInfo
        .subscribe (user => {
          // uživatel by měl obsahovat role byl udělen
          this.hasUser = true;
          this.userSubject.next (user);
          this.userSubject.complete ();
        }, (chyba) => {
          this.hasUser = false;
          this.userSubject.error (chyba);
        });
  }

}

Druhý krok: Vytvořte mapu pracovního postupu a oprávnění

Toto není nic jiného než zmapování toho, co můžeme udělat a kdo můžeme udělat vytvořením stromu s různými entitami a jejich vlastními stavy. Představme si například následující prodejní proces; Naše aplikace může mít několik typů rolí. Pro náš příklad pojďme zmapovat role na prodejce, řešení ARCHITECT a CLIENT.

Nejméně mluvit o procesu:

  • Nejprve prodejce umístí prodejní příležitost provedením akce Přidat novou příležitost, aby se pravděpodobně vytvořil stav příležitosti
  • V tomto okamžiku může KLIENT A PRODEJCE přidat požadavky k příležitosti, takže oba mohou aplikovat akci Přidat požadavky, když jsou požadavky uloženy, pak se stav příležitosti může změnit na odeslané
  • Po zadání požadavků může ARCHITECT chtít přidat řešení, takže potřebuje akci: Upload řešení a pravděpodobně se stav může změnit na vyřešen
  • Jakmile bude řešení poskytnuto, KLIENT možná bude chtít přijmout, takže potřebuje akci ke schválení řešení a stav by se změnil na solution_approved

Chystáme se zde tento proces přerušit, jinak by to příliš narostlo a toto čtení tomu tak není. Takže na základě tohoto procesu mapování a za předpokladu, že entita příležitosti má pole, které sleduje stav, by náš pracovní postup vypadal takto:

{
  "příležitost": {
    "addOpportunity": {"enabledRoles": ["SELLER"]}},
    "created": {
      "addRequirement": {"enabledRoles": ["SELLER", "CLIENT"]}}
    },
    "zadáno": {
      "addSolution": {"enabledRoles": ["ARCHITECT"]}}
    },
    "vyřešeno": {
      "approveSolution": {"enabledRoles": ["CLIENT"]}
    }
}

Krok 3: Služba autorizace šeku pro zobrazení mapy worflow a oprávnění

Nyní, když máme proces mapován do mapy pracovního postupu a oprávnění, musíme vytvořit službu, která ji spotřebuje a zkontrolujeme, zda je uživatel oprávněn či nikoli a mohl by vypadat takto:

// importujte zde výpisy
// mimochodem v angular-cli můžeme vložit soubor JSON do
// enviroment.ts
@Injectable ()
exportovat třídu WorkflowEvents {
  private readonly WORKFLOW_EVENTS = environment ['workflow'];
  private userRoles: Set ;
  // pamatujete si krok 1? používá se zde
  konstruktor (soukromý proudUserService: CurrentUserService) {
  }
  // vrací booleovský pozorovatelný
  veřejná kontrolaAutorizace (cesta: libovolná): Pozorovatelné  {
    // načítáme role pouze jednou
   if (! this.userRoles) {
      vrátit this.currentUserService.getUser ()
        .map (currentUser => currentUser.roles)
        .do (role => {
          const roles = roles.map (role => role.name);
          this.userRoles = nová sada (role);
        })
        .map (roles => this.doCheckAuthorization (path));
    }
    return Observable.of (this.doCheckAuthorization (path));
  }

  private doCheckAuthorization (cesta: řetězec []): boolean {
    if (path.length) {
      const entry = this.findEntry (this.WORKFLOW_EVENTS, cesta);
      if (entry && entry ['enabledRoles'])
             && this.userRoles.size) {
        návrat entry.permissRoles
        .some (enabledRole => this.userRoles.has (enabledRole));
      }
      návrat false;
    }
    návrat false;
  }
/ **
 * Rekurzivně najděte položku workflow-map na základě řetězců cest
 * /
private findEntry (currentObject: any, keys: string [], index = 0) {
    const key = keys [index];
    if (currentObject [key] && index 

V zásadě jde o to, aby se podíval na platný záznam a zkontroloval, zda aktuální uživatelské role jsou zahrnuty do povolených rolí.

Soubor 4: Směrnice

Jakmile máme aktuální uživatelské role, strom oprávnění pracovního postupu a službu pro ověření autorizace aktuálních uživatelských rolí, nyní potřebujeme způsob, jak to oživit, a nejlepším způsobem v úhlové 2/4 je směrnice. Zpočátku byla směrnice, kterou jsem napsal, direktiva atributů, která přepínala atribut display CSS, ale mohlo by to vést k problémům s výkonem, protože sestupné komponenty se stále načítají do DOM, takže je lepší používat strukturální směrnice (díky mému kolegovi Petyo Cholakovovi za tento dobrý úlovek) , viz rozdíl zde), protože můžeme modifikovat DOM cílového prvku a jeho sestupné prvky, abychom se vyhnuli načtení nepoužitých prvků.

@Směrnice({
  selektor: '[appCanAccess]'
})
exportní třída CanAccessDirective implementuje OnInit, OnDestroy {
  @Input ('appCanAccess') appCanAccess: string | řetězec [];
  soukromé povolení $: Předplatné;

  konstruktor (soukromá templateRef: TemplateRef ,
              soukromý viewContainer: ViewContainerRef,
              soukromý workflowEvents: WorkflowEvents) {
  }

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

  private applyPermission (): void {
    this.permission $ = this.workflowEvents
                        .checkAuthorization (this.appCanAccess)
      .subscribe (autorizovaný => {
        if (autorizovaný) {
          this.viewContainer.createEmbeddedView (this.templateRef);
        } jinde {
          this.viewContainer.clear ();
        }
      });
  }

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

}

Nakonec výsledná práce

Nyní potřebujeme čas, abychom je uvedli do činnosti. takže v naší šabloně HTML je jediné, co musíme udělat, něco jako následující kód

Předpokládejme, že máme vzorovou komponentu, která obsahuje objekt příležitosti:

@Součástka({
  selektor: 'sp-price-panel',
  template: `

    
 
 
 
 `
})
exportní třída SampleComponent implementuje OnInit {

  @Input () příležitostObjekt: libovolný;
  konstruktor () {
  }

  ngOnInit () {
  }
}

A to je vše, můžeme mít jednoduchou součást prezentující chování v závislosti na uživatelských rolích a stavu entity.

Díky mým kolegům Petyo a Govind za úlovek a kritiky mého špatného kódování jsme našli toto řešení, které může dokonale vyhovovat našim potřebám, doufám, že vám to také pomůže.

ČERVEN 2018, malý vzorek pracuje => https://emsedano.github.io/ng-entity-state/