[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

@ -153,7 +153,7 @@ runs:
echo $PROXY_HOST_BPM echo $PROXY_HOST_BPM
echo "GIT_HASH=$GIT_HASH" >> $GITHUB_ENV echo "GIT_HASH=$GIT_HASH" >> $GITHUB_ENV
- name: run test - name: run test
id: e2e_run id: e2e_run
if: ${{ steps.determine-affected.outputs.isAffected == 'true' }} if: ${{ steps.determine-affected.outputs.isAffected == 'true' }}
env: env:

View File

@ -392,15 +392,6 @@ jobs:
check-cs-env: "true" check-cs-env: "true"
check-ps-cloud-env: "true" check-ps-cloud-env: "true"
deps: "testing" 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" - description: "Process Cloud: Process"
test-id: "process-services-cloud" test-id: "process-services-cloud"
folder: "process-services-cloud/process" folder: "process-services-cloud/process"

2
.vscode/launch.json vendored
View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,13 @@
import { EcmUserModel, PeopleContentService } from '@alfresco/adf-content-services'; import { EcmUserModel, PeopleContentService } from '@alfresco/adf-content-services';
import { BpmUserModel, PeopleProcessService } from '@alfresco/adf-process-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 { Component, OnInit, Input } from '@angular/core';
import { MenuPositionX, MenuPositionY } from '@angular/material/menu'; import { MenuPositionX, MenuPositionY } from '@angular/material/menu';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
@ -46,6 +52,7 @@ export class UserInfoComponent implements OnInit {
constructor(private peopleContentService: PeopleContentService, constructor(private peopleContentService: PeopleContentService,
private peopleProcessService: PeopleProcessService, private peopleProcessService: PeopleProcessService,
private identityUserService: IdentityUserService, private identityUserService: IdentityUserService,
private basicAlfrescoAuthService: BasicAlfrescoAuthService,
private authService: AuthenticationService) { private authService: AuthenticationService) {
} }
@ -77,7 +84,7 @@ export class UserInfoComponent implements OnInit {
} }
get isLoggedIn(): boolean { get isLoggedIn(): boolean {
if (this.authService.isKerberosEnabled()) { if (this.basicAlfrescoAuthService.isKerberosEnabled()) {
return true; return true;
} }
return this.authService.isLoggedIn(); return this.authService.isLoggedIn();
@ -96,15 +103,15 @@ export class UserInfoComponent implements OnInit {
} }
private isAllLoggedIn() { 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() { private isBpmLoggedIn() {
return this.authService.isBpmLoggedIn() || (this.authService.isECMProvider() && this.authService.isKerberosEnabled()); return this.authService.isBpmLoggedIn() || (this.authService.isECMProvider() && this.basicAlfrescoAuthService.isKerberosEnabled());
} }
private isEcmLoggedIn() { 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 class="adf-full-width" name="implicitFlow" formControlName="implicitFlow">
</mat-slide-toggle> </mat-slide-toggle>
<ng-container *ngIf="supportsCodeFlow"> <ng-container *ngIf="isOAUTH">
<mat-label>Code Flow</mat-label> <mat-label>Code Flow</mat-label>
<mat-slide-toggle class="adf-full-width" name="codeFlow" formControlName="codeFlow"> <mat-slide-toggle class="adf-full-width" name="codeFlow" formControlName="codeFlow">
</mat-slide-toggle> </mat-slide-toggle>

View File

@ -65,7 +65,7 @@ export class HostSettingsComponent implements OnInit {
private storageService: StorageService, private storageService: StorageService,
private alfrescoApiService: AlfrescoApiService, private alfrescoApiService: AlfrescoApiService,
private appConfig: AppConfigService, private appConfig: AppConfigService,
private auth: AuthenticationService private authenticationService: AuthenticationService
) {} ) {}
ngOnInit() { ngOnInit() {
@ -191,8 +191,8 @@ export class HostSettingsComponent implements OnInit {
this.storageService.setItem(AppConfigValues.AUTHTYPE, values.authType); this.storageService.setItem(AppConfigValues.AUTHTYPE, values.authType);
this.alfrescoApiService.reset(); this.alfrescoApiService.reset();
this.auth.reset(); this.authenticationService.reset();
this.alfrescoApiService.getInstance().invalidateSession(); this.authenticationService.logout();
this.success.emit(true); this.success.emit(true);
} }
@ -235,10 +235,6 @@ export class HostSettingsComponent implements OnInit {
return this.form.get('authType').value === 'OAUTH'; return this.form.get('authType').value === 'OAUTH';
} }
get supportsCodeFlow(): boolean {
return this.auth.supportCodeFlow;
}
get providersControl(): UntypedFormControl { get providersControl(): UntypedFormControl {
return this.form.get('providersControl') as 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 { FileModel } from '../../models/ACS/file.model';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page'; import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
import { VersionManagePage } from '../pages/version-manager.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', () => { describe('Content Services Viewer', () => {
const acsUser = new UserModel(); 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 { FolderModel } from '../../models/ACS/folder.model';
import { browser } from 'protractor'; import { browser } from 'protractor';
import { FileModel } from '../../models/ACS/file.model'; 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', () => { describe('Document List - Pagination', () => {
const pagination = { const pagination = {

View File

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

View File

@ -24,7 +24,7 @@ import { createApiService,
UsersActions UsersActions
} from '@alfresco/adf-testing'; } from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../core/pages/content-services.page'; 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 { UploadTogglesPage } from '../../core/pages/dialog/upload-toggles.page';
import { FileModel } from '../../models/ACS/file.model'; import { FileModel } from '../../models/ACS/file.model';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page'; 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 { createApiService, LoginPage, UploadActions, UserModel, UsersActions } from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../core/pages/content-services.page'; 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 { UploadTogglesPage } from '../../core/pages/dialog/upload-toggles.page';
import { FileModel } from '../../models/ACS/file.model'; import { FileModel } from '../../models/ACS/file.model';
import { browser } from 'protractor'; import { browser } from 'protractor';
import { VersionManagePage } from '../../core/pages/version-manager.page'; import { VersionManagePage } from '../pages/version-manager.page';
describe('Upload component', () => { 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 { createApiService, DropActions, LoginPage, StringUtil, UploadActions, UserModel, UsersActions } from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../core/pages/content-services.page'; 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 { UploadTogglesPage } from '../../core/pages/dialog/upload-toggles.page';
import { FileModel } from '../../models/ACS/file.model'; import { FileModel } from '../../models/ACS/file.model';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page'; import { NavigationBarPage } from '../../core/pages/navigation-bar.page';

View File

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

View File

@ -28,9 +28,9 @@ import { createApiService,
import { browser, by, element } from 'protractor'; import { browser, by, element } from 'protractor';
import { FileModel } from '../../models/ACS/file.model'; import { FileModel } from '../../models/ACS/file.model';
import { ContentServicesPage } from '../../core/pages/content-services.page'; 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 { 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', () => { describe('Version component actions', () => {

View File

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

View File

@ -24,7 +24,7 @@ import { createApiService,
UsersActions, ViewerPage UsersActions, ViewerPage
} from '@alfresco/adf-testing'; } from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../core/pages/content-services.page'; 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 { FileModel } from '../../models/ACS/file.model';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page'; import { NavigationBarPage } from '../../core/pages/navigation-bar.page';

View File

@ -18,7 +18,7 @@
import { browser } from 'protractor'; import { browser } from 'protractor';
import { createApiService, LoginPage, UploadActions, UserModel, UsersActions } from '@alfresco/adf-testing'; import { createApiService, LoginPage, UploadActions, UserModel, UsersActions } from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../core/pages/content-services.page'; 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 { FileModel } from '../../models/ACS/file.model';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page'; 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 // @ts-ignore
if (browser.params.testConfig.appConfig.authType === 'OAUTH') { if (browser.params.testConfig.appConfig.authType === 'OAUTH') {
Logger.info(`Configure demo shell OAUTH`);
// @ts-ignore // @ts-ignore
await LocalStorageUtil.setStorageItem('identityHost', browser.params.testConfig.appConfig.identityHost); await LocalStorageUtil.setStorageItem('identityHost', browser.params.testConfig.appConfig.identityHost);
// @ts-ignore // @ts-ignore

View File

@ -270,7 +270,7 @@ describe('Search Number Range Filter', () => {
for (const currentResult of results) { for (const currentResult of results) {
const currentSize = await BrowserActions.getAttribute(currentResult, 'title'); const currentSize = await BrowserActions.getAttribute(currentResult, 'title');
if (currentSize && currentSize.trim() !== '') { 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 LOG = process.env.E2E_LOG_LEVEL;
const HOST_ECM = process.env.PROXY_HOST_ECM || HOST || 'ecm'; const HOST_ECM = process.env.PROXY_HOST_ECM || process.env.PROXY_HOST_ADF || HOST || 'ecm';
const HOST_BPM = process.env.PROXY_HOST_BPM || HOST || 'bpm'; 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 PROVIDER = process.env.PROVIDER ? process.env.PROVIDER : 'ALL';
const AUTH_TYPE = process.env.AUTH_TYPE ? process.env.AUTH_TYPE : 'BASIC'; 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 OAUTH_CLIENT_ID = process.env.OAUTH_CLIENDID || 'alfresco';
const IDENTITY_ADMIN_EMAIL = process.env.IDENTITY_ADMIN_EMAIL || "defaultadmin"; 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 { Node, PermissionsInfo } from '@alfresco/js-api';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
declare let jasmine: any;
describe('ContentService', () => { describe('ContentService', () => {
let contentService: ContentService; let contentService: ContentService;
let authService: AuthenticationService; let authService: AuthenticationService;
let storage: StorageService; let storage: StorageService;
let node: any;
const nodeId = 'fake-node-id';
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@ -44,14 +39,6 @@ describe('ContentService', () => {
storage = TestBed.inject(StorageService); storage = TestBed.inject(StorageService);
storage.clear(); storage.clear();
node = {
entry: {
id: nodeId
}
};
jasmine.Ajax.install();
const appConfig: AppConfigService = TestBed.inject(AppConfigService); const appConfig: AppConfigService = TestBed.inject(AppConfigService);
appConfig.config = { appConfig.config = {
ecmHost: 'http://localhost:9876/ecm', 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', () => { describe('AllowableOperations', () => {
it('should hasAllowableOperations be false if allowableOperation is not present in the node', () => { 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 { Injectable } from '@angular/core';
import { from, Observable, throwError, Subject } from 'rxjs'; import { from, Observable, throwError, Subject } from 'rxjs';
import { catchError, map, switchMap, filter, take } from 'rxjs/operators'; 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 { AlfrescoApiService, BpmProductVersionModel, AuthenticationService } from '@alfresco/adf-core';
import { ApiClientsService } from '@alfresco/adf-core/api';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class DiscoveryApiService { 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. * Gets product information for Content Services.
*/ */
@ -35,15 +46,17 @@ export class DiscoveryApiService {
constructor( constructor(
private authenticationService: AuthenticationService, private authenticationService: AuthenticationService,
private apiClientsService: ApiClientsService private alfrescoApiService: AlfrescoApiService
) { ) {
this.authenticationService.onLogin this.authenticationService.onLogin.subscribe(() => {
.pipe( this.alfrescoApiService.alfrescoApiInitialized.pipe(
filter(() => this.authenticationService.isEcmLoggedIn()), filter(() => this.authenticationService.isEcmLoggedIn()),
take(1), take(1),
switchMap(() => this.getEcmProductInfo()) switchMap(() => this.getEcmProductInfo())
) )
.subscribe((info) => this.ecmProductInfo$.next(info)); .subscribe((info) => this.ecmProductInfo$.next(info));
});
} }
@ -53,9 +66,8 @@ export class DiscoveryApiService {
* @returns ProductVersionModel containing product details * @returns ProductVersionModel containing product details
*/ */
getEcmProductInfo(): Observable<RepositoryInfo> { getEcmProductInfo(): Observable<RepositoryInfo> {
const discoveryApi = this.apiClientsService.get('DiscoveryClient.discovery');
return from(discoveryApi.getRepositoryInformation()) return from(this.discoveryApi.getRepositoryInformation())
.pipe( .pipe(
map((res) => res.entry.repository), map((res) => res.entry.repository),
catchError((err) => throwError(err)) catchError((err) => throwError(err))
@ -68,7 +80,7 @@ export class DiscoveryApiService {
* @returns ProductVersionModel containing product details * @returns ProductVersionModel containing product details
*/ */
getBpmProductInfo(): Observable<BpmProductVersionModel> { getBpmProductInfo(): Observable<BpmProductVersionModel> {
const aboutApi = this.apiClientsService.get('ActivitiClient.about'); const aboutApi = new AboutApi(this.alfrescoApiService.getInstance());
return from(aboutApi.getAppVersion()) return from(aboutApi.getAppVersion())
.pipe( .pipe(
@ -78,7 +90,7 @@ export class DiscoveryApiService {
} }
getBPMSystemProperties(): Observable<SystemPropertiesRepresentation> { getBPMSystemProperties(): Observable<SystemPropertiesRepresentation> {
const systemPropertiesApi = this.apiClientsService.get('ActivitiClient.system-properties'); const systemPropertiesApi = new SystemPropertiesApi(this.alfrescoApiService.getInstance());
return from(systemPropertiesApi.getProperties()) return from(systemPropertiesApi.getProperties())
.pipe( .pipe(

View File

@ -25,7 +25,6 @@ import {
import { import {
AlfrescoApiService, AlfrescoApiService,
AlfrescoApiServiceMock, AlfrescoApiServiceMock,
AuthenticationService,
CoreTestingModule CoreTestingModule
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { PeopleContentQueryRequestModel, PeopleContentService } from './people-content.service'; import { PeopleContentQueryRequestModel, PeopleContentService } from './people-content.service';
@ -34,7 +33,6 @@ import { TestBed } from '@angular/core/testing';
describe('PeopleContentService', () => { describe('PeopleContentService', () => {
let peopleContentService: PeopleContentService; let peopleContentService: PeopleContentService;
let authenticationService: AuthenticationService;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@ -47,7 +45,6 @@ describe('PeopleContentService', () => {
] ]
}); });
authenticationService = TestBed.inject(AuthenticationService);
peopleContentService = TestBed.inject(PeopleContentService); peopleContentService = TestBed.inject(PeopleContentService);
}); });
@ -130,17 +127,6 @@ describe('PeopleContentService', () => {
expect(getCurrentPersonSpy.calls.count()).toEqual(1); 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 () => { 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)); const getCurrentPersonSpy = spyOn(peopleContentService.peopleApi, 'getPerson').and.returnValue(Promise.resolve({entry: fakeEcmAdminUser} as any));
await peopleContentService.getCurrentUserInfo().toPromise(); await peopleContentService.getCurrentUserInfo().toPromise();

View File

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

View File

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

View File

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

View File

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

View File

@ -15,9 +15,6 @@
* limitations under the License. * 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/types';
export * from './lib/adf-http-client.service'; export * from './lib/adf-http-client.service';
export * from './lib/interfaces'; export * from './lib/interfaces';

View File

@ -321,4 +321,86 @@ describe('AdfHttpClient', () => {
req.flush(null, { status: 200, statusText: 'Ok' }); 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; on: ee.EmitterMethod;
off: ee.EmitterMethod; off: ee.EmitterMethod;
once: ee.EmitterMethod; once: ee.EmitterMethod;
_disableCsrf: boolean;
emit: (type: string, ...args: any[]) => void; emit: (type: string, ...args: any[]) => void;
private _disableCsrf = false;
private defaultSecurityOptions = {
withCredentials: true,
isBpmRequest: false,
authentications: {},
defaultHeaders: {}
};
get disableCsrf(): boolean { get disableCsrf(): boolean {
return this._disableCsrf; return this._disableCsrf;
} }
@ -76,8 +69,14 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
this._disableCsrf = disableCsrf; this._disableCsrf = disableCsrf;
} }
constructor(private httpClient: HttpClient private defaultSecurityOptions = {
) { withCredentials: true,
isBpmRequest: false,
authentications: {},
defaultHeaders: {}
};
constructor(private httpClient: HttpClient) {
ee(this); ee(this);
} }
@ -217,7 +216,7 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
} }
eventEmitter.emit('error', err); eventEmitter.emit('error', err);
apiClientEmitter.emit('error', err); apiClientEmitter.emit('error', { ...err, response: { req: err } });
if (err.status === 401) { if (err.status === 401) {
eventEmitter.emit('unauthorized'); 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; // for backwards compatibility to handle cases in code where we try read response.error.response.body;
const error = { 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); return throwError(alfrescoApiError);
}), }),
takeUntil(abort$) takeUntil(abort$)
@ -252,7 +251,7 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
} }
private static getBody(options: RequestOptions): any { 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 isFormData = contentType === 'multipart/form-data';
const isFormUrlEncoded = contentType === 'application/x-www-form-urlencoded'; const isFormUrlEncoded = contentType === 'application/x-www-form-urlencoded';
const body = options.bodyParam; const body = options.bodyParam;
@ -269,20 +268,58 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
} }
private getHeaders(options: RequestOptions): HttpHeaders { private getHeaders(options: RequestOptions): HttpHeaders {
const contentType = options.contentType || AdfHttpClient.jsonPreferredMime(options.contentTypes);
const accept = options.accept || AdfHttpClient.jsonPreferredMime(options.accepts);
const optionsHeaders = { const optionsHeaders = {
...options.headerParams, ...options.headerParams,
...(options.accept && {Accept: options.accept}), ...(accept && {Accept: accept}),
...((options.contentType) && {'Content-Type': options.contentType}) ...((contentType) && {'Content-Type': contentType})
}; };
if (!this.disableCsrf) { if (!this.disableCsrf) {
this.setCsrfToken(optionsHeaders); this.setCsrfToken(optionsHeaders);
} }
return new HttpHeaders(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) { private setCsrfToken(optionsHeaders: any) {
const token = this.createCSRFToken(); const token = this.createCSRFToken();
optionsHeaders['X-CSRF-TOKEN'] = token; 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. * 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 { export interface RequestOptions {
httpMethod?: string; httpMethod?: string;
pathParams?: any;
queryParams?: any; queryParams?: any;
headerParams?: any; headerParams?: any;
formParams?: any; formParams?: any;
bodyParam?: any; bodyParam?: any;
returnType?: any; returnType?: any;
responseType?: string; responseType?: string;
accepts?: string[];
contentTypes?: string[];
readonly accept?: string; readonly accept?: string;
readonly contentType?: 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'; import { AuthenticationInterceptor, SHOULD_ADD_AUTH_TOKEN } from './authentication.interceptor';
class MockAuthentication extends Authentication { class MockAuthentication extends Authentication {
addTokenToHeader(httpHeaders: HttpHeaders): Observable<HttpHeaders> { addTokenToHeader(_: string, httpHeaders: HttpHeaders): Observable<HttpHeaders> {
return of(httpHeaders); return of(httpHeaders);
} }
} }

View File

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

View File

@ -19,5 +19,5 @@ import { HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
export abstract class Authentication { 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 { Injectable } from '@angular/core';
import { AppConfigService, AppConfigValues } from '../app-config/app-config.service'; import { AppConfigService, AppConfigValues } from '../app-config/app-config.service';
import { AlfrescoApiService } from '../services/alfresco-api.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 * Create a factory to resolve an api service instance
@ -34,10 +36,22 @@ export function createAlfrescoApiInstance(angularAlfrescoApiService: AlfrescoApi
providedIn: 'root' providedIn: 'root'
}) })
export class AlfrescoApiLoaderService { 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> { async init(): Promise<any> {
await this.appConfig.load(); 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(); return this.initAngularAlfrescoApi();
} }
@ -59,6 +73,8 @@ export class AlfrescoApiLoaderService {
disableCsrf: this.appConfig.get<boolean>(AppConfigValues.DISABLECSRF), disableCsrf: this.appConfig.get<boolean>(AppConfigValues.DISABLECSRF),
withCredentials: this.appConfig.get<boolean>(AppConfigValues.AUTH_WITH_CREDENTIALS, false), withCredentials: this.appConfig.get<boolean>(AppConfigValues.AUTH_WITH_CREDENTIALS, false),
domainPrefix: this.appConfig.get<string>(AppConfigValues.STORAGE_PREFIX), 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 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 * @returns factory function
*/ */
export function loadAppConfig(appConfigService: AppConfigService, storageService: StorageService, adfHttpClient: AdfHttpClient) { export function loadAppConfig(appConfigService: AppConfigService, storageService: StorageService, adfHttpClient: AdfHttpClient) {
return () => appConfigService.load().then(() => {
const init = () => {
adfHttpClient.disableCsrf = appConfigService.get<boolean>(AppConfigValues.DISABLECSRF, true); adfHttpClient.disableCsrf = appConfigService.get<boolean>(AppConfigValues.DISABLECSRF, true);
storageService.prefix = appConfigService.get<string>(AppConfigValues.STORAGE_PREFIX, ''); 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'); 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 { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ObjectUtils } from '../common/utils/object-utils'; 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 { map, distinctUntilChanged, take } from 'rxjs/operators';
import { ExtensionConfig, ExtensionService, mergeObjects } from '@alfresco/adf-extensions'; import { ExtensionConfig, ExtensionService, mergeObjects } from '@alfresco/adf-extensions';
import { OpenidConfiguration } from '../auth/interfaces/openid-configuration.interface'; import { OpenidConfiguration } from '../auth/interfaces/openid-configuration.interface';
import { OauthConfigModel } from '../auth/models/oauth-config.model'; import { OauthConfigModel } from '../auth/models/oauth-config.model';
/* spellchecker: disable */ /* spellchecker: disable */
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
export enum AppConfigValues { export enum AppConfigValues {
APP_CONFIG_LANGUAGES_KEY = 'languages', APP_CONFIG_LANGUAGES_KEY = 'languages',
@ -44,7 +45,9 @@ export enum AppConfigValues {
AUTH_WITH_CREDENTIALS = 'auth.withCredentials', AUTH_WITH_CREDENTIALS = 'auth.withCredentials',
APPLICATION = 'application', APPLICATION = 'application',
STORAGE_PREFIX = 'application.storagePrefix', 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 // eslint-disable-next-line no-shadow
@ -71,11 +74,15 @@ export class AppConfigService {
}; };
status: Status = Status.INIT; status: Status = Status.INIT;
protected onLoadSubject: Subject<any>; protected onLoadSubject: ReplaySubject<any>;
onLoad: Observable<any>; onLoad: Observable<any>;
get isLoaded() {
return this.status === Status.LOADED;
}
constructor(protected http: HttpClient, protected extensionService: ExtensionService) { constructor(protected http: HttpClient, protected extensionService: ExtensionService) {
this.onLoadSubject = new Subject(); this.onLoadSubject = new ReplaySubject();
this.onLoad = this.onLoadSubject.asObservable(); this.onLoad = this.onLoadSubject.asObservable();
extensionService.setup$.subscribe((config) => { extensionService.setup$.subscribe((config) => {
@ -92,7 +99,7 @@ export class AppConfigService {
select(property: string): Observable<any> { select(property: string): Observable<any> {
return this.onLoadSubject return this.onLoadSubject
.pipe( .pipe(
map((config) => config[property]), map((config) => ObjectUtils.getValue(config, property)),
distinctUntilChanged() distinctUntilChanged()
); );
} }
@ -160,8 +167,7 @@ export class AppConfigService {
this.onLoadSubject.next(this.config); this.onLoadSubject.next(this.config);
} }
protected onDataLoaded(data: any) { protected onDataLoaded() {
this.config = Object.assign({}, this.config, data || {});
this.onLoadSubject.next(this.config); this.onLoadSubject.next(this.config);
this.extensionService.setup$ this.extensionService.setup$
@ -182,9 +188,10 @@ export class AppConfigService {
/** /**
* Loads the config file. * Loads the config file.
* *
* @param callback an optional callback to execute when configuration is loaded
* @returns Notification when loading is complete * @returns Notification when loading is complete
*/ */
load(): Promise<any> { load(callback?: (...args: any[]) => any): Promise<any> {
return new Promise((resolve) => { return new Promise((resolve) => {
const configUrl = `app.config.json?v=${Date.now()}`; const configUrl = `app.config.json?v=${Date.now()}`;
@ -193,8 +200,10 @@ export class AppConfigService {
this.http.get(configUrl).subscribe( this.http.get(configUrl).subscribe(
(data: any) => { (data: any) => {
this.status = Status.LOADED; this.status = Status.LOADED;
this.config = Object.assign({}, this.config, data || {});
callback?.();
resolve(data); resolve(data);
this.onDataLoaded(data); this.onDataLoaded();
}, },
() => { () => {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -227,6 +236,8 @@ export class AppConfigService {
resolve(res); resolve(res);
}, },
error: (err: any) => { error: (err: any) => {
// eslint-disable-next-line no-console
console.error('hostIdp not correctly configured or unreachable');
reject(err); reject(err);
} }
}); });
@ -262,4 +273,5 @@ export class AppConfigService {
return result; return result;
} }
} }

View File

@ -17,9 +17,10 @@
import { HttpClient, HttpHandler, HttpRequest } from '@angular/common/http'; import { HttpClient, HttpHandler, HttpRequest } from '@angular/common/http';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { Observable, of } from 'rxjs'; import { EMPTY, Observable, of } from 'rxjs';
import { AuthBearerInterceptor } from './auth-bearer.interceptor'; import { AuthBearerInterceptor } from './auth-bearer.interceptor';
import { AuthenticationService } from '../services/authentication.service'; import { AuthenticationService } from '../services/authentication.service';
import { RedirectAuthService } from '../oidc/redirect-auth.service';
const mockNext: HttpHandler = { const mockNext: HttpHandler = {
handle: () => new Observable(subscriber => { handle: () => new Observable(subscriber => {
@ -40,7 +41,8 @@ describe('AuthBearerInterceptor', () => {
HttpClient, HttpClient,
HttpHandler, HttpHandler,
AuthBearerInterceptor, 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', () => { 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 = [ const mockUrls = [
'http://example.com/auth/realms/testpath', 'http://example.com/auth/realms/testpath',

View File

@ -16,40 +16,37 @@
*/ */
import { throwError as observableThrowError, Observable } from 'rxjs'; import { throwError as observableThrowError, Observable } from 'rxjs';
import { Injectable, Injector } from '@angular/core'; import { Injectable } from '@angular/core';
import { import {
HttpHandler, HttpInterceptor, HttpRequest, HttpHandler, HttpInterceptor, HttpRequest,
HttpSentEvent, HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpUserEvent, HttpHeaders HttpSentEvent, HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpUserEvent, HttpHeaders
} from '@angular/common/http'; } from '@angular/common/http';
import { AuthenticationService } from '../services/authentication.service';
import { catchError, mergeMap } from 'rxjs/operators'; import { catchError, mergeMap } from 'rxjs/operators';
import { AuthenticationService } from '../services/authentication.service';
@Injectable() @Injectable()
export class AuthBearerInterceptor implements HttpInterceptor { export class AuthBearerInterceptor implements HttpInterceptor {
private excludedUrlsRegex: RegExp[]; private _bearerExcludedUrls: readonly string[] = ['resources/', 'assets/', 'auth/realms', 'idp/'];
constructor(private injector: Injector, private authService: AuthenticationService) { } private excludedUrlsRegex: RegExp[];
constructor(private authenticationService: AuthenticationService) { }
private loadExcludedUrlsRegex() { private loadExcludedUrlsRegex() {
const excludedUrls = this.authService.getBearerExcludedUrls(); const excludedUrls = this.bearerExcludedUrls;
this.excludedUrlsRegex = excludedUrls.map((urlPattern) => new RegExp(`^https?://[^/]+/${urlPattern}`, 'i')) || []; this.excludedUrlsRegex = excludedUrls.map((urlPattern) => new RegExp(`^https?://[^/]+/${urlPattern}`, 'i')) || [];
} }
intercept(req: HttpRequest<any>, next: HttpHandler): intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> { 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) { if (!this.excludedUrlsRegex) {
this.loadExcludedUrlsRegex(); this.loadExcludedUrlsRegex();
} }
const urlRequest = req.url; const requestUrl = req.url;
const shallPass: boolean = this.excludedUrlsRegex.some((regex) => regex.test(urlRequest)); const shallPass: boolean = this.excludedUrlsRegex.some((regex) => regex.test(requestUrl));
if (shallPass) { if (shallPass) {
return next.handle(req) return next.handle(req)
.pipe( .pipe(
@ -57,10 +54,10 @@ export class AuthBearerInterceptor implements HttpInterceptor {
); );
} }
return this.authService.addTokenToHeader(req.headers) return this.authenticationService.addTokenToHeader(requestUrl, req.headers)
.pipe( .pipe(
mergeMap((headersWithBearer) => { mergeMap((headersWithBearer) => {
const headerWithContentType = this.appendJsonContentType(headersWithBearer); const headerWithContentType = this.appendJsonContentType(headersWithBearer, req.body);
const kcReq = req.clone({ headers: headerWithContentType}); const kcReq = req.clone({ headers: headerWithContentType});
return next.handle(kcReq) return next.handle(kcReq)
.pipe( .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, // 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 // 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'); 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.set('Content-Type', 'application/json;charset=UTF-8');
} }
return headers; 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 { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild, UrlTree } from '@angular/router';
import { AuthenticationService } from '../services/authentication.service'; 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 { OauthConfigModel } from '../models/oauth-config.model';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { StorageService } from '../../common/services/storage.service'; import { StorageService } from '../../common/services/storage.service';
import { Observable } from 'rxjs'; 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 { 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 { protected get withCredentials(): boolean {
return this.appConfigService.get<boolean>('auth.withCredentials', false); 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( abstract checkLogin(
activeRoute: ActivatedRouteSnapshot, activeRoute: ActivatedRouteSnapshot,
redirectUrl: string redirectUrl: string
@ -78,15 +89,17 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
let urlToRedirect = `/${this.getLoginRoute()}`; let urlToRedirect = `/${this.getLoginRoute()}`;
if (!this.authenticationService.isOauth()) { if (!this.authenticationService.isOauth()) {
this.authenticationService.setRedirect({ this.basicAlfrescoAuthService.setRedirect({
provider: this.getProvider(), provider: this.getProvider(),
url url
}); });
urlToRedirect = `${urlToRedirect}?redirectUrl=${url}`; urlToRedirect = `${urlToRedirect}?redirectUrl=${url}`;
return this.navigate(urlToRedirect); return this.navigate(urlToRedirect);
} else if (this.getOauthConfig().silentLogin && !this.authenticationService.isPublicUrl()) { } else if (this.getOauthConfig().silentLogin && !this.oidcAuthenticationService.isPublicUrl()) {
this.authenticationService.ssoImplicitLogin(); if (!this.oidcAuthenticationService.hasValidIdToken() || !this.oidcAuthenticationService.hasValidAccessToken()) {
this.oidcAuthenticationService.ssoImplicitLogin();
}
} else { } else {
return this.navigate(urlToRedirect); return this.navigate(urlToRedirect);
} }
@ -101,7 +114,13 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
} }
protected getOauthConfig(): OauthConfigModel { protected getOauthConfig(): OauthConfigModel {
return this.appConfigService.oauth2; return (
this.appConfigService &&
this.appConfigService.get<OauthConfigModel>(
AppConfigValues.OAUTHCONFIG,
null
)
);
} }
protected getLoginRoute(): string { protected getLoginRoute(): string {
@ -113,12 +132,21 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
} }
protected isOAuthWithoutSilentLogin(): boolean { protected isOAuthWithoutSilentLogin(): boolean {
const oauth = this.appConfigService.oauth2; const oauth = this.appConfigService.get<OauthConfigModel>(
return this.authenticationService.isOauth() && !!oauth && !oauth.silentLogin; AppConfigValues.OAUTHCONFIG,
null
);
return (
this.authenticationService.isOauth() && !!oauth && !oauth.silentLogin
);
} }
protected isSilentLogin(): boolean { protected isSilentLogin(): boolean {
const oauth = this.appConfigService.oauth2; const oauth = this.appConfigService.get<OauthConfigModel>(
AppConfigValues.OAUTHCONFIG,
null
);
return this.authenticationService.isOauth() && oauth && oauth.silentLogin; 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 { CoreTestingModule } from '../../testing/core.testing.module';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core'; 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', () => { describe('AuthGuardService BPM', () => {
let authGuard: AuthGuardBpm; let authGuard: AuthGuardBpm;
let authService: AuthenticationService; let authService: AuthenticationService;
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
let oidcAuthenticationService: OidcAuthenticationService;
let router: Router; let router: Router;
let appConfigService: AppConfigService; let appConfigService: AppConfigService;
@ -36,9 +41,21 @@ describe('AuthGuardService BPM', () => {
imports: [ imports: [
TranslateModule.forRoot(), TranslateModule.forRoot(),
CoreTestingModule CoreTestingModule
],
providers: [
{
provide: OidcAuthenticationService, useValue: {
ssoImplicitLogin: () => { },
isPublicUrl: () => false,
hasValidIdToken: () => false,
isLoggedIn: () => false
}
}
] ]
}); });
localStorage.clear(); localStorage.clear();
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
oidcAuthenticationService = TestBed.inject(OidcAuthenticationService);
authService = TestBed.inject(AuthenticationService); authService = TestBed.inject(AuthenticationService);
authGuard = TestBed.inject(AuthGuardBpm); authGuard = TestBed.inject(AuthGuardBpm);
router = TestBed.inject(Router); router = TestBed.inject(Router);
@ -53,8 +70,8 @@ describe('AuthGuardService BPM', () => {
spyOn(router, 'navigateByUrl').and.stub(); spyOn(router, 'navigateByUrl').and.stub();
spyOn(authService, 'isBpmLoggedIn').and.returnValue(false); spyOn(authService, 'isBpmLoggedIn').and.returnValue(false);
spyOn(authService, 'isOauth').and.returnValue(true); spyOn(authService, 'isOauth').and.returnValue(true);
spyOn(authService, 'isPublicUrl').and.returnValue(false); spyOn(oidcAuthenticationService, 'isPublicUrl').and.returnValue(false);
spyOn(authService, 'ssoImplicitLogin').and.stub(); spyOn(oidcAuthenticationService, 'ssoImplicitLogin').and.stub();
appConfigService.config.oauth2 = { appConfigService.config.oauth2 = {
silentLogin: true, silentLogin: true,
@ -69,7 +86,7 @@ describe('AuthGuardService BPM', () => {
const route = { url: 'abc' } as RouterStateSnapshot; const route = { url: 'abc' } as RouterStateSnapshot;
expect(await authGuard.canActivate(null, route)).toBeFalsy(); 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 () => { 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', () => { it('should set redirect url', () => {
spyOn(authService, 'setRedirect').and.callThrough(); spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
spyOn(router, 'navigateByUrl').and.stub(); spyOn(router, 'navigateByUrl').and.stub();
const route = { url: 'some-url' } as RouterStateSnapshot; const route = { url: 'some-url' } as RouterStateSnapshot;
authGuard.canActivate(null, route); authGuard.canActivate(null, route);
expect(authService.setRedirect).toHaveBeenCalledWith({ expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'BPM', url: 'some-url' 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', () => { it('should set redirect navigation commands with query params', () => {
spyOn(authService, 'setRedirect').and.callThrough(); spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
spyOn(router, 'navigateByUrl').and.stub(); spyOn(router, 'navigateByUrl').and.stub();
const route = { url: 'some-url;q=123' } as RouterStateSnapshot; const route = { url: 'some-url;q=123' } as RouterStateSnapshot;
authGuard.canActivate(null, route); authGuard.canActivate(null, route);
expect(authService.setRedirect).toHaveBeenCalledWith({ expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'BPM', url: 'some-url;q=123' 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', () => { it('should set redirect navigation commands with query params', () => {
spyOn(authService, 'setRedirect').and.callThrough(); spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
spyOn(router, 'navigateByUrl').and.stub(); spyOn(router, 'navigateByUrl').and.stub();
const route = { url: '/' } as RouterStateSnapshot; const route = { url: '/' } as RouterStateSnapshot;
authGuard.canActivate(null, route); authGuard.canActivate(null, route);
expect(authService.setRedirect).toHaveBeenCalledWith({ expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'BPM', url: '/' provider: 'BPM', url: '/'
}); });
expect(authService.getRedirect()).toEqual('/'); expect(basicAlfrescoAuthService.getRedirect()).toEqual('/');
}); });
it('should get redirect url from config if there is one configured', () => { it('should get redirect url from config if there is one configured', () => {
appConfigService.config.loginRoute = 'fakeLoginRoute'; appConfigService.config.loginRoute = 'fakeLoginRoute';
spyOn(authService, 'setRedirect').and.callThrough(); spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
spyOn(router, 'navigateByUrl').and.stub(); spyOn(router, 'navigateByUrl').and.stub();
const route = { url: 'some-url' } as RouterStateSnapshot; const route = { url: 'some-url' } as RouterStateSnapshot;
authGuard.canActivate(null, route); authGuard.canActivate(null, route);
expect(authService.setRedirect).toHaveBeenCalledWith({ expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'BPM', url: 'some-url' provider: 'BPM', url: 'some-url'
}); });
expect(router.navigateByUrl).toHaveBeenCalledWith(router.parseUrl('/fakeLoginRoute?redirectUrl=some-url')); expect(router.navigateByUrl).toHaveBeenCalledWith(router.parseUrl('/fakeLoginRoute?redirectUrl=some-url'));
@ -187,13 +204,13 @@ describe('AuthGuardService BPM', () => {
spyOn(materialDialog, 'closeAll'); spyOn(materialDialog, 'closeAll');
spyOn(authService, 'setRedirect').and.callThrough(); spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
spyOn(router, 'navigateByUrl').and.stub(); spyOn(router, 'navigateByUrl').and.stub();
const route = { url: 'some-url' } as RouterStateSnapshot; const route = { url: 'some-url' } as RouterStateSnapshot;
authGuard.canActivate(null, route); authGuard.canActivate(null, route);
expect(authService.setRedirect).toHaveBeenCalledWith({ expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
provider: 'BPM', url: 'some-url' provider: 'BPM', url: 'some-url'
}); });

View File

@ -16,13 +16,30 @@
*/ */
import { Injectable } from '@angular/core'; 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 { 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({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AuthGuardBpm extends AuthGuardBase { 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> { async checkLogin(_: ActivatedRouteSnapshot, redirectUrl: string): Promise<boolean | UrlTree> {
if (this.authenticationService.isBpmLoggedIn() || this.withCredentials) { if (this.authenticationService.isBpmLoggedIn() || this.withCredentials) {
return true; return true;

View File

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

View File

@ -16,13 +16,33 @@
*/ */
import { Injectable } from '@angular/core'; 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 { 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({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AuthGuardEcm extends AuthGuardBase { 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> { async checkLogin(_: ActivatedRouteSnapshot, redirectUrl: string): Promise<boolean | UrlTree> {
if (this.authenticationService.isEcmLoggedIn() || this.withCredentials) { if (this.authenticationService.isEcmLoggedIn() || this.withCredentials) {
return true; return true;

View File

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

View File

@ -16,9 +16,16 @@
*/ */
import { Injectable } from '@angular/core'; 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 { AuthGuardBase } from './auth-guard-base';
import { JwtHelperService } from '../services/jwt-helper.service'; 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({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -27,8 +34,15 @@ export class AuthGuard extends AuthGuardBase {
ticketChangeBind: any; ticketChangeBind: any;
constructor(private jwtHelperService: JwtHelperService) { constructor(private jwtHelperService: JwtHelperService,
super(); 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); this.ticketChangeBind = this.ticketChange.bind(this);
window.addEventListener('storage', this.ticketChangeBind); 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. * limitations under the License.
*/ */
export * from './activiti/activiti-client.types'; export interface Authentication {
export * from './alfresco-js-clients.module'; basicAuth?: BasicAuth;
export * from './discovery/discovery-client.types'; 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 { AuthConfig, AUTH_CONFIG, OAuthModule, OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
import { AlfrescoApiNoAuthService } from '../../api-factories/alfresco-api-no-auth.service'; import { AlfrescoApiNoAuthService } from '../../api-factories/alfresco-api-no-auth.service';
import { AlfrescoApiService } from '../../services/alfresco-api.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 { AuthenticationService } from '../services/authentication.service';
import { StorageService } from '../../common/services/storage.service'; import { StorageService } from '../../common/services/storage.service';
import { AuthModuleConfig, AUTH_MODULE_CONFIG } from './auth-config'; import { AuthModuleConfig, AUTH_MODULE_CONFIG } from './auth-config';
import { authConfigFactory, AuthConfigService } from './auth-config.service'; import { authConfigFactory, AuthConfigService } from './auth-config.service';
import { AuthRoutingModule } from './auth-routing.module'; import { AuthRoutingModule } from './auth-routing.module';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { OidcAuthGuard } from './oidc-auth.guard';
import { OIDCAuthenticationService } from './oidc-authentication.service';
import { RedirectAuthService } from './redirect-auth.service'; import { RedirectAuthService } from './redirect-auth.service';
import { AuthenticationConfirmationComponent } from './view/authentication-confirmation/authentication-confirmation.component'; import { AuthenticationConfirmationComponent } from './view/authentication-confirmation/authentication-confirmation.component';
@ -51,10 +46,10 @@ export function loginFactory(oAuthService: OAuthService, storage: OAuthStorage,
imports: [AuthRoutingModule, OAuthModule.forRoot()], imports: [AuthRoutingModule, OAuthModule.forRoot()],
providers: [ providers: [
{ provide: OAuthStorage, useExisting: StorageService }, { provide: OAuthStorage, useExisting: StorageService },
{ provide: AuthGuard, useClass: OidcAuthGuard }, // { provide: AuthGuard, useClass: OidcAuthGuard },
{ provide: AuthGuardEcm, useClass: OidcAuthGuard }, // { provide: AuthGuardEcm, useClass: OidcAuthGuard },
{ provide: AuthGuardBpm, useClass: OidcAuthGuard }, // { provide: AuthGuardBpm, useClass: OidcAuthGuard },
{ provide: AuthenticationService, useClass: OIDCAuthenticationService }, { provide: AuthenticationService},
{ provide: AlfrescoApiService, useClass: AlfrescoApiNoAuthService }, { provide: AlfrescoApiService, useClass: AlfrescoApiNoAuthService },
{ {
provide: AUTH_CONFIG, provide: AUTH_CONFIG,

View File

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

View File

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

View File

@ -31,6 +31,10 @@ export * from './services/jwt-helper.service';
export * from './services/oauth2.service'; export * from './services/oauth2.service';
export * from './services/user-access.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-user.service.interface';
export * from './interfaces/identity-group.interface'; export * from './interfaces/identity-group.interface';
export * from './interfaces/openid-configuration.interface'; export * from './interfaces/openid-configuration.interface';

View File

@ -16,21 +16,23 @@
*/ */
import { fakeAsync, TestBed } from '@angular/core/testing'; import { fakeAsync, TestBed } from '@angular/core/testing';
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { AuthenticationService } from './authentication.service'; import { AuthenticationService } from './authentication.service';
import { CookieService } from '../../common/services/cookie.service'; import { CookieService } from '../../common/services/cookie.service';
import { AppConfigService } from '../../app-config/app-config.service'; import { AppConfigService } from '../../app-config/app-config.service';
import { setupTestBed } from '../../testing/setup-test-bed'; import { setupTestBed } from '../../testing/setup-test-bed';
import { CoreTestingModule } from '../../testing/core.testing.module'; import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core'; 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; declare let jasmine: any;
describe('AuthenticationService', () => { describe('AuthenticationService', () => {
let apiService: AlfrescoApiService;
let authService: AuthenticationService; let authService: AuthenticationService;
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
let appConfigService: AppConfigService; let appConfigService: AppConfigService;
let cookie: CookieService; let cookie: CookieService;
let oidcAuthenticationService: OidcAuthenticationService;
setupTestBed({ setupTestBed({
imports: [ imports: [
@ -42,8 +44,9 @@ describe('AuthenticationService', () => {
beforeEach(() => { beforeEach(() => {
sessionStorage.clear(); sessionStorage.clear();
localStorage.clear(); localStorage.clear();
apiService = TestBed.inject(AlfrescoApiService);
authService = TestBed.inject(AuthenticationService); authService = TestBed.inject(AuthenticationService);
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
oidcAuthenticationService = TestBed.inject(OidcAuthenticationService);
cookie = TestBed.inject(CookieService); cookie = TestBed.inject(CookieService);
cookie.clear(); cookie.clear();
@ -73,6 +76,23 @@ describe('AuthenticationService', () => {
}); });
appConfigService.load(); 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', () => { describe('when the setting is ECM', () => {
@ -83,40 +103,30 @@ describe('AuthenticationService', () => {
appConfigService.config.auth = { withCredentials: false }; appConfigService.config.auth = { withCredentials: false };
appConfigService.config.providers = 'ECM'; appConfigService.config.providers = 'ECM';
appConfigService.load(); appConfigService.load();
apiService.reset();
}); });
it('should not require cookie service enabled for ECM check', () => { it('should not require cookie service enabled for ECM check', () => {
spyOn(cookie, 'isEnabled').and.returnValue(false); 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, 'isECMProvider').and.returnValue(true);
spyOn(authService, 'isOauth').and.returnValue(false); spyOn(authService, 'isOauth').and.returnValue(false);
spyOn(apiService, 'getInstance').and.callThrough();
expect(authService.isEcmLoggedIn()).toBeFalsy(); 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', () => { it('should require remember me set for ECM check', () => {
spyOn(cookie, 'isEnabled').and.returnValue(true); 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, 'isECMProvider').and.returnValue(true);
spyOn(authService, 'isOauth').and.returnValue(false); spyOn(authService, 'isOauth').and.returnValue(false);
spyOn(apiService, 'getInstance').and.callThrough();
expect(authService.isEcmLoggedIn()).toBeFalsy(); expect(authService.isEcmLoggedIn()).toBeFalsy();
expect(apiService.getInstance).not.toHaveBeenCalled();
}); });
it('[ECM] should return an ECM ticket after the login done', (done) => { 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.isLoggedIn()).toBe(true);
expect(authService.getTicketEcm()).toEqual('fake-post-ticket'); expect(authService.getToken()).toEqual('fake-post-ticket');
expect(authService.isEcmLoggedIn()).toBe(true); expect(authService.isEcmLoggedIn()).toBe(true);
disposableLogin.unsubscribe(); disposableLogin.unsubscribe();
done(); done();
@ -130,7 +140,7 @@ describe('AuthenticationService', () => {
}); });
it('[ECM] should login in the ECM if no provider are defined calling the login', fakeAsync(() => { 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); expect(loginResponse).toEqual(fakeECMLoginResponse);
disposableLogin.unsubscribe(); disposableLogin.unsubscribe();
}); });
@ -143,10 +153,10 @@ describe('AuthenticationService', () => {
})); }));
it('[ECM] should return a ticket undefined after logout', fakeAsync(() => { 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(() => { const disposableLogout = authService.logout().subscribe(() => {
expect(authService.isLoggedIn()).toBe(false); expect(authService.isLoggedIn()).toBe(false);
expect(authService.getTicketEcm()).toBe(null); expect(authService.getToken()).toBe(null);
expect(authService.isEcmLoggedIn()).toBe(false); expect(authService.isEcmLoggedIn()).toBe(false);
disposableLogin.unsubscribe(); disposableLogin.unsubscribe();
disposableLogout.unsubscribe(); disposableLogout.unsubscribe();
@ -170,21 +180,21 @@ describe('AuthenticationService', () => {
}); });
it('[ECM] should set/get redirectUrl when provider is ECM', () => { it('[ECM] should set/get redirectUrl when provider is ECM', () => {
authService.setRedirect({ provider: 'ECM', 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', () => { 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', () => { 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', () => { it('[ECM] should return isECMProvider true', () => {
@ -209,40 +219,30 @@ describe('AuthenticationService', () => {
beforeEach(() => { beforeEach(() => {
appConfigService.config.providers = 'BPM'; appConfigService.config.providers = 'BPM';
appConfigService.load(); appConfigService.load();
apiService.reset();
}); });
it('should require remember me set for BPM check', () => { it('should require remember me set for BPM check', () => {
spyOn(cookie, 'isEnabled').and.returnValue(true); 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, 'isBPMProvider').and.returnValue(true);
spyOn(authService, 'isOauth').and.returnValue(false); spyOn(authService, 'isOauth').and.returnValue(false);
spyOn(apiService, 'getInstance').and.callThrough();
expect(authService.isBpmLoggedIn()).toBeFalsy(); 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', () => { it('should not require cookie service enabled for BPM check', () => {
spyOn(cookie, 'isEnabled').and.returnValue(false); 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(authService, 'isBPMProvider').and.returnValue(true);
spyOn(apiService, 'getInstance').and.callThrough();
expect(authService.isBpmLoggedIn()).toBeFalsy(); expect(authService.isBpmLoggedIn()).toBeFalsy();
expect(apiService.getInstance).toHaveBeenCalled();
}); });
it('[BPM] should return an BPM ticket after the login done', (done) => { 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); expect(authService.isLoggedIn()).toBe(true);
// cspell: disable-next // cspell: disable-next
expect(authService.getTicketBpm()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk'); expect(authService.getToken()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk');
expect(authService.isBpmLoggedIn()).toBe(true); expect(authService.isBpmLoggedIn()).toBe(true);
disposableLogin.unsubscribe(); disposableLogin.unsubscribe();
done(); done();
@ -255,10 +255,10 @@ describe('AuthenticationService', () => {
}); });
it('[BPM] should return a ticket undefined after logout', (done) => { 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(() => { const disposableLogout = authService.logout().subscribe(() => {
expect(authService.isLoggedIn()).toBe(false); expect(authService.isLoggedIn()).toBe(false);
expect(authService.getTicketBpm()).toBe(null); expect(authService.getToken()).toBe(null);
expect(authService.isBpmLoggedIn()).toBe(false); expect(authService.isBpmLoggedIn()).toBe(false);
disposableLogout.unsubscribe(); disposableLogout.unsubscribe();
disposableLogin.unsubscribe(); disposableLogin.unsubscribe();
@ -281,7 +281,7 @@ describe('AuthenticationService', () => {
}, },
(err: any) => { (err: any) => {
expect(err).toBeDefined(); expect(err).toBeDefined();
expect(authService.getTicketBpm()).toBe(undefined); expect(authService.getToken()).toBe(null);
done(); done();
}); });
@ -291,21 +291,21 @@ describe('AuthenticationService', () => {
}); });
it('[BPM] should set/get redirectUrl when provider is BPM', () => { it('[BPM] should set/get redirectUrl when provider is BPM', () => {
authService.setRedirect({ provider: 'BPM', 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', () => { 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', () => { 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', () => { it('[BPM] should return isECMProvider false', () => {
@ -326,11 +326,10 @@ describe('AuthenticationService', () => {
beforeEach(() => { beforeEach(() => {
appConfigService.config.providers = 'ECM'; appConfigService.config.providers = 'ECM';
appConfigService.load(); appConfigService.load();
apiService.reset();
}); });
it('[ECM] should save the remember me cookie as a session cookie after successful login', (done) => { it('[ECM] should save the remember me cookie as a session cookie after successful login', (done) => {
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']).not.toBeUndefined();
expect(cookie['ALFRESCO_REMEMBER_ME'].expiration).toBeNull(); expect(cookie['ALFRESCO_REMEMBER_ME'].expiration).toBeNull();
disposableLogin.unsubscribe(); disposableLogin.unsubscribe();
@ -345,7 +344,7 @@ describe('AuthenticationService', () => {
}); });
it('[ECM] should save the remember me cookie as a persistent cookie after successful login', (done) => { 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']).not.toBeUndefined();
expect(cookie['ALFRESCO_REMEMBER_ME'].expiration).not.toBeNull(); expect(cookie['ALFRESCO_REMEMBER_ME'].expiration).not.toBeNull();
disposableLogin.unsubscribe(); disposableLogin.unsubscribe();
@ -361,7 +360,7 @@ describe('AuthenticationService', () => {
}); });
it('[ECM] should not save the remember me cookie after failed login', (done) => { 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(); expect(cookie['ALFRESCO_REMEMBER_ME']).toBeUndefined();
@ -390,15 +389,14 @@ describe('AuthenticationService', () => {
beforeEach(() => { beforeEach(() => {
appConfigService.config.providers = 'ALL'; appConfigService.config.providers = 'ALL';
appConfigService.load(); appConfigService.load();
apiService.reset();
}); });
it('[ALL] should return both ECM and BPM tickets after the login done', (done) => { it('[ALL] should return both ECM and BPM tickets after the login done', (done) => {
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.isLoggedIn()).toBe(true);
expect(authService.getTicketEcm()).toEqual('fake-post-ticket'); expect(basicAlfrescoAuthService.getTicketEcm()).toEqual('fake-post-ticket');
// cspell: disable-next // cspell: disable-next
expect(authService.getTicketBpm()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk'); expect(basicAlfrescoAuthService.getTicketBpm()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk');
expect(authService.isBpmLoggedIn()).toBe(true); expect(authService.isBpmLoggedIn()).toBe(true);
expect(authService.isEcmLoggedIn()).toBe(true); expect(authService.isEcmLoggedIn()).toBe(true);
disposableLogin.unsubscribe(); disposableLogin.unsubscribe();
@ -417,13 +415,13 @@ describe('AuthenticationService', () => {
}); });
it('[ALL] should return login fail if only ECM call fail', (done) => { 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.isLoggedIn()).toBe(false, 'isLoggedIn');
expect(authService.getTicketEcm()).toBe(null, 'getTicketEcm'); expect(authService.getToken()).toBe(null, 'getTicketEcm');
// cspell: disable-next // cspell: disable-next
expect(authService.getTicketBpm()).toBe(null, 'getTicketBpm'); expect(authService.getToken()).toBe(null, 'getTicketBpm');
expect(authService.isEcmLoggedIn()).toBe(false, 'isEcmLoggedIn'); expect(authService.isEcmLoggedIn()).toBe(false, 'isEcmLoggedIn');
disposableLogin.unsubscribe(); disposableLogin.unsubscribe();
done(); done();
@ -439,12 +437,12 @@ describe('AuthenticationService', () => {
}); });
it('[ALL] should return login fail if only BPM call fail', (done) => { 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.isLoggedIn()).toBe(false);
expect(authService.getTicketEcm()).toBe(null); expect(authService.getToken()).toBe(null);
expect(authService.getTicketBpm()).toBe(null); expect(authService.getToken()).toBe(null);
expect(authService.isBpmLoggedIn()).toBe(false); expect(authService.isBpmLoggedIn()).toBe(false);
disposableLogin.unsubscribe(); disposableLogin.unsubscribe();
done(); done();
@ -462,12 +460,12 @@ describe('AuthenticationService', () => {
}); });
it('[ALL] should return ticket undefined when the credentials are wrong', (done) => { 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.isLoggedIn()).toBe(false);
expect(authService.getTicketEcm()).toBe(null); expect(authService.getToken()).toBe(null);
expect(authService.getTicketBpm()).toBe(null); expect(authService.getToken()).toBe(null);
expect(authService.isBpmLoggedIn()).toBe(false); expect(authService.isBpmLoggedIn()).toBe(false);
expect(authService.isEcmLoggedIn()).toBe(false); expect(authService.isEcmLoggedIn()).toBe(false);
disposableLogin.unsubscribe(); 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', () => { it('[ALL] should return isECMProvider false', () => {
expect(authService.isECMProvider()).toBe(false); expect(authService.isECMProvider()).toBe(false);
}); });
@ -519,4 +493,22 @@ describe('AuthenticationService', () => {
expect(authService.isALLProvider()).toBe(true); 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. * limitations under the License.
*/ */
import { Injectable, inject } from '@angular/core'; import { Injectable, Injector } from '@angular/core';
import { Observable, from } from 'rxjs'; import { OidcAuthenticationService } from './oidc-authentication.service';
import { AppConfigValues } from '../../app-config/app-config.service'; import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
import { map, catchError, tap } from 'rxjs/operators'; import { Observable, Subject, from } from 'rxjs';
import { JwtHelperService } from './jwt-helper.service'; import { HttpHeaders } from '@angular/common/http';
import { StorageService } from '../../common/services/storage.service'; import { AuthenticationServiceInterface } from '../interfaces/authentication-service.interface';
import { BaseAuthenticationService } from './base-authentication.service'; import ee from 'event-emitter';
import { RedirectAuthService } from '../oidc/redirect-auth.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AuthenticationService extends BaseAuthenticationService { export class AuthenticationService implements AuthenticationServiceInterface, ee.Emitter {
private storageService = inject(StorageService);
readonly supportCodeFlow = false;
constructor() { onLogin: Subject<any> = new Subject<any>();
super(); onLogout: Subject<any> = new Subject<any>();
this.alfrescoApi.alfrescoApiInitialized.subscribe(() => {
this.alfrescoApi.getInstance().reply('logged-in', () => {
this.onLogin.next();
});
});
}
/** constructor(
* Checks if the user logged in. private injector: Injector,
* private redirectAuthService: RedirectAuthService
* @returns True if logged in, false otherwise ) {
*/ this.redirectAuthService.onLogin.subscribe(
isLoggedIn(): boolean { (value) => this.onLogin.next(value)
if (!this.isOauth() && this.cookie.isEnabled() && !this.isRememberMeSet()) { );
return false;
}
return this.alfrescoApi.getInstance().isLoggedIn();
}
isLoggedInWith(provider: string): boolean { this.basicAlfrescoAuthService.onLogin.subscribe(
if (provider === 'BPM') { (value) => this.onLogin.next(value)
return this.isBpmLoggedIn(); );
} else if (provider === 'ECM') {
return this.isEcmLoggedIn(); if (this.isOauth()) {
this.oidcAuthenticationService.onLogout.subscribe(
(value) => this.onLogout.next(value)
);
} else { } else {
return this.isLoggedIn(); this.basicAlfrescoAuthService.onLogout.subscribe(
(value) => this.onLogout.next(value)
);
} }
} }
/** get on(): ee.EmitterMethod {
* Does the provider support OAuth? return this.isOauth() ? this.oidcAuthenticationService.on : this.basicAlfrescoAuthService.on;
*
* @returns True if supported, false otherwise
*/
isOauth(): boolean {
return this.alfrescoApi.getInstance().isOauthConfiguration();
} }
/** get off(): ee.EmitterMethod {
* Logs the user in. return this.isOauth() ? this.oidcAuthenticationService.off : this.basicAlfrescoAuthService.off;
*
* @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))
);
} }
/** get once(): ee.EmitterMethod {
* Logs the user in with SSO return this.isOauth() ? this.oidcAuthenticationService.once : this.basicAlfrescoAuthService.once;
*/
ssoImplicitLogin() {
this.alfrescoApi.getInstance().implicitLogin();
} }
get emit(): (type: string, ...args: any[]) => void {
/** return this.isOauth() ? this.oidcAuthenticationService.emit : this.basicAlfrescoAuthService.emit;
* 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))
);
} }
private callApiLogout(): Promise<any> { get onError(): Observable<any> {
if (this.alfrescoApi.getInstance()) { return this.isOauth() ? this.oidcAuthenticationService.onError : this.basicAlfrescoAuthService.onError;
return this.alfrescoApi.getInstance().logout(); }
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());
} }
return Promise.resolve();
} }
/**
* Checks if the user is logged in on an ECM provider.
*
* @returns True if logged in, false otherwise
*/
isEcmLoggedIn(): boolean { isEcmLoggedIn(): boolean {
if (this.isECMProvider() || this.isALLProvider()) { if (this.isOauth()) {
if (!this.isOauth() && this.cookie.isEnabled() && !this.isRememberMeSet()) { return this.oidcAuthenticationService.isEcmLoggedIn();
return false; } 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 { isBpmLoggedIn(): boolean {
if (this.isBPMProvider() || this.isALLProvider()) { if (this.isOauth()) {
if (!this.isOauth() && this.cookie.isEnabled() && !this.isRememberMeSet()) { return this.oidcAuthenticationService.isBpmLoggedIn();
return false; } else {
} return this.basicAlfrescoAuthService.isBpmLoggedIn();
return this.alfrescoApi.getInstance().isBpmLoggedIn(); }
}
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);
} }
return false;
} }
/** /**
* Gets the ECM username. * @returns the username of the authenticated user
* */
* @returns The ECM username getUsername(): string {
if (this.isOauth()) {
return this.oidcAuthenticationService.getUsername();
} else {
return this.basicAlfrescoAuthService.getUsername();
}
}
/**
* @deprecated
* @returns the logged username
*/ */
getEcmUsername(): string { getEcmUsername(): string {
return this.alfrescoApi.getInstance().getEcmUsername(); if (this.isOauth()) {
return this.oidcAuthenticationService.getUsername();
} else {
return this.basicAlfrescoAuthService.getEcmUsername();
}
} }
/** /**
* Gets the BPM username * @deprecated
* * @returns the logged username
* @returns The BPM username
*/ */
getBpmUsername(): string { getBpmUsername(): string {
return this.alfrescoApi.getInstance().getBpmUsername(); if (this.isOauth()) {
return this.oidcAuthenticationService.getUsername();
} else {
return this.basicAlfrescoAuthService.getBpmUsername();
}
} }
isImplicitFlow(): boolean { getAuthHeaders(requestUrl: string, headers: HttpHeaders): HttpHeaders {
return !!this.appConfig.oauth2?.implicitFlow; if (this.isOauth()) {
return this.oidcAuthenticationService.getAuthHeaders(requestUrl, headers);
} else {
return this.basicAlfrescoAuthService.getAuthHeaders(requestUrl, headers);
}
} }
isAuthCodeFlow(): boolean { isOauth(): boolean {
return false; return this.basicAlfrescoAuthService.isOauth();
} }
/** isKerberosEnabled(): boolean {
* Gets the auth token. return !this.isOauth() ? this.basicAlfrescoAuthService.isKerberosEnabled() : false;
*
* @returns Auth token string
*/
getToken(): string {
return this.storageService.getItem(JwtHelperService.USER_ACCESS_TOKEN);
} }
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. * limitations under the License.
*/ */
import { PeopleApi, UserProfileApi } from '@alfresco/js-api';
import { HttpHeaders } from '@angular/common/http'; import { HttpHeaders } from '@angular/common/http';
import { RedirectionModel } from '../models/redirection.model'; import { RedirectionModel } from '../models/redirection.model';
import { Observable, Observer, ReplaySubject, throwError } from 'rxjs'; import { Observable, Observer, ReplaySubject, throwError } from 'rxjs';
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service'; import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { CookieService } from '../../common/services/cookie.service'; import { CookieService } from '../../common/services/cookie.service';
import { LogService } from '../../common/services/log.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'; export abstract class BaseAuthenticationService implements AuthenticationServiceInterface, ee.Emitter {
const REMEMBER_ME_UNTIL = 1000 * 60 * 60 * 24 * 30;
export abstract class BaseAuthenticationService { on: ee.EmitterMethod;
protected alfrescoApi = inject(AlfrescoApiService); off: ee.EmitterMethod;
protected appConfig = inject(AppConfigService); once: ee.EmitterMethod;
protected cookie = inject(CookieService); emit: (type: string, ...args: any[]) => void;
private logService = inject(LogService);
protected bearerExcludedUrls: readonly string[] = ['resources/', 'assets/', 'auth/realms', 'idp/'];
protected redirectUrl: RedirectionModel = null; protected redirectUrl: RedirectionModel = null;
onError = new ReplaySubject<any>(1);
onLogin = new ReplaySubject<any>(1); onLogin = new ReplaySubject<any>(1);
onLogout = new ReplaySubject<any>(1); onLogout = new ReplaySubject<any>(1);
private _peopleApi: PeopleApi; constructor(
get peopleApi(): PeopleApi { protected appConfig: AppConfigService,
this._peopleApi = this._peopleApi ?? new PeopleApi(this.alfrescoApi.getInstance()); protected cookie: CookieService,
return this._peopleApi; private logService: LogService
) {
ee(this);
} }
private _profileApi: UserProfileApi; abstract getAuthHeaders(requestUrl: string, header: HttpHeaders): HttpHeaders;
get profileApi(): UserProfileApi {
this._profileApi = this._profileApi ?? new UserProfileApi(this.alfrescoApi.getInstance());
return this._profileApi;
}
abstract readonly supportCodeFlow: boolean;
abstract getToken(): string; 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[] { abstract isLoggedIn(): boolean;
return this.bearerExcludedUrls;
} 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. * 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 * @param headersArg Header that will receive the token
* @returns The new header with the token added * @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>) => { return new Observable((observer: Observer<any>) => {
let headers = headersArg; let headers = headersArg;
if (!headers) { if (!headers) {
headers = new HttpHeaders(); headers = new HttpHeaders();
} }
try { try {
const header = this.getAuthHeaders(headers);
const header = this.getAuthHeaders(requestUrl, headers);
observer.next(header); observer.next(header);
observer.complete(); 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 { 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 * @returns True if supported, false otherwise
*/ */
isBPMProvider(): boolean { 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 * @returns True if both are supported, false otherwise
*/ */
isALLProvider(): boolean { isALLProvider(): boolean {
return this.alfrescoApi.getInstance().isEcmBpmConfiguration(); const provider = this.appConfig.get('providers') as string;
return provider && provider.toUpperCase() === 'ALL';
} }
/** isOauthConfiguration(): boolean {
* Gets the ECM ticket stored in the Storage. const authType = this.appConfig.get('authType') as string;
* return authType === 'OAUTH';
* @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;
} }
/** /**
@ -196,64 +129,12 @@ export abstract class BaseAuthenticationService {
* @returns Object representing the error message * @returns Object representing the error message
*/ */
handleError(error: any): Observable<any> { handleError(error: any): Observable<any> {
this.onError.next(error || 'Server error');
this.logService.error('Error when logging in', error); this.logService.error('Error when logging in', error);
return throwError(error || 'Server error'); return throwError(error || 'Server error');
} }
/** isOauth(): boolean {
* Does kerberos enabled? return this.appConfig.get(AppConfigValues.AUTHTYPE) === 'OAUTH';
*
* @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');
} }
} }

View File

@ -35,6 +35,7 @@ import { IdentityRoleModel } from '../models/identity-role.model';
import { CoreTestingModule } from '../../testing/core.testing.module'; import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { AdfHttpClient } from '../../../../api/src'; import { AdfHttpClient } from '../../../../api/src';
import { StorageService } from '../../common/services/storage.service';
describe('IdentityUserService', () => { describe('IdentityUserService', () => {
@ -46,6 +47,7 @@ describe('IdentityUserService', () => {
{ id: 'id-5', name: 'MOCK-ROLE-2'} { id: 'id-5', name: 'MOCK-ROLE-2'}
]; ];
let storageService: StorageService;
let service: IdentityUserService; let service: IdentityUserService;
let adfHttpClient: AdfHttpClient; let adfHttpClient: AdfHttpClient;
let requestSpy: jasmine.Spy; let requestSpy: jasmine.Spy;
@ -57,18 +59,14 @@ describe('IdentityUserService', () => {
CoreTestingModule CoreTestingModule
] ]
}); });
storageService = TestBed.inject(StorageService);
service = TestBed.inject(IdentityUserService); service = TestBed.inject(IdentityUserService);
adfHttpClient = TestBed.inject(AdfHttpClient); adfHttpClient = TestBed.inject(AdfHttpClient);
requestSpy = spyOn(adfHttpClient, 'request'); 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', () => { 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(); const user = service.getCurrentUserInfo();
expect(user).toBeDefined(); expect(user).toBeDefined();
expect(user.firstName).toEqual('John'); expect(user.firstName).toEqual('John');
@ -78,7 +76,7 @@ describe('IdentityUserService', () => {
}); });
it('should fallback on Jwt access token for identity user info', () => { 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(); const user = service.getCurrentUserInfo();
expect(user).toBeDefined(); expect(user).toBeDefined();
expect(user.firstName).toEqual('John'); 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. * 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 { By } from '@angular/platform-browser';
import { CardViewKeyValuePairsItemModel } from '../../models/card-view-keyvaluepairs.model'; import { CardViewKeyValuePairsItemModel } from '../../models/card-view-keyvaluepairs.model';
import { CardViewKeyValuePairsItemComponent } from './card-view-keyvaluepairsitem.component'; import { CardViewKeyValuePairsItemComponent } from './card-view-keyvaluepairsitem.component';
@ -122,57 +122,5 @@ describe('CardViewKeyValuePairsItemComponent', () => {
expect(cardViewUpdateService.update).toHaveBeenCalled(); expect(cardViewUpdateService.update).toHaveBeenCalled();
expect(component.property.value.length).toBe(0); 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 { AppConfigService, Status } from '../../app-config/app-config.service';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { ExtensionService } from '@alfresco/adf-extensions'; import { ExtensionService } from '@alfresco/adf-extensions';
@Injectable() @Injectable()
export class AppConfigServiceMock extends AppConfigService { export class AppConfigServiceMock extends AppConfigService {
config: any = { config: any = {
application: { application: {
name: 'Alfresco ADF Application' name: 'Alfresco ADF Application',
storagePrefix: 'ADF_APP'
}, },
ecmHost: 'http://{hostname}{:port}/ecm', ecmHost: 'http://{hostname}{:port}/ecm',
bpmHost: 'http://{hostname}{:port}/bpm', bpmHost: 'http://{hostname}{:port}/bpm',
@ -35,11 +37,12 @@ export class AppConfigServiceMock extends AppConfigService {
super(http, extensionService); super(http, extensionService);
} }
load(): Promise<any> { load(callback?: (...args: any[]) => any): Promise<any> {
return new Promise((resolve) => { return new Promise((resolve) => {
this.status = Status.LOADED; this.status = Status.LOADED;
this.onDataLoaded(this.config); callback?.();
resolve(this.config); 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 { CoreTestingModule } from '../../testing/core.testing.module';
import { AppConfigServiceMock } from '../mock/app-config.service.mock'; import { AppConfigServiceMock } from '../mock/app-config.service.mock';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { CoreModule } from '../../core.module';
describe('StorageService', () => { describe('StorageService', () => {
@ -34,8 +33,6 @@ describe('StorageService', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
TranslateModule.forRoot(),
CoreModule.forRoot(),
CoreTestingModule CoreTestingModule
] ]
}); });
@ -87,6 +84,7 @@ describe('StorageService', () => {
] ]
}); });
appConfig = TestBed.inject(AppConfigService); appConfig = TestBed.inject(AppConfigService);
appConfig.config = { appConfig.config = {
application: { application: {
storagePrefix: '' storagePrefix: ''

View File

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

View File

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

View File

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

View File

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

View File

@ -16,11 +16,12 @@
*/ */
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AuthenticationService } from '../../auth/services/authentication.service';
import { LoginDialogPanelComponent } from './login-dialog-panel.component'; import { LoginDialogPanelComponent } from './login-dialog-panel.component';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { CoreTestingModule } from '../../testing/core.testing.module'; import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core'; 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', () => { describe('LoginDialogPanelComponent', () => {
let component: LoginDialogPanelComponent; let component: LoginDialogPanelComponent;
@ -28,19 +29,23 @@ describe('LoginDialogPanelComponent', () => {
let element: HTMLElement; let element: HTMLElement;
let usernameInput: HTMLInputElement; let usernameInput: HTMLInputElement;
let passwordInput: HTMLInputElement; let passwordInput: HTMLInputElement;
let authService: AuthenticationService; let basicAlfrescoAuthService: BasicAlfrescoAuthService;
beforeEach(async () => { beforeEach(async () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
TranslateModule.forRoot(), TranslateModule.forRoot(),
CoreTestingModule CoreTestingModule
],
providers: [
{ provide: OidcAuthenticationService, useValue: {}}
] ]
}); });
fixture = TestBed.createComponent(LoginDialogPanelComponent); fixture = TestBed.createComponent(LoginDialogPanelComponent);
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
element = fixture.nativeElement; element = fixture.nativeElement;
component = fixture.componentInstance; component = fixture.componentInstance;
authService = TestBed.inject(AuthenticationService);
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
@ -76,7 +81,7 @@ describe('LoginDialogPanelComponent', () => {
expect(event.token.ticket).toBe('ticket'); expect(event.token.ticket).toBe('ticket');
done(); 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'); 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 { LoginErrorEvent } from '../models/login-error.event';
import { LoginSuccessEvent } from '../models/login-success.event'; import { LoginSuccessEvent } from '../models/login-success.event';
import { LoginComponent } from './login.component'; import { LoginComponent } from './login.component';
import { of, throwError } from 'rxjs'; import { EMPTY, of, throwError } from 'rxjs';
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { CoreTestingModule } from '../../testing/core.testing.module'; import { CoreTestingModule } from '../../testing/core.testing.module';
import { LogService } from '../../common/services/log.service'; 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', () => { describe('LoginComponent', () => {
let component: LoginComponent; let component: LoginComponent;
@ -38,7 +39,7 @@ describe('LoginComponent', () => {
let router: Router; let router: Router;
let userPreferences: UserPreferencesService; let userPreferences: UserPreferencesService;
let appConfigService: AppConfigService; let appConfigService: AppConfigService;
let alfrescoApiService: AlfrescoApiService; let basicAlfrescoAuthService: BasicAlfrescoAuthService;
let usernameInput; let usernameInput;
let passwordInput; let passwordInput;
@ -60,6 +61,16 @@ describe('LoginComponent', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
CoreTestingModule CoreTestingModule
],
providers: [
{
provide: OidcAuthenticationService, useValue: {
ssoImplicitLogin: () => { },
isPublicUrl: () => false,
hasValidIdToken: () => false,
isLoggedIn: () => false
}
}
] ]
}); });
fixture = TestBed.createComponent(LoginComponent); fixture = TestBed.createComponent(LoginComponent);
@ -69,11 +80,11 @@ describe('LoginComponent', () => {
component.showRememberMe = true; component.showRememberMe = true;
component.showLoginActions = true; component.showLoginActions = true;
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
authService = TestBed.inject(AuthenticationService); authService = TestBed.inject(AuthenticationService);
router = TestBed.inject(Router); router = TestBed.inject(Router);
userPreferences = TestBed.inject(UserPreferencesService); userPreferences = TestBed.inject(UserPreferencesService);
appConfigService = TestBed.inject(AppConfigService); appConfigService = TestBed.inject(AppConfigService);
alfrescoApiService = TestBed.inject(AlfrescoApiService);
const logService = TestBed.inject(LogService); const logService = TestBed.inject(LogService);
spyOn(logService, 'error'); spyOn(logService, 'error');
@ -111,7 +122,7 @@ describe('LoginComponent', () => {
}); });
it('should redirect to route on successful login', () => { it('should redirect to route on successful login', () => {
spyOn(authService, 'login').and.returnValue( spyOn(basicAlfrescoAuthService, 'login').and.returnValue(
of({ type: 'type', ticket: 'ticket' }) of({ type: 'type', ticket: 'ticket' })
); );
const redirect = '/home'; const redirect = '/home';
@ -161,10 +172,10 @@ describe('LoginComponent', () => {
appConfigService.config = {}; appConfigService.config = {};
appConfigService.config.providers = 'ECM'; 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'; const redirect = '/home';
component.successRoute = redirect; component.successRoute = redirect;
authService.setRedirect({ provider: 'ECM', url: 'some-route' }); basicAlfrescoAuthService.setRedirect({ provider: 'ECM', url: 'some-route' });
spyOn(router, 'navigateByUrl'); spyOn(router, 'navigateByUrl');
@ -174,8 +185,7 @@ describe('LoginComponent', () => {
it('should update user preferences upon login', async () => { it('should update user preferences upon login', async () => {
spyOn(userPreferences, 'setStoragePrefix').and.callThrough(); spyOn(userPreferences, 'setStoragePrefix').and.callThrough();
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' })); spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
spyOn(alfrescoApiService.getInstance(), 'login').and.returnValue(Promise.resolve());
component.success.subscribe(() => { component.success.subscribe(() => {
expect(userPreferences.setStoragePrefix).toHaveBeenCalledWith('fake-username'); 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', () => { 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'); loginWithCredentials('fake-wrong-username', 'fake-wrong-password');
expect(getLoginButtonText()).toEqual('LOGIN.BUTTON.LOGIN'); expect(getLoginButtonText()).toEqual('LOGIN.BUTTON.LOGIN');
}); });
it('should be changed to the "welcome key" after a successful login attempt', () => { it('should be changed to the "welcome key" after a successful login attempt', () => {
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' })); spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
loginWithCredentials('fake-username', 'fake-password'); loginWithCredentials('fake-username', 'fake-password');
expect(getLoginButtonText()).toEqual('LOGIN.BUTTON.WELCOME'); expect(getLoginButtonText()).toEqual('LOGIN.BUTTON.WELCOME');
@ -295,12 +305,12 @@ describe('LoginComponent', () => {
}); });
it('should be taken into consideration during login attempt', fakeAsync(() => { 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; component.rememberMe = false;
loginWithCredentials('fake-username', 'fake-password'); 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) => { 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(() => { component.error.subscribe(() => {
fixture.detectChanges(); fixture.detectChanges();
@ -484,7 +494,7 @@ describe('LoginComponent', () => {
}); });
it('should return error with a wrong password', (done) => { 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(() => { component.error.subscribe(() => {
fixture.detectChanges(); fixture.detectChanges();
@ -500,7 +510,7 @@ describe('LoginComponent', () => {
}); });
it('should return error with a wrong username and password', (done) => { 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(() => { component.error.subscribe(() => {
fixture.detectChanges(); fixture.detectChanges();
@ -516,7 +526,7 @@ describe('LoginComponent', () => {
}); });
it('should return CORS error when server CORS error occurs', (done) => { it('should return CORS error when server CORS error occurs', (done) => {
spyOn(authService, 'login').and.returnValue(throwError({ spyOn(basicAlfrescoAuthService, 'login').and.returnValue(throwError({
error: { error: {
crossDomain: true, crossDomain: true,
message: 'ERROR: the network is offline, Origin is not allowed by Access-Control-Allow-Origin' 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(() => { 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 })); .and.returnValue(throwError({ message: 'ERROR: Invalid CSRF-token', status: 403 }));
component.error.subscribe(() => { component.error.subscribe(() => {
@ -552,7 +562,7 @@ describe('LoginComponent', () => {
})); }));
it('should return ECM read-only error when error occurs', fakeAsync(() => { it('should return ECM read-only error when error occurs', fakeAsync(() => {
spyOn(authService, 'login') spyOn(basicAlfrescoAuthService, 'login')
.and.returnValue( .and.returnValue(
throwError( throwError(
{ {
@ -600,7 +610,7 @@ describe('LoginComponent', () => {
}); });
it('should return success event after the login have succeeded', (done) => { it('should return success event after the login have succeeded', (done) => {
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' })); spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
expect(component.isError).toBe(false); 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(() => { 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) => { component.success.subscribe((event) => {
fixture.detectChanges(); fixture.detectChanges();
@ -631,7 +641,7 @@ describe('LoginComponent', () => {
})); }));
it('should emit error event after the login has failed', fakeAsync(() => { 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) => { component.error.subscribe((error) => {
fixture.detectChanges(); fixture.detectChanges();
@ -668,7 +678,7 @@ describe('LoginComponent', () => {
}); });
it('should emit only the username and not the password as part of the executeSubmit', fakeAsync(() => { 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) => { component.executeSubmit.subscribe((res) => {
fixture.detectChanges(); fixture.detectChanges();
@ -688,7 +698,6 @@ describe('LoginComponent', () => {
beforeEach(() => { beforeEach(() => {
appConfigService.config.oauth2 = { implicitFlow: true, silentLogin: false }; appConfigService.config.oauth2 = { implicitFlow: true, silentLogin: false };
appConfigService.load(); appConfigService.load();
alfrescoApiService.reset();
}); });
it('should not show login username and password if SSO implicit flow is active', fakeAsync(() => { 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 { DomSanitizer, SafeStyle } from '@angular/platform-browser';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; 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 // eslint-disable-next-line no-shadow
enum LoginSteps { enum LoginSteps {
@ -52,7 +54,7 @@ interface LoginFormValues {
templateUrl: './login.component.html', templateUrl: './login.component.html',
styleUrls: ['./login.component.scss'], styleUrls: ['./login.component.scss'],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
host: { class: 'adf-login' } host: {class: 'adf-login'}
}) })
export class LoginComponent implements OnInit, OnDestroy { export class LoginComponent implements OnInit, OnDestroy {
isPasswordShow: boolean = false; isPasswordShow: boolean = false;
@ -129,6 +131,8 @@ export class LoginComponent implements OnInit, OnDestroy {
constructor( constructor(
private _fb: UntypedFormBuilder, private _fb: UntypedFormBuilder,
private authService: AuthenticationService, private authService: AuthenticationService,
private basicAlfrescoAuthService: BasicAlfrescoAuthService,
private oidcAuthenticationService: OidcAuthenticationService,
private translateService: TranslationService, private translateService: TranslationService,
private router: Router, private router: Router,
private appConfig: AppConfigService, private appConfig: AppConfigService,
@ -160,7 +164,7 @@ export class LoginComponent implements OnInit, OnDestroy {
const url = params['redirectUrl']; const url = params['redirectUrl'];
const provider = this.appConfig.get<string>(AppConfigValues.PROVIDERS); 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() { redirectToImplicitLogin() {
this.authService.ssoImplicitLogin(); this.oidcAuthenticationService.ssoImplicitLogin();
} }
/** /**
@ -193,12 +197,13 @@ export class LoginComponent implements OnInit, OnDestroy {
this.disableError(); this.disableError();
const args = new LoginSubmitEvent({ const args = new LoginSubmitEvent({
controls: { username: this.form.controls.username } controls: {username: this.form.controls.username}
}); });
this.executeSubmit.emit(args); this.executeSubmit.emit(args);
if (!args.defaultPrevented) { if (!args.defaultPrevented) {
this.actualLoginStep = LoginSteps.Checking; this.actualLoginStep = LoginSteps.Checking;
this.performLogin(values); this.performLogin(values);
} }
} }
@ -207,7 +212,7 @@ export class LoginComponent implements OnInit, OnDestroy {
if (this.authService.isLoggedIn()) { if (this.authService.isLoggedIn()) {
this.router.navigate([this.successRoute]); this.router.navigate([this.successRoute]);
} }
this.authService.ssoImplicitLogin(); this.oidcAuthenticationService.ssoImplicitLogin();
} }
/** /**
@ -237,30 +242,31 @@ export class LoginComponent implements OnInit, OnDestroy {
} }
} }
performLogin(values: LoginFormValues) { performLogin(values: { username: string; password: string }) {
this.authService.login(values.username, values.password, this.rememberMe).subscribe( this.authService.login(values.username, values.password, this.rememberMe)
(token) => { .subscribe(
const redirectUrl = this.authService.getRedirect(); async (token: any) => {
const redirectUrl = this.basicAlfrescoAuthService.getRedirect();
this.actualLoginStep = LoginSteps.Welcome; this.actualLoginStep = LoginSteps.Welcome;
this.userPreferences.setStoragePrefix(values.username); this.userPreferences.setStoragePrefix(values.username);
values.password = null; values.password = null;
this.success.emit(new LoginSuccessEvent(token, values.username, null)); this.success.emit(new LoginSuccessEvent(token, values.username, null));
if (redirectUrl) { if (redirectUrl) {
this.authService.setRedirect(null); this.basicAlfrescoAuthService.setRedirect(null);
this.router.navigateByUrl(redirectUrl); await this.router.navigateByUrl(redirectUrl);
} else if (this.successRoute) { } else if (this.successRoute) {
this.router.navigate([this.successRoute]); await this.router.navigate([this.successRoute]);
}
},
(err: any) => {
this.actualLoginStep = LoginSteps.Landing;
this.displayErrorMessage(err);
this.isError = true;
this.error.emit(new LoginErrorEvent(err));
} }
}, );
(err: any) => {
this.actualLoginStep = LoginSteps.Landing;
this.displayErrorMessage(err);
this.isError = true;
this.error.emit(new LoginErrorEvent(err));
}
);
} }
/** /**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,10 +17,12 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreTestingModule, DownloadPromptDialogComponent, DownloadPromptActions } from '@alfresco/adf-core';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { MatDialogRef } from '@angular/material/dialog'; import { MatDialogRef } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core'; 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 = { const mockDialog = {
close: jasmine.createSpy('close') close: jasmine.createSpy('close')

View File

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

View File

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

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