[ADF-3666] User Profile - Provide a way to show SSO Logged-In User (#3976)

* * Created IdentityUserService
* Created IdentityUserModel
* Added JWT lib into package.json

* * Provided a way to show SSO user info

* * Added  condition to check sso login

* * Refactored userInfo component* Updated unit tests to the recent changes

* * Created IdenityUser model
* Refactored userInfoComponent

* * Modified userInfoComponent
* Modified unit tests

* * Used Fullname pipe* Removed the logic that we had to display fullname in the ecm/bpm/identity model* Created JwtHelperService* Modified ecm/bpm services * added test cases to the identity service

* * Moved jwt service to core/service

* * Updated userInfo doc

* * Added missing return types

* * Created a mockToken* Added unit tests to the JwtHelperService* Updated identityUserService unit test

* Update jwt-helper.service.spec.ts

* * Updated bpm/ecm/identity services
This commit is contained in:
siva kumar
2018-11-22 15:33:19 +05:30
committed by Maurizio Vitale
parent a39e44e4a9
commit 974929e76d
19 changed files with 629 additions and 153 deletions

View File

@@ -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

View File

@@ -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',

View File

@@ -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';

View File

@@ -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';

View File

@@ -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');
});
});

View File

@@ -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)));
}
}

View File

@@ -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';

View File

