diff --git a/ng2-components/ng2-alfresco-core/index.ts b/ng2-components/ng2-alfresco-core/index.ts index c3408b541f..56ddef5541 100644 --- a/ng2-components/ng2-alfresco-core/index.ts +++ b/ng2-components/ng2-alfresco-core/index.ts @@ -29,6 +29,7 @@ import { AlfrescoContentService, AlfrescoSettingsService, StorageService, + CookieService, AlfrescoApiService, AlfrescoTranslateLoader, AlfrescoTranslationService, @@ -69,6 +70,7 @@ export const ALFRESCO_CORE_PROVIDERS: any[] = [ AlfrescoContentService, AlfrescoSettingsService, StorageService, + CookieService, AlfrescoApiService, AlfrescoTranslateLoader, AlfrescoTranslationService, diff --git a/ng2-components/ng2-alfresco-core/src/assets/cookie.service.mock.ts b/ng2-components/ng2-alfresco-core/src/assets/cookie.service.mock.ts new file mode 100644 index 0000000000..f2fd4a22a9 --- /dev/null +++ b/ng2-components/ng2-alfresco-core/src/assets/cookie.service.mock.ts @@ -0,0 +1,27 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class CookieServiceMock { + + getItem(key: string): string | null { + return this[key] && this[key].data || null; + } + + setItem(key: string, data: string, expiration: Date | null, path: string | null): void { + this[key] = {data, expiration, path}; + } +} diff --git a/ng2-components/ng2-alfresco-core/src/services/alfresco-authentication.service.spec.ts b/ng2-components/ng2-alfresco-core/src/services/alfresco-authentication.service.spec.ts index 25182bd052..8237bc1fba 100644 --- a/ng2-components/ng2-alfresco-core/src/services/alfresco-authentication.service.spec.ts +++ b/ng2-components/ng2-alfresco-core/src/services/alfresco-authentication.service.spec.ts @@ -20,6 +20,8 @@ import { AlfrescoSettingsService } from './alfresco-settings.service'; import { AlfrescoAuthenticationService } from './alfresco-authentication.service'; import { AlfrescoApiService } from './alfresco-api.service'; import { StorageService } from './storage.service'; +import { CookieService } from './cookie.service'; +import { CookieServiceMock } from './../assets/cookie.service.mock'; import { LogService } from './log.service'; declare let jasmine: any; @@ -29,6 +31,7 @@ describe('AlfrescoAuthenticationService', () => { let authService: AlfrescoAuthenticationService; let settingsService: AlfrescoSettingsService; let storage: StorageService; + let cookie: CookieService; beforeEach(() => { injector = ReflectiveInjector.resolveAndCreate([ @@ -36,11 +39,13 @@ describe('AlfrescoAuthenticationService', () => { AlfrescoApiService, AlfrescoAuthenticationService, StorageService, + { provide: CookieService, useClass: CookieServiceMock }, LogService ]); authService = injector.get(AlfrescoAuthenticationService); settingsService = injector.get(AlfrescoSettingsService); + cookie = injector.get(CookieService); storage = injector.get(StorageService); storage.clear(); @@ -51,6 +56,64 @@ describe('AlfrescoAuthenticationService', () => { jasmine.Ajax.uninstall(); }); + describe('remembe me', () => { + + beforeEach(() => { + settingsService.setProviders('ECM'); + }); + + it('should save the remember me cookie as a session cookie after successful login', (done) => { + authService.login('fake-username', 'fake-password', false).subscribe(() => { + expect(cookie['ALFRESCO_REMEMBER_ME']).not.toBeUndefined(); + expect(cookie['ALFRESCO_REMEMBER_ME'].expiration).toBeNull(); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 201, + contentType: 'application/json', + responseText: JSON.stringify({'entry': {'id': 'fake-post-ticket', 'userId': 'admin'}}) + }); + }); + + it('should save the remember me cookie as a persistent cookie after successful login', (done) => { + authService.login('fake-username', 'fake-password', true).subscribe(() => { + expect(cookie['ALFRESCO_REMEMBER_ME']).not.toBeUndefined(); + expect(cookie['ALFRESCO_REMEMBER_ME'].expiration).not.toBeNull(); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 201, + contentType: 'application/json', + responseText: JSON.stringify({'entry': {'id': 'fake-post-ticket', 'userId': 'admin'}}) + }); + }); + + it('should not save the remember me cookie after failed login', (done) => { + authService.login('fake-username', 'fake-password').subscribe( + (res) => {}, + (err: any) => { + expect(cookie['ALFRESCO_REMEMBER_ME']).toBeUndefined(); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 403, + contentType: 'application/json', + responseText: JSON.stringify({ + 'error': { + 'errorKey': 'Login failed', + 'statusCode': 403, + 'briefSummary': '05150009 Login failed', + 'stackTrace': 'For security reasons the stack trace is no longer displayed, but the property is kept for previous versions.', + 'descriptionURL': 'https://api-explorer.alfresco.com' + } + }) + }); + }); + }); + describe('when the setting is ECM', () => { beforeEach(() => { diff --git a/ng2-components/ng2-alfresco-core/src/services/alfresco-authentication.service.ts b/ng2-components/ng2-alfresco-core/src/services/alfresco-authentication.service.ts index e437b0832e..1c64265bcc 100644 --- a/ng2-components/ng2-alfresco-core/src/services/alfresco-authentication.service.ts +++ b/ng2-components/ng2-alfresco-core/src/services/alfresco-authentication.service.ts @@ -19,23 +19,29 @@ import { Injectable } from '@angular/core'; import { Observable, Subject } from 'rxjs/Rx'; import { AlfrescoSettingsService } from './alfresco-settings.service'; import { StorageService } from './storage.service'; +import { CookieService } from './cookie.service'; import { LogService } from './log.service'; import { AlfrescoApiService } from './alfresco-api.service'; +const REMEMBER_ME_COOKIE_KEY = 'ALFRESCO_REMEMBER_ME'; +const REMEMBER_ME_UNTIL = 1000 * 60 * 60 * 24 * 30 ; + @Injectable() export class AlfrescoAuthenticationService { onLogin: Subject = new Subject(); onLogout: Subject = new Subject(); - constructor(private settingsService: AlfrescoSettingsService, - public alfrescoApi: AlfrescoApiService, - private storage: StorageService, - private logService: LogService) { + constructor( + private settingsService: AlfrescoSettingsService, + public alfrescoApi: AlfrescoApiService, + private storage: StorageService, + private cookie: CookieService, + private logService: LogService) { } /** - * The method return tru if the user is logged in + * The method return true if the user is logged in * @returns {boolean} */ isLoggedIn(): boolean { @@ -48,17 +54,43 @@ export class AlfrescoAuthenticationService { * @param password * @returns {Observable|Observable} */ - login(username: string, password: string): Observable<{ type: string, ticket: any }> { + login(username: string, password: string, rememberMe: boolean = false): Observable<{ type: string, ticket: any }> { this.removeTicket(); return Observable.fromPromise(this.callApiLogin(username, password)) .map((response: any) => { + this.saveRememberMeCookie(rememberMe); this.saveTickets(); this.onLogin.next(response); - return {type: this.settingsService.getProviders(), ticket: response}; + return { type: this.settingsService.getProviders(), ticket: response }; }) .catch(err => this.handleError(err)); } + /** + * The method save the "remember me" cookie as a long life cookie or a session cookie + * depending on the given paramter + */ + private saveRememberMeCookie(rememberMe: boolean): void { + let expiration = null; + + if (rememberMe) { + expiration = new Date(); + const time = expiration.getTime(); + const expireTime = time + REMEMBER_ME_UNTIL; + expiration.setTime(expireTime); + } + this.cookie.setItem(REMEMBER_ME_COOKIE_KEY, '1', expiration, null); + } + + /** + * The method retrieve whether the "remember me" cookie was set or not + * + * @returns {boolean} + */ + private isRememberMeSet(): boolean { + 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 @@ -130,7 +162,7 @@ export class AlfrescoAuthenticationService { /** * The method save the ECM and BPM ticket in the Storage */ - saveTickets() { + saveTickets(): void { this.saveTicketEcm(); this.saveTicketBpm(); } @@ -155,16 +187,20 @@ export class AlfrescoAuthenticationService { /** * The method return true if user is logged in on ecm provider + * + * @returns {boolean} */ - isEcmLoggedIn() { - return this.alfrescoApi.getInstance().ecmAuth && !!this.alfrescoApi.getInstance().ecmAuth.isLoggedIn(); + isEcmLoggedIn(): boolean { + return this.isRememberMeSet() && this.alfrescoApi.getInstance().ecmAuth && !!this.alfrescoApi.getInstance().ecmAuth.isLoggedIn(); } /** * The method return true if user is logged in on bpm provider + * + * @returns {boolean} */ - isBpmLoggedIn() { - return this.alfrescoApi.getInstance().bpmAuth && !!this.alfrescoApi.getInstance().bpmAuth.isLoggedIn(); + isBpmLoggedIn(): boolean { + return this.isRememberMeSet() && this.alfrescoApi.getInstance().bpmAuth && !!this.alfrescoApi.getInstance().bpmAuth.isLoggedIn(); } /** diff --git a/ng2-components/ng2-alfresco-core/src/services/alfresco-content.service.spec.ts b/ng2-components/ng2-alfresco-core/src/services/alfresco-content.service.spec.ts index 81806e372d..3920f13fb6 100644 --- a/ng2-components/ng2-alfresco-core/src/services/alfresco-content.service.spec.ts +++ b/ng2-components/ng2-alfresco-core/src/services/alfresco-content.service.spec.ts @@ -21,6 +21,8 @@ import { AlfrescoAuthenticationService } from './alfresco-authentication.service import { AlfrescoContentService } from './alfresco-content.service'; import { AlfrescoApiService } from './alfresco-api.service'; import { StorageService } from './storage.service'; +import { CookieService } from './cookie.service'; +import { CookieServiceMock } from './../assets/cookie.service.mock'; import { LogService } from './log.service'; declare let jasmine: any; @@ -42,6 +44,7 @@ describe('AlfrescoContentService', () => { AlfrescoAuthenticationService, AlfrescoSettingsService, StorageService, + { provide: CookieService, useClass: CookieServiceMock }, LogService ]); diff --git a/ng2-components/ng2-alfresco-core/src/services/auth-guard-bpm.service.spec.ts b/ng2-components/ng2-alfresco-core/src/services/auth-guard-bpm.service.spec.ts index be69f7a0a1..12e178125b 100644 --- a/ng2-components/ng2-alfresco-core/src/services/auth-guard-bpm.service.spec.ts +++ b/ng2-components/ng2-alfresco-core/src/services/auth-guard-bpm.service.spec.ts @@ -20,6 +20,8 @@ import { AlfrescoAuthenticationService } from './alfresco-authentication.service import { AlfrescoApiService } from './alfresco-api.service'; import { StorageService } from './storage.service'; import { LogService } from './log.service'; +import { CookieService } from './cookie.service'; +import { CookieServiceMock } from './../assets/cookie.service.mock'; import { AuthGuardBpm } from './auth-guard-bpm.service'; import { Router} from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; @@ -34,6 +36,7 @@ describe('AuthGuardService BPM', () => { AlfrescoApiService, AlfrescoAuthenticationService, StorageService, + { provide: CookieService, useClass: CookieServiceMock }, LogService], imports: [RouterTestingModule] }); diff --git a/ng2-components/ng2-alfresco-core/src/services/auth-guard-ecm.service.spec.ts b/ng2-components/ng2-alfresco-core/src/services/auth-guard-ecm.service.spec.ts index 6abb94e241..f09502d820 100644 --- a/ng2-components/ng2-alfresco-core/src/services/auth-guard-ecm.service.spec.ts +++ b/ng2-components/ng2-alfresco-core/src/services/auth-guard-ecm.service.spec.ts @@ -19,6 +19,8 @@ import { AlfrescoSettingsService } from './alfresco-settings.service'; import { AlfrescoAuthenticationService } from './alfresco-authentication.service'; import { AlfrescoApiService } from './alfresco-api.service'; import { StorageService } from './storage.service'; +import { CookieService } from './cookie.service'; +import { CookieServiceMock } from './../assets/cookie.service.mock'; import { LogService } from './log.service'; import { AuthGuardEcm } from './auth-guard-ecm.service'; import { Router} from '@angular/router'; @@ -34,6 +36,7 @@ describe('AuthGuardService ECM', () => { AlfrescoApiService, AlfrescoAuthenticationService, StorageService, + { provide: CookieService, useClass: CookieServiceMock }, LogService], imports: [RouterTestingModule] }); diff --git a/ng2-components/ng2-alfresco-core/src/services/auth-guard.service.spec.ts b/ng2-components/ng2-alfresco-core/src/services/auth-guard.service.spec.ts index c0de8ef28c..8591c23522 100644 --- a/ng2-components/ng2-alfresco-core/src/services/auth-guard.service.spec.ts +++ b/ng2-components/ng2-alfresco-core/src/services/auth-guard.service.spec.ts @@ -19,6 +19,8 @@ import { AlfrescoSettingsService } from './alfresco-settings.service'; import { AlfrescoAuthenticationService } from './alfresco-authentication.service'; import { AlfrescoApiService } from './alfresco-api.service'; import { StorageService } from './storage.service'; +import { CookieService } from './cookie.service'; +import { CookieServiceMock } from './../assets/cookie.service.mock'; import { LogService } from './log.service'; import { AuthGuard } from './auth-guard.service'; import { Router} from '@angular/router'; @@ -34,6 +36,7 @@ describe('AuthGuardService', () => { AlfrescoApiService, AlfrescoAuthenticationService, StorageService, + { provide: CookieService, useClass: CookieServiceMock }, LogService], imports: [RouterTestingModule] }); diff --git a/ng2-components/ng2-alfresco-core/src/services/cookie.service.ts b/ng2-components/ng2-alfresco-core/src/services/cookie.service.ts new file mode 100644 index 0000000000..4603142e29 --- /dev/null +++ b/ng2-components/ng2-alfresco-core/src/services/cookie.service.ts @@ -0,0 +1,48 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable } from '@angular/core'; + +@Injectable() +export class CookieService { + + /** + * Retrieve cookie by key. + * + * @returns {string | null} + */ + getItem(key: string): string | null { + const regexp = new RegExp('(?:' + key + '|;\s*' + key + ')=(.*?)(?:;|$)', 'g'); + const result = regexp.exec(document.cookie); + return (result === null) ? null : result[1]; + } + + /** + * Set a cookie. + * @param key + * @param data + * @param expiration + * @param path + * + * @returns {boolean} + */ + setItem(key: string, data: string, expiration: Date | null, path: string | null): void { + document.cookie = `${key}=${data}` + + (expiration ? ';expires=' + expiration.toUTCString() : '') + + (path ? `;path=${path}` : ';path=/'); + } +} diff --git a/ng2-components/ng2-alfresco-core/src/services/index.ts b/ng2-components/ng2-alfresco-core/src/services/index.ts index 905cbbd57b..2f3d9d80e3 100644 --- a/ng2-components/ng2-alfresco-core/src/services/index.ts +++ b/ng2-components/ng2-alfresco-core/src/services/index.ts @@ -17,6 +17,7 @@ export * from './content.service'; export * from './storage.service'; +export * from './cookie.service'; export * from './alfresco-api.service'; export * from './alfresco-settings.service'; export * from './alfresco-content.service'; diff --git a/ng2-components/ng2-alfresco-documentlist/src/services/document-list.service.spec.ts b/ng2-components/ng2-alfresco-documentlist/src/services/document-list.service.spec.ts index 9171956d8d..1010826a5e 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/services/document-list.service.spec.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/services/document-list.service.spec.ts @@ -15,8 +15,18 @@ * limitations under the License. */ -import { AlfrescoSettingsService, AlfrescoAuthenticationService, AlfrescoApiService, StorageService, AlfrescoContentService, LogService, LogServiceMock } from 'ng2-alfresco-core'; +import { + AlfrescoSettingsService, + AlfrescoAuthenticationService, + AlfrescoApiService, + StorageService, + CookieService, + AlfrescoContentService, + LogService, + LogServiceMock +} from 'ng2-alfresco-core'; import { FileNode } from '../assets/document-library.model.mock'; +import { CookieServiceMock } from '../../../ng2-alfresco-core/src/assets/cookie.service.mock'; import { ReflectiveInjector } from '@angular/core'; import { DocumentListService } from './document-list.service'; @@ -100,6 +110,7 @@ describe('DocumentListService', () => { AlfrescoContentService, DocumentListService, StorageService, + { provide: CookieService, useClass: CookieServiceMock }, { provide: LogService, useClass: LogServiceMock } ]); diff --git a/ng2-components/ng2-alfresco-login/src/components/alfresco-login.component.html b/ng2-components/ng2-alfresco-login/src/components/alfresco-login.component.html index 95277447e3..7cb0cbbb48 100644 --- a/ng2-components/ng2-alfresco-login/src/components/alfresco-login.component.html +++ b/ng2-components/ng2-alfresco-login/src/components/alfresco-login.component.html @@ -82,7 +82,7 @@