mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-12 17:04:57 +00:00
[AAE-15018] - Add support for roles as part of the access token authorization (#8642)
This commit is contained in:
parent
b16777cce0
commit
f0d2456f42
@ -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: [
|
||||
@ -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) {
|
||||
|
@ -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) {
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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';
|
||||
|
||||
constructor(private storageService: StorageService) {
|
||||
}
|
||||
|
@ -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({
|
||||
@ -37,13 +33,11 @@ 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 },
|
||||
@ -51,131 +45,112 @@ describe('UserAccessService', () => {
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
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(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();
|
||||
|
||||
expect(userAccessService.hasGlobalAccess(['MOCKED_ROLES'])).toBe(false);
|
||||
});
|
||||
expect(hasGlobalAccess).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
@ -18,13 +18,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { JwtHelperService } from './jwt-helper.service';
|
||||
import { ApplicationAccessModel } from '../models/application-access.model';
|
||||
import { UserAccessModel } from '../models/user-access.model';
|
||||
import { AppConfigService } from '../../app-config/app-config.service';
|
||||
import { OAuth2Service } from './oauth2.service';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
const IDENTITY_MICRO_SERVICE_INGRESS = 'identity-adapter-service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -34,60 +28,43 @@ export class UserAccessService {
|
||||
private applicationAccess: ApplicationAccessModel[];
|
||||
|
||||
constructor(private jwtHelperService: JwtHelperService,
|
||||
private appConfigService: AppConfigService,
|
||||
private oAuth2Service: OAuth2Service) {
|
||||
private appConfigService: AppConfigService) {
|
||||
}
|
||||
|
||||
async fetchUserAccess() {
|
||||
if (!this.hasFetchedAccess()) {
|
||||
if (this.hasRolesInJwt()) {
|
||||
this.fetchAccessFromJwt();
|
||||
} else if (this.isOauth()) {
|
||||
await this.fetchAccessFromApi();
|
||||
}
|
||||
fetchUserAccess() {
|
||||
if (this.hasRolesInRealmAccess()) {
|
||||
this.fetchAccessFromRealmAccess();
|
||||
} else if (this.hasRolesInHxpAuthorization()) {
|
||||
this.fetchAccessFromHxpAuthorization();
|
||||
}
|
||||
}
|
||||
|
||||
private fetchAccessFromJwt() {
|
||||
private fetchAccessFromRealmAccess() {
|
||||
this.globalAccess = this.jwtHelperService.getValueFromLocalToken<any>(JwtHelperService.REALM_ACCESS).roles;
|
||||
this.applicationAccess = this.jwtHelperService.getValueFromLocalToken<any>(JwtHelperService.RESOURCE_ACCESS);
|
||||
}
|
||||
|
||||
private async fetchAccessFromApi() {
|
||||
const url = `${this.identityHost}/${IDENTITY_MICRO_SERVICE_INGRESS}/v1/roles`;
|
||||
const appkey = this.appConfigService.get('application.key');
|
||||
const opts = appkey ? { url, queryParams: { appkey } } : { url };
|
||||
|
||||
await this.oAuth2Service.get(opts)
|
||||
.pipe(
|
||||
catchError(() => of({
|
||||
globalAccess: {
|
||||
roles: []
|
||||
},
|
||||
applicationAccess: []
|
||||
}))
|
||||
)
|
||||
.toPromise()
|
||||
.then((response: UserAccessModel) => {
|
||||
this.globalAccess = response.globalAccess.roles;
|
||||
this.applicationAccess = response.applicationAccess;
|
||||
});
|
||||
private fetchAccessFromHxpAuthorization() {
|
||||
this.globalAccess = [];
|
||||
const hxpAuthorization = this.jwtHelperService.getValueFromLocalToken<any>(JwtHelperService.HXP_AUTHORIZATION);
|
||||
if (hxpAuthorization?.appkey && hxpAuthorization?.role) {
|
||||
this.applicationAccess = [
|
||||
{
|
||||
name: hxpAuthorization.appkey,
|
||||
roles: hxpAuthorization.role
|
||||
}
|
||||
];
|
||||
} else {
|
||||
this.applicationAccess = [];
|
||||
}
|
||||
}
|
||||
|
||||
private hasRolesInJwt(): boolean {
|
||||
private hasRolesInRealmAccess(): boolean {
|
||||
return !!this.jwtHelperService.getValueFromLocalToken(JwtHelperService.REALM_ACCESS);
|
||||
}
|
||||
|
||||
private hasFetchedAccess(): boolean {
|
||||
return !!this.globalAccess && !!this.applicationAccess;
|
||||
}
|
||||
|
||||
private get identityHost(): string {
|
||||
return `${this.appConfigService.get('bpmHost')}`;
|
||||
}
|
||||
|
||||
private isOauth(): boolean {
|
||||
return this.appConfigService.get('authType') === 'OAUTH';
|
||||
private hasRolesInHxpAuthorization(): boolean {
|
||||
return !!this.jwtHelperService.getValueFromLocalToken(JwtHelperService.HXP_AUTHORIZATION);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,9 +75,20 @@ export class UserAccessService {
|
||||
*/
|
||||
hasGlobalAccess(rolesToCheck: string[]): boolean {
|
||||
if (rolesToCheck?.length > 0) {
|
||||
return this.globalAccess ? this.globalAccess.some((role: string) => rolesToCheck.includes(role)) : false;
|
||||
if (this.hasRolesInRealmAccess()) {
|
||||
return this.globalAccess ? this.globalAccess.some((role: string) => rolesToCheck.includes(role)) : false;
|
||||
} else if (this.hasRolesInHxpAuthorization()) {
|
||||
return this.isCurrentAppKeyInToken() ? this.applicationAccess[0]?.roles.some((role: string) => rolesToCheck.includes(role)) : false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private isCurrentAppKeyInToken(): boolean {
|
||||
const currentAppKey = this.appConfigService.get('application.key');
|
||||
return this.applicationAccess?.length ? currentAppKey === this.applicationAccess[0]?.name : false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,17 +100,9 @@ export class UserAccessService {
|
||||
*/
|
||||
hasApplicationAccess(appName: string, rolesToCheck: string[]): boolean {
|
||||
if (rolesToCheck?.length > 0) {
|
||||
const appAccess = this.hasRolesInJwt() ? this.applicationAccess[appName] : this.applicationAccess.find((app: ApplicationAccessModel) => app.name === appName);
|
||||
const appAccess = this.hasRolesInRealmAccess() ? this.applicationAccess[appName] : this.applicationAccess.find((app: ApplicationAccessModel) => app.name === appName);
|
||||
return appAccess ? appAccess.roles.some(appRole => rolesToCheck.includes(appRole)) : false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the cached user access
|
||||
*/
|
||||
resetAccess() {
|
||||
this.globalAccess = undefined;
|
||||
this.applicationAccess = undefined;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user