adjust permissions services
This commit is contained in:
parent
b607cff5f9
commit
c6edb0cd96
@ -2,5 +2,9 @@ import { ValidationFn } from './permissions-router-data.model';
|
||||
|
||||
export interface IqserPermission {
|
||||
name: string;
|
||||
validationFunction?: ValidationFn;
|
||||
validationFn?: ValidationFn<IqserPermission>;
|
||||
}
|
||||
|
||||
export interface IqserPermissionsObject {
|
||||
[name: string]: IqserPermission;
|
||||
}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { ActivatedRouteSnapshot, NavigationExtras, Route, RouterStateSnapshot } from '@angular/router';
|
||||
|
||||
export interface IqserPermissionsRouterData {
|
||||
only?: string | string[] | OnlyFn;
|
||||
except?: string | string[] | ExceptFn;
|
||||
allow: string | string[] | AllowFn;
|
||||
redirectTo?: RedirectTo | RedirectToFn;
|
||||
}
|
||||
|
||||
@ -11,22 +10,30 @@ export interface IqserRedirectToNavigationParameters {
|
||||
navigationExtras?: NavigationExtras | NavigationExtrasFn;
|
||||
}
|
||||
|
||||
export type OnlyFn = (route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) => string | string[];
|
||||
export type ExceptFn = (route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) => string | string[];
|
||||
export type AllowFn = (route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) => string | string[];
|
||||
|
||||
export type RedirectTo =
|
||||
| string
|
||||
| { [name: string]: IqserRedirectToNavigationParameters | string | RedirectToFn }
|
||||
| IqserRedirectToNavigationParameters;
|
||||
| string[]
|
||||
| IqserRedirectToNavigationParameters
|
||||
| { [name: string]: IqserRedirectToNavigationParameters | string | RedirectToFn };
|
||||
|
||||
export type RedirectToFn = (
|
||||
rejectedPermissionName?: string,
|
||||
route?: ActivatedRouteSnapshot | Route,
|
||||
state?: RouterStateSnapshot,
|
||||
) => RedirectTo;
|
||||
export type RedirectToFn = (failedPermission: string, route?: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) => RedirectTo;
|
||||
|
||||
export type NavigationCommandsFn = (route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) => any[];
|
||||
export type NavigationExtrasFn = (route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) => NavigationExtras;
|
||||
export type ValidationFn = (name?: string, store?: any) => Promise<void | string | boolean> | boolean | string[];
|
||||
export type ValidationFn<T> = (name: string, store: Record<string, T>) => Promise<void | string | boolean> | boolean | string[];
|
||||
|
||||
export type IqserActivatedRouteSnapshot = ActivatedRouteSnapshot & {
|
||||
data: {
|
||||
permissions: IqserPermissionsRouterData;
|
||||
};
|
||||
};
|
||||
|
||||
export type IqserRoute = Route & {
|
||||
data: {
|
||||
permissions: IqserPermissionsRouterData;
|
||||
};
|
||||
};
|
||||
|
||||
export const DEFAULT_REDIRECT_KEY = 'default';
|
||||
|
||||
@ -2,5 +2,5 @@ import { ValidationFn } from './permissions-router-data.model';
|
||||
|
||||
export interface IqserRole {
|
||||
name: string;
|
||||
validationFunction: ValidationFn | string[];
|
||||
validationFn: ValidationFn<IqserRole> | string[];
|
||||
}
|
||||
|
||||
863
src/lib/permissions/permissions.directive.spec.ts
Normal file
863
src/lib/permissions/permissions.directive.spec.ts
Normal file
@ -0,0 +1,863 @@
|
||||
import { Component, Type } from '@angular/core';
|
||||
import { IqserPermissionsModule } from '.';
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { IqserPermissionsService } from './services/permissions.service';
|
||||
import { IqserRolesService } from './services/roles.service';
|
||||
|
||||
const ADMIN = 'ADMIN' as const;
|
||||
const GUEST = 'GUEST' as const;
|
||||
|
||||
function detectChanges(fixture: ComponentFixture<unknown>) {
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
let fixture: ComponentFixture<unknown>;
|
||||
let permissionsService: IqserPermissionsService;
|
||||
let rolesService: IqserRolesService;
|
||||
|
||||
function configureTestBed(component: Type<unknown>) {
|
||||
TestBed.configureTestingModule({ declarations: [component], imports: [IqserPermissionsModule.forRoot()] });
|
||||
fixture = TestBed.createComponent(component);
|
||||
detectChanges(fixture);
|
||||
permissionsService = fixture.debugElement.injector.get(IqserPermissionsService);
|
||||
rolesService = fixture.debugElement.injector.get(IqserRolesService);
|
||||
}
|
||||
|
||||
describe('Permission directive angular', () => {
|
||||
@Component({
|
||||
template: ` <ng-template [iqserPermissions]="'ADMIN'">
|
||||
<div>123</div>
|
||||
</ng-template>`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
beforeEach(fakeAsync(() => configureTestBed(TestComponent)));
|
||||
|
||||
it('Should show the component', fakeAsync(() => {
|
||||
permissionsService.load([ADMIN, GUEST]);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toBeTruthy();
|
||||
expect(content.innerHTML).toEqual('123');
|
||||
}));
|
||||
|
||||
it('Should not show the component', fakeAsync(() => {
|
||||
permissionsService.load([GUEST]);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
}));
|
||||
|
||||
it('Should show component when permission added', fakeAsync(() => {
|
||||
permissionsService.load([GUEST]);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add(ADMIN);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('123');
|
||||
}));
|
||||
|
||||
it('Should hide component when permission removed', fakeAsync(() => {
|
||||
permissionsService.load([ADMIN, GUEST]);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('123');
|
||||
|
||||
permissionsService.remove(ADMIN);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Permission directive angular roles', () => {
|
||||
@Component({
|
||||
template: ` <ng-template [iqserPermissions]="'ADMIN'">
|
||||
<div>123</div>
|
||||
</ng-template>`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
const awesomePermissions = 'AWESOME';
|
||||
beforeEach(fakeAsync(() => configureTestBed(TestComponent)));
|
||||
|
||||
it('Should show the component when key of role is the same', fakeAsync(() => {
|
||||
rolesService.add('ADMIN', [awesomePermissions]);
|
||||
permissionsService.add(awesomePermissions);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toBeTruthy();
|
||||
expect(content.innerHTML).toEqual('123');
|
||||
}));
|
||||
|
||||
it('should show the component when permissions array is the same ', fakeAsync(() => {
|
||||
rolesService.add('ADMIN', [awesomePermissions]);
|
||||
permissionsService.add(awesomePermissions);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toBeTruthy();
|
||||
expect(content.innerHTML).toEqual('123');
|
||||
}));
|
||||
|
||||
it('should hide the component when user deletes all roles', fakeAsync(() => {
|
||||
permissionsService.add(awesomePermissions);
|
||||
rolesService.add('ADMIN', [awesomePermissions]);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toBeTruthy();
|
||||
expect(content.innerHTML).toEqual('123');
|
||||
|
||||
rolesService.clear();
|
||||
detectChanges(fixture);
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
|
||||
it('should hide the component when user deletes one role', fakeAsync(() => {
|
||||
permissionsService.add(awesomePermissions);
|
||||
rolesService.add('ADMIN', [awesomePermissions]);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toBeTruthy();
|
||||
expect(content.innerHTML).toEqual('123');
|
||||
|
||||
rolesService.remove('ADMIN');
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Permission directive angular roles array', () => {
|
||||
@Component({
|
||||
template: ` <ng-template [iqserPermissions]="['ADMIN', 'GUEST']">
|
||||
<div>123</div>
|
||||
</ng-template>`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
const awesomePermission = 'AWESOME';
|
||||
beforeEach(fakeAsync(() => configureTestBed(TestComponent)));
|
||||
|
||||
it('Should show the component when key of role is the same', fakeAsync(() => {
|
||||
permissionsService.add(awesomePermission);
|
||||
rolesService.add('ADMIN', [awesomePermission]);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toBeTruthy();
|
||||
expect(content.innerHTML).toEqual('123');
|
||||
}));
|
||||
|
||||
it('should show the component when there is permission ', fakeAsync(() => {
|
||||
permissionsService.add(awesomePermission);
|
||||
rolesService.add('ADMIN', ['AWESOME']);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toBeTruthy();
|
||||
expect(content.innerHTML).toEqual('123');
|
||||
}));
|
||||
|
||||
it('should hide the component when user deletes all roles', fakeAsync(() => {
|
||||
permissionsService.add(awesomePermission);
|
||||
rolesService.add('ADMIN', [awesomePermission]);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toBeTruthy();
|
||||
expect(content.innerHTML).toEqual('123');
|
||||
|
||||
rolesService.clear();
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
|
||||
it('should hide the component when user deletes one roles', fakeAsync(() => {
|
||||
permissionsService.add(awesomePermission);
|
||||
rolesService.add('ADMIN', [awesomePermission]);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toBeTruthy();
|
||||
expect(content.innerHTML).toEqual('123');
|
||||
|
||||
rolesService.remove('ADMIN');
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Permission directive angular testing different selectors *iqserPermissions', () => {
|
||||
@Component({
|
||||
template: ` <div *iqserPermissions="['ADMIN']">
|
||||
<div>123</div>
|
||||
</div>`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
beforeEach(fakeAsync(() => configureTestBed(TestComponent)));
|
||||
|
||||
it('Should show the component when key of role is the same', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('AWESOME');
|
||||
rolesService.add('ADMIN', ['AWESOME']);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should hide the component when key of role is the same', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
rolesService.add('GG', ['Awsesome']);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Permission directive angular testing different async functions in roles', () => {
|
||||
@Component({
|
||||
template: ` <div *iqserPermissions="'ADMIN'">
|
||||
<div>123</div>
|
||||
</div>`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
beforeEach(fakeAsync(() => configureTestBed(TestComponent)));
|
||||
|
||||
it('Should show the component when promise returns truthy value', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
rolesService.add('ADMIN', () => true);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should not show the component when promise returns truthy value', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
rolesService.add('ADMIN', () => false);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
|
||||
it('Should show the component when promise returns truthy value', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
rolesService.add('ADMIN', () => Promise.resolve(true));
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should not show the component when promise rejects', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
rolesService.add('ADMIN', () => Promise.reject());
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Permission directive angular testing different async functions in roles via array', () => {
|
||||
@Component({
|
||||
template: ` <div *iqserPermissions="['ADMIN', 'GUEST']">
|
||||
<div>123</div>
|
||||
</div>`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
beforeEach(fakeAsync(() => configureTestBed(TestComponent)));
|
||||
|
||||
it('Should show the component when promise returns truthy value', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
rolesService.add('ADMIN', () => Promise.resolve(true));
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should not show the component when promise returns false value', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
rolesService.add('ADMIN', () => Promise.resolve(false));
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
|
||||
it('Should not show the component when promise rejects', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
rolesService.add('ADMIN', () => Promise.reject());
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
|
||||
it('Should show the component when one of the promises fulfills ', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
rolesService.add('ADMIN', () => Promise.reject());
|
||||
rolesService.add('GUEST', () => Promise.resolve(true));
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should show the component when one of the promises fulfills with 0 value', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
rolesService.add('ADMIN', () => {
|
||||
return Promise.reject();
|
||||
});
|
||||
|
||||
rolesService.add('GUEST', () => {
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
detectChanges(fixture);
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should not show the component when all promises fails', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
rolesService.add('ADMIN', () => {
|
||||
return Promise.reject();
|
||||
});
|
||||
|
||||
rolesService.add('GUEST', () => {
|
||||
return Promise.reject();
|
||||
});
|
||||
|
||||
detectChanges(fixture);
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
|
||||
it('Should show the component when one of promises returns true', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
rolesService.add('GUEST', () => {
|
||||
return true;
|
||||
});
|
||||
|
||||
rolesService.add('ADMIN', () => {
|
||||
return Promise.reject();
|
||||
});
|
||||
|
||||
detectChanges(fixture);
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should show the component when 1 passes second fails', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
rolesService.add('ADMIN', () => {
|
||||
return Promise.reject();
|
||||
});
|
||||
permissionsService.add('AWESOME');
|
||||
rolesService.add('GUEST', ['AWESOME']);
|
||||
|
||||
detectChanges(fixture);
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should show the component when one rejects but another one fulfils', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
rolesService.add('ADMIN', () => {
|
||||
return Promise.reject();
|
||||
});
|
||||
permissionsService.add('AWESOME');
|
||||
rolesService.add('GUEST', ['AWESOME']);
|
||||
|
||||
detectChanges(fixture);
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Permission directive angular testing different async functions in permissions via array', () => {
|
||||
@Component({
|
||||
template: ` <div *iqserPermissions="['ADMIN', 'GUEST']">
|
||||
<div>123</div>
|
||||
</div>`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
beforeEach(fakeAsync(() => configureTestBed(TestComponent)));
|
||||
|
||||
it('Should show the component when promise returns truthy value', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => true);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should not show the component when promise returns false value', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => false);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
|
||||
it('Should show the component when promise returns truthy value', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => Promise.resolve(true));
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should not show the component when promise rejects', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => Promise.reject());
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
|
||||
it('Should show the component when one of the promises fulfills ', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => Promise.resolve());
|
||||
permissionsService.add('GUEST', () => Promise.reject());
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should show the component when one of the promises fulfills with 0 value', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => Promise.resolve());
|
||||
permissionsService.add('GUEST', () => Promise.resolve());
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should not show the component when all promises fails', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => Promise.reject());
|
||||
permissionsService.add('GUEST', () => Promise.reject());
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
|
||||
it('Should show the component when one of promises returns true', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('GUEST', () => true);
|
||||
permissionsService.add('ADMIN', () => Promise.reject());
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should not show the component when all promises fails', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => Promise.reject());
|
||||
permissionsService.add('GUEST', () => Promise.resolve(true));
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should show the component when one rejects but another one fulfils', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => Promise.reject());
|
||||
permissionsService.add('GUEST', () => true);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should show the component when one rejects but another one fulfils', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => true);
|
||||
permissionsService.add('GUEST', () => Promise.reject());
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should show the component when functions with name and store fulfils', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', (name, store) => {
|
||||
expect(name).toBeTruthy();
|
||||
expect(store[name!].name).toBeTruthy();
|
||||
return name === 'ADMIN';
|
||||
});
|
||||
|
||||
permissionsService.add('GUEST', () => Promise.reject());
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Permission directive angular testing different async functions in permissions via string', () => {
|
||||
@Component({
|
||||
template: ` <div *iqserPermissions="'ADMIN'">
|
||||
<div>123</div>
|
||||
</div>`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
beforeEach(fakeAsync(() => configureTestBed(TestComponent)));
|
||||
|
||||
it('Should show the component when promise returns truthy value', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => true);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should not show the component when promise returns false value', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => false);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
|
||||
it('Should show the component when promise returns truthy value', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => Promise.resolve(true));
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should not show the component when promise rejects', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => Promise.reject());
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
|
||||
it('Should show the component when one of the promises fulfills ', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => Promise.resolve());
|
||||
permissionsService.add('GUEST', () => Promise.resolve(true));
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should show the component when one of the promises fulfills with 0 value', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => Promise.resolve());
|
||||
permissionsService.add('GUEST', () => Promise.reject());
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should not show the component when all promises fails', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => Promise.reject());
|
||||
permissionsService.add('GUEST', () => Promise.reject());
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toEqual(null);
|
||||
}));
|
||||
|
||||
it('Should show the component when one of promises returns true', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('GUEST', () => Promise.reject());
|
||||
permissionsService.add('ADMIN', () => true);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should not show the component when all promises fails', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => Promise.resolve(true));
|
||||
permissionsService.add('GUEST', () => Promise.reject());
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should show the component when one rejects but another one fulfils', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
|
||||
permissionsService.add('ADMIN', () => true);
|
||||
permissionsService.add('GUEST', () => Promise.reject());
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
|
||||
it('Should show the component when functions with name and store fulfils', fakeAsync(() => {
|
||||
const content = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content).toEqual(null);
|
||||
permissionsService.add('ADMIN', (name, store) => {
|
||||
expect(name).toBeTruthy();
|
||||
expect(store[name!].name).toBeTruthy();
|
||||
return name === 'ADMIN';
|
||||
});
|
||||
|
||||
permissionsService.add('GUEST', () => Promise.reject());
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual('<div>123</div>');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Permissions directive testing else block', () => {
|
||||
@Component({
|
||||
template: `
|
||||
<div *iqserPermissions="['FAILED_BLOCK']; else elseBlock">main</div>
|
||||
|
||||
<ng-template #elseBlock>
|
||||
<div>elseBlock</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #thenBlock>
|
||||
<div>thenBlock</div>
|
||||
</ng-template>
|
||||
`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
beforeEach(fakeAsync(() => configureTestBed(TestComponent)));
|
||||
|
||||
it('Should fail and show else block', () => {
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual(`elseBlock`);
|
||||
});
|
||||
|
||||
it('Should add element remove element and show then block', fakeAsync(() => {
|
||||
rolesService.add('FAILED_BLOCK', () => true);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content3 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content3).toBeTruthy();
|
||||
expect(content3.innerHTML).toEqual('main');
|
||||
|
||||
rolesService.remove('FAILED_BLOCK');
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual(`elseBlock`);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Permissions directive testing then block', () => {
|
||||
@Component({
|
||||
template: `
|
||||
<div *iqserPermissions="['THEN_BLOCK']; else elseBlock; then: thenBlock">main</div>
|
||||
|
||||
<ng-template #elseBlock>
|
||||
<div>elseBlock</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #thenBlock>
|
||||
<div>thenBlock</div>
|
||||
</ng-template>
|
||||
`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
beforeEach(fakeAsync(() => configureTestBed(TestComponent)));
|
||||
|
||||
it('Should fail and show then block', fakeAsync(() => {
|
||||
rolesService.add('THEN_BLOCK', () => true);
|
||||
detectChanges(fixture);
|
||||
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual(`thenBlock`);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Permissions directive when no permission specified should return true', () => {
|
||||
@Component({
|
||||
template: `
|
||||
<ng-template [iqserPermissions]="">
|
||||
<div>123</div>
|
||||
</ng-template>
|
||||
`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
beforeEach(fakeAsync(() => configureTestBed(TestComponent)));
|
||||
|
||||
it('Except and only should success and show then block', () => {
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual(`123`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Permissions directive when no permission specified as array should return true', () => {
|
||||
@Component({
|
||||
template: `
|
||||
<ng-template [iqserPermissions]="[]">
|
||||
<div>123</div>
|
||||
</ng-template>
|
||||
`,
|
||||
})
|
||||
class TestComponent {}
|
||||
|
||||
beforeEach(fakeAsync(() => configureTestBed(TestComponent)));
|
||||
|
||||
it('Except and only should success and show then block', () => {
|
||||
const content2 = fixture.debugElement.nativeElement.querySelector('div');
|
||||
expect(content2).toBeTruthy();
|
||||
expect(content2.innerHTML).toEqual(`123`);
|
||||
});
|
||||
});
|
||||
@ -1,205 +1,147 @@
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Directive,
|
||||
EmbeddedViewRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
TemplateRef,
|
||||
ViewContainerRef,
|
||||
ɵstringify as stringify,
|
||||
} from '@angular/core';
|
||||
|
||||
import { merge, Subscription } from 'rxjs';
|
||||
import { skip, take } from 'rxjs/operators';
|
||||
import { merge, Subject, Subscription, switchMap } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
import { isBoolean, notEmptyValue } from './utils';
|
||||
import { notEmpty } from './utils';
|
||||
import { IqserRolesService } from './services/roles.service';
|
||||
import { IqserPermissionsService } from './services/permissions.service';
|
||||
|
||||
type NgTemplate = TemplateRef<unknown>;
|
||||
|
||||
@Directive({
|
||||
selector: '[ngxPermissionsOnly],[ngxPermissionsExcept]',
|
||||
selector: '[iqserPermissions]',
|
||||
})
|
||||
export class NgxPermissionsDirective implements OnInit, OnDestroy, OnChanges {
|
||||
@Input() ngxPermissionsOnly!: string | string[];
|
||||
@Input() ngxPermissionsOnlyThen!: TemplateRef<any>;
|
||||
@Input() ngxPermissionsOnlyElse!: TemplateRef<any>;
|
||||
export class IqserPermissionsDirective implements OnDestroy, OnInit {
|
||||
/**
|
||||
* Assert the correct type of the expression bound to the `iqserPermissions` input within the template.
|
||||
*
|
||||
* The presence of this static field is a signal to the Ivy template type check compiler that
|
||||
* when the `IqserPermissionsDirective` structural directive renders its template, the type of the expression bound
|
||||
* to `iqserPermissions` should be narrowed in some way.
|
||||
* For `iqserPermissions`, the binding expression itself is used to
|
||||
* narrow its type, which allows the strictNullChecks feature of TypeScript to work with `IqserPermissionsDirective`.
|
||||
*/
|
||||
static ngTemplateGuard_iqserPermissions: 'binding';
|
||||
|
||||
@Input() ngxPermissionsExcept!: string | string[];
|
||||
@Input() ngxPermissionsExceptElse!: TemplateRef<any>;
|
||||
@Input() ngxPermissionsExceptThen!: TemplateRef<any>;
|
||||
@Output() readonly permissionsAuthorized = new EventEmitter();
|
||||
@Output() readonly permissionsUnauthorized = new EventEmitter();
|
||||
|
||||
@Input() ngxPermissionsThen!: TemplateRef<any>;
|
||||
@Input() ngxPermissionsElse!: TemplateRef<any>;
|
||||
#permissions?: string | string[];
|
||||
#thenTemplateRef: TemplateRef<unknown>;
|
||||
#elseTemplateRef?: TemplateRef<unknown>;
|
||||
#thenViewRef?: EmbeddedViewRef<unknown>;
|
||||
#elseViewRef?: EmbeddedViewRef<unknown>;
|
||||
|
||||
@Output() permissionsAuthorized = new EventEmitter();
|
||||
@Output() permissionsUnauthorized = new EventEmitter();
|
||||
|
||||
private initPermissionSubscription?: Subscription;
|
||||
// skip first run cause merge will fire twice
|
||||
private firstMergeUnusedRun = 1;
|
||||
private currentAuthorizedState?: boolean;
|
||||
readonly #updateView = new Subject<void>();
|
||||
readonly #subscription = new Subscription();
|
||||
|
||||
constructor(
|
||||
private permissionsService: IqserPermissionsService,
|
||||
private rolesService: IqserRolesService,
|
||||
private viewContainer: ViewContainerRef,
|
||||
private changeDetector: ChangeDetectorRef,
|
||||
private templateRef: TemplateRef<any>,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.viewContainer.clear();
|
||||
this.initPermissionSubscription = this.validateExceptOnlyPermissions();
|
||||
private readonly _permissionsService: IqserPermissionsService,
|
||||
private readonly _rolesService: IqserRolesService,
|
||||
private readonly _viewContainer: ViewContainerRef,
|
||||
templateRef: TemplateRef<unknown>,
|
||||
) {
|
||||
this.#thenTemplateRef = templateRef;
|
||||
this.#subscription = this.#updateView.pipe(switchMap(() => this.#waitForRolesAndPermissions())).subscribe();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
const onlyChanges = changes['ngxPermissionsOnly'];
|
||||
const exceptChanges = changes['ngxPermissionsExcept'];
|
||||
if (onlyChanges || exceptChanges) {
|
||||
// Due to bug when you pass empty array
|
||||
if (onlyChanges && onlyChanges.firstChange) {
|
||||
return;
|
||||
}
|
||||
if (exceptChanges && exceptChanges.firstChange) {
|
||||
return;
|
||||
}
|
||||
@Input()
|
||||
set iqserPermissions(value: string | string[]) {
|
||||
this.#permissions = value;
|
||||
this.#updateView.next();
|
||||
}
|
||||
|
||||
merge(this.permissionsService.permissions$, this.rolesService.roles$)
|
||||
.pipe(skip(this.firstMergeUnusedRun), take(1))
|
||||
.subscribe(() => {
|
||||
if (notEmptyValue(this.ngxPermissionsExcept)) {
|
||||
this.validateExceptAndOnlyPermissions();
|
||||
return;
|
||||
}
|
||||
@Input()
|
||||
set iqserPermissionsThen(template: NgTemplate) {
|
||||
assertTemplate('iqserPermissionsThen', template);
|
||||
this.#thenTemplateRef = template;
|
||||
this.#thenViewRef = undefined;
|
||||
this.#updateView.next();
|
||||
}
|
||||
|
||||
if (notEmptyValue(this.ngxPermissionsOnly)) {
|
||||
this.validateOnlyPermissions();
|
||||
return;
|
||||
}
|
||||
@Input()
|
||||
set iqserPermissionsElse(template: NgTemplate) {
|
||||
assertTemplate('iqserPermissionsElse', template);
|
||||
this.#elseTemplateRef = template;
|
||||
this.#elseViewRef = undefined;
|
||||
this.#updateView.next();
|
||||
}
|
||||
|
||||
this.handleAuthorisedPermission(this.getAuthorisedTemplates());
|
||||
});
|
||||
}
|
||||
/**
|
||||
This assures that when the directive has an empty input (such as [iqserPermissions]="") the view is updated
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.#updateView.next();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.initPermissionSubscription) {
|
||||
this.initPermissionSubscription.unsubscribe();
|
||||
}
|
||||
this.#subscription.unsubscribe();
|
||||
}
|
||||
|
||||
private validateExceptOnlyPermissions(): Subscription {
|
||||
return merge(this.permissionsService.permissions$, this.rolesService.roles$)
|
||||
.pipe(skip(this.firstMergeUnusedRun))
|
||||
.subscribe(() => {
|
||||
if (notEmptyValue(this.ngxPermissionsExcept)) {
|
||||
this.validateExceptAndOnlyPermissions();
|
||||
return;
|
||||
}
|
||||
|
||||
if (notEmptyValue(this.ngxPermissionsOnly)) {
|
||||
this.validateOnlyPermissions();
|
||||
return;
|
||||
}
|
||||
this.handleAuthorisedPermission(this.getAuthorisedTemplates());
|
||||
});
|
||||
#waitForRolesAndPermissions() {
|
||||
return merge(this._permissionsService.permissions$, this._rolesService.roles$).pipe(
|
||||
tap(() => (notEmpty(this.#permissions) ? this.#validate() : this.#showThenBlock())),
|
||||
);
|
||||
}
|
||||
|
||||
private validateExceptAndOnlyPermissions(): void {
|
||||
Promise.all([
|
||||
this.permissionsService.hasPermission(this.ngxPermissionsExcept),
|
||||
this.rolesService.hasOnlyRoles(this.ngxPermissionsExcept),
|
||||
])
|
||||
.then(([hasPermission, hasRole]) => {
|
||||
if (hasPermission || hasRole) {
|
||||
this.handleUnauthorisedPermission(this.ngxPermissionsExceptElse || this.ngxPermissionsElse);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!!this.ngxPermissionsOnly) {
|
||||
throw false;
|
||||
}
|
||||
|
||||
this.handleAuthorisedPermission(this.ngxPermissionsExceptThen || this.ngxPermissionsThen || this.templateRef);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!!this.ngxPermissionsOnly) {
|
||||
this.validateOnlyPermissions();
|
||||
} else {
|
||||
this.handleAuthorisedPermission(this.ngxPermissionsExceptThen || this.ngxPermissionsThen || this.templateRef);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private validateOnlyPermissions(): void {
|
||||
Promise.all([
|
||||
this.permissionsService.hasPermission(this.ngxPermissionsOnly),
|
||||
this.rolesService.hasOnlyRoles(this.ngxPermissionsOnly),
|
||||
])
|
||||
#validate(): void {
|
||||
Promise.all([this._permissionsService.has(this.#permissions), this._rolesService.has(this.#permissions)])
|
||||
.then(([hasPermissions, hasRoles]) => {
|
||||
if (hasPermissions || hasRoles) {
|
||||
this.handleAuthorisedPermission(this.ngxPermissionsOnlyThen || this.ngxPermissionsThen || this.templateRef);
|
||||
} else {
|
||||
this.handleUnauthorisedPermission(this.ngxPermissionsOnlyElse || this.ngxPermissionsElse);
|
||||
return this.#showThenBlock();
|
||||
}
|
||||
|
||||
return this.#showElseBlock();
|
||||
})
|
||||
.catch(() => {
|
||||
this.handleUnauthorisedPermission(this.ngxPermissionsOnlyElse || this.ngxPermissionsElse);
|
||||
});
|
||||
.catch(() => this.#showElseBlock());
|
||||
}
|
||||
|
||||
private handleUnauthorisedPermission(template: TemplateRef<any>): void {
|
||||
if (isBoolean(this.currentAuthorizedState) && !this.currentAuthorizedState) {
|
||||
#showElseBlock() {
|
||||
if (this.#elseViewRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentAuthorizedState = false;
|
||||
this.permissionsUnauthorized.emit();
|
||||
|
||||
if (!this.elseBlockDefined()) {
|
||||
return;
|
||||
} else {
|
||||
this.showTemplateBlockInView(template);
|
||||
}
|
||||
this.#thenViewRef = undefined;
|
||||
this.#elseViewRef = this.#showTemplate(this.#elseTemplateRef);
|
||||
}
|
||||
|
||||
private handleAuthorisedPermission(template: TemplateRef<any>): void {
|
||||
if (isBoolean(this.currentAuthorizedState) && this.currentAuthorizedState) {
|
||||
#showThenBlock() {
|
||||
if (this.#thenViewRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentAuthorizedState = true;
|
||||
this.permissionsAuthorized.emit();
|
||||
|
||||
if (!this.thenBlockDefined()) {
|
||||
return;
|
||||
} else {
|
||||
this.showTemplateBlockInView(template);
|
||||
}
|
||||
this.#elseViewRef = undefined;
|
||||
this.#thenViewRef = this.#showTemplate(this.#thenTemplateRef);
|
||||
}
|
||||
|
||||
private showTemplateBlockInView(template: TemplateRef<any>): void {
|
||||
this.viewContainer.clear();
|
||||
#showTemplate(template?: NgTemplate) {
|
||||
this._viewContainer.clear();
|
||||
|
||||
if (!template) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.viewContainer.createEmbeddedView(template);
|
||||
this.changeDetector.markForCheck();
|
||||
}
|
||||
|
||||
private getAuthorisedTemplates(): TemplateRef<any> {
|
||||
return this.ngxPermissionsOnlyThen || this.ngxPermissionsExceptThen || this.ngxPermissionsThen || this.templateRef;
|
||||
}
|
||||
|
||||
private elseBlockDefined(): boolean {
|
||||
return !!this.ngxPermissionsExceptElse || !!this.ngxPermissionsElse;
|
||||
}
|
||||
|
||||
private thenBlockDefined() {
|
||||
return !!this.ngxPermissionsExceptThen || !!this.ngxPermissionsThen;
|
||||
return this._viewContainer.createEmbeddedView(template);
|
||||
}
|
||||
}
|
||||
|
||||
function assertTemplate(property: string, templateRef: TemplateRef<unknown>): void {
|
||||
const isTemplateRefOrNull = !!(!templateRef || templateRef.createEmbeddedView);
|
||||
if (!isTemplateRefOrNull) {
|
||||
throw new Error(`${property} must be a TemplateRef, but received '${stringify(templateRef)}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { NgModule, Optional } from '@angular/core';
|
||||
import { NgxPermissionsDirective } from './permissions.directive';
|
||||
import { IqserPermissionsDirective } from './permissions.directive';
|
||||
import { IqserPermissionsService } from './services/permissions.service';
|
||||
import { IqserPermissionsGuard } from './services/permissions-guard.service';
|
||||
import { IqserRolesService } from './services/roles.service';
|
||||
|
||||
@NgModule({
|
||||
declarations: [NgxPermissionsDirective],
|
||||
declarations: [IqserPermissionsDirective],
|
||||
exports: [IqserPermissionsDirective],
|
||||
})
|
||||
export class IqserPermissionsModule {
|
||||
constructor(@Optional() permissionsService: IqserPermissionsService) {
|
||||
|
||||
462
src/lib/permissions/services/permissions-guard.service.spec.ts
Normal file
462
src/lib/permissions/services/permissions-guard.service.spec.ts
Normal file
@ -0,0 +1,462 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { IqserActivatedRouteSnapshot, IqserPermissionsModule } from '..';
|
||||
import { IqserPermissionsService } from './permissions.service';
|
||||
import { IqserRolesService } from './roles.service';
|
||||
import { IqserPermissionsGuard } from './permissions-guard.service';
|
||||
|
||||
const ADMIN = 'ADMIN' as const;
|
||||
|
||||
const defaultRouterState = {} as RouterStateSnapshot;
|
||||
const defaultRouter: Partial<Router> = {
|
||||
navigate: () => Promise.resolve(true),
|
||||
};
|
||||
|
||||
let router: Router;
|
||||
let routerNavigationSpy: jest.SpyInstance;
|
||||
let testRoute: Partial<IqserActivatedRouteSnapshot>;
|
||||
let permissionGuard: IqserPermissionsGuard;
|
||||
let permissionsService: IqserPermissionsService;
|
||||
|
||||
function configureTestBed() {
|
||||
router = { ...defaultRouter } as Router;
|
||||
routerNavigationSpy = jest.spyOn(router, 'navigate');
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [IqserPermissionsModule.forRoot()],
|
||||
providers: [
|
||||
{
|
||||
provide: Router,
|
||||
useValue: router,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
permissionGuard = TestBed.inject(IqserPermissionsGuard);
|
||||
permissionsService = TestBed.inject(IqserPermissionsService);
|
||||
}
|
||||
|
||||
describe('Permissions guard', () => {
|
||||
beforeEach(async () => {
|
||||
configureTestBed();
|
||||
permissionsService.add([ADMIN]);
|
||||
});
|
||||
|
||||
it('should create an instance', () => {
|
||||
expect(permissionGuard).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return true when only fulfils', async () => {
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: ADMIN,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(routerNavigationSpy).not.toHaveBeenCalled();
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return true when only is empty array', async () => {
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(routerNavigationSpy).not.toHaveBeenCalled();
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return true when no permissions specified', async () => {
|
||||
testRoute = {};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(routerNavigationSpy).not.toHaveBeenCalled();
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false when only doesnt match', async () => {
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: 'DOESNT MATCH',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(routerNavigationSpy).not.toHaveBeenCalled();
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return false when no permission match', async () => {
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: ['DOESNT MATCH', 'DOESNT MATCH 2'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(routerNavigationSpy).not.toHaveBeenCalled();
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return false when only doesnt match and navigate to 404', async () => {
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: 'DOESNT MATCH',
|
||||
redirectTo: './404',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(result).toEqual(false);
|
||||
expect(routerNavigationSpy).toHaveBeenCalledWith(['./404']);
|
||||
});
|
||||
|
||||
it('should return false when only doesnt match and navigate to array 404', async () => {
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: 'DOESNT MATCH',
|
||||
redirectTo: ['./404'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(result).toEqual(false);
|
||||
expect(routerNavigationSpy).toHaveBeenCalledWith(['./404']);
|
||||
});
|
||||
|
||||
it('should return false when only doesnt match and navigate to redirectTo function', async () => {
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: 'DOESNT MATCH',
|
||||
redirectTo: () => ['./403'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(result).toEqual(false);
|
||||
expect(routerNavigationSpy).toHaveBeenCalledWith(['./403']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Permissions guard dynamically', () => {
|
||||
beforeEach(async () => {
|
||||
configureTestBed();
|
||||
permissionsService.add(ADMIN);
|
||||
});
|
||||
|
||||
it('should return true when only function matches', async () => {
|
||||
testRoute = {
|
||||
params: {
|
||||
id: 44,
|
||||
},
|
||||
data: {
|
||||
permissions: {
|
||||
allow: route => {
|
||||
if ((route as ActivatedRouteSnapshot).params['id'] === 44) {
|
||||
return [ADMIN];
|
||||
}
|
||||
|
||||
return 'notManager';
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(routerNavigationSpy).not.toHaveBeenCalled();
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false when only function is called and should redirect', async () => {
|
||||
testRoute = {
|
||||
params: {
|
||||
id: 100,
|
||||
},
|
||||
data: {
|
||||
permissions: {
|
||||
allow: route => {
|
||||
if ((route as ActivatedRouteSnapshot).params['id'] === 44) {
|
||||
return [ADMIN];
|
||||
}
|
||||
|
||||
return 'notManager';
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(routerNavigationSpy).not.toHaveBeenCalled();
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Permissions guard dynamic redirectTo', () => {
|
||||
beforeEach(() => {
|
||||
configureTestBed();
|
||||
permissionsService.add(ADMIN);
|
||||
});
|
||||
|
||||
it('should redirect to parameters from navigationCommands and navigationExtras', async () => {
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: 'TIED',
|
||||
redirectTo: {
|
||||
navigationCommands: ['123'],
|
||||
navigationExtras: {
|
||||
skipLocationChange: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(result).toEqual(false);
|
||||
expect(routerNavigationSpy).toHaveBeenCalledWith(['123'], { skipLocationChange: true });
|
||||
});
|
||||
|
||||
it('should redirect to function parameters from navigationCommands and navigationExtras', async () => {
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: 'TIED',
|
||||
redirectTo: {
|
||||
navigationCommands: () => ['123'],
|
||||
navigationExtras: () => ({
|
||||
skipLocationChange: true,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(result).toEqual(false);
|
||||
expect(routerNavigationSpy).toHaveBeenCalledWith(['123'], { skipLocationChange: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Permissions guard with multiple redirectTo rules', () => {
|
||||
beforeEach(() => {
|
||||
configureTestBed();
|
||||
permissionsService.add('canReadAgenda');
|
||||
});
|
||||
|
||||
it('should fail on canEditAgenda and redirect to dashboard', async () => {
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: ['canReadAgenda', 'canEditAgenda', 'canRun'],
|
||||
redirectTo: {
|
||||
canReadAgenda: 'agendaList',
|
||||
canEditAgenda: 'dashboard',
|
||||
default: 'login',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(result).toEqual(false);
|
||||
expect(routerNavigationSpy).toHaveBeenCalledWith(['dashboard']);
|
||||
});
|
||||
|
||||
it('should redirect to dashboard when canEditAgenda fails', async () => {
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: ['canReadAgenda', 'canEditAgenda', 'canRun'],
|
||||
redirectTo: {
|
||||
canReadAgenda: 'agendaList',
|
||||
canEditAgenda: () => 'dashboard',
|
||||
default: 'login',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(result).toEqual(false);
|
||||
expect(routerNavigationSpy).toHaveBeenCalledWith(['dashboard']);
|
||||
});
|
||||
|
||||
it('should redirect to 123 when canEditAgenda fails with navigation parameters', async () => {
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: ['canEditAgenda', 'canRun'],
|
||||
redirectTo: {
|
||||
canReadAgenda: 'agendaList',
|
||||
canEditAgenda: {
|
||||
navigationCommands: ['123'],
|
||||
navigationExtras: {
|
||||
skipLocationChange: true,
|
||||
},
|
||||
},
|
||||
default: 'login',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(result).toEqual(false);
|
||||
expect(routerNavigationSpy).toHaveBeenCalledWith(['123'], { skipLocationChange: true });
|
||||
});
|
||||
|
||||
it('should redirect to 123 when canEditAgenda returns parameters from function', async () => {
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: ['canReadAgenda', 'canEditAgenda', 'canRun'],
|
||||
redirectTo: {
|
||||
canReadAgenda: 'agendaList',
|
||||
canEditAgenda: () => {
|
||||
return {
|
||||
navigationCommands: ['123'],
|
||||
navigationExtras: {
|
||||
skipLocationChange: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
default: 'login',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(result).toEqual(false);
|
||||
expect(routerNavigationSpy).toHaveBeenCalledWith(['123'], { skipLocationChange: true });
|
||||
});
|
||||
|
||||
it('should redirect to default when a permission fails with no redirection rule', async () => {
|
||||
permissionsService.add(['canEditAgenda']);
|
||||
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: ['canEditAgenda', 'Can run'],
|
||||
redirectTo: {
|
||||
canEditAgenda: 'dashboard',
|
||||
default: 'login',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(result).toEqual(false);
|
||||
expect(routerNavigationSpy).toHaveBeenCalledWith(['login']);
|
||||
expect(routerNavigationSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should activate path when nothing fails', async () => {
|
||||
permissionsService.add('canEditAgenda');
|
||||
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: ['canEditAgenda'],
|
||||
redirectTo: {
|
||||
canReadAgenda: 'agendaList',
|
||||
canEditAgenda: 'dashboard',
|
||||
canRun: 'run',
|
||||
default: 'login',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(result).toEqual(true);
|
||||
expect(routerNavigationSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Permissions guard redirectTo as function', () => {
|
||||
beforeEach(() => {
|
||||
configureTestBed();
|
||||
permissionsService.add('canReadAgenda');
|
||||
});
|
||||
|
||||
it('should dynamically redirect', async () => {
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: ['canRun'],
|
||||
redirectTo: failedPermission => failedPermission,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(result).toEqual(false);
|
||||
expect(routerNavigationSpy).toHaveBeenCalledWith(['canRun']);
|
||||
expect(routerNavigationSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should allow to pass when at least one of parameters allow passing', async () => {
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: ['canReadAgenda', 'CAN_SWIM'],
|
||||
redirectTo: () => 'login',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(result).toEqual(true);
|
||||
expect(routerNavigationSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Role guard with redirectTo as function', () => {
|
||||
let roleService: IqserRolesService;
|
||||
|
||||
beforeEach(() => {
|
||||
configureTestBed();
|
||||
roleService = TestBed.inject(IqserRolesService);
|
||||
permissionsService.add('canReadAgenda');
|
||||
permissionsService.add('AWESOME');
|
||||
roleService.add('ADMIN', ['AWESOME', 'canReadAgenda']);
|
||||
});
|
||||
|
||||
it('should dynamically pass if one satisfies', async () => {
|
||||
roleService.add('RUN', ['BLABLA', 'BLABLA2']);
|
||||
|
||||
testRoute = {
|
||||
data: {
|
||||
permissions: {
|
||||
allow: ['RUN', 'AWESOME'],
|
||||
redirectTo: failedPermission => failedPermission,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await permissionGuard.canActivate(testRoute as ActivatedRouteSnapshot, defaultRouterState);
|
||||
expect(result).toEqual(true);
|
||||
expect(routerNavigationSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@ -10,27 +10,23 @@ import {
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
|
||||
import { firstValueFrom, forkJoin, from, Observable, of } from 'rxjs';
|
||||
import { firstValueFrom, forkJoin, from, of } from 'rxjs';
|
||||
import { first, mergeMap, tap } from 'rxjs/operators';
|
||||
|
||||
import {
|
||||
DEFAULT_REDIRECT_KEY,
|
||||
ExceptFn,
|
||||
IqserPermissionsRouterData,
|
||||
IqserRedirectToNavigationParameters,
|
||||
NavigationCommandsFn,
|
||||
NavigationExtrasFn,
|
||||
OnlyFn,
|
||||
RedirectTo,
|
||||
RedirectToFn,
|
||||
} from '../models/permissions-router-data.model';
|
||||
import { IqserPermissionsService } from './permissions.service';
|
||||
import { IqserRolesService } from './roles.service';
|
||||
import { isFunction, isPlainObject, transformStringToArray } from '../utils';
|
||||
import { isFunction, isRedirectWithParameters, isString, transformPermission } from '../utils';
|
||||
|
||||
export interface IqserPermissionsData {
|
||||
only?: string | string[];
|
||||
except?: string | string[];
|
||||
allow: string | string[];
|
||||
redirectTo?: RedirectTo | RedirectToFn;
|
||||
}
|
||||
|
||||
@ -42,167 +38,69 @@ export class IqserPermissionsGuard implements CanActivate, CanLoad, CanActivateC
|
||||
private readonly _router: Router,
|
||||
) {}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> | boolean {
|
||||
return this.#hasPermissions(route, state);
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
return this.#checkPermissions(route, state);
|
||||
}
|
||||
|
||||
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
|
||||
return this.#hasPermissions(childRoute, state);
|
||||
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
return this.#checkPermissions(childRoute, state);
|
||||
}
|
||||
|
||||
canLoad(route: Route): boolean | Observable<boolean> | Promise<boolean> {
|
||||
return this.#hasPermissions(route);
|
||||
canLoad(route: Route) {
|
||||
return this.#checkPermissions(route);
|
||||
}
|
||||
|
||||
passingOnlyPermissionsValidation(
|
||||
permissions: IqserPermissionsData,
|
||||
route: ActivatedRouteSnapshot | Route,
|
||||
state?: RouterStateSnapshot,
|
||||
): Promise<boolean> {
|
||||
if (
|
||||
isFunction<RedirectToFn>(permissions.redirectTo) ||
|
||||
(isPlainObject(permissions.redirectTo) && !this.#isRedirectionWithParameters(permissions.redirectTo))
|
||||
) {
|
||||
return this.#onlyRedirectCheck(permissions, route, state);
|
||||
}
|
||||
return this.#checkOnlyPermissions(permissions, route, state);
|
||||
}
|
||||
|
||||
#hasPermissions(route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) {
|
||||
const routeDataPermissions = !!route && route.data ? (route.data['permissions'] as IqserPermissionsRouterData) : {};
|
||||
const permissions = this.#transformPermission(routeDataPermissions, route, state);
|
||||
|
||||
if (this.#isParameterAvailable(permissions.except)) {
|
||||
return this.#passingExceptPermissionsValidation(permissions, route, state);
|
||||
#validate(permissions: IqserPermissionsData, route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) {
|
||||
if (isFunction<RedirectToFn>(permissions.redirectTo) || !isRedirectWithParameters(permissions.redirectTo)) {
|
||||
return this.#checkRedirect(permissions, route, state);
|
||||
}
|
||||
|
||||
if (this.#isParameterAvailable(permissions.only)) {
|
||||
return this.passingOnlyPermissionsValidation(permissions, route, state);
|
||||
return this.#validatePermissions(permissions, route, state);
|
||||
}
|
||||
|
||||
#checkPermissions(route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) {
|
||||
const routePermissions = route?.data ? (route.data['permissions'] as IqserPermissionsRouterData) : undefined;
|
||||
if (!routePermissions) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
const permissions = transformPermission(routePermissions, route, state);
|
||||
|
||||
#transformPermission(
|
||||
permissions: IqserPermissionsRouterData,
|
||||
route: ActivatedRouteSnapshot | Route,
|
||||
state?: RouterStateSnapshot,
|
||||
): IqserPermissionsData {
|
||||
const only = isFunction<OnlyFn>(permissions.only) ? permissions.only(route, state) : transformStringToArray(permissions.only);
|
||||
const except = isFunction<ExceptFn>(permissions.except)
|
||||
? permissions.except(route, state)
|
||||
: transformStringToArray(permissions.except);
|
||||
const redirectTo = permissions.redirectTo;
|
||||
|
||||
return {
|
||||
only,
|
||||
except,
|
||||
redirectTo,
|
||||
};
|
||||
}
|
||||
|
||||
#isParameterAvailable(permission?: string | string[]) {
|
||||
return !!permission && permission.length > 0;
|
||||
}
|
||||
|
||||
#passingExceptPermissionsValidation(
|
||||
permissions: IqserPermissionsData,
|
||||
route: ActivatedRouteSnapshot | Route,
|
||||
state?: RouterStateSnapshot,
|
||||
): Promise<boolean> {
|
||||
if (
|
||||
!!permissions.redirectTo &&
|
||||
(isFunction<RedirectToFn>(permissions.redirectTo) ||
|
||||
(isPlainObject(permissions.redirectTo) && !this.#isRedirectionWithParameters(permissions.redirectTo)))
|
||||
) {
|
||||
let failedPermission = '';
|
||||
|
||||
const res = from(permissions.except ?? []).pipe(
|
||||
mergeMap(permissionsExcept => {
|
||||
return forkJoin([
|
||||
this._permissionsService.hasPermission(permissionsExcept),
|
||||
this._rolesService.hasOnlyRoles(permissionsExcept),
|
||||
]).pipe(
|
||||
tap(hasPermissions => {
|
||||
const dontHavePermissions = hasPermissions.every(hasPermission => hasPermission === false);
|
||||
|
||||
if (!dontHavePermissions) {
|
||||
failedPermission = permissionsExcept;
|
||||
}
|
||||
}),
|
||||
);
|
||||
}),
|
||||
first(hasPermissions => hasPermissions.some(hasPermission => hasPermission === true), false),
|
||||
mergeMap(isAllFalse => {
|
||||
if (!!failedPermission) {
|
||||
this.#handleRedirectOfFailedPermission(permissions, failedPermission, route, state);
|
||||
|
||||
return of(false);
|
||||
}
|
||||
|
||||
if (!isAllFalse && permissions.only) {
|
||||
return this.#onlyRedirectCheck(permissions, route, state);
|
||||
}
|
||||
|
||||
return of(!isAllFalse);
|
||||
}),
|
||||
);
|
||||
|
||||
return firstValueFrom(res);
|
||||
if (permissions?.allow?.length > 0) {
|
||||
return this.#validate(permissions, route, state);
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
this._permissionsService.hasPermission(permissions.except),
|
||||
this._rolesService.hasOnlyRoles(permissions.except),
|
||||
]).then(([hasPermission, hasRoles]) => {
|
||||
if (hasPermission || hasRoles) {
|
||||
if (permissions.redirectTo) {
|
||||
this.#redirectToAnotherRoute(permissions.redirectTo, route, state);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (permissions.only) {
|
||||
return this.#checkOnlyPermissions(permissions, route, state);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
#redirectToAnotherRoute(
|
||||
permissionRedirectTo: RedirectTo | RedirectToFn,
|
||||
route: ActivatedRouteSnapshot | Route,
|
||||
failedPermissionName: string,
|
||||
state?: RouterStateSnapshot,
|
||||
failedPermissionName?: string,
|
||||
): void {
|
||||
) {
|
||||
const redirectTo = isFunction<RedirectToFn>(permissionRedirectTo)
|
||||
? permissionRedirectTo(failedPermissionName, route, state)
|
||||
: permissionRedirectTo;
|
||||
|
||||
if (this.#isRedirectionWithParameters(redirectTo)) {
|
||||
if (isRedirectWithParameters(redirectTo)) {
|
||||
redirectTo.navigationCommands = this.#transformNavigationCommands(redirectTo.navigationCommands, route, state);
|
||||
redirectTo.navigationExtras = this.#transformNavigationExtras(redirectTo.navigationExtras ?? {}, route, state);
|
||||
this._router.navigate(redirectTo.navigationCommands, redirectTo.navigationExtras);
|
||||
return;
|
||||
return this._router.navigate(redirectTo.navigationCommands, redirectTo.navigationExtras);
|
||||
}
|
||||
|
||||
if (Array.isArray(redirectTo)) {
|
||||
this._router.navigate(redirectTo);
|
||||
} else {
|
||||
this._router.navigate([redirectTo]);
|
||||
return this._router.navigate(redirectTo);
|
||||
}
|
||||
}
|
||||
|
||||
#isRedirectionWithParameters(object: any | IqserRedirectToNavigationParameters): object is IqserRedirectToNavigationParameters {
|
||||
return isPlainObject(object) && (!!object.navigationCommands || !!object.navigationExtras);
|
||||
return this._router.navigate([redirectTo]);
|
||||
}
|
||||
|
||||
#transformNavigationCommands(
|
||||
navigationCommands: any[] | NavigationCommandsFn,
|
||||
route: ActivatedRouteSnapshot | Route,
|
||||
state?: RouterStateSnapshot,
|
||||
): any[] {
|
||||
) {
|
||||
return isFunction<NavigationCommandsFn>(navigationCommands) ? navigationCommands(route, state) : navigationCommands;
|
||||
}
|
||||
|
||||
@ -214,24 +112,21 @@ export class IqserPermissionsGuard implements CanActivate, CanLoad, CanActivateC
|
||||
return isFunction<NavigationExtrasFn>(navigationExtras) ? navigationExtras(route, state) : navigationExtras;
|
||||
}
|
||||
|
||||
#onlyRedirectCheck(
|
||||
permissions: IqserPermissionsData,
|
||||
route: ActivatedRouteSnapshot | Route,
|
||||
state?: RouterStateSnapshot,
|
||||
): Promise<boolean> {
|
||||
let failedPermission = '';
|
||||
#checkRedirect(permissions: IqserPermissionsData, route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) {
|
||||
if (!permissions.allow || permissions.allow.length === 0) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
const res = from(permissions.only ?? []).pipe(
|
||||
mergeMap(permissionsOnly => {
|
||||
return forkJoin([
|
||||
this._permissionsService.hasPermission(permissionsOnly),
|
||||
this._rolesService.hasOnlyRoles(permissionsOnly),
|
||||
]).pipe(
|
||||
let failedPermission = '';
|
||||
const res = from(permissions.allow).pipe(
|
||||
mergeMap(permission => {
|
||||
return forkJoin([this._permissionsService.has(permission), this._rolesService.has(permission)]).pipe(
|
||||
tap(hasPermissions => {
|
||||
const failed = hasPermissions.every(hasPermission => hasPermission === false);
|
||||
|
||||
if (failed) {
|
||||
failedPermission = permissionsOnly;
|
||||
failedPermission = permission;
|
||||
console.log(`Permission ${permission} is not allowed`);
|
||||
}
|
||||
}),
|
||||
);
|
||||
@ -247,16 +142,18 @@ export class IqserPermissionsGuard implements CanActivate, CanLoad, CanActivateC
|
||||
if (isFunction<RedirectToFn>(permissions.redirectTo)) {
|
||||
if (pass) {
|
||||
return of(true);
|
||||
} else {
|
||||
this.#handleRedirectOfFailedPermission(permissions, failedPermission, route, state);
|
||||
return of(false);
|
||||
}
|
||||
} else {
|
||||
if (!!failedPermission) {
|
||||
this.#handleRedirectOfFailedPermission(permissions, failedPermission, route, state);
|
||||
}
|
||||
return of(!pass);
|
||||
|
||||
const redirectHandle = this.#handleRedirectOfFailedPermission(permissions, failedPermission, route, state);
|
||||
return redirectHandle.then(() => false);
|
||||
}
|
||||
|
||||
if (!!failedPermission) {
|
||||
const redirectHandle = this.#handleRedirectOfFailedPermission(permissions, failedPermission, route, state);
|
||||
return redirectHandle.then(() => !pass);
|
||||
}
|
||||
|
||||
return of(!pass);
|
||||
}),
|
||||
);
|
||||
|
||||
@ -269,29 +166,44 @@ export class IqserPermissionsGuard implements CanActivate, CanLoad, CanActivateC
|
||||
route: ActivatedRouteSnapshot | Route,
|
||||
state?: RouterStateSnapshot,
|
||||
) {
|
||||
if (this.#isFailedPermissionPropertyOfRedirectTo(permissions, failedPermission)) {
|
||||
if (!isFunction<RedirectToFn>(permissions.redirectTo)) {
|
||||
// @ts-ignore
|
||||
this.#redirectToAnotherRoute(permissions.redirectTo[failedPermission], route, state, failedPermission);
|
||||
}
|
||||
} else {
|
||||
if (isFunction<RedirectToFn>(permissions.redirectTo)) {
|
||||
this.#redirectToAnotherRoute(permissions.redirectTo, route, state, failedPermission);
|
||||
} else {
|
||||
if (permissions.redirectTo) {
|
||||
// @ts-ignore
|
||||
this.#redirectToAnotherRoute(permissions.redirectTo[DEFAULT_REDIRECT_KEY], route, state, failedPermission);
|
||||
}
|
||||
}
|
||||
const failedPermissionRedirectTo = this.#getFailedPermissionRedirectTo(permissions.redirectTo, failedPermission);
|
||||
|
||||
if (failedPermissionRedirectTo) {
|
||||
return this.#redirectToAnotherRoute(failedPermissionRedirectTo, route, failedPermission, state);
|
||||
}
|
||||
|
||||
if (isFunction<RedirectToFn>(permissions.redirectTo) || isString(permissions.redirectTo) || Array.isArray(permissions.redirectTo)) {
|
||||
return this.#redirectToAnotherRoute(permissions.redirectTo, route, failedPermission, state);
|
||||
}
|
||||
|
||||
if (permissions.redirectTo && !isRedirectWithParameters(permissions.redirectTo)) {
|
||||
const defaultRoute = permissions.redirectTo[DEFAULT_REDIRECT_KEY];
|
||||
|
||||
if (!defaultRoute) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
return this.#redirectToAnotherRoute(defaultRoute, route, failedPermission, state);
|
||||
}
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
#isFailedPermissionPropertyOfRedirectTo(permissions: IqserPermissionsData, failedPermission: string): boolean {
|
||||
// @ts-ignore
|
||||
return !!permissions.redirectTo && permissions.redirectTo[failedPermission];
|
||||
#getFailedPermissionRedirectTo(redirectTo: RedirectTo | RedirectToFn | undefined, failedPermission: string) {
|
||||
if (
|
||||
!!redirectTo &&
|
||||
!isFunction<RedirectToFn>(redirectTo) &&
|
||||
!isString(redirectTo) &&
|
||||
!isRedirectWithParameters(redirectTo) &&
|
||||
!Array.isArray(redirectTo)
|
||||
) {
|
||||
return redirectTo[failedPermission];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
#checkOnlyPermissions(
|
||||
#validatePermissions(
|
||||
purePermissions: IqserPermissionsData,
|
||||
route: ActivatedRouteSnapshot | Route,
|
||||
state?: RouterStateSnapshot,
|
||||
@ -300,19 +212,19 @@ export class IqserPermissionsGuard implements CanActivate, CanLoad, CanActivateC
|
||||
...purePermissions,
|
||||
};
|
||||
|
||||
return Promise.all([
|
||||
this._permissionsService.hasPermission(permissions.only),
|
||||
this._rolesService.hasOnlyRoles(permissions.only),
|
||||
]).then(([hasPermission, hasRole]) => {
|
||||
if (hasPermission || hasRole) {
|
||||
return true;
|
||||
}
|
||||
return Promise.all([this._permissionsService.has(permissions.allow), this._rolesService.has(permissions.allow)]).then(
|
||||
([hasPermission, hasRole]) => {
|
||||
if (hasPermission || hasRole) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (permissions.redirectTo) {
|
||||
this.#redirectToAnotherRoute(permissions.redirectTo, route, state);
|
||||
}
|
||||
if (permissions.redirectTo) {
|
||||
const redirect = this.#redirectToAnotherRoute(permissions.redirectTo, route, permissions.allow[0], state);
|
||||
return redirect.then(() => false);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
return false;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
157
src/lib/permissions/services/permissions.service.spec.ts
Normal file
157
src/lib/permissions/services/permissions.service.spec.ts
Normal file
@ -0,0 +1,157 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { IqserPermissionsModule } from '../permissions.module';
|
||||
import { IqserPermissionsService } from './permissions.service';
|
||||
|
||||
const ADMIN = 'ADMIN' as const;
|
||||
const GUEST = 'GUEST' as const;
|
||||
|
||||
describe('Permissions Service', () => {
|
||||
let permissionsService: IqserPermissionsService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [IqserPermissionsModule.forRoot()],
|
||||
});
|
||||
|
||||
permissionsService = TestBed.inject(IqserPermissionsService);
|
||||
});
|
||||
|
||||
it('should create an instance', () => {
|
||||
expect(permissionsService).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should add permission to permissions object', () => {
|
||||
expect(permissionsService.get(ADMIN)).toBeFalsy();
|
||||
permissionsService.add(ADMIN);
|
||||
expect(permissionsService.get(ADMIN)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should remove permission from permissions object', () => {
|
||||
expect(permissionsService.get(ADMIN)).toBeFalsy();
|
||||
permissionsService.add(ADMIN);
|
||||
expect(permissionsService.get(ADMIN)).toBeTruthy();
|
||||
permissionsService.remove(ADMIN);
|
||||
expect(permissionsService.get(ADMIN)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should remove all permissions from permissions object', () => {
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(0);
|
||||
|
||||
permissionsService.add(ADMIN);
|
||||
permissionsService.add(GUEST);
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(2);
|
||||
|
||||
permissionsService.clear();
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should add multiple permissions', () => {
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(0);
|
||||
|
||||
permissionsService.add([ADMIN, GUEST]);
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(2);
|
||||
expect(permissionsService.get()).toEqual({
|
||||
ADMIN: { name: 'ADMIN' },
|
||||
GUEST: { name: 'GUEST' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should return true when permission name is present in permissions object', async () => {
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(0);
|
||||
|
||||
permissionsService.add([ADMIN, GUEST]);
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(2);
|
||||
|
||||
let result = await permissionsService.has('ADMIN');
|
||||
expect(result).toEqual(true);
|
||||
|
||||
result = await permissionsService.has('SHOULDNOTHAVEROLE');
|
||||
expect(result).toEqual(false);
|
||||
|
||||
result = await permissionsService.has(['ADMIN']);
|
||||
expect(result).toEqual(true);
|
||||
|
||||
result = await permissionsService.has(['ADMIN', 'IRIISISTABLE']);
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return true when permission function return true', async () => {
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(0);
|
||||
|
||||
permissionsService.add(ADMIN, () => true);
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(1);
|
||||
|
||||
let result = await permissionsService.has('ADMIN');
|
||||
expect(result).toEqual(true);
|
||||
|
||||
permissionsService.add(GUEST, () => false);
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(2);
|
||||
|
||||
result = await permissionsService.has('GUEST');
|
||||
expect(result).toEqual(false);
|
||||
|
||||
permissionsService.add('TEST1', () => Promise.resolve(true));
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(3);
|
||||
|
||||
result = await permissionsService.has('TEST1');
|
||||
expect(result).toEqual(true);
|
||||
|
||||
permissionsService.add('TEST2', () => Promise.resolve(false));
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(4);
|
||||
|
||||
result = await permissionsService.has('TEST2');
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
// TODO: permissions array with function should not be allowed
|
||||
it('should return true when permissions array function return true', async () => {
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(0);
|
||||
|
||||
permissionsService.add([ADMIN], () => true);
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(1);
|
||||
|
||||
let result = await permissionsService.has('ADMIN');
|
||||
expect(result).toEqual(true);
|
||||
|
||||
permissionsService.add([GUEST], () => false);
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(2);
|
||||
|
||||
result = await permissionsService.has('GUEST');
|
||||
expect(result).toEqual(false);
|
||||
|
||||
permissionsService.add(['TEST1'], () => Promise.resolve(true));
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(3);
|
||||
|
||||
result = await permissionsService.has('TEST1');
|
||||
expect(result).toEqual(true);
|
||||
|
||||
permissionsService.add(['TEST9'], () => Promise.resolve(false));
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(4);
|
||||
|
||||
result = await permissionsService.has(['TEST9']);
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should call validationFn with permission name and store', async () => {
|
||||
permissionsService.add('TEST11', (name, store) => {
|
||||
expect(name).toEqual('TEST11');
|
||||
expect(store['TEST11']).toBeTruthy();
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(1);
|
||||
|
||||
const result = await permissionsService.has(['TEST11']);
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return true when called with empty parameters', async () => {
|
||||
const result = await permissionsService.has('');
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return true when called with empty array', async () => {
|
||||
const result = await permissionsService.has([]);
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
});
|
||||
@ -1,15 +1,11 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { BehaviorSubject, from, Observable, of } from 'rxjs';
|
||||
import { BehaviorSubject, firstValueFrom, from, Observable, of } from 'rxjs';
|
||||
import { catchError, first, map, mergeAll, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { isBoolean, isFunction, transformStringToArray } from '../utils';
|
||||
import { isFunction, toArray } from '../utils';
|
||||
import { ValidationFn } from '../models/permissions-router-data.model';
|
||||
import { IqserPermission } from '../models/permission.model';
|
||||
|
||||
export interface IqserPermissionsObject {
|
||||
[name: string]: IqserPermission;
|
||||
}
|
||||
import { IqserPermission, IqserPermissionsObject } from '../models/permission.model';
|
||||
|
||||
@Injectable()
|
||||
export class IqserPermissionsService {
|
||||
@ -23,103 +19,85 @@ export class IqserPermissionsService {
|
||||
/**
|
||||
* Remove all permissions from permissions source
|
||||
*/
|
||||
public flushPermissions(): void {
|
||||
clear(): void {
|
||||
this.#permissions$.next({});
|
||||
}
|
||||
|
||||
public hasPermission(permission?: string | string[]): Promise<boolean> {
|
||||
has(permission?: string | string[]): Promise<boolean> {
|
||||
if (!permission || (Array.isArray(permission) && permission.length === 0)) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
permission = transformStringToArray(permission);
|
||||
return this.hasArrayPermission(permission);
|
||||
permission = toArray(permission);
|
||||
return this.#hasArray(permission);
|
||||
}
|
||||
|
||||
public loadPermissions(permissions: string[], validationFunction?: ValidationFn): void {
|
||||
const newPermissions = permissions.reduce((source, name) => this.reducePermission(source, name, validationFunction), {});
|
||||
load(permissions: string[], validationFn?: ValidationFn<IqserPermission>): void {
|
||||
const newPermissions = permissions.reduce((source, name) => this.#reduce(source, name, validationFn), {});
|
||||
this.#permissions$.next(newPermissions);
|
||||
}
|
||||
|
||||
public addPermission(permission: string | string[], validationFunction?: ValidationFn): void {
|
||||
if (Array.isArray(permission)) {
|
||||
const permissions = permission.reduce(
|
||||
(source, name) => this.reducePermission(source, name, validationFunction),
|
||||
this.#permissions$.value,
|
||||
);
|
||||
add(permission: string | string[], validationFn?: ValidationFn<IqserPermission>) {
|
||||
const permissions = toArray(permission).reduce(
|
||||
(source, name) => this.#reduce(source, name, validationFn),
|
||||
this.#permissions$.value,
|
||||
);
|
||||
|
||||
this.#permissions$.next(permissions);
|
||||
} else {
|
||||
const permissions = this.reducePermission(this.#permissions$.value, permission, validationFunction);
|
||||
|
||||
this.#permissions$.next(permissions);
|
||||
}
|
||||
return this.#permissions$.next(permissions);
|
||||
}
|
||||
|
||||
public removePermission(permissionName: string): void {
|
||||
const permissions = {
|
||||
...this.#permissions$.value,
|
||||
};
|
||||
delete permissions[permissionName];
|
||||
remove(name: string): void {
|
||||
const permissions = { ...this.#permissions$.value };
|
||||
delete permissions[name];
|
||||
this.#permissions$.next(permissions);
|
||||
}
|
||||
|
||||
public getPermission(name: string): IqserPermission | undefined {
|
||||
return this.#permissions$.value[name];
|
||||
get(): IqserPermissionsObject;
|
||||
get(name: string): IqserPermission | undefined;
|
||||
get(name?: string): IqserPermission | IqserPermissionsObject | undefined {
|
||||
return name ? this.#permissions$.value[name] : this.#permissions$.value;
|
||||
}
|
||||
|
||||
public getPermissions(): IqserPermissionsObject {
|
||||
return this.#permissions$.value;
|
||||
}
|
||||
|
||||
private reducePermission(source: IqserPermissionsObject, name: string, validationFunction?: ValidationFn): IqserPermissionsObject {
|
||||
if (!!validationFunction && isFunction(validationFunction)) {
|
||||
return {
|
||||
...source,
|
||||
[name]: { name, validationFunction },
|
||||
};
|
||||
#reduce(source: IqserPermissionsObject, name: string, validationFn?: ValidationFn<IqserPermission>): IqserPermissionsObject {
|
||||
if (!!validationFn && isFunction(validationFn)) {
|
||||
return { ...source, [name]: { name, validationFn } };
|
||||
}
|
||||
return {
|
||||
...source,
|
||||
[name]: { name },
|
||||
};
|
||||
|
||||
return { ...source, [name]: { name } };
|
||||
}
|
||||
|
||||
private hasArrayPermission(permissions: string[]): Promise<boolean> {
|
||||
#hasArray(permissions: string[]): Promise<boolean> {
|
||||
const promises = permissions.map(key => {
|
||||
if (this.hasPermissionValidationFunction(key)) {
|
||||
const validationFunction = this.#permissions$.value[key].validationFunction;
|
||||
if (this.#hasValidationFn(key)) {
|
||||
const validationFunction = this.#permissions$.value[key].validationFn;
|
||||
if (!validationFunction) {
|
||||
return of(false);
|
||||
}
|
||||
const immutableValue = { ...this.#permissions$.value };
|
||||
|
||||
return of(null).pipe(
|
||||
map(() => validationFunction(key, immutableValue)),
|
||||
switchMap(promise => (isBoolean(promise) ? of(promise) : promise)),
|
||||
switchMap(async () => validationFunction(key, immutableValue)),
|
||||
catchError(() => of(false)),
|
||||
);
|
||||
}
|
||||
|
||||
// check for name of the permission if there is no validation function
|
||||
return of(!!this.#permissions$.value[key]);
|
||||
});
|
||||
|
||||
return from(promises)
|
||||
.pipe(
|
||||
mergeAll(),
|
||||
first(data => data !== false, false),
|
||||
map(data => data !== false),
|
||||
)
|
||||
.toPromise()
|
||||
.then((data: any) => data);
|
||||
const res = from(promises).pipe(
|
||||
mergeAll(),
|
||||
first(data => data !== false, false),
|
||||
map(data => data !== false),
|
||||
);
|
||||
|
||||
return firstValueFrom(res);
|
||||
}
|
||||
|
||||
private hasPermissionValidationFunction(key: string): boolean {
|
||||
#hasValidationFn(key: string): boolean {
|
||||
return (
|
||||
!!this.#permissions$.value[key] &&
|
||||
!!this.#permissions$.value[key].validationFunction &&
|
||||
isFunction(this.#permissions$.value[key].validationFunction)
|
||||
!!this.#permissions$.value[key].validationFn &&
|
||||
isFunction(this.#permissions$.value[key].validationFn)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
209
src/lib/permissions/services/roles.service.spec.ts
Normal file
209
src/lib/permissions/services/roles.service.spec.ts
Normal file
@ -0,0 +1,209 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { ValidationFn } from '../models/permissions-router-data.model';
|
||||
import { IqserRolesService } from './roles.service';
|
||||
import { IqserPermissionsService } from './permissions.service';
|
||||
import { IqserPermissionsModule } from '../permissions.module';
|
||||
import { IqserRole } from '../models/role.model';
|
||||
|
||||
const ADMIN = 'ADMIN' as const;
|
||||
const GUEST = 'GUEST' as const;
|
||||
|
||||
describe('Roles Service', () => {
|
||||
let rolesService: IqserRolesService;
|
||||
let permissionsService: IqserPermissionsService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [IqserPermissionsModule.forRoot()],
|
||||
});
|
||||
|
||||
rolesService = TestBed.inject(IqserRolesService);
|
||||
permissionsService = TestBed.inject(IqserPermissionsService);
|
||||
});
|
||||
|
||||
it('should create an instance', () => {
|
||||
expect(rolesService).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should add role to role object', () => {
|
||||
expect(rolesService.get(ADMIN)).toBeFalsy();
|
||||
|
||||
rolesService.add(ADMIN, ['edit', 'remove']);
|
||||
|
||||
expect(rolesService.get(ADMIN)).toBeTruthy();
|
||||
expect(rolesService.get()).toEqual({ ADMIN: { name: ADMIN, validationFn: ['edit', 'remove'] } });
|
||||
});
|
||||
|
||||
it('should remove role from role object', () => {
|
||||
expect(rolesService.get(ADMIN)).toBeFalsy();
|
||||
|
||||
rolesService.add(ADMIN, ['edit', 'remove']);
|
||||
expect(rolesService.get(ADMIN)).toBeTruthy();
|
||||
|
||||
rolesService.remove(ADMIN);
|
||||
expect(rolesService.get(ADMIN)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should remove all roles from object', () => {
|
||||
expect(Object.keys(rolesService.get()).length).toEqual(0);
|
||||
|
||||
rolesService.add(ADMIN, ['edit', 'remove']);
|
||||
rolesService.add(GUEST, ['edit', 'remove']);
|
||||
expect(Object.keys(rolesService.get()).length).toEqual(2);
|
||||
|
||||
rolesService.clear();
|
||||
expect(Object.keys(rolesService.get()).length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should add multiple roles', () => {
|
||||
expect(Object.keys(rolesService.get()).length).toEqual(0);
|
||||
rolesService.add({
|
||||
ADMIN: ['Nice'],
|
||||
GUEST: ['Awesome'],
|
||||
});
|
||||
|
||||
expect(Object.keys(rolesService.get()).length).toEqual(2);
|
||||
expect(rolesService.get()).toEqual({
|
||||
ADMIN: { name: ADMIN, validationFn: ['Nice'] },
|
||||
GUEST: { name: GUEST, validationFn: ['Awesome'] },
|
||||
});
|
||||
});
|
||||
|
||||
it('return true when role name is present in Roles object', async () => {
|
||||
expect(Object.keys(rolesService.get()).length).toEqual(0);
|
||||
|
||||
rolesService.add({
|
||||
ADMIN: ['Nice'],
|
||||
GUEST: ['Awesome'],
|
||||
});
|
||||
|
||||
expect(Object.keys(rolesService.get()).length).toEqual(2);
|
||||
|
||||
let result = await rolesService.has(ADMIN);
|
||||
expect(result).toEqual(true);
|
||||
|
||||
result = await rolesService.has('SHOULDNOTHAVEROLE');
|
||||
expect(result).toEqual(false);
|
||||
|
||||
result = await rolesService.has([ADMIN, 'IRIISISTABLE']);
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('return true when role permission name is present in Roles object', async () => {
|
||||
expect(Object.keys(rolesService.get()).length).toEqual(0);
|
||||
|
||||
rolesService.add({
|
||||
ADMIN: ['Nice'],
|
||||
GUEST: ['Awesome'],
|
||||
});
|
||||
|
||||
expect(Object.keys(rolesService.get()).length).toEqual(2);
|
||||
let result = await rolesService.has(ADMIN);
|
||||
expect(result).toEqual(true);
|
||||
|
||||
result = await rolesService.has([ADMIN, 'IRRISISTABLE']);
|
||||
expect(result).toEqual(true);
|
||||
|
||||
result = await rolesService.has('SHOULDNOTHAVEROLE');
|
||||
expect(result).toEqual(false);
|
||||
|
||||
result = await rolesService.has(['SHOULDNOTHAVEROLE']);
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return role when requested with has role', () => {
|
||||
rolesService.add('role', () => true);
|
||||
const role = rolesService.get('role');
|
||||
|
||||
if (!role) {
|
||||
return expect(role).toBeTruthy();
|
||||
}
|
||||
|
||||
expect(role.name).toBe('role');
|
||||
|
||||
const validationFn = role.validationFn as ValidationFn<IqserRole>;
|
||||
expect(validationFn(role.name, rolesService.get())).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return true when checking with empty permission(not specified)', async () => {
|
||||
const result = await rolesService.has('');
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false when permission array is empty', async () => {
|
||||
const result = await rolesService.has('Empty');
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return false when role is not specified in the list', async () => {
|
||||
rolesService.add('test', ['One']);
|
||||
|
||||
const result = await rolesService.has('nice');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when passing empty array', async () => {
|
||||
rolesService.add('test', ['One']);
|
||||
|
||||
const result = await rolesService.has([]);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should add permissions to roles automatically', async () => {
|
||||
rolesService.add('test', ['one', 'two']);
|
||||
|
||||
const result = await rolesService.has('test');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should remove roles and permissions add the same time', async () => {
|
||||
rolesService.add('test', ['one', 'two']);
|
||||
|
||||
let result = await rolesService.has('test');
|
||||
expect(result).toBe(true);
|
||||
result = await permissionsService.has('one');
|
||||
expect(result).toBe(true);
|
||||
|
||||
rolesService.clear();
|
||||
permissionsService.clear();
|
||||
|
||||
result = await rolesService.has('test');
|
||||
expect(result).toBe(false);
|
||||
result = await permissionsService.has('one');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should remove all permissions and roles', () => {
|
||||
expect(Object.keys(rolesService.get()).length).toEqual(0);
|
||||
|
||||
rolesService.add(ADMIN, ['edit', 'remove']);
|
||||
rolesService.add(GUEST, ['edit1', 'remove2']);
|
||||
|
||||
expect(Object.keys(rolesService.get()).length).toEqual(2);
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(4);
|
||||
|
||||
rolesService.clear();
|
||||
permissionsService.clear();
|
||||
|
||||
expect(Object.keys(rolesService.get()).length).toEqual(0);
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should add multiple roles with permissions', () => {
|
||||
expect(Object.keys(rolesService.get()).length).toEqual(0);
|
||||
|
||||
rolesService.add({
|
||||
ADMIN: ['Nice'],
|
||||
GUEST: ['Awesome', 'Another awesome'],
|
||||
});
|
||||
|
||||
expect(Object.keys(rolesService.get()).length).toEqual(2);
|
||||
expect(rolesService.get()).toEqual({
|
||||
ADMIN: { name: ADMIN, validationFn: ['Nice'] },
|
||||
GUEST: { name: GUEST, validationFn: ['Awesome', 'Another awesome'] },
|
||||
});
|
||||
|
||||
expect(Object.keys(rolesService.get()).length).toEqual(2);
|
||||
expect(Object.keys(permissionsService.get()).length).toEqual(3);
|
||||
});
|
||||
});
|
||||
@ -6,7 +6,7 @@ import { catchError, every, first, map, mergeAll, mergeMap, switchMap } from 'rx
|
||||
import { IqserPermissionsService } from './permissions.service';
|
||||
import { ValidationFn } from '../models/permissions-router-data.model';
|
||||
import { IqserRole } from '../models/role.model';
|
||||
import { isBoolean, isFunction, isPromise, transformStringToArray } from '../utils';
|
||||
import { isFunction, isString, toArray } from '../utils';
|
||||
|
||||
export interface IqserRolesObject {
|
||||
[name: string]: IqserRole;
|
||||
@ -21,86 +21,76 @@ export class IqserRolesService {
|
||||
this.roles$ = this.#roles$.asObservable();
|
||||
}
|
||||
|
||||
addRole(name: string, validationFunction: ValidationFn | string[]) {
|
||||
const roles = {
|
||||
...this.#roles$.value,
|
||||
[name]: { name, validationFunction },
|
||||
};
|
||||
this.#roles$.next(roles);
|
||||
add(role: string, validationFn: ValidationFn<IqserRole> | string[]): void;
|
||||
add(rolesObj: Record<string, ValidationFn<IqserRole> | string[]>): void;
|
||||
add(role: string | Record<string, ValidationFn<IqserRole> | string[]>, validationFn?: ValidationFn<IqserRole> | string[]) {
|
||||
if (isString(role) && validationFn) {
|
||||
return this.#add(role, validationFn);
|
||||
}
|
||||
|
||||
if (typeof role === 'object') {
|
||||
return Object.keys(role).forEach(key => this.#add(key, role[key]));
|
||||
}
|
||||
|
||||
throw new Error('Invalid add role arguments');
|
||||
}
|
||||
|
||||
addRoleWithPermissions(name: string, permissions: string[]) {
|
||||
this._permissionsService.addPermission(permissions);
|
||||
this.addRole(name, permissions);
|
||||
}
|
||||
|
||||
addRoles(rolesObj: { [name: string]: ValidationFn | string[] }) {
|
||||
Object.keys(rolesObj).forEach((key, index) => {
|
||||
this.addRole(key, rolesObj[key]);
|
||||
});
|
||||
}
|
||||
|
||||
addRolesWithPermissions(rolesObj: { [name: string]: string[] }) {
|
||||
Object.keys(rolesObj).forEach((key, index) => {
|
||||
this.addRoleWithPermissions(key, rolesObj[key]);
|
||||
});
|
||||
}
|
||||
|
||||
flushRoles() {
|
||||
clear() {
|
||||
this.#roles$.next({});
|
||||
}
|
||||
|
||||
flushRolesAndPermissions() {
|
||||
this.flushRoles();
|
||||
this._permissionsService.flushPermissions();
|
||||
}
|
||||
|
||||
removeRole(roleName: string) {
|
||||
const roles = {
|
||||
...this.#roles$.value,
|
||||
};
|
||||
delete roles[roleName];
|
||||
remove(role: string) {
|
||||
const roles = { ...this.#roles$.value };
|
||||
delete roles[role];
|
||||
this.#roles$.next(roles);
|
||||
}
|
||||
|
||||
getRoles(): IqserRolesObject {
|
||||
return this.#roles$.value;
|
||||
get(): IqserRolesObject;
|
||||
|
||||
get(role: string): IqserRole | undefined;
|
||||
|
||||
get(role?: string): IqserRolesObject | IqserRole | undefined {
|
||||
return role ? this.#roles$.value[role] : this.#roles$.value;
|
||||
}
|
||||
|
||||
getRole(name: string): IqserRole | undefined {
|
||||
return this.#roles$.value[name];
|
||||
}
|
||||
|
||||
hasOnlyRoles(names?: string | string[]): Promise<boolean> {
|
||||
has(names?: string | string[]): Promise<boolean> {
|
||||
const isNamesEmpty = !names || (Array.isArray(names) && names.length === 0);
|
||||
|
||||
if (isNamesEmpty) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
names = transformStringToArray(names);
|
||||
names = toArray(names);
|
||||
|
||||
return Promise.all([this.#hasRoleKey(names), this.#hasRolePermission(this.#roles$.value, names)]).then(
|
||||
([hasRoles, hasPermissions]) => {
|
||||
return !!hasRoles || !!hasPermissions;
|
||||
},
|
||||
);
|
||||
return Promise.all([this.#hasRoleKey(names), this.#hasPermission(names)]).then(([hasRoles, hasPermissions]) => {
|
||||
return !!hasRoles || !!hasPermissions;
|
||||
});
|
||||
}
|
||||
|
||||
#add(role: string, validationFn: ValidationFn<IqserRole> | string[]) {
|
||||
const roles: IqserRolesObject = {
|
||||
...this.#roles$.value,
|
||||
[role]: { name: role, validationFn },
|
||||
};
|
||||
|
||||
if (Array.isArray(validationFn)) {
|
||||
this._permissionsService.add(validationFn);
|
||||
}
|
||||
|
||||
return this.#roles$.next(roles);
|
||||
}
|
||||
|
||||
#hasRoleKey(roleName: string[]): Promise<boolean> {
|
||||
const promises = roleName.map(key => {
|
||||
const hasValidationFunction =
|
||||
!!this.#roles$.value[key] &&
|
||||
!!this.#roles$.value[key].validationFunction &&
|
||||
isFunction(this.#roles$.value[key].validationFunction);
|
||||
const role = this.#roles$.value[key];
|
||||
const hasValidationFn = !!role && !!role.validationFn;
|
||||
|
||||
if (hasValidationFunction && !isPromise(this.#roles$.value[key].validationFunction)) {
|
||||
const validationFunction = this.#roles$.value[key].validationFunction as ValidationFn;
|
||||
if (hasValidationFn && isFunction<ValidationFn<IqserRole>>(role.validationFn)) {
|
||||
const validationFn = role.validationFn;
|
||||
const immutableValue = { ...this.#roles$.value };
|
||||
|
||||
return of(null).pipe(
|
||||
map(() => validationFunction(key, immutableValue)),
|
||||
switchMap(promise => (isBoolean(promise) ? of(promise) : promise)),
|
||||
switchMap(async () => validationFn(key, immutableValue)),
|
||||
catchError(() => of(false)),
|
||||
);
|
||||
}
|
||||
@ -117,12 +107,14 @@ export class IqserRolesService {
|
||||
return firstValueFrom(res);
|
||||
}
|
||||
|
||||
#hasRolePermission(roles: IqserRolesObject, roleNames: string[]): Promise<boolean | undefined> {
|
||||
#hasPermission(roleNames: string[]): Promise<boolean | undefined> {
|
||||
const res = from(roleNames).pipe(
|
||||
mergeMap(key => {
|
||||
if (roles[key] && Array.isArray(roles[key].validationFunction)) {
|
||||
return from(<string[]>roles[key].validationFunction).pipe(
|
||||
mergeMap(permission => this._permissionsService.hasPermission(permission)),
|
||||
const role = this.#roles$.value[key];
|
||||
|
||||
if (role && !isFunction<ValidationFn<IqserRole>>(role.validationFn)) {
|
||||
return from(role.validationFn).pipe(
|
||||
mergeMap(permission => this._permissionsService.has(permission)),
|
||||
every(hasPermission => hasPermission === true),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,38 +1,49 @@
|
||||
export function isFunction<T>(value: any): value is T {
|
||||
import { AllowFn, IqserPermissionsRouterData, IqserRedirectToNavigationParameters } from './models/permissions-router-data.model';
|
||||
import { ActivatedRouteSnapshot, Route, RouterStateSnapshot } from '@angular/router';
|
||||
import { IqserPermissionsData } from './services/permissions-guard.service';
|
||||
|
||||
export function isFunction<T>(value: unknown): value is T {
|
||||
return typeof value === 'function';
|
||||
}
|
||||
|
||||
export function isPlainObject(value: any): boolean {
|
||||
export function isPlainObject(value: unknown): boolean {
|
||||
if (Object.prototype.toString.call(value) !== '[object Object]') {
|
||||
return false;
|
||||
} else {
|
||||
const prototype = Object.getPrototypeOf(value);
|
||||
return prototype === null || prototype === Object.prototype;
|
||||
}
|
||||
|
||||
const prototype = Object.getPrototypeOf(value);
|
||||
return prototype === null || prototype === Object.prototype;
|
||||
}
|
||||
|
||||
export function isString(value: any): value is string {
|
||||
export function isString(value: unknown): value is string {
|
||||
return !!value && typeof value === 'string';
|
||||
}
|
||||
|
||||
export function isBoolean(value: any): value is boolean {
|
||||
return typeof value === 'boolean';
|
||||
export function notEmpty(value?: string | string[]): boolean {
|
||||
return Array.isArray(value) ? value.length > 0 : !!value;
|
||||
}
|
||||
|
||||
export function isPromise(promise: any) {
|
||||
return Object.prototype.toString.call(promise) === '[object Promise]';
|
||||
}
|
||||
|
||||
export function notEmptyValue(value: string | string[]): boolean {
|
||||
if (Array.isArray(value)) {
|
||||
return value.length > 0;
|
||||
}
|
||||
return !!value;
|
||||
}
|
||||
|
||||
export function transformStringToArray(value?: string | string[]): string[] {
|
||||
export function toArray(value?: string | string[]): string[] {
|
||||
if (isString(value)) {
|
||||
return [value];
|
||||
}
|
||||
return value ?? [];
|
||||
}
|
||||
|
||||
export function isRedirectWithParameters(object: any | IqserRedirectToNavigationParameters): object is IqserRedirectToNavigationParameters {
|
||||
return isPlainObject(object) && (!!object.navigationCommands || !!object.navigationExtras);
|
||||
}
|
||||
|
||||
export function transformPermission(
|
||||
permissions: IqserPermissionsRouterData,
|
||||
route: ActivatedRouteSnapshot | Route,
|
||||
state?: RouterStateSnapshot,
|
||||
): IqserPermissionsData {
|
||||
const only = isFunction<AllowFn>(permissions.allow) ? permissions.allow(route, state) : toArray(permissions.allow);
|
||||
const redirectTo = permissions.redirectTo;
|
||||
|
||||
return {
|
||||
allow: only,
|
||||
redirectTo,
|
||||
};
|
||||
}
|
||||
|
||||
9
tsconfig.spec.json
Normal file
9
tsconfig.spec.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc/spec",
|
||||
"types": ["jest", "node"],
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["./src/lib/**/*.spec.ts", "./src/lib/**/*.d.ts"]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user