Skip to content

Commit 364a53a

Browse files
authored
Merge pull request #109 from netgrif/NAE-1717
[NAE-1717] Banned roles not hiding menu entries
2 parents 422004c + f7eb237 commit 364a53a

File tree

9 files changed

+414
-16
lines changed

9 files changed

+414
-16
lines changed

projects/netgrif-components-core/src/commons/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ export interface Access {
245245
* For `string` values the format is: <net import id>.<role name>
246246
*/
247247
role?: Array<string> | string | RoleAccess | Array<RoleAccess>;
248+
bannedRole?: Array<string> | string | RoleAccess | Array<RoleAccess>;
248249
group?: Array<string> | string;
249250
authority?: Array<string> | string;
250251

projects/netgrif-components-core/src/lib/authorization/permission/access.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class AccessService {
5656
* @returns whether the user passes the role guard condition for accessing the specified view
5757
*/
5858
public passesRoleGuard(view: View, url: string): boolean {
59-
return !view.access.hasOwnProperty('role') || this._roleGuard.canAccessView(view, url);
59+
return (!view.access.hasOwnProperty('role') && !view.access.hasOwnProperty('bannedRole')) || this._roleGuard.canAccessView(view, url);
6060
}
6161

6262
/**
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import {TestBed} from '@angular/core/testing';
2+
3+
import {MaterialModule} from "../../material/material.module";
4+
import {CommonModule} from "@angular/common";
5+
import {FlexModule} from "@angular/flex-layout";
6+
import {BrowserAnimationsModule, NoopAnimationsModule} from "@angular/platform-browser/animations";
7+
import {TranslateLibModule} from "../../translate/translate-lib.module";
8+
import {HttpClientTestingModule} from "@angular/common/http/testing";
9+
import {UserService} from "../../user/services/user.service";
10+
import {MockUserService} from "../../utility/tests/mocks/mock-user.service";
11+
import { Injectable, NO_ERRORS_SCHEMA } from '@angular/core';
12+
import {ConfigurationService} from "../../configuration/configuration.service";
13+
import {TestConfigurationService} from "../../utility/tests/test-config";
14+
import {AuthenticationModule} from "../../authentication/authentication.module";
15+
import {RouterTestingModule} from "@angular/router/testing";
16+
import { RoleGuardService } from './role-guard.service';
17+
import { Access, View } from '../../../commons/schema';
18+
import { User } from '../../user/models/user';
19+
20+
describe('RoleGuardService', () => {
21+
let service: RoleGuardService;
22+
23+
beforeEach(() => {
24+
TestBed.configureTestingModule({
25+
imports: [
26+
MaterialModule,
27+
CommonModule,
28+
FlexModule,
29+
NoopAnimationsModule,
30+
BrowserAnimationsModule,
31+
TranslateLibModule,
32+
HttpClientTestingModule,
33+
AuthenticationModule,
34+
RouterTestingModule.withRoutes([]),
35+
],
36+
providers: [
37+
RoleGuardService,
38+
{provide: ConfigurationService, useClass: TestConfigurationService},
39+
{provide: UserService, useClass: CustomMockUserService},
40+
],
41+
declarations: [],
42+
schemas: [NO_ERRORS_SCHEMA]
43+
});
44+
service = TestBed.inject(RoleGuardService);
45+
});
46+
47+
it('should be created', () => {
48+
expect(service).toBeTruthy();
49+
});
50+
51+
it('should access view with role only', () => {
52+
const view = {access: {role: ['test.role_user']} as Access} as View
53+
expect(service.canAccessView(view, 'test')).toBeTrue();
54+
});
55+
56+
it('should not access view', () => {
57+
const view = {access: {role: [], bannedRole: []} as Access} as View
58+
expect(service.canAccessView(view, 'test')).toBeFalse();
59+
});
60+
61+
it('should not access view with banned role only', () => {
62+
const view = {access: {bannedRole: ['test.banned_role']} as Access} as View
63+
expect(service.canAccessView(view, 'test')).toBeFalse();
64+
});
65+
66+
it('should not access view with role and banned role', () => {
67+
const view = {access: {role: ['test.role_user'], bannedRole: ['test.banned_role']} as Access} as View
68+
expect(service.canAccessView(view, 'test')).toBeFalse();
69+
});
70+
});
71+
72+
@Injectable()
73+
class CustomMockUserService extends MockUserService {
74+
constructor() {
75+
super();
76+
this._user = new User('123', 'test@netgrif.com', 'Test', 'User', ['ROLE_USER'], [{
77+
stringId: 'role_user',
78+
name: 'role_user',
79+
description: '',
80+
importId: 'role_user',
81+
netImportId: 'test',
82+
netVersion: '1.0.0',
83+
netStringId: 'test',
84+
},{
85+
stringId: 'banned_role',
86+
name: 'banned_role',
87+
description: '',
88+
importId: 'banned_role',
89+
netImportId: 'test',
90+
netVersion: '1.0.0',
91+
netStringId: 'test',
92+
} ]);
93+
}
94+
}

projects/netgrif-components-core/src/lib/authorization/role/role-guard.service.ts

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,29 +37,51 @@ export class RoleGuardService implements CanActivate {
3737
}
3838

3939
public canAccessView(view: View, url: string): boolean {
40-
if (typeof view.access !== 'string' && view.access.hasOwnProperty('role')) {
41-
const allowedRoles = this.parseRoleConstraints(view.access.role, url);
42-
43-
return allowedRoles.some(constraint => {
44-
if (constraint.roleIdentifier) {
45-
return this._userService.hasRoleByIdentifier(constraint.roleIdentifier, constraint.processIdentifier);
46-
} else {
47-
return this._userService.hasRoleByName(constraint.roleName, constraint.processIdentifier);
40+
if (typeof view.access !== 'string' && (view.access.hasOwnProperty('role') || view.access.hasOwnProperty('bannedRole'))) {
41+
42+
if (view.access.hasOwnProperty('role') && view.access.hasOwnProperty('bannedRole')) {
43+
const bannedRoles = this.parseRoleConstraints(view.access.bannedRole, url);
44+
const allowedRoles = this.parseRoleConstraints(view.access.role, url);
45+
46+
if (bannedRoles.some(role => this.decideAccessByRole(role))) {
47+
return false;
48+
}
49+
50+
if (allowedRoles.length === 0) {
51+
this._log.warn(`View at '${url}' defines role access constraint with an empty array!`
52+
+ ` No users will be allowed to enter this view!`);
4853
}
49-
});
54+
return allowedRoles.some(role => this.decideAccessByRole(role)); // user was not denied access by a banned role, they need at least one allowed role
55+
}
56+
57+
if (view.access.hasOwnProperty('bannedRole')) {
58+
const bannedRoles = this.parseRoleConstraints(view.access.bannedRole, url);
59+
return !bannedRoles.some(constraint => {
60+
return this.decideAccessByRole(constraint);
61+
});
62+
}
63+
64+
if (view.access.hasOwnProperty('role')) {
65+
const allowedRoles = this.parseRoleConstraints(view.access.role, url);
66+
if (allowedRoles.length === 0) {
67+
this._log.warn(`View at '${url}' defines role access constraint with an empty array!`
68+
+ ` No users will be allowed to enter this view!`);
69+
}
70+
return allowedRoles.some(constraint => {
71+
return this.decideAccessByRole(constraint);
72+
});
73+
}
5074
}
5175
throw new Error('Role guard is declared for a view with no role guard configuration!'
5276
+ ` Add role guard configuration for view at ${url}, or remove the guard.`);
5377
}
5478

55-
protected parseRoleConstraints(roleConstrains: Access['role'], viewUrl: string): Array<RoleConstraint> {
79+
protected parseRoleConstraints(roleConstrains: Access['role'] | Access['bannedRole'], viewUrl: string): Array<RoleConstraint> {
5680
if (typeof roleConstrains === 'string') {
5781
return this.parseStringRoleConstraints(roleConstrains);
5882
}
5983
if (Array.isArray(roleConstrains)) {
6084
if (roleConstrains.length === 0) {
61-
this._log.warn(`View at '${viewUrl}' defines role access constraint with an empty array!`
62-
+ ` No users will be allowed to enter this view!`);
6385
return [];
6486
}
6587
if (typeof roleConstrains[0] === 'string') {
@@ -102,4 +124,12 @@ export class RoleGuardService implements CanActivate {
102124
});
103125
}
104126

127+
private decideAccessByRole(constraint: RoleConstraint): boolean {
128+
if (constraint.roleIdentifier) {
129+
return this._userService.hasRoleByIdentifier(constraint.roleIdentifier, constraint.processIdentifier);
130+
} else {
131+
return this._userService.hasRoleByName(constraint.roleName, constraint.processIdentifier);
132+
}
133+
}
134+
105135
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import {waitForAsync, ComponentFixture, TestBed} from '@angular/core/testing';
2+
import {CommonModule} from '@angular/common';
3+
import {FlexLayoutModule, FlexModule} from '@angular/flex-layout';
4+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
5+
import {HttpClientTestingModule} from '@angular/common/http/testing';
6+
import {MaterialModule} from '../../material/material.module';
7+
import {TranslateLibModule} from '../../translate/translate-lib.module';
8+
import {ConfigurationService} from '../../configuration/configuration.service';
9+
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
10+
import {BreakpointObserver} from '@angular/cdk/layout';
11+
import {LoggerService} from '../../logger/services/logger.service';
12+
import {RouterTestingModule} from '@angular/router/testing';
13+
import {UserPreferenceService} from '../../user/services/user-preference.service';
14+
import {MockUserPreferenceService} from '../../utility/tests/mocks/mock-user-preference.service';
15+
import {ResizableModule} from 'angular-resizable-element';
16+
import {TestLoggingConfigurationService} from '../../utility/tests/test-logging-config';
17+
import {AuthenticationMethodService} from '../../authentication/services/authentication-method.service';
18+
import {MockAuthenticationMethodService} from '../../utility/tests/mocks/mock-authentication-method-service';
19+
import {AuthenticationService} from '../../authentication/services/authentication/authentication.service';
20+
import {UserResourceService} from '../../resources/engine-endpoint/user-resource.service';
21+
import {MockAuthenticationService} from '../../utility/tests/mocks/mock-authentication.service';
22+
import {MockUserResourceService} from '../../utility/tests/mocks/mock-user-resource.service';
23+
import { AbstractNavigationDoubleDrawerComponent } from './abstract-navigation-double-drawer';
24+
import { ActivatedRoute, Router } from '@angular/router';
25+
import { UserService } from '../../user/services/user.service';
26+
import { AccessService } from '../../authorization/permission/access.service';
27+
import { UriService } from '../service/uri.service';
28+
import { LanguageService } from '../../translate/language.service';
29+
import {
30+
DynamicNavigationRouteProviderService
31+
} from '../../routing/dynamic-navigation-route-provider/dynamic-navigation-route-provider.service';
32+
import { AuthenticationModule } from '../../authentication/authentication.module';
33+
34+
describe('AbstractNavigationDoubleDrawerComponent', () => {
35+
let component: TestDrawerComponent;
36+
let fixture: ComponentFixture<TestDrawerComponent>;
37+
38+
beforeEach(waitForAsync(() => {
39+
TestBed.configureTestingModule({
40+
declarations: [TestDrawerComponent],
41+
imports: [
42+
CommonModule,
43+
RouterTestingModule.withRoutes([]),
44+
MaterialModule,
45+
FlexModule,
46+
FlexLayoutModule,
47+
NoopAnimationsModule,
48+
TranslateLibModule,
49+
HttpClientTestingModule,
50+
ResizableModule,
51+
AuthenticationModule
52+
],
53+
providers: [
54+
{provide: AuthenticationMethodService, useClass: MockAuthenticationMethodService},
55+
{provide: ConfigurationService, useClass: TestLoggingConfigurationService},
56+
{provide: AuthenticationService, useClass: MockAuthenticationService},
57+
{provide: UserResourceService, useClass: MockUserResourceService},
58+
{provide: UserPreferenceService, useClass: MockUserPreferenceService}
59+
],
60+
schemas: [CUSTOM_ELEMENTS_SCHEMA]
61+
}).compileComponents();
62+
}));
63+
64+
beforeEach(() => {
65+
fixture = TestBed.createComponent(TestDrawerComponent);
66+
component = fixture.componentInstance;
67+
spyOn(component, 'toggleLeftMenu');
68+
spyOn(component, 'toggleRightMenu');
69+
fixture.detectChanges();
70+
});
71+
72+
it('should create', () => {
73+
expect(component).toBeTruthy();
74+
});
75+
76+
it('should toggle menu', (done) => {
77+
component.toggleMenu();
78+
expect(component.toggleLeftMenu).toHaveBeenCalled();
79+
expect(component.toggleRightMenu).toHaveBeenCalled();
80+
done();
81+
});
82+
83+
it('should logout', () => {
84+
component.logout();
85+
expect(component.user.id).toEqual('');
86+
});
87+
88+
afterEach(() => {
89+
TestBed.resetTestingModule();
90+
});
91+
92+
});
93+
94+
@Component({
95+
selector: 'ncc-test-nav-drawer',
96+
template: ''
97+
})
98+
class TestDrawerComponent extends AbstractNavigationDoubleDrawerComponent {
99+
constructor(_router: Router,
100+
_activatedRoute: ActivatedRoute,
101+
_breakpoint: BreakpointObserver,
102+
_languageService: LanguageService,
103+
_userService: UserService,
104+
_accessService: AccessService,
105+
_log: LoggerService,
106+
_config: ConfigurationService,
107+
_uriService: UriService,
108+
_dynamicRouteProviderService: DynamicNavigationRouteProviderService) {
109+
super(_router, _activatedRoute, _breakpoint, _languageService, _userService, _accessService, _log, _config, _uriService,
110+
_dynamicRouteProviderService)
111+
}
112+
}
113+
114+

projects/netgrif-components-core/src/lib/navigation/navigation-double-drawer/abstract-navigation-double-drawer.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -353,14 +353,16 @@ export abstract class AbstractNavigationDoubleDrawerComponent implements OnInit,
353353
id: filter.stringId,
354354
resource: filter
355355
};
356-
const resolvedRoles = this.resolveAccessRoles(filter);
356+
const resolvedRoles = this.resolveAccessRoles(filter, 'allowed_roles');
357+
const resolvedBannedRoles = this.resolveAccessRoles(filter, 'banned_roles');
357358
if(!!resolvedRoles) item.access['role'] = resolvedRoles;
359+
if(!!resolvedBannedRoles) item.access['bannedRole'] = resolvedBannedRoles;
358360
if (!this._accessService.canAccessView(item, item.routingPath)) return;
359361
return item;
360362
}
361363

362-
protected resolveAccessRoles(filter: Case): Array<RoleAccess> | undefined {
363-
const allowedRoles = filter.immediateData.find(f => f.stringId === 'allowed_roles')?.options;
364+
protected resolveAccessRoles(filter: Case, roleType: string): Array<RoleAccess> | undefined {
365+
const allowedRoles = filter.immediateData.find(f => f.stringId === roleType)?.options;
364366
if (!allowedRoles || Object.keys(allowedRoles).length === 0) return undefined;
365367
const roles = [];
366368
Object.keys(allowedRoles).forEach(combined => {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {TestBed} from '@angular/core/testing';
2+
import { UriService } from './uri.service';
3+
import { HttpClientTestingModule } from '@angular/common/http/testing';
4+
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
5+
import { ConfigurationService } from '../../configuration/configuration.service';
6+
import { TestLoggingConfigurationService } from '../../utility/tests/test-logging-config';
7+
import { AuthenticationModule } from '../../authentication/authentication.module';
8+
import { UriResourceService } from './uri-resource.service';
9+
import { MockUriResourceService } from '../../utility/tests/mocks/mock-uri-resource.service';
10+
import { RouterTestingModule } from '@angular/router/testing';
11+
12+
describe('UriService', () => {
13+
let service: UriService;
14+
15+
beforeEach(() => {
16+
TestBed.configureTestingModule({
17+
imports: [
18+
HttpClientTestingModule,
19+
NoopAnimationsModule,
20+
AuthenticationModule,
21+
RouterTestingModule.withRoutes([])
22+
],
23+
providers: [
24+
{provide: ConfigurationService, useClass: TestLoggingConfigurationService},
25+
{provide: UriResourceService, useClass: MockUriResourceService}
26+
]
27+
});
28+
service = TestBed.inject(UriService);
29+
});
30+
31+
it('should be created', () => {
32+
expect(service).toBeTruthy();
33+
});
34+
35+
it('should get node by path', done => {
36+
const sub = service.getNodeByPath('test');
37+
sub.subscribe(res => {
38+
expect(res.uriPath).toEqual('root/test1')
39+
done();
40+
})
41+
});
42+
43+
it('should get nodes by level', done => {
44+
const sub = service.getNodesOnLevel(1);
45+
sub.subscribe(res => {
46+
expect(res.length).toEqual(2)
47+
done();
48+
})
49+
});
50+
51+
it('should get child nodes', done => {
52+
const sub = service.getChildNodes();
53+
sub.subscribe(res => {
54+
expect(res.length).toEqual(2)
55+
done();
56+
})
57+
});
58+
59+
});

0 commit comments

Comments
 (0)