add deny input to allow directive & update tests

This commit is contained in:
Dan Percic 2022-12-19 12:06:07 +02:00
parent bba66ea0d6
commit a4b9c045cd
6 changed files with 206 additions and 88 deletions

View File

@ -28,8 +28,8 @@ export class AppComponent implements OnInit {
<div <div
*allow="['ADMIN', 'GUEST']" *allow="['ADMIN', 'GUEST']"
(permissionsAuthorized)="yourCustomAuthorizedFunction()" (authorized)="yourCustomAuthorizedFunction()"
(permissionsUnauthorized)="yourCustomAuthorizedFunction()" (unauthorized)="yourCustomAuthorizedFunction()"
> >
<div>You can see this text congrats</div> <div>You can see this text congrats</div>
</div> </div>
@ -38,11 +38,24 @@ export class AppComponent implements OnInit {
<div>You can see this text congrats</div> <div>You can see this text congrats</div>
</ng-template> </ng-template>
----------------------------------------------
<div
*deny="['ADMIN', 'GUEST']"
(authorized)="yourCustomAuthorizedFunction()"
(unauthorized)="yourCustomAuthorizedFunction()"
>
<div>You cant see this text congrats</div>
</div>
<ng-template deny="ADMIN">
<div>You cant see this text congrats</div>
</ng-template>
---------------------------------------------- ----------------------------------------------
<ng-template <ng-template
[allow]="['MANAGER']" [allow]="['MANAGER']"
[allowIf]="someObject.isEmpty" [allowIf]="someObject.isEmpty"
[allowDeny]="['GUEST']"
[allowThen]="thenBlock" [allowThen]="thenBlock"
[allowElse]="elseBlock"> [allowElse]="elseBlock">
</ng-template> </ng-template>
@ -57,7 +70,7 @@ export class AppComponent implements OnInit {
---------------------------------------------- ----------------------------------------------
<div *allow="['can-read']; if: !someObject.isEmpty; else: elseBlock; then: thenBlock">main</div> <div *allow="['can-read']; deny: ['GUEST']; if: !someObject.isEmpty; else: elseBlock; then: thenBlock">main</div>
<ng-template #elseBlock> <ng-template #elseBlock>
<div>elseBlock</div> <div>elseBlock</div>

View File

@ -41,11 +41,13 @@ function getFixtureContent(): HTMLElement {
describe('Permission directive', () => { describe('Permission directive', () => {
@Component({ @Component({
template: ` <ng-template [allow]="'ADMIN'" (authorized)="isAuthorized()" (unauthorized)="isUnauthorized()"> template: `
<div>123</div> <ng-template [allow]="'ADMIN'" (authorized)="isAuthorized()" (unauthorized)="isUnauthorized()">
</ng-template>`, <div>123</div>
</ng-template>`,
}) })
class TestComponent extends BaseTestComponent {} class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent)); beforeEach(() => configureTestBed(TestComponent));
@ -104,11 +106,13 @@ describe('Permission directive', () => {
describe('Permission directive with roles', () => { describe('Permission directive with roles', () => {
@Component({ @Component({
template: ` <ng-template [allow]="'ADMIN'"> template: `
<div>123</div> <ng-template [allow]="'ADMIN'">
</ng-template>`, <div>123</div>
</ng-template>`,
}) })
class TestComponent extends BaseTestComponent {} class TestComponent extends BaseTestComponent {
}
const awesomePermissions = 'AWESOME'; const awesomePermissions = 'AWESOME';
beforeEach(() => configureTestBed(TestComponent)); beforeEach(() => configureTestBed(TestComponent));
@ -166,11 +170,13 @@ describe('Permission directive with roles', () => {
describe('Permission directive with roles array', () => { describe('Permission directive with roles array', () => {
@Component({ @Component({
template: ` <ng-template [allow]="['ADMIN', 'GUEST']"> template: `
<div>123</div> <ng-template [allow]="['ADMIN', 'GUEST']">
</ng-template>`, <div>123</div>
</ng-template>`,
}) })
class TestComponent extends BaseTestComponent {} class TestComponent extends BaseTestComponent {
}
const awesomePermission = 'AWESOME'; const awesomePermission = 'AWESOME';
beforeEach(() => configureTestBed(TestComponent)); beforeEach(() => configureTestBed(TestComponent));
@ -221,11 +227,13 @@ describe('Permission directive with roles array', () => {
describe('Permission directive testing different selectors *allow', () => { describe('Permission directive testing different selectors *allow', () => {
@Component({ @Component({
template: ` <div *allow="['ADMIN']"> template: `
<div>123</div> <div *allow="['ADMIN']; deny: 'GUEST'">
</div>`, <div>123</div>
</div>`,
}) })
class TestComponent extends BaseTestComponent {} class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent)); beforeEach(() => configureTestBed(TestComponent));
@ -249,15 +257,56 @@ describe('Permission directive testing different selectors *allow', () => {
expect(getFixtureContent()).toEqual(null); expect(getFixtureContent()).toEqual(null);
})); }));
it('should hide the component when deny permission is added', fakeAsync(() => {
permissionsService.add(ADMIN);
tick();
const content = getFixtureContent();
expect(content).toBeTruthy();
expect(content.innerHTML).toEqual('<div>123</div>');
permissionsService.add(GUEST);
tick();
expect(getFixtureContent()).toEqual(null);
}));
it('should hide the component when deny role is added', fakeAsync(() => {
permissionsService.add(ADMIN);
tick();
const content = getFixtureContent();
expect(content).toBeTruthy();
expect(content.innerHTML).toEqual('<div>123</div>');
rolesService.add(GUEST);
tick();
expect(getFixtureContent()).toEqual(null);
}));
it('should show the component when deny permission is remove', fakeAsync(() => {
permissionsService.add([ADMIN, GUEST]);
tick();
expect(getFixtureContent()).toEqual(null);
permissionsService.remove(GUEST);
tick();
const content = getFixtureContent();
expect(content).toBeTruthy();
expect(content.innerHTML).toEqual('<div>123</div>');
}));
}); });
describe('Permission directive angular testing different async functions in roles', () => { describe('Permission directive angular testing different async functions in roles', () => {
@Component({ @Component({
template: ` <div *allow="'ADMIN'"> template: `
<div>123</div> <div *allow="'ADMIN'">
</div>`, <div>123</div>
</div>`,
}) })
class TestComponent extends BaseTestComponent {} class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent)); beforeEach(() => configureTestBed(TestComponent));
@ -284,11 +333,13 @@ describe('Permission directive angular testing different async functions in role
describe('Permission directive angular testing different async functions in roles via array', () => { describe('Permission directive angular testing different async functions in roles via array', () => {
@Component({ @Component({
template: ` <div *allow="['ADMIN', 'GUEST']"> template: `
<div>123</div> <div *allow="['ADMIN', 'GUEST']">
</div>`, <div>123</div>
</div>`,
}) })
class TestComponent extends BaseTestComponent {} class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent)); beforeEach(() => configureTestBed(TestComponent));
@ -327,11 +378,13 @@ describe('Permission directive angular testing different async functions in role
describe('Permission directive with different async functions in permissions via array', () => { describe('Permission directive with different async functions in permissions via array', () => {
@Component({ @Component({
template: ` <div *allow="['ADMIN', 'GUEST']"> template: `
<div>123</div> <div *allow="['ADMIN', 'GUEST']">
</div>`, <div>123</div>
</div>`,
}) })
class TestComponent extends BaseTestComponent {} class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent)); beforeEach(() => configureTestBed(TestComponent));
@ -390,11 +443,13 @@ describe('Permission directive with different async functions in permissions via
describe('Permission directive testing different async functions in permissions via string', () => { describe('Permission directive testing different async functions in permissions via string', () => {
@Component({ @Component({
template: ` <div *allow="'ADMIN'"> template: `
<div>123</div> <div *allow="'ADMIN'">
</div>`, <div>123</div>
</div>`,
}) })
class TestComponent extends BaseTestComponent {} class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent)); beforeEach(() => configureTestBed(TestComponent));
@ -472,7 +527,8 @@ describe('Permissions directive with else block', () => {
</ng-template> </ng-template>
`, `,
}) })
class TestComponent extends BaseTestComponent {} class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent)); beforeEach(() => configureTestBed(TestComponent));
@ -513,7 +569,8 @@ describe('Permissions directive with then block', () => {
</ng-template> </ng-template>
`, `,
}) })
class TestComponent extends BaseTestComponent {} class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent)); beforeEach(() => configureTestBed(TestComponent));
@ -535,7 +592,8 @@ describe('Permissions directive with empty argument', () => {
</ng-template> </ng-template>
`, `,
}) })
class TestComponent extends BaseTestComponent {} class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent)); beforeEach(() => configureTestBed(TestComponent));
@ -554,7 +612,8 @@ describe('Permissions directive with an empty array', () => {
</ng-template> </ng-template>
`, `,
}) })
class TestComponent extends BaseTestComponent {} class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent)); beforeEach(() => configureTestBed(TestComponent));
@ -567,11 +626,13 @@ describe('Permissions directive with an empty array', () => {
describe('Permission directive with true if condition', () => { describe('Permission directive with true if condition', () => {
@Component({ @Component({
template: ` <ng-template [allow]="'ADMIN'" [allowIf]="true"> template: `
<div>123</div> <ng-template [allow]="'ADMIN'" [allowIf]="true">
</ng-template>`, <div>123</div>
</ng-template>`,
}) })
class TestComponent extends BaseTestComponent {} class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent)); beforeEach(() => configureTestBed(TestComponent));
@ -591,11 +652,13 @@ describe('Permission directive with true if condition', () => {
describe('Permission directive with true if condition', () => { describe('Permission directive with true if condition', () => {
@Component({ @Component({
template: ` <ng-template [allow]="'ADMIN'" [allowIf]="false"> template: `
<div>123</div> <ng-template [allow]="'ADMIN'" [allowIf]="false">
</ng-template>`, <div>123</div>
</ng-template>`,
}) })
class TestComponent extends BaseTestComponent {} class TestComponent extends BaseTestComponent {
}
beforeEach(() => configureTestBed(TestComponent)); beforeEach(() => configureTestBed(TestComponent));
@ -613,9 +676,10 @@ describe('Permission directive with true if condition', () => {
describe('Permission directive with true promise if condition', () => { describe('Permission directive with true promise if condition', () => {
@Component({ @Component({
template: ` <ng-template [allow]="'ADMIN'" [allowIf]="condition"> template: `
<div>123</div> <ng-template [allow]="'ADMIN'" [allowIf]="condition">
</ng-template>`, <div>123</div>
</ng-template>`,
}) })
class TestComponent extends BaseTestComponent { class TestComponent extends BaseTestComponent {
condition = Promise.resolve(true); condition = Promise.resolve(true);
@ -639,9 +703,10 @@ describe('Permission directive with true promise if condition', () => {
describe('Permission directive with false promise if condition', () => { describe('Permission directive with false promise if condition', () => {
@Component({ @Component({
template: ` <div *allow="'ADMIN'; if: condition"> template: `
<div>123</div> <div *allow="'ADMIN'; if: condition">
</div>`, <div>123</div>
</div>`,
}) })
class TestComponent extends BaseTestComponent { class TestComponent extends BaseTestComponent {
condition = Promise.resolve(false); condition = Promise.resolve(false);
@ -718,9 +783,10 @@ describe('Permissions directive with then block and if condition', () => {
describe('Permission directive with false promise, if condition and change detection on push', () => { describe('Permission directive with false promise, if condition and change detection on push', () => {
@Component({ @Component({
template: ` <div *allow="'ADMIN'; if: condition"> template: `
<div>123</div> <div *allow="'ADMIN'; if: condition; deny: 'GUEST'">
</div>`, <div>123</div>
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
class TestComponent extends BaseTestComponent { class TestComponent extends BaseTestComponent {
@ -740,7 +806,7 @@ describe('Permission directive with false promise, if condition and change detec
expect(getFixtureContent()).toEqual(null); expect(getFixtureContent()).toEqual(null);
})); }));
it('should show else block when permission is missing and condition is false', fakeAsync(() => { it('should show component when permission is added and condition is true', fakeAsync(() => {
permissionsService.add(ADMIN); permissionsService.add(ADMIN);
(fixture.componentInstance as TestComponent).condition.next(true); (fixture.componentInstance as TestComponent).condition.next(true);
tick(); tick();
@ -749,4 +815,19 @@ describe('Permission directive with false promise, if condition and change detec
expect(content).toBeTruthy(); expect(content).toBeTruthy();
expect(content.innerHTML).toEqual(`<div>123</div>`); expect(content.innerHTML).toEqual(`<div>123</div>`);
})); }));
it('should hide component when permission is present, condition is true and deny permission is present', fakeAsync(() => {
permissionsService.add(ADMIN);
(fixture.componentInstance as TestComponent).condition.next(true);
tick();
const content = getFixtureContent();
expect(content).toBeTruthy();
expect(content.innerHTML).toEqual(`<div>123</div>`);
permissionsService.add(GUEST);
tick();
expect(getFixtureContent()).toEqual(null);
}));
}); });

View File

@ -18,6 +18,7 @@ export class IqserAllowDirective extends IqserPermissionsDirective implements On
* narrow its type, which allows the strictNullChecks feature of TypeScript to work with `IqserPermissionsDirective`. * narrow its type, which allows the strictNullChecks feature of TypeScript to work with `IqserPermissionsDirective`.
*/ */
static ngTemplateGuard_allow: 'binding'; static ngTemplateGuard_allow: 'binding';
#deny?: string | List;
constructor(templateRef: TemplateRef<unknown>) { constructor(templateRef: TemplateRef<unknown>) {
super(templateRef); super(templateRef);
@ -40,8 +41,35 @@ export class IqserAllowDirective extends IqserPermissionsDirective implements On
this.setElseTemplateRef(template); this.setElseTemplateRef(template);
} }
@Input()
set allowDeny(value: string | List) {
this.#deny = value;
this._updateView.next();
}
@Input() @Input()
set allowIf(value: boolean | Promise<boolean> | Observable<boolean>) { set allowIf(value: boolean | Promise<boolean> | Observable<boolean>) {
this.setIf(value); this.setIf(value);
} }
get #hasDenyPermission() {
if (!this.#deny) {
return false;
}
return this._permissionsService.hasSome(this.#deny) || this._rolesService.hasSome(this.#deny);
}
protected override _validate() {
const hasDenyPermission = this.#hasDenyPermission;
if (!this._permissions) {
return !hasDenyPermission;
}
if (hasDenyPermission) {
return false;
}
return this._permissionsService.has(this._permissions) || this._rolesService.has(this._permissions);
}
} }

View File

@ -50,6 +50,6 @@ export class IqserDenyDirective extends IqserPermissionsDirective implements OnD
return true; return true;
} }
return !(this._permissionsService.hasAtLeastOne(this._permissions) || this._rolesService.hasAtLeastOne(this._permissions)); return !(this._permissionsService.hasSome(this._permissions) || this._rolesService.hasSome(this._permissions));
} }
} }

View File

@ -24,25 +24,11 @@ export class IqserPermissionsService {
has(permissions: List): boolean; has(permissions: List): boolean;
has(permissions: string | List): boolean; has(permissions: string | List): boolean;
has(permissions: string | List): boolean { has(permissions: string | List): boolean {
const isEmpty = !permissions || permissions.length === 0; return this.#evaluate(permissions).every(result => result);
if (isEmpty) {
return true;
}
const all = this.#permissions$.value;
const results = toArray(permissions).map(permission => all[permission]?.(permission, all) ?? false);
return results.every(result => result);
} }
hasAtLeastOne(permissions: List | string): boolean { hasSome(permissions: List | string): boolean {
const isEmpty = !permissions || permissions.length === 0; return this.#evaluate(permissions).some(result => result);
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) {
@ -69,8 +55,11 @@ export class IqserPermissionsService {
} }
has$(permission: string): Observable<boolean>; has$(permission: string): Observable<boolean>;
has$(permissions: List): Observable<boolean>; has$(permissions: List): Observable<boolean>;
has$(permissions: string | List): Observable<boolean>; has$(permissions: string | List): Observable<boolean>;
has$(permissions: string | List) { has$(permissions: string | List) {
const isEmpty = !permissions || permissions.length === 0; const isEmpty = !permissions || permissions.length === 0;
if (isEmpty) { if (isEmpty) {
@ -83,6 +72,16 @@ export class IqserPermissionsService {
); );
} }
#evaluate(permissions: List | string) {
const isEmpty = !permissions || permissions.length === 0;
if (isEmpty) {
return [true];
}
const all = this.#permissions$.value;
return toArray(permissions).map(permission => all[permission]?.(permission, all) ?? false);
}
#reduce(permissions: IqserPermissions | List, initialValue = {} as IqserPermissions) { #reduce(permissions: IqserPermissions | List, initialValue = {} as IqserPermissions) {
if (isArray(permissions)) { if (isArray(permissions)) {
return this.#permissions$.next(this.#reduceList(permissions, initialValue)); return this.#permissions$.next(this.#reduceList(permissions, initialValue));

View File

@ -41,25 +41,22 @@ export class IqserRolesService {
} }
has(roles: string | List): boolean { has(roles: string | List): boolean {
const isEmpty = !roles || roles.length === 0; const results = this.#evaluate(roles);
return this.#checkPermissionsIfNeeded(results).every(result => result);
if (isEmpty) {
return true;
}
const validations = toArray(roles).map(role => this.#runValidation(role));
return this.#checkPermissionsIfNeeded(validations).every(result => result);
} }
hasAtLeastOne(roles: string | List): boolean { hasSome(roles: string | List): boolean {
const isEmpty = !roles || roles.length === 0; const results = this.#evaluate(roles);
return this.#checkPermissionsIfNeeded(results).some(result => result);
}
#evaluate(roles: string | List) {
const isEmpty = !roles || roles.length === 0;
if (isEmpty) { if (isEmpty) {
return true; return [true];
} }
const validations = toArray(roles).map(role => this.#runValidation(role)); return 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>) {