diff --git a/projects/ngx-permissions/src/lib/directive/permissions.directive.ts b/projects/ngx-permissions/src/lib/directive/permissions.directive.ts index c323e45..5900d3e 100644 --- a/projects/ngx-permissions/src/lib/directive/permissions.directive.ts +++ b/projects/ngx-permissions/src/lib/directive/permissions.directive.ts @@ -39,13 +39,16 @@ 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; + @Output() permissionsAuthorized = new EventEmitter(); @Output() permissionsUnauthorized = new EventEmitter(); @@ -126,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]) => { @@ -152,7 +155,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.ngxPermissionsOnlyContext), + 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..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 @@ -4,6 +4,7 @@ export interface NgxPermissionsRouterData { only?: string | string[] | OnlyFn; except?: string | string[] | ExceptFn; redirectTo?: RedirectTo | RedirectToFn; + context?: any; } export interface NgxRedirectToNavigationParameters { @@ -13,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 @@ -23,6 +25,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..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'; @@ -32,6 +33,7 @@ export interface NgxPermissionsData { only?: string | string[]; except?: string | string[]; redirectTo?: RedirectTo | RedirectToFn; + context?: any; } @Injectable() @@ -78,13 +80,16 @@ 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; - return { only, except, - redirectTo + redirectTo, + context, }; } @@ -110,7 +115,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 +146,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 +228,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 +300,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))