Add deny directive
This commit is contained in:
parent
05d6488bc8
commit
bba66ea0d6
@ -1,8 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Type } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Type } from '@angular/core';
|
||||||
import { IqserPermissionsModule } from '.';
|
import { IqserPermissionsModule } from '../../index';
|
||||||
import { ComponentFixture, ComponentFixtureAutoDetect, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
import { ComponentFixture, ComponentFixtureAutoDetect, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||||
import { IqserPermissionsService } from './services/permissions.service';
|
import { IqserPermissionsService } from '../../services/permissions.service';
|
||||||
import { IqserRolesService } from './services/roles.service';
|
import { IqserRolesService } from '../../services/roles.service';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
class BaseTestComponent {
|
class BaseTestComponent {
|
||||||
@ -41,7 +41,7 @@ function getFixtureContent(): HTMLElement {
|
|||||||
|
|
||||||
describe('Permission directive', () => {
|
describe('Permission directive', () => {
|
||||||
@Component({
|
@Component({
|
||||||
template: ` <ng-template [allow]="'ADMIN'" (permissionsAuthorized)="isAuthorized()" (permissionsUnauthorized)="isUnauthorized()">
|
template: ` <ng-template [allow]="'ADMIN'" (authorized)="isAuthorized()" (unauthorized)="isUnauthorized()">
|
||||||
<div>123</div>
|
<div>123</div>
|
||||||
</ng-template>`,
|
</ng-template>`,
|
||||||
})
|
})
|
||||||
47
src/lib/permissions/directives/allow/allow.directive.ts
Normal file
47
src/lib/permissions/directives/allow/allow.directive.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { Directive, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { List } from '../../../utils';
|
||||||
|
import { assertTemplate, IqserPermissionsDirective } from '../permissions.directive';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[allow]',
|
||||||
|
})
|
||||||
|
export class IqserAllowDirective extends IqserPermissionsDirective implements OnDestroy, OnInit {
|
||||||
|
/**
|
||||||
|
* Assert the correct type of the expression bound to the `allow` 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 `allow` should be narrowed in some way.
|
||||||
|
* For `allow`, the binding expression itself is used to
|
||||||
|
* narrow its type, which allows the strictNullChecks feature of TypeScript to work with `IqserPermissionsDirective`.
|
||||||
|
*/
|
||||||
|
static ngTemplateGuard_allow: 'binding';
|
||||||
|
|
||||||
|
constructor(templateRef: TemplateRef<unknown>) {
|
||||||
|
super(templateRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set allow(value: string | List) {
|
||||||
|
this.setPermissions(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set allowThen(template: TemplateRef<unknown>) {
|
||||||
|
assertTemplate('allowThen', template);
|
||||||
|
this.setThenTemplateRef(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set allowElse(template: TemplateRef<unknown>) {
|
||||||
|
assertTemplate('allowElse', template);
|
||||||
|
this.setElseTemplateRef(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set allowIf(value: boolean | Promise<boolean> | Observable<boolean>) {
|
||||||
|
this.setIf(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
788
src/lib/permissions/directives/deny/deny.directive.spec.ts
Normal file
788
src/lib/permissions/directives/deny/deny.directive.spec.ts
Normal file
@ -0,0 +1,788 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, Type } from '@angular/core';
|
||||||
|
import { IqserPermissionsModule } from '../../index';
|
||||||
|
import { ComponentFixture, ComponentFixtureAutoDetect, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||||
|
import { IqserPermissionsService } from '../../services/permissions.service';
|
||||||
|
import { IqserRolesService } from '../../services/roles.service';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
|
class BaseTestComponent {
|
||||||
|
isAuthorized() {}
|
||||||
|
|
||||||
|
isUnauthorized() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ADMIN = 'ADMIN' as const;
|
||||||
|
const GUEST = 'GUEST' as const;
|
||||||
|
|
||||||
|
let fixture: ComponentFixture<BaseTestComponent>;
|
||||||
|
let permissionsService: IqserPermissionsService;
|
||||||
|
let rolesService: IqserRolesService;
|
||||||
|
let isAuthorizedSpy: jest.SpyInstance;
|
||||||
|
let isUnauthorizedSpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
function configureTestBed(component: Type<BaseTestComponent>) {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [component],
|
||||||
|
imports: [IqserPermissionsModule.forRoot()],
|
||||||
|
providers: [{ provide: ComponentFixtureAutoDetect, useValue: true }],
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(component);
|
||||||
|
isAuthorizedSpy = jest.spyOn(fixture.componentInstance, 'isAuthorized');
|
||||||
|
isUnauthorizedSpy = jest.spyOn(fixture.componentInstance, 'isUnauthorized');
|
||||||
|
|
||||||
|
permissionsService = TestBed.inject(IqserPermissionsService);
|
||||||
|
rolesService = TestBed.inject(IqserRolesService);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFixtureContent(): HTMLElement {
|
||||||
|
return fixture.debugElement.nativeElement.querySelector('div');
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Permission directive', () => {
|
||||||
|
@Component({
|
||||||
|
template: ` <ng-template [deny]="'ADMIN'" (authorized)="isAuthorized()" (unauthorized)="isUnauthorized()">
|
||||||
|
<div>123</div>
|
||||||
|
</ng-template>`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should show the component', fakeAsync(() => {
|
||||||
|
permissionsService.load([GUEST]);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('123');
|
||||||
|
tick();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not show the component', fakeAsync(() => {
|
||||||
|
permissionsService.load([ADMIN, GUEST]);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
expect(isAuthorizedSpy).toHaveBeenCalledTimes(0);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show component when permission removed', fakeAsync(() => {
|
||||||
|
permissionsService.load([ADMIN, GUEST]);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(isUnauthorizedSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
|
||||||
|
permissionsService.remove(ADMIN);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('123');
|
||||||
|
|
||||||
|
expect(isAuthorizedSpy).toHaveBeenCalledTimes(1);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should hide component when permission added', fakeAsync(() => {
|
||||||
|
permissionsService.load([GUEST]);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('123');
|
||||||
|
|
||||||
|
permissionsService.add(ADMIN);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
expect(isUnauthorizedSpy).toHaveBeenCalledTimes(1);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permission directive with roles', () => {
|
||||||
|
@Component({
|
||||||
|
template: ` <ng-template [deny]="'ADMIN'">
|
||||||
|
<div>123</div>
|
||||||
|
</ng-template>`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {}
|
||||||
|
|
||||||
|
const awesomePermissions = 'AWESOME';
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should hide the component when key of role is the same', fakeAsync(() => {
|
||||||
|
rolesService.add({ ADMIN: [awesomePermissions] });
|
||||||
|
permissionsService.add(awesomePermissions);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toBe(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show the component when user deletes all roles', fakeAsync(() => {
|
||||||
|
permissionsService.add(awesomePermissions);
|
||||||
|
rolesService.add({ ADMIN: [awesomePermissions] });
|
||||||
|
tick();
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
|
||||||
|
rolesService.clear();
|
||||||
|
tick();
|
||||||
|
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('123');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show the component when user deletes one role', fakeAsync(() => {
|
||||||
|
permissionsService.add(awesomePermissions);
|
||||||
|
rolesService.add({ ADMIN: [awesomePermissions] });
|
||||||
|
tick();
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
|
||||||
|
rolesService.remove(ADMIN);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('123');
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permission directive with roles array', () => {
|
||||||
|
@Component({
|
||||||
|
template: ` <ng-template [deny]="['ADMIN', 'GUEST']">
|
||||||
|
<div>123</div>
|
||||||
|
</ng-template>`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {}
|
||||||
|
|
||||||
|
const awesomePermission = 'AWESOME';
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should hide the component when key of role is the same', fakeAsync(() => {
|
||||||
|
permissionsService.add(awesomePermission);
|
||||||
|
rolesService.add({ ADMIN: [awesomePermission] });
|
||||||
|
rolesService.add({ GUEST: [awesomePermission] });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toBe(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show the component when user deletes all roles', fakeAsync(() => {
|
||||||
|
permissionsService.add(awesomePermission);
|
||||||
|
rolesService.add({ ADMIN: [awesomePermission] });
|
||||||
|
rolesService.add({ GUEST: [awesomePermission] });
|
||||||
|
tick();
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
|
||||||
|
rolesService.clear();
|
||||||
|
tick();
|
||||||
|
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('123');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should hide the component when user deletes one role', fakeAsync(() => {
|
||||||
|
permissionsService.add(awesomePermission);
|
||||||
|
rolesService.add({ ADMIN: [awesomePermission] });
|
||||||
|
rolesService.add({ GUEST: [awesomePermission] });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
|
||||||
|
rolesService.remove(ADMIN);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permission directive testing different selectors *deny', () => {
|
||||||
|
@Component({
|
||||||
|
template: ` <div *deny="['ADMIN']">
|
||||||
|
<div>123</div>
|
||||||
|
</div>`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('Should hide the component when key of role is the same', fakeAsync(() => {
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
permissionsService.add('AWESOME');
|
||||||
|
rolesService.add({ ADMIN: ['AWESOME'] });
|
||||||
|
tick();
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show the component when key of role is the same', fakeAsync(() => {
|
||||||
|
let content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
rolesService.add({ GG: ['Awsesome'] });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permission directive angular testing different async functions in roles', () => {
|
||||||
|
@Component({
|
||||||
|
template: ` <div *deny="'ADMIN'">
|
||||||
|
<div>123</div>
|
||||||
|
</div>`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should hide the component when promise returns truthy value', fakeAsync(() => {
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
rolesService.add({ ADMIN: () => true });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show the component when promise returns falsy value', fakeAsync(() => {
|
||||||
|
let content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
rolesService.add({ ADMIN: () => false });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permission directive angular testing different async functions in roles via array', () => {
|
||||||
|
@Component({
|
||||||
|
template: ` <div *deny="['ADMIN', 'GUEST']">
|
||||||
|
<div>123</div>
|
||||||
|
</div>`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should hide the component when returns truthy value', fakeAsync(() => {
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
rolesService.add({ ADMIN: () => true });
|
||||||
|
rolesService.add({ GUEST: () => true });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should hide the component when one returns falsy value', fakeAsync(() => {
|
||||||
|
let content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
rolesService.add({ ADMIN: () => false });
|
||||||
|
rolesService.add({ GUEST: () => true });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toBe(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show the component when all return falsy value', fakeAsync(() => {
|
||||||
|
let content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
rolesService.add({ ADMIN: () => false });
|
||||||
|
rolesService.add({ GUEST: () => false });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should hide the component when 1 passes second fails', fakeAsync(() => {
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
rolesService.add({ ADMIN: () => false });
|
||||||
|
rolesService.add({ GUEST: ['AWESOME'] });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permission directive with different async functions in permissions via array', () => {
|
||||||
|
@Component({
|
||||||
|
template: ` <div *deny="['ADMIN', 'GUEST']">
|
||||||
|
<div>123</div>
|
||||||
|
</div>`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should hide the component when returns truthy value', fakeAsync(() => {
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
permissionsService.add({ ADMIN: () => true });
|
||||||
|
permissionsService.add({ GUEST: () => true });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show the component when returns falsy value', fakeAsync(() => {
|
||||||
|
let content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
permissionsService.add({ ADMIN: () => false });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should hide the component when returns truthy value', fakeAsync(() => {
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
permissionsService.add({
|
||||||
|
ADMIN: () => true,
|
||||||
|
GUEST: () => true,
|
||||||
|
});
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not show the component when functions with name and store fulfils', fakeAsync(() => {
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
permissionsService.add({
|
||||||
|
ADMIN: (name, store) => {
|
||||||
|
expect(name).toBeTruthy();
|
||||||
|
expect(store[name!].name).toBeTruthy();
|
||||||
|
return name === ADMIN;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
permissionsService.add({ GUEST: () => false });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permission directive testing different async functions in permissions via string', () => {
|
||||||
|
@Component({
|
||||||
|
template: ` <div *deny="'ADMIN'">
|
||||||
|
<div>123</div>
|
||||||
|
</div>`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should show the component when promise returns truthy value', fakeAsync(() => {
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
permissionsService.add({ ADMIN: () => true });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show the component when promise returns false value', fakeAsync(() => {
|
||||||
|
let content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
permissionsService.add({ ADMIN: () => false });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show the component when only one of the promises fulfills', fakeAsync(() => {
|
||||||
|
let content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
permissionsService.add({ ADMIN: () => false });
|
||||||
|
permissionsService.add({ GUEST: () => true });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show the component when all promises fails', fakeAsync(() => {
|
||||||
|
let content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
permissionsService.add({ ADMIN: () => false });
|
||||||
|
permissionsService.add({ GUEST: () => false });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not show the component when functions with name and store fulfils', fakeAsync(() => {
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('<div>123</div>');
|
||||||
|
|
||||||
|
permissionsService.add({
|
||||||
|
ADMIN: (name, store) => {
|
||||||
|
expect(name).toBeTruthy();
|
||||||
|
expect(store[name!].name).toBeTruthy();
|
||||||
|
return name === ADMIN;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
permissionsService.add({ GUEST: () => false });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permissions directive with else block', () => {
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<div *deny="['FAILED_BLOCK']; else elseBlock">main</div>
|
||||||
|
|
||||||
|
<ng-template #elseBlock>
|
||||||
|
<div>elseBlockContent</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #thenBlock>
|
||||||
|
<div>thenBlockContent</div>
|
||||||
|
</ng-template>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should show else block when permissions fail', fakeAsync(() => {
|
||||||
|
let content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual(`main`);
|
||||||
|
|
||||||
|
permissionsService.add(ADMIN);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual(`elseBlockContent`);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show else block when permissions change', fakeAsync(() => {
|
||||||
|
rolesService.add({ FAILED_BLOCK: () => true });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('elseBlockContent');
|
||||||
|
|
||||||
|
rolesService.remove('FAILED_BLOCK');
|
||||||
|
tick();
|
||||||
|
|
||||||
|
const content2 = getFixtureContent();
|
||||||
|
expect(content2).toBeTruthy();
|
||||||
|
expect(content2.innerHTML).toEqual(`main`);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permissions directive with then block', () => {
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<div *deny="['THEN_BLOCK']; else elseBlock; then: thenBlock">main</div>
|
||||||
|
|
||||||
|
<ng-template #elseBlock>
|
||||||
|
<div>elseBlockContent</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #thenBlock>
|
||||||
|
<div>thenBlockContent</div>
|
||||||
|
</ng-template>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should pass and show then block', fakeAsync(() => {
|
||||||
|
rolesService.add({ THEN_BLOCK: () => false });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual(`thenBlockContent`);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permissions directive with empty argument', () => {
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<ng-template [deny]="">
|
||||||
|
<div>123</div>
|
||||||
|
</ng-template>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should succeed and show the component', () => {
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual(`123`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permissions directive with an empty array', () => {
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<ng-template [deny]="[]">
|
||||||
|
<div>123</div>
|
||||||
|
</ng-template>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should succeed and show the component', () => {
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual(`123`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permission directive with true if condition', () => {
|
||||||
|
@Component({
|
||||||
|
template: ` <ng-template [deny]="'ADMIN'" [denyIf]="true">
|
||||||
|
<div>123</div>
|
||||||
|
</ng-template>`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should show the component when permission is absent', () => {
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hide the component when permission is present', fakeAsync(() => {
|
||||||
|
permissionsService.add(ADMIN);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permission directive with true if condition', () => {
|
||||||
|
@Component({
|
||||||
|
template: ` <ng-template [deny]="'ADMIN'" [denyIf]="false">
|
||||||
|
<div>123</div>
|
||||||
|
</ng-template>`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should hide the component when permission is present', fakeAsync(() => {
|
||||||
|
permissionsService.add(ADMIN);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should hide the component when permission is absent', fakeAsync(() => {
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permission directive with true promise if condition', () => {
|
||||||
|
@Component({
|
||||||
|
template: ` <ng-template [deny]="'ADMIN'" [denyIf]="condition">
|
||||||
|
<div>123</div>
|
||||||
|
</ng-template>`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {
|
||||||
|
condition = Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should show the component when permission is absent', () => {
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual('123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hide the component when permission is present', fakeAsync(() => {
|
||||||
|
permissionsService.add(ADMIN);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permission directive with false promise if condition', () => {
|
||||||
|
@Component({
|
||||||
|
template: ` <div *deny="'ADMIN'; if: condition">
|
||||||
|
<div>123</div>
|
||||||
|
</div>`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {
|
||||||
|
condition = Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should hide the component when permission is present', fakeAsync(() => {
|
||||||
|
permissionsService.add(ADMIN);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should hide the component when permission is absent', fakeAsync(() => {
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permissions directive with then block and if condition', () => {
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<div *deny="['THEN_BLOCK']; if: condition; else: elseBlock; then: thenBlock">main</div>
|
||||||
|
|
||||||
|
<ng-template #elseBlock>
|
||||||
|
<div>elseBlockContent</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #thenBlock>
|
||||||
|
<div>thenBlockContent</div>
|
||||||
|
</ng-template>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {
|
||||||
|
condition = new BehaviorSubject(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should show then block when permission missing', fakeAsync(() => {
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual(`thenBlockContent`);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show else block when permission added', fakeAsync(() => {
|
||||||
|
permissionsService.add('THEN_BLOCK');
|
||||||
|
tick();
|
||||||
|
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual(`elseBlockContent`);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show else block when permission added but condition is false', fakeAsync(() => {
|
||||||
|
permissionsService.add('THEN_BLOCK');
|
||||||
|
(fixture.componentInstance as TestComponent).condition.next(false);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual(`elseBlockContent`);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show else block when permission is missing and condition is false', fakeAsync(() => {
|
||||||
|
(fixture.componentInstance as TestComponent).condition.next(false);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual(`elseBlockContent`);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permission directive with false promise, if condition and change detection on push', () => {
|
||||||
|
@Component({
|
||||||
|
template: ` <div *deny="'ADMIN'; if: condition">
|
||||||
|
<div>123</div>
|
||||||
|
</div>`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
class TestComponent extends BaseTestComponent {
|
||||||
|
condition = new BehaviorSubject(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => configureTestBed(TestComponent));
|
||||||
|
|
||||||
|
it('should hide the component when permission is present', fakeAsync(() => {
|
||||||
|
permissionsService.add(ADMIN);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should hide the component when permission is present and condition is true', fakeAsync(() => {
|
||||||
|
permissionsService.add(ADMIN);
|
||||||
|
(fixture.componentInstance as TestComponent).condition.next(true);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should hide the component when permission is absent', fakeAsync(() => {
|
||||||
|
expect(getFixtureContent()).toEqual(null);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show the component when permission is missing and condition is true', fakeAsync(() => {
|
||||||
|
(fixture.componentInstance as TestComponent).condition.next(true);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
const content = getFixtureContent();
|
||||||
|
expect(content).toBeTruthy();
|
||||||
|
expect(content.innerHTML).toEqual(`<div>123</div>`);
|
||||||
|
}));
|
||||||
|
});
|
||||||
55
src/lib/permissions/directives/deny/deny.directive.ts
Normal file
55
src/lib/permissions/directives/deny/deny.directive.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { Directive, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { List } from '../../../utils';
|
||||||
|
import { assertTemplate, IqserPermissionsDirective } from '../permissions.directive';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[deny]',
|
||||||
|
})
|
||||||
|
export class IqserDenyDirective extends IqserPermissionsDirective implements OnDestroy, OnInit {
|
||||||
|
/**
|
||||||
|
* Assert the correct type of the expression bound to the `allow` 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 `allow` should be narrowed in some way.
|
||||||
|
* For `allow`, the binding expression itself is used to
|
||||||
|
* narrow its type, which allows the strictNullChecks feature of TypeScript to work with `IqserPermissionsDirective`.
|
||||||
|
*/
|
||||||
|
static ngTemplateGuard_deny: 'binding';
|
||||||
|
|
||||||
|
constructor(templateRef: TemplateRef<unknown>) {
|
||||||
|
super(templateRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set deny(value: string | List) {
|
||||||
|
this.setPermissions(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set denyThen(template: TemplateRef<unknown>) {
|
||||||
|
assertTemplate('denyThen', template);
|
||||||
|
this.setThenTemplateRef(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set denyElse(template: TemplateRef<unknown>) {
|
||||||
|
assertTemplate('denyElse', template);
|
||||||
|
this.setElseTemplateRef(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set denyIf(value: boolean | Promise<boolean> | Observable<boolean>) {
|
||||||
|
this.setIf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override _validate() {
|
||||||
|
if (!this._permissions) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !(this._permissionsService.hasAtLeastOne(this._permissions) || this._rolesService.hasAtLeastOne(this._permissions));
|
||||||
|
}
|
||||||
|
}
|
||||||
133
src/lib/permissions/directives/permissions.directive.ts
Normal file
133
src/lib/permissions/directives/permissions.directive.ts
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import {
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Directive,
|
||||||
|
EmbeddedViewRef,
|
||||||
|
EventEmitter,
|
||||||
|
inject,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
Output,
|
||||||
|
TemplateRef,
|
||||||
|
ViewContainerRef,
|
||||||
|
ɵstringify as stringify,
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import { BehaviorSubject, combineLatest, merge, Observable, of, Subject, Subscription, switchMap } from 'rxjs';
|
||||||
|
import { map, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { IqserRolesService } from '../services/roles.service';
|
||||||
|
import { IqserPermissionsService } from '../services/permissions.service';
|
||||||
|
import { List } from '../../utils';
|
||||||
|
import { isBoolean } from '../utils';
|
||||||
|
|
||||||
|
@Directive()
|
||||||
|
export abstract class IqserPermissionsDirective implements OnDestroy, OnInit {
|
||||||
|
@Output() readonly authorized = new EventEmitter();
|
||||||
|
@Output() readonly unauthorized = new EventEmitter();
|
||||||
|
protected readonly _permissionsService = inject(IqserPermissionsService);
|
||||||
|
protected readonly _rolesService = inject(IqserRolesService);
|
||||||
|
protected readonly _viewContainer = inject(ViewContainerRef);
|
||||||
|
protected readonly _changeDetector = inject(ChangeDetectorRef);
|
||||||
|
protected _permissions?: string | List;
|
||||||
|
protected _thenTemplateRef: TemplateRef<unknown>;
|
||||||
|
protected _elseTemplateRef?: TemplateRef<unknown>;
|
||||||
|
protected _thenViewRef: EmbeddedViewRef<unknown> | boolean = false;
|
||||||
|
protected _elseViewRef: EmbeddedViewRef<unknown> | boolean = false;
|
||||||
|
protected readonly _updateView = new Subject<void>();
|
||||||
|
protected readonly _subscription = new Subscription();
|
||||||
|
protected readonly _if = new BehaviorSubject<Promise<boolean> | Observable<boolean>>(of(true));
|
||||||
|
|
||||||
|
protected constructor(templateRef: TemplateRef<unknown>) {
|
||||||
|
this._thenTemplateRef = templateRef;
|
||||||
|
|
||||||
|
const ifCondition$ = this._if.pipe(switchMap(condition => condition));
|
||||||
|
|
||||||
|
this._subscription = combineLatest([ifCondition$, this._updateView])
|
||||||
|
.pipe(
|
||||||
|
switchMap(([ifCondition]) => this._validateRolesAndPermissions().pipe(map(hasPermission => ifCondition && hasPermission))),
|
||||||
|
tap(isAllowed => (isAllowed ? this._showThenBlock() : this._showElseBlock())),
|
||||||
|
tap(() => this._changeDetector.markForCheck()),
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
This assures that when the directive has an empty input (such as [allow]="") the view is updated
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this._updateView.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this._subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setPermissions(value: string | List) {
|
||||||
|
this._permissions = value;
|
||||||
|
this._updateView.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setThenTemplateRef(template: TemplateRef<unknown>) {
|
||||||
|
this._thenTemplateRef = template;
|
||||||
|
this._thenViewRef = false;
|
||||||
|
this._updateView.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setElseTemplateRef(template: TemplateRef<unknown>) {
|
||||||
|
this._elseTemplateRef = template;
|
||||||
|
this._elseViewRef = false;
|
||||||
|
this._updateView.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setIf(value: boolean | Promise<boolean> | Observable<boolean>) {
|
||||||
|
if (isBoolean(value)) {
|
||||||
|
this._if.next(of(value));
|
||||||
|
} else {
|
||||||
|
this._if.next(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _validateRolesAndPermissions() {
|
||||||
|
return merge(this._permissionsService.permissions$, this._rolesService.roles$).pipe(map(() => this._validate()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _validate() {
|
||||||
|
if (!this._permissions) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._permissionsService.has(this._permissions) || this._rolesService.has(this._permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _showElseBlock() {
|
||||||
|
if (this._elseViewRef) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.unauthorized.emit();
|
||||||
|
this._thenViewRef = false;
|
||||||
|
this._elseViewRef = this._showTemplate(this._elseTemplateRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _showThenBlock() {
|
||||||
|
if (this._thenViewRef) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.authorized.emit();
|
||||||
|
this._elseViewRef = false;
|
||||||
|
this._thenViewRef = this._showTemplate(this._thenTemplateRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _showTemplate(template?: TemplateRef<unknown>) {
|
||||||
|
this._viewContainer.clear();
|
||||||
|
return template ? this._viewContainer.createEmbeddedView(template) : true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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)}'.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,6 @@ export * from './types';
|
|||||||
export * from './services/permissions-guard.service';
|
export * from './services/permissions-guard.service';
|
||||||
export * from './services/permissions.service';
|
export * from './services/permissions.service';
|
||||||
export * from './services/roles.service';
|
export * from './services/roles.service';
|
||||||
export * from './permissions.directive';
|
export * from './directives/permissions.directive';
|
||||||
export * from './permissions.module';
|
export * from './permissions.module';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
|||||||
@ -1,156 +0,0 @@
|
|||||||
import {
|
|
||||||
ChangeDetectorRef,
|
|
||||||
Directive,
|
|
||||||
EmbeddedViewRef,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
Output,
|
|
||||||
TemplateRef,
|
|
||||||
ViewContainerRef,
|
|
||||||
ɵstringify as stringify,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { BehaviorSubject, combineLatest, merge, Observable, of, Subject, Subscription, switchMap } from 'rxjs';
|
|
||||||
import { map, tap } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { IqserRolesService } from './services/roles.service';
|
|
||||||
import { IqserPermissionsService } from './services/permissions.service';
|
|
||||||
import { List } from '../utils';
|
|
||||||
import { isBoolean } from './utils';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: '[allow]',
|
|
||||||
})
|
|
||||||
export class IqserPermissionsDirective implements OnDestroy, OnInit {
|
|
||||||
/**
|
|
||||||
* Assert the correct type of the expression bound to the `allow` 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 `allow` should be narrowed in some way.
|
|
||||||
* For `allow`, the binding expression itself is used to
|
|
||||||
* narrow its type, which allows the strictNullChecks feature of TypeScript to work with `IqserPermissionsDirective`.
|
|
||||||
*/
|
|
||||||
static ngTemplateGuard_allow: 'binding';
|
|
||||||
|
|
||||||
@Output() readonly permissionsAuthorized = new EventEmitter();
|
|
||||||
@Output() readonly permissionsUnauthorized = new EventEmitter();
|
|
||||||
|
|
||||||
#permissions?: string | List;
|
|
||||||
#thenTemplateRef: TemplateRef<unknown>;
|
|
||||||
#elseTemplateRef?: TemplateRef<unknown>;
|
|
||||||
#thenViewRef: EmbeddedViewRef<unknown> | boolean = false;
|
|
||||||
#elseViewRef: EmbeddedViewRef<unknown> | boolean = false;
|
|
||||||
|
|
||||||
readonly #updateView = new Subject<void>();
|
|
||||||
readonly #subscription = new Subscription();
|
|
||||||
readonly #if = new BehaviorSubject<Promise<boolean> | Observable<boolean>>(of(true));
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly _permissionsService: IqserPermissionsService,
|
|
||||||
private readonly _rolesService: IqserRolesService,
|
|
||||||
private readonly _viewContainer: ViewContainerRef,
|
|
||||||
private readonly _changeDetector: ChangeDetectorRef,
|
|
||||||
templateRef: TemplateRef<unknown>,
|
|
||||||
) {
|
|
||||||
this.#thenTemplateRef = templateRef;
|
|
||||||
|
|
||||||
const ifCondition$ = this.#if.pipe(switchMap(condition => condition));
|
|
||||||
|
|
||||||
this.#subscription = combineLatest([ifCondition$, this.#updateView])
|
|
||||||
.pipe(
|
|
||||||
switchMap(([ifCondition]) => this.#validateRolesAndPermissions().pipe(map(hasPermission => ifCondition && hasPermission))),
|
|
||||||
tap(isAllowed => (isAllowed ? this.#showThenBlock() : this.#showElseBlock())),
|
|
||||||
tap(() => this._changeDetector.markForCheck()),
|
|
||||||
)
|
|
||||||
.subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
set allow(value: string | List) {
|
|
||||||
this.#permissions = value;
|
|
||||||
this.#updateView.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
set allowThen(template: TemplateRef<unknown>) {
|
|
||||||
assertTemplate('allowThen', template);
|
|
||||||
this.#thenTemplateRef = template;
|
|
||||||
this.#thenViewRef = false;
|
|
||||||
this.#updateView.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
set allowElse(template: TemplateRef<unknown>) {
|
|
||||||
assertTemplate('allowElse', template);
|
|
||||||
this.#elseTemplateRef = template;
|
|
||||||
this.#elseViewRef = false;
|
|
||||||
this.#updateView.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
set allowIf(value: boolean | Promise<boolean> | Observable<boolean>) {
|
|
||||||
if (isBoolean(value)) {
|
|
||||||
this.#if.next(of(value));
|
|
||||||
} else {
|
|
||||||
this.#if.next(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
This assures that when the directive has an empty input (such as [allow]="") the view is updated
|
|
||||||
*/
|
|
||||||
ngOnInit() {
|
|
||||||
this.#updateView.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.#subscription.unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
#validateRolesAndPermissions() {
|
|
||||||
return merge(this._permissionsService.permissions$, this._rolesService.roles$).pipe(map(() => this.#validate()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#validate() {
|
|
||||||
if (!this.#permissions) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._permissionsService.has(this.#permissions) || this._rolesService.has(this.#permissions);
|
|
||||||
}
|
|
||||||
|
|
||||||
#showElseBlock() {
|
|
||||||
if (this.#elseViewRef) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.permissionsUnauthorized.emit();
|
|
||||||
this.#thenViewRef = false;
|
|
||||||
this.#elseViewRef = this.#showTemplate(this.#elseTemplateRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
#showThenBlock() {
|
|
||||||
if (this.#thenViewRef) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.permissionsAuthorized.emit();
|
|
||||||
this.#elseViewRef = false;
|
|
||||||
this.#thenViewRef = this.#showTemplate(this.#thenTemplateRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
#showTemplate(template?: TemplateRef<unknown>) {
|
|
||||||
this._viewContainer.clear();
|
|
||||||
return template ? this._viewContainer.createEmbeddedView(template) : true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,12 +1,13 @@
|
|||||||
import { NgModule, Optional } from '@angular/core';
|
import { NgModule, Optional } from '@angular/core';
|
||||||
import { IqserPermissionsDirective } from './permissions.directive';
|
import { IqserAllowDirective } from './directives/allow/allow.directive';
|
||||||
import { IqserPermissionsService } from './services/permissions.service';
|
import { IqserPermissionsService } from './services/permissions.service';
|
||||||
import { IqserPermissionsGuard } from './services/permissions-guard.service';
|
import { IqserPermissionsGuard } from './services/permissions-guard.service';
|
||||||
import { IqserRolesService } from './services/roles.service';
|
import { IqserRolesService } from './services/roles.service';
|
||||||
|
import { IqserDenyDirective } from './directives/deny/deny.directive';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [IqserPermissionsDirective],
|
declarations: [IqserAllowDirective, IqserDenyDirective],
|
||||||
exports: [IqserPermissionsDirective],
|
exports: [IqserAllowDirective, IqserDenyDirective],
|
||||||
})
|
})
|
||||||
export class IqserPermissionsModule {
|
export class IqserPermissionsModule {
|
||||||
constructor(@Optional() permissionsService: IqserPermissionsService) {
|
constructor(@Optional() permissionsService: IqserPermissionsService) {
|
||||||
|
|||||||
@ -34,6 +34,17 @@ export class IqserPermissionsService {
|
|||||||
return results.every(result => result);
|
return results.every(result => result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasAtLeastOne(permissions: List | string): boolean {
|
||||||
|
const isEmpty = !permissions || permissions.length === 0;
|
||||||
|
if (isEmpty) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const all = this.#permissions$.value;
|
||||||
|
const results = toArray(permissions).map(permission => all[permission]?.(permission, all) ?? false);
|
||||||
|
return results.some(result => result);
|
||||||
|
}
|
||||||
|
|
||||||
load(permissions: IqserPermissions | List) {
|
load(permissions: IqserPermissions | List) {
|
||||||
return this.#reduce(permissions);
|
return this.#reduce(permissions);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,6 +51,17 @@ export class IqserRolesService {
|
|||||||
return this.#checkPermissionsIfNeeded(validations).every(result => result);
|
return this.#checkPermissionsIfNeeded(validations).every(result => result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasAtLeastOne(roles: string | List): boolean {
|
||||||
|
const isEmpty = !roles || roles.length === 0;
|
||||||
|
|
||||||
|
if (isEmpty) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validations = toArray(roles).map(role => this.#runValidation(role));
|
||||||
|
return this.#checkPermissionsIfNeeded(validations).some(result => result);
|
||||||
|
}
|
||||||
|
|
||||||
#checkPermissionsIfNeeded(results: List<string | boolean | List>) {
|
#checkPermissionsIfNeeded(results: List<string | boolean | List>) {
|
||||||
return results.map(result => (isBoolean(result) ? result : this._permissionsService.has(result)));
|
return results.map(result => (isBoolean(result) ? result : this._permissionsService.has(result)));
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user