Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AAE-15018] - Add support for roles as part of the access token autho… #8642

Merged
merged 1 commit into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,12 @@ import { AuthGuardSsoRoleService } from './auth-guard-sso-role.service';
import { JwtHelperService } from '../services/jwt-helper.service';
import { MatDialog } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core';
import { UserAccessService } from '../services/user-access.service';

describe('Auth Guard SSO role service', () => {

let authGuard: AuthGuardSsoRoleService;
let jwtHelperService: JwtHelperService;
let routerService: Router;
let userAccessService: UserAccessService;

setupTestBed({
imports: [
Expand All @@ -44,8 +42,6 @@ describe('Auth Guard SSO role service', () => {
authGuard = TestBed.inject(AuthGuardSsoRoleService);
jwtHelperService = TestBed.inject(JwtHelperService);
routerService = TestBed.inject(Router);
userAccessService = TestBed.inject(UserAccessService);
userAccessService.resetAccess();
});

function spyUserAccess(realmRoles: string[], resourceAccess: any) {
Expand Down
12 changes: 6 additions & 6 deletions lib/core/src/lib/auth/guard/auth-guard-sso-role.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ export class AuthGuardSsoRoleService implements CanActivate {
private dialog: MatDialog) {
}

async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
await this.userAccessService.fetchUserAccess();
canActivate(route: ActivatedRouteSnapshot): boolean {
this.userAccessService.fetchUserAccess();
let hasRealmRole = false;
let hasClientRole = true;
if (route.data) {
Expand All @@ -40,7 +40,7 @@ export class AuthGuardSsoRoleService implements CanActivate {
hasRealmRole = true;
} else {
const excludedRoles = route.data['excludedRoles'] || [];
hasRealmRole = await this.validateRoles(rolesToCheck, excludedRoles);
hasRealmRole = this.validateRoles(rolesToCheck, excludedRoles);
}
}

Expand All @@ -63,14 +63,14 @@ export class AuthGuardSsoRoleService implements CanActivate {
return hasRole;
}

private async validateRoles(rolesToCheck: string[], excludedRoles?: string[]): Promise<boolean> {
private validateRoles(rolesToCheck: string[], excludedRoles?: string[]): boolean {
if (excludedRoles?.length > 0) {
return await this.hasRoles(rolesToCheck) && !await this.hasRoles(excludedRoles);
return this.hasRoles(rolesToCheck) && !this.hasRoles(excludedRoles);
}
return this.hasRoles(rolesToCheck);
}

private async hasRoles(roles: string[] = []): Promise<boolean> {
private hasRoles(roles: string[] = []): boolean {
return this.userAccessService.hasGlobalAccess(roles);
}

Expand Down
1 change: 1 addition & 0 deletions lib/core/src/lib/auth/services/jwt-helper.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class JwtHelperService {
static REALM_ACCESS = 'realm_access';
static RESOURCE_ACCESS = 'resource_access';
static USER_PREFERRED_USERNAME = 'preferred_username';
static HXP_AUTHORIZATION = 'hxp_authorization';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should not affect the general one...


constructor(private storageService: StorageService) {
}
Expand Down
133 changes: 54 additions & 79 deletions lib/core/src/lib/auth/services/user-access.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,11 @@ import { CoreTestingModule, setupTestBed } from '../../testing';
import { TestBed } from '@angular/core/testing';
import { UserAccessService } from './user-access.service';
import { JwtHelperService } from './jwt-helper.service';
import { OAuth2Service } from './oauth2.service';
import { of, throwError } from 'rxjs';
import { userAccessMock } from '../../mock/user-access.mock';
import { AppConfigService } from '../../app-config';

describe('UserAccessService', () => {
let userAccessService: UserAccessService;
let jwtHelperService: JwtHelperService;
let oauth2Service: OAuth2Service;
let appConfigService: AppConfigService;

setupTestBed({
Expand All @@ -37,145 +33,124 @@ describe('UserAccessService', () => {

beforeEach(() => {
userAccessService = TestBed.inject(UserAccessService);
userAccessService.resetAccess();
jwtHelperService = TestBed.inject(JwtHelperService);
oauth2Service = TestBed.inject(OAuth2Service);
appConfigService = TestBed.inject(AppConfigService);
});

function spyUserAccess(realmRoles: string[], resourceAccess: any) {
function spyRealmAccess(realmRoles: string[], resourceAccess: any) {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
realm_access: { roles: realmRoles },
resource_access: resourceAccess
});
}

it('should return true when no roles to check are passed in global access', async () => {
spyUserAccess(['MOCK_USER_ROLE'], {});
await userAccessService.fetchUserAccess();
function spyHxpAuthorization(appkey: string, roles: string[]) {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
hxp_authorization: {
appkey,
role: roles
}
});
}

it('should return true when no roles to check are passed in global access', () => {
spyRealmAccess(['MOCK_USER_ROLE'], {});
userAccessService.fetchUserAccess();
const hasGlobalAccess = userAccessService.hasGlobalAccess([]);

expect(hasGlobalAccess).toBe(true);
});

it('should return true when no roles to check are passed in application access', async () => {
spyUserAccess([], { mockApp: { roles: ['MOCK_APP_ROLE'] } });
await userAccessService.fetchUserAccess();
it('should return true when no roles to check are passed in application access', () => {
spyRealmAccess([], { mockApp: { roles: ['MOCK_APP_ROLE'] } });
userAccessService.fetchUserAccess();
const hasApplicationAccess = userAccessService.hasApplicationAccess('mockApp', []);

expect(hasApplicationAccess).toBe(true);
});

describe('Access from JWT token', () => {
describe('Access present in realm_access', () => {

it('should return true when the user has one of the global roles', async () => {
spyUserAccess(['MOCK_USER_ROLE', 'MOCK_USER_ROLE_2'], {});
await userAccessService.fetchUserAccess();
it('should return true when the user has one of the global roles', () => {
spyRealmAccess(['MOCK_USER_ROLE', 'MOCK_USER_ROLE_2'], {});
userAccessService.fetchUserAccess();
const hasGlobalAccess = userAccessService.hasGlobalAccess(['MOCK_USER_ROLE']);

expect(hasGlobalAccess).toEqual(true);
});

it('should return true when the user has one of the roles for an application', async () => {
spyUserAccess([], { mockApp: { roles: ['MOCK_APP_ROLE', 'MOCK_APP_ROLE_2'] } });
await userAccessService.fetchUserAccess();
it('should return true when the user has one of the roles for an application', () => {
spyRealmAccess([], { mockApp: { roles: ['MOCK_APP_ROLE', 'MOCK_APP_ROLE_2'] } });
userAccessService.fetchUserAccess();
const hasApplicationAccess = userAccessService.hasApplicationAccess('mockApp', ['MOCK_APP_ROLE']);

expect(hasApplicationAccess).toEqual(true);
});

it('should return false when the user has none of the global roles', async () => {
spyUserAccess(['MOCK_USER_ROLE'], {});
await userAccessService.fetchUserAccess();
it('should return false when the user has none of the global roles', () => {
spyRealmAccess(['MOCK_USER_ROLE'], {});
userAccessService.fetchUserAccess();
const hasGlobalAccess = userAccessService.hasGlobalAccess(['MOCK_USER_ROLE_2']);

expect(hasGlobalAccess).toEqual(false);
});

it('should return false when the user has none of the roles for an application', async () => {
spyUserAccess([], { mockApp: { roles: ['MOCK_APP_ROLE'] } });
await userAccessService.fetchUserAccess();
it('should return false when the user has none of the roles for an application', () => {
spyRealmAccess([], { mockApp: { roles: ['MOCK_APP_ROLE'] } });
userAccessService.fetchUserAccess();
const hasApplicationAccess = userAccessService.hasApplicationAccess('mockApp', ['MOCK_APP_ROLE_2']);

expect(hasApplicationAccess).toEqual(false);
});
});

describe('Access from the API', () => {
let getAccessFromApiSpy: jasmine.Spy;
describe('Access present in hxp_authorization', () => {

beforeEach(() => {
spyOn(jwtHelperService, 'getValueFromLocalToken').and.returnValue(undefined);
getAccessFromApiSpy = spyOn(oauth2Service, 'get').and.returnValue(of(userAccessMock));
appConfigService.config.authType = 'OAUTH';
});

it('should return true when the user has one of the global roles', async () => {
await userAccessService.fetchUserAccess();
it('should return true when the user has one of the global roles', () => {
spyHxpAuthorization('mockApp1', ['MOCK_GLOBAL_USER_ROLE']);
appConfigService.config = { application: { key: 'mockApp1' } };
userAccessService.fetchUserAccess();
const hasGlobalAccess = userAccessService.hasGlobalAccess(['MOCK_GLOBAL_USER_ROLE']);

expect(hasGlobalAccess).toEqual(true);
});

it('should return true when the user has one of the roles for an application', async () => {
await userAccessService.fetchUserAccess();
it('should return true when the user has one of the roles for an application', () => {
spyHxpAuthorization('mockApp1', ['MOCK_USER_ROLE_APP_1']);
appConfigService.config = { application: { key: 'mockApp1' } };
userAccessService.fetchUserAccess();
const hasApplicationAccess = userAccessService.hasApplicationAccess('mockApp1', ['MOCK_USER_ROLE_APP_1']);

expect(hasApplicationAccess).toEqual(true);
});

it('should return false when the user has none of the global roles', async () => {
await userAccessService.fetchUserAccess();
it('should return false when the user has none of the global roles', () => {
spyHxpAuthorization('mockApp1', ['MOCK_USER_ROLE_APP_1']);
appConfigService.config = { application: { key: 'mockApp1' } };
userAccessService.fetchUserAccess();
const hasGlobalAccess = userAccessService.hasGlobalAccess(['MOCK_USER_ROLE_NOT_EXISTING']);

expect(hasGlobalAccess).toEqual(false);
});

it('should return false when the user has none of the roles for an application', async () => {
await userAccessService.fetchUserAccess();
it('should return false when the user has none of the roles for an application', () => {
spyHxpAuthorization('mockApp1', ['MOCK_USER_ROLE_APP_1']);
appConfigService.config = { application: { key: 'mockApp1' } };
userAccessService.fetchUserAccess();
const hasApplicationAccess = userAccessService.hasApplicationAccess('mockApp1', ['MOCK_ROLE_NOT_EXISING_IN_APP']);

expect(hasApplicationAccess).toEqual(false);
});
});

it('should not call more than once the api to fetch the user access', async () => {
await userAccessService.fetchUserAccess();
await userAccessService.fetchUserAccess();
await userAccessService.fetchUserAccess();

expect(getAccessFromApiSpy.calls.count()).toEqual(1);
});

it('should the url be composed from bpm host of app.config', async () => {
const fakeIdentityHost = 'https://fake-identity-host.fake.com';
appConfigService.config.bpmHost = fakeIdentityHost;
await userAccessService.fetchUserAccess();

expect(getAccessFromApiSpy).toHaveBeenCalledWith({ url: `${fakeIdentityHost}/identity-adapter-service/v1/roles` });
});

it('should the url contain appkey if its present in app config', async () => {
const fakeIdentityHost = 'https://fake-identity-host.fake.com';
appConfigService.config.bpmHost = fakeIdentityHost;
appConfigService.config.application.key = 'fake-app-key';
await userAccessService.fetchUserAccess();

expect(getAccessFromApiSpy).toHaveBeenCalledWith({ url: `${fakeIdentityHost}/identity-adapter-service/v1/roles` , queryParams: { appkey: 'fake-app-key' } });
});

it('should not fetch the access from the API if is not configured with OAUTH', async () => {
appConfigService.config.authType = 'BASIC';
await userAccessService.fetchUserAccess();

expect(getAccessFromApiSpy).not.toHaveBeenCalled();
});

it('should set empty access list on fething roles error', async () => {
getAccessFromApiSpy.and.returnValue(throwError({ status: 503 }));
await userAccessService.fetchUserAccess();
it('should return false when access is neither in realm_access or hxp_authorization', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue({ mock_access: {} });
userAccessService.fetchUserAccess();
const hasGlobalAccess = userAccessService.hasGlobalAccess(['mock_role']);

expect(userAccessService.hasGlobalAccess(['MOCKED_ROLES'])).toBe(false);
});
expect(hasGlobalAccess).toEqual(false);
});
});
Loading
Loading