[AAE-4985] - Make SSO Role Service accept a content admin role that is not part of the JWT token (#6942)

* Add ability to check if the user is an ACS_ADMIN - not part of JTW token

* Make get user api call only once

* Add unit tests

* Add documentation

* Fix comments

* Exclude flaky tests, dependent on another test

* Fix unit test

* Fix comments

* Update documentation
This commit is contained in:
arditdomi
2021-04-26 14:27:22 +01:00
committed by GitHub
parent 585a1b6918
commit 574db8d7cc
7 changed files with 106 additions and 7 deletions

View File

@@ -23,12 +23,16 @@ import { AuthGuardSsoRoleService } from './auth-guard-sso-role.service';
import { JwtHelperService } from './jwt-helper.service';
import { MatDialog } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core';
import { PeopleContentService } from './people-content.service';
import { of } from 'rxjs';
import { getFakeUserWithContentAdminCapability, getFakeUserWithContentUserCapability } from '../mock/ecm-user.service.mock';
describe('Auth Guard SSO role service', () => {
let authGuard: AuthGuardSsoRoleService;
let jwtHelperService: JwtHelperService;
let routerService: Router;
let peopleContentService: PeopleContentService;
setupTestBed({
imports: [
@@ -42,6 +46,7 @@ describe('Auth Guard SSO role service', () => {
authGuard = TestBed.inject(AuthGuardSsoRoleService);
jwtHelperService = TestBed.inject(JwtHelperService);
routerService = TestBed.inject(Router);
peopleContentService = TestBed.inject(PeopleContentService);
});
it('Should canActivate be true if the Role is present int the JWT token', async(async () => {
@@ -185,4 +190,39 @@ describe('Auth Guard SSO role service', () => {
expect(await authGuard.canActivate(route)).toBeFalsy();
expect(materialDialog.closeAll).toHaveBeenCalled();
});
describe('Content Admin', () => {
afterEach(() => {
peopleContentService.hasCheckedIsContentAdmin = false;
});
it('Should give access to a content section (ALFRESCO_ADMINISTRATORS) when the user has content admin capability', async () => {
spyOn(peopleContentService, 'getCurrentPerson').and.returnValue(of(getFakeUserWithContentAdminCapability()));
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
router.data = { 'roles': ['ALFRESCO_ADMINISTRATORS'] };
expect(await authGuard.canActivate(router)).toBeTruthy();
});
it('Should not give access to a content section (ALFRESCO_ADMINISTRATORS) when the user does not have content admin capability', async () => {
spyOn(peopleContentService, 'getCurrentPerson').and.returnValue(of(getFakeUserWithContentUserCapability()));
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
router.data = { 'roles': ['ALFRESCO_ADMINISTRATORS'] };
expect(await authGuard.canActivate(router)).toBeFalsy();
});
it('Should not call the service to check if the user has content admin capability when the roles do not contain ALFRESCO_ADMINISTRATORS', async () => {
const getCurrentPersonSpy = spyOn(peopleContentService, 'getCurrentPerson').and.returnValue(of(getFakeUserWithContentAdminCapability()));
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
router.data = { 'roles': ['fakeRole'] };
await authGuard.canActivate(router);
expect(getCurrentPersonSpy).not.toHaveBeenCalled();
});
});
});

View File

@@ -19,24 +19,28 @@ import { Injectable } from '@angular/core';
import { JwtHelperService } from './jwt-helper.service';
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';
import { ContentGroups, PeopleContentService } from './people-content.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuardSsoRoleService implements CanActivate {
constructor(private jwtHelperService: JwtHelperService, private router: Router, private dialog: MatDialog) {
constructor(private jwtHelperService: JwtHelperService,
private router: Router,
private dialog: MatDialog,
private peopleContentService: PeopleContentService) {
}
canActivate(route: ActivatedRouteSnapshot): boolean {
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
let hasRole;
let hasRealmRole = false;
let hasClientRole = true;
if (route.data) {
if (route.data['roles']) {
const rolesToCheck = route.data['roles'];
hasRealmRole = this.jwtHelperService.hasRealmRoles(rolesToCheck);
const rolesToCheck: string[] = route.data['roles'];
const isContentAdmin = rolesToCheck.includes(ContentGroups.ALFRESCO_ADMINISTRATORS) ? await this.peopleContentService.isContentAdmin() : false;
hasRealmRole = this.jwtHelperService.hasRealmRoles(rolesToCheck) || isContentAdmin;
}
if (route.data['clientRoles']) {

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { fakeEcmUser, createNewPersonMock } from '../mock/ecm-user.service.mock';
import { fakeEcmUser, createNewPersonMock, getFakeUserWithContentAdminCapability } from '../mock/ecm-user.service.mock';
import { AlfrescoApiServiceMock } from '../mock/alfresco-api.service.mock';
import { CoreTestingModule } from '../testing/core.testing.module';
import { PeopleContentService } from './people-content.service';
@@ -24,6 +24,7 @@ import { setupTestBed } from '../testing/setup-test-bed';
import { TranslateModule } from '@ngx-translate/core';
import { TestBed } from '@angular/core/testing';
import { LogService } from './log.service';
import { of } from 'rxjs';
describe('PeopleContentService', () => {
@@ -101,4 +102,16 @@ describe('PeopleContentService', () => {
done();
});
});
it('Should make the api call to check if the user is a content admin only once', async () => {
const getCurrentPersonSpy = spyOn(service.peopleApi, 'getPerson').and.returnValue(of(getFakeUserWithContentAdminCapability()));
expect(await service.isContentAdmin()).toBe(true);
expect(getCurrentPersonSpy.calls.count()).toEqual(1);
await service.isContentAdmin();
expect(await service.isContentAdmin()).toBe(true);
expect(getCurrentPersonSpy.calls.count()).toEqual(1);
});
});

View File

@@ -23,10 +23,16 @@ import { PersonEntry, PeopleApi, PersonBodyCreate } from '@alfresco/js-api';
import { EcmUserModel } from '../models/ecm-user.model';
import { LogService } from './log.service';
export enum ContentGroups {
ALFRESCO_ADMINISTRATORS = 'ALFRESCO_ADMINISTRATORS'
}
@Injectable({
providedIn: 'root'
})
export class PeopleContentService {
private hasContentAdminRole: boolean = false;
hasCheckedIsContentAdmin: boolean = false;
private _peopleApi: PeopleApi;
@@ -60,6 +66,7 @@ export class PeopleContentService {
/**
* Creates new person.
* @param newPerson Object containing the new person details.
* @param opts Optional parameters
* @returns Created new person
*/
createPerson(newPerson: PersonBodyCreate, opts?: any): Observable<EcmUserModel> {
@@ -69,6 +76,15 @@ export class PeopleContentService {
);
}
async isContentAdmin(): Promise<boolean> {
if (!this.hasCheckedIsContentAdmin) {
const user: PersonEntry = await this.getCurrentPerson().toPromise();
this.hasContentAdminRole = user?.entry?.capabilities?.isAdmin;
this.hasCheckedIsContentAdmin = true;
}
return this.hasContentAdminRole;
}
private handleError(error: any) {
this.logService.error(error);
return throwError(error || 'Server error');