[AAE-12501] move auth in ADF (#8689)

* remove unneeded JS-API dep
move auth in the right place

* [AAE-12501] Replace alfresco api client with AdfHttpClient

* [AAE-12501] Restore get username methods

* [AAE-12501] Get username with authentication service

* [AAE-12501] Create a request options interface with the needed props, remove the import from js-api, return the body from request

* add emitters

* [AAE-12501] Replace Math.random() to fix hospot security issue, fix lint issues

* [AAE-12501] Comment temporary setCsrfToken because is not possible to import app config service from core due to circular dependencies

* [AAE-12501] Get disableCsrf from app config serviice when app configuration is loaded

* [AAE-12501] Remove wrong character

* Pass down the requestUrl for request interception
bring back check from js-api
fixing isLogin issues part1
some fix around emit
Narrow access for methods
fix sso username issue
Switch to dynamic service injection
add emitters
move auth inside ADF

* clean

* fix unit test

* fix lint

* Fix exports

* Fix process-services unit tests

* Fix core unit tests

Fix core unit tests

Fix core unit tests

Fix core unit tests

Fix core unit tests

Fix core unit tests

Fix core unit tests

* Fix content-services unit tests: getEcmUsername from authentication service

Fix content-services unit tests: alfresco api service has been replaced by authentication service

* Fix circular dependecies issue importing AppConfigService outside the api entrypoint dir

* Import AuthModule even in not only canary mode to let the e2es run

* Fix authentication unit tests

* Fix unit test '[ECM] should return a ticket undefined after logout'

* Remove AlfrescoApiService is not used anymore

* Fix unit test '[BPM] should return an BPM ticket after the login done': add Basic suffix to basicAuth

* Fix unit tests core

* Fix login errors with the BASIC authentication

* Fix missing onLogin event

* Temporary skip unit tests to check e2es

* Fix login component doesn't add the authorization header

* Fix prefix is undefined

* Fix image is not showed by the alfresco file viewer because alf_ticket is not added to the content url query params, pass ticketEcm to the alfrescoApi configuration used by alfrescoApiClient.ts getAlfTicket()

* Fix C280012: set app prefix before calling content api

* Revert "Fix image is not showed by the alfresco file viewer because alf_ticket is not added to the content url query params, pass ticketEcm to the alfrescoApi configuration used by alfrescoApiClient.ts getAlfTicket()"

This reverts commit afbf086b98d72835aab8b15d4af433efeaac2d3b.

* try to change adf core autoamtion service init

* go back

* grant type password login

* fix

* remove automatic login in reset try

* fix not silent login

* lint happy

* fix

* Update alfresco-api-v2-loader.service.ts

* fixint

* Revert "Temporary skip unit tests to check e2es"

This reverts commit a0adc7e58a001a54442c82952761bff891caa5cd.

* fix modules

* fix app config stream in storing service
fix app config stream for sub property

* fix identity test to use the real service

* fix unit

* fix unit

* fix unit

* remove test that are probably have never been green

* fix

* fix PC

* fix localstorage

* fix

* fix

* fix

* fix

* fix storybook
move e2e in content for versioning
fix lint

* fix

* fix size

* enable log

* some fix for usernames

* remove log

* fix rebase

* [AAE-12502] Restore isKerberosEnabled into authentication service

* subject onLogin

* fix unit

* Fix lint issue

* fix

* Update error message

* Revert change did by b79c5d37d6\#diff-ad85723e21276e05e577bab652c6ab0d243bd0ad54d4cc70ef6e60dc5e635c33L38

* Refresh the browser to wait for the user to click process cloud page

* Remove e2e, the application list is already tested by the app-list-cloud.component.spec.ts https://github.com/Alfresco/alfresco-ng2-components/blob/dev-eromano-AAE-12501-2/lib/process-services-cloud/src/lib/app/components/app-list-cloud.component.spec.ts\#L147

* [12502] Add getUsername method to the AuthenticationService

* [12501] restore mutlipart/form-data header needed by angular http-client to to fix 415 unsupported media type

* Revert "[12501] restore mutlipart/form-data header needed by angular http-client to to fix 415 unsupported media type"

This reverts commit d8c584b94f649b57859d74157ec0861f2ebddebb.

* [12501] fix unsupported upload file on admin-apa, append json content type only calling alfresco api

* [12501] fix unsupported upload file on admin-apa

[12501] fix unsupported upload file on admin-apa

* Revert "[12501] fix unsupported upload file on admin-apa"

This reverts commit 53cda21d795588d87244c78c5a5347afd04ea2b1.

* Improve getHeaders

* Revert change

* Set application/json content type if request body is not a FormData

* Logout by the authenticationService

* Update returned error message

* Fix lint issues after rebasing

* [12502] Add basic template with data-automation-ad selector to check when is attached to the Dom

* Fix issues after rebase

---------

Co-authored-by: Amedeo Lepore <amedeo.lepore@hyland.com>
Co-authored-by: Andras Popovics <popovics@ndras.hu>
This commit is contained in:
Eugenio Romano 2023-11-06 14:25:27 +01:00 committed by GitHub
parent 057e0bcd7c
commit 08da9ae2c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
111 changed files with 2157 additions and 1417 deletions

View File

@ -392,15 +392,6 @@ jobs:
check-cs-env: "true"
check-ps-cloud-env: "true"
deps: "testing"
- description: "Process Cloud: People"
test-id: "process-services-cloud"
folder: "process-services-cloud/people"
provider: "ALL"
auth: "OAUTH"
apa-proxy: true
check-cs-env: "true"
check-ps-cloud-env: "true"
deps: "testing"
- description: "Process Cloud: Process"
test-id: "process-services-cloud"
folder: "process-services-cloud/process"

2
.vscode/launch.json vendored
View File

@ -10,7 +10,7 @@
"name": "e2e",
"program": "${workspaceFolder}/node_modules/protractor/bin/protractor",
"args": [
"`${workspaceFolder}/.vscode/closest-config-finder.sh ${file} e2e/protractor.conf.js`",
"./e2e/protractor.conf.js",
"--specs=${file}"
],
"envFile": "${workspaceFolder}/.env",

View File

@ -141,7 +141,8 @@
"webscript",
"Whitespaces",
"xdescribe",
"xsrf"
"xsrf",
"BPMECM"
],
"dictionaries": [
"html",

View File

@ -18,11 +18,11 @@
import { Component, ViewEncapsulation, OnInit } from '@angular/core';
import {
AuthenticationService,
AlfrescoApiService,
PageTitleService
} from '@alfresco/adf-core';
import { Router } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';
import { AdfHttpClient } from '@alfresco/adf-core/api';
@Component({
selector: 'app-root',
@ -33,7 +33,7 @@ import { MatDialog } from '@angular/material/dialog';
export class AppComponent implements OnInit {
constructor(private pageTitleService: PageTitleService,
private alfrescoApiService: AlfrescoApiService,
private adfHttpClient: AdfHttpClient,
private authenticationService: AuthenticationService,
private router: Router,
private dialogRef: MatDialog) {
@ -43,7 +43,7 @@ export class AppComponent implements OnInit {
ngOnInit() {
this.pageTitleService.setTitle('title');
this.alfrescoApiService.getInstance().on('error', (error) => {
this.adfHttpClient.on('error', (error) => {
if (error.status === 401) {
if (!this.authenticationService.isLoggedIn()) {
this.dialogRef.closeAll();

View File

@ -74,7 +74,7 @@ import { UserInfoComponent } from './components/app-layout/user-info/user-info.c
environment.e2e ? NoopAnimationsModule : BrowserAnimationsModule,
ReactiveFormsModule,
RouterModule.forRoot(appRoutes, { useHash: true, relativeLinkResolution: 'legacy' }),
...(environment.oidc ? [AuthModule.forRoot({ useHash: true })] : []),
AuthModule.forRoot({ useHash: true }),
FormsModule,
HttpClientModule,
MaterialModule,

View File

@ -17,7 +17,13 @@
import { EcmUserModel, PeopleContentService } from '@alfresco/adf-content-services';
import { BpmUserModel, PeopleProcessService } from '@alfresco/adf-process-services';
import { AuthenticationService, IdentityUserModel, IdentityUserService, UserInfoMode } from '@alfresco/adf-core';
import {
AuthenticationService,
BasicAlfrescoAuthService,
IdentityUserModel,
IdentityUserService,
UserInfoMode
} from '@alfresco/adf-core';
import { Component, OnInit, Input } from '@angular/core';
import { MenuPositionX, MenuPositionY } from '@angular/material/menu';
import { Observable, of } from 'rxjs';
@ -46,6 +52,7 @@ export class UserInfoComponent implements OnInit {
constructor(private peopleContentService: PeopleContentService,
private peopleProcessService: PeopleProcessService,
private identityUserService: IdentityUserService,
private basicAlfrescoAuthService: BasicAlfrescoAuthService,
private authService: AuthenticationService) {
}
@ -77,7 +84,7 @@ export class UserInfoComponent implements OnInit {
}
get isLoggedIn(): boolean {
if (this.authService.isKerberosEnabled()) {
if (this.basicAlfrescoAuthService.isKerberosEnabled()) {
return true;
}
return this.authService.isLoggedIn();
@ -96,15 +103,15 @@ export class UserInfoComponent implements OnInit {
}
private isAllLoggedIn() {
return (this.authService.isEcmLoggedIn() && this.authService.isBpmLoggedIn()) || (this.authService.isALLProvider() && this.authService.isKerberosEnabled());
return (this.authService.isEcmLoggedIn() && this.authService.isBpmLoggedIn()) || (this.authService.isALLProvider() && this.basicAlfrescoAuthService.isKerberosEnabled());
}
private isBpmLoggedIn() {
return this.authService.isBpmLoggedIn() || (this.authService.isECMProvider() && this.authService.isKerberosEnabled());
return this.authService.isBpmLoggedIn() || (this.authService.isECMProvider() && this.basicAlfrescoAuthService.isKerberosEnabled());
}
private isEcmLoggedIn() {
return this.authService.isEcmLoggedIn() || (this.authService.isECMProvider() && this.authService.isKerberosEnabled());
return this.authService.isEcmLoggedIn() || (this.authService.isECMProvider() && this.basicAlfrescoAuthService.isKerberosEnabled());
}
}

View File

@ -71,7 +71,7 @@
<mat-slide-toggle class="adf-full-width" name="implicitFlow" formControlName="implicitFlow">
</mat-slide-toggle>
<ng-container *ngIf="supportsCodeFlow">
<ng-container *ngIf="isOAUTH">
<mat-label>Code Flow</mat-label>
<mat-slide-toggle class="adf-full-width" name="codeFlow" formControlName="codeFlow">
</mat-slide-toggle>

View File

@ -65,7 +65,7 @@ export class HostSettingsComponent implements OnInit {
private storageService: StorageService,
private alfrescoApiService: AlfrescoApiService,
private appConfig: AppConfigService,
private auth: AuthenticationService
private authenticationService: AuthenticationService
) {}
ngOnInit() {
@ -191,8 +191,8 @@ export class HostSettingsComponent implements OnInit {
this.storageService.setItem(AppConfigValues.AUTHTYPE, values.authType);
this.alfrescoApiService.reset();
this.auth.reset();
this.alfrescoApiService.getInstance().invalidateSession();
this.authenticationService.reset();
this.authenticationService.logout();
this.success.emit(true);
}
@ -235,10 +235,6 @@ export class HostSettingsComponent implements OnInit {
return this.form.get('authType').value === 'OAUTH';
}
get supportsCodeFlow(): boolean {
return this.auth.supportCodeFlow;
}
get providersControl(): UntypedFormControl {
return this.form.get('providersControl') as UntypedFormControl;
}

View File

@ -21,7 +21,7 @@ import { ContentServicesPage } from '../../core/pages/content-services.page';
import { FileModel } from '../../models/ACS/file.model';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
import { VersionManagePage } from '../pages/version-manager.page';
import { MetadataViewPage } from '../pages/metadata-view.page';
import { MetadataViewPage } from '../../core/pages/metadata-view.page';
describe('Content Services Viewer', () => {
const acsUser = new UserModel();

View File

@ -0,0 +1,94 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { browser } from 'protractor';
import { createApiService, FileBrowserUtil, LoginPage, UploadActions, UserModel, UsersActions, ViewerPage } from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../core/pages/content-services.page';
import { FileModel } from '../../models/ACS/file.model';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
import { VersionManagePage } from '../pages/version-manager.page';
describe('Viewer', () => {
const navigationBarPage = new NavigationBarPage();
const viewerPage = new ViewerPage();
const loginPage = new LoginPage();
const contentServicesPage = new ContentServicesPage();
const apiService = createApiService();
const uploadActions = new UploadActions(apiService);
const usersActions = new UsersActions(apiService);
const versionManagePage = new VersionManagePage();
const acsUser = new UserModel();
let txtFileUploaded;
const txtFileInfo = new FileModel({
name: browser.params.resources.Files.ADF_DOCUMENTS.TXT.file_name,
location: browser.params.resources.Files.ADF_DOCUMENTS.TXT.file_path
});
const fileModelVersionTwo = new FileModel({
name: browser.params.resources.Files.ADF_DOCUMENTS.TXT.file_name,
location: browser.params.resources.Files.ADF_DOCUMENTS.TXT.file_location
});
beforeAll(async () => {
await apiService.loginWithProfile('admin');
await usersActions.createUser(acsUser);
await apiService.login(acsUser.username, acsUser.password);
txtFileUploaded = await uploadActions.uploadFile(txtFileInfo.location, txtFileInfo.name, '-my-');
await loginPage.login(acsUser.username, acsUser.password);
});
afterAll(async () => {
await apiService.loginWithProfile('admin');
await uploadActions.deleteFileOrFolder(txtFileUploaded.entry.id);
await navigationBarPage.clickLogoutButton();
});
beforeEach(async () => {
await contentServicesPage.goToDocumentList();
await contentServicesPage.doubleClickRow(txtFileUploaded.entry.name);
await viewerPage.waitTillContentLoaded();
});
afterEach(async () => {
await viewerPage.clickCloseButton();
});
it('[C362242] Should the Viewer be able to view a previous version of a file', async () => {
await contentServicesPage.versionManagerContent(txtFileInfo.name);
await versionManagePage.showNewVersionButton.click();
await versionManagePage.uploadNewVersionFile(fileModelVersionTwo.location);
await versionManagePage.closeVersionDialog();
await contentServicesPage.doubleClickRow(txtFileUploaded.entry.name);
await viewerPage.waitTillContentLoaded();
await viewerPage.clickInfoButton();
await viewerPage.clickOnTab('Versions');
await versionManagePage.viewFileVersion('1.0');
await viewerPage.expectUrlToContain('1.0');
});
it('[C362265] Should the Viewer be able to download a previous version of a file', async () => {
await viewerPage.clickDownloadButton();
await FileBrowserUtil.isFileDownloaded(txtFileInfo.name);
});
});

View File

@ -31,7 +31,7 @@ import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
import { FolderModel } from '../../models/ACS/folder.model';
import { browser } from 'protractor';
import { FileModel } from '../../models/ACS/file.model';
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
import { UploadDialogPage } from '../pages/upload-dialog.page';
describe('Document List - Pagination', () => {
const pagination = {

View File

@ -18,7 +18,7 @@
import { browser } from 'protractor';
import { createApiService, LoginPage, UploadActions, UserModel, UsersActions } from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../core/pages/content-services.page';
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
import { UploadDialogPage } from '../pages/upload-dialog.page';
import { UploadTogglesPage } from '../../core/pages/dialog/upload-toggles.page';
import { FileModel } from '../../models/ACS/file.model';

View File

@ -24,7 +24,7 @@ import { createApiService,
UsersActions
} from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../core/pages/content-services.page';
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
import { UploadDialogPage } from '../pages/upload-dialog.page';
import { UploadTogglesPage } from '../../core/pages/dialog/upload-toggles.page';
import { FileModel } from '../../models/ACS/file.model';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';

View File

@ -17,11 +17,11 @@
import { createApiService, LoginPage, UploadActions, UserModel, UsersActions } from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../core/pages/content-services.page';
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
import { UploadDialogPage } from '../pages/upload-dialog.page';
import { UploadTogglesPage } from '../../core/pages/dialog/upload-toggles.page';
import { FileModel } from '../../models/ACS/file.model';
import { browser } from 'protractor';
import { VersionManagePage } from '../../core/pages/version-manager.page';
import { VersionManagePage } from '../pages/version-manager.page';
describe('Upload component', () => {

View File

@ -19,7 +19,7 @@ import { browser, by, element } from 'protractor';
import { createApiService, DropActions, LoginPage, StringUtil, UploadActions, UserModel, UsersActions } from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../core/pages/content-services.page';
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
import { UploadDialogPage } from '../pages/upload-dialog.page';
import { UploadTogglesPage } from '../../core/pages/dialog/upload-toggles.page';
import { FileModel } from '../../models/ACS/file.model';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';

View File

@ -18,7 +18,7 @@
import { browser } from 'protractor';
import { createApiService, LoginPage, SnackbarPage, StringUtil, UserModel, UsersActions } from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../core/pages/content-services.page';
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
import { UploadDialogPage } from '../pages/upload-dialog.page';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
import { FileModel } from '../../models/ACS/file.model';
import CONSTANTS = require('../../util/constants');

View File

@ -28,9 +28,9 @@ import { createApiService,
import { browser, by, element } from 'protractor';
import { FileModel } from '../../models/ACS/file.model';
import { ContentServicesPage } from '../../core/pages/content-services.page';
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
import { UploadDialogPage } from '../pages/upload-dialog.page';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
import { VersionManagePage } from '../../core/pages/version-manager.page';
import { VersionManagePage } from '../pages/version-manager.page';
describe('Version component actions', () => {

View File

@ -26,8 +26,8 @@ import {
UsersActions
} from '@alfresco/adf-testing';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
import { VersionManagePage } from '../../core/pages/version-manager.page';
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
import { VersionManagePage } from '../pages/version-manager.page';
import { UploadDialogPage } from '../pages/upload-dialog.page';
import { ContentServicesPage } from '../../core/pages/content-services.page';
import { FileModel } from '../../models/ACS/file.model';
import CONSTANTS = require('../../util/constants');

View File

@ -24,7 +24,7 @@ import { createApiService,
UsersActions, ViewerPage
} from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../core/pages/content-services.page';
import { VersionManagePage } from '../../core/pages/version-manager.page';
import { VersionManagePage } from '../pages/version-manager.page';
import { FileModel } from '../../models/ACS/file.model';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';

View File

@ -18,7 +18,7 @@
import { browser } from 'protractor';
import { createApiService, LoginPage, UploadActions, UserModel, UsersActions } from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../core/pages/content-services.page';
import { VersionManagePage } from '../../core/pages/version-manager.page';
import { VersionManagePage } from '../pages/version-manager.page';
import { FileModel } from '../../models/ACS/file.model';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';

View File

@ -1,79 +0,0 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { createApiService, Application, AppListCloudPage, IdentityService, LocalStorageUtil, LoginPage } from '@alfresco/adf-testing';
import { browser } from 'protractor';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
describe('Applications list', () => {
const simpleApp = browser.params.resources.ACTIVITI_CLOUD_APPS.SIMPLE_APP.name;
const loginSSOPage = new LoginPage();
const navigationBarPage = new NavigationBarPage();
const appListCloudPage = new AppListCloudPage();
const apiService = createApiService();
const applicationsService = new Application(apiService);
const identityService = new IdentityService(apiService);
let testUser;
const appNames = [];
beforeAll(async () => {
await apiService.loginWithProfile('identityAdmin');
testUser = await identityService.createIdentityUserWithRole( [identityService.ROLES.ACTIVITI_USER, identityService.ROLES.ACTIVITI_DEVOPS]);
await loginSSOPage.login(testUser.username, testUser.password);
await apiService.login(testUser.username, testUser.password);
const applications = await applicationsService.getApplicationsByStatus('RUNNING');
applications.list.entries.forEach(app => {
appNames.push(app.entry.name.toLowerCase());
});
await LocalStorageUtil.setConfigField('alfresco-deployed-apps', '[]');
await LocalStorageUtil.apiReset();
});
afterAll(async () => {
await apiService.loginWithProfile('identityAdmin');
await identityService.deleteIdentityUser(testUser.idIdentityService);
});
it('[C310373] Should all the app with running state be displayed on dashboard when alfresco-deployed-apps is not used in config file', async () => {
await navigationBarPage.navigateToProcessServicesCloudPage();
await appListCloudPage.checkApsContainer();
const list = await appListCloudPage.getNameOfTheApplications();
await expect(JSON.stringify(list)).toEqual(JSON.stringify(appNames));
});
it('[C289910] Should the app be displayed on dashboard when is deployed on APS', async () => {
await browser.refresh();
await navigationBarPage.navigateToProcessServicesCloudPage();
await appListCloudPage.checkApsContainer();
await appListCloudPage.checkAppIsDisplayed(simpleApp);
await appListCloudPage.checkAppIsDisplayed(browser.params.resources.ACTIVITI_CLOUD_APPS.CANDIDATE_BASE_APP.name);
await appListCloudPage.checkAppIsDisplayed(browser.params.resources.ACTIVITI_CLOUD_APPS.SUB_PROCESS_APP.name);
await expect(await appListCloudPage.countAllApps()).toEqual(3);
});
});

View File

@ -285,7 +285,7 @@ exports.config = {
// @ts-ignore
if (browser.params.testConfig.appConfig.authType === 'OAUTH') {
Logger.info(`Configure demo shell OAUTH`);
// @ts-ignore
await LocalStorageUtil.setStorageItem('identityHost', browser.params.testConfig.appConfig.identityHost);
// @ts-ignore

View File

@ -270,7 +270,7 @@ describe('Search Number Range Filter', () => {
for (const currentResult of results) {
const currentSize = await BrowserActions.getAttribute(currentResult, 'title');
if (currentSize && currentSize.trim() !== '') {
await expect(currentSize === '0').toBe(true);
await expect((currentSize === '0' || currentSize === '1')).toBe(true);
}
}
});

View File

@ -9,14 +9,14 @@ const HOST = process.env.URL_HOST_ADF;
const LOG = process.env.E2E_LOG_LEVEL;
const HOST_ECM = process.env.PROXY_HOST_ECM || HOST || 'ecm';
const HOST_BPM = process.env.PROXY_HOST_BPM || HOST || 'bpm';
const HOST_ECM = process.env.PROXY_HOST_ECM || process.env.PROXY_HOST_ADF || HOST || 'ecm';
const HOST_BPM = process.env.PROXY_HOST_BPM || process.env.PROXY_HOST_ADF || HOST || 'bpm';
const HOST_SSO = process.env.HOST_SSO || process.env.PROXY_HOST_ADF || HOST || 'oauth';
const IDENTITY_HOST = process.env.IDENTITY_HOST || process.env.HOST_SSO + '/auth/admin/realms/alfresco';
const PROVIDER = process.env.PROVIDER ? process.env.PROVIDER : 'ALL';
const AUTH_TYPE = process.env.AUTH_TYPE ? process.env.AUTH_TYPE : 'BASIC';
const HOST_SSO = process.env.HOST_SSO || process.env.PROXY_HOST_ADF || HOST || 'oauth';
const IDENTITY_HOST = process.env.IDENTITY_HOST || process.env.HOST_SSO + '/auth/admin/realms/alfresco';
const OAUTH_CLIENT_ID = process.env.OAUTH_CLIENDID || 'alfresco';
const IDENTITY_ADMIN_EMAIL = process.env.IDENTITY_ADMIN_EMAIL || "defaultadmin";

View File

@ -21,16 +21,11 @@ import { AppConfigService, AuthenticationService, StorageService, CoreTestingMod
import { Node, PermissionsInfo } from '@alfresco/js-api';
import { TranslateModule } from '@ngx-translate/core';
declare let jasmine: any;
describe('ContentService', () => {
let contentService: ContentService;
let authService: AuthenticationService;
let storage: StorageService;
let node: any;
const nodeId = 'fake-node-id';
beforeEach(() => {
TestBed.configureTestingModule({
@ -44,14 +39,6 @@ describe('ContentService', () => {
storage = TestBed.inject(StorageService);
storage.clear();
node = {
entry: {
id: nodeId
}
};
jasmine.Ajax.install();
const appConfig: AppConfigService = TestBed.inject(AppConfigService);
appConfig.config = {
ecmHost: 'http://localhost:9876/ecm',
@ -59,24 +46,6 @@ describe('ContentService', () => {
};
});
afterEach(() => {
jasmine.Ajax.uninstall();
});
it('should return a valid content URL', (done) => {
authService.login('fake-username', 'fake-password').subscribe(() => {
expect(contentService.getContentUrl(node)).toContain('/ecm/alfresco/api/' +
'-default-/public/alfresco/versions/1/nodes/fake-node-id/content?attachment=false&alf_ticket=fake-post-ticket');
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 201,
contentType: 'application/json',
responseText: JSON.stringify({ entry: { id: 'fake-post-ticket', userId: 'admin' } })
});
});
describe('AllowableOperations', () => {
it('should hasAllowableOperations be false if allowableOperation is not present in the node', () => {

View File

@ -18,16 +18,27 @@
import { Injectable } from '@angular/core';
import { from, Observable, throwError, Subject } from 'rxjs';
import { catchError, map, switchMap, filter, take } from 'rxjs/operators';
import { RepositoryInfo, SystemPropertiesRepresentation } from '@alfresco/js-api';
import {
RepositoryInfo,
SystemPropertiesRepresentation,
DiscoveryApi,
AboutApi,
SystemPropertiesApi
} from '@alfresco/js-api';
import { BpmProductVersionModel, AuthenticationService } from '@alfresco/adf-core';
import { ApiClientsService } from '@alfresco/adf-core/api';
import { AlfrescoApiService, BpmProductVersionModel, AuthenticationService } from '@alfresco/adf-core';
@Injectable({
providedIn: 'root'
})
export class DiscoveryApiService {
private _discoveryApi: DiscoveryApi;
get discoveryApi(): DiscoveryApi {
this._discoveryApi = this._discoveryApi ?? new DiscoveryApi(this.alfrescoApiService.getInstance());
return this._discoveryApi;
}
/**
* Gets product information for Content Services.
*/
@ -35,15 +46,17 @@ export class DiscoveryApiService {
constructor(
private authenticationService: AuthenticationService,
private apiClientsService: ApiClientsService
private alfrescoApiService: AlfrescoApiService
) {
this.authenticationService.onLogin
.pipe(
this.authenticationService.onLogin.subscribe(() => {
this.alfrescoApiService.alfrescoApiInitialized.pipe(
filter(() => this.authenticationService.isEcmLoggedIn()),
take(1),
switchMap(() => this.getEcmProductInfo())
)
.subscribe((info) => this.ecmProductInfo$.next(info));
});
}
@ -53,9 +66,8 @@ export class DiscoveryApiService {
* @returns ProductVersionModel containing product details
*/
getEcmProductInfo(): Observable<RepositoryInfo> {
const discoveryApi = this.apiClientsService.get('DiscoveryClient.discovery');
return from(discoveryApi.getRepositoryInformation())
return from(this.discoveryApi.getRepositoryInformation())
.pipe(
map((res) => res.entry.repository),
catchError((err) => throwError(err))
@ -68,7 +80,7 @@ export class DiscoveryApiService {
* @returns ProductVersionModel containing product details
*/
getBpmProductInfo(): Observable<BpmProductVersionModel> {
const aboutApi = this.apiClientsService.get('ActivitiClient.about');
const aboutApi = new AboutApi(this.alfrescoApiService.getInstance());
return from(aboutApi.getAppVersion())
.pipe(
@ -78,7 +90,7 @@ export class DiscoveryApiService {
}
getBPMSystemProperties(): Observable<SystemPropertiesRepresentation> {
const systemPropertiesApi = this.apiClientsService.get('ActivitiClient.system-properties');
const systemPropertiesApi = new SystemPropertiesApi(this.alfrescoApiService.getInstance());
return from(systemPropertiesApi.getProperties())
.pipe(

View File

@ -25,7 +25,6 @@ import {
import {
AlfrescoApiService,
AlfrescoApiServiceMock,
AuthenticationService,
CoreTestingModule
} from '@alfresco/adf-core';
import { PeopleContentQueryRequestModel, PeopleContentService } from './people-content.service';
@ -34,7 +33,6 @@ import { TestBed } from '@angular/core/testing';
describe('PeopleContentService', () => {
let peopleContentService: PeopleContentService;
let authenticationService: AuthenticationService;
beforeEach(() => {
TestBed.configureTestingModule({
@ -47,7 +45,6 @@ describe('PeopleContentService', () => {
]
});
authenticationService = TestBed.inject(AuthenticationService);
peopleContentService = TestBed.inject(PeopleContentService);
});
@ -130,17 +127,6 @@ describe('PeopleContentService', () => {
expect(getCurrentPersonSpy.calls.count()).toEqual(1);
});
it('should reset the admin cache upon logout', async () => {
spyOn(peopleContentService.peopleApi, 'getPerson').and.returnValue(Promise.resolve({ entry: fakeEcmAdminUser } as any));
const user = await peopleContentService.getCurrentUserInfo().toPromise();
expect(user.id).toEqual('fake-id');
expect(peopleContentService.isCurrentUserAdmin()).toBe(true);
authenticationService.onLogout.next(true);
expect(peopleContentService.isCurrentUserAdmin()).toBe(false);
});
it('should not change current user on every getPerson call', async () => {
const getCurrentPersonSpy = spyOn(peopleContentService.peopleApi, 'getPerson').and.returnValue(Promise.resolve({entry: fakeEcmAdminUser} as any));
await peopleContentService.getCurrentUserInfo().toPromise();

View File

@ -18,8 +18,7 @@
import { Component, ViewChild } from '@angular/core';
import { LibraryFavoriteDirective } from './library-favorite.directive';
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { AlfrescoApiServiceMock, CoreModule, AlfrescoApiService } from '@alfresco/adf-core';
import { CoreTestingModule } from '@alfresco/adf-core';
import { LibraryEntity } from '../interfaces/library-entity.interface';
@Component({
@ -40,10 +39,7 @@ describe('LibraryFavoriteDirective', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), CoreModule.forRoot()],
providers: [
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }
],
imports: [CoreTestingModule],
declarations: [TestComponent, LibraryFavoriteDirective]
});
fixture = TestBed.createComponent(TestComponent);

View File

@ -18,7 +18,6 @@
import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange, QueryList, Component, ViewChild, SimpleChanges } from '@angular/core';
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import {
AlfrescoApiService,
DataColumnListComponent,
DataColumnComponent,
DataColumn,
@ -27,7 +26,8 @@ import {
ObjectDataTableAdapter,
ShowHeaderMode,
ThumbnailService,
AppConfigService
AppConfigService,
AuthenticationService
} from '@alfresco/adf-core';
import { ContentService } from '../../common/services/content.service';
import { Subject, of, throwError } from 'rxjs';
@ -70,7 +70,6 @@ const mockDialog = {
describe('DocumentList', () => {
let documentList: DocumentListComponent;
let documentListService: DocumentListService;
let apiService: AlfrescoApiService;
let customResourcesService: CustomResourcesService;
let thumbnailService: ThumbnailService;
let contentService: ContentService;
@ -82,6 +81,7 @@ describe('DocumentList', () => {
let spyFavorite: any;
let spyFolder: any;
let spyFolderNode: any;
let authenticationService: AuthenticationService;
beforeEach(() => {
TestBed.configureTestingModule({
@ -99,11 +99,11 @@ describe('DocumentList', () => {
documentList = fixture.componentInstance;
documentListService = TestBed.inject(DocumentListService);
apiService = TestBed.inject(AlfrescoApiService);
customResourcesService = TestBed.inject(CustomResourcesService);
thumbnailService = TestBed.inject(ThumbnailService);
contentService = TestBed.inject(ContentService);
appConfigService = TestBed.inject(AppConfigService);
authenticationService = TestBed.inject(AuthenticationService);
spyFolder = spyOn(documentListService, 'getFolder').and.returnValue(of({ list: {} }));
spyFolderNode = spyOn(documentListService, 'getFolderNode').and.returnValue(of(new NodeEntry({ entry: new Node() })));
@ -611,7 +611,7 @@ describe('DocumentList', () => {
title: 'FileAction'
});
spyOn(apiService.getInstance(), 'getEcmUsername').and.returnValue('lockOwner');
spyOn(authenticationService, 'getEcmUsername').and.returnValue('lockOwner');
documentList.actions = [documentMenu];
@ -642,7 +642,7 @@ describe('DocumentList', () => {
title: 'FileAction'
});
spyOn(apiService.getInstance(), 'getEcmUsername').and.returnValue('jerryTheKillerCow');
spyOn(authenticationService, 'getEcmUsername').and.returnValue('jerryTheKillerCow');
documentList.actions = [documentMenu];

View File

@ -17,7 +17,7 @@
import { TestBed } from '@angular/core/testing';
import { LockService } from './lock.service';
import { CoreTestingModule, AlfrescoApiService } from '@alfresco/adf-core';
import { CoreTestingModule, AuthenticationService } from '@alfresco/adf-core';
import { Node } from '@alfresco/js-api';
import { TranslateModule } from '@ngx-translate/core';
import { addDays, subDays } from 'date-fns';
@ -25,7 +25,7 @@ import { addDays, subDays } from 'date-fns';
describe('PeopleProcessService', () => {
let service: LockService;
let apiService: AlfrescoApiService;
let authenticationService: AuthenticationService;
const fakeNodeUnlocked: Node = { name: 'unlocked', isLocked: false, isFile: true } as Node;
const fakeFolderNode: Node = { name: 'unlocked', isLocked: false, isFile: false, isFolder: true } as Node;
@ -39,7 +39,7 @@ describe('PeopleProcessService', () => {
]
});
service = TestBed.inject(LockService);
apiService = TestBed.inject(AlfrescoApiService);
authenticationService = TestBed.inject(AuthenticationService);
});
it('should return false when no lock is configured', () => {
@ -145,22 +145,22 @@ describe('PeopleProcessService', () => {
} as Node;
it('should return false when the user is the lock owner', () => {
spyOn(apiService.getInstance(), 'getEcmUsername').and.returnValue('lock-owner-user');
spyOn(authenticationService, 'getEcmUsername').and.returnValue('lock-owner-user');
expect(service.isLocked(nodeOwnerAllowedLock)).toBeFalsy();
});
it('should return true when the user is not the lock owner', () => {
spyOn(apiService.getInstance(), 'getEcmUsername').and.returnValue('banana-user');
spyOn(authenticationService, 'getEcmUsername').and.returnValue('banana-user');
expect(service.isLocked(nodeOwnerAllowedLock)).toBeTruthy();
});
it('should return false when the user is not the lock owner but the lock is expired', () => {
spyOn(apiService.getInstance(), 'getEcmUsername').and.returnValue('banana-user');
spyOn(authenticationService, 'getEcmUsername').and.returnValue('banana-user');
expect(service.isLocked(nodeOwnerAllowedLockWithExpiredDate)).toBeFalsy();
});
it('should return true when is not the lock owner and the expiration date is valid', () => {
spyOn(apiService.getInstance(), 'getEcmUsername').and.returnValue('banana-user');
spyOn(authenticationService, 'getEcmUsername').and.returnValue('banana-user');
expect(service.isLocked(nodeOwnerAllowedLockWithActiveExpiration)).toBeTruthy();
});
});

View File

@ -27,7 +27,8 @@ import {
AlfrescoApiServiceMock,
AppConfigServiceMock,
TranslationMock,
CookieServiceMock
CookieServiceMock,
AuthModule
} from '@alfresco/adf-core';
import { ContentModule } from '../content.module';
import { TranslateModule } from '@ngx-translate/core';
@ -37,6 +38,7 @@ import { MatIconTestingModule } from '@angular/material/icon/testing';
@NgModule({
imports: [
AuthModule.forRoot({ useHash: true }),
NoopAnimationsModule,
RouterTestingModule,
TranslateModule,

View File

@ -15,9 +15,6 @@
* limitations under the License.
*/
export * from './lib/api-client.factory';
export * from './lib/api-clients.service';
export * from './lib/clients';
export * from './lib/types';
export * from './lib/adf-http-client.service';
export * from './lib/interfaces';

View File

@ -321,4 +321,86 @@ describe('AdfHttpClient', () => {
req.flush(null, { status: 200, statusText: 'Ok' });
});
it('should set Content-type to multipart/form-data if contentTypes array contains only multipart/form-data element', () => {
const options: RequestOptions = {
path: '',
httpMethod: 'POST',
contentTypes: ['multipart/form-data'],
queryParams: {
lastModifiedFrom: new Date('2022-08-17T00:00:00.000Z')
}
};
angularHttpClient.request('http://example.com', options, securityOptions, emitters).catch(error =>
fail(error)
);
const req = controller.expectOne('http://example.com?lastModifiedFrom=2022-08-17T00%3A00%3A00.000Z');
expect(req.request.headers.get('Content-Type')).toEqual('multipart/form-data');
req.flush(null, { status: 200, statusText: 'Ok' });
});
it('should set Content-type header to application/json if contentTypes array contains application/json', () => {
const options: RequestOptions = {
path: '',
httpMethod: 'POST',
contentTypes: ['multipart/form-data', 'application/json'],
queryParams: {
lastModifiedFrom: new Date('2022-08-17T00:00:00.000Z')
}
};
angularHttpClient.request('http://example.com', options, securityOptions, emitters).catch(error =>
fail(error)
);
const req = controller.expectOne('http://example.com?lastModifiedFrom=2022-08-17T00%3A00%3A00.000Z');
expect(req.request.headers.get('Content-Type')).toEqual('application/json');
req.flush(null, { status: 200, statusText: 'Ok' });
});
it('should set Content-type to application/json if contentTypes is not passed to the request options', () => {
const options: RequestOptions = {
path: '',
httpMethod: 'POST',
queryParams: {
lastModifiedFrom: new Date('2022-08-17T00:00:00.000Z')
}
};
angularHttpClient.request('http://example.com', options, securityOptions, emitters).catch(error =>
fail(error)
);
const req = controller.expectOne('http://example.com?lastModifiedFrom=2022-08-17T00%3A00%3A00.000Z');
expect(req.request.headers.get('Content-Type')).toEqual('application/json');
req.flush(null, { status: 200, statusText: 'Ok' });
});
it('should set Accept header to application/json if accepts is not passed to the request options', () => {
const options: RequestOptions = {
path: '',
httpMethod: 'POST',
queryParams: {
lastModifiedFrom: new Date('2022-08-17T00:00:00.000Z')
}
};
angularHttpClient.request('http://example.com', options, securityOptions, emitters).catch(error =>
fail(error)
);
const req = controller.expectOne('http://example.com?lastModifiedFrom=2022-08-17T00%3A00%3A00.000Z');
expect(req.request.headers.get('Accept')).toEqual('application/json');
req.flush(null, { status: 200, statusText: 'Ok' });
});
});

View File

@ -57,17 +57,10 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
on: ee.EmitterMethod;
off: ee.EmitterMethod;
once: ee.EmitterMethod;
_disableCsrf: boolean;
emit: (type: string, ...args: any[]) => void;
private _disableCsrf = false;
private defaultSecurityOptions = {
withCredentials: true,
isBpmRequest: false,
authentications: {},
defaultHeaders: {}
};
get disableCsrf(): boolean {
return this._disableCsrf;
}
@ -76,8 +69,14 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
this._disableCsrf = disableCsrf;
}
constructor(private httpClient: HttpClient
) {
private defaultSecurityOptions = {
withCredentials: true,
isBpmRequest: false,
authentications: {},
defaultHeaders: {}
};
constructor(private httpClient: HttpClient) {
ee(this);
}
@ -217,7 +216,7 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
}
eventEmitter.emit('error', err);
apiClientEmitter.emit('error', err);
apiClientEmitter.emit('error', { ...err, response: { req: err } });
if (err.status === 401) {
eventEmitter.emit('unauthorized');
@ -232,10 +231,10 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
// for backwards compatibility to handle cases in code where we try read response.error.response.body;
const error = {
response: {...err, body: err.error}
...err, body: err.error
};
const alfrescoApiError = new AlfrescoApiResponseError(msg, err.status, error.response);
const alfrescoApiError = new AlfrescoApiResponseError(msg, err.status, error);
return throwError(alfrescoApiError);
}),
takeUntil(abort$)
@ -252,7 +251,7 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
}
private static getBody(options: RequestOptions): any {
const contentType = options.contentType;
const contentType = options.contentType ? options.contentType : AdfHttpClient.jsonPreferredMime(options.contentTypes);
const isFormData = contentType === 'multipart/form-data';
const isFormUrlEncoded = contentType === 'application/x-www-form-urlencoded';
const body = options.bodyParam;
@ -269,20 +268,58 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
}
private getHeaders(options: RequestOptions): HttpHeaders {
const contentType = options.contentType || AdfHttpClient.jsonPreferredMime(options.contentTypes);
const accept = options.accept || AdfHttpClient.jsonPreferredMime(options.accepts);
const optionsHeaders = {
...options.headerParams,
...(options.accept && {Accept: options.accept}),
...((options.contentType) && {'Content-Type': options.contentType})
...(accept && {Accept: accept}),
...((contentType) && {'Content-Type': contentType})
};
if (!this.disableCsrf) {
this.setCsrfToken(optionsHeaders);
}
return new HttpHeaders(optionsHeaders);
}
/**
* Chooses a content type from the given array, with JSON preferred; i.e. return JSON if included, otherwise return the first.
*
* @param contentTypes a contentType array
* @returns The chosen content type, preferring JSON.
*/
private static jsonPreferredMime(contentTypes: readonly string[]): string {
if (!contentTypes?.length) {
return 'application/json';
}
for (let i = 0; i < contentTypes.length; i++) {
if (AdfHttpClient.isJsonMime(contentTypes[i])) {
return contentTypes[i];
}
}
return contentTypes[0];
}
/**
* Checks whether the given content type represents JSON.<br>
* JSON content type examples:<br>
* <ul>
* <li>application/json</li>
* <li>application/json; charset=UTF8</li>
* <li>APPLICATION/JSON</li>
* </ul>
*
* @param contentType The MIME content type to check.
* @returns <code>true</code> if <code>contentType</code> represents JSON, otherwise <code>false</code>.
*/
private static isJsonMime(contentType: string): boolean {
return Boolean(contentType?.match(/^application\/json(;.*)?$/i));
}
private setCsrfToken(optionsHeaders: any) {
const token = this.createCSRFToken();
optionsHeaders['X-CSRF-TOKEN'] = token;

View File

@ -1,25 +0,0 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { InjectionToken } from '@angular/core';
import { Constructor } from './types';
export interface ApiClientFactory {
create<T>(apiClass: Constructor<T>): T;
}
export const API_CLIENT_FACTORY_TOKEN = new InjectionToken<ApiClientFactory>('api-client-factory');

View File

@ -1,66 +0,0 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { AboutApi } from '@alfresco/js-api';
import { TestBed } from '@angular/core/testing';
import { ApiClientFactory, API_CLIENT_FACTORY_TOKEN } from './api-client.factory';
import { ApiClientsService } from './api-clients.service';
import { Constructor } from './types';
class MockApiClientFactory implements ApiClientFactory {
create<T>(apiClass: Constructor<T>): T {
return new apiClass();
}
}
describe('ApiService', () => {
let apiService: ApiClientsService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
ApiClientsService,
{ provide: API_CLIENT_FACTORY_TOKEN, useClass: MockApiClientFactory }
]
});
apiService = TestBed.inject(ApiClientsService);
});
it('should add api to registry', () => {
apiService.register('ActivitiClient.about', AboutApi);
expect(apiService.get('ActivitiClient.about') instanceof AboutApi).toBeTruthy();
});
it('should throw error if we try to get unregisterd API', () => {
expect(() => apiService.get('ActivitiClient.about')).toThrowError();
apiService.register('ActivitiClient.about', AboutApi);
expect(() => apiService.get('ActivitiClient.about')).not.toThrowError();
});
it('should create only single instance of API', () => {
apiService.register('ActivitiClient.about', AboutApi);
const a = apiService.get('ActivitiClient.about');
const b = apiService.get('ActivitiClient.about');
expect(a).toBe(b);
});
});

View File

@ -1,66 +0,0 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { Inject, Injectable } from '@angular/core';
import { ApiClientFactory, API_CLIENT_FACTORY_TOKEN } from './api-client.factory';
import { Constructor, Dictionary } from './types';
/* eslint-disable */
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace AlfrescoCore {
interface ApiRegistry {
}
}
}
/* eslint-enable */
@Injectable()
export class ApiClientsService {
constructor(@Inject(API_CLIENT_FACTORY_TOKEN) private apiCreateFactory: ApiClientFactory) {
}
private registry: Dictionary<Constructor<any>> = {};
private instances: Partial<AlfrescoCore.ApiRegistry> = {};
get<T extends keyof AlfrescoCore.ApiRegistry>(apiName: T): AlfrescoCore.ApiRegistry[T] {
const apiClass = this.registry[apiName];
if (!apiClass) {
throw new Error(`Api not registred: ${apiName}`);
}
return this.instances[apiName] as AlfrescoCore.ApiRegistry[T] ?? this.instantiateApi(apiName);
}
register<T extends keyof AlfrescoCore.ApiRegistry>(apiName: T, api: Constructor<AlfrescoCore.ApiRegistry[T]>): void {
this.registry[apiName] = api;
}
private instantiateApi<T extends keyof AlfrescoCore.ApiRegistry>(apiName: T): AlfrescoCore.ApiRegistry[T] {
const apiClass = this.registry[apiName];
const instance = this.apiCreateFactory.create<AlfrescoCore.ApiRegistry[T]>(apiClass);
this.instances[apiName] = instance;
return instance;
}
}

View File

@ -1,28 +0,0 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { AboutApi, SystemPropertiesApi } from '@alfresco/js-api';
import { NgModule } from '@angular/core';
import { ApiClientsService } from '../../api-clients.service';
@NgModule()
export class ActivitiClientModule {
constructor(private apiClientsService: ApiClientsService) {
this.apiClientsService.register('ActivitiClient.about', AboutApi);
this.apiClientsService.register('ActivitiClient.system-properties', SystemPropertiesApi);
}
}

View File

@ -1,29 +0,0 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { AboutApi, SystemPropertiesApi } from '@alfresco/js-api';
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace AlfrescoCore {
interface ApiRegistry {
['ActivitiClient.about']: AboutApi;
['ActivitiClient.system-properties']: SystemPropertiesApi;
}
}
}

View File

@ -1,38 +0,0 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { ApiClientsService } from '../api-clients.service';
import { ActivitiClientModule } from './activiti/activiti-client.module';
import { DiscoveryClientModule } from './discovery/discovery-client.module';
@NgModule({
imports: [
HttpClientModule,
HttpClientXsrfModule.withOptions({
cookieName: 'CSRF-TOKEN',
headerName: 'X-CSRF-TOKEN'
}),
ActivitiClientModule,
DiscoveryClientModule
],
providers: [
ApiClientsService
]
})
export class AlfrescoJsClientsModule { }

View File

@ -1,27 +0,0 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { DiscoveryApi } from '@alfresco/js-api';
import { NgModule } from '@angular/core';
import { ApiClientsService } from '../../api-clients.service';
@NgModule()
export class DiscoveryClientModule {
constructor(private apiClientsService: ApiClientsService) {
this.apiClientsService.register('DiscoveryClient.discovery', DiscoveryApi);
}
}

View File

@ -1,27 +0,0 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { DiscoveryApi } from '@alfresco/js-api';
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace AlfrescoCore {
interface ApiRegistry {
['DiscoveryClient.discovery']: DiscoveryApi;
}
}
}

View File

@ -15,22 +15,41 @@
* limitations under the License.
*/
export interface SecurityOptions {
readonly withCredentials?: boolean;
readonly authentications?: Authentication;
readonly defaultHeaders?: Record<string, string>;
}
export interface Oauth2 {
refreshToken?: string;
accessToken?: string;
}
export interface BasicAuth {
username?: string;
password?: string;
ticket?: string;
}
export interface Authentication {
basicAuth?: BasicAuth;
oauth2?: Oauth2;
cookie?: string;
type?: string;
}
export interface RequestOptions {
httpMethod?: string;
pathParams?: any;
queryParams?: any;
headerParams?: any;
formParams?: any;
bodyParam?: any;
returnType?: any;
responseType?: string;
accepts?: string[];
contentTypes?: string[];
readonly accept?: string;
readonly contentType?: string;
}
export interface SecurityOptions {
readonly isBpmRequest: boolean;
readonly enableCsrf?: boolean;
readonly withCredentials?: boolean;
readonly authentications: any;
readonly defaultHeaders: Record<string, string>;
}

View File

@ -22,7 +22,7 @@ import { Authentication } from '../authentication';
import { AuthenticationInterceptor, SHOULD_ADD_AUTH_TOKEN } from './authentication.interceptor';
class MockAuthentication extends Authentication {
addTokenToHeader(httpHeaders: HttpHeaders): Observable<HttpHeaders> {
addTokenToHeader(_: string, httpHeaders: HttpHeaders): Observable<HttpHeaders> {
return of(httpHeaders);
}
}

View File

@ -43,7 +43,7 @@ export class AuthenticationInterceptor implements HttpInterceptor {
Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
if (req.context.get(SHOULD_ADD_AUTH_TOKEN)) {
return this.authService.addTokenToHeader(req.headers).pipe(
return this.authService.addTokenToHeader(req.url, req.headers).pipe(
mergeMap((headersWithBearer) => {
const headerWithContentType = this.appendJsonContentType(headersWithBearer);
const kcReq = req.clone({ headers: headerWithContentType});

View File

@ -19,5 +19,5 @@ import { HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
export abstract class Authentication {
public abstract addTokenToHeader(headers: HttpHeaders): Observable<HttpHeaders>;
public abstract addTokenToHeader(requestUrl: string, headers: HttpHeaders): Observable<HttpHeaders>;
}

View File

@ -19,6 +19,8 @@ import { AlfrescoApiConfig } from '@alfresco/js-api';
import { Injectable } from '@angular/core';
import { AppConfigService, AppConfigValues } from '../app-config/app-config.service';
import { AlfrescoApiService } from '../services/alfresco-api.service';
import { StorageService } from '../common/services/storage.service';
import { AuthenticationService, BasicAlfrescoAuthService } from '../auth';
/**
* Create a factory to resolve an api service instance
@ -34,10 +36,22 @@ export function createAlfrescoApiInstance(angularAlfrescoApiService: AlfrescoApi
providedIn: 'root'
})
export class AlfrescoApiLoaderService {
constructor(private readonly appConfig: AppConfigService, private readonly apiService: AlfrescoApiService) {}
constructor(private readonly appConfig: AppConfigService,
private readonly apiService: AlfrescoApiService,
private readonly basicAlfrescoAuthService: BasicAlfrescoAuthService,
private readonly authService: AuthenticationService,
private storageService: StorageService) {
}
async init(): Promise<any> {
await this.appConfig.load();
this.authService.onLogin.subscribe(async () => {
if (this.authService.isOauth() && (this.authService.isALLProvider() || this.authService.isECMProvider())) {
await this.basicAlfrescoAuthService.requireAlfTicket();
}
});
return this.initAngularAlfrescoApi();
}
@ -59,6 +73,8 @@ export class AlfrescoApiLoaderService {
disableCsrf: this.appConfig.get<boolean>(AppConfigValues.DISABLECSRF),
withCredentials: this.appConfig.get<boolean>(AppConfigValues.AUTH_WITH_CREDENTIALS, false),
domainPrefix: this.appConfig.get<string>(AppConfigValues.STORAGE_PREFIX),
ticketEcm: this.storageService.getItem(AppConfigValues.CONTENT_TICKET_STORAGE_LABEL),
ticketBpm: this.storageService.getItem(AppConfigValues.PROCESS_TICKET_STORAGE_LABEL),
oauth2: oauth
});

View File

@ -1,29 +0,0 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { ApiClientFactory, Constructor } from '@alfresco/adf-core/api';
import { Injectable } from '@angular/core';
import { AlfrescoApiService } from '../services/alfresco-api.service';
@Injectable()
export class LegacyClientFactory implements ApiClientFactory {
constructor(private alfrescoApiService: AlfrescoApiService) { }
create<T>(apiClass: Constructor<T>): T {
return new apiClass(this.alfrescoApiService.getInstance());
}
}

View File

@ -1,28 +0,0 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { NgModule } from '@angular/core';
import { API_CLIENT_FACTORY_TOKEN } from '@alfresco/adf-core/api';
import { LegacyClientFactory } from './legacy-api-client.factory';
@NgModule({
providers: [
{ provide: API_CLIENT_FACTORY_TOKEN, useClass: LegacyClientFactory }
]
})
export class LegacyApiClientModule { }

View File

@ -28,8 +28,14 @@ import { AdfHttpClient } from '@alfresco/adf-core/api';
* @returns factory function
*/
export function loadAppConfig(appConfigService: AppConfigService, storageService: StorageService, adfHttpClient: AdfHttpClient) {
return () => appConfigService.load().then(() => {
const init = () => {
adfHttpClient.disableCsrf = appConfigService.get<boolean>(AppConfigValues.DISABLECSRF, true);
storageService.prefix = appConfigService.get<string>(AppConfigValues.STORAGE_PREFIX, '');
appConfigService.select(AppConfigValues.STORAGE_PREFIX).subscribe((property) => {
storageService.prefix = property;
});
}
};
return () => appConfigService.load(init);
};

View File

@ -188,4 +188,14 @@ describe('AppConfigService', () => {
expect(appConfigService.get('files.excluded')[0]).toBe('excluded');
});
it('should execute callback function if is passed to the load method', async () => {
const fakeCallBack = jasmine.createSpy('fakeCallBack');
fakeCallBack.and.returnValue(()=>{});
await appConfigService.load(fakeCallBack);
expect(fakeCallBack).toHaveBeenCalled();
});
});

View File

@ -18,13 +18,14 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ObjectUtils } from '../common/utils/object-utils';
import { Observable, Subject } from 'rxjs';
import { Observable, ReplaySubject } from 'rxjs';
import { map, distinctUntilChanged, take } from 'rxjs/operators';
import { ExtensionConfig, ExtensionService, mergeObjects } from '@alfresco/adf-extensions';
import { OpenidConfiguration } from '../auth/interfaces/openid-configuration.interface';
import { OauthConfigModel } from '../auth/models/oauth-config.model';
/* spellchecker: disable */
// eslint-disable-next-line no-shadow
export enum AppConfigValues {
APP_CONFIG_LANGUAGES_KEY = 'languages',
@ -44,7 +45,9 @@ export enum AppConfigValues {
AUTH_WITH_CREDENTIALS = 'auth.withCredentials',
APPLICATION = 'application',
STORAGE_PREFIX = 'application.storagePrefix',
NOTIFY_DURATION = 'notificationDefaultDuration'
NOTIFY_DURATION = 'notificationDefaultDuration',
CONTENT_TICKET_STORAGE_LABEL = 'ticket-ECM',
PROCESS_TICKET_STORAGE_LABEL = 'ticket-BPM'
}
// eslint-disable-next-line no-shadow
@ -71,11 +74,15 @@ export class AppConfigService {
};
status: Status = Status.INIT;
protected onLoadSubject: Subject<any>;
protected onLoadSubject: ReplaySubject<any>;
onLoad: Observable<any>;
get isLoaded() {
return this.status === Status.LOADED;
}
constructor(protected http: HttpClient, protected extensionService: ExtensionService) {
this.onLoadSubject = new Subject();
this.onLoadSubject = new ReplaySubject();
this.onLoad = this.onLoadSubject.asObservable();
extensionService.setup$.subscribe((config) => {
@ -92,7 +99,7 @@ export class AppConfigService {
select(property: string): Observable<any> {
return this.onLoadSubject
.pipe(
map((config) => config[property]),
map((config) => ObjectUtils.getValue(config, property)),
distinctUntilChanged()
);
}
@ -160,8 +167,7 @@ export class AppConfigService {
this.onLoadSubject.next(this.config);
}
protected onDataLoaded(data: any) {
this.config = Object.assign({}, this.config, data || {});
protected onDataLoaded() {
this.onLoadSubject.next(this.config);
this.extensionService.setup$
@ -182,9 +188,10 @@ export class AppConfigService {
/**
* Loads the config file.
*
* @param callback an optional callback to execute when configuration is loaded
* @returns Notification when loading is complete
*/
load(): Promise<any> {
load(callback?: (...args: any[]) => any): Promise<any> {
return new Promise((resolve) => {
const configUrl = `app.config.json?v=${Date.now()}`;
@ -193,8 +200,10 @@ export class AppConfigService {
this.http.get(configUrl).subscribe(
(data: any) => {
this.status = Status.LOADED;
this.config = Object.assign({}, this.config, data || {});
callback?.();
resolve(data);
this.onDataLoaded(data);
this.onDataLoaded();
},
() => {
// eslint-disable-next-line no-console
@ -227,6 +236,8 @@ export class AppConfigService {
resolve(res);
},
error: (err: any) => {
// eslint-disable-next-line no-console
console.error('hostIdp not correctly configured or unreachable');
reject(err);
}
});
@ -262,4 +273,5 @@ export class AppConfigService {
return result;
}
}

View File

@ -17,9 +17,10 @@
import { HttpClient, HttpHandler, HttpRequest } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { Observable, of } from 'rxjs';
import { EMPTY, Observable, of } from 'rxjs';
import { AuthBearerInterceptor } from './auth-bearer.interceptor';
import { AuthenticationService } from '../services/authentication.service';
import { RedirectAuthService } from '../oidc/redirect-auth.service';
const mockNext: HttpHandler = {
handle: () => new Observable(subscriber => {
@ -40,7 +41,8 @@ describe('AuthBearerInterceptor', () => {
HttpClient,
HttpHandler,
AuthBearerInterceptor,
AuthenticationService
AuthenticationService,
{ provide: RedirectAuthService, useValue: { onLogin: EMPTY } }
]
});
@ -85,7 +87,7 @@ describe('AuthBearerInterceptor', () => {
});
it('should interceptor add auth token to every URL if excluded URLs array is empty', () => {
spyOn(authService, 'getBearerExcludedUrls').and.returnValue([]);
spyOnProperty<any>(interceptor, 'bearerExcludedUrls').and.returnValue([]);
const mockUrls = [
'http://example.com/auth/realms/testpath',

View File

@ -16,40 +16,37 @@
*/
import { throwError as observableThrowError, Observable } from 'rxjs';
import { Injectable, Injector } from '@angular/core';
import { Injectable } from '@angular/core';
import {
HttpHandler, HttpInterceptor, HttpRequest,
HttpSentEvent, HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpUserEvent, HttpHeaders
} from '@angular/common/http';
import { AuthenticationService } from '../services/authentication.service';
import { catchError, mergeMap } from 'rxjs/operators';
import { AuthenticationService } from '../services/authentication.service';
@Injectable()
export class AuthBearerInterceptor implements HttpInterceptor {
private _bearerExcludedUrls: readonly string[] = ['resources/', 'assets/', 'auth/realms', 'idp/'];
private excludedUrlsRegex: RegExp[];
constructor(private injector: Injector, private authService: AuthenticationService) { }
constructor(private authenticationService: AuthenticationService) { }
private loadExcludedUrlsRegex() {
const excludedUrls = this.authService.getBearerExcludedUrls();
const excludedUrls = this.bearerExcludedUrls;
this.excludedUrlsRegex = excludedUrls.map((urlPattern) => new RegExp(`^https?://[^/]+/${urlPattern}`, 'i')) || [];
}
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
this.authService = this.injector.get(AuthenticationService);
if (!this.authService?.getBearerExcludedUrls()) {
return next.handle(req);
}
if (!this.excludedUrlsRegex) {
this.loadExcludedUrlsRegex();
}
const urlRequest = req.url;
const shallPass: boolean = this.excludedUrlsRegex.some((regex) => regex.test(urlRequest));
const requestUrl = req.url;
const shallPass: boolean = this.excludedUrlsRegex.some((regex) => regex.test(requestUrl));
if (shallPass) {
return next.handle(req)
.pipe(
@ -57,10 +54,10 @@ export class AuthBearerInterceptor implements HttpInterceptor {
);
}
return this.authService.addTokenToHeader(req.headers)
return this.authenticationService.addTokenToHeader(requestUrl, req.headers)
.pipe(
mergeMap((headersWithBearer) => {
const headerWithContentType = this.appendJsonContentType(headersWithBearer);
const headerWithContentType = this.appendJsonContentType(headersWithBearer, req.body);
const kcReq = req.clone({ headers: headerWithContentType});
return next.handle(kcReq)
.pipe(
@ -70,7 +67,7 @@ export class AuthBearerInterceptor implements HttpInterceptor {
);
}
private appendJsonContentType(headers: HttpHeaders): HttpHeaders {
private appendJsonContentType(headers: HttpHeaders, reqBody: any): HttpHeaders {
// prevent adding any content type, to properly handle formData with boundary browser generated value,
// as adding any Content-Type its going to break the upload functionality
@ -79,11 +76,15 @@ export class AuthBearerInterceptor implements HttpInterceptor {
return headers.delete('Content-Type');
}
if (!headers.get('Content-Type')) {
if (!headers.get('Content-Type') && !(reqBody instanceof FormData)) {
return headers.set('Content-Type', 'application/json;charset=UTF-8');
}
return headers;
}
protected get bearerExcludedUrls(): readonly string[] {
return this._bearerExcludedUrls;
}
}

View File

@ -0,0 +1,374 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
import { Authentication } from '../interfaces/authentication.interface';
import { CookieService } from '../../common/services/cookie.service';
import { ContentAuth } from './content-auth';
import { ProcessAuth } from './process-auth';
import { catchError, map } from 'rxjs/operators';
import { from, Observable } from 'rxjs';
import { RedirectionModel } from '../models/redirection.model';
import { BaseAuthenticationService } from '../services/base-authentication.service';
import { LogService } from '../../common';
import { HttpHeaders } from '@angular/common/http';
const REMEMBER_ME_COOKIE_KEY = 'ALFRESCO_REMEMBER_ME';
const REMEMBER_ME_UNTIL = 1000 * 60 * 60 * 24 * 30;
@Injectable({
providedIn: 'root'
})
export class BasicAlfrescoAuthService extends BaseAuthenticationService {
protected redirectUrl: RedirectionModel = null;
authentications: Authentication = {
basicAuth: {
ticket: ''
},
type: 'basic'
};
constructor(
logService: LogService,
appConfig: AppConfigService,
cookie: CookieService,
private contentAuth: ContentAuth,
private processAuth: ProcessAuth
) {
super(appConfig, cookie, logService);
this.appConfig.onLoad
.subscribe(() => {
if (!this.isOauth() && this.isLoggedIn()) {
this.onLogin.next('logged-in');
}
});
this.contentAuth.onLogout.pipe(map((event) => {
this.onLogout.next(event);
}));
this.contentAuth.onLogin.pipe(map((event) => {
this.onLogin.next(event);
}));
this.contentAuth.onError.pipe(map((event) => {
this.onError.next(event);
}));
this.processAuth.onLogout.pipe(map((event) => {
this.onLogout.next(event);
}));
this.processAuth.onLogin.pipe(map((event) => {
this.onLogin.next(event);
}));
this.processAuth.onError.pipe(map((event) => {
this.onError.next(event);
}));
}
/**
* Logs the user in.
*
* @param username Username for the login
* @param password Password for the login
* @param rememberMe Stores the user's login details if true
* @returns Object with auth type ("ECM", "BPM" or "ALL") and auth ticket
*/
login(username: string, password: string, rememberMe: boolean = false): Observable<{ type: string; ticket: any }> {
return from(this.executeLogin(username, password)).pipe(
map((response: any) => {
this.saveRememberMeCookie(rememberMe);
this.onLogin.next(response);
return {
type: this.appConfig.get(AppConfigValues.PROVIDERS),
ticket: response
};
}),
catchError((err) => this.handleError(err))
);
}
/**
* login Alfresco API
*
* @param username username to login
* @param password password to login
* @returns A promise that returns {new authentication ticket} if resolved and {error} if rejected.
*/
async executeLogin(username: string, password: string): Promise<any> {
if (!this.isCredentialValid(username) || !this.isCredentialValid(password)) {
return Promise.reject(new Error('missing username or password'));
}
if (username) {
username = username.trim();
}
if (this.isBPMProvider()) {
try {
return await this.processAuth.login(username, password);
} catch (e) {
return Promise.reject(e);
}
} else if (this.isECMProvider()) {
try {
return await this.contentAuth.login(username, password);
} catch (e) {
return Promise.reject(e);
}
} else if (this.isALLProvider()) {
return this.loginBPMECM(username, password);
} else {
return Promise.reject(new Error('Unknown configuration'));
}
}
private loginBPMECM(username: string, password: string): Promise<any> {
const contentPromise = this.contentAuth.login(username, password);
const processPromise = this.processAuth.login(username, password);
return new Promise((resolve, reject) => {
Promise.all([contentPromise, processPromise]).then(
(data) => {
this.onLogin.next('success');
resolve(data);
},
(error) => {
this.contentAuth.invalidateSession();
this.processAuth.invalidateSession();
if (error.status === 401) {
this.onError.next('unauthorized');
}
this.onError.next('error');
reject(error);
});
});
}
/**
* Checks whether the "remember me" cookie was set or not.
*
* @returns True if set, false otherwise
*/
isRememberMeSet(): boolean {
return this.cookie.getItem(REMEMBER_ME_COOKIE_KEY) !== null;
}
/**
* Saves the "remember me" cookie as either a long-life cookie or a session cookie.
*
* @param rememberMe Enables a long-life cookie
*/
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);
}
isCredentialValid(credential: string): boolean {
return credential !== undefined && credential !== null && credential !== '';
}
getToken(): string {
if (this.isBPMProvider()) {
return this.processAuth.getToken();
} else if (this.isECMProvider()) {
return this.contentAuth.getToken();
} else if (this.isALLProvider()) {
return this.contentAuth.getToken();
} else {
return '';
}
}
/**
* @deprecated
* @returns content auth token
*/
getTicketEcm(): string {
return this.contentAuth.getToken();
}
/**
* @deprecated
* @returns process auth token
*/
getTicketBpm(): string {
return this.processAuth.getToken();
}
isBpmLoggedIn(): boolean {
return this.processAuth.isLoggedIn();
}
isEcmLoggedIn(): boolean {
return this.contentAuth.isLoggedIn();
}
isLoggedIn(): boolean {
const authWithCredentials = this.isKerberosEnabled();
if (this.isBPMProvider()) {
return this.processAuth.isLoggedIn();
} else if (this.isECMProvider()) {
return authWithCredentials ? true : this.contentAuth.isLoggedIn();
} else if (this.isALLProvider()) {
return authWithCredentials ? true : (this.contentAuth.isLoggedIn() && this.processAuth.isLoggedIn());
} else {
return false;
}
}
/**
* logout Alfresco API
*/
async logout(): Promise<any> {
if (this.isBPMProvider()) {
return this.processAuth.logout();
} else if (this.isECMProvider()) {
return this.contentAuth.logout();
} else if (this.isALLProvider()) {
return this.logoutBPMECM();
}
return Promise.resolve();
}
private logoutBPMECM(): Promise<any> {
const contentPromise = this.contentAuth.logout();
const processPromise = this.processAuth.logout();
return new Promise((resolve, reject) => {
Promise.all([contentPromise, processPromise]).then(
() => {
this.contentAuth.ticket = undefined;
this.processAuth.ticket = undefined;
this.onLogout.next('logout');
resolve('logout');
},
(error) => {
if (error.status === 401) {
this.onError.next('unauthorized');
}
this.onError.next('error');
reject(error);
});
});
}
reset(): void {
}
/**
* Gets the URL to redirect to after login.
*
* @returns The redirect URL
*/
getRedirect(): string {
const provider = this.appConfig.get<string>(AppConfigValues.PROVIDERS);
return this.hasValidRedirection(provider) ? this.redirectUrl.url : null;
}
setRedirect(url?: RedirectionModel) {
this.redirectUrl = url;
}
private hasValidRedirection(provider: string): boolean {
return this.redirectUrl && (this.redirectUrl.provider === provider || this.hasSelectedProviderAll(provider));
}
private hasSelectedProviderAll(provider: string): boolean {
return this.redirectUrl && (this.redirectUrl.provider === 'ALL' || provider === 'ALL');
}
getBpmUsername(): string {
return this.processAuth.getUsername();
}
getEcmUsername(): string {
return this.contentAuth.getUsername();
}
getUsername(): string {
if (this.isBPMProvider()) {
return this.processAuth.getUsername();
} else if (this.isECMProvider()) {
return this.contentAuth.getUsername();
} else {
return this.contentAuth.getUsername();
}
}
/**
* Does kerberos enabled?
*
* @returns True if enabled, false otherwise
*/
isKerberosEnabled(): boolean {
return this.appConfig.get<boolean>(AppConfigValues.AUTH_WITH_CREDENTIALS, false);
}
getAuthHeaders(requestUrl: string, header: HttpHeaders): HttpHeaders {
return this.addBasicAuth(requestUrl, header);
}
private addBasicAuth(requestUrl: string, header: HttpHeaders): HttpHeaders {
const ticket = this.getTicketEcmBase64(requestUrl);
if (!ticket) {
return header;
}
return header.set('Authorization', ticket);
}
async requireAlfTicket(): Promise<void> {
return this.contentAuth.requireAlfTicket();
}
/**
* Gets the BPM ticket from the Storage in Base 64 format.
*
* @param requestUrl the request url
* @returns The ticket or `null` if none was found
*/
private getTicketEcmBase64(requestUrl: string): string | null {
let ticket = null;
const contextRootBpm = this.appConfig.get<string>(AppConfigValues.CONTEXTROOTBPM) || 'activiti-app';
const contextRoot = this.appConfig.get<string>(AppConfigValues.CONTEXTROOTECM) || 'alfresco';
if (contextRoot && requestUrl.indexOf(contextRoot) !== -1) {
ticket = 'Basic ' + btoa(this.contentAuth.getToken());
} else if (contextRootBpm && requestUrl.indexOf(contextRootBpm) !== -1) {
ticket = 'Basic ' + this.processAuth.getToken();
}
return ticket;
}
}

View File

@ -0,0 +1,220 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { AdfHttpClient } from '@alfresco/adf-core/api';
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
import { StorageService } from '../../common/services/storage.service';
import { ReplaySubject, Subject } from 'rxjs';
import { Authentication } from '../interfaces/authentication.interface';
export interface TicketBody {
userId?: string;
password?: string;
}
export interface TicketEntry {
entry: {
id?: string;
userId?: string;
};
}
@Injectable({
providedIn: 'root'
})
export class ContentAuth {
onLogin = new ReplaySubject<any>(1);
onLogout = new ReplaySubject<any>(1);
onError = new Subject<any>();
ticket: string;
config = {
ticketEcm: null
};
authentications: Authentication = {
basicAuth: {
ticket: ''
},
type: 'basic'
};
get basePath(): string {
const contextRootEcm = this.appConfigService.get<string>(AppConfigValues.CONTEXTROOTECM) || 'alfresco';
return this.appConfigService.get<string>(AppConfigValues.ECMHOST) + '/' + contextRootEcm + '/api/-default-/public/authentication/versions/1';
}
constructor(private appConfigService: AppConfigService,
private adfHttpClient: AdfHttpClient,
private storageService: StorageService) {
this.appConfigService.onLoad.subscribe(() => {
this.setConfig();
});
}
private setConfig() {
if (this.storageService.getItem(AppConfigValues.CONTENT_TICKET_STORAGE_LABEL)) {
this.setTicket(this.storageService.getItem(AppConfigValues.CONTENT_TICKET_STORAGE_LABEL));
}
}
saveUsername(username: string) {
this.storageService.setItem('ACS_USERNAME', username);
}
getUsername() {
return this.storageService.getItem('ACS_USERNAME');
}
/**
* login Alfresco API
*
* @param username username to login
* @param password password to login
* @returns A promise that returns {new authentication ticket} if resolved and {error} if rejected.
*/
login(username: string, password: string): Promise<any> {
this.authentications.basicAuth.username = username;
this.authentications.basicAuth.password = password;
const loginRequest: any = {};
loginRequest.userId = this.authentications.basicAuth.username;
loginRequest.password = this.authentications.basicAuth.password;
return new Promise((resolve, reject) => {
this.createTicket(loginRequest)
.then((data: any) => {
this.saveUsername(username);
this.setTicket(data.entry.id);
this.adfHttpClient.emit('success');
this.onLogin.next('success');
resolve(data.entry.id);
})
.catch((error) => {
this.saveUsername('');
if (error.status === 401) {
this.adfHttpClient.emit('unauthorized', error);
this.onError.next('unauthorized');
} else if (error.status === 403) {
this.adfHttpClient.emit('forbidden', error);
this.onError.next('forbidden');
} else {
this.adfHttpClient.emit('error', error);
this.onError.next('error');
}
reject(error);
});
});
}
/**
* logout Alfresco API
*
* @returns A promise that returns { authentication ticket} if resolved and {error} if rejected.
*/
logout(): Promise<any> {
this.saveUsername('');
return new Promise((resolve, reject) => {
this.deleteTicket().then(
() => {
this.invalidateSession();
this.adfHttpClient.emit('logout');
this.onLogout.next('logout');
resolve('logout');
},
(error) => {
if (error.status === 401) {
this.adfHttpClient.emit('unauthorized');
this.onError.next('unauthorized');
}
this.adfHttpClient.emit('error');
this.onError.next('error');
reject(error);
});
});
}
/**
* Set the current Ticket
*
* @param ticket a string representing the ticket
*/
setTicket(ticket: string) {
this.authentications.basicAuth.username = 'ROLE_TICKET';
this.authentications.basicAuth.password = ticket;
this.config.ticketEcm = ticket;
this.storageService.setItem(AppConfigValues.CONTENT_TICKET_STORAGE_LABEL, ticket);
this.ticket = ticket;
}
/**
* @returns the current Ticket
*/
getToken(): string {
if (!this.ticket) {
this.onError.next('error');
}
return this.ticket;
}
invalidateSession() {
this.storageService.removeItem(AppConfigValues.CONTENT_TICKET_STORAGE_LABEL);
this.authentications.basicAuth.username = null;
this.authentications.basicAuth.password = null;
this.config.ticketEcm = null;
this.ticket = null;
}
/**
* @returns If the client is logged in return true
*/
isLoggedIn(): boolean {
return !!this.ticket;
}
/**
* @returns return the Authentication
*/
getAuthentication() {
return this.authentications;
}
createTicket(ticketBodyCreate: TicketBody): Promise<TicketEntry> {
if (ticketBodyCreate === null || ticketBodyCreate === undefined) {
this.onError.next((`Missing param ticketBodyCreate`));
throw new Error(`Missing param ticketBodyCreate`);
}
return this.adfHttpClient.post(this.basePath + '/tickets', {bodyParam: ticketBodyCreate});
}
async requireAlfTicket(): Promise<void> {
const ticket = await this.adfHttpClient.get(this.basePath + '/tickets/-me-');
this.setTicket(ticket.entry.id);
}
deleteTicket(): Promise<any> {
return this.adfHttpClient.delete(this.basePath + '/tickets/-me-');
}
}

View File

@ -0,0 +1,210 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { AdfHttpClient } from '@alfresco/adf-core/api';
import { Authentication } from '../interfaces/authentication.interface';
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
import { StorageService } from '../../common/services/storage.service';
import { ReplaySubject, Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ProcessAuth {
onLogin = new ReplaySubject<any>(1);
onLogout = new ReplaySubject<any>(1);
onError = new Subject<any>();
ticket: string;
config = {
ticketBpm: null
};
authentications: Authentication = {
basicAuth: {ticket: ''}, type: 'activiti'
};
get basePath(): string {
const contextRootBpm = this.appConfigService.get<string>(AppConfigValues.CONTEXTROOTBPM) || 'activiti-app';
return this.appConfigService.get<string>(AppConfigValues.BPMHOST) + '/' + contextRootBpm;
}
constructor(private appConfigService: AppConfigService,
private adfHttpClient: AdfHttpClient,
private storageService: StorageService) {
this.appConfigService.onLoad.subscribe(() => {
this.setConfig();
});
}
private setConfig() {
this.ticket = undefined;
this.setTicket(this.storageService.getItem(AppConfigValues.PROCESS_TICKET_STORAGE_LABEL));
}
saveUsername(username: string) {
this.storageService.setItem('APS_USERNAME', username);
}
getUsername() {
return this.storageService.getItem('APS_USERNAME');
}
/**
* login Activiti API
*
* @param username Username to login
* @param password Password to login
* @returns A promise that returns {new authentication ticket} if resolved and {error} if rejected.
*/
login(username: string, password: string): Promise<any> {
this.authentications.basicAuth.username = username;
this.authentications.basicAuth.password = password;
const options = {
headerParams: {
'Content-Type': 'application/x-www-form-urlencoded',
'Cache-Control': 'no-cache'
},
formParams: {
j_username: this.authentications.basicAuth.username,
j_password: this.authentications.basicAuth.password,
_spring_security_remember_me: true,
submit: 'Login'
},
contentType: 'application/x-www-form-urlencoded',
accept: 'application/json'
};
const promise: any = new Promise((resolve, reject) => {
this.adfHttpClient.post(this.basePath + '/app/authentication', options).then(
() => {
this.saveUsername(username);
const ticket = this.basicAuth(this.authentications.basicAuth.username, this.authentications.basicAuth.password);
this.setTicket(ticket);
this.onLogin.next('success');
this.adfHttpClient.emit('success');
this.adfHttpClient.emit('logged-in');
resolve(ticket);
},
(error) => {
this.saveUsername('');
if (error.status === 401) {
this.adfHttpClient.emit('unauthorized', error);
this.onError.next('unauthorized');
} else if (error.status === 403) {
this.adfHttpClient.emit('forbidden', error);
this.onError.next('forbidden');
} else {
this.adfHttpClient.emit('error', error);
this.onError.next('error');
}
reject(error);
});
});
return promise;
}
/**
* logout Alfresco API
*
* @returns A promise that returns {new authentication ticket} if resolved and {error} if rejected.
*/
async logout(): Promise<any> {
this.saveUsername('');
return new Promise((resolve, reject) => {
this.adfHttpClient.get(this.basePath + `/app/logout`, {}).then(
() => {
this.invalidateSession();
this.onLogout.next('logout');
this.adfHttpClient.emit('logout');
resolve('logout');
},
(error) => {
if (error.status === 401) {
this.adfHttpClient.emit('unauthorized');
this.onError.next('unauthorized');
}
this.adfHttpClient.emit('error');
this.onError.next('error');
reject(error);
});
});
}
basicAuth(username: string, password: string): string {
const str: any = username + ':' + password;
let base64;
if (typeof Buffer === 'function') {
base64 = Buffer.from(str.toString(), 'binary').toString('base64');
} else {
base64 = btoa(str);
}
return `Basic ${base64}`;
}
/**
* Set the current Ticket
*
* @param ticket a string representing the ticket
*/
setTicket(ticket: string) {
if (ticket && ticket !== 'null') {
this.authentications.basicAuth.ticket = ticket;
this.authentications.basicAuth.password = null;
this.config.ticketBpm = ticket;
this.storageService.setItem(AppConfigValues.PROCESS_TICKET_STORAGE_LABEL, ticket);
this.ticket = ticket;
}
}
invalidateSession() {
this.storageService.removeItem(AppConfigValues.PROCESS_TICKET_STORAGE_LABEL);
this.authentications.basicAuth.ticket = null;
this.authentications.basicAuth.password = null;
this.authentications.basicAuth.username = null;
this.config.ticketBpm = null;
this.ticket = null;
}
/**
* @returns the current Ticket
*/
getToken(): string {
if (!this.ticket) {
this.onError.next('error');
return null;
}
return this.ticket;
}
/**
* @returns If the client is logged in return true
*/
isLoggedIn(): boolean {
return !!this.ticket;
}
}

View File

@ -17,24 +17,35 @@
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild, UrlTree } from '@angular/router';
import { AuthenticationService } from '../services/authentication.service';
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
import {
AppConfigService,
AppConfigValues
} from '../../app-config/app-config.service';
import { OauthConfigModel } from '../models/oauth-config.model';
import { MatDialog } from '@angular/material/dialog';
import { StorageService } from '../../common/services/storage.service';
import { Observable } from 'rxjs';
import { inject } from '@angular/core';
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
import { OidcAuthenticationService } from '../services/oidc-authentication.service';
export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
protected authenticationService = inject(AuthenticationService);
protected router = inject(Router);
protected appConfigService = inject(AppConfigService);
protected dialog = inject(MatDialog);
private storageService = inject(StorageService);
protected get withCredentials(): boolean {
return this.appConfigService.get<boolean>('auth.withCredentials', false);
}
constructor(
protected authenticationService: AuthenticationService,
protected basicAlfrescoAuthService: BasicAlfrescoAuthService,
protected oidcAuthenticationService: OidcAuthenticationService,
protected router: Router,
protected appConfigService: AppConfigService,
protected dialog: MatDialog,
private storageService: StorageService
) {
}
abstract checkLogin(
activeRoute: ActivatedRouteSnapshot,
redirectUrl: string
@ -78,15 +89,17 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
let urlToRedirect = `/${this.getLoginRoute()}`;
if (!this.authenticationService.isOauth()) {
this.authenticationService.setRedirect({
this.basicAlfrescoAuthService.setRedirect({
provider: this.getProvider(),
url
});
urlToRedirect = `${urlToRedirect}?redirectUrl=${url}`;
return this.navigate(urlToRedirect);
} else if (this.getOauthConfig().silentLogin && !this.authenticationService.isPublicUrl()) {
this.authenticationService.ssoImplicitLogin();
} else if (this.getOauthConfig().silentLogin && !this.oidcAuthenticationService.isPublicUrl()) {
if (!this.oidcAuthenticationService.hasValidIdToken() || !this.oidcAuthenticationService.hasValidAccessToken()) {
this.oidcAuthenticationService.ssoImplicitLogin();
}
} else {
return this.navigate(urlToRedirect);
}
@ -101,7 +114,13 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
}
protected getOauthConfig(): OauthConfigModel {
return this.appConfigService.oauth2;
return (
this.appConfigService &&
this.appConfigService.get<OauthConfigModel>(
AppConfigValues.OAUTHCONFIG,
null
)
);
}
protected getLoginRoute(): string {
@ -113,12 +132,21 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
}
protected isOAuthWithoutSilentLogin(): boolean {
const oauth = this.appConfigService.oauth2;
return this.authenticationService.isOauth() && !!oauth && !oauth.silentLogin;
const oauth = this.appConfigService.get<OauthConfigModel>(
AppConfigValues.OAUTHCONFIG,
null
);
return (
this.authenticationService.isOauth() && !!oauth && !oauth.silentLogin
);
}
protected isSilentLogin(): boolean {
const oauth = this.appConfigService.oauth2;
const oauth = this.appConfigService.get<OauthConfigModel>(
AppConfigValues.OAUTHCONFIG,
null
);
return this.authenticationService.isOauth() && oauth && oauth.silentLogin;
}
}

View File

@ -23,11 +23,16 @@ import { RouterStateSnapshot, Router } from '@angular/router';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { MatDialog } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core';
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
import { OidcAuthenticationService } from '../services/oidc-authentication.service';
describe('AuthGuardService BPM', () => {
let authGuard: AuthGuardBpm;
let authService: AuthenticationService;
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
let oidcAuthenticationService: OidcAuthenticationService;
let router: Router;
let appConfigService: AppConfigService;
@ -36,9 +41,21 @@ describe('AuthGuardService BPM', () => {
imports: [
TranslateModule.forRoot(),
CoreTestingModule
],
providers: [
{
provide: OidcAuthenticationService, useValue: {
ssoImplicitLogin: () => { },
isPublicUrl: () => false,
hasValidIdToken: () => false,
isLoggedIn: () => false
}
}
]
});
localStorage.clear();
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
oidcAuthenticationService = TestBed.inject(OidcAuthenticationService);
authService = TestBed.inject(AuthenticationService);
authGuard = TestBed.inject(AuthGuardBpm);
router = TestBed.inject(Router);
@ -53,8 +70,8 @@ describe('AuthGuardService BPM', () => {
spyOn(router, 'navigateByUrl').and.stub();
spyOn(authService, 'isBpmLoggedIn').and.returnValue(false);
spyOn(authService, 'isOauth').and.returnValue(true);
spyOn(authService, 'isPublicUrl').and.returnValue(false);
spyOn(authService, 'ssoImplicitLogin').and.stub();
spyOn(oidcAuthenticationService, 'isPublicUrl').and.returnValue(false);
spyOn(oidcAuthenticationService, 'ssoImplicitLogin').and.stub();
appConfigService.config.oauth2 = {
silentLogin: true,
@ -69,7 +86,7 @@ describe('AuthGuardService BPM', () => {
const route = { url: 'abc' } as RouterStateSnapshot;
expect(await authGuard.canActivate(null, route)).toBeFalsy();
expect(authService.ssoImplicitLogin).toHaveBeenCalledTimes(1);
expect(oidcAuthenticationService.ssoImplicitLogin).toHaveBeenCalledTimes(1);
});
it('if the alfresco js api is logged in should canActivate be true', async () => {
@ -130,53 +147,53 @@ describe('AuthGuardService BPM', () => {
});
it('should set redirect url', () => {
spyOn(authService, 'setRedirect').and.callThrough();
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
spyOn(router, 'navigateByUrl').and.stub();
const route = { url: 'some-url' } as RouterStateSnapshot;
authGuard.canActivate(null, route);
expect(authService.setRedirect).toHaveBeenCalledWith({
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'BPM', url: 'some-url'
});
expect(authService.getRedirect()).toEqual('some-url');
expect(basicAlfrescoAuthService.getRedirect()).toEqual('some-url');
});
it('should set redirect navigation commands with query params', () => {
spyOn(authService, 'setRedirect').and.callThrough();
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
spyOn(router, 'navigateByUrl').and.stub();
const route = { url: 'some-url;q=123' } as RouterStateSnapshot;
authGuard.canActivate(null, route);
expect(authService.setRedirect).toHaveBeenCalledWith({
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'BPM', url: 'some-url;q=123'
});
expect(authService.getRedirect()).toEqual('some-url;q=123');
expect(basicAlfrescoAuthService.getRedirect()).toEqual('some-url;q=123');
});
it('should set redirect navigation commands with query params', () => {
spyOn(authService, 'setRedirect').and.callThrough();
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
spyOn(router, 'navigateByUrl').and.stub();
const route = { url: '/' } as RouterStateSnapshot;
authGuard.canActivate(null, route);
expect(authService.setRedirect).toHaveBeenCalledWith({
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'BPM', url: '/'
});
expect(authService.getRedirect()).toEqual('/');
expect(basicAlfrescoAuthService.getRedirect()).toEqual('/');
});
it('should get redirect url from config if there is one configured', () => {
appConfigService.config.loginRoute = 'fakeLoginRoute';
spyOn(authService, 'setRedirect').and.callThrough();
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
spyOn(router, 'navigateByUrl').and.stub();
const route = { url: 'some-url' } as RouterStateSnapshot;
authGuard.canActivate(null, route);
expect(authService.setRedirect).toHaveBeenCalledWith({
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'BPM', url: 'some-url'
});
expect(router.navigateByUrl).toHaveBeenCalledWith(router.parseUrl('/fakeLoginRoute?redirectUrl=some-url'));
@ -187,13 +204,13 @@ describe('AuthGuardService BPM', () => {
spyOn(materialDialog, 'closeAll');
spyOn(authService, 'setRedirect').and.callThrough();
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
spyOn(router, 'navigateByUrl').and.stub();
const route = { url: 'some-url' } as RouterStateSnapshot;
authGuard.canActivate(null, route);
expect(authService.setRedirect).toHaveBeenCalledWith({
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'BPM', url: 'some-url'
});

View File

@ -16,13 +16,30 @@
*/
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, UrlTree } from '@angular/router';
import { ActivatedRouteSnapshot, Router, UrlTree } from '@angular/router';
import { AppConfigService } from '../../app-config/app-config.service';
import { AuthenticationService } from '../services/authentication.service';
import { AuthGuardBase } from './auth-guard-base';
import { MatDialog } from '@angular/material/dialog';
import { StorageService } from '../../common/services/storage.service';
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
import { OidcAuthenticationService } from '../services/oidc-authentication.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuardBpm extends AuthGuardBase {
constructor(authenticationService: AuthenticationService,
basicAlfrescoAuthService: BasicAlfrescoAuthService,
oidcAuthenticationService: OidcAuthenticationService,
router: Router,
appConfigService: AppConfigService,
dialog: MatDialog,
storageService: StorageService) {
super(authenticationService,basicAlfrescoAuthService, oidcAuthenticationService,router, appConfigService, dialog, storageService);
}
async checkLogin(_: ActivatedRouteSnapshot, redirectUrl: string): Promise<boolean | UrlTree> {
if (this.authenticationService.isBpmLoggedIn() || this.withCredentials) {
return true;

View File

@ -23,11 +23,15 @@ import { RouterStateSnapshot, Router } from '@angular/router';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { MatDialog } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core';
import { OidcAuthenticationService } from '../services/oidc-authentication.service';
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
describe('AuthGuardService ECM', () => {
let authGuard: AuthGuardEcm;
let authService: AuthenticationService;
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
let oidcAuthenticationService: OidcAuthenticationService;
let router: Router;
let appConfigService: AppConfigService;
@ -36,9 +40,21 @@ describe('AuthGuardService ECM', () => {
imports: [
TranslateModule.forRoot(),
CoreTestingModule
],
providers: [
{
provide: OidcAuthenticationService, useValue: {
ssoImplicitLogin: () => { },
isPublicUrl: () => false,
hasValidIdToken: () => false,
isLoggedIn: () => false
}
}
]
});
localStorage.clear();
oidcAuthenticationService = TestBed.inject(OidcAuthenticationService);
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
authService = TestBed.inject(AuthenticationService);
authGuard = TestBed.inject(AuthGuardEcm);
router = TestBed.inject(Router);
@ -98,8 +114,8 @@ describe('AuthGuardService ECM', () => {
it('should redirect url if the alfresco js api is NOT logged in and isOAuth with silentLogin', async () => {
spyOn(authService, 'isEcmLoggedIn').and.returnValue(false);
spyOn(authService, 'isOauth').and.returnValue(true);
spyOn(authService, 'isPublicUrl').and.returnValue(false);
spyOn(authService, 'ssoImplicitLogin').and.stub();
spyOn(oidcAuthenticationService, 'isPublicUrl').and.returnValue(false);
spyOn(oidcAuthenticationService, 'ssoImplicitLogin').and.stub();
appConfigService.config.oauth2 = {
silentLogin: true,
@ -113,7 +129,7 @@ describe('AuthGuardService ECM', () => {
const route = {url : 'abc'} as RouterStateSnapshot;
expect(await authGuard.canActivate(null, route)).toBeFalsy();
expect(authService.ssoImplicitLogin).toHaveBeenCalledTimes(1);
expect(oidcAuthenticationService.ssoImplicitLogin).toHaveBeenCalledTimes(1);
});
it('should not redirect url if NOT logged in and isOAuth but no silentLogin configured', async () => {
@ -128,53 +144,53 @@ describe('AuthGuardService ECM', () => {
});
it('should set redirect navigation commands', () => {
spyOn(authService, 'setRedirect').and.callThrough();
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
spyOn(router, 'navigateByUrl').and.stub();
const route = { url: 'some-url' } as RouterStateSnapshot;
authGuard.canActivate(null, route);
expect(authService.setRedirect).toHaveBeenCalledWith({
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'ECM', url: 'some-url'
});
expect(authService.getRedirect()).toEqual('some-url');
expect(basicAlfrescoAuthService.getRedirect()).toEqual('some-url');
});
it('should set redirect navigation commands with query params', () => {
spyOn(authService, 'setRedirect').and.callThrough();
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
spyOn(router, 'navigateByUrl').and.stub();
const route = { url: 'some-url;q=123' } as RouterStateSnapshot;
authGuard.canActivate(null, route);
expect(authService.setRedirect).toHaveBeenCalledWith({
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'ECM', url: 'some-url;q=123'
});
expect(authService.getRedirect()).toEqual('some-url;q=123');
expect(basicAlfrescoAuthService.getRedirect()).toEqual('some-url;q=123');
});
it('should set redirect navigation commands with query params', () => {
spyOn(authService, 'setRedirect').and.callThrough();
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
spyOn(router, 'navigateByUrl').and.stub();
const route = { url: '/' } as RouterStateSnapshot;
authGuard.canActivate(null, route);
expect(authService.setRedirect).toHaveBeenCalledWith({
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'ECM', url: '/'
});
expect(authService.getRedirect()).toEqual('/');
expect(basicAlfrescoAuthService.getRedirect()).toEqual('/');
});
it('should get redirect url from config if there is one configured', () => {
appConfigService.config.loginRoute = 'fakeLoginRoute';
spyOn(authService, 'setRedirect').and.callThrough();
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
spyOn(router, 'navigateByUrl').and.stub();
const route = { url: 'some-url' } as RouterStateSnapshot;
authGuard.canActivate(null, route);
expect(authService.setRedirect).toHaveBeenCalledWith({
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'ECM', url: 'some-url'
});
expect(router.navigateByUrl).toHaveBeenCalledWith(router.parseUrl('/fakeLoginRoute?redirectUrl=some-url'));
@ -185,13 +201,13 @@ describe('AuthGuardService ECM', () => {
spyOn(materialDialog, 'closeAll');
spyOn(authService, 'setRedirect').and.callThrough();
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
spyOn(router, 'navigateByUrl').and.stub();
const route = { url: 'some-url' } as RouterStateSnapshot;
authGuard.canActivate(null, route);
expect(authService.setRedirect).toHaveBeenCalledWith({
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'ECM', url: 'some-url'
});

View File

@ -16,13 +16,33 @@
*/
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, UrlTree } from '@angular/router';
import {
ActivatedRouteSnapshot, Router, UrlTree
} from '@angular/router';
import { AuthenticationService } from '../services/authentication.service';
import { AppConfigService } from '../../app-config/app-config.service';
import { AuthGuardBase } from './auth-guard-base';
import { MatDialog } from '@angular/material/dialog';
import { StorageService } from '../../common/services/storage.service';
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
import { OidcAuthenticationService } from '../services/oidc-authentication.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuardEcm extends AuthGuardBase {
constructor(authenticationService: AuthenticationService,
basicAlfrescoAuthService: BasicAlfrescoAuthService,
oidcAuthenticationService: OidcAuthenticationService,
router: Router,
appConfigService: AppConfigService,
dialog: MatDialog,
storageService: StorageService) {
super(authenticationService, basicAlfrescoAuthService, oidcAuthenticationService, router, appConfigService, dialog, storageService);
}
async checkLogin(_: ActivatedRouteSnapshot, redirectUrl: string): Promise<boolean | UrlTree> {
if (this.authenticationService.isEcmLoggedIn() || this.withCredentials) {
return true;

View File

@ -23,6 +23,8 @@ import { AuthenticationService } from '../services/authentication.service';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { StorageService } from '../../common/services/storage.service';
import { OidcAuthenticationService } from '../services/oidc-authentication.service';
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
describe('AuthGuardService', () => {
let state;
@ -31,17 +33,30 @@ describe('AuthGuardService', () => {
let authGuard: AuthGuard;
let storageService: StorageService;
let appConfigService: AppConfigService;
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
let oidcAuthenticationService: OidcAuthenticationService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
CoreTestingModule
],
providers: [
{
provide: OidcAuthenticationService, useValue: {
ssoImplicitLogin: () => { },
isPublicUrl: () => false,
hasValidIdToken: () => false
}
}
]
});
localStorage.clear();
state = { url: '' };
authService = TestBed.inject(AuthenticationService);
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
oidcAuthenticationService = TestBed.inject(OidcAuthenticationService);
router = TestBed.inject(Router);
authGuard = TestBed.inject(AuthGuard);
appConfigService = TestBed.inject(AppConfigService);
@ -110,13 +125,13 @@ describe('AuthGuardService', () => {
});
it('should NOT redirect url if the User is NOT logged in and isOAuth but with silentLogin configured', async () => {
spyOn(authService, 'ssoImplicitLogin').and.stub();
spyOn(oidcAuthenticationService, 'ssoImplicitLogin').and.stub();
spyOn(authService, 'isLoggedIn').and.returnValue(false);
spyOn(authService, 'isOauth').and.returnValue(true);
appConfigService.config.oauth2.silentLogin = true;
expect(await authGuard.canActivate(null, state)).toBeFalsy();
expect(authService.ssoImplicitLogin).toHaveBeenCalledTimes(1);
expect(oidcAuthenticationService.ssoImplicitLogin).toHaveBeenCalledTimes(1);
});
it('should set redirect url', async () => {
@ -124,11 +139,11 @@ describe('AuthGuardService', () => {
appConfigService.config.loginRoute = 'login';
spyOn(router, 'navigateByUrl');
spyOn(authService, 'setRedirect');
spyOn(basicAlfrescoAuthService, 'setRedirect');
await authGuard.canActivate(null, state);
expect(authService.setRedirect).toHaveBeenCalledWith({
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'ALL', url: 'some-url'
});
expect(router.navigateByUrl).toHaveBeenCalledWith(router.parseUrl('/login?redirectUrl=some-url'));
@ -140,11 +155,11 @@ describe('AuthGuardService', () => {
appConfigService.config.provider = 'ALL';
spyOn(router, 'navigateByUrl');
spyOn(authService, 'setRedirect');
spyOn(basicAlfrescoAuthService, 'setRedirect');
await authGuard.canActivate(null, state);
expect(authService.setRedirect).toHaveBeenCalledWith({
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'ALL', url: 'some-url;q=query'
});
expect(router.navigateByUrl).toHaveBeenCalledWith(router.parseUrl('/login?redirectUrl=some-url;q=query'));
@ -155,11 +170,11 @@ describe('AuthGuardService', () => {
appConfigService.config.loginRoute = 'fakeLoginRoute';
spyOn(router, 'navigateByUrl');
spyOn(authService, 'setRedirect');
spyOn(basicAlfrescoAuthService, 'setRedirect');
await authGuard.canActivate(null, state);
expect(authService.setRedirect).toHaveBeenCalledWith({
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'ALL', url: 'some-url'
});
expect(router.navigateByUrl).toHaveBeenCalledWith(router.parseUrl('/fakeLoginRoute?redirectUrl=some-url'));
@ -169,11 +184,11 @@ describe('AuthGuardService', () => {
state.url = '/';
spyOn(router, 'navigateByUrl');
spyOn(authService, 'setRedirect');
spyOn(basicAlfrescoAuthService, 'setRedirect');
await authGuard.canActivate(null, state);
expect(authService.setRedirect).toHaveBeenCalledWith({
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'ALL', url: '/'
});
});

View File

@ -16,9 +16,16 @@
*/
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, UrlTree } from '@angular/router';
import { ActivatedRouteSnapshot, Router, UrlTree } from '@angular/router';
import { AuthenticationService } from '../services/authentication.service';
import { AppConfigService } from '../../app-config/app-config.service';
import { AuthGuardBase } from './auth-guard-base';
import { JwtHelperService } from '../services/jwt-helper.service';
import { MatDialog } from '@angular/material/dialog';
import { StorageService } from '../../common/services/storage.service';
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
import { OidcAuthenticationService } from '../services/oidc-authentication.service';
@Injectable({
providedIn: 'root'
@ -27,8 +34,15 @@ export class AuthGuard extends AuthGuardBase {
ticketChangeBind: any;
constructor(private jwtHelperService: JwtHelperService) {
super();
constructor(private jwtHelperService: JwtHelperService,
authenticationService: AuthenticationService,
basicAlfrescoAuthService: BasicAlfrescoAuthService,
oidcAuthenticationService: OidcAuthenticationService,
router: Router,
appConfigService: AppConfigService,
dialog: MatDialog,
storageService: StorageService) {
super(authenticationService, basicAlfrescoAuthService, oidcAuthenticationService, router, appConfigService, dialog, storageService);
this.ticketChangeBind = this.ticketChange.bind(this);
window.addEventListener('storage', this.ticketChangeBind);

View File

@ -0,0 +1,60 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { HttpHeaders } from '@angular/common/http';
import ee from 'event-emitter';
import { Observable } from 'rxjs';
export interface AuthenticationServiceInterface {
onError: any;
onLogin: any;
onLogout: any;
on: ee.EmitterMethod;
off: ee.EmitterMethod;
once: ee.EmitterMethod;
emit: (type: string, ...args: any[]) => void;
getToken(): string;
isLoggedIn(): boolean;
isOauth(): boolean;
logout(): any;
isEcmLoggedIn(): boolean;
isBpmLoggedIn(): boolean;
isECMProvider(): boolean;
isBPMProvider(): boolean;
isALLProvider(): boolean;
getEcmUsername(): string;
getBpmUsername(): string;
getAuthHeaders(requestUrl: string, header: HttpHeaders): HttpHeaders;
addTokenToHeader(requestUrl: string, headersArg?: HttpHeaders): Observable<HttpHeaders>;
reset(): void;
}

View File

@ -15,6 +15,14 @@
* limitations under the License.
*/
export * from './activiti/activiti-client.types';
export * from './alfresco-js-clients.module';
export * from './discovery/discovery-client.types';
export interface Authentication {
basicAuth?: BasicAuth;
cookie?: string;
type?: string;
}
export interface BasicAuth {
username?: string;
password?: string;
ticket?: string;
}

View File

@ -19,17 +19,12 @@ import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
import { AuthConfig, AUTH_CONFIG, OAuthModule, OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
import { AlfrescoApiNoAuthService } from '../../api-factories/alfresco-api-no-auth.service';
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { AuthGuardBpm } from '../guard/auth-guard-bpm.service';
import { AuthGuardEcm } from '../guard/auth-guard-ecm.service';
import { AuthGuard } from '../guard/auth-guard.service';
import { AuthenticationService } from '../services/authentication.service';
import { StorageService } from '../../common/services/storage.service';
import { AuthModuleConfig, AUTH_MODULE_CONFIG } from './auth-config';
import { authConfigFactory, AuthConfigService } from './auth-config.service';
import { AuthRoutingModule } from './auth-routing.module';
import { AuthService } from './auth.service';
import { OidcAuthGuard } from './oidc-auth.guard';
import { OIDCAuthenticationService } from './oidc-authentication.service';
import { RedirectAuthService } from './redirect-auth.service';
import { AuthenticationConfirmationComponent } from './view/authentication-confirmation/authentication-confirmation.component';
@ -51,10 +46,10 @@ export function loginFactory(oAuthService: OAuthService, storage: OAuthStorage,
imports: [AuthRoutingModule, OAuthModule.forRoot()],
providers: [
{ provide: OAuthStorage, useExisting: StorageService },
{ provide: AuthGuard, useClass: OidcAuthGuard },
{ provide: AuthGuardEcm, useClass: OidcAuthGuard },
{ provide: AuthGuardBpm, useClass: OidcAuthGuard },
{ provide: AuthenticationService, useClass: OIDCAuthenticationService },
// { provide: AuthGuard, useClass: OidcAuthGuard },
// { provide: AuthGuardEcm, useClass: OidcAuthGuard },
// { provide: AuthGuardBpm, useClass: OidcAuthGuard },
{ provide: AuthenticationService},
{ provide: AlfrescoApiService, useClass: AlfrescoApiNoAuthService },
{
provide: AUTH_CONFIG,

View File

@ -22,6 +22,8 @@ import { Observable } from 'rxjs';
* Provide authentication/authorization through OAuth2/OIDC protocol.
*/
export abstract class AuthService {
abstract onLogin: Observable<any>;
/** Subscribe to whether the user has valid Id/Access tokens. */
abstract authenticated$: Observable<boolean>;

View File

@ -1,134 +0,0 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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, inject } from '@angular/core';
import { OAuthEvent, OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
import { EMPTY, Observable } from 'rxjs';
import { catchError, filter, map } from 'rxjs/operators';
import { AppConfigValues } from '../../app-config/app-config.service';
import { BaseAuthenticationService } from '../services/base-authentication.service';
import { JwtHelperService } from '../services/jwt-helper.service';
import { AuthConfigService } from '../oidc/auth-config.service';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class OIDCAuthenticationService extends BaseAuthenticationService {
private authStorage = inject(OAuthStorage);
private oauthService = inject(OAuthService);
private readonly authConfig = inject(AuthConfigService);
private readonly auth = inject(AuthService);
readonly supportCodeFlow = true;
constructor() {
super();
this.alfrescoApi.alfrescoApiInitialized.subscribe(() => {
this.oauthService.events.pipe(
filter((event)=> event.type === 'token_received')
).subscribe(()=>{
this.onLogin.next({});
});
});
}
isEcmLoggedIn(): boolean {
return this.isLoggedIn();
}
isBpmLoggedIn(): boolean {
return this.isLoggedIn();
}
isLoggedIn(): boolean {
return this.oauthService.hasValidAccessToken() && this.oauthService.hasValidIdToken();
}
isLoggedInWith(_provider?: string): boolean {
return this.isLoggedIn();
}
isOauth(): boolean {
return this.appConfig.get(AppConfigValues.AUTHTYPE) === 'OAUTH';
}
isImplicitFlow(): boolean {
return !!this.appConfig.oauth2?.implicitFlow;
}
isAuthCodeFlow(): boolean {
return !!this.appConfig.oauth2?.codeFlow;
}
login(username: string, password: string, rememberMe: boolean = false): Observable<{ type: string; ticket: any }> {
return this.auth.baseAuthLogin(username, password).pipe(
map((response) => {
this.saveRememberMeCookie(rememberMe);
this.onLogin.next(response);
return {
type: this.appConfig.get(AppConfigValues.PROVIDERS),
ticket: response
};
}),
catchError((err) => this.handleError(err))
);
}
getEcmUsername(): string {
return (this.oauthService.getIdentityClaims() as any).preferred_username;
}
getBpmUsername(): string {
return (this.oauthService.getIdentityClaims() as any).preferred_username;
}
ssoImplicitLogin() {
this.oauthService.initLoginFlow();
}
ssoCodeFlowLogin() {
this.oauthService.initCodeFlow();
}
isRememberMeSet(): boolean {
return true;
}
logout() {
this.oauthService.logOut();
return EMPTY;
}
getToken(): string {
return this.authStorage.getItem(JwtHelperService.USER_ACCESS_TOKEN);
}
reset(): void {
const config = this.authConfig.loadAppConfig();
this.auth.updateIDPConfiguration(config);
const oauth2 = this.appConfig.oauth2;
if (config.oidc && oauth2.silentLogin) {
this.auth.login();
}
}
once(event: string): Observable<OAuthEvent> {
return this.oauthService.events.pipe(filter(_event => _event.type === event));
}
}

View File

@ -19,13 +19,16 @@ import { Inject, Injectable } from '@angular/core';
import { AuthConfig, AUTH_CONFIG, OAuthErrorEvent, OAuthService, OAuthStorage, TokenResponse } from 'angular-oauth2-oidc';
import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
import { from, Observable } from 'rxjs';
import { distinctUntilChanged, filter, map, shareReplay, startWith } from 'rxjs/operators';
import { distinctUntilChanged, filter, map, shareReplay } from 'rxjs/operators';
import { AuthService } from './auth.service';
const isPromise = <T>(value: T | Promise<T>): value is Promise<T> => value && typeof (value as Promise<T>).then === 'function';
@Injectable()
export class RedirectAuthService extends AuthService {
onLogin: Observable<any>;
private _loadDiscoveryDocumentPromise = Promise.resolve(false);
/** Subscribe to whether the user has valid Id/Access tokens. */
@ -52,29 +55,32 @@ export class RedirectAuthService extends AuthService {
) {
super();
this.authConfig = authConfig;
}
init() {
this.oauthService.clearHashAfterLogin = true;
this.authenticated$ = this.oauthService.events.pipe(
startWith(undefined),
map(() => this.authenticated),
distinctUntilChanged(),
shareReplay(1)
);
this.onLogin = this.authenticated$.pipe(
filter((authenticated) => authenticated),
map(() => undefined)
);
this.idpUnreachable$ = this.oauthService.events.pipe(
filter((event): event is OAuthErrorEvent => event.type === 'discovery_document_load_error'),
map((event) => event.reason as Error)
);
}
init() {
if (isPromise(this.authConfig)) {
return this.authConfig.then((config) => this.configureAuth(config));
}
return this.configureAuth(this.authConfig);
}
logout() {

View File

@ -24,7 +24,7 @@ import { AuthService } from '../../auth.service';
const ROUTE_DEFAULT = '/';
@Component({
template: '',
template: '<div data-automation-id="auth-confirmation"></div>',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AuthenticationConfirmationComponent {

View File

@ -31,6 +31,10 @@ export * from './services/jwt-helper.service';
export * from './services/oauth2.service';
export * from './services/user-access.service';
export * from './basic-auth/basic-alfresco-auth.service';
export * from './basic-auth/process-auth';
export * from './basic-auth/content-auth';
export * from './interfaces/identity-user.service.interface';
export * from './interfaces/identity-group.interface';
export * from './interfaces/openid-configuration.interface';

View File

@ -16,21 +16,23 @@
*/
import { fakeAsync, TestBed } from '@angular/core/testing';
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { AuthenticationService } from './authentication.service';
import { CookieService } from '../../common/services/cookie.service';
import { AppConfigService } from '../../app-config/app-config.service';
import { setupTestBed } from '../../testing/setup-test-bed';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
import { OidcAuthenticationService } from './oidc-authentication.service';
declare let jasmine: any;
describe('AuthenticationService', () => {
let apiService: AlfrescoApiService;
let authService: AuthenticationService;
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
let appConfigService: AppConfigService;
let cookie: CookieService;
let oidcAuthenticationService: OidcAuthenticationService;
setupTestBed({
imports: [
@ -42,8 +44,9 @@ describe('AuthenticationService', () => {
beforeEach(() => {
sessionStorage.clear();
localStorage.clear();
apiService = TestBed.inject(AlfrescoApiService);
authService = TestBed.inject(AuthenticationService);
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
oidcAuthenticationService = TestBed.inject(OidcAuthenticationService);
cookie = TestBed.inject(CookieService);
cookie.clear();
@ -73,6 +76,23 @@ describe('AuthenticationService', () => {
});
appConfigService.load();
});
it('should kerberos be disabled if is oauth', () => {
spyOn(authService, 'isOauth').and.returnValue(true);
expect(authService.isKerberosEnabled()).toEqual(false);
});
it('should kerberos not enabled if is oauth is false and basic auth return false', () => {
spyOn(authService, 'isOauth').and.returnValue(false);
spyOn(basicAlfrescoAuthService, 'isKerberosEnabled').and.returnValue(false);
expect(authService.isKerberosEnabled()).toEqual(false);
});
it('should kerberos be enabled if is oauth is false and basic auth return true', () => {
spyOn(authService, 'isOauth').and.returnValue(false);
spyOn(basicAlfrescoAuthService, 'isKerberosEnabled').and.returnValue(true);
expect(authService.isKerberosEnabled()).toEqual(true);
});
});
describe('when the setting is ECM', () => {
@ -83,40 +103,30 @@ describe('AuthenticationService', () => {
appConfigService.config.auth = { withCredentials: false };
appConfigService.config.providers = 'ECM';
appConfigService.load();
apiService.reset();
});
it('should not require cookie service enabled for ECM check', () => {
spyOn(cookie, 'isEnabled').and.returnValue(false);
spyOn(authService, 'isRememberMeSet').and.returnValue(false);
spyOn(basicAlfrescoAuthService, 'isRememberMeSet').and.returnValue(false);
spyOn(authService, 'isECMProvider').and.returnValue(true);
spyOn(authService, 'isOauth').and.returnValue(false);
spyOn(apiService, 'getInstance').and.callThrough();
expect(authService.isEcmLoggedIn()).toBeFalsy();
expect(apiService.getInstance).toHaveBeenCalled();
});
it('should check if loggedin on ECM in case the provider is ECM', () => {
spyOn(authService, 'isEcmLoggedIn').and.returnValue(true);
expect(authService.isLoggedInWith('ECM')).toBe(true);
});
it('should require remember me set for ECM check', () => {
spyOn(cookie, 'isEnabled').and.returnValue(true);
spyOn(authService, 'isRememberMeSet').and.returnValue(false);
spyOn(basicAlfrescoAuthService, 'isRememberMeSet').and.returnValue(false);
spyOn(authService, 'isECMProvider').and.returnValue(true);
spyOn(authService, 'isOauth').and.returnValue(false);
spyOn(apiService, 'getInstance').and.callThrough();
expect(authService.isEcmLoggedIn()).toBeFalsy();
expect(apiService.getInstance).not.toHaveBeenCalled();
});
it('[ECM] should return an ECM ticket after the login done', (done) => {
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(() => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(() => {
expect(authService.isLoggedIn()).toBe(true);
expect(authService.getTicketEcm()).toEqual('fake-post-ticket');
expect(authService.getToken()).toEqual('fake-post-ticket');
expect(authService.isEcmLoggedIn()).toBe(true);
disposableLogin.unsubscribe();
done();
@ -130,7 +140,7 @@ describe('AuthenticationService', () => {
});
it('[ECM] should login in the ECM if no provider are defined calling the login', fakeAsync(() => {
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe((loginResponse) => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe((loginResponse) => {
expect(loginResponse).toEqual(fakeECMLoginResponse);
disposableLogin.unsubscribe();
});
@ -143,10 +153,10 @@ describe('AuthenticationService', () => {
}));
it('[ECM] should return a ticket undefined after logout', fakeAsync(() => {
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(() => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(() => {
const disposableLogout = authService.logout().subscribe(() => {
expect(authService.isLoggedIn()).toBe(false);
expect(authService.getTicketEcm()).toBe(null);
expect(authService.getToken()).toBe(null);
expect(authService.isEcmLoggedIn()).toBe(false);
disposableLogin.unsubscribe();
disposableLogout.unsubscribe();
@ -170,21 +180,21 @@ describe('AuthenticationService', () => {
});
it('[ECM] should set/get redirectUrl when provider is ECM', () => {
authService.setRedirect({ provider: 'ECM', url: 'some-url' });
basicAlfrescoAuthService.setRedirect({ provider: 'ECM', url: 'some-url' });
expect(authService.getRedirect()).toEqual('some-url');
expect(basicAlfrescoAuthService.getRedirect()).toEqual('some-url');
});
it('[ECM] should set/get redirectUrl when provider is BPM', () => {
authService.setRedirect({ provider: 'BPM', url: 'some-url' });
basicAlfrescoAuthService.setRedirect({ provider: 'BPM', url: 'some-url' });
expect(authService.getRedirect()).toBeNull();
expect(basicAlfrescoAuthService.getRedirect()).toBeNull();
});
it('[ECM] should return null as redirectUrl when redirectUrl field is not set', () => {
authService.setRedirect(null);
basicAlfrescoAuthService.setRedirect(null);
expect(authService.getRedirect()).toBeNull();
expect(basicAlfrescoAuthService.getRedirect()).toBeNull();
});
it('[ECM] should return isECMProvider true', () => {
@ -209,40 +219,30 @@ describe('AuthenticationService', () => {
beforeEach(() => {
appConfigService.config.providers = 'BPM';
appConfigService.load();
apiService.reset();
});
it('should require remember me set for BPM check', () => {
spyOn(cookie, 'isEnabled').and.returnValue(true);
spyOn(authService, 'isRememberMeSet').and.returnValue(false);
spyOn(basicAlfrescoAuthService, 'isRememberMeSet').and.returnValue(false);
spyOn(authService, 'isBPMProvider').and.returnValue(true);
spyOn(authService, 'isOauth').and.returnValue(false);
spyOn(apiService, 'getInstance').and.callThrough();
expect(authService.isBpmLoggedIn()).toBeFalsy();
expect(apiService.getInstance).not.toHaveBeenCalled();
});
it('should check if loggedin on BPM in case the provider is BPM', () => {
spyOn(authService, 'isBpmLoggedIn').and.returnValue(true);
expect(authService.isLoggedInWith('BPM')).toBe(true);
});
it('should not require cookie service enabled for BPM check', () => {
spyOn(cookie, 'isEnabled').and.returnValue(false);
spyOn(authService, 'isRememberMeSet').and.returnValue(false);
spyOn(basicAlfrescoAuthService, 'isRememberMeSet').and.returnValue(false);
spyOn(authService, 'isBPMProvider').and.returnValue(true);
spyOn(apiService, 'getInstance').and.callThrough();
expect(authService.isBpmLoggedIn()).toBeFalsy();
expect(apiService.getInstance).toHaveBeenCalled();
});
it('[BPM] should return an BPM ticket after the login done', (done) => {
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(() => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(() => {
expect(authService.isLoggedIn()).toBe(true);
// cspell: disable-next
expect(authService.getTicketBpm()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk');
expect(authService.getToken()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk');
expect(authService.isBpmLoggedIn()).toBe(true);
disposableLogin.unsubscribe();
done();
@ -255,10 +255,10 @@ describe('AuthenticationService', () => {
});
it('[BPM] should return a ticket undefined after logout', (done) => {
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(() => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(() => {
const disposableLogout = authService.logout().subscribe(() => {
expect(authService.isLoggedIn()).toBe(false);
expect(authService.getTicketBpm()).toBe(null);
expect(authService.getToken()).toBe(null);
expect(authService.isBpmLoggedIn()).toBe(false);
disposableLogout.unsubscribe();
disposableLogin.unsubscribe();
@ -281,7 +281,7 @@ describe('AuthenticationService', () => {
},
(err: any) => {
expect(err).toBeDefined();
expect(authService.getTicketBpm()).toBe(undefined);
expect(authService.getToken()).toBe(null);
done();
});
@ -291,21 +291,21 @@ describe('AuthenticationService', () => {
});
it('[BPM] should set/get redirectUrl when provider is BPM', () => {
authService.setRedirect({ provider: 'BPM', url: 'some-url' });
basicAlfrescoAuthService.setRedirect({ provider: 'BPM', url: 'some-url' });
expect(authService.getRedirect()).toEqual('some-url');
expect(basicAlfrescoAuthService.getRedirect()).toEqual('some-url');
});
it('[BPM] should set/get redirectUrl when provider is ECM', () => {
authService.setRedirect({ provider: 'ECM', url: 'some-url' });
basicAlfrescoAuthService.setRedirect({ provider: 'ECM', url: 'some-url' });
expect(authService.getRedirect()).toBeNull();
expect(basicAlfrescoAuthService.getRedirect()).toBeNull();
});
it('[BPM] should return null as redirectUrl when redirectUrl field is not set', () => {
authService.setRedirect(null);
basicAlfrescoAuthService.setRedirect(null);
expect(authService.getRedirect()).toBeNull();
expect(basicAlfrescoAuthService.getRedirect()).toBeNull();
});
it('[BPM] should return isECMProvider false', () => {
@ -326,11 +326,10 @@ describe('AuthenticationService', () => {
beforeEach(() => {
appConfigService.config.providers = 'ECM';
appConfigService.load();
apiService.reset();
});
it('[ECM] should save the remember me cookie as a session cookie after successful login', (done) => {
const disposableLogin = authService.login('fake-username', 'fake-password', false).subscribe(() => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password', false).subscribe(() => {
expect(cookie['ALFRESCO_REMEMBER_ME']).not.toBeUndefined();
expect(cookie['ALFRESCO_REMEMBER_ME'].expiration).toBeNull();
disposableLogin.unsubscribe();
@ -345,7 +344,7 @@ describe('AuthenticationService', () => {
});
it('[ECM] should save the remember me cookie as a persistent cookie after successful login', (done) => {
const disposableLogin = authService.login('fake-username', 'fake-password', true).subscribe(() => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password', true).subscribe(() => {
expect(cookie['ALFRESCO_REMEMBER_ME']).not.toBeUndefined();
expect(cookie['ALFRESCO_REMEMBER_ME'].expiration).not.toBeNull();
disposableLogin.unsubscribe();
@ -361,7 +360,7 @@ describe('AuthenticationService', () => {
});
it('[ECM] should not save the remember me cookie after failed login', (done) => {
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(
() => {},
() => {
expect(cookie['ALFRESCO_REMEMBER_ME']).toBeUndefined();
@ -390,15 +389,14 @@ describe('AuthenticationService', () => {
beforeEach(() => {
appConfigService.config.providers = 'ALL';
appConfigService.load();
apiService.reset();
});
it('[ALL] should return both ECM and BPM tickets after the login done', (done) => {
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(() => {
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(() => {
expect(authService.isLoggedIn()).toBe(true);
expect(authService.getTicketEcm()).toEqual('fake-post-ticket');
expect(basicAlfrescoAuthService.getTicketEcm()).toEqual('fake-post-ticket');
// cspell: disable-next
expect(authService.getTicketBpm()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk');
expect(basicAlfrescoAuthService.getTicketBpm()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk');
expect(authService.isBpmLoggedIn()).toBe(true);
expect(authService.isEcmLoggedIn()).toBe(true);
disposableLogin.unsubscribe();
@ -417,13 +415,13 @@ describe('AuthenticationService', () => {
});
it('[ALL] should return login fail if only ECM call fail', (done) => {
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(
() => {},
() => {
expect(authService.isLoggedIn()).toBe(false, 'isLoggedIn');
expect(authService.getTicketEcm()).toBe(null, 'getTicketEcm');
expect(authService.getToken()).toBe(null, 'getTicketEcm');
// cspell: disable-next
expect(authService.getTicketBpm()).toBe(null, 'getTicketBpm');
expect(authService.getToken()).toBe(null, 'getTicketBpm');
expect(authService.isEcmLoggedIn()).toBe(false, 'isEcmLoggedIn');
disposableLogin.unsubscribe();
done();
@ -439,12 +437,12 @@ describe('AuthenticationService', () => {
});
it('[ALL] should return login fail if only BPM call fail', (done) => {
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(
() => {},
() => {
expect(authService.isLoggedIn()).toBe(false);
expect(authService.getTicketEcm()).toBe(null);
expect(authService.getTicketBpm()).toBe(null);
expect(authService.getToken()).toBe(null);
expect(authService.getToken()).toBe(null);
expect(authService.isBpmLoggedIn()).toBe(false);
disposableLogin.unsubscribe();
done();
@ -462,12 +460,12 @@ describe('AuthenticationService', () => {
});
it('[ALL] should return ticket undefined when the credentials are wrong', (done) => {
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(
() => {},
() => {
expect(authService.isLoggedIn()).toBe(false);
expect(authService.getTicketEcm()).toBe(null);
expect(authService.getTicketBpm()).toBe(null);
expect(authService.getToken()).toBe(null);
expect(authService.getToken()).toBe(null);
expect(authService.isBpmLoggedIn()).toBe(false);
expect(authService.isEcmLoggedIn()).toBe(false);
disposableLogin.unsubscribe();
@ -483,30 +481,6 @@ describe('AuthenticationService', () => {
});
});
it('[ALL] should set/get redirectUrl when provider is ALL', () => {
authService.setRedirect({ provider: 'ALL', url: 'some-url' });
expect(authService.getRedirect()).toEqual('some-url');
});
it('[ALL] should set/get redirectUrl when provider is BPM', () => {
authService.setRedirect({ provider: 'BPM', url: 'some-url' });
expect(authService.getRedirect()).toEqual('some-url');
});
it('[ALL] should set/get redirectUrl when provider is ECM', () => {
authService.setRedirect({ provider: 'ECM', url: 'some-url' });
expect(authService.getRedirect()).toEqual('some-url');
});
it('[ALL] should return null as redirectUrl when redirectUrl field is not set', () => {
authService.setRedirect(null);
expect(authService.getRedirect()).toBeNull();
});
it('[ALL] should return isECMProvider false', () => {
expect(authService.isECMProvider()).toBe(false);
});
@ -519,4 +493,22 @@ describe('AuthenticationService', () => {
expect(authService.isALLProvider()).toBe(true);
});
});
describe('getUsername', () => {
it('should get the username of the authenticated user if isOAuth is true', () => {
spyOn(authService, 'isOauth').and.returnValue(true);
spyOn(oidcAuthenticationService, 'getUsername').and.returnValue('mike.portnoy');
const username = authService.getUsername();
expect(username).toEqual('mike.portnoy');
});
it('should get the username of the authenticated user if isOAuth is false', () => {
spyOn(authService, 'isOauth').and.returnValue(false);
spyOn(oidcAuthenticationService, 'getUsername').and.returnValue('mike.portnoy');
spyOn(basicAlfrescoAuthService, 'getUsername').and.returnValue('john.petrucci');
const username = authService.getUsername();
expect(username).toEqual('john.petrucci');
});
});
});

View File

@ -15,184 +15,195 @@
* limitations under the License.
*/
import { Injectable, inject } from '@angular/core';
import { Observable, from } from 'rxjs';
import { AppConfigValues } from '../../app-config/app-config.service';
import { map, catchError, tap } from 'rxjs/operators';
import { JwtHelperService } from './jwt-helper.service';
import { StorageService } from '../../common/services/storage.service';
import { BaseAuthenticationService } from './base-authentication.service';
import { Injectable, Injector } from '@angular/core';
import { OidcAuthenticationService } from './oidc-authentication.service';
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
import { Observable, Subject, from } from 'rxjs';
import { HttpHeaders } from '@angular/common/http';
import { AuthenticationServiceInterface } from '../interfaces/authentication-service.interface';
import ee from 'event-emitter';
import { RedirectAuthService } from '../oidc/redirect-auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthenticationService extends BaseAuthenticationService {
private storageService = inject(StorageService);
readonly supportCodeFlow = false;
export class AuthenticationService implements AuthenticationServiceInterface, ee.Emitter {
constructor() {
super();
this.alfrescoApi.alfrescoApiInitialized.subscribe(() => {
this.alfrescoApi.getInstance().reply('logged-in', () => {
this.onLogin.next();
});
});
}
onLogin: Subject<any> = new Subject<any>();
onLogout: Subject<any> = new Subject<any>();
/**
* Checks if the user logged in.
*
* @returns True if logged in, false otherwise
*/
isLoggedIn(): boolean {
if (!this.isOauth() && this.cookie.isEnabled() && !this.isRememberMeSet()) {
return false;
}
return this.alfrescoApi.getInstance().isLoggedIn();
}
constructor(
private injector: Injector,
private redirectAuthService: RedirectAuthService
) {
this.redirectAuthService.onLogin.subscribe(
(value) => this.onLogin.next(value)
);
isLoggedInWith(provider: string): boolean {
if (provider === 'BPM') {
return this.isBpmLoggedIn();
} else if (provider === 'ECM') {
return this.isEcmLoggedIn();
this.basicAlfrescoAuthService.onLogin.subscribe(
(value) => this.onLogin.next(value)
);
if (this.isOauth()) {
this.oidcAuthenticationService.onLogout.subscribe(
(value) => this.onLogout.next(value)
);
} else {
return this.isLoggedIn();
}
}
/**
* Does the provider support OAuth?
*
* @returns True if supported, false otherwise
*/
isOauth(): boolean {
return this.alfrescoApi.getInstance().isOauthConfiguration();
}
/**
* Logs the user in.
*
* @param username Username for the login
* @param password Password for the login
* @param rememberMe Stores the user's login details if true
* @returns Object with auth type ("ECM", "BPM" or "ALL") and auth ticket
*/
login(username: string, password: string, rememberMe: boolean = false): Observable<{ type: string; ticket: any }> {
return from(this.alfrescoApi.getInstance().login(username, password)).pipe(
map((response: any) => {
this.saveRememberMeCookie(rememberMe);
this.onLogin.next(response);
return {
type: this.appConfig.get(AppConfigValues.PROVIDERS),
ticket: response
};
}),
catchError((err) => this.handleError(err))
this.basicAlfrescoAuthService.onLogout.subscribe(
(value) => this.onLogout.next(value)
);
}
/**
* Logs the user in with SSO
*/
ssoImplicitLogin() {
this.alfrescoApi.getInstance().implicitLogin();
}
/**
* Logs the user out.
*
* @returns Response event called when logout is complete
*/
logout() {
return from(this.callApiLogout()).pipe(
tap((response) => {
this.onLogout.next(response);
return response;
}),
catchError((err) => this.handleError(err))
);
get on(): ee.EmitterMethod {
return this.isOauth() ? this.oidcAuthenticationService.on : this.basicAlfrescoAuthService.on;
}
private callApiLogout(): Promise<any> {
if (this.alfrescoApi.getInstance()) {
return this.alfrescoApi.getInstance().logout();
}
return Promise.resolve();
get off(): ee.EmitterMethod {
return this.isOauth() ? this.oidcAuthenticationService.off : this.basicAlfrescoAuthService.off;
}
get once(): ee.EmitterMethod {
return this.isOauth() ? this.oidcAuthenticationService.once : this.basicAlfrescoAuthService.once;
}
get emit(): (type: string, ...args: any[]) => void {
return this.isOauth() ? this.oidcAuthenticationService.emit : this.basicAlfrescoAuthService.emit;
}
get onError(): Observable<any> {
return this.isOauth() ? this.oidcAuthenticationService.onError : this.basicAlfrescoAuthService.onError;
}
addTokenToHeader(requestUrl: string, headersArg?: HttpHeaders): Observable<HttpHeaders> {
return this.isOauth() ? this.oidcAuthenticationService.addTokenToHeader(requestUrl, headersArg) : this.basicAlfrescoAuthService.addTokenToHeader(requestUrl, headersArg);
}
isECMProvider(): boolean {
return this.isOauth() ? this.oidcAuthenticationService.isECMProvider() : this.basicAlfrescoAuthService.isECMProvider();
}
isBPMProvider(): boolean {
return this.isOauth() ? this.oidcAuthenticationService.isBPMProvider() : this.basicAlfrescoAuthService.isBPMProvider();
}
isALLProvider(): boolean {
return this.isOauth() ? this.oidcAuthenticationService.isALLProvider() : this.basicAlfrescoAuthService.isALLProvider();
}
private get oidcAuthenticationService(): OidcAuthenticationService {
return this.injector.get(OidcAuthenticationService);
}
private get basicAlfrescoAuthService(): BasicAlfrescoAuthService {
return this.injector.get(BasicAlfrescoAuthService);
}
getToken(): string {
if (this.isOauth()) {
return this.oidcAuthenticationService.getToken();
} else {
return this.basicAlfrescoAuthService.getToken();
}
}
isLoggedIn(): boolean {
if (this.isOauth()) {
return this.oidcAuthenticationService.isLoggedIn();
} else {
return this.basicAlfrescoAuthService.isLoggedIn();
}
}
logout(): Observable<any> {
if (this.isOauth()) {
return this.oidcAuthenticationService.logout();
} else {
return from(this.basicAlfrescoAuthService.logout());
}
}
/**
* Checks if the user is logged in on an ECM provider.
*
* @returns True if logged in, false otherwise
*/
isEcmLoggedIn(): boolean {
if (this.isECMProvider() || this.isALLProvider()) {
if (!this.isOauth() && this.cookie.isEnabled() && !this.isRememberMeSet()) {
return false;
if (this.isOauth()) {
return this.oidcAuthenticationService.isEcmLoggedIn();
} else {
return this.basicAlfrescoAuthService.isEcmLoggedIn();
}
return this.alfrescoApi.getInstance().isEcmLoggedIn();
}
return false;
}
/**
* Checks if the user is logged in on a BPM provider.
*
* @returns True if logged in, false otherwise
*/
isBpmLoggedIn(): boolean {
if (this.isBPMProvider() || this.isALLProvider()) {
if (!this.isOauth() && this.cookie.isEnabled() && !this.isRememberMeSet()) {
return false;
if (this.isOauth()) {
return this.oidcAuthenticationService.isBpmLoggedIn();
} else {
return this.basicAlfrescoAuthService.isBpmLoggedIn();
}
return this.alfrescoApi.getInstance().isBpmLoggedIn();
}
return false;
reset(): void {
if (this.isOauth()) {
return this.oidcAuthenticationService.reset();
} else {
return this.basicAlfrescoAuthService.reset();
}
}
login(username: string, password: string, rememberMe?: boolean): Observable<{ type: string; ticket: any }> {
if (this.isOauth()) {
return this.oidcAuthenticationService.loginWithPassword(username, password);
} else {
return this.basicAlfrescoAuthService.login(username, password, rememberMe);
}
}
/**
* Gets the ECM username.
*
* @returns The ECM username
* @returns the username of the authenticated user
*/
getUsername(): string {
if (this.isOauth()) {
return this.oidcAuthenticationService.getUsername();
} else {
return this.basicAlfrescoAuthService.getUsername();
}
}
/**
* @deprecated
* @returns the logged username
*/
getEcmUsername(): string {
return this.alfrescoApi.getInstance().getEcmUsername();
if (this.isOauth()) {
return this.oidcAuthenticationService.getUsername();
} else {
return this.basicAlfrescoAuthService.getEcmUsername();
}
}
/**
* Gets the BPM username
*
* @returns The BPM username
* @deprecated
* @returns the logged username
*/
getBpmUsername(): string {
return this.alfrescoApi.getInstance().getBpmUsername();
if (this.isOauth()) {
return this.oidcAuthenticationService.getUsername();
} else {
return this.basicAlfrescoAuthService.getBpmUsername();
}
}
isImplicitFlow(): boolean {
return !!this.appConfig.oauth2?.implicitFlow;
getAuthHeaders(requestUrl: string, headers: HttpHeaders): HttpHeaders {
if (this.isOauth()) {
return this.oidcAuthenticationService.getAuthHeaders(requestUrl, headers);
} else {
return this.basicAlfrescoAuthService.getAuthHeaders(requestUrl, headers);
}
}
isAuthCodeFlow(): boolean {
return false;
isOauth(): boolean {
return this.basicAlfrescoAuthService.isOauth();
}
/**
* Gets the auth token.
*
* @returns Auth token string
*/
getToken(): string {
return this.storageService.getItem(JwtHelperService.USER_ACCESS_TOKEN);
isKerberosEnabled(): boolean {
return !this.isOauth() ? this.basicAlfrescoAuthService.isKerberosEnabled() : false;
}
reset() { }
once(event: string): Observable<any> {
const alfrescoApiEvent = event === 'token_received' ? 'token_issued' : event;
return new Observable((subscriber) => {
this.alfrescoApi.getInstance().oauth2Auth.once(alfrescoApiEvent, () => subscriber.next());
});
}
}

View File

@ -15,76 +15,70 @@
* limitations under the License.
*/
import { PeopleApi, UserProfileApi } from '@alfresco/js-api';
import { HttpHeaders } from '@angular/common/http';
import { RedirectionModel } from '../models/redirection.model';
import { Observable, Observer, ReplaySubject, throwError } from 'rxjs';
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { CookieService } from '../../common/services/cookie.service';
import { LogService } from '../../common/services/log.service';
import { inject } from '@angular/core';
import { AuthenticationServiceInterface } from '../interfaces/authentication-service.interface';
import ee from 'event-emitter';
const REMEMBER_ME_COOKIE_KEY = 'ALFRESCO_REMEMBER_ME';
const REMEMBER_ME_UNTIL = 1000 * 60 * 60 * 24 * 30;
export abstract class BaseAuthenticationService implements AuthenticationServiceInterface, ee.Emitter {
export abstract class BaseAuthenticationService {
protected alfrescoApi = inject(AlfrescoApiService);
protected appConfig = inject(AppConfigService);
protected cookie = inject(CookieService);
private logService = inject(LogService);
on: ee.EmitterMethod;
off: ee.EmitterMethod;
once: ee.EmitterMethod;
emit: (type: string, ...args: any[]) => void;
protected bearerExcludedUrls: readonly string[] = ['resources/', 'assets/', 'auth/realms', 'idp/'];
protected redirectUrl: RedirectionModel = null;
onError = new ReplaySubject<any>(1);
onLogin = new ReplaySubject<any>(1);
onLogout = new ReplaySubject<any>(1);
private _peopleApi: PeopleApi;
get peopleApi(): PeopleApi {
this._peopleApi = this._peopleApi ?? new PeopleApi(this.alfrescoApi.getInstance());
return this._peopleApi;
constructor(
protected appConfig: AppConfigService,
protected cookie: CookieService,
private logService: LogService
) {
ee(this);
}
private _profileApi: UserProfileApi;
get profileApi(): UserProfileApi {
this._profileApi = this._profileApi ?? new UserProfileApi(this.alfrescoApi.getInstance());
return this._profileApi;
}
abstract getAuthHeaders(requestUrl: string, header: HttpHeaders): HttpHeaders;
abstract readonly supportCodeFlow: boolean;
abstract getToken(): string;
abstract isLoggedIn(): boolean;
abstract isLoggedInWith(provider: string): boolean;
abstract isOauth(): boolean;
abstract login(username: string, password: string, rememberMe?: boolean): Observable<{ type: string; ticket: any }>;
abstract ssoImplicitLogin(): void;
abstract logout(): Observable<any>;
abstract isEcmLoggedIn(): boolean;
abstract isBpmLoggedIn(): boolean;
abstract getEcmUsername(): string;
abstract getBpmUsername(): string;
abstract reset(): void;
abstract once(event: string): Observable<any>;
getBearerExcludedUrls(): readonly string[] {
return this.bearerExcludedUrls;
}
abstract isLoggedIn(): boolean;
abstract logout(): any;
abstract isEcmLoggedIn(): boolean;
abstract isBpmLoggedIn(): boolean;
abstract reset(): void;
abstract getEcmUsername(): string;
abstract getBpmUsername(): string;
/**
* Adds the auth token to an HTTP header using the 'bearer' scheme.
*
* @param requestUrl the request url
* @param headersArg Header that will receive the token
* @returns The new header with the token added
*/
addTokenToHeader(headersArg?: HttpHeaders): Observable<HttpHeaders> {
addTokenToHeader(requestUrl: string, headersArg?: HttpHeaders): Observable<HttpHeaders> {
return new Observable((observer: Observer<any>) => {
let headers = headersArg;
if (!headers) {
headers = new HttpHeaders();
}
try {
const header = this.getAuthHeaders(headers);
const header = this.getAuthHeaders(requestUrl, headers);
observer.next(header);
observer.complete();
@ -94,50 +88,9 @@ export abstract class BaseAuthenticationService {
});
}
private getAuthHeaders(header: HttpHeaders): HttpHeaders {
const authType = this.appConfig.get<string>(AppConfigValues.AUTHTYPE, 'BASIC');
switch (authType) {
case 'OAUTH':
return this.addBearerToken(header);
case 'BASIC':
return this.addBasicAuth(header);
default:
return header;
}
}
private addBearerToken(header: HttpHeaders): HttpHeaders {
const token: string = this.getToken();
if (!token) {
return header;
}
return header.set('Authorization', 'bearer ' + token);
}
private addBasicAuth(header: HttpHeaders): HttpHeaders {
const ticket: string = this.getTicketEcmBase64();
if (!ticket) {
return header;
}
return header.set('Authorization', ticket);
}
isPublicUrl(): boolean {
return this.alfrescoApi.getInstance().isPublicUrl();
}
/**
* Does the provider support ECM?
*
* @returns True if supported, false otherwise
*/
isECMProvider(): boolean {
return this.alfrescoApi.getInstance().isEcmConfiguration();
const provider = this.appConfig.get('providers') as string;
return provider && provider.toUpperCase() === 'ECM';
}
/**
@ -146,7 +99,12 @@ export abstract class BaseAuthenticationService {
* @returns True if supported, false otherwise
*/
isBPMProvider(): boolean {
return this.alfrescoApi.getInstance().isBpmConfiguration();
const provider = this.appConfig.get('providers');
if (provider && (typeof provider === 'string' || provider instanceof String)) {
return provider.toUpperCase() === 'BPM';
} else {
return false;
}
}
/**
@ -155,38 +113,13 @@ export abstract class BaseAuthenticationService {
* @returns True if both are supported, false otherwise
*/
isALLProvider(): boolean {
return this.alfrescoApi.getInstance().isEcmBpmConfiguration();
const provider = this.appConfig.get('providers') as string;
return provider && provider.toUpperCase() === 'ALL';
}
/**
* Gets the ECM ticket stored in the Storage.
*
* @returns The ticket or `null` if none was found
*/
getTicketEcm(): string | null {
return this.alfrescoApi.getInstance()?.getTicketEcm();
}
/**
* Gets the BPM ticket stored in the Storage.
*
* @returns The ticket or `null` if none was found
*/
getTicketBpm(): string | null {
return this.alfrescoApi.getInstance()?.getTicketBpm();
}
/**
* Gets the BPM ticket from the Storage in Base 64 format.
*
* @returns The ticket or `null` if none was found
*/
getTicketEcmBase64(): string | null {
const ticket = this.alfrescoApi.getInstance()?.getTicketEcm();
if (ticket) {
return 'Basic ' + btoa(ticket);
}
return null;
isOauthConfiguration(): boolean {
const authType = this.appConfig.get('authType') as string;
return authType === 'OAUTH';
}
/**
@ -196,64 +129,12 @@ export abstract class BaseAuthenticationService {
* @returns Object representing the error message
*/
handleError(error: any): Observable<any> {
this.onError.next(error || 'Server error');
this.logService.error('Error when logging in', error);
return throwError(error || 'Server error');
}
/**
* Does kerberos enabled?
*
* @returns True if enabled, false otherwise
*/
isKerberosEnabled(): boolean {
return this.appConfig.get<boolean>(AppConfigValues.AUTH_WITH_CREDENTIALS, false);
}
/**
* Saves the "remember me" cookie as either a long-life cookie or a session cookie.
*
* @param rememberMe Enables a long-life cookie
*/
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);
}
/**
* Checks whether the "remember me" cookie was set or not.
*
* @returns True if set, false otherwise
*/
isRememberMeSet(): boolean {
return this.cookie.getItem(REMEMBER_ME_COOKIE_KEY) !== null;
}
setRedirect(url?: RedirectionModel) {
this.redirectUrl = url;
}
/**
* Gets the URL to redirect to after login.
*
* @returns The redirect URL
*/
getRedirect(): string {
const provider = this.appConfig.get<string>(AppConfigValues.PROVIDERS);
return this.hasValidRedirection(provider) ? this.redirectUrl.url : null;
}
private hasValidRedirection(provider: string): boolean {
return this.redirectUrl && (this.redirectUrl.provider === provider || this.hasSelectedProviderAll(provider));
}
private hasSelectedProviderAll(provider: string): boolean {
return this.redirectUrl && (this.redirectUrl.provider === 'ALL' || provider === 'ALL');
isOauth(): boolean {
return this.appConfig.get(AppConfigValues.AUTHTYPE) === 'OAUTH';
}
}

View File

@ -35,6 +35,7 @@ import { IdentityRoleModel } from '../models/identity-role.model';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { AdfHttpClient } from '../../../../api/src';
import { StorageService } from '../../common/services/storage.service';
describe('IdentityUserService', () => {
@ -46,6 +47,7 @@ describe('IdentityUserService', () => {
{ id: 'id-5', name: 'MOCK-ROLE-2'}
];
let storageService: StorageService;
let service: IdentityUserService;
let adfHttpClient: AdfHttpClient;
let requestSpy: jasmine.Spy;
@ -57,18 +59,14 @@ describe('IdentityUserService', () => {
CoreTestingModule
]
});
storageService = TestBed.inject(StorageService);
service = TestBed.inject(IdentityUserService);
adfHttpClient = TestBed.inject(AdfHttpClient);
requestSpy = spyOn(adfHttpClient, 'request');
const store = {};
spyOn(localStorage, 'getItem').and.callFake( (key: string): string => store[key] || null);
spyOn(localStorage, 'setItem').and.callFake((key: string, value: string): string => store[key] = value);
});
it('should fetch identity user info from Jwt id token', () => {
localStorage.setItem(JwtHelperService.USER_ID_TOKEN, mockToken);
storageService.setItem(JwtHelperService.USER_ID_TOKEN, mockToken);
const user = service.getCurrentUserInfo();
expect(user).toBeDefined();
expect(user.firstName).toEqual('John');
@ -78,7 +76,7 @@ describe('IdentityUserService', () => {
});
it('should fallback on Jwt access token for identity user info', () => {
localStorage.setItem(JwtHelperService.USER_ACCESS_TOKEN, mockToken);
storageService.setItem(JwtHelperService.USER_ACCESS_TOKEN, mockToken);
const user = service.getCurrentUserInfo();
expect(user).toBeDefined();
expect(user.firstName).toEqual('John');

View File

@ -0,0 +1,194 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
import { EMPTY, Observable, defer } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
import { OauthConfigModel } from '../models/oauth-config.model';
import { BaseAuthenticationService } from './base-authentication.service';
import { CookieService } from '../../common/services/cookie.service';
import { JwtHelperService } from './jwt-helper.service';
import { LogService } from '../../common/services/log.service';
import { AuthConfigService } from '../oidc/auth-config.service';
import { AuthService } from '../oidc/auth.service';
import { Minimatch } from 'minimatch';
import { HttpHeaders } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class OidcAuthenticationService extends BaseAuthenticationService {
constructor(
appConfig: AppConfigService,
cookie: CookieService,
logService: LogService,
private jwtHelperService: JwtHelperService,
private authStorage: OAuthStorage,
private oauthService: OAuthService,
private readonly authConfig: AuthConfigService,
private readonly auth: AuthService
) {
super(appConfig, cookie, logService);
}
isEcmLoggedIn(): boolean {
if (this.isECMProvider() || this.isALLProvider()) {
return this.isLoggedIn();
}
return false;
}
isBpmLoggedIn(): boolean {
if (this.isBPMProvider() || this.isALLProvider()) {
return this.isLoggedIn();
}
return false;
}
isLoggedIn(): boolean {
return this.oauthService.hasValidAccessToken() && this.oauthService.hasValidIdToken();
}
hasValidAccessToken(): boolean {
return this.oauthService.hasValidAccessToken();
}
hasValidIdToken(): boolean {
return this.oauthService.hasValidIdToken();
}
isImplicitFlow() {
const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
return !!oauth2?.implicitFlow;
}
isAuthCodeFlow() {
const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
return !!oauth2?.codeFlow;
}
login(username: string, password: string): Observable<{ type: string; ticket: any }> {
return this.auth.baseAuthLogin(username, password).pipe(
map((response) => {
this.onLogin.next(response);
return {
type: this.appConfig.get(AppConfigValues.PROVIDERS),
ticket: response
};
}),
catchError((err) => this.handleError(err))
);
}
loginWithPassword(username: string, password: string): Observable<{ type: string; ticket: any }> {
return defer(async () => {
try {
await this.authConfig.loadConfig();
await this.oauthService.loadDiscoveryDocument();
await this.oauthService.fetchTokenUsingPasswordFlowAndLoadUserProfile(username, password);
await this.oauthService.refreshToken();
const accessToken = this.oauthService.getAccessToken();
this.onLogin.next(accessToken);
return {
type: this.appConfig.get(AppConfigValues.PROVIDERS) as string,
ticket: accessToken
};
} catch (err) {
throw this.handleError(err);
}
});
}
getUsername(){
return this.jwtHelperService.getValueFromLocalToken<string>(JwtHelperService.USER_PREFERRED_USERNAME);
}
/**
* @deprecated
* @returns the logged username
*/
getEcmUsername(): string {
return this.getUsername();
}
/**
* @deprecated
* @returns the logged username
*/
getBpmUsername(): string {
return this.getUsername();
}
ssoImplicitLogin() {
this.auth.login();
}
ssoCodeFlowLogin() {
this.oauthService.initCodeFlow();
}
isRememberMeSet(): boolean {
return true;
}
logout() {
this.oauthService.logOut();
return EMPTY;
}
getToken(): string {
return this.authStorage.getItem(JwtHelperService.USER_ACCESS_TOKEN);
}
reset(): void {
const config = this.authConfig.loadAppConfig();
this.auth.updateIDPConfiguration(config);
}
isPublicUrl(): boolean {
const oauth2 = this.appConfig.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null);
if (Array.isArray(oauth2.publicUrls)) {
return oauth2.publicUrls.length > 0 &&
oauth2.publicUrls.some((urlPattern: string) => {
const minimatch = new Minimatch(urlPattern);
return minimatch.match(window.location.href);
});
}
return false;
}
getAuthHeaders(_requestUrl: string, header: HttpHeaders): HttpHeaders {
return this.addBearerToken(header);
}
private addBearerToken(header: HttpHeaders): HttpHeaders {
const token: string = this.getToken();
if (!token) {
return header;
}
return header.set('Authorization', 'bearer ' + token);
}
}

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { CardViewKeyValuePairsItemModel } from '../../models/card-view-keyvaluepairs.model';
import { CardViewKeyValuePairsItemComponent } from './card-view-keyvaluepairsitem.component';
@ -122,57 +122,5 @@ describe('CardViewKeyValuePairsItemComponent', () => {
expect(cardViewUpdateService.update).toHaveBeenCalled();
expect(component.property.value.length).toBe(0);
});
it('should update property on input blur', waitForAsync(() => {
spyOn(cardViewUpdateService, 'update');
component.ngOnChanges();
fixture.detectChanges();
const addButton = fixture.debugElement.query(By.css('.adf-card-view__key-value-pairs__add-btn'));
addButton.triggerEventHandler('click', null);
fixture.detectChanges();
const nameInput = fixture.debugElement.query(By.css(`[data-automation-id="card-${component.property.key}-name-input-0"]`));
const valueInput = fixture.debugElement.query(By.css(`[data-automation-id="card-${component.property.key}-value-input-0"]`));
nameInput.nativeElement.value = mockData[0].name;
nameInput.nativeElement.dispatchEvent(new Event('input'));
valueInput.nativeElement.value = mockData[0].value;
valueInput.nativeElement.dispatchEvent(new Event('input'));
fixture.whenStable().then(() => {
fixture.detectChanges();
valueInput.triggerEventHandler('blur', null);
fixture.detectChanges();
expect(cardViewUpdateService.update).toHaveBeenCalled();
expect(JSON.stringify(component.property.value)).toBe(JSON.stringify(mockData));
});
}));
it('should not update property if at least one input is empty on blur', waitForAsync(() => {
spyOn(cardViewUpdateService, 'update');
component.ngOnChanges();
fixture.detectChanges();
const addButton = fixture.debugElement.query(By.css('.adf-card-view__key-value-pairs__add-btn'));
addButton.triggerEventHandler('click', null);
fixture.detectChanges();
const valueInput = fixture.debugElement.query(By.css(`[data-automation-id="card-${component.property.key}-value-input-0"]`));
valueInput.nativeElement.value = mockData[0].value;
valueInput.nativeElement.dispatchEvent(new Event('input'));
fixture.whenStable().then(() => {
fixture.detectChanges();
valueInput.triggerEventHandler('blur', null);
fixture.detectChanges();
expect(cardViewUpdateService.update).not.toHaveBeenCalled();
});
}));
});
});

View File

@ -19,12 +19,14 @@ import { Injectable } from '@angular/core';
import { AppConfigService, Status } from '../../app-config/app-config.service';
import { HttpClient } from '@angular/common/http';
import { ExtensionService } from '@alfresco/adf-extensions';
@Injectable()
export class AppConfigServiceMock extends AppConfigService {
config: any = {
application: {
name: 'Alfresco ADF Application'
name: 'Alfresco ADF Application',
storagePrefix: 'ADF_APP'
},
ecmHost: 'http://{hostname}{:port}/ecm',
bpmHost: 'http://{hostname}{:port}/bpm',
@ -35,11 +37,12 @@ export class AppConfigServiceMock extends AppConfigService {
super(http, extensionService);
}
load(): Promise<any> {
load(callback?: (...args: any[]) => any): Promise<any> {
return new Promise((resolve) => {
this.status = Status.LOADED;
this.onDataLoaded(this.config);
callback?.();
resolve(this.config);
this.onDataLoaded();
});
}
}

View File

@ -21,7 +21,6 @@ import { StorageService } from '../../common/services/storage.service';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { AppConfigServiceMock } from '../mock/app-config.service.mock';
import { TranslateModule } from '@ngx-translate/core';
import { CoreModule } from '../../core.module';
describe('StorageService', () => {
@ -34,8 +33,6 @@ describe('StorageService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
CoreModule.forRoot(),
CoreTestingModule
]
});
@ -87,6 +84,7 @@ describe('StorageService', () => {
]
});
appConfig = TestBed.inject(AppConfigService);
appConfig.config = {
application: {
storagePrefix: ''

View File

@ -81,7 +81,7 @@ export class StorageService {
*/
removeItem(key: string) {
if (this.useLocalStorage) {
localStorage.removeItem(this.prefix + key);
localStorage.removeItem(`${this.prefix}` + key);
} else {
delete this.memoryStore[this.prefix + key];
}

View File

@ -26,7 +26,7 @@ export class ObjectUtils {
*/
static getValue(target: any, key: string): any {
if (!target) {
if (!target || !key) {
return undefined;
}

View File

@ -53,9 +53,8 @@ import { ExtensionsModule } from '@alfresco/adf-extensions';
import { directionalityConfigFactory } from './common/services/directionality-config-factory';
import { DirectionalityConfigService } from './common/services/directionality-config.service';
import { SearchTextModule } from './search-text/search-text-input.module';
import { AdfHttpClient, AlfrescoJsClientsModule } from '@alfresco/adf-core/api';
import { AdfHttpClient } from '@alfresco/adf-core/api';
import { AuthenticationInterceptor, Authentication } from '@alfresco/adf-core/auth';
import { LegacyApiClientModule } from './api-factories/legacy-api-client.module';
import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthenticationService } from './auth/services/authentication.service';
import { MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar';
@ -101,8 +100,6 @@ import { AdfDateTimeFnsAdapter } from './common/utils/datetime-fns-adapter';
NotificationHistoryModule,
SearchTextModule,
BlankPageModule,
LegacyApiClientModule,
AlfrescoJsClientsModule,
HttpClientModule,
HttpClientXsrfModule.withOptions({
cookieName: 'CSRF-TOKEN',

View File

@ -37,7 +37,7 @@ export class LogoutDirective implements OnInit {
private renderer: Renderer2,
private router: Router,
private appConfig: AppConfigService,
private auth: AuthenticationService) {
private authenticationService: AuthenticationService) {
}
ngOnInit() {
@ -58,14 +58,14 @@ export class LogoutDirective implements OnInit {
}
logout() {
this.auth.logout().subscribe(
this.authenticationService.logout().subscribe(
() => this.redirectToUri(),
() => this.redirectToUri()
);
}
redirectToUri() {
if (this.enableRedirect && !this.auth.isOauth()) {
if (this.enableRedirect && !this.authenticationService.isOauth()) {
const redirectRoute = this.getRedirectUri();
this.router.navigate([redirectRoute]);
}

View File

@ -16,11 +16,12 @@
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AuthenticationService } from '../../auth/services/authentication.service';
import { LoginDialogPanelComponent } from './login-dialog-panel.component';
import { of } from 'rxjs';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { BasicAlfrescoAuthService } from '../../auth/basic-auth/basic-alfresco-auth.service';
import { OidcAuthenticationService } from '../../auth/services/oidc-authentication.service';
describe('LoginDialogPanelComponent', () => {
let component: LoginDialogPanelComponent;
@ -28,19 +29,23 @@ describe('LoginDialogPanelComponent', () => {
let element: HTMLElement;
let usernameInput: HTMLInputElement;
let passwordInput: HTMLInputElement;
let authService: AuthenticationService;
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
CoreTestingModule
],
providers: [
{ provide: OidcAuthenticationService, useValue: {}}
]
});
fixture = TestBed.createComponent(LoginDialogPanelComponent);
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
element = fixture.nativeElement;
component = fixture.componentInstance;
authService = TestBed.inject(AuthenticationService);
fixture.detectChanges();
await fixture.whenStable();
@ -76,7 +81,7 @@ describe('LoginDialogPanelComponent', () => {
expect(event.token.ticket).toBe('ticket');
done();
});
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
loginWithCredentials('fake-username', 'fake-password');
});

View File

@ -25,10 +25,11 @@ import { AuthenticationService } from '../../auth/services/authentication.servic
import { LoginErrorEvent } from '../models/login-error.event';
import { LoginSuccessEvent } from '../models/login-success.event';
import { LoginComponent } from './login.component';
import { of, throwError } from 'rxjs';
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { EMPTY, of, throwError } from 'rxjs';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { LogService } from '../../common/services/log.service';
import { BasicAlfrescoAuthService } from '../../auth/basic-auth/basic-alfresco-auth.service';
import { OidcAuthenticationService } from '../../auth/services/oidc-authentication.service';
describe('LoginComponent', () => {
let component: LoginComponent;
@ -38,7 +39,7 @@ describe('LoginComponent', () => {
let router: Router;
let userPreferences: UserPreferencesService;
let appConfigService: AppConfigService;
let alfrescoApiService: AlfrescoApiService;
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
let usernameInput;
let passwordInput;
@ -60,6 +61,16 @@ describe('LoginComponent', () => {
TestBed.configureTestingModule({
imports: [
CoreTestingModule
],
providers: [
{
provide: OidcAuthenticationService, useValue: {
ssoImplicitLogin: () => { },
isPublicUrl: () => false,
hasValidIdToken: () => false,
isLoggedIn: () => false
}
}
]
});
fixture = TestBed.createComponent(LoginComponent);
@ -69,11 +80,11 @@ describe('LoginComponent', () => {
component.showRememberMe = true;
component.showLoginActions = true;
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
authService = TestBed.inject(AuthenticationService);
router = TestBed.inject(Router);
userPreferences = TestBed.inject(UserPreferencesService);
appConfigService = TestBed.inject(AppConfigService);
alfrescoApiService = TestBed.inject(AlfrescoApiService);
const logService = TestBed.inject(LogService);
spyOn(logService, 'error');
@ -111,7 +122,7 @@ describe('LoginComponent', () => {
});
it('should redirect to route on successful login', () => {
spyOn(authService, 'login').and.returnValue(
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(
of({ type: 'type', ticket: 'ticket' })
);
const redirect = '/home';
@ -161,10 +172,10 @@ describe('LoginComponent', () => {
appConfigService.config = {};
appConfigService.config.providers = 'ECM';
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
const redirect = '/home';
component.successRoute = redirect;
authService.setRedirect({ provider: 'ECM', url: 'some-route' });
basicAlfrescoAuthService.setRedirect({ provider: 'ECM', url: 'some-route' });
spyOn(router, 'navigateByUrl');
@ -174,8 +185,7 @@ describe('LoginComponent', () => {
it('should update user preferences upon login', async () => {
spyOn(userPreferences, 'setStoragePrefix').and.callThrough();
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
spyOn(alfrescoApiService.getInstance(), 'login').and.returnValue(Promise.resolve());
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
component.success.subscribe(() => {
expect(userPreferences.setStoragePrefix).toHaveBeenCalledWith('fake-username');
@ -206,14 +216,14 @@ describe('LoginComponent', () => {
});
it('should be changed back to the default after a failed login attempt', () => {
spyOn(authService, 'login').and.returnValue(throwError('Fake server error'));
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(throwError('Fake server error'));
loginWithCredentials('fake-wrong-username', 'fake-wrong-password');
expect(getLoginButtonText()).toEqual('LOGIN.BUTTON.LOGIN');
});
it('should be changed to the "welcome key" after a successful login attempt', () => {
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
loginWithCredentials('fake-username', 'fake-password');
expect(getLoginButtonText()).toEqual('LOGIN.BUTTON.WELCOME');
@ -295,12 +305,12 @@ describe('LoginComponent', () => {
});
it('should be taken into consideration during login attempt', fakeAsync(() => {
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
component.rememberMe = false;
loginWithCredentials('fake-username', 'fake-password');
expect(authService.login).toHaveBeenCalledWith('fake-username', 'fake-password', false);
expect(basicAlfrescoAuthService.login).toHaveBeenCalledWith('fake-username', 'fake-password', false);
}));
});
@ -469,7 +479,7 @@ describe('LoginComponent', () => {
});
it('should return error with a wrong username', (done) => {
spyOn(alfrescoApiService.getInstance(), 'login').and.returnValue(Promise.reject(new Error('login error')));
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(throwError(new Error()));
component.error.subscribe(() => {
fixture.detectChanges();
@ -484,7 +494,7 @@ describe('LoginComponent', () => {
});
it('should return error with a wrong password', (done) => {
spyOn(alfrescoApiService.getInstance(), 'login').and.returnValue(Promise.reject(new Error('login error')));
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(throwError(new Error()));
component.error.subscribe(() => {
fixture.detectChanges();
@ -500,7 +510,7 @@ describe('LoginComponent', () => {
});
it('should return error with a wrong username and password', (done) => {
spyOn(alfrescoApiService.getInstance(), 'login').and.returnValue(Promise.reject(new Error('login error')));
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(throwError(new Error()));
component.error.subscribe(() => {
fixture.detectChanges();
@ -516,7 +526,7 @@ describe('LoginComponent', () => {
});
it('should return CORS error when server CORS error occurs', (done) => {
spyOn(authService, 'login').and.returnValue(throwError({
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(throwError({
error: {
crossDomain: true,
message: 'ERROR: the network is offline, Origin is not allowed by Access-Control-Allow-Origin'
@ -537,7 +547,7 @@ describe('LoginComponent', () => {
});
it('should return CSRF error when server CSRF error occurs', fakeAsync(() => {
spyOn(authService, 'login')
spyOn(basicAlfrescoAuthService, 'login')
.and.returnValue(throwError({ message: 'ERROR: Invalid CSRF-token', status: 403 }));
component.error.subscribe(() => {
@ -552,7 +562,7 @@ describe('LoginComponent', () => {
}));
it('should return ECM read-only error when error occurs', fakeAsync(() => {
spyOn(authService, 'login')
spyOn(basicAlfrescoAuthService, 'login')
.and.returnValue(
throwError(
{
@ -600,7 +610,7 @@ describe('LoginComponent', () => {
});
it('should return success event after the login have succeeded', (done) => {
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
expect(component.isError).toBe(false);
@ -616,7 +626,7 @@ describe('LoginComponent', () => {
});
it('should emit success event after the login has succeeded and discard password', fakeAsync(() => {
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
component.success.subscribe((event) => {
fixture.detectChanges();
@ -631,7 +641,7 @@ describe('LoginComponent', () => {
}));
it('should emit error event after the login has failed', fakeAsync(() => {
spyOn(authService, 'login').and.returnValue(throwError('Fake server error'));
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(throwError('Fake server error'));
component.error.subscribe((error) => {
fixture.detectChanges();
@ -668,7 +678,7 @@ describe('LoginComponent', () => {
});
it('should emit only the username and not the password as part of the executeSubmit', fakeAsync(() => {
spyOn(alfrescoApiService.getInstance(), 'login').and.returnValue(Promise.resolve());
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(EMPTY);
component.executeSubmit.subscribe((res) => {
fixture.detectChanges();
@ -688,7 +698,6 @@ describe('LoginComponent', () => {
beforeEach(() => {
appConfigService.config.oauth2 = { implicitFlow: true, silentLogin: false };
appConfigService.load();
alfrescoApiService.reset();
});
it('should not show login username and password if SSO implicit flow is active', fakeAsync(() => {

View File

@ -29,6 +29,8 @@ import { AppConfigService, AppConfigValues } from '../../app-config/app-config.s
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { BasicAlfrescoAuthService } from '../../auth/basic-auth/basic-alfresco-auth.service';
import { OidcAuthenticationService } from '../../auth/services/oidc-authentication.service';
// eslint-disable-next-line no-shadow
enum LoginSteps {
@ -129,6 +131,8 @@ export class LoginComponent implements OnInit, OnDestroy {
constructor(
private _fb: UntypedFormBuilder,
private authService: AuthenticationService,
private basicAlfrescoAuthService: BasicAlfrescoAuthService,
private oidcAuthenticationService: OidcAuthenticationService,
private translateService: TranslationService,
private router: Router,
private appConfig: AppConfigService,
@ -160,7 +164,7 @@ export class LoginComponent implements OnInit, OnDestroy {
const url = params['redirectUrl'];
const provider = this.appConfig.get<string>(AppConfigValues.PROVIDERS);
this.authService.setRedirect({ provider, url });
this.basicAlfrescoAuthService.setRedirect({provider, url});
});
}
@ -181,7 +185,7 @@ export class LoginComponent implements OnInit, OnDestroy {
}
redirectToImplicitLogin() {
this.authService.ssoImplicitLogin();
this.oidcAuthenticationService.ssoImplicitLogin();
}
/**
@ -199,6 +203,7 @@ export class LoginComponent implements OnInit, OnDestroy {
if (!args.defaultPrevented) {
this.actualLoginStep = LoginSteps.Checking;
this.performLogin(values);
}
}
@ -207,7 +212,7 @@ export class LoginComponent implements OnInit, OnDestroy {
if (this.authService.isLoggedIn()) {
this.router.navigate([this.successRoute]);
}
this.authService.ssoImplicitLogin();
this.oidcAuthenticationService.ssoImplicitLogin();
}
/**
@ -237,10 +242,11 @@ export class LoginComponent implements OnInit, OnDestroy {
}
}
performLogin(values: LoginFormValues) {
this.authService.login(values.username, values.password, this.rememberMe).subscribe(
(token) => {
const redirectUrl = this.authService.getRedirect();
performLogin(values: { username: string; password: string }) {
this.authService.login(values.username, values.password, this.rememberMe)
.subscribe(
async (token: any) => {
const redirectUrl = this.basicAlfrescoAuthService.getRedirect();
this.actualLoginStep = LoginSteps.Welcome;
this.userPreferences.setStoragePrefix(values.username);
@ -248,10 +254,10 @@ export class LoginComponent implements OnInit, OnDestroy {
this.success.emit(new LoginSuccessEvent(token, values.username, null));
if (redirectUrl) {
this.authService.setRedirect(null);
this.router.navigateByUrl(redirectUrl);
this.basicAlfrescoAuthService.setRedirect(null);
await this.router.navigateByUrl(redirectUrl);
} else if (this.successRoute) {
this.router.navigate([this.successRoute]);
await this.router.navigate([this.successRoute]);
}
},
(err: any) => {

View File

@ -20,6 +20,7 @@ import { LoginComponent } from '../components/login.component';
import { LoginFooterDirective } from './login-footer.directive';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { OidcAuthenticationService } from '../../auth/services/oidc-authentication.service';
describe('LoginFooterDirective', () => {
let fixture: ComponentFixture<LoginComponent>;
@ -31,6 +32,11 @@ describe('LoginFooterDirective', () => {
imports: [
TranslateModule.forRoot(),
CoreTestingModule
],
providers: [
{
provide: OidcAuthenticationService, useValue: {}
}
]
});
fixture = TestBed.createComponent(LoginComponent);

View File

@ -20,6 +20,7 @@ import { LoginComponent } from '../components/login.component';
import { LoginHeaderDirective } from './login-header.directive';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { OidcAuthenticationService } from '../../auth/services/oidc-authentication.service';
describe('LoginHeaderDirective', () => {
let fixture: ComponentFixture<LoginComponent>;
@ -31,6 +32,9 @@ describe('LoginHeaderDirective', () => {
imports: [
TranslateModule.forRoot(),
CoreTestingModule
],
providers: [
{ provide: OidcAuthenticationService, useValue: {} }
]
});
fixture = TestBed.createComponent(LoginComponent);

View File

@ -16,12 +16,12 @@
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SnackbarContentComponent } from '@alfresco/adf-core';
import { MatIcon, MatIconModule } from '@angular/material/icon';
import { MAT_SNACK_BAR_DATA, MatSnackBarModule, MatSnackBarRef } from '@angular/material/snack-bar';
import { MatButtonModule } from '@angular/material/button';
import { By } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core';
import { SnackbarContentComponent } from './snackbar-content.component';
describe('SnackbarContentComponent', () => {
let component: SnackbarContentComponent;

View File

@ -36,7 +36,8 @@ export class CoreAutomationService {
private userPreferencesService: UserPreferencesService,
private storageService: StorageService,
private auth: AuthenticationService
) {}
) {
}
setup() {
const adfProxy = window['adf'] || {};

View File

@ -20,9 +20,11 @@ import { CoreModule } from '../core.module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core';
import { provideTranslations } from '../translation/translation.service';
import { AuthModule } from '../../../src/lib/auth/oidc/auth.module';
@NgModule({
imports: [
AuthModule.forRoot(),
TranslateModule.forRoot(),
CoreModule.forRoot(),
BrowserAnimationsModule

View File

@ -32,9 +32,11 @@ import { CookieServiceMock } from '../mock/cookie.service.mock';
import { HttpClientModule } from '@angular/common/http';
import { directionalityConfigFactory } from '../common/services/directionality-config-factory';
import { DirectionalityConfigService } from '../common/services/directionality-config.service';
import { AuthModule } from '../auth';
@NgModule({
imports: [
AuthModule.forRoot({ useHash: true }),
NoopAnimationsModule,
RouterTestingModule,
HttpClientModule,

View File

@ -20,6 +20,7 @@ import { TranslateLoaderService } from './translate-loader.service';
import { TranslationService } from './translation.service';
import { TranslateModule } from '@ngx-translate/core';
import { CoreModule } from '../core.module';
import { AuthModule } from '../auth';
declare let jasmine: any;
@ -30,6 +31,7 @@ describe('TranslateLoader', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
AuthModule.forRoot({ useHash: true }),
TranslateModule.forRoot(),
CoreModule.forRoot()
],

View File

@ -17,10 +17,12 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreTestingModule, DownloadPromptDialogComponent, DownloadPromptActions } from '@alfresco/adf-core';
import { By } from '@angular/platform-browser';
import { MatDialogRef } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core';
import { DownloadPromptDialogComponent } from './download-prompt-dialog.component';
import { CoreTestingModule } from '../../../testing/core.testing.module';
import { DownloadPromptActions } from '../../models/download-prompt.actions';
const mockDialog = {
close: jasmine.createSpy('close')

View File

@ -26,11 +26,12 @@ import {
AppConfigServiceMock,
TranslationService,
TranslationMock,
CoreModule
CoreModule, AuthModule
} from '@alfresco/adf-core';
@NgModule({
imports: [
AuthModule.forRoot({ useHash: true }),
NoopAnimationsModule,
TranslateModule,
CoreModule.forRoot(),

View File

@ -25,7 +25,7 @@ import {
FormModel,
FormOutcomeEvent,
FormOutcomeModel, FormRenderingService, FormService,
UploadWidgetContentLinkModel, WidgetVisibilityService, provideTranslations
UploadWidgetContentLinkModel, WidgetVisibilityService, provideTranslations, AuthModule
} from '@alfresco/adf-core';
import { Node } from '@alfresco/js-api';
import { ESCAPE } from '@angular/cdk/keycodes';
@ -1151,6 +1151,7 @@ describe('Multilingual Form', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
AuthModule.forRoot({ useHash: true }),
NoopAnimationsModule,
TranslateModule.forRoot(),
CoreModule.forRoot(),
@ -1226,6 +1227,7 @@ describe('retrieve metadata on submit', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
AuthModule.forRoot({ useHash: true }),
NoopAnimationsModule,
TranslateModule.forRoot(),
CoreModule.forRoot(),

View File

@ -17,9 +17,7 @@
import { TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { JwtHelperService } from '@alfresco/adf-core';
import { IdentityUserService } from './identity-user.service';
import { mockToken } from '../mock/jwt-helper.service.spec';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import {
mockSearchUserByApp,
@ -52,37 +50,6 @@ describe('IdentityUserService', () => {
requestSpy = spyOn(adfHttpClient, 'request');
});
describe('Current user info (JWT token)', () => {
beforeEach(() => {
const store = {};
spyOn(localStorage, 'getItem').and.callFake((key: string): string => store[key] || null);
spyOn(localStorage, 'setItem').and.callFake((key: string, value: string): string => store[key] = value);
});
it('should fetch identity user info from Jwt id token', () => {
localStorage.setItem(JwtHelperService.USER_ID_TOKEN, mockToken);
const user = service.getCurrentUserInfo();
expect(user).toBeDefined();
expect(user.firstName).toEqual('John');
expect(user.lastName).toEqual('Doe');
expect(user.email).toEqual('johnDoe@gmail.com');
expect(user.username).toEqual('johnDoe1');
});
it('should fallback on Jwt access token for identity user info', () => {
localStorage.setItem(JwtHelperService.USER_ACCESS_TOKEN, mockToken);
const user = service.getCurrentUserInfo();
expect(user).toBeDefined();
expect(user.firstName).toEqual('John');
expect(user.lastName).toEqual('Doe');
expect(user.email).toEqual('johnDoe@gmail.com');
expect(user.username).toEqual('johnDoe1');
});
});
describe('Search', () => {
it('should fetch users', (done) => {

Some files were not shown because too many files have changed in this diff Show More