Skip to content

Commit

Permalink
feat: NgxSecurityState.permissionsChecker and `NgxSecurityService.h…
Browse files Browse the repository at this point in the history
…asPermission` accept a 2nd parameter of type `any`
  • Loading branch information
mselerin committed Feb 28, 2020
1 parent e7cfae0 commit eb878fe
Show file tree
Hide file tree
Showing 9 changed files with 520 additions and 297 deletions.
663 changes: 414 additions & 249 deletions package-lock.json

Large diffs are not rendered by default.

44 changes: 22 additions & 22 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,28 @@
"codecov": "cat coverage/lcovonly/lcov.info | codecov"
},
"dependencies": {
"@angular/animations": "~9.0.0",
"@angular/common": "~9.0.0",
"@angular/compiler": "~9.0.0",
"@angular/core": "~9.0.0",
"@angular/forms": "~9.0.0",
"@angular/platform-browser": "~9.0.0",
"@angular/platform-browser-dynamic": "~9.0.0",
"@angular/router": "^9.0.0",
"@angular/animations": "^9.0.4",
"@angular/common": "^9.0.4",
"@angular/compiler": "^9.0.4",
"@angular/core": "^9.0.4",
"@angular/forms": "^9.0.4",
"@angular/platform-browser": "^9.0.4",
"@angular/platform-browser-dynamic": "^9.0.4",
"@angular/router": "^9.0.4",
"rxjs": "~6.5.4",
"tslib": "^1.10.0",
"tslib": "^1.11.1",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.900.1",
"@angular-devkit/build-ng-packagr": "~0.900.1",
"@angular/cli": "~9.0.1",
"@angular/compiler-cli": "~9.0.0",
"@angular/language-service": "~9.0.0",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"@types/jest": "^25.1.2",
"@types/node": "^12.11.1",
"@angular-devkit/build-angular": "^0.900.4",
"@angular-devkit/build-ng-packagr": "^0.900.4",
"@angular/cli": "^9.0.4",
"@angular/compiler-cli": "^9.0.4",
"@angular/language-service": "^9.0.4",
"@types/jasmine": "^3.5.7",
"@types/jasminewd2": "^2.0.8",
"@types/jest": "^25.1.3",
"@types/node": "^13.7.6",
"codecov": "^3.1.0",
"codelyzer": "^5.1.2",
"cpx": "^1.5.0",
Expand All @@ -60,11 +60,11 @@
"jest": "^25.1.0",
"jest-preset-angular": "^8.0.0",
"lerna": "^3.20.2",
"ng-packagr": "^9.0.0",
"ng-packagr": "^9.0.2",
"protractor": "~5.4.3",
"ts-jest": "^25.2.0",
"ts-node": "~8.3.0",
"tslint": "~5.18.0",
"ts-jest": "^25.2.1",
"ts-node": "^8.6.2",
"tslint": "^6.0.0",
"typescript": "~3.7.5"
}
}
12 changes: 12 additions & 0 deletions projects/demo/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ <h1>

<div>
<button (click)="switchAuthentication()">Switch auth</button>
<button (click)="toggleFoo()">Toggle foo</button>
</div>

<app-sample *secuIsAuthenticated></app-sample>
Expand Down Expand Up @@ -48,3 +49,14 @@ <h1>
<span>NO</span>
</ng-template>
</div>



<div>
<b>HasPermission('foo', {{'{'}}foo: {{foo.value}}{{'}'}}) ?</b>&nbsp;
<span *secuHasPermissions="'foo'; resource: foo; else permElse">YES</span>
<ng-template #permElse>
<span>NO</span>
</ng-template>
</div>