@@ -1,48 +1,68 @@
<div id="userinfo_container"
[class.adf-userinfo-name-right]="showOnRight()"
<div id="userinfo_container" [class.adf-userinfo-name-right]="showOnRight()"
class="adf-userinfo-container" *ngIf="isLoggedIn()">
<span *ngIf="ecmUser && showName" id="adf-userinfo-ecm-name-display"
class="adf-userinfo-name">{{ecmUser.fullNameDisplay}}</span>
<span *ngIf="bpmUser && !ecmUser && showName" id="adf-userinfo-bpm-name-display"
class="adf-userinfo-name">{{bpmUser.fullNameDisplay}}</span>
<ng-container *ngIf="showName">
<span *ngIf="identityUser$ | async as identityUser; else showBpmAndEcmUserFullNames" id="adf-userinfo-identity-name-display"
class="adf-userinfo-name">{{identityUser | fullName}}</span>
<ng-template #showBpmAndEcmUserFullNames>
<span *ngIf="ecmUser$ | async as ecmUser; else showBpmUserFullName" id="adf-userinfo-ecm-name-display"
class="adf-userinfo-name">{{ecmUser | fullName}}</span>
<ng-template #showBpmUserFullName>
<span *ngIf="bpmUser$ | async as bpmUser" id="adf-userinfo-bpm-name-display"
class="adf-userinfo-name">{{bpmUser | fullName}}</span>
</ng-template>
</ng-template>
</ng-container>
<button mat-button [matMenuTriggerFor]="menu" class="adf-userinfo-menu_button" data-automation-id="adf-user-profile">
<div class="adf-userinfo-button-profile" id="user-profile">
<div *ngIf="bpmUser && !ecmUser" id="bpm-user-image">
<div *ngIf="!hasBpmUserPictureId()" [outerHTML]="bpmUser | usernameInitials:'adf-userinfo-pic'"></div>
<div *ngIf="hasBpmUserPictureId()" class="adf-userinfo-profile-container">
<img id="logged-user-img" [src]="bpmUserImage" alt="user-info-profile-button"
class="adf-userinfo-profile-image"/>
</div>
<div *ngIf="identityUser$ | async as identityUser; else showBpmAndEcmUserImage" id="identity-user-image">
<div [outerHTML]="identityUser | usernameInitials:'adf-userinfo-pic'"></div>
</div>
<div *ngIf="ecmUser" id="ecm-user-image">
<div *ngIf="!hasEcmUserAvatarId()"
[outerHTML]="ecmUser | usernameInitials:'adf-userinfo-pic'"></div>
<div *ngIf="hasEcmUserAvatarId()" class="adf-userinfo-profile-container">
<img id="logged-user-img" [src]="ecmUserImage" alt="user-info-profile-button"
<ng-template #showBpmAndEcmUserImage>
<div *ngIf="ecmUser$ | async as ecmUser; else showBpmUserImage" id="ecm-user-image">
<div *ngIf="ecmUser.avatarId; else initialTemplate" class="adf-userinfo-profile-container">
<img id="logged-user-img" [src]="getEcmAvatar(ecmUser.avatarId)" alt="user-info-profile-button"
class="adf-userinfo-profile-image"/>
</div>
</div>
</div>
</button>
<ng-template #initialTemplate>
<div [outerHTML]="ecmUser | usernameInitials:'adf-userinfo-pic'"></div>
</ng-template>
</div>
<ng-template #showBpmUserImage>
<div *ngIf="bpmUser$ | async as bpmUser" id="bpm-user-image">
<div *ngIf="bpmUser.pictureId; else initialTemplate" class="adf-userinfo-profile-container">
<img id="logged-user-img" [src]="getBpmUserImage()" alt="user-info-profile-button"
class="adf-userinfo-profile-image"/>
</div>
<ng-template #initialTemplate>
<div [outerHTML]="bpmUser | usernameInitials:'adf-userinfo-pic'"></div>
</ng-template>
</div>
</ng-template>
</ng-template>
</div>
</button>
<mat-menu #menu="matMenu" id="user-profile-lists" [xPosition]="menuPositionX" [yPosition]="menuPositionY" [overlapTrigger]="false" class="adf-userinfo-menu">
<mat-tab-group id="tab-group-env" (click)="stopClosing($event)"
class="adf-userinfo-tab" [class.adf-hide-tab]="!bpmUser || !ecmUser">
<mat-tab id="ecm-panel" label="{{ 'USER_PROFILE.TAB.CS' | translate }}" *ngIf="ecmUser">
class="adf-userinfo-tab" [class.adf-hide-tab]="!(bpmUser$ | async) || !(ecmUser$ | async)">
<mat-tab id="ecm-panel" label="{{ 'USER_PROFILE.TAB.CS' | translate }}" *ngIf="ecmUser$ | async as ecmUser">
<mat-card class="adf-userinfo-card">
<mat-card-header class="adf-userinfo-card-header" [style.background-image]="'url(' + ecmBackgroundImage + ')'">
<div *ngIf="!hasEcmUserAvatarId()"
[outerHTML]="ecmUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'">
</div>
<div *ngIf="hasEcmUserAvatarId()" class="adf-userinfo-profile-container adf-hide-small">
<div *ngIf="ecmUser.avatarId; else initialTemplate" class="adf-userinfo-profile-container adf-hide-small">
<img class="adf-userinfo-profile-picture" id="ecm-user-detail-image"
alt="ecm-profile-image" [src]="ecmUserImage" />
alt="ecm-profile-image" [src]="getEcmAvatar(ecmUser.avatarId)" />
</div>
<div class="adf-userinfo-title" id="ecm-username">{{ecmUser.fullNameDisplay}}</div>
<ng-template #initialTemplate>
<div [outerHTML]="ecmUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'"></div>
</ng-template>
<div class="adf-userinfo-title" id="ecm-username">{{ecmUser | fullName}}</div>
</mat-card-header>
<mat-card-content>
<div class="adf-userinfo-supporting-text">
<div class="adf-userinfo-detail">
<span id="ecm-full-name" class="adf-userinfo__detail-title">{{ecmUser.fullNameDisplay}}</span>
<span id="ecm-full-name" class="adf-userinfo__detail-title">{{ecmUser | fullName}}</span>
<span class="adf-userinfo__detail-profile" id="ecm-email"> {{ecmUser.email}} </span>
</div>
<div class="adf-userinfo-detail">
@@ -55,18 +75,20 @@
</mat-card-content>
</mat-card>
</mat-tab>
<mat-tab id="bpm-panel" label="{{ 'USER_PROFILE.TAB.PS' | translate }}" *ngIf="bpmUser">
<mat-tab id="bpm-panel" label="{{ 'USER_PROFILE.TAB.PS' | translate }}" *ngIf="bpmUser$ | async as bpmUser">
<mat-card class="adf-userinfo-card">
<mat-card-header class="adf-userinfo-card-header" [style.background-image]="'url(' + bpmBackgroundImage + ')'">
<div *ngIf="!hasBpmUserPictureId()" [outerHTML]="bpmUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'"></div>
<img *ngIf="hasBpmUserPictureId()" class="adf-userinfo-profile-picture adf-hide-small" id="bpm-user-detail-image"
alt="bpm-profile-image" [src]="bpmUserImage"/>
<div class="adf-userinfo-title" id="bpm-username">{{bpmUser.fullNameDisplay}}</div>
<img *ngIf="bpmUser.pictureId; else initialTemplate" class="adf-userinfo-profile-picture adf-hide-small" id="bpm-user-detail-image"
alt="bpm-profile-image" [src]="getBpmUserImage()"/>
<ng-template #initialTemplate>
<div [outerHTML]="bpmUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'"></div>
</ng-template>
<div class="adf-userinfo-title" id="bpm-username">{{bpmUser | fullName}}</div>
</mat-card-header>
<mat-card-content>
<div class="adf-userinfo-supporting-text">
<div class="adf-userinfo-detail">
<span id="bpm-full-name" class="adf-userinfo__detail-title">{{ bpmUser.fullNameDisplay }}</span>
<span id="bpm-full-name" class="adf-userinfo__detail-title">{{ bpmUser | fullName }}</span>
<span class="adf-userinfo__detail-profile" id="bpm-email"> {{bpmUser.email}} </span>
</div>
<div class="adf-userinfo-detail">
@@ -79,6 +101,24 @@
</mat-card-content>
</mat-card>
</mat-tab>
<mat-tab id="identity-panel" *ngIf="identityUser$ | async as identityUser">
<mat-card class="adf-userinfo-card">
<mat-card-header class="adf-userinfo-card-header" [style.background-image]="'url(' + bpmBackgroundImage + ')'">
<div
[outerHTML]="identityUser | usernameInitials:'adf-userinfo-profile-initials adf-hide-small'">
</div>
<div class="adf-userinfo-title" id="identity-username">{{identityUser | fullName}}</div>
</mat-card-header>
<mat-card-content>
<div class="adf-userinfo-supporting-text">
<div class="adf-userinfo-detail">
<span id="identity-full-name" class="adf-userinfo__detail-title">{{identityUser | fullName}}</span>
<span class="adf-userinfo__detail-profile" id="identity-email"> {{identityUser.email}} </span>
</div>
</div>
</mat-card-content>
</mat-card>
</mat-tab>
</mat-tab-group>
</mat-menu>
</div>

