diff --git a/docs/core/services/auth-guard-sso-role.service.md b/docs/core/services/auth-guard-sso-role.service.md index 3d8917caeda..ae2f09edde6 100644 --- a/docs/core/services/auth-guard-sso-role.service.md +++ b/docs/core/services/auth-guard-sso-role.service.md @@ -31,9 +31,10 @@ const appRoutes: Routes = [ ``` If the user now clicks on a link or button that follows this route, they will be not able to access this content if they do not have the Realms roles. +
**Note**: An additional role ALFRESCO_ADMINISTRATORS can be used in the roles array, which will result in checking whether the logged in user has Content Admin capabilities or not, as this role is not part of the JWT token it will call a Content API to determine it. -Client role Example +Client role Example ```ts const appRoutes: Routes = [ ... diff --git a/e2e/protractor.excludes.json b/e2e/protractor.excludes.json index 2cd61e8d20f..bf3f9244f04 100644 --- a/e2e/protractor.excludes.json +++ b/e2e/protractor.excludes.json @@ -5,5 +5,7 @@ "C279931": "login problem APS not basic", "C279930": "login problem APS not basic", "C593560": "https://alfresco.atlassian.net/browse/ADF-5366", + "C269081": "https://alfresco.atlassian.net/browse/ADF-5385", + "C272819": "https://alfresco.atlassian.net/browse/ADF-5385", "C290069": "https://alfresco.atlassian.net/browse/ADF-5387" } diff --git a/lib/core/mock/ecm-user.service.mock.ts b/lib/core/mock/ecm-user.service.mock.ts index a968b3cdd21..813c0a3bc17 100644 --- a/lib/core/mock/ecm-user.service.mock.ts +++ b/lib/core/mock/ecm-user.service.mock.ts @@ -16,6 +16,7 @@ */ import { EcmCompanyModel } from '../models/ecm-company.model'; +import { PersonEntry, Person } from '@alfresco/js-api'; export let fakeEcmCompany: EcmCompanyModel = { organization: 'company-fake-name', @@ -99,3 +100,25 @@ export const createNewPersonMock = { password: 'fake-avatar-id', email: 'fakeEcm@ecmUser.com' }; + +export function getFakeUserWithContentAdminCapability(): PersonEntry { + const fakeEcmUserWithAdminCapabilities = { + ...fakeEcmUser, + capabilities: { + isAdmin: true + } + }; + const mockPerson = new Person(fakeEcmUserWithAdminCapabilities); + return { entry: mockPerson }; +} + +export function getFakeUserWithContentUserCapability(): PersonEntry { + const fakeEcmUserWithAdminCapabilities = { + ...fakeEcmUser, + capabilities: { + isAdmin: false + } + }; + const mockPerson = new Person(fakeEcmUserWithAdminCapabilities); + return { entry: mockPerson }; +} diff --git a/lib/core/services/auth-guard-sso-role.service.spec.ts b/lib/core/services/auth-guard-sso-role.service.spec.ts index fd0fd5b294f..12a810903d4 100644 --- a/lib/core/services/auth-guard-sso-role.service.spec.ts +++ b/lib/core/services/auth-guard-sso-role.service.spec.ts @@ -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(); + }); + }); }); diff --git a/lib/core/services/auth-guard-sso-role.service.ts b/lib/core/services/auth-guard-sso-role.service.ts index 648e188ee14..60c1087b0c3 100644 --- a/lib/core/services/auth-guard-sso-role.service.ts +++ b/lib/core/services/auth-guard-sso-role.service.ts @@ -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 { 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']) { diff --git a/lib/core/services/people-content.service.spec.ts b/lib/core/services/people-content.service.spec.ts index cef4ff4ca5b..5258c51df96 100644 --- a/lib/core/services/people-content.service.spec.ts +++ b/lib/core/services/people-content.service.spec.ts @@ -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); + }); }); diff --git a/lib/core/services/people-content.service.ts b/lib/core/services/people-content.service.ts index 883b6f30a8e..827f52c946e 100644 --- a/lib/core/services/people-content.service.ts +++ b/lib/core/services/people-content.service.ts @@ -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 { @@ -69,6 +76,15 @@ export class PeopleContentService { ); } + async isContentAdmin(): Promise { + 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');