diff --git a/docs/core/user-info.component.md b/docs/core/user-info.component.md index 421c737e95..9db2214241 100644 --- a/docs/core/user-info.component.md +++ b/docs/core/user-info.component.md @@ -32,3 +32,4 @@ Shows user information. The component shows a round icon for the user and will show extra information about the user when clicked. If user is logged in with both ACS and APS, the ACS image will be shown. +In case of SSO authentication, the information related to the user like firstname, lastname will be fetched using the Keycloak Api diff --git a/lib/core/mock/bpm-user.service.mock.ts b/lib/core/mock/bpm-user.service.mock.ts index 796260bab9..cbbc6236ca 100644 --- a/lib/core/mock/bpm-user.service.mock.ts +++ b/lib/core/mock/bpm-user.service.mock.ts @@ -24,7 +24,6 @@ export let fakeBpmUserNoImage = { externalId: 'fake-external-id', firstName: 'fake-first-name', lastName: 'fake-last-name', - fullname: 'fake-full-name', groups: [], id: 'fake-id', lastUpdate: 'fake-update-date', @@ -47,7 +46,6 @@ export let fakeBpmUser = { externalId: 'fake-external-id', firstName: 'fake-bpm-first-name', lastName: 'fake-bpm-last-name', - fullname: 'fake-bpm-full-name', groups: [], id: 'fake-id', lastUpdate: 'fake-update-date', @@ -70,7 +68,6 @@ export let fakeBpmEditedUser = { externalId: 'fake-external-id', firstName: 'fake-first-name', lastName: 'fake-last-name', - fullname: 'fake-full-name', groups: [], id: 'fake-id', lastUpdate: 'fake-update-date', diff --git a/lib/core/mock/jwt-helper.service.spec.ts b/lib/core/mock/jwt-helper.service.spec.ts new file mode 100644 index 0000000000..9329ed525e --- /dev/null +++ b/lib/core/mock/jwt-helper.service.spec.ts @@ -0,0 +1,22 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export let mockToken = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.' + + 'eyJzdWIiOiIxMjM0NTY3ODkwIiwiZW1haWwiOiJqb2huRG9lQGdtYWlsLmNvbSI' + + 'sImdpdmVuX25hbWUiOiJKb2huIERvZSIsImp0aSI6IjY1ZGMzZTEyLWJhNGUtNDQ' + + '2Mi1iZjAyLTBlZGQ2MTYwM2M2NCIsImlhdCI6MTU0MjcyMTYxMywiZXhwIjoxNTQyNzI3NzY5fQ' + + '.cUxMzfiJeLwh9Er2CBn_y8ehQgSm_s2-NHehx-SRZKg'; diff --git a/lib/core/mock/public-api.ts b/lib/core/mock/public-api.ts index 3f9e626ad4..1b943355a3 100644 --- a/lib/core/mock/public-api.ts +++ b/lib/core/mock/public-api.ts @@ -36,3 +36,4 @@ export * from './form/formDefinitionVisibility.mock'; export * from './form/start-form.component.mock'; export * from './form/form.service.mock'; export * from './form/widget-visibility.service.mock'; +export * from './jwt-helper.service.spec'; diff --git a/lib/core/services/jwt-helper.service.spec.ts b/lib/core/services/jwt-helper.service.spec.ts new file mode 100644 index 0000000000..ac66526235 --- /dev/null +++ b/lib/core/services/jwt-helper.service.spec.ts @@ -0,0 +1,45 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TestBed } from '@angular/core/testing'; +import { JwtHelperService } from './jwt-helper.service'; +import { mockToken } from './../mock/jwt-helper.service.spec'; + +describe('JwtHelperService', () => { + + let jwtHelperService: JwtHelperService; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [JwtHelperService] + }); + jwtHelperService = TestBed.get(JwtHelperService); + }); + + it('should be able to create the service', () => { + expect(jwtHelperService).not.toBeNull(); + expect(jwtHelperService).toBeDefined(); + }); + + it('Should decode the Jwt token', () => { + const result = jwtHelperService.decodeToken(mockToken); + expect(result).toBeDefined(); + expect(result).not.toBeNull(''); + expect(result['given_name']).toBe('John Doe'); + expect(result['email']).toBe('johnDoe@gmail.com'); + }); +}); diff --git a/lib/core/services/jwt-helper.service.ts b/lib/core/services/jwt-helper.service.ts new file mode 100644 index 0000000000..192299b935 --- /dev/null +++ b/lib/core/services/jwt-helper.service.ts @@ -0,0 +1,62 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class JwtHelperService { + + constructor() {} + + decodeToken(token): Object { + let parts = token.split('.'); + + if (parts.length !== 3) { + throw new Error('JWT must have 3 parts'); + } + + let decoded = this.urlBase64Decode(parts[1]); + if (!decoded) { + throw new Error('Cannot decode the token'); + } + + return JSON.parse(decoded); + } + + private urlBase64Decode(token): string { + let output = token.replace(/-/g, '+').replace(/_/g, '/'); + switch (output.length % 4) { + case 0: { + break; + } + case 2: { + output += '=='; + break; + } + case 3: { + output += '='; + break; + } + default: { + throw new Error('Illegal base64url string!'); + } + } + return decodeURIComponent(escape(window.atob(output))); + } +} diff --git a/lib/core/services/public-api.ts b/lib/core/services/public-api.ts index 2e7c97b866..78059ea9c2 100644 --- a/lib/core/services/public-api.ts +++ b/lib/core/services/public-api.ts @@ -50,3 +50,4 @@ export * from './search-configuration.service'; export * from './comment-content.service'; export * from './login-dialog.service'; export * from './external-alfresco-api.service'; +export * from './jwt-helper.service'; diff --git a/lib/core/userinfo/components/user-info.component.html b/lib/core/userinfo/components/user-info.component.html index be3a6d7143..5303a54eb5 100644 --- a/lib/core/userinfo/components/user-info.component.html +++ b/lib/core/userinfo/components/user-info.component.html @@ -1,48 +1,68 @@ -
- {{ecmUser.fullNameDisplay}} - {{bpmUser.fullNameDisplay}} + + + {{identityUser | fullName}} + + {{ecmUser | fullName}} + + {{bpmUser | fullName}} + + + + + +
+
+
+ +
+
+ user-info-profile-button +
+ +
+
+
+
+ + + - + class="adf-userinfo-tab" [class.adf-hide-tab]="!(bpmUser$ | async) || !(ecmUser$ | async)"> + -
-
-
+
ecm-profile-image + alt="ecm-profile-image" [src]="getEcmAvatar(ecmUser.avatarId)" />
-
{{ecmUser.fullNameDisplay}}
+ +
+
+ +
{{ecmUser | fullName}}
- {{ecmUser.fullNameDisplay}} + {{ecmUser | fullName}} {{ecmUser.email}}
@@ -55,18 +75,20 @@ - + -
- bpm-profile-image -
{{bpmUser.fullNameDisplay}}
+ bpm-profile-image + +
+
+
{{bpmUser | fullName}}
- {{ bpmUser.fullNameDisplay }} + {{ bpmUser | fullName }} {{bpmUser.email}}
@@ -79,6 +101,24 @@ + + + +
+
+
{{identityUser | fullName}}
+
+ +
+
+ {{identityUser | fullName}} + {{identityUser.email}} +
+
+
+
+
diff --git a/lib/core/userinfo/components/user-info.component.spec.ts b/lib/core/userinfo/components/user-info.component.spec.ts index c168466d4b..88b8e6d23c 100644 --- a/lib/core/userinfo/components/user-info.component.spec.ts +++ b/lib/core/userinfo/components/user-info.component.spec.ts @@ -23,11 +23,14 @@ import { fakeBpmUser } from '../../mock/bpm-user.service.mock'; import { fakeEcmEditedUser, fakeEcmUser, fakeEcmUserNoImage } from '../../mock/ecm-user.service.mock'; import { BpmUserService } from '../services/bpm-user.service'; import { EcmUserService } from '../services/ecm-user.service'; +import { IdentityUserService } from '../services/identity-user.service'; import { BpmUserModel } from './../models/bpm-user.model'; +import { EcmUserModel } from './../models/ecm-user.model'; import { UserInfoComponent } from './user-info.component'; import { of } from 'rxjs'; import { setupTestBed } from '../../testing/setupTestBed'; import { CoreTestingModule } from '../../testing/core.testing.module'; +import { IdentityUserModel } from '../models/identity-user.model'; class FakeSanitizer extends DomSanitizer { @@ -69,6 +72,11 @@ describe('User info component', () => { let contentService: ContentService; let ecmUserService: EcmUserService; let bpmUserService: BpmUserService; + let identityUserService: IdentityUserService; + + let identityUserMock = { firstName: 'fake-identity-first-name', lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' }; + let identityUserWithOutFirstNameMock = { firstName: null, lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' }; + let identityUserWithOutLastNameMock = { firstName: 'fake-identity-first-name', lastName: null, email: 'fakeIdentity@email.com' }; function openUserInfo() { fixture.detectChanges(); @@ -90,6 +98,7 @@ describe('User info component', () => { ecmUserService = TestBed.get(EcmUserService); bpmUserService = TestBed.get(BpmUserService); contentService = TestBed.get(ContentService); + identityUserService = TestBed.get(IdentityUserService); })); afterEach(() => { @@ -113,11 +122,25 @@ describe('User info component', () => { describe('ui ', () => { beforeEach(() => { + spyOn(authService, 'isOauth').and.returnValue(false); spyOn(authService, 'isEcmLoggedIn').and.returnValue(true); spyOn(authService, 'isLoggedIn').and.returnValue(true); spyOn(ecmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeEcmEditedUser)); + fixture.detectChanges(); }); + it('should able to fetch ecm userInfo', async(() => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + component.ecmUser$.subscribe((response: EcmUserModel) => { + expect(response).toBeDefined(); + expect(response.firstName).toBe('fake-ecm-first-name'); + expect(response.lastName).toBe('fake-ecm-last-name'); + expect(response.email).toBe('fakeEcm@ecmUser.com'); + }); + }); + })); + it('should show ecm only last name when user first name is null ', async(() => { fixture.detectChanges(); @@ -176,6 +199,7 @@ describe('User info component', () => { describe('and has image', () => { beforeEach(async(() => { + spyOn(authService, 'isOauth').and.returnValue(false); spyOn(authService, 'isEcmLoggedIn').and.returnValue(true); spyOn(authService, 'isLoggedIn').and.returnValue(true); spyOn(ecmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeEcmUser)); @@ -205,7 +229,10 @@ describe('User info component', () => { imageButton.click(); fixture.detectChanges(); let loggedImage = fixture.debugElement.query(By.css('#logged-user-img')); - expect(component.ecmUser.avatarId).toBe('fake-avatar-id'); + component.ecmUser$.subscribe((response: EcmUserModel) => { + expect(response).toBeDefined(); + expect(response.avatarId).toBe('fake-avatar-id'); + }); expect(element.querySelector('#userinfo_container')).not.toBeNull(); expect(loggedImage).not.toBeNull(); expect(loggedImage.properties.src).toContain('assets/images/ecmImg.gif'); @@ -236,6 +263,7 @@ describe('User info component', () => { describe('and has no image', () => { beforeEach(async(() => { + spyOn(authService, 'isOauth').and.returnValue(false); spyOn(authService, 'isEcmLoggedIn').and.returnValue(true); spyOn(authService, 'isLoggedIn').and.returnValue(true); spyOn(ecmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeEcmUserNoImage)); @@ -266,7 +294,10 @@ describe('User info component', () => { fixture.whenStable().then(() => { fixture.detectChanges(); let pipe = new InitialUsernamePipe(new FakeSanitizer()); - expect(component.ecmUser.avatarId).toBeNull(); + component.ecmUser$.subscribe((response: EcmUserModel) => { + expect(response).toBeDefined(); + expect(response.avatarId).toBeNull(); + }); expect(pipe.transform({ id: 13, firstName: 'Wilbur', @@ -284,12 +315,27 @@ describe('User info component', () => { let getCurrentUserInfoStub; beforeEach(async(() => { + spyOn(authService, 'isOauth').and.returnValue(false); spyOn(authService, 'isBpmLoggedIn').and.returnValue(true); spyOn(authService, 'isLoggedIn').and.returnValue(true); getCurrentUserInfoStub = spyOn(bpmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeBpmUser)); })); + it('should fetch bpm userInfo', async(() => { + getCurrentUserInfoStub.and.returnValue(of(fakeBpmUser)); + fixture.detectChanges(); + fixture.whenStable().then(() => { + component.bpmUser$.subscribe((response: BpmUserModel) => { + expect(response).toBeDefined(); + expect(response.firstName).toBe('fake-bpm-first-name'); + expect(response.lastName).toBe('fake-bpm-last-name'); + expect(response.email).toBe('fakeBpm@fake.com'); + }); + }); + })); + it('should show full name next the user image', async(() => { + getCurrentUserInfoStub.and.returnValue(of(fakeBpmUser)); fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -305,6 +351,7 @@ describe('User info component', () => { })); it('should get the bpm current user image from the service', async(() => { + getCurrentUserInfoStub.and.returnValue(of(fakeBpmUser)); fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -316,18 +363,20 @@ describe('User info component', () => { })); it('should show last name if first name is null', async(() => { + fixture.detectChanges(); let wrongBpmUser: BpmUserModel = new BpmUserModel({ firstName: null, lastName: 'fake-last-name' }); getCurrentUserInfoStub.and.returnValue(of(wrongBpmUser)); - fixture.detectChanges(); fixture.whenStable().then(() => { + const fullNameElement = (element.querySelector('#adf-userinfo-bpm-name-display')); + fixture.detectChanges(); expect(element.querySelector('#userinfo_container')).toBeDefined(); expect(element.querySelector('#adf-userinfo-bpm-name-display')).not.toBeNull(); - expect(element.querySelector('#adf-userinfo-bpm-name-display').textContent).toContain('fake-last-name'); - expect(element.querySelector('#adf-userinfo-bpm-name-display').textContent).not.toContain('fake-bpm-first-name'); + expect(fullNameElement.textContent).toContain('fake-last-name'); + expect(fullNameElement.textContent).not.toContain('fake-first-name'); }); })); @@ -383,6 +432,7 @@ describe('User info component', () => { let ecmUserInfoSpy; beforeEach(async(() => { + spyOn(authService, 'isOauth').and.returnValue(false); spyOn(authService, 'isEcmLoggedIn').and.returnValue(true); spyOn(authService, 'isBpmLoggedIn').and.returnValue(true); spyOn(authService, 'isLoggedIn').and.returnValue(true); @@ -392,6 +442,32 @@ describe('User info component', () => { spyOn(bpmUserService, 'getCurrentUserInfo').and.returnValue(of(fakeBpmUser)); })); + it('should able to fetch ecm userInfo', async(() => { + fixture.detectChanges(); + + fixture.whenStable().then(() => { + component.ecmUser$.subscribe((response: EcmUserModel) => { + expect(response).toBeDefined(); + expect(response.firstName).toBe('fake-ecm-first-name'); + expect(response.lastName).toBe('fake-ecm-last-name'); + expect(response.email).toBe('fakeEcm@ecmUser.com'); + }); + }); + })); + + it('should able to fetch bpm userInfo', async(() => { + fixture.detectChanges(); + + fixture.whenStable().then(() => { + component.bpmUser$.subscribe((response: BpmUserModel) => { + expect(response).toBeDefined(); + expect(response.firstName).toBe('fake-bpm-first-name'); + expect(response.lastName).toBe('fake-bpm-last-name'); + expect(response.email).toBe('fakeBpm@fake.com'); + }); + }); + })); + it('should get the bpm user informations from the service', async(() => { openUserInfo(); let bpmTab = fixture.debugElement.queryAll(By.css('#tab-group-env .mat-tab-labels .mat-tab-label'))[1]; @@ -439,7 +515,6 @@ describe('User info component', () => { fixture.whenStable().then(() => { fixture.detectChanges(); expect(element.querySelector('#userinfo_container')).toBeDefined(); - expect(element.querySelector('#logged-user-img')).toBeNull(); expect(element.querySelector('#user-initials-image').textContent).toContain('ff'); }); })); @@ -465,4 +540,90 @@ describe('User info component', () => { expect(fixture.debugElement.query(By.css('#user-profile-lists'))).not.toBeNull(); }); }); + + describe('when identity user is logged in', () => { + + let getCurrentUserInfoStub; + + beforeEach(async(() => { + spyOn(authService, 'isOauth').and.returnValue(true); + spyOn(authService, 'isLoggedIn').and.returnValue(true); + getCurrentUserInfoStub = spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue(of(identityUserMock)); + })); + + it('should able to fetch identity userInfo', async(() => { + fixture.detectChanges(); + + fixture.whenStable().then(() => { + component.identityUser$.subscribe((response: IdentityUserModel) => { + expect(response).toBeDefined(); + expect(response.firstName).toBe('fake-identity-first-name'); + expect(response.lastName).toBe('fake-identity-last-name'); + expect(response.email).toBe('fakeIdentity@email.com'); + }); + }); + })); + + it('should show full name next the user image', async(() => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + let imageButton: HTMLButtonElement = element.querySelector('#identity-user-image'); + imageButton.click(); + fixture.detectChanges(); + let bpmUserName = element.querySelector('#identity-username'); + fixture.detectChanges(); + expect(element.querySelector('#userinfo_container')).not.toBeNull(); + expect(bpmUserName).toBeDefined(); + expect(bpmUserName).not.toBeNull(); + expect(bpmUserName.textContent).toContain('fake-identity-first-name fake-identity-last-name'); + }); + })); + + it('should show last name if first name is null', async(() => { + fixture.detectChanges(); + let fakeIdentityUser: IdentityUserModel = new IdentityUserModel(identityUserWithOutFirstNameMock); + getCurrentUserInfoStub.and.returnValue(of(fakeIdentityUser)); + + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + const fullNameElement = element.querySelector('#adf-userinfo-identity-name-display'); + expect(element.querySelector('#userinfo_container')).toBeDefined(); + expect(element.querySelector('#adf-userinfo-identity-name-display')).not.toBeNull(); + expect(fullNameElement.textContent).toContain('fake-identity-last-name'); + expect(fullNameElement.textContent).not.toContain('fake-identity-first-name'); + }); + })); + + it('should not show first name if it is null string', async(() => { + let fakeIdentityUser: IdentityUserModel = new IdentityUserModel(identityUserWithOutFirstNameMock); + getCurrentUserInfoStub.and.returnValue(of(fakeIdentityUser)); + + fixture.detectChanges(); + fixture.whenStable().then(() => { + const fullNameElement = element.querySelector('#adf-userinfo-identity-name-display'); + fixture.detectChanges(); + expect(element.querySelector('#userinfo_container')).toBeDefined(); + expect(fullNameElement).toBeDefined(); + expect(fullNameElement.textContent).toContain('fake-identity-last-name'); + expect(fullNameElement.textContent).not.toContain('null'); + }); + })); + + it('should not show last name if it is null string', async(() => { + let fakeIdentityUser: IdentityUserModel = new IdentityUserModel(identityUserWithOutLastNameMock); + getCurrentUserInfoStub.and.returnValue(of(fakeIdentityUser)); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + const fullNameElement = element.querySelector('#adf-userinfo-identity-name-display'); + fixture.detectChanges(); + expect(element.querySelector('#userinfo_container')).toBeDefined(); + expect(fullNameElement).toBeDefined(); + expect(fullNameElement.textContent).toContain('fake-identity-first-name'); + expect(fullNameElement.textContent).not.toContain('null'); + }); + })); + }); }); diff --git a/lib/core/userinfo/components/user-info.component.ts b/lib/core/userinfo/components/user-info.component.ts index 965ba2670b..472ca672d2 100644 --- a/lib/core/userinfo/components/user-info.component.ts +++ b/lib/core/userinfo/components/user-info.component.ts @@ -19,8 +19,11 @@ import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; import { AuthenticationService } from '../../services/authentication.service'; import { BpmUserModel } from './../models/bpm-user.model'; import { EcmUserModel } from './../models/ecm-user.model'; +import { IdentityUserModel } from './../models/identity-user.model'; import { BpmUserService } from './../services/bpm-user.service'; import { EcmUserService } from './../services/ecm-user.service'; +import { IdentityUserService } from '../services/identity-user.service'; +import { Observable } from 'rxjs'; @Component({ selector: 'adf-userinfo', @@ -56,14 +59,14 @@ export class UserInfoComponent implements OnInit { @Input() namePosition: string = 'right'; - ecmUser: EcmUserModel; - bpmUser: BpmUserModel; - bpmUserImage: any; - ecmUserImage: any; + ecmUser$: Observable; + bpmUser$: Observable; + identityUser$: Observable; selectedIndex: number; constructor(private ecmUserService: EcmUserService, private bpmUserService: BpmUserService, + private identityUserService: IdentityUserService, private authService: AuthenticationService) { } @@ -72,58 +75,47 @@ export class UserInfoComponent implements OnInit { } getUserInfo() { - this.loadEcmUserInfo(); - this.loadBpmUserInfo(); + if (this.authService.isOauth()) { + this.loadIdentityUserInfo(); + } else if (this.authService.isEcmLoggedIn() && this.authService.isBpmLoggedIn()) { + this.loadEcmUserInfo(); + this.loadBpmUserInfo(); + } else if (this.authService.isEcmLoggedIn()) { + this.loadEcmUserInfo(); + } else if (this.authService.isBpmLoggedIn()) { + this.loadBpmUserInfo(); + } } - isLoggedIn() { + isLoggedIn(): boolean { return this.authService.isLoggedIn(); } loadEcmUserInfo(): void { - if (this.authService.isEcmLoggedIn()) { - this.ecmUserService.getCurrentUserInfo() - .subscribe((res) => { - this.ecmUser = new EcmUserModel(res); - this.getEcmAvatar(); - }); - } else { - this.ecmUser = null; - this.ecmUserImage = null; - } + this.ecmUser$ = this.ecmUserService.getCurrentUserInfo(); } - loadBpmUserInfo(): void { - if (this.authService.isBpmLoggedIn()) { - this.bpmUserService.getCurrentUserInfo() - .subscribe((res) => { - this.bpmUser = new BpmUserModel(res); - }); - this.bpmUserImage = this.bpmUserService.getCurrentUserProfileImage(); - } else { - this.bpmUser = null; - this.bpmUserImage = null; - } + loadBpmUserInfo() { + this.bpmUser$ = this.bpmUserService.getCurrentUserInfo(); + } + + loadIdentityUserInfo() { + this.identityUser$ = this.identityUserService.getCurrentUserInfo(); } stopClosing(event) { event.stopPropagation(); } - private getEcmAvatar() { - this.ecmUserImage = this.ecmUserService.getUserProfileImage(this.ecmUser.avatarId); + getEcmAvatar(avatarId: any ): string { + return this.ecmUserService.getUserProfileImage(avatarId); } - showOnRight() { + getBpmUserImage(): string { + return this.bpmUserService.getCurrentUserProfileImage(); + } + + showOnRight(): boolean { return this.namePosition === 'right'; } - - hasBpmUserPictureId(): boolean { - return !!this.bpmUser.pictureId; - } - - hasEcmUserAvatarId(): boolean { - return !!this.ecmUser.avatarId; - } - } diff --git a/lib/core/userinfo/models/bpm-user.model.ts b/lib/core/userinfo/models/bpm-user.model.ts index 41f77f92b0..717ad200d7 100644 --- a/lib/core/userinfo/models/bpm-user.model.ts +++ b/lib/core/userinfo/models/bpm-user.model.ts @@ -27,7 +27,6 @@ export class BpmUserModel implements UserRepresentation { firstName: string; lastName: string; fullname: string; - fullNameDisplay: string; groups: any; id: number; lastUpdate: Date; @@ -51,7 +50,6 @@ export class BpmUserModel implements UserRepresentation { this.firstName = obj.firstName; this.lastName = obj.lastName; this.fullname = obj.fullname; - this.fullNameDisplay = obj ? this.formatValue(obj.firstName).trim() + ' ' + this.formatValue(obj.lastName).trim() : null; this.groups = obj.groups; this.id = obj.id; this.lastUpdate = obj.lastUpdate; @@ -65,8 +63,4 @@ export class BpmUserModel implements UserRepresentation { this.type = obj.type; } } - - private formatValue(value: string): string { - return value && value !== 'null' ? value : ''; - } } diff --git a/lib/core/userinfo/models/ecm-user.model.ts b/lib/core/userinfo/models/ecm-user.model.ts index 1c1fa2542b..87f18e2144 100644 --- a/lib/core/userinfo/models/ecm-user.model.ts +++ b/lib/core/userinfo/models/ecm-user.model.ts @@ -22,7 +22,6 @@ export class EcmUserModel implements Person { id: string; firstName: string; lastName: string; - fullNameDisplay: string; description: string; avatarId: string; email: string; @@ -43,7 +42,6 @@ export class EcmUserModel implements Person { this.id = obj && obj.id || null; this.firstName = obj && obj.firstName; this.lastName = obj && obj.lastName; - this.fullNameDisplay = obj ? this.formatValue(obj.firstName).trim() + ' ' + this.formatValue(obj.lastName).trim() : null; this.description = obj && obj.description || null; this.avatarId = obj && obj.avatarId || null; this.email = obj && obj.email || null; @@ -60,8 +58,4 @@ export class EcmUserModel implements Person { this.enabled = obj && obj.enabled; this.emailNotificationsEnabled = obj && obj.emailNotificationsEnabled; } - - private formatValue(value: string) { - return value && value !== 'null' ? value : ''; - } } diff --git a/lib/core/userinfo/models/identity-user.model.ts b/lib/core/userinfo/models/identity-user.model.ts new file mode 100644 index 0000000000..75a707a471 --- /dev/null +++ b/lib/core/userinfo/models/identity-user.model.ts @@ -0,0 +1,31 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class IdentityUserModel { + + firstName: string; + lastName: string; + email: string; + + constructor(obj?: any) { + if (obj) { + this.firstName = obj.firstName || null; + this.lastName = obj.lastName || null; + this.email = obj.email || null; + } + } +} diff --git a/lib/core/userinfo/public-api.ts b/lib/core/userinfo/public-api.ts index 1e4709092c..d9a36b842f 100644 --- a/lib/core/userinfo/public-api.ts +++ b/lib/core/userinfo/public-api.ts @@ -18,5 +18,9 @@ export * from './components/user-info.component'; export * from './services/bpm-user.service'; export * from './services/ecm-user.service'; +export * from './services/identity-user.service'; +export * from './models/bpm-user.model'; +export * from './models/ecm-user.model'; +export * from './models/identity-user.model'; export * from './userinfo.module'; diff --git a/lib/core/userinfo/services/bpm-user.service.ts b/lib/core/userinfo/services/bpm-user.service.ts index b62cea8d94..a661223ef0 100644 --- a/lib/core/userinfo/services/bpm-user.service.ts +++ b/lib/core/userinfo/services/bpm-user.service.ts @@ -22,6 +22,7 @@ import { AlfrescoApiService } from '../../services/alfresco-api.service'; import { LogService } from '../../services/log.service'; import { BpmUserModel } from '../models/bpm-user.model'; import { map, catchError } from 'rxjs/operators'; +import { UserRepresentation } from 'alfresco-js-api'; /** * @@ -44,7 +45,9 @@ export class BpmUserService { getCurrentUserInfo(): Observable { return from(this.apiService.getInstance().activiti.profileApi.getProfile()) .pipe( - map((data) => data), + map((data: UserRepresentation) => { + return new BpmUserModel(data); + }), catchError(err => this.handleError(err)) ); } diff --git a/lib/core/userinfo/services/ecm-user.service.ts b/lib/core/userinfo/services/ecm-user.service.ts index 629c73c280..c4c5486789 100644 --- a/lib/core/userinfo/services/ecm-user.service.ts +++ b/lib/core/userinfo/services/ecm-user.service.ts @@ -23,6 +23,7 @@ import { ContentService } from '../../services/content.service'; import { AlfrescoApiService } from '../../services/alfresco-api.service'; import { LogService } from '../../services/log.service'; import { EcmUserModel } from '../models/ecm-user.model'; +import { PersonEntry } from 'alfresco-js-api'; @Injectable({ providedIn: 'root' @@ -42,7 +43,9 @@ export class EcmUserService { getUserInfo(userName: string): Observable { return from(this.apiService.getInstance().core.peopleApi.getPerson(userName)) .pipe( - map(data => data['entry']), + map((personEntry: PersonEntry) => { + return new EcmUserModel(personEntry.entry); + }), catchError(err => this.handleError(err)) ); } diff --git a/lib/core/userinfo/services/identity-user.service.spec.ts b/lib/core/userinfo/services/identity-user.service.spec.ts new file mode 100644 index 0000000000..ba943fcdaa --- /dev/null +++ b/lib/core/userinfo/services/identity-user.service.spec.ts @@ -0,0 +1,60 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TestBed } from '@angular/core/testing'; +import { IdentityUserService } from '../services/identity-user.service'; +import { setupTestBed } from '../../testing/setupTestBed'; +import { CoreModule } from '../../core.module'; +import { mockToken } from './../../mock/jwt-helper.service.spec'; + +describe('IdentityUserService', () => { + + let service: IdentityUserService; + + setupTestBed({ + imports: [ + CoreModule.forRoot() + ] + }); + + beforeEach(() => { + service = TestBed.get(IdentityUserService); + }); + + beforeEach(() => { + let store = {}; + + spyOn(localStorage, 'getItem').and.callFake( (key: string): String => { + return store[key] || null; + }); + spyOn(localStorage, 'setItem').and.callFake((key: string, value: string): string => { + return store[key] = value; + }); + }); + + it('should able to fetch identity user info from Jwt token', (done) => { + localStorage.setItem('access_token', mockToken); + service.getCurrentUserInfo().subscribe( + (user) => { + expect(user).toBeDefined(); + expect(user.firstName).toEqual('John'); + expect(user.lastName).toEqual('Doe'); + expect(user.email).toEqual('johnDoe@gmail.com'); + done(); + }); + }); +}); diff --git a/lib/core/userinfo/services/identity-user.service.ts b/lib/core/userinfo/services/identity-user.service.ts new file mode 100644 index 0000000000..4f4db75e97 --- /dev/null +++ b/lib/core/userinfo/services/identity-user.service.ts @@ -0,0 +1,51 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable } from '@angular/core'; +import { of, Observable } from 'rxjs'; +import { IdentityUserModel } from '../models/identity-user.model'; +import { JwtHelperService } from './../../services/jwt-helper.service'; + +@Injectable({ + providedIn: 'root' +}) +export class IdentityUserService { + + static USER_NAME = 'given_name'; + static USER_EMAIL = 'email'; + static USER_ACCESS_TOKEN = 'access_token'; + + constructor(private helper: JwtHelperService) {} + + getCurrentUserInfo(): Observable { + const fullName = this.getValueFromToken(IdentityUserService.USER_NAME); + const email = this.getValueFromToken(IdentityUserService.USER_EMAIL); + const nameParts = fullName.split(' '); + const user = { firstName: nameParts[0], lastName: nameParts[1], email: email }; + return of(new IdentityUserModel(user)); + } + + getValueFromToken(key: string): T { + let value; + const token = localStorage.getItem(IdentityUserService.USER_ACCESS_TOKEN); + if (token) { + const tokenPayload = this.helper.decodeToken(token); + value = tokenPayload[key]; + } + return value; + } +} diff --git a/package-lock.json b/package-lock.json index 04beee6d7b..c05b710748 100644 --- a/package-lock.json +++ b/package-lock.json @@ -787,18 +787,18 @@ "resolved": "https://registry.npmjs.org/@nrwl/schematics/-/schematics-6.3.1.tgz", "integrity": "sha512-Dy4D6RaD+4BZFMfRr+5QaE9J5AjZ7UlldIpsyWvh6VofR9JseVpbetsvcSUsTT3fpjmORIHGMQM7Kpp+gnHaeA==", "requires": { - "@types/yargs": "11.1.2", - "app-root-path": "2.0.1", + "@types/yargs": "^11.0.0", + "app-root-path": "^2.0.1", "cosmiconfig": "4.0.0", "fs-extra": "6.0.0", "graphviz": "0.0.8", "npm-run-all": "4.1.2", - "opn": "5.4.0", + "opn": "^5.3.0", "semver": "5.4.1", "strip-json-comments": "2.0.1", "tmp": "0.0.33", - "viz.js": "1.8.2", - "yargs": "11.1.0", + "viz.js": "^1.8.1", + "yargs": "^11.0.0", "yargs-parser": "10.0.0" }, "dependencies": { @@ -807,9 +807,9 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.0.tgz", "integrity": "sha512-lk2cUCo8QzbiEWEbt7Cw3m27WMiRG321xsssbcIpfMhpRjrlC08WBOVQqj1/nQYYNnPtyIhP1oqLO3QwT2tPCw==", "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.2" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "semver": { @@ -822,7 +822,7 @@ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "requires": { - "os-tmpdir": "1.0.2" + "os-tmpdir": "~1.0.2" } }, "yargs-parser": { @@ -830,7 +830,7 @@ "resolved": "http://registry.npmjs.org/yargs-parser/-/yargs-parser-10.0.0.tgz", "integrity": "sha512-+DHejWujTVYeMHLff8U96rLc4uE4Emncoftvn5AjhB1Jw1pWxLzgBUT/WYbPrHmy6YPEBTZQx5myHhVcuuu64g==", "requires": { - "camelcase": "4.1.0" + "camelcase": "^4.1.0" } } } @@ -5731,14 +5731,14 @@ "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz", "integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==", "requires": { - "duplexer": "0.1.1", - "flatmap-stream": "0.1.1", - "from": "0.1.7", + "duplexer": "^0.1.1", + "flatmap-stream": "^0.1.0", + "from": "^0.1.7", "map-stream": "0.0.7", - "pause-stream": "0.0.11", - "split": "1.0.1", - "stream-combiner": "0.2.2", - "through": "2.3.8" + "pause-stream": "^0.0.11", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through": "^2.3.8" } }, "eventemitter3": { @@ -6559,12 +6559,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6579,17 +6581,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -6706,7 +6711,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -6718,6 +6724,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6732,6 +6739,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6739,12 +6747,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -6763,6 +6773,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -6843,7 +6854,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -6855,6 +6867,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -6976,6 +6989,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -7487,7 +7501,7 @@ "resolved": "https://registry.npmjs.org/graphviz/-/graphviz-0.0.8.tgz", "integrity": "sha1-5ZnkBzPvgOFlO/6JpfAx7PKqSqo=", "requires": { - "temp": "0.4.0" + "temp": "~0.4.0" } }, "gray-matter": { @@ -12043,15 +12057,15 @@ "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.2.tgz", "integrity": "sha512-Z2aRlajMK4SQ8u19ZA75NZZu7wupfCNQWdYosIi8S6FgBdGf/8Y6Hgyjdc8zU2cYmIRVCx1nM80tJPkdEd+UYg==", "requires": { - "ansi-styles": "3.2.1", - "chalk": "2.4.1", - "cross-spawn": "5.1.0", - "memorystream": "0.3.1", - "minimatch": "3.0.4", - "ps-tree": "1.1.0", - "read-pkg": "3.0.0", - "shell-quote": "1.6.1", - "string.prototype.padend": "3.0.0" + "ansi-styles": "^3.2.0", + "chalk": "^2.1.0", + "cross-spawn": "^5.1.0", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "ps-tree": "^1.1.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" }, "dependencies": { "load-json-file": { @@ -12059,10 +12073,10 @@ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "requires": { - "graceful-fs": "4.1.11", - "parse-json": "4.0.0", - "pify": "3.0.0", - "strip-bom": "3.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" } }, "parse-json": { @@ -12070,8 +12084,8 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "requires": { - "error-ex": "1.3.2", - "json-parse-better-errors": "1.0.2" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } }, "read-pkg": { @@ -12079,9 +12093,9 @@ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "requires": { - "load-json-file": "4.0.0", - "normalize-package-data": "2.4.0", - "path-type": "3.0.0" + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" } }, "strip-bom": { @@ -12763,7 +12777,7 @@ "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "requires": { - "through": "2.3.8" + "through": "~2.3" } }, "pbkdf2": { @@ -13364,7 +13378,7 @@ "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz", "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=", "requires": { - "event-stream": "3.3.6" + "event-stream": "~3.3.0" } }, "pseudomap": { @@ -14978,10 +14992,10 @@ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", "requires": { - "array-filter": "0.0.1", - "array-map": "0.0.0", - "array-reduce": "0.0.0", - "jsonify": "0.0.0" + "array-filter": "~0.0.0", + "array-map": "~0.0.0", + "array-reduce": "~0.0.0", + "jsonify": "~0.0.0" } }, "shelljs": { @@ -15523,7 +15537,7 @@ "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "requires": { - "through": "2.3.8" + "through": "2" } }, "split-string": { @@ -15638,8 +15652,8 @@ "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", "requires": { - "duplexer": "0.1.1", - "through": "2.3.8" + "duplexer": "~0.1.1", + "through": "~2.3.4" } }, "stream-each": { @@ -15707,9 +15721,9 @@ "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz", "integrity": "sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=", "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.12.0", - "function-bind": "1.1.1" + "define-properties": "^1.1.2", + "es-abstract": "^1.4.3", + "function-bind": "^1.0.2" } }, "string_decoder": {