[ADF-2795] SSO implicitflow (#3332)

* Enable OAUTH2

* Create SSO services

* SSO improvements

* Rollback sso login change

* Add SSO configuration from Setting component

* Refactoring

* Remove login ECM/BPM toggle and move use the userpreference instead of store

* fix host setting unit test

* Fix unit test missing instance

* use the Js api oauth

* add logout component and clean sso not used class

* fix dependencies cicle

* add translation settings

* fix style setting page

* clean

* JS APi should receive the oauth config from the userPreference and not from the config file

* change login if SSO is present

* missing spaces

* add sso test in login component

* add logout directive new properties test

* Improve host setting and remove library reference

* fix login test

* Remove unused code

* Fix authentication unit test

* fix authguard unit test

* fix csrf check login component

* fix unit test core and demo shell

* remove
This commit is contained in:
Maurizio Vitale
2018-06-08 00:19:58 +02:00
committed by Eugenio Romano
parent 3a6c12e624
commit f8e92b2fb0
57 changed files with 1295 additions and 681 deletions
+10
View File
@@ -6,6 +6,16 @@
"PROPERTIES": "Properties", "PROPERTIES": "Properties",
"VERSIONS": "Versions" "VERSIONS": "Versions"
}, },
"HOME": {
"TITLE": "Angular components for Alfresco",
"DOCUMENTATION": "Documentation"
},
"LOGOUT": {
"TITLE": "Logout Page",
"SUB_TITLE": "You are now logged out",
"LOGIN": "Login",
"HOME": "Home"
},
"ADF_VERSION_MANAGER": { "ADF_VERSION_MANAGER": {
"ALLOW_DELETE": "Allow delete", "ALLOW_DELETE": "Allow delete",
"SHOW_COMMENTS" : "Show comments on versions", "SHOW_COMMENTS" : "Show comments on versions",
+11
View File
@@ -2,7 +2,18 @@
"$schema": "../../lib/core/app-config/schema.json", "$schema": "../../lib/core/app-config/schema.json",
"ecmHost": "http://{hostname}:{port}", "ecmHost": "http://{hostname}:{port}",
"bpmHost": "http://{hostname}:{port}", "bpmHost": "http://{hostname}:{port}",
"providers": "OAUTH",
"contextRootBpm": "activiti-app", "contextRootBpm": "activiti-app",
"oauth2": {
"host": "YOUR_AUTH_SERVER",
"clientId": "activiti",
"scope": "openid",
"secret": "",
"implicitFlow": true,
"silentLogin": true,
"redirectUri": "/",
"redirectUriLogout": "/logout"
},
"application": { "application": {
"name": "Alfresco ADF Application", "name": "Alfresco ADF Application",
"copyright": "© 2016 - 2018 Alfresco Software, Inc. All Rights Reserved." "copyright": "© 2016 - 2018 Alfresco Software, Inc. All Rights Reserved."
+2 -11
View File
@@ -16,7 +16,7 @@
*/ */
import { Component, ViewEncapsulation, OnInit } from '@angular/core'; import { Component, ViewEncapsulation, OnInit } from '@angular/core';
import { AlfrescoApiService, SettingsService, PageTitleService, StorageService } from '@alfresco/adf-core'; import { AlfrescoApiService, PageTitleService } from '@alfresco/adf-core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
@Component({ @Component({
@@ -27,15 +27,12 @@ import { Router } from '@angular/router';
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
constructor(private settingsService: SettingsService, constructor(private pageTitleService: PageTitleService,
private storage: StorageService,
private pageTitleService: PageTitleService,
private alfrescoApiService: AlfrescoApiService, private alfrescoApiService: AlfrescoApiService,
private router: Router) { private router: Router) {
} }
ngOnInit() { ngOnInit() {
this.setProvider();
this.pageTitleService.setTitle('title'); this.pageTitleService.setTitle('title');
@@ -46,10 +43,4 @@ export class AppComponent implements OnInit {
} }
}); });
} }
private setProvider() {
if (this.storage.hasItem(`providers`)) {
this.settingsService.setProviders(this.storage.getItem(`providers`));
}
}
} }
+2
View File
@@ -11,6 +11,7 @@ import { AppComponent } from './app.component';
import { AdfModule } from './adf.module'; import { AdfModule } from './adf.module';
import { MaterialModule } from './material.module'; import { MaterialModule } from './material.module';
import { LoginComponent } from './components/login/login.component'; import { LoginComponent } from './components/login/login.component';
import { LogoutComponent } from './components/logout/logout.component';
import { SettingsComponent } from './components/settings/settings.component'; import { SettingsComponent } from './components/settings/settings.component';
import { AppLayoutComponent } from './components/app-layout/app-layout.component'; import { AppLayoutComponent } from './components/app-layout/app-layout.component';
import { HomeComponent } from './components/home/home.component'; import { HomeComponent } from './components/home/home.component';
@@ -71,6 +72,7 @@ import { NotificationsComponent } from './components/notifications/notifications
declarations: [ declarations: [
AppComponent, AppComponent,
LoginComponent, LoginComponent,
LogoutComponent,
SettingsComponent, SettingsComponent,
AppLayoutComponent, AppLayoutComponent,
HomeComponent, HomeComponent,
+5 -3
View File
@@ -17,11 +17,12 @@
import { ModuleWithProviders } from '@angular/core'; import { ModuleWithProviders } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { AuthGuard, AuthGuardBpm, AuthGuardEcm, ErrorContentComponent } from '@alfresco/adf-core'; import { AuthGuard, AuthGuardEcm, ErrorContentComponent, AuthGuardBpm } from '@alfresco/adf-core';
import { AppLayoutComponent } from './components/app-layout/app-layout.component'; import { AppLayoutComponent } from './components/app-layout/app-layout.component';
import { LoginComponent } from './components/login/login.component'; import { LoginComponent } from './components/login/login.component';
import { SettingsComponent } from './components/settings/settings.component'; import { SettingsComponent } from './components/settings/settings.component';
import { HomeComponent } from './components/home/home.component'; import { HomeComponent } from './components/home/home.component';
import { LogoutComponent } from './components/logout/logout.component';
import { AboutComponent } from './components/about/about.component'; import { AboutComponent } from './components/about/about.component';
import { ProcessServiceComponent } from './components/process-service/process-service.component'; import { ProcessServiceComponent } from './components/process-service/process-service.component';
import { ShowDiagramComponent } from './components/process-service/show-diagram.component'; import { ShowDiagramComponent } from './components/process-service/show-diagram.component';
@@ -52,8 +53,9 @@ import { NotificationsComponent } from './components/notifications/notifications
export const appRoutes: Routes = [ export const appRoutes: Routes = [
{ path: 'login', component: LoginComponent }, { path: 'login', component: LoginComponent },
{ path: 'logout', component: LogoutComponent },
{ path: 'settings', component: SettingsComponent }, { path: 'settings', component: SettingsComponent },
{ path: 'files/:nodeId/view', component: FileViewComponent, canActivate: [AuthGuardEcm], outlet: 'overlay' }, { path: 'files/:nodeId/view', component: FileViewComponent, canActivate: [ AuthGuardEcm ], outlet: 'overlay' },
{ path: 'preview/blob', component: BlobPreviewComponent, outlet: 'overlay', pathMatch: 'full' }, { path: 'preview/blob', component: BlobPreviewComponent, outlet: 'overlay', pathMatch: 'full' },
{ path: 'preview/s/:id', component: SharedLinkViewComponent }, { path: 'preview/s/:id', component: SharedLinkViewComponent },
{ {
@@ -226,4 +228,4 @@ export const appRoutes: Routes = [
} }
]; ];
export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes); export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes, { initialNavigation: true });
@@ -37,7 +37,7 @@
<mat-icon matListIcon class="sidenav-menu-icon">{{link.icon}}</mat-icon> <mat-icon matListIcon class="sidenav-menu-icon">{{link.icon}}</mat-icon>
<div class="sidenav-menu-label" *ngIf="!isMenuMinimized()">{{link.title | translate }}</div> <div class="sidenav-menu-label" *ngIf="!isMenuMinimized()">{{link.title | translate }}</div>
</a> </a>
<a mat-list-item adf-logout class="adf-sidenav-link"> <a mat-list-item adf-logout [enabelRedirect]="enabelRedirect" redirectUri="/logout" class="adf-sidenav-link">
<mat-icon matListIcon class="sidenav-menu-icon">exit_to_app</mat-icon> <mat-icon matListIcon class="sidenav-menu-icon">exit_to_app</mat-icon>
<div class="sidenav-menu-label" *ngIf="!isMenuMinimized()">Logout</div> <div class="sidenav-menu-label" *ngIf="!isMenuMinimized()">Logout</div>
</a> </a>
@@ -16,7 +16,7 @@
*/ */
import { Component, ViewEncapsulation, OnInit } from '@angular/core'; import { Component, ViewEncapsulation, OnInit } from '@angular/core';
import { UserPreferencesService, AppConfigService } from '@alfresco/adf-core'; import { UserPreferencesService, AppConfigService, AlfrescoApiService } from '@alfresco/adf-core';
@Component({ @Component({
templateUrl: 'app-layout.component.html', templateUrl: 'app-layout.component.html',
@@ -54,18 +54,23 @@ export class AppLayoutComponent implements OnInit {
expandedSidenav = false; expandedSidenav = false;
enabelRedirect = true;
ngOnInit() { ngOnInit() {
const expand = this.config.get<boolean>('sideNav.expandedSidenav'); const expand = this.config.get<boolean>('sideNav.expandedSidenav');
const preserveState = this.config.get('sideNav.preserveState'); const preserveState = this.config.get('sideNav.preserveState');
if (preserveState && expand) { if (preserveState && expand) {
this.expandedSidenav = (this.userpreference.get('expandedSidenav', expand.toString()) === 'true'); this.expandedSidenav = (this.userpreference.get('expandedSidenav', expand.toString()) === 'true');
} else if (expand) { } else if (expand) {
this.expandedSidenav = expand; this.expandedSidenav = expand;
} }
} }
constructor( private userpreference: UserPreferencesService, private config: AppConfigService) {
constructor(private userpreference: UserPreferencesService, private config: AppConfigService, private alfrescoApiService: AlfrescoApiService) {
if (this.alfrescoApiService.getInstance().isOauthConfiguration()) {
this.enabelRedirect = false;
}
} }
setState(state) { setState(state) {
@@ -25,7 +25,7 @@ import { MatDialog } from '@angular/material';
import { ActivatedRoute, Params, Router } from '@angular/router'; import { ActivatedRoute, Params, Router } from '@angular/router';
import { MinimalNodeEntity, NodePaging, Pagination, MinimalNodeEntryEntity, SiteEntry } from 'alfresco-js-api'; import { MinimalNodeEntity, NodePaging, Pagination, MinimalNodeEntryEntity, SiteEntry } from 'alfresco-js-api';
import { import {
AuthenticationService, AppConfigService, ContentService, TranslationService, AuthenticationService, ContentService, TranslationService,
FileUploadEvent, FolderCreatedEvent, LogService, NotificationService, FileUploadEvent, FolderCreatedEvent, LogService, NotificationService,
UploadService, DataColumn, DataRow, UserPreferencesService, UploadService, DataColumn, DataRow, UserPreferencesService,
PaginationComponent, FormValues, DisplayMode, UserPreferenceValues, InfinitePaginationComponent PaginationComponent, FormValues, DisplayMode, UserPreferenceValues, InfinitePaginationComponent
@@ -56,7 +56,7 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
displayMode = DisplayMode.List; displayMode = DisplayMode.List;
includeFields = ['isFavorite', 'isLocked', 'aspectNames']; includeFields = ['isFavorite', 'isLocked', 'aspectNames'];
baseShareUrl = this.appConfig.get<string>('ecmHost') + '/preview/s/'; baseShareUrl = this.preference.ecmHost + '/preview/s/';
toolbarColor = 'default'; toolbarColor = 'default';
@@ -191,7 +191,6 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
private router: Router, private router: Router,
private logService: LogService, private logService: LogService,
private preference: UserPreferencesService, private preference: UserPreferencesService,
private appConfig: AppConfigService,
private preview: PreviewService, private preview: PreviewService,
@Optional() private route: ActivatedRoute, @Optional() private route: ActivatedRoute,
public authenticationService: AuthenticationService) { public authenticationService: AuthenticationService) {
@@ -2,10 +2,10 @@
<div class="adf-home-section ad"> <div class="adf-home-section ad">
<div class="adf-home-headline"> <div class="adf-home-headline">
<h1 class="mat-h1">ADF</h1> <h1 class="mat-h1">ADF</h1>
<h2> Angular components for Alfresco</h2> <h2>{{ 'APP.HOME.TITLE' | translate}}</h2>
</div> </div>
<div class="adf-home-start"> <div class="adf-home-start">
<a mat-raised-button class="adf-home-docs-button adf-primary-color" href="https://github.com/Alfresco/alfresco-ng2-components/tree/master/docs">DOCS</a> <a mat-raised-button class="adf-home-docs-button adf-primary-color" href="https://github.com/Alfresco/alfresco-ng2-components/tree/master/docs">{{ 'APP.HOME.DOCUMENTATION' | translate}}</a>
</div> </div>
</div> </div>
</header> </header>
@@ -1,24 +1,6 @@
<!--BPM, ECN AND CSRF TOGGLE--> <!--BPM, ECN AND CSRF TOGGLE-->
<div class="settings"> <div class="settings">
<p class="toggle">
<mat-slide-toggle
id="switch1"
[color]="'primary'"
(change)="toggleECM()"
[checked]="isECM">
{{ 'LOGIN.CONTENT_SERVICES'| translate }}
</mat-slide-toggle>
</p>
<p class="toggle">
<mat-slide-toggle
id="switch2"
[color]="'primary'"
(change)="toggleBPM()"
[checked]="isBPM">
{{ 'LOGIN.PROCESS_SERVICES'| translate }}
</mat-slide-toggle>
</p>
<p class="toggle"> <p class="toggle">
<mat-slide-toggle <mat-slide-toggle
id="switch3" id="switch3"
@@ -57,24 +39,6 @@
(success)="onLogin($event)" (success)="onLogin($event)"
(error)="onError($event)"> (error)="onError($event)">
<div class="mobile-settings"> <div class="mobile-settings">
<p>
<mat-slide-toggle
id="switch1-mobile"
[color]="'primary'"
(change)="toggleECM()"
[checked]="isECM">
{{ 'LOGIN.CONTENT_SERVICES'| translate }}
</mat-slide-toggle>
</p>
<p>
<mat-slide-toggle
id="switch2-mobile"
[color]="'primary'"
(change)="toggleBPM()"
[checked]="isBPM">
{{ 'LOGIN.PROCESS_SERVICES'| translate }}
</mat-slide-toggle>
</p>
<p> <p>
<mat-slide-toggle <mat-slide-toggle
id="switch3-mobile" id="switch3-mobile"
@@ -18,7 +18,7 @@
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, OnInit, ViewChild } from '@angular/core';
import { Validators } from '@angular/forms'; import { Validators } from '@angular/forms';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { LogService, StorageService } from '@alfresco/adf-core'; import { LogService, UserPreferencesService } from '@alfresco/adf-core';
@Component({ @Component({
selector: 'app-login', selector: 'app-login',
@@ -34,13 +34,11 @@ export class LoginComponent implements OnInit {
customValidation: any; customValidation: any;
disableCsrf = false; disableCsrf = false;
isECM = true;
isBPM = false;
showFooter = true; showFooter = true;
customMinLength = 2; customMinLength = 2;
constructor(private router: Router, constructor(private router: Router,
private storage: StorageService, private userPreference: UserPreferencesService,
private logService: LogService) { private logService: LogService) {
this.customValidation = { this.customValidation = {
username: ['', Validators.compose([Validators.required, Validators.minLength(this.customMinLength)])], username: ['', Validators.compose([Validators.required, Validators.minLength(this.customMinLength)])],
@@ -53,24 +51,11 @@ export class LoginComponent implements OnInit {
this.alfrescologin.addCustomValidationError('username', 'minlength', 'LOGIN.MESSAGES.USERNAME-MIN', {minLength: this.customMinLength}); this.alfrescologin.addCustomValidationError('username', 'minlength', 'LOGIN.MESSAGES.USERNAME-MIN', {minLength: this.customMinLength});
this.alfrescologin.addCustomValidationError('password', 'required', 'LOGIN.MESSAGES.PASSWORD-REQUIRED'); this.alfrescologin.addCustomValidationError('password', 'required', 'LOGIN.MESSAGES.PASSWORD-REQUIRED');
if (this.storage.hasItem('providers')) {
this.providers = this.storage.getItem('providers');
}
this.initProviders(); this.initProviders();
} }
initProviders() { initProviders() {
if (this.providers === 'BPM') { this.providers = this.userPreference.providers;
this.isECM = false;
this.isBPM = true;
} else if (this.providers === 'ECM') {
this.isECM = true;
this.isBPM = false;
} else if (this.providers === 'ALL') {
this.isECM = true;
this.isBPM = true;
}
} }
onLogin($event) { onLogin($event) {
@@ -81,16 +66,6 @@ export class LoginComponent implements OnInit {
this.logService.error($event); this.logService.error($event);
} }
toggleECM() {
this.isECM = !this.isECM;
this.storage.setItem('providers', this.updateProvider());
}
toggleBPM() {
this.isBPM = !this.isBPM;
this.storage.setItem('providers', this.updateProvider());
}
toggleCSRF() { toggleCSRF() {
this.disableCsrf = !this.disableCsrf; this.disableCsrf = !this.disableCsrf;
} }
@@ -99,26 +74,6 @@ export class LoginComponent implements OnInit {
this.showFooter = !this.showFooter; this.showFooter = !this.showFooter;
} }
updateProvider() {
if (this.isBPM && this.isECM) {
this.providers = 'ALL';
return this.providers;
}
if (this.isECM) {
this.providers = 'ECM';
return this.providers;
}
if (this.isBPM) {
this.providers = 'BPM';
return this.providers;
}
this.providers = '';
return this.providers;
}
checkForm(event: any) { checkForm(event: any) {
const values = event.values; const values = event.values;
this.logService.log(values); this.logService.log(values);
@@ -0,0 +1,17 @@
<header class="adf-logout-background">
<div class="adf-logout-section">
<div class="adf-logout-headline">
<h1>{{ 'APP.LOGOUT.TITLE' | translate}}</h1>
<h2>{{ 'APP.LOGOUT.SUB_TITLE' | translate}}</h2>
</div>
<div class="adf-logout-login">
<a mat-raised-button class="adf-logout-docs-button adf-primary-color"
href="/login">{{ 'APP.LOGOUT.LOGIN' | translate}}</a>
</div>
<div class="adf-logout-home">
<a mat-raised-button class="adf-logout-docs-button adf-primary-color"
href="/">{{ 'APP.LOGOUT.HOME' | translate}}</a>
</div>
</div>
</header>
@@ -0,0 +1,39 @@
:host {
display: flex;
justify-content: center;
align-items: center;
}
.adf-logout-header-background {
overflow: hidden;
}
.adf-logout-section {
text-align: center;
padding-top: 60px;
}
.adf-logout-headline {
h1 {
font-size: 56px;
font-weight: 300;
line-height: 56px;
margin: 15px 5px;
}
h2 {
font-size: 18px;
font-weight: 300;
line-height: 28px;
margin: 15px 0 25px 0;
}
}
.adf-logout-docs-button {
margin: 30px;
}
.adf-logout-login {
float: left;
}
@@ -0,0 +1,25 @@
/*!
* @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 { Component } from '@angular/core';
@Component({
selector: 'app-logout',
templateUrl: './logout.component.html',
styleUrls: ['./logout.component.scss']
})
export class LogoutComponent {}
@@ -1 +1 @@
<adf-host-settings (error)="onError($event)"></adf-host-settings> <adf-host-settings (cancel)="onCancel($event)" (success)="onSuccess($event)" (error)="onError($event)"></adf-host-settings>
@@ -16,7 +16,8 @@
*/ */
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { LogService } from '@alfresco/adf-core'; import { LogService, AuthenticationService, AlfrescoApiService } from '@alfresco/adf-core';
import { Router } from '@angular/router';
@Component({ @Component({
selector: 'app-settings', selector: 'app-settings',
@@ -24,10 +25,23 @@ import { LogService } from '@alfresco/adf-core';
}) })
export class SettingsComponent { export class SettingsComponent {
constructor(public logService: LogService) { constructor(private router: Router,
private authService: AuthenticationService,
private alfrescoApiService: AlfrescoApiService,
public logService: LogService) {
} }
onError(error: string) { onError(error: string) {
this.logService.log(error); this.logService.log(error);
} }
onCancel() {
this.router.navigate(['/']);
}
onSuccess() {
this.authService.removeTicket();
this.alfrescoApiService.reset();
this.router.navigate(['/']);
}
} }
+8
View File
@@ -13,6 +13,14 @@ Logs the user out when the decorated element is clicked.
<button adf-logout>Logout</button> <button adf-logout>Logout</button>
``` ```
### Properties
| Name | Type | Default value | Description |
| -- | -- | -- | -- |
| redirectUri | `string` | 'login' | Uri to be redirect after the logout |
| enabelRedirect | `boolean` | 'true' | enable/disable redirect after logout |
## See also ## See also
- [Login component](login.component.md) - [Login component](login.component.md)
+1
View File
@@ -19,6 +19,7 @@ let button = new MenuButton({
isVisible: this.isItemValid.bind(this) isVisible: this.isItemValid.bind(this)
}); });
``` ```
## Class members
## Properties ## Properties
@@ -42,6 +42,8 @@ export class FolderDialogComponent implements OnInit {
@Output() @Output()
error: EventEmitter<any> = new EventEmitter<any>(); error: EventEmitter<any> = new EventEmitter<any>();
/** Emitted when the edit/create folder is successfully created/mmodified
*/
@Output() @Output()
success: EventEmitter<any> = new EventEmitter<MinimalNodeEntryEntity>(); success: EventEmitter<any> = new EventEmitter<MinimalNodeEntryEntity>();
@@ -20,6 +20,9 @@ import {
AppConfigService, AppConfigService,
ContentService, ContentService,
StorageService, StorageService,
UserPreferencesService,
setupTestBed,
CoreModule,
TranslationMock TranslationMock
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { FileNode, FolderNode } from '../../mock'; import { FileNode, FolderNode } from '../../mock';
@@ -28,16 +31,25 @@ import { DocumentActionsService } from './document-actions.service';
import { DocumentListService } from './document-list.service'; import { DocumentListService } from './document-list.service';
import { NodeActionsService } from './node-actions.service'; import { NodeActionsService } from './node-actions.service';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { TestBed } from '@angular/core/testing';
describe('DocumentActionsService', () => { describe('DocumentActionsService', () => {
let service: DocumentActionsService; let service: DocumentActionsService;
let documentListService: DocumentListService; let documentListService: DocumentListService;
let nodeActionsService: NodeActionsService; let nodeActionsService: NodeActionsService;
let userPreferences;
setupTestBed({
imports: [
CoreModule.forRoot()
]
});
beforeEach(() => { beforeEach(() => {
userPreferences = TestBed.get(UserPreferencesService);
let contentService = new ContentService(null, null, null, null); let contentService = new ContentService(null, null, null, null);
let alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService()); let alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), userPreferences, new StorageService());
documentListService = new DocumentListService(null, contentService, alfrescoApiService, null, null); documentListService = new DocumentListService(null, contentService, alfrescoApiService, null, null);
service = new DocumentActionsService(null, null, new TranslationMock(), documentListService, contentService); service = new DocumentActionsService(null, null, new TranslationMock(), documentListService, contentService);
@@ -16,8 +16,9 @@
*/ */
import { AlfrescoApiServiceMock, AlfrescoApiService, import { AlfrescoApiServiceMock, AlfrescoApiService,
AppConfigService, StorageService, ContentService } from '@alfresco/adf-core'; AppConfigService, StorageService, ContentService, UserPreferencesService, setupTestBed, CoreModule } from '@alfresco/adf-core';
import { DocumentListService } from './document-list.service'; import { DocumentListService } from './document-list.service';
import { TestBed } from '@angular/core/testing';
declare let jasmine: any; declare let jasmine: any;
@@ -25,6 +26,7 @@ describe('DocumentListService', () => {
let service: DocumentListService; let service: DocumentListService;
let alfrescoApiService: AlfrescoApiService; let alfrescoApiService: AlfrescoApiService;
let userPreferences: UserPreferencesService;
let fakeEntryNode = { let fakeEntryNode = {
'entry': { 'entry': {
@@ -87,9 +89,17 @@ describe('DocumentListService', () => {
} }
}; };
setupTestBed({
imports: [
CoreModule.forRoot()
]
});
beforeEach(() => { beforeEach(() => {
userPreferences = TestBed.get(UserPreferencesService);
let contentService = new ContentService(null, null, null, null); let contentService = new ContentService(null, null, null, null);
alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService()); alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), userPreferences, new StorageService());
service = new DocumentListService(null, contentService, alfrescoApiService, null, null); service = new DocumentListService(null, contentService, alfrescoApiService, null, null);
jasmine.Ajax.install(); jasmine.Ajax.install();
}); });
@@ -16,7 +16,7 @@
*/ */
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { AlfrescoApiServiceMock, AppConfigService, StorageService, ContentService, setupTestBed, CoreModule, TranslationMock } from '@alfresco/adf-core'; import { AlfrescoApiServiceMock, AppConfigService, StorageService, ContentService, setupTestBed, CoreModule, TranslationMock, UserPreferencesService } from '@alfresco/adf-core';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { FileNode, FolderNode } from '../../mock'; import { FileNode, FolderNode } from '../../mock';
import { ContentActionHandler } from '../models/content-action.model'; import { ContentActionHandler } from '../models/content-action.model';
@@ -27,6 +27,7 @@ describe('FolderActionsService', () => {
let service: FolderActionsService; let service: FolderActionsService;
let documentListService: DocumentListService; let documentListService: DocumentListService;
let userPreferences: UserPreferencesService;
setupTestBed({ setupTestBed({
imports: [ imports: [
@@ -38,8 +39,10 @@ describe('FolderActionsService', () => {
let appConfig: AppConfigService = TestBed.get(AppConfigService); let appConfig: AppConfigService = TestBed.get(AppConfigService);
appConfig.config.ecmHost = 'http://localhost:9876/ecm'; appConfig.config.ecmHost = 'http://localhost:9876/ecm';
userPreferences = TestBed.get(UserPreferencesService);
let contentService = new ContentService(null, null, null, null); let contentService = new ContentService(null, null, null, null);
let alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService()); let alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), userPreferences, new StorageService());
documentListService = new DocumentListService(null, contentService, alfrescoApiService, null, null); documentListService = new DocumentListService(null, contentService, alfrescoApiService, null, null);
service = new FolderActionsService(null, documentListService, contentService, new TranslationMock()); service = new FolderActionsService(null, documentListService, contentService, new TranslationMock());
}); });
@@ -15,17 +15,27 @@
* limitations under the License. * limitations under the License.
*/ */
import { AlfrescoApiServiceMock, AppConfigService, StorageService } from '@alfresco/adf-core'; import { AlfrescoApiServiceMock, AppConfigService, StorageService, setupTestBed, CoreModule, UserPreferencesService } from '@alfresco/adf-core';
import { RatingService } from './rating.service'; import { RatingService } from './rating.service';
import { TestBed } from '@angular/core/testing';
declare let jasmine: any; declare let jasmine: any;
describe('Rating service', () => { describe('Rating service', () => {
let service; let service;
let userPreferences: UserPreferencesService;
setupTestBed({
imports: [
CoreModule.forRoot()
]
});
beforeEach(() => { beforeEach(() => {
service = new RatingService(new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService())); userPreferences = TestBed.get(UserPreferencesService);
service = new RatingService(new AlfrescoApiServiceMock(new AppConfigService(null), userPreferences, new StorageService()));
}); });
beforeEach(() => { beforeEach(() => {
@@ -15,17 +15,26 @@
* limitations under the License. * limitations under the License.
*/ */
import { AlfrescoApiServiceMock, LogService, AppConfigService, StorageService } from '@alfresco/adf-core'; import { AlfrescoApiServiceMock, LogService, AppConfigService, StorageService, setupTestBed, CoreModule, UserPreferencesService } from '@alfresco/adf-core';
import { TagService } from './tag.service'; import { TagService } from './tag.service';
import { TestBed } from '@angular/core/testing';
declare let jasmine: any; declare let jasmine: any;
describe('TagService', () => { describe('TagService', () => {
let service: TagService; let service: TagService;
let userPreferences: UserPreferencesService;
setupTestBed({
imports: [
CoreModule.forRoot()
]
});
beforeEach(() => { beforeEach(() => {
service = new TagService(new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService()), new LogService(new AppConfigService(null))); userPreferences = TestBed.get(UserPreferencesService);
service = new TagService(new AlfrescoApiServiceMock(new AppConfigService(null), userPreferences, new StorageService()), new LogService(new AppConfigService(null)));
}); });
beforeEach(() => { beforeEach(() => {
+4 -4
View File
@@ -18,10 +18,10 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { Http } from '@angular/http'; import { Http } from '@angular/http';
import { AuthenticationService } from '../services/authentication.service'; import { AuthenticationService } from '../services/authentication.service';
import { AppConfigService } from '../app-config/app-config.service';
import { BpmProductVersionModel, EcmProductVersionModel } from '../models/product-version.model'; import { BpmProductVersionModel, EcmProductVersionModel } from '../models/product-version.model';
import { DiscoveryApiService } from '../services/discovery-api.service'; import { DiscoveryApiService } from '../services/discovery-api.service';
import { ObjectDataTableAdapter } from '../datatable/data/object-datatable-adapter'; import { ObjectDataTableAdapter } from '../datatable/data/object-datatable-adapter';
import { UserPreferencesService } from '../services/user-preferences.service';
@Component({ @Component({
selector: 'adf-about', selector: 'adf-about',
@@ -44,7 +44,7 @@ export class AboutComponent implements OnInit {
bpmVersion: BpmProductVersionModel = null; bpmVersion: BpmProductVersionModel = null;
constructor(private http: Http, constructor(private http: Http,
private appConfig: AppConfigService, private userPreference: UserPreferencesService,
private authService: AuthenticationService, private authService: AuthenticationService,
private discovery: DiscoveryApiService) { private discovery: DiscoveryApiService) {
} }
@@ -114,8 +114,8 @@ export class AboutComponent implements OnInit {
}); });
this.ecmHost = this.appConfig.get<string>('ecmHost'); this.ecmHost = this.userPreference.ecmHost;
this.bpmHost = this.appConfig.get<string>('bpmHost'); this.bpmHost = this.userPreference.bpmHost;
} }
private gitHubLinkCreation(alfrescoPackagesTableRepresentation): void { private gitHubLinkCreation(alfrescoPackagesTableRepresentation): void {
+7 -2
View File
@@ -461,12 +461,17 @@
"oauth2": { "oauth2": {
"description": "AUTH configuration parameters", "description": "AUTH configuration parameters",
"type": "object", "type": "object",
"required": [ "host", "clientId", "secret" ], "required": [ "host", "clientId", "secret", "scope" ],
"properties": { "properties": {
"host": { "type": "string" }, "host": { "type": "string" },
"silentLogin": { "type": "boolean" },
"authPath": { "type": "string" }, "authPath": { "type": "string" },
"clientId": { "type": "string" }, "clientId": { "type": "string" },
"secret": { "type": "string" } "secret": { "type": "string" },
"redirectUri": { "type": "string" },
"redirectUriLogout": { "type": "string" },
"silentRefreshRedirectUri": { "type": "string" },
"scope": { "type": "string" }
} }
}, },
"adf-version-manager": { "adf-version-manager": {
+1
View File
@@ -17,6 +17,7 @@
import { CommonModule, DatePipe } from '@angular/common'; import { CommonModule, DatePipe } from '@angular/common';
import { HttpClient, HttpClientModule } from '@angular/common/http'; import { HttpClient, HttpClientModule } from '@angular/common/http';
import { APP_INITIALIZER, NgModule, ModuleWithProviders } from '@angular/core'; import { APP_INITIALIZER, NgModule, ModuleWithProviders } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
+133 -37
View File
@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component } from '@angular/core'; import { Component, ContentChildren } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
@@ -23,56 +23,152 @@ import { Observable } from 'rxjs/Observable';
import { AuthenticationService } from '../services'; import { AuthenticationService } from '../services';
import { setupTestBed } from '../testing/setupTestBed'; import { setupTestBed } from '../testing/setupTestBed';
import { CoreModule } from '../core.module'; import { CoreModule } from '../core.module';
import { LogoutDirective } from './logout.directive';
describe('LogoutDirective', () => { describe('LogoutDirective', () => {
@Component({ describe('No input', () => {
selector: 'adf-test-component',
template: '<button adf-logout></button>'
})
class TestComponent {}
let fixture: ComponentFixture<TestComponent>; @Component({
let router: Router; selector: 'adf-test-component',
let authService: AuthenticationService; template: '<button adf-logout></button>'
})
class TestComponent {
@ContentChildren(LogoutDirective)
logoutDirective: LogoutDirective;
}
let fixture: ComponentFixture<TestComponent>;
let router: Router;
let authService: AuthenticationService;
setupTestBed({
imports: [
CoreModule.forRoot(),
RouterTestingModule
],
declarations: [
TestComponent
]
});
beforeEach(() => {
router = TestBed.get(Router);
authService = TestBed.get(AuthenticationService);
fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
});
it('should redirect to login on click', () => {
spyOn(router, 'navigate').and.callThrough();
spyOn(authService, 'logout').and.returnValue(Observable.of(true));
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(authService.logout).toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalledWith(['/login']);
});
it('should redirect to login even on logout error', () => {
spyOn(router, 'navigate').and.callThrough();
spyOn(authService, 'logout').and.returnValue(Observable.throw('err'));
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(authService.logout).toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalledWith(['/login']);
});
setupTestBed({
imports: [
CoreModule.forRoot(),
RouterTestingModule
],
declarations: [
TestComponent
]
}); });
beforeEach(() => { describe('redirectUri', () => {
router = TestBed.get(Router);
authService = TestBed.get(AuthenticationService); @Component({
fixture = TestBed.createComponent(TestComponent); selector: 'adf-test-component',
fixture.detectChanges(); template: '<button adf-logout redirectUri="/myCustomUri"></button>'
})
class TestComponent {
@ContentChildren(LogoutDirective)
logoutDirective: LogoutDirective;
}
let fixture: ComponentFixture<TestComponent>;
let router: Router;
let authService: AuthenticationService;
setupTestBed({
imports: [
CoreModule.forRoot(),
RouterTestingModule
],
declarations: [
TestComponent
]
});
beforeEach(() => {
router = TestBed.get(Router);
authService = TestBed.get(AuthenticationService);
fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
});
it('should redirect to the the input redirectUri on click if present', () => {
spyOn(router, 'navigate').and.callThrough();
spyOn(authService, 'logout').and.returnValue(Observable.of(true));
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(authService.logout).toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalledWith(['/myCustomUri']);
});
}); });
it('should redirect to login on click', () => { describe('redirectUri', () => {
spyOn(router, 'navigate').and.callThrough();
spyOn(authService, 'logout').and.returnValue(Observable.of(true));
const button = fixture.nativeElement.querySelector('button'); @Component({
button.click(); selector: 'adf-test-component',
template: '<button adf-logout [enabelRedirect]="false"></button>'
})
class TestComponent {
@ContentChildren(LogoutDirective)
logoutDirective: LogoutDirective;
}
expect(authService.logout).toHaveBeenCalled(); let fixture: ComponentFixture<TestComponent>;
expect(router.navigate).toHaveBeenCalledWith([ '/login' ]); let router: Router;
}); let authService: AuthenticationService;
it('should redirect to login even on logout error', () => { setupTestBed({
spyOn(router, 'navigate').and.callThrough(); imports: [
spyOn(authService, 'logout').and.returnValue(Observable.throw('err')); CoreModule.forRoot(),
RouterTestingModule
],
declarations: [
TestComponent
]
});
const button = fixture.nativeElement.querySelector('button'); beforeEach(() => {
button.click(); router = TestBed.get(Router);
authService = TestBed.get(AuthenticationService);
fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
});
expect(authService.logout).toHaveBeenCalled(); it('should not redirect if enabelRedirect is false', () => {
expect(router.navigate).toHaveBeenCalledWith([ '/login' ]); spyOn(router, 'navigate').and.callThrough();
spyOn(authService, 'logout').and.returnValue(Observable.of(true));
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(authService.logout).toHaveBeenCalled();
expect(router.navigate).not.toHaveBeenCalled();
});
}); });
}); });
+19 -10
View File
@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Directive, ElementRef, OnInit, Renderer2 } from '@angular/core'; import { Input, Directive, ElementRef, OnInit, Renderer2 } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { AuthenticationService } from '../services/authentication.service'; import { AuthenticationService } from '../services/authentication.service';
@@ -24,11 +24,18 @@ import { AuthenticationService } from '../services/authentication.service';
}) })
export class LogoutDirective implements OnInit { export class LogoutDirective implements OnInit {
constructor( /** Uri to be redirect after the logout default value login */
private elementRef: ElementRef, @Input()
private renderer: Renderer2, redirectUri: string = '/login';
private router: Router,
private auth: AuthenticationService) { /** Enable redirect after logout */
@Input()
enabelRedirect: boolean = true;
constructor(private elementRef: ElementRef,
private renderer: Renderer2,
private router: Router,
private auth: AuthenticationService) {
} }
ngOnInit() { ngOnInit() {
@@ -42,12 +49,14 @@ export class LogoutDirective implements OnInit {
logout() { logout() {
this.auth.logout().subscribe( this.auth.logout().subscribe(
() => this.redirectToLogin(), () => this.redirectToUri(),
() => this.redirectToLogin() () => this.redirectToUri()
); );
} }
redirectToLogin() { redirectToUri() {
this.router.navigate(['/login']); if (this.enabelRedirect) {
this.router.navigate([this.redirectUri]);
}
} }
} }
@@ -16,20 +16,28 @@
*/ */
import { SimpleChange } from '@angular/core'; import { SimpleChange } from '@angular/core';
import { fakeAsync, tick } from '@angular/core/testing'; import { fakeAsync, tick, TestBed } from '@angular/core/testing';
import { NodeFavoriteDirective } from './node-favorite.directive'; import { NodeFavoriteDirective } from './node-favorite.directive';
import { AlfrescoApiServiceMock } from '../mock/alfresco-api.service.mock'; import { AlfrescoApiServiceMock } from '../mock/alfresco-api.service.mock';
import { AppConfigService } from '../app-config/app-config.service'; import { AppConfigService } from '../app-config/app-config.service';
import { StorageService } from '../services/storage.service'; import { StorageService } from '../services/storage.service';
import { AlfrescoApiService } from '../services/alfresco-api.service'; import { UserPreferencesService } from '../services/user-preferences.service';
import { setupTestBed } from '../testing/setupTestBed';
import { CoreTestingModule } from '../testing/core.testing.module';
describe('NodeFavoriteDirective', () => { describe('NodeFavoriteDirective', () => {
let directive; let directive;
let alfrescoApiService: AlfrescoApiService; let alfrescoApiService;
let userPreferences;
setupTestBed({
imports: [CoreTestingModule]
});
beforeEach(() => { beforeEach(() => {
alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService()); userPreferences = TestBed.get(UserPreferencesService);
alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), userPreferences, new StorageService());
directive = new NodeFavoriteDirective( alfrescoApiService); directive = new NodeFavoriteDirective( alfrescoApiService);
}); });
+8 -2
View File
@@ -118,6 +118,7 @@
"ERROR_PLURAL": "{{ number }} items couldn't be deleted" "ERROR_PLURAL": "{{ number }} items couldn't be deleted"
}, },
"HOST_SETTINGS": { "HOST_SETTINGS": {
"REQUIRED": "The field is required",
"CS_URL_ERROR": "Content Services address doesn't match the URL format", "CS_URL_ERROR": "Content Services address doesn't match the URL format",
"PS_URL_ERROR": "Process Services address doesn't match the URL format", "PS_URL_ERROR": "Process Services address doesn't match the URL format",
"TITLE": "Settings", "TITLE": "Settings",
@@ -125,7 +126,11 @@
"BP-HOST": "Process Services URL", "BP-HOST": "Process Services URL",
"BACK": "Back", "BACK": "Back",
"APPLY": "APPLY", "APPLY": "APPLY",
"NOT_VALID": "http(s)://host|ip:port(/path) not recognized, try a different URL." "NOT_VALID": "http(s)://host|ip:port(/path) not recognized, try a different URL.",
"REDIRECT": "Redirect Uri",
"SILENT": "Silent Login",
"SCOPE": "Scope",
"CLIENT": "ClientId"
}, },
"CARDVIEW": { "CARDVIEW": {
"VALIDATORS": { "VALIDATORS": {
@@ -193,7 +198,8 @@
"BUTTON": { "BUTTON": {
"LOGIN": "SIGN IN", "LOGIN": "SIGN IN",
"CHECKING": "CHECKING", "CHECKING": "CHECKING",
"WELCOME": "WELCOME" "WELCOME": "WELCOME",
"SSO": "SIGN IN SSO"
}, },
"ACTION": { "ACTION": {
"HELP": "NEED HELP?", "HELP": "NEED HELP?",
+104 -83
View File
@@ -1,6 +1,6 @@
<div class="adf-login-content" [style.background-image]="'url(' + backgroundImageUrl + ')'"> <div class="adf-login-content" [style.background-image]="'url(' + backgroundImageUrl + ')'">
<div class="ie11FixerParent"> <div class="ie11FixerParent">
<div class="ie11FixerChild"> <div class="ie11FixerChild">
<mat-card class="adf-login-card-wide"> <mat-card class="adf-login-card-wide">
<form id="adf-login-form" [formGroup]="form" (submit)="onSubmit(form.value)" autocomplete="off"> <form id="adf-login-form" [formGroup]="form" (submit)="onSubmit(form.value)" autocomplete="off">
@@ -10,107 +10,128 @@
<div class="adf-alfresco-logo"> <div class="adf-alfresco-logo">
<!--HEADER TEMPLATE--> <!--HEADER TEMPLATE-->
<ng-template *ngIf="headerTemplate" <ng-template *ngIf="headerTemplate"
ngFor [ngForOf]="[data]" ngFor [ngForOf]="[data]"
[ngForTemplate]="headerTemplate"> [ngForTemplate]="headerTemplate">
</ng-template> </ng-template>
<img *ngIf="!headerTemplate" class="adf-img-logo" [src]="logoImageUrl" <img *ngIf="!headerTemplate" class="adf-img-logo" [src]="logoImageUrl"
alt="{{'LOGIN.LOGO' | translate }}"> alt="{{'LOGIN.LOGO' | translate }}">
</div> </div>
</mat-card-title> </mat-card-title>
</mat-card-header> </mat-card-header>
<mat-card-content class="adf-login-controls"> <mat-card-content class="adf-login-controls">
<!--ERRORS AREA--> <div *ngIf="!implicitFlow">
<div class="adf-error-container"> <!--ERRORS AREA-->
<div *ngIf="isError" id="login-error" data-automation-id="login-error" <div class="adf-error-container">
class="error adf-error-message"> <div *ngIf="isError" id="login-error" data-automation-id="login-error"
<mat-icon class="error-icon">warning</mat-icon> class="error adf-error-message">
<span class="login-error-message">{{errorMsg | translate }}</span> <mat-icon class="error-icon">warning</mat-icon>
</div> <span class="login-error-message">{{errorMsg | translate }}</span>
</div>
<!--USERNAME FIELD-->
<div class="adf-login__field" [ngClass]="{'is-invalid': isErrorStyle(form.controls.username)}">
<mat-form-field class="adf-full-width" floatPlaceholder="never" color="primary">
<input matInput placeholder="{{'LOGIN.LABEL.USERNAME' | translate }}"
type="text"
class="adf-full-width"
[formControl]="form.controls['username']"
autocapitalize="none"
id="username"
data-automation-id="username"
(blur)="trimUsername($event)"
tabindex="1">
</mat-form-field>
<span class="adf-login-validation" for="username" *ngIf="formError.username">
<span id="username-error" class="adf-login-error" data-automation-id="username-error">{{formError.username | translate }}</span>
</span>
</div>
<!--PASSWORD FIELD-->
<div class="adf-login__field">
<mat-form-field class="adf-full-width" floatPlaceholder="never" color="primary">
<input matInput placeholder="{{'LOGIN.LABEL.PASSWORD' | translate }}"
type="password"
[formControl]="form.controls['password']"
id="password"
data-automation-id="password"
tabindex="2">
<mat-icon *ngIf="isPasswordShow" matSuffix class="adf-login-password-icon"
data-automation-id="hide_password" (click)="toggleShowPassword()">visibility
</mat-icon>
<mat-icon *ngIf="!isPasswordShow" matSuffix class="adf-login-password-icon"
data-automation-id="show_password" (click)="toggleShowPassword()">visibility_off
</mat-icon>
</mat-form-field>
<span class="adf-login-validation" for="password" *ngIf="formError.password">
<span id="password-required" class="adf-login-error"
data-automation-id="password-required">{{formError.password | translate }}</span>
</span>
</div>
<!--CUSTOM CONTENT-->
<ng-content></ng-content>
<br>
<button type="submit" id="login-button" tabindex="3"
class="adf-login-button"
mat-raised-button color="primary"
[class.isChecking]="actualLoginStep === LoginSteps.Checking"
[class.isWelcome]="actualLoginStep === LoginSteps.Welcome"
data-automation-id="login-button" [disabled]="!form.valid">
<span *ngIf="actualLoginStep === LoginSteps.Landing" class="adf-login-button-label">{{ 'LOGIN.BUTTON.LOGIN' | translate }}</span>
<div *ngIf="actualLoginStep === LoginSteps.Checking" class="adf-interactive-login-label">
<span class="adf-login-button-label">{{ 'LOGIN.BUTTON.CHECKING' | translate }}</span>
<div class="adf-login-spinner-container">
<mat-spinner id="checking-spinner" class="adf-login-checking-spinner" [diameter]="25"></mat-spinner>
</div> </div>
</div> </div>
<!--USERNAME FIELD-->
<div class="adf-login__field"
[ngClass]="{'is-invalid': isErrorStyle(form.controls.username)}">
<mat-form-field class="adf-full-width" floatPlaceholder="never" color="primary">
<input matInput placeholder="{{'LOGIN.LABEL.USERNAME' | translate }}"
type="text"
class="adf-full-width"
[formControl]="form.controls['username']"
autocapitalize="none"
id="username"
data-automation-id="username"
(blur)="trimUsername($event)"
tabindex="1">
</mat-form-field>
<div *ngIf="actualLoginStep === LoginSteps.Welcome" class="adf-interactive-login-label"> <span class="adf-login-validation" for="username" *ngIf="formError.username">
<span class="adf-login-button-label">{{ 'LOGIN.BUTTON.WELCOME' | translate }}</span> <span id="username-error" class="adf-login-error" data-automation-id="username-error">{{formError.username | translate }}</span>
<mat-icon class="welcome-icon">done</mat-icon> </span>
</div> </div>
</button> <!--PASSWORD FIELD-->
<div *ngIf="showRememberMe" class="adf-login__remember-me"> <div class="adf-login__field">
<mat-checkbox id="adf-login-remember" color="primary" class="adf-login-rememberme" [checked]="rememberMe" <mat-form-field class="adf-full-width" floatPlaceholder="never" color="primary">
(change)="rememberMe = !rememberMe">{{ 'LOGIN.LABEL.REMEMBER' | translate }} <input matInput placeholder="{{'LOGIN.LABEL.PASSWORD' | translate }}"
</mat-checkbox> type="password"
[formControl]="form.controls['password']"
id="password"
data-automation-id="password"
tabindex="2">
<mat-icon *ngIf="isPasswordShow" matSuffix class="adf-login-password-icon"
data-automation-id="hide_password" (click)="toggleShowPassword()">
visibility
</mat-icon>
<mat-icon *ngIf="!isPasswordShow" matSuffix class="adf-login-password-icon"
data-automation-id="show_password" (click)="toggleShowPassword()">
visibility_off
</mat-icon>
</mat-form-field>
<span class="adf-login-validation" for="password" *ngIf="formError.password">
<span id="password-required" class="adf-login-error"
data-automation-id="password-required">{{formError.password | translate }}</span>
</span>
</div>
<!--CUSTOM CONTENT-->
<ng-content></ng-content>
<br>
<button type="submit" id="login-button" tabindex="3"
class="adf-login-button"
mat-raised-button color="primary"
[class.isChecking]="actualLoginStep === LoginSteps.Checking"
[class.isWelcome]="actualLoginStep === LoginSteps.Welcome"
data-automation-id="login-button" [disabled]="!form.valid">
<span *ngIf="actualLoginStep === LoginSteps.Landing" class="adf-login-button-label">{{ 'LOGIN.BUTTON.LOGIN' | translate }}</span>
<div *ngIf="actualLoginStep === LoginSteps.Checking"
class="adf-interactive-login-label">
<span
class="adf-login-button-label">{{ 'LOGIN.BUTTON.CHECKING' | translate }}</span>
<div class="adf-login-spinner-container">
<mat-spinner id="checking-spinner" class="adf-login-checking-spinner"
[diameter]="25"></mat-spinner>
</div>
</div>
<div *ngIf="actualLoginStep === LoginSteps.Welcome" class="adf-interactive-login-label">
<span class="adf-login-button-label">{{ 'LOGIN.BUTTON.WELCOME' | translate }}</span>
<mat-icon class="welcome-icon">done</mat-icon>
</div>
</button>
<div *ngIf="showRememberMe" class="adf-login__remember-me">
<mat-checkbox id="adf-login-remember" color="primary" class="adf-login-rememberme"
[checked]="rememberMe"
(change)="rememberMe = !rememberMe">{{ 'LOGIN.LABEL.REMEMBER' | translate
}}
</mat-checkbox>
</div>
</div> </div>
<div *ngIf="implicitFlow">
<button type="button" (click)="implicitLogin()" id="login-button-sso" tabindex="1"
class="adf-login-button"
mat-raised-button color="primary"
data-automation-id="login-button-sso">
<span class="adf-login-button-label">{{ 'LOGIN.BUTTON.SSO' | translate }}</span>
</button>
</div>
</mat-card-content> </mat-card-content>
<mat-card-actions *ngIf="footerTemplate || showLoginActions"> <mat-card-actions *ngIf="footerTemplate || showLoginActions">
<div class="adf-login-action-container"> <div class="adf-login-action-container">
<!--FOOTER TEMPLATE--> <!--FOOTER TEMPLATE-->
<ng-template *ngIf="footerTemplate" <ng-template *ngIf="footerTemplate"
ngFor [ngForOf]="[data]" ngFor [ngForOf]="[data]"
[ngForTemplate]="footerTemplate"> [ngForTemplate]="footerTemplate">
</ng-template> </ng-template>
<div class="adf-login-action" *ngIf="!footerTemplate && showLoginActions"> <div class="adf-login-action" *ngIf="!footerTemplate && showLoginActions">
<div id="adf-login-action-left" class="adf-login-action-left"> <div id="adf-login-action-left" class="adf-login-action-left">
@@ -131,5 +152,5 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@@ -58,7 +58,7 @@ describe('LoginComponent', () => {
imports: [CoreTestingModule] imports: [CoreTestingModule]
}); });
beforeEach(() => { beforeEach(async(() => {
fixture = TestBed.createComponent(LoginComponent); fixture = TestBed.createComponent(LoginComponent);
element = fixture.nativeElement; element = fixture.nativeElement;
@@ -66,15 +66,17 @@ describe('LoginComponent', () => {
component.showRememberMe = true; component.showRememberMe = true;
component.showLoginActions = true; component.showLoginActions = true;
usernameInput = element.querySelector('#username');
passwordInput = element.querySelector('#password');
authService = TestBed.get(AuthenticationService); authService = TestBed.get(AuthenticationService);
router = TestBed.get(Router); router = TestBed.get(Router);
userPreferences = TestBed.get(UserPreferencesService); userPreferences = TestBed.get(UserPreferencesService);
fixture.detectChanges(); fixture.detectChanges();
});
fixture.whenStable().then(() => {
usernameInput = element.querySelector('#username');
passwordInput = element.querySelector('#password');
});
}));
afterEach(() => { afterEach(() => {
fixture.destroy(); fixture.destroy();
@@ -98,7 +100,7 @@ describe('LoginComponent', () => {
}); });
it('should redirect to route on successful login', () => { it('should redirect to route on successful login', () => {
spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket'})); spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket' }));
const redirect = '/home'; const redirect = '/home';
component.successRoute = redirect; component.successRoute = redirect;
spyOn(router, 'navigate'); spyOn(router, 'navigate');
@@ -107,10 +109,10 @@ describe('LoginComponent', () => {
}); });
it('should redirect to previous route state on successful login', () => { it('should redirect to previous route state on successful login', () => {
spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket'})); spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket' }));
const redirect = '/home'; const redirect = '/home';
component.successRoute = redirect; component.successRoute = redirect;
authService.setRedirect({ provider: 'ECM', navigation: ['some-route'] } ); authService.setRedirect({ provider: 'ECM', navigation: ['some-route'] });
spyOn(router, 'navigate'); spyOn(router, 'navigate');
@@ -158,7 +160,7 @@ describe('LoginComponent', () => {
}); });
it('should be changed to the "welcome key" after a successful login attempt', () => { it('should be changed to the "welcome key" after a successful login attempt', () => {
spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket'})); spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket' }));
loginWithCredentials('fake-username', 'fake-password'); loginWithCredentials('fake-username', 'fake-password');
expect(getLoginButtonText()).toEqual('LOGIN.BUTTON.WELCOME'); expect(getLoginButtonText()).toEqual('LOGIN.BUTTON.WELCOME');
@@ -382,7 +384,7 @@ describe('LoginComponent', () => {
}); });
it('should return success event after the login have succeeded', (done) => { it('should return success event after the login have succeeded', (done) => {
spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket'})); spyOn(authService, 'login').and.returnValue(Observable.of({ type: 'type', ticket: 'ticket' }));
component.providers = 'ECM'; component.providers = 'ECM';
expect(component.isError).toBe(false); expect(component.isError).toBe(false);
@@ -514,7 +516,7 @@ describe('LoginComponent', () => {
expect(component.isError).toBe(false); expect(component.isError).toBe(false);
expect(event).toEqual( expect(event).toEqual(
new LoginSuccessEvent({type: 'type', ticket: 'ticket'}, 'fake-username', null) new LoginSuccessEvent({ type: 'type', ticket: 'ticket' }, 'fake-username', null)
); );
}); });
@@ -583,4 +585,47 @@ describe('LoginComponent', () => {
loginWithCredentials('fake-username', 'fake-password'); loginWithCredentials('fake-username', 'fake-password');
})); }));
describe('SSO', () => {
beforeEach(() => {
userPreferences.oauthConfig = { implicitFlow: true };
});
afterEach(() => {
userPreferences.oauthConfig = null;
});
it('should not show login username and password if SSO implicit flow is active', async(() => {
spyOn(authService, 'isOauth').and.returnValue(true);
component.ngOnInit();
fixture.detectChanges();
expect(element.querySelector('#username')).toBeNull();
expect(element.querySelector('#password')).toBeNull();
}));
it('should not show the login base auth button', async(() => {
spyOn(authService, 'isOauth').and.returnValue(true);
userPreferences.oauthConfig = { implicitFlow: true };
component.ngOnInit();
fixture.detectChanges();
expect(element.querySelector('#login-button')).toBeNull();
}));
it('should show the login SSO button', async(() => {
spyOn(authService, 'isOauth').and.returnValue(true);
userPreferences.oauthConfig = { implicitFlow: true };
component.ngOnInit();
fixture.detectChanges();
expect(element.querySelector('#login-button-sso')).toBeDefined();
}));
});
}); });
+13 -1
View File
@@ -120,6 +120,8 @@ export class LoginComponent implements OnInit {
@Output() @Output()
executeSubmit = new EventEmitter<LoginSubmitEvent>(); executeSubmit = new EventEmitter<LoginSubmitEvent>();
implicitFlow: boolean = false;
form: FormGroup; form: FormGroup;
isError: boolean = false; isError: boolean = false;
errorMsg: string; errorMsg: string;
@@ -154,6 +156,12 @@ export class LoginComponent implements OnInit {
} }
ngOnInit() { ngOnInit() {
if (this.authService.isOauth()) {
if (this.userPreferences.oauthConfig && this.userPreferences.oauthConfig.implicitFlow) {
this.implicitFlow = true;
}
}
if (this.hasCustomFiledsValidation()) { if (this.hasCustomFiledsValidation()) {
this.form = this._fb.group(this.fieldsValidation); this.form = this._fb.group(this.fieldsValidation);
} else { } else {
@@ -178,7 +186,7 @@ export class LoginComponent implements OnInit {
this.settingsService.csrfDisabled = this.disableCsrf; this.settingsService.csrfDisabled = this.disableCsrf;
this.disableError(); this.disableError();
const args = new LoginSubmitEvent({controls : { username : this.form.controls.username} }); const args = new LoginSubmitEvent({ controls: { username: this.form.controls.username } });
this.executeSubmit.emit(args); this.executeSubmit.emit(args);
if (args.defaultPrevented) { if (args.defaultPrevented) {
@@ -188,6 +196,10 @@ export class LoginComponent implements OnInit {
} }
} }
implicitLogin() {
this.authService.ssoImplictiLogin();
}
/** /**
* The method check the error in the form and push the error in the formError object * The method check the error in the form and push the error in the formError object
* @param data * @param data
+3 -31
View File
@@ -16,49 +16,21 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { AlfrescoApi } from 'alfresco-js-api';
import * as alfrescoApi from 'alfresco-js-api';
import { AppConfigService } from '../app-config/app-config.service'; import { AppConfigService } from '../app-config/app-config.service';
import { StorageService } from '../services/storage.service'; import { StorageService } from '../services/storage.service';
import { AlfrescoApiService } from '../services/alfresco-api.service'; import { AlfrescoApiService } from '../services/alfresco-api.service';
import { UserPreferencesService } from '../services/user-preferences.service';
/* tslint:disable:adf-file-name */ /* tslint:disable:adf-file-name */
@Injectable() @Injectable()
export class AlfrescoApiServiceMock extends AlfrescoApiService { export class AlfrescoApiServiceMock extends AlfrescoApiService {
constructor(protected appConfig: AppConfigService, constructor(protected appConfig: AppConfigService,
protected userPreference: UserPreferencesService,
protected storage: StorageService) { protected storage: StorageService) {
super(appConfig, storage); super(appConfig, userPreference, storage);
if (!this.alfrescoApi) { if (!this.alfrescoApi) {
this.initAlfrescoApi(); this.initAlfrescoApi();
} }
} }
async load() {
await this.appConfig.load().then(() => {
if (!this.alfrescoApi) {
this.initAlfrescoApi();
}
});
}
async reset() {
if (this.alfrescoApi) {
this.alfrescoApi = null;
}
this.initAlfrescoApi();
}
protected initAlfrescoApi() {
this.alfrescoApi = <AlfrescoApi> new alfrescoApi({
provider: this.storage.getItem('AUTH_TYPE'),
ticketEcm: this.storage.getItem('ticket-ECM'),
ticketBpm: this.storage.getItem('ticket-BPM'),
hostEcm: this.appConfig.get<string>('ecmHost'),
hostBpm: this.appConfig.get<string>('bpmHost'),
contextRoot: 'alfresco',
disableCsrf: this.storage.getItem('DISABLE_CSRF') === 'true',
oauth2: this.appConfig.get<any>('oauth2')
});
}
} }
+29
View File
@@ -0,0 +1,29 @@
/*!
* @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 interface OauthConfigModel {
host: string;
clientId: string;
scope: string;
implicitFlow: boolean;
redirectUri: string;
silentLogin?: boolean;
silentRefreshRedirectUri?: string;
secret?: string;
refreshTokenTimeout?: number;
redirectUriLogout?: string;
}
+20 -9
View File
@@ -25,6 +25,7 @@ import * as alfrescoApi from 'alfresco-js-api';
import { AppConfigService } from '../app-config/app-config.service'; import { AppConfigService } from '../app-config/app-config.service';
import { StorageService } from './storage.service'; import { StorageService } from './storage.service';
import { Subject } from 'rxjs/Subject'; import { Subject } from 'rxjs/Subject';
import { UserPreferencesService } from './user-preferences.service';
/* tslint:disable:adf-file-name */ /* tslint:disable:adf-file-name */
@@ -95,6 +96,7 @@ export class AlfrescoApiService {
} }
constructor(protected appConfig: AppConfigService, constructor(protected appConfig: AppConfigService,
protected userPreference: UserPreferencesService,
protected storage: StorageService) { protected storage: StorageService) {
} }
@@ -105,23 +107,32 @@ export class AlfrescoApiService {
} }
async reset() { async reset() {
if (this.alfrescoApi) {
this.alfrescoApi = null;
}
this.initAlfrescoApi(); this.initAlfrescoApi();
} }
protected initAlfrescoApi() { protected initAlfrescoApi() {
this.alfrescoApi = <AlfrescoApi> new alfrescoApi({ let oauth: any = Object.assign({}, this.userPreference.oauthConfig);
provider: this.storage.getItem('AUTH_TYPE'), if (oauth) {
oauth.redirectUri = window.location.origin + (oauth.redirectUri || '/');
oauth.redirectUriLogout = window.location.origin + (oauth.redirectUriLogout || '/');
}
const config = {
provider: this.userPreference.providers,
ticketEcm: this.storage.getItem('ticket-ECM'), ticketEcm: this.storage.getItem('ticket-ECM'),
ticketBpm: this.storage.getItem('ticket-BPM'), ticketBpm: this.storage.getItem('ticket-BPM'),
hostEcm: this.appConfig.get<string>('ecmHost'), hostEcm: this.userPreference.ecmHost,
hostBpm: this.appConfig.get<string>('bpmHost'), hostBpm: this.userPreference.bpmHost,
contextRootBpm: this.appConfig.get<string>('contextRootBpm'), contextRootBpm: this.appConfig.get<string>('contextRootBpm'),
contextRoot: this.appConfig.get<string>('contextRootEcm'), contextRoot: this.appConfig.get<string>('contextRootEcm'),
disableCsrf: this.storage.getItem('DISABLE_CSRF') === 'true', disableCsrf: this.storage.getItem('DISABLE_CSRF') === 'true',
oauth2: this.appConfig.get<any>('oauth2') oauth2: oauth
}); };
if (this.alfrescoApi) {
this.alfrescoApi.configureJsApi(config);
} else {
this.alfrescoApi = <AlfrescoApi> new alfrescoApi(config);
}
} }
} }
@@ -35,6 +35,7 @@ describe('AuthGuardService BPM', () => {
}); });
beforeEach(() => { beforeEach(() => {
localStorage.clear();
authService = TestBed.get(AuthenticationService); authService = TestBed.get(AuthenticationService);
authGuard = TestBed.get(AuthGuardBpm); authGuard = TestBed.get(AuthGuardBpm);
routerService = TestBed.get(Router); routerService = TestBed.get(Router);
+1 -1
View File
@@ -38,7 +38,7 @@ export class AuthGuardEcm implements CanActivate {
} }
private isLoggedIn(): Promise<boolean> { private isLoggedIn(): Promise<boolean> {
if (!this.authApi.isLoggedIn()) { if (this.authApi === undefined || !this.authApi.isLoggedIn()) {
return Promise.resolve(false); return Promise.resolve(false);
} }
@@ -35,6 +35,7 @@ describe('AuthGuardService', () => {
}); });
beforeEach(() => { beforeEach(() => {
localStorage.clear();
state = { url: '' }; state = { url: '' };
authService = TestBed.get(AuthenticationService); authService = TestBed.get(AuthenticationService);
router = TestBed.get(Router); router = TestBed.get(Router);
+18 -10
View File
@@ -21,22 +21,24 @@ import {
CanActivateChild, RouterStateSnapshot, Router, CanActivateChild, RouterStateSnapshot, Router,
PRIMARY_OUTLET, UrlTree, UrlSegmentGroup, UrlSegment PRIMARY_OUTLET, UrlTree, UrlSegmentGroup, UrlSegment
} from '@angular/router'; } from '@angular/router';
import { AppConfigService } from '../app-config/app-config.service';
import { AuthenticationService } from './authentication.service'; import { AuthenticationService } from './authentication.service';
import { Observable } from 'rxjs/Observable';
import { AppConfigService } from '../app-config/app-config.service';
import { UserPreferencesService } from './user-preferences.service';
@Injectable() @Injectable()
export class AuthGuard implements CanActivate, CanActivateChild { export class AuthGuard implements CanActivate, CanActivateChild {
constructor(private authService: AuthenticationService, constructor(private authService: AuthenticationService,
private router: Router, private router: Router,
private userPreference: UserPreferencesService,
private appConfig: AppConfigService) {} private appConfig: AppConfigService) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable<boolean> {
const redirectUrl = state.url; const redirectUrl = state.url;
return this.checkLogin(redirectUrl); return this.checkLogin(redirectUrl);
} }
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable<boolean> {
return this.canActivate(route, state); return this.canActivate(route, state);
} }
@@ -44,23 +46,29 @@ export class AuthGuard implements CanActivate, CanActivateChild {
if (this.authService.isLoggedIn()) { if (this.authService.isLoggedIn()) {
return true; return true;
} }
if (!this.authService.isOauth() || this.isOAuthWithoutSilentLogin() ) {
const navigation = this.getNavigationCommands(redirectUrl);
const navigation = this.getNavigationCommands(redirectUrl); this.authService.setRedirect({ provider: 'ALL', navigation } );
this.authService.setRedirect({ provider: 'ALL', navigation } ); const pathToLogin = this.getRouteDestinationForLogin();
const pathToLogin = this.getRouteDestinationForLogin(); this.router.navigate(['/' + pathToLogin]);
this.router.navigate(['/' + pathToLogin]); }
return false; return false;
} }
private getRouteDestinationForLogin(): string { isOAuthWithoutSilentLogin() {
return this.authService.isOauth() && this.userPreference.oauthConfig.silentLogin === false;
}
public getRouteDestinationForLogin(): string {
return this.appConfig && return this.appConfig &&
this.appConfig.get<string>('loginRoute') ? this.appConfig.get<string>('loginRoute') ?
this.appConfig.get<string>('loginRoute') : 'login'; this.appConfig.get<string>('loginRoute') : 'login';
} }
private getNavigationCommands(redirectUrl: string): any[] { public getNavigationCommands(redirectUrl: string): any[] {
const urlTree: UrlTree = this.router.parseUrl(redirectUrl); const urlTree: UrlTree = this.router.parseUrl(redirectUrl);
const urlSegmentGroup: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET]; const urlSegmentGroup: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET];
@@ -60,7 +60,8 @@ describe('AuthenticationService', () => {
describe('remember me', () => { describe('remember me', () => {
beforeEach(() => { beforeEach(() => {
preferences.authType = 'ECM'; preferences.providers = 'ECM';
apiService.reset();
}); });
it('[ECM] should save the remember me cookie as a session cookie after successful login', (done) => { it('[ECM] should save the remember me cookie as a session cookie after successful login', (done) => {
@@ -123,7 +124,8 @@ describe('AuthenticationService', () => {
describe('when the setting is ECM', () => { describe('when the setting is ECM', () => {
beforeEach(() => { beforeEach(() => {
preferences.authType = 'ECM'; preferences.providers = 'ECM';
apiService.reset();
}); });
it('should require remember me set for ECM check', () => { it('should require remember me set for ECM check', () => {
@@ -154,7 +156,7 @@ describe('AuthenticationService', () => {
}); });
jasmine.Ajax.requests.mostRecent().respondWith({ jasmine.Ajax.requests.mostRecent().respondWith({
'status': 201, status: 201,
contentType: 'application/json', contentType: 'application/json',
responseText: JSON.stringify({ 'entry': { 'id': 'fake-post-ticket', 'userId': 'admin' } }) responseText: JSON.stringify({ 'entry': { 'id': 'fake-post-ticket', 'userId': 'admin' } })
}); });
@@ -274,26 +276,27 @@ describe('AuthenticationService', () => {
it('[ECM] should set/get redirectUrl when provider is ECM', () => { it('[ECM] should set/get redirectUrl when provider is ECM', () => {
authService.setRedirect({ provider: 'ECM', navigation: ['some-url'] }); authService.setRedirect({ provider: 'ECM', navigation: ['some-url'] });
expect(authService.getRedirect(preferences.authType)).toEqual(['some-url']); expect(authService.getRedirect(preferences.providers)).toEqual(['some-url']);
}); });
it('[ECM] should set/get redirectUrl when provider is BPM', () => { it('[ECM] should set/get redirectUrl when provider is BPM', () => {
authService.setRedirect({ provider: 'BPM', navigation: ['some-url'] }); authService.setRedirect({ provider: 'BPM', navigation: ['some-url'] });
expect(authService.getRedirect(preferences.authType)).toBeNull(); expect(authService.getRedirect(preferences.providers)).toBeNull();
}); });
it('[ECM] should return null as redirectUrl when redirectUrl field is not set', () => { it('[ECM] should return null as redirectUrl when redirectUrl field is not set', () => {
authService.setRedirect(null); authService.setRedirect(null);
expect(authService.getRedirect(preferences.authType)).toBeNull(); expect(authService.getRedirect(preferences.providers)).toBeNull();
}); });
}); });
describe('when the setting is BPM', () => { describe('when the setting is BPM', () => {
beforeEach(() => { beforeEach(() => {
preferences.authType = 'BPM'; preferences.providers = 'BPM';
apiService.reset();
}); });
it('should require remember me set for BPM check', () => { it('should require remember me set for BPM check', () => {
@@ -426,26 +429,27 @@ describe('AuthenticationService', () => {
it('[BPM] should set/get redirectUrl when provider is BPM', () => { it('[BPM] should set/get redirectUrl when provider is BPM', () => {
authService.setRedirect({ provider: 'BPM', navigation: ['some-url'] }); authService.setRedirect({ provider: 'BPM', navigation: ['some-url'] });
expect(authService.getRedirect(preferences.authType)).toEqual(['some-url']); expect(authService.getRedirect(preferences.providers)).toEqual(['some-url']);
}); });
it('[BPM] should set/get redirectUrl when provider is ECM', () => { it('[BPM] should set/get redirectUrl when provider is ECM', () => {
authService.setRedirect({ provider: 'ECM', navigation: ['some-url'] }); authService.setRedirect({ provider: 'ECM', navigation: ['some-url'] });
expect(authService.getRedirect(preferences.authType)).toBeNull(); expect(authService.getRedirect(preferences.providers)).toBeNull();
}); });
it('[BPM] should return null as redirectUrl when redirectUrl field is not set', () => { it('[BPM] should return null as redirectUrl when redirectUrl field is not set', () => {
authService.setRedirect(null); authService.setRedirect(null);
expect(authService.getRedirect(preferences.authType)).toBeNull(); expect(authService.getRedirect(preferences.providers)).toBeNull();
}); });
}); });
describe('when the setting is both ECM and BPM ', () => { describe('when the setting is both ECM and BPM ', () => {
beforeEach(() => { beforeEach(() => {
preferences.authType = 'ALL'; preferences.providers = 'ALL';
apiService.reset();
}); });
it('[ALL] should return both ECM and BPM tickets after the login done', (done) => { it('[ALL] should return both ECM and BPM tickets after the login done', (done) => {
@@ -542,25 +546,25 @@ describe('AuthenticationService', () => {
it('[ALL] should set/get redirectUrl when provider is ALL', () => { it('[ALL] should set/get redirectUrl when provider is ALL', () => {
authService.setRedirect({ provider: 'ALL', navigation: ['some-url'] }); authService.setRedirect({ provider: 'ALL', navigation: ['some-url'] });
expect(authService.getRedirect(preferences.authType)).toEqual(['some-url']); expect(authService.getRedirect(preferences.providers)).toEqual(['some-url']);
}); });
it('[ALL] should set/get redirectUrl when provider is BPM', () => { it('[ALL] should set/get redirectUrl when provider is BPM', () => {
authService.setRedirect({ provider: 'BPM', navigation: ['some-url'] }); authService.setRedirect({ provider: 'BPM', navigation: ['some-url'] });
expect(authService.getRedirect(preferences.authType)).toEqual(['some-url']); expect(authService.getRedirect(preferences.providers)).toEqual(['some-url']);
}); });
it('[ALL] should set/get redirectUrl when provider is ECM', () => { it('[ALL] should set/get redirectUrl when provider is ECM', () => {
authService.setRedirect({ provider: 'ECM', navigation: ['some-url'] }); authService.setRedirect({ provider: 'ECM', navigation: ['some-url'] });
expect(authService.getRedirect(preferences.authType)).toEqual(['some-url']); expect(authService.getRedirect(preferences.providers)).toEqual(['some-url']);
}); });
it('[ALL] should return null as redirectUrl when redirectUrl field is not set', () => { it('[ALL] should return null as redirectUrl when redirectUrl field is not set', () => {
authService.setRedirect(null); authService.setRedirect(null);
expect(authService.getRedirect(preferences.authType)).toBeNull(); expect(authService.getRedirect(preferences.providers)).toBeNull();
}); });
}); });
+17 -15
View File
@@ -54,6 +54,10 @@ export class AuthenticationService {
return !!this.alfrescoApi.getInstance().isLoggedIn(); return !!this.alfrescoApi.getInstance().isLoggedIn();
} }
isOauth(): boolean {
return this.alfrescoApi.getInstance().isOauthConfiguration();
}
/** /**
* Logs the user in. * Logs the user in.
* @param username Username for the login * @param username Username for the login
@@ -63,19 +67,26 @@ export class AuthenticationService {
*/ */
login(username: string, password: string, rememberMe: boolean = false): Observable<{ type: string, ticket: any }> { login(username: string, password: string, rememberMe: boolean = false): Observable<{ type: string, ticket: any }> {
this.removeTicket(); this.removeTicket();
return Observable.fromPromise(this.callApiLogin(username, password)) return Observable.fromPromise(this.alfrescoApi.getInstance().login(username, password))
.map((response: any) => { .map((response: any) => {
this.saveRememberMeCookie(rememberMe); this.saveRememberMeCookie(rememberMe);
this.saveTickets(); this.saveTickets();
this.onLogin.next(response); this.onLogin.next(response);
return { return {
type: this.preferences.authType, type: this.preferences.providers,
ticket: response ticket: response
}; };
}) })
.catch(err => this.handleError(err)); .catch(err => this.handleError(err));
} }
/**
* Logs the user in with SSO
*/
ssoImplictiLogin() {
this.alfrescoApi.getInstance().implicitLogin();
}
/** /**
* Saves the "remember me" cookie as either a long-life cookie or a session cookie. * Saves the "remember me" cookie as either a long-life cookie or a session cookie.
* @param rememberMe Enables a long-life cookie * @param rememberMe Enables a long-life cookie
@@ -100,15 +111,6 @@ export class AuthenticationService {
return (this.cookie.getItem(REMEMBER_ME_COOKIE_KEY) === null) ? false : true; return (this.cookie.getItem(REMEMBER_ME_COOKIE_KEY) === null) ? false : true;
} }
/**
* Initialize the alfresco Api with user and password end call the login method
* @param username
* @param password
*/
private callApiLogin(username: string, password: string) {
return this.alfrescoApi.getInstance().login(username, password);
}
/** /**
* Logs the user out. * Logs the user out.
* @returns Response event called when logout is complete * @returns Response event called when logout is complete
@@ -213,7 +215,7 @@ export class AuthenticationService {
if (this.cookie.isEnabled() && !this.isRememberMeSet()) { if (this.cookie.isEnabled() && !this.isRememberMeSet()) {
return false; return false;
} }
return this.alfrescoApi.getInstance().ecmAuth && !!this.alfrescoApi.getInstance().ecmAuth.isLoggedIn(); return this.alfrescoApi.getInstance().isEcmLoggedIn();
} }
/** /**
@@ -224,7 +226,7 @@ export class AuthenticationService {
if (this.cookie.isEnabled() && !this.isRememberMeSet()) { if (this.cookie.isEnabled() && !this.isRememberMeSet()) {
return false; return false;
} }
return this.alfrescoApi.getInstance().bpmAuth && !!this.alfrescoApi.getInstance().bpmAuth.isLoggedIn(); return this.alfrescoApi.getInstance().isBpmLoggedIn();
} }
/** /**
@@ -232,7 +234,7 @@ export class AuthenticationService {
* @returns The ECM username * @returns The ECM username
*/ */
getEcmUsername(): string { getEcmUsername(): string {
return this.alfrescoApi.getInstance().ecmAuth.username; return this.alfrescoApi.getInstance().getEcmUsername();
} }
/** /**
@@ -240,7 +242,7 @@ export class AuthenticationService {
* @returns The BPM username * @returns The BPM username
*/ */
getBpmUsername(): string { getBpmUsername(): string {
return this.alfrescoApi.getInstance().bpmAuth.username; return this.alfrescoApi.getInstance().getBpmUsername();
} }
/** Sets the URL to redirect to after login. /** Sets the URL to redirect to after login.
+3 -1
View File
@@ -17,6 +17,7 @@
import { inject, TestBed } from '@angular/core/testing'; import { inject, TestBed } from '@angular/core/testing';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { Observable } from 'rxjs/Observable';
import { AppConfigService } from '../app-config/app-config.service'; import { AppConfigService } from '../app-config/app-config.service';
import { PageTitleService } from './page-title.service'; import { PageTitleService } from './page-title.service';
@@ -54,7 +55,8 @@ class TestConfig {
get: () => this.setup.applicationName, get: () => this.setup.applicationName,
load: () => { load: () => {
return Promise.resolve(); return Promise.resolve();
} },
onLoad: Observable.of({})
} }
}; };
+20 -14
View File
@@ -23,25 +23,18 @@ import { UploadService } from './upload.service';
import { AppConfigService } from '../app-config/app-config.service'; import { AppConfigService } from '../app-config/app-config.service';
import { AlfrescoApiService } from './alfresco-api.service'; import { AlfrescoApiService } from './alfresco-api.service';
import { StorageService } from './storage.service'; import { StorageService } from './storage.service';
import { AlfrescoApiServiceMock } from '../mock/alfresco-api.service.mock';
import { setupTestBed } from '../testing/setupTestBed';
import { CoreTestingModule } from '../testing/core.testing.module';
declare let jasmine: any; declare let jasmine: any;
describe('UploadService', () => { describe('UploadService', () => {
let service: UploadService; let service: UploadService;
beforeEach(async(() => { setupTestBed({
TestBed.configureTestingModule({ imports: [CoreTestingModule, AppConfigModule]
imports: [ });
AppConfigModule
],
providers: [
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock },
StorageService,
UploadService
]
}).compileComponents();
}));
beforeEach(() => { beforeEach(() => {
let appConfig: AppConfigService = TestBed.get(AppConfigService); let appConfig: AppConfigService = TestBed.get(AppConfigService);
@@ -53,6 +46,8 @@ describe('UploadService', () => {
}; };
service = TestBed.get(UploadService); service = TestBed.get(UploadService);
service.queue = [];
service.activeTask = null;
jasmine.Ajax.install(); jasmine.Ajax.install();
}); });
@@ -162,6 +157,8 @@ describe('UploadService', () => {
}); });
it('If newVersion is set, name should be a param', () => { it('If newVersion is set, name should be a param', () => {
let uploadFileSpy = spyOn(service.apiService.getInstance().upload, 'uploadFile').and.callThrough();
let emitter = new EventEmitter(); let emitter = new EventEmitter();
const filesFake = new FileModel(<File> { name: 'fake-name', size: 10 }, { const filesFake = new FileModel(<File> { name: 'fake-name', size: 10 }, {
@@ -170,7 +167,16 @@ describe('UploadService', () => {
service.addToQueue(filesFake); service.addToQueue(filesFake);
service.uploadFilesInTheQueue(emitter); service.uploadFilesInTheQueue(emitter);
expect(jasmine.Ajax.requests.mostRecent().params.has('name')).toBe(true); expect(uploadFileSpy).toHaveBeenCalledWith({
name: 'fake-name',
size: 10
}, undefined, undefined, null, {
renditions: 'doclib',
overwrite: true,
majorVersion: undefined,
comment: undefined,
name: 'fake-name'
});
}); });
it('should use custom root folder ID given to the service', (done) => { it('should use custom root folder ID given to the service', (done) => {
@@ -156,9 +156,9 @@ describe('UserPreferencesService', () => {
}); });
it('should stream only the selected attribute changes when using select', (done) => { it('should stream only the selected attribute changes when using select', (done) => {
preferences.disableCSRF = true; preferences.disableCSRF = false;
preferences.select(UserPreferenceValues.DisableCSRF).subscribe((disableCSRFFlag) => { preferences.select(UserPreferenceValues.DisableCSRF).subscribe((disableCSRFFlag) => {
expect(disableCSRFFlag).toBeTruthy(); expect(disableCSRFFlag).toBeFalsy();
done(); done();
}); });
}); });
+76 -15
View File
@@ -20,9 +20,9 @@ import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { AppConfigService } from '../app-config/app-config.service'; import { AppConfigService } from '../app-config/app-config.service';
import { AlfrescoApiService } from './alfresco-api.service';
import { StorageService } from './storage.service'; import { StorageService } from './storage.service';
import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/distinctUntilChanged';
import { OauthConfigModel } from '../models/oauth-config.model';
export enum UserPreferenceValues { export enum UserPreferenceValues {
PaginationSize = 'PAGINATION_SIZE', PaginationSize = 'PAGINATION_SIZE',
@@ -50,17 +50,14 @@ export class UserPreferencesService {
* @deprecated we are grouping every value changed on the user preference in a single stream : userPreferenceValue$ * @deprecated we are grouping every value changed on the user preference in a single stream : userPreferenceValue$
*/ */
locale$: Observable<string>; locale$: Observable<string>;
private localeSubject: BehaviorSubject<string> ; private localeSubject: BehaviorSubject<string>;
private onChangeSubject: BehaviorSubject<any>; private onChangeSubject: BehaviorSubject<any>;
onChange: Observable<any>; onChange: Observable<any>;
constructor( constructor(public translate: TranslateService,
public translate: TranslateService, private appConfig: AppConfigService,
private appConfig: AppConfigService, private storage: StorageService) {
private storage: StorageService,
private apiService: AlfrescoApiService
) {
this.appConfig.onLoad.subscribe(this.initUserPreferenceStatus.bind(this)); this.appConfig.onLoad.subscribe(this.initUserPreferenceStatus.bind(this));
this.localeSubject = new BehaviorSubject(this.userPreferenceStatus[UserPreferenceValues.Locale]); this.localeSubject = new BehaviorSubject(this.userPreferenceStatus[UserPreferenceValues.Locale]);
this.locale$ = this.localeSubject.asObservable(); this.locale$ = this.localeSubject.asObservable();
@@ -106,7 +103,9 @@ export class UserPreferencesService {
* @param value New value for the property * @param value New value for the property
*/ */
set(property: string, value: any) { set(property: string, value: any) {
if (!property) { return; } if (!property) {
return;
}
this.storage.setItem( this.storage.setItem(
this.getPropertyKey(property), this.getPropertyKey(property),
value value
@@ -149,19 +148,29 @@ export class UserPreferencesService {
} }
/** Authorization type (can be "ECM", "BPM" or "ALL"). */ /** Authorization type (can be "ECM", "BPM" or "ALL"). */
set authType(value: string) { /** @deprecated in 2.4.0 */
this.storage.setItem('AUTH_TYPE', value); set authType(authType: string) {
this.apiService.reset(); let storedAuthType = this.storage.getItem('AUTH_TYPE');
if (authType !== storedAuthType) {
this.storage.setItem('AUTH_TYPE', authType);
}
} }
/** @deprecated in 2.4.0 */
get authType(): string { get authType(): string {
return this.storage.getItem('AUTH_TYPE') || 'ALL'; return this.storage.getItem('AUTH_TYPE') || 'ALL';
} }
/** Prevents the CSRF Token from being submitted if true. Only valid for Process Services. */ /** Prevents the CSRF Token from being submitted if true. Only valid for Process Services. */
set disableCSRF(value: boolean) { set disableCSRF(csrf: boolean) {
this.set('DISABLE_CSRF', value); let storedCSRF = this.storage.getItem('DISABLE_CSRF');
this.apiService.reset();
if (csrf !== null && csrf !== undefined) {
if (csrf.toString() === storedCSRF) {
this.set('DISABLE_CSRF', csrf);
}
}
} }
get disableCSRF(): boolean { get disableCSRF(): boolean {
@@ -196,4 +205,56 @@ export class UserPreferencesService {
return this.appConfig.get<string>('locale') || this.translate.getBrowserLang() || 'en'; return this.appConfig.get<string>('locale') || this.translate.getBrowserLang() || 'en';
} }
get providers(): string {
if (this.storage.hasItem('providers')) {
return this.storage.getItem('providers');
} else {
return this.appConfig.get('providers', 'ECM');
}
}
set providers(providers: string) {
this.storage.setItem('providers', providers);
}
get bpmHost(): string {
if (this.storage.hasItem('bpmHost')) {
return this.storage.getItem('bpmHost');
} else {
return this.appConfig.get('bpmHost');
}
}
set bpmHost(bpmHost: string) {
this.storage.setItem('bpmHost', bpmHost);
}
get ecmHost(): string {
if (this.storage.hasItem('ecmHost')) {
return this.storage.getItem('ecmHost');
} else {
return this.appConfig.get('ecmHost');
}
}
set ecmHost(ecmHost: string) {
this.storage.setItem('ecmHost', ecmHost);
}
get oauthConfig(): OauthConfigModel {
if (this.storage.hasItem('oauthConfig')) {
return JSON.parse(this.storage.getItem('oauthConfig'));
} else {
return this.appConfig.get<OauthConfigModel>('oauth2');
}
}
set oauthConfig(oauthConfig: OauthConfigModel) {
this.storage.setItem('oauthConfig', JSON.stringify(oauthConfig));
}
get sso(): boolean {
return this.providers === 'OAUTH' && this.oauthConfig.implicitFlow;
}
} }
+86 -59
View File
@@ -1,70 +1,97 @@
<div class="adf-setting-container"> <div class="adf-setting-container">
<div class="adf-setting-card-padding"></div>
<mat-toolbar color="primary" class="adf-setting-toolbar"> <mat-toolbar color="primary" class="adf-setting-toolbar">
<h3>{{'CORE.HOST_SETTINGS.TITLE' | translate}}</h3> <h3>{{'CORE.HOST_SETTINGS.TITLE' | translate}}</h3>
</mat-toolbar> </mat-toolbar>
<mat-card class="adf-setting-card"> <mat-card class="adf-setting-card">
<div *ngIf="providers==='ALL' || providers==='ECM'"> <form id="host-form" [formGroup]="form" (submit)="onSubmit(form.value)">
<mat-card-header> <mat-form-field>
<mat-card-subtitle>{{'CORE.HOST_SETTINGS.CS-HOST' | translate }}</mat-card-subtitle> <mat-select placeholder="Provider" [formControl]="providers">
</mat-card-header> <mat-option *ngFor="let provider of providersValues" [value]="provider.value">
<mat-card-content> {{ provider.title }}
<mat-form-field class="full-width"> </mat-option>
<mat-icon class="adf-CORE.HOST_SETTINGS-link-icon" matPrefix>link</mat-icon> </mat-select>
<input matInput </mat-form-field>
[formControl]="urlFormControlEcm"
data-automation-id="ecmHost"
type="text"
(change)="onChangeECMHost($event)"
tabindex="2"
id="ecmHost"
value="{{ecmHost}}"
placeholder="http(s)://host|ip:port(/path)">
<mat-error *ngIf="urlFormControlEcm.hasError('pattern')">
{{ 'CORE.HOST_SETTINGS.NOT_VALID'| translate }}
</mat-error>
</mat-form-field>
<p>
</mat-card-content>
</div>
<p>
<div *ngIf="providers==='ALL' || providers==='BPM'">
<mat-card-header>
<mat-card-subtitle>{{'CORE.HOST_SETTINGS.BP-HOST' | translate }}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<mat-form-field class="full-width"> <ng-container *ngIf="isALL() || isECM()">
<mat-icon class="adf-CORE.HOST_SETTINGS-link-icon" matPrefix>link</mat-icon> <mat-card-content>
<input matInput <mat-form-field class="full-width" floatLabel="{{'CORE.HOST_SETTINGS.CS-HOST' | translate }}" >
[formControl]="urlFormControlBpm" <mat-label>{{'CORE.HOST_SETTINGS.CS-HOST' | translate }}</mat-label>
data-automation-id="bpmHost" <input matInput [formControl]="ecmHost" data-automation-id="ecmHost" type="text" tabindex="2" id="ecmHost" placeholder="http(s)://host|ip:port(/path)">
type="text" <mat-error *ngIf="ecmHost.hasError('pattern')">
(change)="onChangeBPMHost($event)" {{ 'CORE.HOST_SETTINGS.NOT_VALID'| translate }}
tabindex="2" </mat-error>
id="bpmHost" <mat-error *ngIf="ecmHost.hasError('required')">
value="{{bpmHost}}" {{ 'CORE.HOST_SETTINGS.REQUIRED'| translate }}
placeholder="http(s)://host|ip:port(/path)"> </mat-error>
<mat-error *ngIf="urlFormControlBpm.hasError('pattern')"> </mat-form-field>
{{ 'CORE.HOST_SETTINGS.NOT_VALID'| translate }} <p>
</mat-error> </mat-card-content>
</mat-form-field> </ng-container>
</mat-card-content>
</div>
<mat-card-actions class="adf-CORE.HOST_SETTINGS-actions">
<button mat-button onclick="window.history.back()" color="primary"> <ng-container *ngIf="isALL() || isOAUTH() || isBPM()">
{{'CORE.HOST_SETTINGS.BACK' | translate }} <mat-card-content>
</button> <mat-form-field class="full-width" floatLabel="{{'CORE.HOST_SETTINGS.BP-HOST' | translate }}">
<mat-label>{{'CORE.HOST_SETTINGS.BP-HOST' | translate }}</mat-label>
<input matInput [formControl]="bpmHost" data-automation-id="bpmHost" type="text" tabindex="2" id="bpmHost" placeholder="http(s)://host|ip:port(/path)">
<mat-error *ngIf="bpmHost.hasError('pattern')">
{{ 'CORE.HOST_SETTINGS.NOT_VALID'| translate }}
</mat-error>
<mat-error *ngIf="bpmHost.hasError('required')">
{{ 'CORE.HOST_SETTINGS.REQUIRED'| translate }}
</mat-error>
</mat-form-field>
<ng-container *ngIf="isOAUTH()">
<div formGroupName="oauthConfig">
<mat-form-field class="full-width" floatLabel="Auth Host">
<mat-label>Auth Host</mat-label>
<input matInput name="host" id="oauthHost" formControlName="host" placeholder="http(s)://host|ip:port(/path)" >
<mat-error *ngIf="host.hasError('pattern')">
{{ 'CORE.HOST_SETTINGS.NOT_VALID'| translate }}
</mat-error>
<mat-error *ngIf="host.hasError('required')">
{{ 'CORE.HOST_SETTINGS.REQUIRED'| translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="full-width" floatLabel="Client Id">
<mat-label>{{ 'CORE.HOST_SETTINGS.CLIENT'| translate }}d</mat-label>
<input matInput name="clientId" id="clientId" formControlName="clientId" placeholder="Client Id">
<mat-error *ngIf="clientId.hasError('required')">
{{ 'CORE.HOST_SETTINGS.REQUIRED'| translate }}
</mat-error>
</mat-form-field>
<button mat-raised-button (click)="save($event)" <mat-form-field class="full-width" floatLabel="Scope">
[disabled]="urlFormControlBpm.hasError('pattern') || urlFormControlEcm.hasError('pattern')" <mat-label>{{ 'CORE.HOST_SETTINGS.SCOPE'| translate }}</mat-label>
color="primary"> <input matInput name="{{ 'CORE.HOST_SETTINGS.SCOPE'| translate }}" formControlName="scope" placeholder="Scope Id">
{{'CORE.HOST_SETTINGS.APPLY' | translate }} <mat-error *ngIf="scope.hasError('required')">
</button> {{ 'CORE.HOST_SETTINGS.REQUIRED'| translate }}
</mat-error>
</mat-form-field>
</mat-card-actions> <label for="silentLogin">{{ 'CORE.HOST_SETTINGS.SILENT'| translate }}</label>
<mat-slide-toggle class="full-width" name="silentLogin" [color]="'primary'" formControlName="silentLogin">
</mat-slide-toggle>
<mat-form-field class="full-width" floatLabel="Redirect Uri">
<mat-label>{{ 'CORE.HOST_SETTINGS.REDIRECT'| translate }}</mat-label>
<input matInput placeholder="{{ 'CORE.HOST_SETTINGS.REDIRECT'| translate }}" name="redirectUri" formControlName="redirectUri">
<mat-error *ngIf="redirectUri.hasError('required')">
{{ 'CORE.HOST_SETTINGS.REQUIRED'| translate }}
</mat-error>
</mat-form-field>
</div>
</ng-container>
</mat-card-content>
</ng-container>
<mat-card-actions class="adf-actions">
<button mat-button (click)="onCancel()" color="primary">
{{'CORE.HOST_SETTINGS.BACK' | translate }}
</button>
<button type="submit" id="host-button" tabindex="4" class="adf-login-button" mat-raised-button color="primary" data-automation-id="host-button"
[disabled]="!form.valid">
{{'CORE.HOST_SETTINGS.APPLY' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card> </mat-card>
<div class="adf-setting-card-padding"></div>
</div> </div>
@@ -5,12 +5,10 @@
height: 100%; height: 100%;
align-items: center; align-items: center;
.adf-setting-toolbar {
width: 600px;
}
.adf-setting-container { .adf-setting-container {
width: 800px;
display: table; display: table;
margin: 0 auto;
border-collapse: collapse; border-collapse: collapse;
border-spacing: 0; border-spacing: 0;
} }
+231 -62
View File
@@ -19,11 +19,14 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HostSettingsComponent } from './host-settings.component'; import { HostSettingsComponent } from './host-settings.component';
import { setupTestBed } from '../testing/setupTestBed'; import { setupTestBed } from '../testing/setupTestBed';
import { CoreTestingModule } from '../testing/core.testing.module'; import { CoreTestingModule } from '../testing/core.testing.module';
import { UserPreferencesService } from '../services/user-preferences.service';
describe('HostSettingsComponent', () => { describe('HostSettingsComponent', () => {
let fixture: ComponentFixture<HostSettingsComponent>; let fixture: ComponentFixture<HostSettingsComponent>;
let component: HostSettingsComponent; let component: HostSettingsComponent;
let userPreferences: UserPreferencesService;
let element: any;
setupTestBed({ setupTestBed({
imports: [CoreTestingModule] imports: [CoreTestingModule]
@@ -32,96 +35,262 @@ describe('HostSettingsComponent', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(HostSettingsComponent); fixture = TestBed.createComponent(HostSettingsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
userPreferences = TestBed.get(UserPreferencesService);
element = fixture.nativeElement;
}); });
afterEach(() => { afterEach(() => {
fixture.destroy(); fixture.destroy();
}); });
it('should emit an error when the ECM url inserted is wrong', (done) => { describe('BPM ', () => {
fixture.detectChanges();
component.error.subscribe((message: string) => { let ecmUrlInput;
expect(message).toEqual('CORE.HOST_SETTING.CS_URL_ERROR'); let bpmUrlInput;
done();
beforeEach(() => {
userPreferences.providers = 'BPM';
fixture.detectChanges();
bpmUrlInput = element.querySelector('#bpmHost');
ecmUrlInput = element.querySelector('#ecmHost');
}); });
const ecmUrlInput = fixture.nativeElement.querySelector('#ecmHost'); afterEach(() => {
ecmUrlInput.value = 'wrong_url'; fixture.destroy();
const event: any = {};
event.target = ecmUrlInput;
component.onChangeECMHost(event);
});
it('should emit ecmHostChange when the ECM url inserted is correct', (done) => {
fixture.detectChanges();
const url = 'http://localhost:9999/ecm';
component.ecmHostChange.subscribe((message: string) => {
expect(message).toEqual(url);
done();
}); });
const ecmUrlInput = fixture.nativeElement.querySelector('#ecmHost'); it('should have a valid form when the url inserted is correct', (done) => {
ecmUrlInput.value = url; const url = 'http://localhost:9999/bpm';
const event: any = {}; component.form.statusChanges.subscribe((status: string) => {
event.target = ecmUrlInput; expect(status).toEqual('VALID');
component.onChangeECMHost(event); done();
}); });
it('should emit an error when the BPM url inserted is wrong', (done) => { component.form.valueChanges.subscribe((values) => {
fixture.detectChanges(); expect(values.bpmHost).toEqual(url);
});
component.error.subscribe((message: string) => { bpmUrlInput.value = url;
expect(message).toEqual('CORE.HOST_SETTING.PS_URL_ERROR'); bpmUrlInput.dispatchEvent(new Event('input'));
done();
}); });
const bpmUrlInput: any = fixture.nativeElement.querySelector('#bpmHost'); it('should have an invalid form when the inserted is wrong', (done) => {
bpmUrlInput.value = 'wrong_url'; const url = 'wrong';
const event: any = {}; component.form.statusChanges.subscribe((status: string) => {
event.target = bpmUrlInput; expect(status).toEqual('INVALID');
component.onChangeBPMHost(event); expect(component.bpmHost.hasError('pattern')).toBeTruthy();
}); done();
});
it('should emit bpmHostChange when the BPM url inserted is correct', (done) => { bpmUrlInput.value = url;
fixture.detectChanges(); bpmUrlInput.dispatchEvent(new Event('input'));
const url = 'http://localhost:9999/bpm';
component.ecmHostChange.subscribe((message: string) => {
expect(message).toEqual(url);
done();
}); });
const ecmUrlInput = fixture.nativeElement.querySelector('#bpmHost'); it('should not render the ECM url config if setting provider is BPM', () => {
ecmUrlInput.value = url; expect(ecmUrlInput).toEqual(null);
expect(bpmUrlInput).toBeDefined();
});
const event: any = {};
event.target = ecmUrlInput;
component.onChangeECMHost(event);
}); });
it('should not render the ECM url config if setting provider is BPM', () => { describe('ECM ', () => {
component.providers = 'BPM';
fixture.detectChanges(); let ecmUrlInput;
let bpmUrlInput;
const bpmUrlInput = fixture.nativeElement.querySelector('#bpmHost'); beforeEach(() => {
const ecmUrlInput = fixture.nativeElement.querySelector('#ecmHost'); userPreferences.providers = 'ECM';
expect(ecmUrlInput).toEqual(null); fixture.detectChanges();
expect(bpmUrlInput).toBeDefined(); bpmUrlInput = element.querySelector('#bpmHost');
ecmUrlInput = element.querySelector('#ecmHost');
});
afterEach(() => {
fixture.destroy();
});
it('should have a valid form when the url inserted is correct', (done) => {
const url = 'http://localhost:9999/ecm';
component.form.statusChanges.subscribe((status: string) => {
expect(status).toEqual('VALID');
done();
});
ecmUrlInput.value = url;
ecmUrlInput.dispatchEvent(new Event('input'));
});
it('should have an invalid form when the url inserted is wrong', (done) => {
const url = 'wrong';
component.form.statusChanges.subscribe((status: string) => {
expect(status).toEqual('INVALID');
expect(component.ecmHost.hasError('pattern')).toBeTruthy();
done();
});
ecmUrlInput.value = url;
ecmUrlInput.dispatchEvent(new Event('input'));
});
it('should not render the BPM url config if setting provider is BPM', () => {
expect(bpmUrlInput).toEqual(null);
expect(ecmUrlInput).toBeDefined();
});
}); });
it('should hide the BPM url config if setting provider is ECM', () => { describe('ALL ', () => {
component.providers = 'ECM';
fixture.detectChanges(); let ecmUrlInput;
let bpmUrlInput;
beforeEach(() => {
userPreferences.providers = 'ALL';
fixture.detectChanges();
bpmUrlInput = element.querySelector('#bpmHost');
ecmUrlInput = element.querySelector('#ecmHost');
});
afterEach(() => {
fixture.destroy();
});
it('should have a valid form when the BPM and ECM url inserted are correct', (done) => {
const urlEcm = 'http://localhost:9999/ecm';
const urlBpm = 'http://localhost:9999/bpm';
component.form.statusChanges.subscribe((status: string) => {
expect(status).toEqual('VALID');
done();
});
ecmUrlInput.value = urlEcm;
bpmUrlInput.value = urlBpm;
ecmUrlInput.dispatchEvent(new Event('input'));
});
it('should have an invalid form when one of the ECM url inserted is wrong', (done) => {
const url = 'wrong';
component.form.statusChanges.subscribe((status: string) => {
expect(status).toEqual('INVALID');
expect(component.ecmHost.hasError('pattern')).toBeTruthy();
done();
});
ecmUrlInput.value = url;
ecmUrlInput.dispatchEvent(new Event('input'));
});
it('should have an invalid form when one of the BPM url inserted is wrong', (done) => {
const url = 'wrong';
component.form.statusChanges.subscribe((status: string) => {
expect(status).toEqual('INVALID');
expect(component.bpmHost.hasError('pattern')).toBeTruthy();
done();
});
bpmUrlInput.value = url;
bpmUrlInput.dispatchEvent(new Event('input'));
});
it('should have an invalid form when both BPM and ECM url inserted are wrong', (done) => {
const url = 'wrong';
component.form.statusChanges.subscribe((status: string) => {
expect(status).toEqual('INVALID');
expect(component.bpmHost.hasError('pattern')).toBeTruthy();
done();
});
bpmUrlInput.value = url;
ecmUrlInput.value = url;
bpmUrlInput.dispatchEvent(new Event('input'));
});
const ecmUrlInput = fixture.nativeElement.querySelector('#ecmHost');
const bpmUrlInput = fixture.nativeElement.querySelector('#bpmHost');
expect(bpmUrlInput).toEqual(null);
expect(ecmUrlInput).toBeDefined();
}); });
describe('OAUTH ', () => {
let ecmUrlInput;
let bpmUrlInput;
let oauthHostUrlInput;
let clientIdInput;
beforeEach(() => {
userPreferences.providers = 'OAUTH';
userPreferences.oauthConfig = {
host: 'http://localhost:6543',
redirectUri: '/',
silentLogin: false,
implicitFlow: true,
clientId: 'activiti',
scope: 'openid',
secret: ''
};
fixture.detectChanges();
bpmUrlInput = element.querySelector('#bpmHost');
oauthHostUrlInput = element.querySelector('#oauthHost');
clientIdInput = element.querySelector('#clientId');
});
afterEach(() => {
fixture.destroy();
});
it('should have a valid form when the BPM is correct', (done) => {
const urlBpm = 'http://localhost:9999/bpm';
component.form.statusChanges.subscribe((status: string) => {
expect(status).toEqual('VALID');
done();
});
bpmUrlInput.value = urlBpm;
bpmUrlInput.dispatchEvent(new Event('input'));
});
it('should have an invalid form when the url inserted is wrong', (done) => {
const url = 'wrong';
component.form.statusChanges.subscribe((status: string) => {
expect(status).toEqual('INVALID');
expect(component.bpmHost.hasError('pattern')).toBeTruthy();
done();
});
bpmUrlInput.value = url;
bpmUrlInput.dispatchEvent(new Event('input'));
});
it('should have an invalid form when the host is wrong', (done) => {
const hostUrl = 'wrong';
component.form.statusChanges.subscribe((status: string) => {
expect(status).toEqual('INVALID');
expect(component.host.hasError('pattern')).toBeTruthy();
done();
});
oauthHostUrlInput.value = hostUrl;
oauthHostUrlInput.dispatchEvent(new Event('input'));
});
it('should have a required clientId an invalid form when the clientId is missing', (done) => {
component.form.statusChanges.subscribe((status: string) => {
expect(status).toEqual('INVALID');
expect(component.clientId.hasError('required')).toBeTruthy();
done();
});
clientIdInput.value = '';
clientIdInput.dispatchEvent(new Event('input'));
});
});
}); });
+126 -51
View File
@@ -15,12 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; import { Component, EventEmitter, Output, ViewEncapsulation, OnInit } from '@angular/core';
import { FormControl, Validators } from '@angular/forms'; import { Validators, FormGroup, FormBuilder, AbstractControl } from '@angular/forms';
import { LogService } from '../services/log.service'; import { UserPreferencesService } from '../services';
import { SettingsService } from '../services/settings.service';
import { StorageService } from '../services/storage.service';
import { TranslationService } from '../services/translation.service';
@Component({ @Component({
selector: 'adf-host-settings', selector: 'adf-host-settings',
@@ -31,20 +28,18 @@ import { TranslationService } from '../services/translation.service';
styleUrls: ['host-settings.component.scss'], styleUrls: ['host-settings.component.scss'],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class HostSettingsComponent { export class HostSettingsComponent implements OnInit {
HOST_REGEX: string = '^(http|https):\/\/.*[^/]$'; HOST_REGEX: string = '^(http|https):\/\/.*[^/]$';
ecmHost: string; providersValues = [
ecmHostTmp: string; { title: 'ECM and BPM', value: 'ALL' },
bpmHost: string; { title: 'BPM', value: 'BPM' },
bpmHostTmp: string; { title: 'ECM', value: 'ECM' },
urlFormControlEcm = new FormControl('', [Validators.required, Validators.pattern(this.HOST_REGEX)]); { title: 'OAUTH', value: 'OAUTH' }
urlFormControlBpm = new FormControl('', [Validators.required, Validators.pattern(this.HOST_REGEX)]); ];
/** Determines which configurations are shown. Possible valid values are "ECM", "BPM" or "ALL". */ form: FormGroup;
@Input()
providers: string = 'ALL';
/** Emitted when the URL is invalid. */ /** Emitted when the URL is invalid. */
@Output() @Output()
@@ -54,56 +49,136 @@ export class HostSettingsComponent {
@Output() @Output()
ecmHostChange = new EventEmitter<string>(); ecmHostChange = new EventEmitter<string>();
@Output()
cancel = new EventEmitter<boolean>();
@Output()
success = new EventEmitter<boolean>();
/** Emitted when the bpm host URL is changed. */ /** Emitted when the bpm host URL is changed. */
@Output() @Output()
bpmHostChange = new EventEmitter<string>(); bpmHostChange = new EventEmitter<string>();
constructor(private settingsService: SettingsService, constructor(private fb: FormBuilder,
private storage: StorageService, private userPreference: UserPreferencesService) {
private logService: LogService,
private translationService: TranslationService) {
this.ecmHostTmp = this.ecmHost = storage.getItem('ecmHost') || this.settingsService.ecmHost;
this.bpmHostTmp = this.bpmHost = storage.getItem('bpmHost') || this.settingsService.bpmHost;
} }
public onChangeECMHost(event: any): void { ngOnInit() {
let value = (<HTMLInputElement> event.target).value.trim();
if (value && this.isValidUrl(value)) { let providerSelected = this.userPreference.providers;
this.logService.info(`ECM host: ${value}`);
this.ecmHostTmp = value; this.form = this.fb.group({
this.ecmHostChange.emit(value); providers: [providerSelected, Validators.required],
} else { ecmHost: [this.userPreference.ecmHost, [Validators.required, Validators.pattern(this.HOST_REGEX)]],
this.translationService.get('CORE.HOST_SETTING.CS_URL_ERROR').subscribe((message) => { bpmHost: [this.userPreference.bpmHost, [Validators.required, Validators.pattern(this.HOST_REGEX)]]
this.error.emit(message); });
const oAuthConfig = this.userPreference.oauthConfig;
if (oAuthConfig) {
const oauthGroup = this.fb.group( {
host: [oAuthConfig.host, [Validators.required, Validators.pattern(this.HOST_REGEX)]],
clientId: [oAuthConfig.clientId, Validators.required],
redirectUri: [oAuthConfig.redirectUri, Validators.required],
scope: [oAuthConfig.scope, Validators.required],
secret: oAuthConfig.secret,
silentLogin: oAuthConfig.silentLogin,
implicitFlow: oAuthConfig.implicitFlow
}); });
this.form.addControl('oauthConfig', oauthGroup);
} }
} }
public onChangeBPMHost(event: any): void { onCancel() {
let value = (<HTMLInputElement> event.target).value.trim(); this.cancel.emit(true);
if (value && this.isValidUrl(value)) {
this.logService.info(`BPM host: ${value}`);
this.bpmHostTmp = value;
this.bpmHostChange.emit(value);
} else {
this.translationService.get('CORE.HOST_SETTING.PS_URL_ERROR').subscribe((message) => {
this.error.emit(message);
});
}
} }
public save(event: KeyboardEvent): void { onSubmit(values: any) {
if (this.bpmHost !== this.bpmHostTmp) { this.userPreference.providers = values.providers;
this.storage.setItem(`bpmHost`, this.bpmHostTmp); if (this.isBPM()) {
this.saveBPMValues(values);
} else if (this.isECM()) {
this.saveECMValues(values);
} else if (this.isALL()) {
this.saveECMValues(values);
this.saveBPMValues(values);
} else if (this.isOAUTH()) {
this.saveOAuthValues(values);
} }
if (this.ecmHost !== this.ecmHostTmp) { this.success.emit(true);
this.storage.setItem(`ecmHost`, this.ecmHostTmp);
}
window.location.href = '/';
} }
isValidUrl(url: string) { saveOAuthValues(values: any) {
return /^(http|https):\/\/.*/.test(url); this.userPreference.oauthConfig = values.oauthConfig;
this.userPreference.bpmHost = values.bpmHost;
}
saveBPMValues(values: any) {
this.userPreference.bpmHost = values.bpmHost;
}
saveECMValues(values: any) {
this.userPreference.ecmHost = values.ecmHost;
}
isBPM(): boolean {
return this.providers.value === 'BPM';
}
isECM(): boolean {
return this.providers.value === 'ECM';
}
isALL(): boolean {
return this.providers.value === 'ALL';
}
isOAUTH(): boolean {
return this.providers.value === 'OAUTH';
}
get providers(): AbstractControl {
return this.form.get('providers');
}
get bpmHost(): AbstractControl {
return this.form.get('bpmHost');
}
get ecmHost(): AbstractControl {
return this.form.get('ecmHost');
}
get host(): AbstractControl {
return this.oauthConfig.get('host');
}
get clientId(): AbstractControl {
return this.oauthConfig.get('clientId');
}
get scope(): AbstractControl {
return this.oauthConfig.get('scope');
}
get secretId(): AbstractControl {
return this.oauthConfig.get('secretId');
}
get implicitFlow(): AbstractControl {
return this.oauthConfig.get('implicitFlow');
}
get silentLogin(): AbstractControl {
return this.oauthConfig.get('silentLogin');
}
get redirectUri(): AbstractControl {
return this.oauthConfig.get('redirectUri');
}
get oauthConfig(): AbstractControl {
return this.form.get('oauthConfig');
} }
} }
+2
View File
@@ -36,6 +36,8 @@ export const setupTestBed = (moduleDef: TestModuleMetadata) => {
preventAngularFromResetting(); preventAngularFromResetting();
TestBed.configureTestingModule(moduleDef); TestBed.configureTestingModule(moduleDef);
await TestBed.compileComponents(); await TestBed.compileComponents();
localStorage.clear();
sessionStorage.clear();
// prevent Angular from resetting testing module // prevent Angular from resetting testing module
TestBed.resetTestingModule = () => TestBed; TestBed.resetTestingModule = () => TestBed;
@@ -15,11 +15,11 @@
* limitations under the License. * limitations under the License.
*/ */
import { async } from '@angular/core/testing'; import { async, TestBed } from '@angular/core/testing';
import { mockError, fakeProcessFilters } from '../../mock'; import { mockError, fakeProcessFilters } from '../../mock';
import { FilterProcessRepresentationModel } from '../models/filter-process.model'; import { FilterProcessRepresentationModel } from '../models/filter-process.model';
import { ProcessFilterService } from './process-filter.service'; import { ProcessFilterService } from './process-filter.service';
import { AlfrescoApiServiceMock, AlfrescoApiService, AppConfigService, StorageService } from '@alfresco/adf-core'; import { AlfrescoApiServiceMock, AlfrescoApiService, AppConfigService, StorageService, UserPreferencesService, setupTestBed, CoreModule } from '@alfresco/adf-core';
declare let jasmine: any; declare let jasmine: any;
@@ -28,9 +28,17 @@ describe('Process filter', () => {
let service: ProcessFilterService; let service: ProcessFilterService;
let apiService: AlfrescoApiService; let apiService: AlfrescoApiService;
let alfrescoApi: any; let alfrescoApi: any;
let userPreferences: UserPreferencesService;
setupTestBed({
imports: [
CoreModule.forRoot()
]
});
beforeEach(() => { beforeEach(() => {
apiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService() ); userPreferences = TestBed.get(UserPreferencesService);
apiService = new AlfrescoApiServiceMock(new AppConfigService(null), userPreferences, new StorageService() );
service = new ProcessFilterService(apiService); service = new ProcessFilterService(apiService);
alfrescoApi = apiService.getInstance(); alfrescoApi = apiService.getInstance();
}); });
@@ -15,13 +15,13 @@
* limitations under the License. * limitations under the License.
*/ */
import { async } from '@angular/core/testing'; import { async, TestBed } from '@angular/core/testing';
import { exampleProcess, fakeProcessInstances } from '../../mock'; import { exampleProcess, fakeProcessInstances } from '../../mock';
import { mockError, fakeProcessDef, fakeTasksList } from '../../mock'; import { mockError, fakeProcessDef, fakeTasksList } from '../../mock';
import { ProcessFilterParamRepresentationModel } from '../models/filter-process.model'; import { ProcessFilterParamRepresentationModel } from '../models/filter-process.model';
import { ProcessInstanceVariable } from '../models/process-instance-variable.model'; import { ProcessInstanceVariable } from '../models/process-instance-variable.model';
import { ProcessService } from './process.service'; import { ProcessService } from './process.service';
import { AlfrescoApiService, AlfrescoApiServiceMock, AppConfigService, StorageService } from '@alfresco/adf-core'; import { AlfrescoApiService, AlfrescoApiServiceMock, AppConfigService, StorageService, UserPreferencesService, setupTestBed, CoreModule } from '@alfresco/adf-core';
declare let moment: any; declare let moment: any;
@@ -30,9 +30,17 @@ describe('ProcessService', () => {
let service: ProcessService; let service: ProcessService;
let apiService: AlfrescoApiService; let apiService: AlfrescoApiService;
let alfrescoApi: any; let alfrescoApi: any;
let userPreferences: UserPreferencesService;
setupTestBed({
imports: [
CoreModule.forRoot()
]
});
beforeEach(() => { beforeEach(() => {
apiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService() ); userPreferences = TestBed.get(UserPreferencesService);
apiService = new AlfrescoApiServiceMock(new AppConfigService(null), userPreferences, new StorageService() );
service = new ProcessService(apiService); service = new ProcessService(apiService);
alfrescoApi = apiService.getInstance(); alfrescoApi = apiService.getInstance();
}); });
@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { async } from '@angular/core/testing'; import { async, TestBed } from '@angular/core/testing';
import { import {
fakeAppFilter, fakeAppFilter,
fakeAppPromise, fakeAppPromise,
@@ -23,15 +23,24 @@ import {
} from '../../mock'; } from '../../mock';
import { FilterRepresentationModel } from '../models/filter.model'; import { FilterRepresentationModel } from '../models/filter.model';
import { TaskFilterService } from './task-filter.service'; import { TaskFilterService } from './task-filter.service';
import { AlfrescoApiServiceMock, LogService, AppConfigService, StorageService } from '@alfresco/adf-core'; import { AlfrescoApiServiceMock, LogService, AppConfigService, StorageService, setupTestBed, CoreModule, UserPreferencesService } from '@alfresco/adf-core';
declare let jasmine: any; declare let jasmine: any;
describe('Activiti Task filter Service', () => { describe('Activiti Task filter Service', () => {
let service: TaskFilterService; let service: TaskFilterService;
let userPreferences: UserPreferencesService;
setupTestBed({
imports: [
CoreModule.forRoot()
]
});
beforeEach(async(() => { beforeEach(async(() => {
service = new TaskFilterService(new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService()), new LogService(new AppConfigService(null))); userPreferences = TestBed.get(UserPreferencesService);
service = new TaskFilterService(new AlfrescoApiServiceMock(new AppConfigService(null), userPreferences, new StorageService()), new LogService(new AppConfigService(null)));
jasmine.Ajax.install(); jasmine.Ajax.install();
})); }));
@@ -15,8 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { async } from '@angular/core/testing'; import { async, TestBed } from '@angular/core/testing';
import { UserProcessModel } from '@alfresco/adf-core'; import { UserProcessModel, setupTestBed, CoreModule } from '@alfresco/adf-core';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { import {
fakeCompletedTaskList, fakeCompletedTaskList,
@@ -35,16 +35,24 @@ import {
import { FilterRepresentationModel, TaskQueryRequestRepresentationModel } from '../models/filter.model'; import { FilterRepresentationModel, TaskQueryRequestRepresentationModel } from '../models/filter.model';
import { TaskDetailsModel } from '../models/task-details.model'; import { TaskDetailsModel } from '../models/task-details.model';
import { TaskListService } from './tasklist.service'; import { TaskListService } from './tasklist.service';
import { AlfrescoApiServiceMock, LogService, AppConfigService, StorageService } from '@alfresco/adf-core'; import { AlfrescoApiServiceMock, LogService, AppConfigService, StorageService, UserPreferencesService } from '@alfresco/adf-core';
declare let jasmine: any; declare let jasmine: any;
describe('Activiti TaskList Service', () => { describe('Activiti TaskList Service', () => {
let service: TaskListService; let service: TaskListService;
let userPreferences: UserPreferencesService;
setupTestBed({
imports: [
CoreModule.forRoot()
]
});
beforeEach(async(() => { beforeEach(async(() => {
service = new TaskListService(new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService() ), new LogService(new AppConfigService(null))); userPreferences = TestBed.get(UserPreferencesService);
service = new TaskListService(new AlfrescoApiServiceMock(new AppConfigService(null), userPreferences, new StorageService() ), new LogService(new AppConfigService(null)));
jasmine.Ajax.install(); jasmine.Ajax.install();
})); }));
+27 -106
View File
@@ -1033,6 +1033,14 @@
"tslib": "1.9.1" "tslib": "1.9.1"
} }
}, },
"@auth0/angular-jwt": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-1.2.0.tgz",
"integrity": "sha512-T2t8ed4/g/w5aniVmOJ7AieTrQ9iU7urAbzRBx5OToY4UeEzzng+yP4yk114Xwoi3iV9AS4zhqMqgKcQ3vNvxA==",
"requires": {
"url": "0.11.0"
}
},
"@mat-datetimepicker/core": { "@mat-datetimepicker/core": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/@mat-datetimepicker/core/-/core-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@mat-datetimepicker/core/-/core-1.0.4.tgz",
@@ -1403,9 +1411,9 @@
} }
}, },
"alfresco-js-api": { "alfresco-js-api": {
"version": "2.4.0-beta6", "version": "2.4.0-fed4e011ee70eb36b5e2015859e719153d70b6c2",
"resolved": "https://registry.npmjs.org/alfresco-js-api/-/alfresco-js-api-2.4.0-beta6.tgz", "resolved": "https://registry.npmjs.org/alfresco-js-api/-/alfresco-js-api-2.4.0-fed4e011ee70eb36b5e2015859e719153d70b6c2.tgz",
"integrity": "sha512-ytEScVOdD9MeoIqkYwBHVE/GpmwD/0PE9eEEMZLzs/7r5igDhqIw4BrleIerfXh/S4y+U15fP0UFv++BW2XKEQ==", "integrity": "sha512-HWA2zLbuRTi1mjIoaEW2ZSWKMDfOWcYOgplFZovQgXUOaFZuPJH1AQ7RbImq/G5/NKwe9AJSUAnkg5vpuKEBTg==",
"requires": { "requires": {
"event-emitter": "0.3.4", "event-emitter": "0.3.4",
"superagent": "3.8.2" "superagent": "3.8.2"
@@ -1477,6 +1485,14 @@
} }
} }
}, },
"angular-oauth2-oidc": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/angular-oauth2-oidc/-/angular-oauth2-oidc-3.1.4.tgz",
"integrity": "sha1-B3k5VhbXLxXp3985Ld2pxpY3Qug=",
"requires": {
"jsrsasign": "8.0.12"
}
},
"angular2-template-loader": { "angular2-template-loader": {
"version": "0.6.2", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/angular2-template-loader/-/angular2-template-loader-0.6.2.tgz", "resolved": "https://registry.npmjs.org/angular2-template-loader/-/angular2-template-loader-0.6.2.tgz",
@@ -5888,30 +5904,6 @@
"locate-path": "2.0.0" "locate-path": "2.0.0"
} }
}, },
"findup": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/findup/-/findup-0.1.5.tgz",
"integrity": "sha1-itkpozk7rGJ5V6fl3kYjsGsOLOs=",
"dev": true,
"requires": {
"colors": "0.6.2",
"commander": "2.1.0"
},
"dependencies": {
"colors": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz",
"integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=",
"dev": true
},
"commander": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz",
"integrity": "sha1-0SG7roYNmZKj1Re6lvVliOR8Z4E=",
"dev": true
}
}
},
"flatten": { "flatten": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz",
@@ -6853,28 +6845,6 @@
} }
} }
}, },
"ghooks": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/ghooks/-/ghooks-2.0.4.tgz",
"integrity": "sha1-/VDgQP9UiQauQstReToBv+JFZ7k=",
"dev": true,
"requires": {
"findup": "0.1.5",
"lodash.clone": "4.5.0",
"manage-path": "2.0.0",
"opt-cli": "1.5.1",
"path-exists": "3.0.0",
"spawn-command": "0.0.2"
},
"dependencies": {
"spawn-command": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
"integrity": "sha1-lUThpDygRfhTGqwaSMspva5iM44=",
"dev": true
}
}
},
"github-build": { "github-build": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/github-build/-/github-build-1.2.0.tgz", "resolved": "https://registry.npmjs.org/github-build/-/github-build-1.2.0.tgz",
@@ -8951,6 +8921,11 @@
} }
} }
}, },
"jsrsasign": {
"version": "8.0.12",
"resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-8.0.12.tgz",
"integrity": "sha1-Iqu5ZW00owuVMENnIINeicLlwxY="
},
"karma": { "karma": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/karma/-/karma-2.0.2.tgz", "resolved": "https://registry.npmjs.org/karma/-/karma-2.0.2.tgz",
@@ -9467,14 +9442,7 @@
"lodash": { "lodash": {
"version": "4.17.10", "version": "4.17.10",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
"dev": true
},
"lodash._baseclone": {
"version": "4.5.7",
"resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-4.5.7.tgz",
"integrity": "sha1-zkKt4IOE711i+nfDD2GkbmhvhDQ=",
"dev": true
}, },
"lodash._basecopy": { "lodash._basecopy": {
"version": "3.0.1", "version": "3.0.1",
@@ -9555,12 +9523,6 @@
"lodash._createcompounder": "3.0.0" "lodash._createcompounder": "3.0.0"
} }
}, },
"lodash.clone": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz",
"integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=",
"dev": true
},
"lodash.clonedeep": { "lodash.clonedeep": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
@@ -10018,12 +9980,6 @@
"integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==",
"dev": true "dev": true
}, },
"manage-path": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/manage-path/-/manage-path-2.0.0.tgz",
"integrity": "sha1-9M+EV7km7u4qg7FzUBQUvHbrlZc=",
"dev": true
},
"map-cache": { "map-cache": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
@@ -11519,38 +11475,6 @@
"is-wsl": "1.1.0" "is-wsl": "1.1.0"
} }
}, },
"opt-cli": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/opt-cli/-/opt-cli-1.5.1.tgz",
"integrity": "sha1-BNtEexPJa5kusxaFJm9O0NlzbcI=",
"dev": true,
"requires": {
"commander": "2.9.0",
"lodash.clone": "4.3.2",
"manage-path": "2.0.0",
"spawn-command": "0.0.2-1"
},
"dependencies": {
"commander": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
"integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
"dev": true,
"requires": {
"graceful-readlink": "1.0.1"
}
},
"lodash.clone": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.3.2.tgz",
"integrity": "sha1-5WsXa2gjp93jj38r9Y3n1ZcSAOk=",
"dev": true,
"requires": {
"lodash._baseclone": "4.5.7"
}
}
}
},
"optimist": { "optimist": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
@@ -14575,8 +14499,7 @@
"querystring": { "querystring": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
"dev": true
}, },
"querystring-es3": { "querystring-es3": {
"version": "0.2.1", "version": "0.2.1",
@@ -18756,7 +18679,6 @@
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
"integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
"dev": true,
"requires": { "requires": {
"punycode": "1.3.2", "punycode": "1.3.2",
"querystring": "0.2.0" "querystring": "0.2.0"
@@ -18765,8 +18687,7 @@
"punycode": { "punycode": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
"dev": true
} }
} }
}, },