26 changes: 17 additions & 9 deletions projects/demo/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import { of } from 'rxjs';
import { Component, OnInit } from '@angular/core';
import { NgxSecurityService } from 'ngx-security';
import {Component, OnInit} from '@angular/core';
import {NgxSecurityService} from 'ngx-security';
import {map} from "rxjs/operators";

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit
{
export class AppComponent implements OnInit {
private authenticated: boolean = false;
private foo = { value: 'foo' };

constructor(
private security: NgxSecurityService
) {}

ngOnInit() {
this.security.setPermissionChecker((name: string, resource?: any) => {
return this.security.isAuthenticated().pipe(
map(authenticated => authenticated && resource && resource.value === 'foo')
);
});

this.security.setAuthenticated(this.authenticated);
}

Expand All @@ -26,10 +32,12 @@ export class AppComponent implements OnInit
this.security.updateState({
authenticated: this.authenticated,
roles: ['ADMIN', 'USER'],
groups: ['GROUP_A', 'GROUP_B'],
permissionsChecker: (name: string) => {
return of(true);
}
groups: ['GROUP_A', 'GROUP_B']
});
}

toggleFoo() {
this.foo.value = this.foo.value === 'foo' ? 'bar' : 'foo';
this.foo = {...this.foo};
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
import { NgxSecurityService } from '../services/ngx-security.service';
import { Component } from '@angular/core';
import { NgxSecurityModule } from '../ngx-security.module';
import {ComponentFixture, fakeAsync, TestBed} from '@angular/core/testing';
import {NgxSecurityService} from '../services/ngx-security.service';
import {Component} from '@angular/core';
import {NgxSecurityModule} from '../ngx-security.module';
import {of} from "rxjs";

@Component({
selector: 'test-security',
template: `<div>TEST</div>`
})
export class TestSecuredComponent {}
export class TestSecuredComponent {
public foo = {value: ''};
}



describe('NgxSecurityDirectives', () => {
let security;
let fixture;
let security: NgxSecurityService;
let fixture: ComponentFixture<TestSecuredComponent>;
let element;


Expand All @@ -30,7 +33,7 @@ describe('NgxSecurityDirectives', () => {
element = fixture.nativeElement;
expect(element).toBeDefined();

security = TestBed.get(NgxSecurityService);
security = TestBed.inject(NgxSecurityService);
expect(security).toBeDefined();
};

Expand Down Expand Up @@ -390,6 +393,25 @@ describe('NgxSecurityDirectives', () => {
});


describe('secuHasPermissions with resource', () => {
beforeEach(() => {
instantiateTest(`*secuHasPermissions="['X', 'Y']; resource: foo"`);
});

it('should show/hide the component', fakeAsync(() => {
security.setPermissionChecker((perm, resource) => of(resource.value === 'bar'));

fixture.componentInstance.foo = {value: 'foo'};
fixture.detectChanges();
expectVisibility(false);

fixture.componentInstance.foo = {value: 'bar'};
fixture.detectChanges();
expectVisibility(true);
}));
});



describe('secuHasNotPermissions', () => {
beforeEach(() => {
Expand Down
24 changes: 20 additions & 4 deletions projects/ngx-security/src/lib/directives/ngx-security.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { every, first, map, tap } from 'rxjs/operators';
export class BaseSecurityDirective implements OnInit, OnDestroy {
private stateSubscription: Subscription;
protected elseTemplateRef: TemplateRef<any>;
protected resource: any;
protected expectedValue: boolean;
private lastValue?: boolean;

Expand Down Expand Up @@ -36,7 +37,7 @@ export class BaseSecurityDirective implements OnInit, OnDestroy {
}


private handleStateChange(): void {
protected handleStateChange(): void {
this.isAuthorized().pipe(
map(hasPerm => {
if (this.lastValue !== hasPerm) {
Expand Down Expand Up @@ -213,8 +214,13 @@ export class HasPermissionsDirective extends BaseSecurityDirective {
this.elseTemplateRef = elseBlock;
}

@Input('secuHasPermissionsResource') set testedResource(resource: any) {
this.resource = resource;
this.handleStateChange();
}

isAuthorized(): Observable<boolean> {
return this.testEvery(this.input, (n => this.security.hasPermission(n)), true);
return this.testEvery(this.input, (n => this.security.hasPermission(n, this.resource)), true);
}
}

Expand All @@ -227,8 +233,13 @@ export class HasNotPermissionsDirective extends BaseSecurityDirective {
this.elseTemplateRef = elseBlock;
}

@Input('secuHasNotPermissionsResource') set testedResource(resource: any) {
this.resource = resource;
this.handleStateChange();
}

isAuthorized(): Observable<boolean> {
return this.testEvery(this.input, (n => this.security.hasPermission(n)), false);
return this.testEvery(this.input, (n => this.security.hasPermission(n, this.resource)), false);
}
}

Expand All @@ -241,7 +252,12 @@ export class HasAnyPermissionsDirective extends BaseSecurityDirective {
this.elseTemplateRef = elseBlock;
}

@Input('secuHasAnyPermissionsResource') set testedResource(resource: any) {
this.resource = resource;
this.handleStateChange();
}

isAuthorized(): Observable<boolean> {
return this.testFirst(this.input, (n => this.security.hasPermission(n)), true);
return this.testFirst(this.input, (n => this.security.hasPermission(n, this.resource)), true);
}
}
2 changes: 1 addition & 1 deletion projects/ngx-security/src/lib/models/ngx-security.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface NgxSecurityState {
authenticationChecker: () => CheckerResult;
rolesChecker: (name: string) => CheckerResult;
groupsChecker: (name: string) => CheckerResult;
permissionsChecker: (name: string) => CheckerResult;
permissionsChecker: (name: string, resource?: any) => CheckerResult;
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ describe('NgxSecurityService', () => {
tick();

security.setPermissionChecker(() => of(true));
security.hasPermission('X').subscribe(d => expect(d).toBeTruthy());
security.hasPermission('X', {foo: 'bar'}).subscribe(d => expect(d).toBeTruthy());
tick();

security.addPermission('Y');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export class NgxSecurityService



public setPermissionChecker(fn: (name: string) => CheckerResult): void {
public setPermissionChecker(fn: (name: string, resource?: any) => CheckerResult): void {
this.updateState({ permissionsChecker: fn });
}

Expand All @@ -149,15 +149,15 @@ export class NgxSecurityService
this.setPermissions([]);
}

public hasPermission(name: string): Observable<boolean> {
public hasPermission(name: string, resource?: any): Observable<boolean> {
// Check inside state
const check = this.securityState.permissions.some((r: string) => r.toUpperCase() === name);
if (check)
return of(check);

// Check with callback
if (this.securityState.permissionsChecker)
return asObservable(this.securityState.permissionsChecker(name));
return asObservable(this.securityState.permissionsChecker(name, resource));

// Default
return of(false);
Expand Down

0 comments on commit eb878fe

Please sign in to comment.