Loading packages/angular/authorization/src/lib/authorization-development-controls/authorization-development-controls.component.cy.ts +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(() => { Loading @@ -17,14 +16,6 @@ describe(AuthorizationDevelopmentControlsComponent.name, () => { cy.mount(AuthorizationDevelopmentControlsComponent, { imports: [ NoopAnimationsModule, AuthorizationModule.forRoot({ development: true, roles: { admin: [ 'permission1', 'permission2' ], user: [ 'permission1' ], guest: [], }, }), ], }); }); Loading @@ -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(); Loading packages/angular/authorization/src/lib/authorization-development-controls/authorization-development-controls.component.html +2 −2 Original line number Diff line number Diff line Loading @@ -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> Loading packages/angular/authorization/src/lib/authorization-development.service.ts +58 −8 Original line number Diff line number Diff line Loading @@ -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), Loading @@ -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[], Loading @@ -47,7 +97,7 @@ export class AuthorizationDevelopmentService extends AuthorizationService { ignorePermissionList ?? [] ), ]), switchMap(ignorePermissionList => super.hasPermission(identifier, scope, ignorePermissionList)), switchMap(ignorePermissionList => super.hasPermission$(identifier, scope, ignorePermissionList)), ); } Loading packages/angular/authorization/src/lib/authorization.module.tsdeleted 100644 → 0 +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, }; } } packages/angular/authorization/src/lib/authorization.service.ts +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, ); } Loading @@ -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; } Loading @@ -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> { Loading @@ -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
packages/angular/authorization/src/lib/authorization-development-controls/authorization-development-controls.component.cy.ts +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(() => { Loading @@ -17,14 +16,6 @@ describe(AuthorizationDevelopmentControlsComponent.name, () => { cy.mount(AuthorizationDevelopmentControlsComponent, { imports: [ NoopAnimationsModule, AuthorizationModule.forRoot({ development: true, roles: { admin: [ 'permission1', 'permission2' ], user: [ 'permission1' ], guest: [], }, }), ], }); }); Loading @@ -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(); Loading
packages/angular/authorization/src/lib/authorization-development-controls/authorization-development-controls.component.html +2 −2 Original line number Diff line number Diff line Loading @@ -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> Loading
packages/angular/authorization/src/lib/authorization-development.service.ts +58 −8 Original line number Diff line number Diff line Loading @@ -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), Loading @@ -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[], Loading @@ -47,7 +97,7 @@ export class AuthorizationDevelopmentService extends AuthorizationService { ignorePermissionList ?? [] ), ]), switchMap(ignorePermissionList => super.hasPermission(identifier, scope, ignorePermissionList)), switchMap(ignorePermissionList => super.hasPermission$(identifier, scope, ignorePermissionList)), ); } Loading
packages/angular/authorization/src/lib/authorization.module.tsdeleted 100644 → 0 +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, }; } }
packages/angular/authorization/src/lib/authorization.service.ts +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, ); } Loading @@ -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; } Loading @@ -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> { Loading @@ -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(); } }