View File

@@ -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 = <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');
});
}));
});
});

View File

@@ -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<EcmUserModel>;
bpmUser$: Observable<BpmUserModel>;
identityUser$: Observable<IdentityUserModel>;
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;
}
}

View File

@@ -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 : '';
}
}

View File

@@ -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 : '';
}
}

View File

@@ -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;
}
}
}

View File

@@ -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';

View File

@@ -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<BpmUserModel> {
return from(this.apiService.getInstance().activiti.profileApi.getProfile())
.pipe(
map((data) => <BpmUserModel> data),
map((data: UserRepresentation) => {
return new BpmUserModel(data);
}),
catchError(err => this.handleError(err))
);
}

View File

@@ -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<EcmUserModel> {
return from(this.apiService.getInstance().core.peopleApi.getPerson(userName))
.pipe(
map(data => <EcmUserModel> data['entry']),
map((personEntry: PersonEntry) => {
return new EcmUserModel(personEntry.entry);
}),
catchError(err => this.handleError(err))
);
}

View File

@@ -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] = <string> 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();
});
});
});

View File

@@ -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<IdentityUserModel> {
const fullName = this.getValueFromToken<string>(IdentityUserService.USER_NAME);
const email = this.getValueFromToken<string>(IdentityUserService.USER_EMAIL);
const nameParts = fullName.split(' ');
const user = { firstName: nameParts[0], lastName: nameParts[1], email: email };
return of(new IdentityUserModel(user));
}
getValueFromToken<T>(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 <T> value;
}
}

124
package-lock.json generated
View File

@@ -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": {