From ce294b1e0df82f77ae29ab85e663b5837d4221d8 Mon Sep 17 00:00:00 2001 From: "paolo.sanchi" Date: Wed, 12 Aug 2020 11:55:46 +0200 Subject: [PATCH 1/3] added the ability to pass a context object/any to the validation function --- .../lib/directive/permissions.directive.ts | 7 +++- .../model/permissions-router-data.model.ts | 4 ++- .../router/permissions-guard.service.spec.ts | 25 ++++++++++++++ .../lib/router/permissions-guard.service.ts | 13 ++++--- .../lib/service/permissions.service.spec.ts | 34 +++++++++++++++++++ .../src/lib/service/permissions.service.ts | 8 ++--- 6 files changed, 80 insertions(+), 11 deletions(-) diff --git a/projects/ngx-permissions/src/lib/directive/permissions.directive.ts b/projects/ngx-permissions/src/lib/directive/permissions.directive.ts index c323e45..588fe5b 100644 --- a/projects/ngx-permissions/src/lib/directive/permissions.directive.ts +++ b/projects/ngx-permissions/src/lib/directive/permissions.directive.ts @@ -46,6 +46,8 @@ export class NgxPermissionsDirective implements OnInit, OnDestroy, OnChanges { @Input() ngxPermissionsUnauthorisedStrategy: string | StrategyFunction; @Input() ngxPermissionsAuthorisedStrategy: string | StrategyFunction; + @Input() ngxPermissionsContext: any; + @Output() permissionsAuthorized = new EventEmitter(); @Output() permissionsUnauthorized = new EventEmitter(); @@ -152,7 +154,10 @@ export class NgxPermissionsDirective implements OnInit, OnDestroy, OnChanges { private validateOnlyPermissions(): void { Promise - .all([this.permissionsService.hasPermission(this.ngxPermissionsOnly), this.rolesService.hasOnlyRoles(this.ngxPermissionsOnly)]) + .all([ + this.permissionsService.hasPermission(this.ngxPermissionsOnly, this.ngxPermissionsContext), + this.rolesService.hasOnlyRoles(this.ngxPermissionsOnly) + ]) .then(([hasPermissions, hasRoles]) => { if (hasPermissions || hasRoles) { this.handleAuthorisedPermission(this.ngxPermissionsOnlyThen || this.ngxPermissionsThen || this.templateRef); diff --git a/projects/ngx-permissions/src/lib/model/permissions-router-data.model.ts b/projects/ngx-permissions/src/lib/model/permissions-router-data.model.ts index e530ecd..dacfe92 100644 --- a/projects/ngx-permissions/src/lib/model/permissions-router-data.model.ts +++ b/projects/ngx-permissions/src/lib/model/permissions-router-data.model.ts @@ -4,6 +4,7 @@ export interface NgxPermissionsRouterData { only?: string | string[] | OnlyFn; except?: string | string[] | ExceptFn; redirectTo?: RedirectTo | RedirectToFn; + context?: any; } export interface NgxRedirectToNavigationParameters { @@ -23,6 +24,7 @@ export declare type RedirectToFn = export declare type NavigationCommandsFn = (route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) => any[]; export declare type NavigationExtrasFn = (route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) => NavigationExtras; -export declare type ValidationFn = ((name?: string, store?: any) => Promise | boolean | string[]); +export declare type ValidationFn = + ((name?: string, store?: any, context?: any) => Promise | boolean | string[]); export const DEFAULT_REDIRECT_KEY = 'default'; diff --git a/projects/ngx-permissions/src/lib/router/permissions-guard.service.spec.ts b/projects/ngx-permissions/src/lib/router/permissions-guard.service.spec.ts index 3faf44b..159969e 100644 --- a/projects/ngx-permissions/src/lib/router/permissions-guard.service.spec.ts +++ b/projects/ngx-permissions/src/lib/router/permissions-guard.service.spec.ts @@ -23,6 +23,7 @@ describe('Permissions guard only', () => { spyOn(fakeRouter, 'navigate'); service.addPermission('ADMIN'); + service.addPermission('ADMIN_CONTEXT', (name, store, context) => context === true); permissionGuard = new NgxPermissionsGuard(service, rolesService, fakeRouter as Router); })); @@ -41,6 +42,30 @@ describe('Permissions guard only', () => { }); })); + it ('should return true when only fulfils, with context', fakeAsync(() => { + testRoute = { data: { + permissions: { + only: 'ADMIN_CONTEXT', + context: true + } + }}; + (permissionGuard.canActivate(testRoute, {} as RouterStateSnapshot) as any).then((data) => { + expect(data).toEqual(true); + }); + })); + + it ('should return false when only doesnt match, with context', fakeAsync(() => { + testRoute = { data: { + permissions: { + only: 'ADMIN_CONTEXT', + context: false + } + }}; + (permissionGuard.canActivate(testRoute, {} as RouterStateSnapshot) as any).then((data) => { + expect(data).toEqual(false); + }); + })); + it ('should return true when only is empty array', fakeAsync(() => { testRoute = { data: { permissions: { diff --git a/projects/ngx-permissions/src/lib/router/permissions-guard.service.ts b/projects/ngx-permissions/src/lib/router/permissions-guard.service.ts index 0b2c646..64d52c3 100644 --- a/projects/ngx-permissions/src/lib/router/permissions-guard.service.ts +++ b/projects/ngx-permissions/src/lib/router/permissions-guard.service.ts @@ -32,6 +32,7 @@ export interface NgxPermissionsData { only?: string | string[]; except?: string | string[]; redirectTo?: RedirectTo | RedirectToFn; + context?: any; } @Injectable() @@ -79,12 +80,14 @@ export class NgxPermissionsGuard implements CanActivate, CanLoad, CanActivateChi ? permissions.except(route, state) : transformStringToArray(permissions.except); const redirectTo = permissions.redirectTo; + const context = permissions.context; return { only, except, - redirectTo + redirectTo, + context, }; } @@ -110,7 +113,7 @@ export class NgxPermissionsGuard implements CanActivate, CanLoad, CanActivateChi .pipe( mergeMap(permissionsExcept => { return forkJoin([ - this.permissionsService.hasPermission(permissionsExcept), + this.permissionsService.hasPermission(permissionsExcept, permissions.context), this.rolesService.hasOnlyRoles(permissionsExcept) ]).pipe( tap(hasPermissions => { @@ -141,7 +144,7 @@ export class NgxPermissionsGuard implements CanActivate, CanLoad, CanActivateChi } return Promise.all([ - this.permissionsService.hasPermission(permissions.except), + this.permissionsService.hasPermission(permissions.except, permissions.context), this.rolesService.hasOnlyRoles(permissions.except) ]).then(([hasPermission, hasRoles]) => { if (hasPermission || hasRoles) { @@ -223,7 +226,7 @@ export class NgxPermissionsGuard implements CanActivate, CanLoad, CanActivateChi .pipe( mergeMap(permissionsOnly => { return forkJoin([ - this.permissionsService.hasPermission(permissionsOnly), + this.permissionsService.hasPermission(permissionsOnly, permissions.context), this.rolesService.hasOnlyRoles(permissionsOnly) ]).pipe( tap(hasPermissions => { @@ -295,7 +298,7 @@ export class NgxPermissionsGuard implements CanActivate, CanLoad, CanActivateChi }; return Promise.all([ - this.permissionsService.hasPermission(permissions.only), + this.permissionsService.hasPermission(permissions.only, permissions.context), this.rolesService.hasOnlyRoles(permissions.only) ]).then(([hasPermission, hasRole]) => { if (hasPermission || hasRole) { diff --git a/projects/ngx-permissions/src/lib/service/permissions.service.spec.ts b/projects/ngx-permissions/src/lib/service/permissions.service.spec.ts index 02e5f7a..1980389 100644 --- a/projects/ngx-permissions/src/lib/service/permissions.service.spec.ts +++ b/projects/ngx-permissions/src/lib/service/permissions.service.spec.ts @@ -168,6 +168,40 @@ describe('Permissions Service', () => { // }); })); + it ('return true when role permission with context function return true', fakeAsync(() => { + expect(Object.keys(localService.getPermissions()).length).toEqual(0); + localService.addPermission(PermissionsNamesEnum.ADMIN as any, (name, store, context) => { + return context; + }); + expect(Object.keys(localService.getPermissions()).length).toEqual(1); + localService.hasPermission('ADMIN', true).then((data) => { + expect(data).toEqual(true); + }); + + localService.addPermission(PermissionsNamesEnum.GUEST as any, (name, store, context) => { + return false && context; + }); + expect(Object.keys(localService.getPermissions()).length).toEqual(2); + localService.hasPermission('GUEST', true).then((data) => { + expect(data).toEqual(false); + }); + + localService.addPermission('TEST1' as any, (name, store, context) => { + return Promise.resolve(context); + }); + expect(Object.keys(localService.getPermissions()).length).toEqual(3); + localService.hasPermission('TEST1', true).then((data) => { + expect(data).toEqual(true); + }); + localService.addPermission('TEST2' as any, (name, store, context) => { + return Promise.resolve(context); + }); + expect(Object.keys(localService.getPermissions()).length).toEqual(4); + localService.hasPermission('TEST2', false).then((data) => { + expect(data).toEqual(false); + }); + })); + it ('return call function with name and store in array', fakeAsync(() => { localService.addPermission(['TEST11'] as any, (n, store) => { diff --git a/projects/ngx-permissions/src/lib/service/permissions.service.ts b/projects/ngx-permissions/src/lib/service/permissions.service.ts index a0bf38f..66b57a1 100644 --- a/projects/ngx-permissions/src/lib/service/permissions.service.ts +++ b/projects/ngx-permissions/src/lib/service/permissions.service.ts @@ -36,13 +36,13 @@ export class NgxPermissionsService { this.permissionsSource.next({}); } - public hasPermission(permission: string | string[]): Promise { + public hasPermission(permission: string | string[], context?: any): Promise { if (!permission || (Array.isArray(permission) && permission.length === 0)) { return Promise.resolve(true); } permission = transformStringToArray(permission); - return this.hasArrayPermission(permission); + return this.hasArrayPermission(permission, context); } public loadPermissions(permissions: string[], validationFunction?: ValidationFn): void { @@ -95,14 +95,14 @@ export class NgxPermissionsService { }; } - private hasArrayPermission(permissions: string[]): Promise { + private hasArrayPermission(permissions: string[], context?: any): Promise { const promises: Observable[] = permissions.map(key => { if (this.hasPermissionValidationFunction(key)) { const validationFunction = this.permissionsSource.value[key].validationFunction; const immutableValue = {...this.permissionsSource.value}; return of(null).pipe( - map(() => validationFunction(key, immutableValue)), + map(() => validationFunction(key, immutableValue, context)), switchMap((promise: Promise | boolean): ObservableInput => isBoolean(promise) ? of(promise as boolean) : promise as Promise), catchError(() => of(false)) From 3f3d5db97cae33a08f2967079d361976ab0dceaa Mon Sep 17 00:00:00 2001 From: "paolo.sanchi" Date: Wed, 12 Aug 2020 12:55:35 +0200 Subject: [PATCH 2/3] fix context usage in directive --- .../src/lib/directive/permissions.directive.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/projects/ngx-permissions/src/lib/directive/permissions.directive.ts b/projects/ngx-permissions/src/lib/directive/permissions.directive.ts index 588fe5b..5900d3e 100644 --- a/projects/ngx-permissions/src/lib/directive/permissions.directive.ts +++ b/projects/ngx-permissions/src/lib/directive/permissions.directive.ts @@ -39,14 +39,15 @@ export class NgxPermissionsDirective implements OnInit, OnDestroy, OnChanges { @Input() ngxPermissionsOnlyAuthorisedStrategy: string | StrategyFunction; @Input() ngxPermissionsOnlyUnauthorisedStrategy: string | StrategyFunction; + @Input() ngxPermissionsOnlyContext: any; @Input() ngxPermissionsExceptUnauthorisedStrategy: string | StrategyFunction; @Input() ngxPermissionsExceptAuthorisedStrategy: string | StrategyFunction; + @Input() ngxPermissionsExceptContext: any; @Input() ngxPermissionsUnauthorisedStrategy: string | StrategyFunction; @Input() ngxPermissionsAuthorisedStrategy: string | StrategyFunction; - @Input() ngxPermissionsContext: any; @Output() permissionsAuthorized = new EventEmitter(); @Output() permissionsUnauthorized = new EventEmitter(); @@ -128,7 +129,7 @@ export class NgxPermissionsDirective implements OnInit, OnDestroy, OnChanges { private validateExceptAndOnlyPermissions(): void { Promise .all([ - this.permissionsService.hasPermission(this.ngxPermissionsExcept), + this.permissionsService.hasPermission(this.ngxPermissionsExcept, this.ngxPermissionsExceptContext), this.rolesService.hasOnlyRoles(this.ngxPermissionsExcept) ]) .then(([hasPermission, hasRole]) => { @@ -155,7 +156,7 @@ export class NgxPermissionsDirective implements OnInit, OnDestroy, OnChanges { private validateOnlyPermissions(): void { Promise .all([ - this.permissionsService.hasPermission(this.ngxPermissionsOnly, this.ngxPermissionsContext), + this.permissionsService.hasPermission(this.ngxPermissionsOnly, this.ngxPermissionsOnlyContext), this.rolesService.hasOnlyRoles(this.ngxPermissionsOnly) ]) .then(([hasPermissions, hasRoles]) => { From 4fd189892b768dbfbe0dbe8bf452db0120bfde7c Mon Sep 17 00:00:00 2001 From: "paolo.sanchi" Date: Thu, 13 Aug 2020 16:05:50 +0200 Subject: [PATCH 3/3] context in routing can be a ContextFn so it access to the current route snapshot --- .../src/lib/model/permissions-router-data.model.ts | 1 + .../src/lib/router/permissions-guard.service.ts | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/projects/ngx-permissions/src/lib/model/permissions-router-data.model.ts b/projects/ngx-permissions/src/lib/model/permissions-router-data.model.ts index dacfe92..b828515 100644 --- a/projects/ngx-permissions/src/lib/model/permissions-router-data.model.ts +++ b/projects/ngx-permissions/src/lib/model/permissions-router-data.model.ts @@ -14,6 +14,7 @@ export interface NgxRedirectToNavigationParameters { export declare type OnlyFn = (route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) => string | string[]; export declare type ExceptFn = (route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) => string | string[]; +export declare type ContextFn = (route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) => string | string[]; export declare type RedirectTo = string diff --git a/projects/ngx-permissions/src/lib/router/permissions-guard.service.ts b/projects/ngx-permissions/src/lib/router/permissions-guard.service.ts index 64d52c3..4c86a35 100644 --- a/projects/ngx-permissions/src/lib/router/permissions-guard.service.ts +++ b/projects/ngx-permissions/src/lib/router/permissions-guard.service.ts @@ -21,6 +21,7 @@ import { NgxPermissionsRouterData, NgxRedirectToNavigationParameters, OnlyFn, + ContextFn, RedirectTo, RedirectToFn } from '../model/permissions-router-data.model'; @@ -79,9 +80,10 @@ export class NgxPermissionsGuard implements CanActivate, CanLoad, CanActivateChi const except = isFunction(permissions.except) ? permissions.except(route, state) : transformStringToArray(permissions.except); + const context = isFunction(permissions.context) + ? permissions.context(route, state) + : transformStringToArray(permissions.context); const redirectTo = permissions.redirectTo; - const context = permissions.context; - return { only,