Commit 74ce4d02 authored by Merzough Münker's avatar Merzough Münker
Browse files

fix: refactor the authorization package

parent d3001056
Loading
Loading
Loading
Loading
+1 −18
Original line number Diff line number Diff line
import { TestBed } from '@angular/core/testing';
import { AuthorizationDevelopmentControlsComponent } from './authorization-development-controls.component';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { AuthorizationModule } from '../authorization.module';
import { AuthorizationDevelopmentControlsComponent } from './authorization-development-controls.component';

describe(AuthorizationDevelopmentControlsComponent.name, () => {
  beforeEach(() => {
@@ -17,14 +16,6 @@ describe(AuthorizationDevelopmentControlsComponent.name, () => {
    cy.mount(AuthorizationDevelopmentControlsComponent, {
      imports: [
        NoopAnimationsModule,
        AuthorizationModule.forRoot({
          development: true,
          roles: {
            admin: [ 'permission1', 'permission2' ],
            user: [ 'permission1' ],
            guest: [],
          },
        }),
      ],
    });
  });
@@ -33,14 +24,6 @@ describe(AuthorizationDevelopmentControlsComponent.name, () => {
    cy.mount(AuthorizationDevelopmentControlsComponent, {
      imports: [
        NoopAnimationsModule,
        AuthorizationModule.forRoot({
          development: true,
          roles: {
            admin: [ 'permission1', 'permission2' ],
            user: [ 'permission1' ],
            guest: [],
          },
        }),
      ],
    });
    cy.get('button').click();
+2 −2
Original line number Diff line number Diff line
@@ -11,10 +11,10 @@
  <div class="rxap-container mat-elevation-z2">
    <h2 i18n>Permissions</h2>
    <mat-list role="list">
      <mat-list-item *ngFor="let permission of authorization.getPermissions() | async" role="listitem">
      <mat-list-item *ngFor="let permission of authorization.getPermissions$() | async" role="listitem">
        <mat-slide-toggle
          (toggleChange)="authorization.togglePermission(permission)"
          [checked]="authorization.hasPermission(permission) | async">
          [checked]="authorization.hasPermission$(permission) | async">
          {{permission}}
        </mat-slide-toggle>
      </mat-list-item>
+58 −8
Original line number Diff line number Diff line
@@ -3,32 +3,58 @@ import {
  Injectable,
} from '@angular/core';
import { Method } from '@rxap/pattern';
import { isPromiseLike } from '@rxap/utilities';
import {
  BehaviorSubject,
  defer,
  firstValueFrom,
  from,
  Observable,
  of,
  switchMap,
} from 'rxjs';
import {
  AuthorizationService,
  PermissionMap,
} from './authorization.service';
import { RXAP_GET_SYSTEM_ROLES_METHOD } from './tokens';
import {
  map,
  shareReplay,
  take,
  tap,
} from 'rxjs/operators';
import { AuthorizationService } from './authorization.service';
import { RXAP_GET_SYSTEM_ROLES_METHOD } from './tokens';

/**
 * A map of roles and permissions
 * @example
 * {
 *  'admin': ['permission1', 'permission2'],
 *  'user': ['permission1']
 *  'guest': []
 * }
 */
export type PermissionMap = Record<string, string[]>;

@Injectable()
export class AuthorizationDevelopmentService extends AuthorizationService {

  public disabledPermissions$ = new BehaviorSubject<string[]>([]);

  protected readonly systemRoles$: Observable<PermissionMap>;

  constructor(
    @Inject(RXAP_GET_SYSTEM_ROLES_METHOD)
      getSystemRoles: Method<PermissionMap>,
  ) {
    super(getSystemRoles);
    super();
    this.systemRoles$ = defer(
      () => {
        const systemRoles = getSystemRoles.call();
        if (isPromiseLike(systemRoles)) {
          return from(systemRoles);
        } else {
          return of(systemRoles);
        }
      },
    ).pipe(shareReplay(1));
    console.warn('use authorization development service');
    this.getRoles().pipe(
      take(1),
@@ -36,7 +62,31 @@ export class AuthorizationDevelopmentService extends AuthorizationService {
    ).subscribe();
  }

  public override hasPermission(
  public getRoles(): Observable<string[]> {
    return this.systemRoles$.pipe(map(roles => Object.keys(roles)));
  }

  public async setUserRoles(userRoles: string[]) {
    const systemRoles = await firstValueFrom(this.systemRoles$);

    let permissions: string[] = [];

    for (const userRole of userRoles) {
      if (systemRoles && systemRoles[userRole]) {
        permissions.push(...systemRoles[userRole]);
      }
    }

    permissions = permissions.filter(
      (permission, index, self) => self.indexOf(permission) === index,
    );

    this.permissions$.next(permissions);

    return permissions;
  }

  public override hasPermission$(
    identifier: string,
    scope?: string | null,
    ignorePermissionList?: string[],
@@ -47,7 +97,7 @@ export class AuthorizationDevelopmentService extends AuthorizationService {
          ignorePermissionList ?? []
        ),
      ]),
      switchMap(ignorePermissionList => super.hasPermission(identifier, scope, ignorePermissionList)),
      switchMap(ignorePermissionList => super.hasPermission$(identifier, scope, ignorePermissionList)),
    );
  }

+0 −53
Original line number Diff line number Diff line
import { NgModule } from '@angular/core';
import {
  AuthorizationService,
  PermissionMap,
} from './authorization.service';
import { AuthorizationDevelopmentService } from './authorization-development.service';
import { NgModuleWithProviders } from 'ng-mocks';
import { RXAP_GET_SYSTEM_ROLES_METHOD } from './tokens';
import { GetSystemRolesRemoteMethod } from './get-system-roles.remote-method';
import { ToMethod } from '@rxap/pattern';

export interface AuthorizationModuleOptions {
  /**
   * Enable the development mode
   * If enabled the AuthorizationDevelopmentService is used instead of the AuthorizationService
   */
  development?: boolean;
  rolesUrl?: string;
  roles?: PermissionMap;
}

@NgModule({})
export class AuthorizationModule {

  static forRoot(options: AuthorizationModuleOptions = {}): NgModuleWithProviders<AuthorizationModule> {
    const providers: NgModule['providers'] = [
      {
        provide: AuthorizationService,
        useClass: options.development ? AuthorizationDevelopmentService : AuthorizationService,
      },
    ];
    if (options.roles && options.rolesUrl) {
      throw new Error('You can only provide roles or rolesUrl as AuthorizationModuleOptions');
    }
    if (options.rolesUrl) {
      providers.push({
        provide: RXAP_GET_SYSTEM_ROLES_METHOD,
        useClass: GetSystemRolesRemoteMethod,
      });
    }
    if (options.roles) {
      providers.push({
        provide: RXAP_GET_SYSTEM_ROLES_METHOD,
        useValue: ToMethod(() => options.roles),
      });
    }
    return {
      ngModule: AuthorizationModule,
      providers,
    };
  }

}
+36 −70
Original line number Diff line number Diff line
import {
  Inject,
  Injectable,
  isDevMode,
} from '@angular/core';
import { coerceArray } from '@rxap/utilities';
import {
  BehaviorSubject,
  defer,
  firstValueFrom,
  from,
  Observable,
  of,
} from 'rxjs';
import {
  distinctUntilChanged,
  map,
  shareReplay,
} from 'rxjs/operators';
import { RXAP_GET_SYSTEM_ROLES_METHOD } from './tokens';
import { Method } from '@rxap/pattern';
import { isPromiseLike } from '@rxap/utilities';

type Item = [ string, number, Item[] ];

/**
 * A map of roles and permissions
 * @example
 * {
 *  'admin': ['permission1', 'permission2'],
 *  'user': ['permission1']
 *  'guest': []
 * }
 */
export type PermissionMap = Record<string, string[]>;

@Injectable()
export class AuthorizationService {
  protected readonly permissions$ = new BehaviorSubject<string[]>([]);

  protected readonly systemRoles$: Observable<PermissionMap>;

  constructor(
    @Inject(RXAP_GET_SYSTEM_ROLES_METHOD)
    private readonly getSystemRoles: Method<PermissionMap>,
  ) {
    this.systemRoles$ = defer(
      () => {
        const systemRoles = this.getSystemRoles.call();
        if (isPromiseLike(systemRoles)) {
          return from(systemRoles);
        } else {
          return of(systemRoles);
        }
      },
    ).pipe(shareReplay(1));
  }

  public async setUserRoles(userRoles: string[]) {
    const systemRoles = await firstValueFrom(this.systemRoles$);

    let permissions: string[] = [];

    for (const userRole of userRoles) {
      if (systemRoles && systemRoles[userRole]) {
        permissions.push(...systemRoles[userRole]);
      }
    }
@Injectable({ providedIn: 'root' })
export class AuthorizationService {

    permissions = permissions.filter(
      (permission, index, self) => self.indexOf(permission) === index,
    );
  protected readonly permissions$ = new BehaviorSubject<string[]>([]);

  public setPermissions(permissions: string[]): void {
    this.permissions$.next(permissions);

    return permissions;
  }

  public checkPermission(
    identifier: string,
    identifier: string | string[],
    permissions: string[],
    scope?: string | null,
  ): boolean {

    identifier = coerceArray(identifier);

    if (!identifier.length) {
      return true;
    }

    if (isDevMode()) {
      console.log(
        `check permission for '${ identifier }'${ scope ? ` with scope '${ scope }': ` : ' :' }`,
        `check permission for [${ identifier.join(', ') }]${ scope ? ` with scope '${ scope }': ` : ' :' }`,
        permissions,
      );
    }
@@ -105,7 +58,7 @@ export class AuthorizationService {
        ).sort((a, b) => a.length - b.length);
    }

    if (permissionSubset.includes(identifier)) {
    if (identifier.every(id => permissionSubset.includes(id))) {
      return true;
    }

@@ -127,13 +80,13 @@ export class AuthorizationService {

    });

    return permissionRegexList.some((permissionRegex) =>
      identifier.match(permissionRegex),
    );
    return identifier.every(id => permissionRegexList.some((permissionRegex) =>
      id.match(permissionRegex),
    ));
  }

  public hasPermission(
    identifier: string,
  public hasPermission$(
    identifier: string | string[],
    scope?: string | null,
    ignorePermissionList?: string[],
  ): Observable<boolean> {
@@ -148,12 +101,25 @@ export class AuthorizationService {
    );
  }

  public getPermissions(): Observable<string[]> {
  public hasPermission(
    identifier: string | string[],
    scope?: string | null,
    ignorePermissionList?: string[],
  ): boolean {
    const allPermissions = this.getPermissions();
    const permissions = ignorePermissionList ?
      allPermissions.filter((permission) => !ignorePermissionList.includes(permission)) :
      allPermissions;

    return this.checkPermission(identifier, permissions, scope);
  }

  public getPermissions$(): Observable<string[]> {
    return this.permissions$.asObservable().pipe(map(permissions => permissions.slice()));
  }

  public getRoles(): Observable<string[]> {
    return this.systemRoles$.pipe(map(roles => Object.keys(roles)));
  public getPermissions(): string[] {
    return this.permissions$.value.slice();
  }

}
Loading