mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-12 17:04:57 +00:00
[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:
parent
057e0bcd7c
commit
08da9ae2c3
9
.github/workflows/pull-request.yml
vendored
9
.github/workflows/pull-request.yml
vendored
@ -392,15 +392,6 @@ jobs:
|
||||
check-cs-env: "true"
|
||||
check-ps-cloud-env: "true"
|
||||
deps: "testing"
|
||||
- description: "Process Cloud: People"
|
||||
test-id: "process-services-cloud"
|
||||
folder: "process-services-cloud/people"
|
||||
provider: "ALL"
|
||||
auth: "OAUTH"
|
||||
apa-proxy: true
|
||||
check-cs-env: "true"
|
||||
check-ps-cloud-env: "true"
|
||||
deps: "testing"
|
||||
- description: "Process Cloud: Process"
|
||||
test-id: "process-services-cloud"
|
||||
folder: "process-services-cloud/process"
|
||||
|
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -10,7 +10,7 @@
|
||||
"name": "e2e",
|
||||
"program": "${workspaceFolder}/node_modules/protractor/bin/protractor",
|
||||
"args": [
|
||||
"`${workspaceFolder}/.vscode/closest-config-finder.sh ${file} e2e/protractor.conf.js`",
|
||||
"./e2e/protractor.conf.js",
|
||||
"--specs=${file}"
|
||||
],
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
|
@ -141,7 +141,8 @@
|
||||
"webscript",
|
||||
"Whitespaces",
|
||||
"xdescribe",
|
||||
"xsrf"
|
||||
"xsrf",
|
||||
"BPMECM"
|
||||
],
|
||||
"dictionaries": [
|
||||
"html",
|
||||
|
@ -18,11 +18,11 @@
|
||||
import { Component, ViewEncapsulation, OnInit } from '@angular/core';
|
||||
import {
|
||||
AuthenticationService,
|
||||
AlfrescoApiService,
|
||||
PageTitleService
|
||||
} from '@alfresco/adf-core';
|
||||
import { Router } from '@angular/router';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { AdfHttpClient } from '@alfresco/adf-core/api';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@ -33,7 +33,7 @@ import { MatDialog } from '@angular/material/dialog';
|
||||
export class AppComponent implements OnInit {
|
||||
|
||||
constructor(private pageTitleService: PageTitleService,
|
||||
private alfrescoApiService: AlfrescoApiService,
|
||||
private adfHttpClient: AdfHttpClient,
|
||||
private authenticationService: AuthenticationService,
|
||||
private router: Router,
|
||||
private dialogRef: MatDialog) {
|
||||
@ -43,7 +43,7 @@ export class AppComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
this.pageTitleService.setTitle('title');
|
||||
|
||||
this.alfrescoApiService.getInstance().on('error', (error) => {
|
||||
this.adfHttpClient.on('error', (error) => {
|
||||
if (error.status === 401) {
|
||||
if (!this.authenticationService.isLoggedIn()) {
|
||||
this.dialogRef.closeAll();
|
||||
|
@ -74,7 +74,7 @@ import { UserInfoComponent } from './components/app-layout/user-info/user-info.c
|
||||
environment.e2e ? NoopAnimationsModule : BrowserAnimationsModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule.forRoot(appRoutes, { useHash: true, relativeLinkResolution: 'legacy' }),
|
||||
...(environment.oidc ? [AuthModule.forRoot({ useHash: true })] : []),
|
||||
AuthModule.forRoot({ useHash: true }),
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
MaterialModule,
|
||||
|
@ -17,7 +17,13 @@
|
||||
|
||||
import { EcmUserModel, PeopleContentService } from '@alfresco/adf-content-services';
|
||||
import { BpmUserModel, PeopleProcessService } from '@alfresco/adf-process-services';
|
||||
import { AuthenticationService, IdentityUserModel, IdentityUserService, UserInfoMode } from '@alfresco/adf-core';
|
||||
import {
|
||||
AuthenticationService,
|
||||
BasicAlfrescoAuthService,
|
||||
IdentityUserModel,
|
||||
IdentityUserService,
|
||||
UserInfoMode
|
||||
} from '@alfresco/adf-core';
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { MenuPositionX, MenuPositionY } from '@angular/material/menu';
|
||||
import { Observable, of } from 'rxjs';
|
||||
@ -46,6 +52,7 @@ export class UserInfoComponent implements OnInit {
|
||||
constructor(private peopleContentService: PeopleContentService,
|
||||
private peopleProcessService: PeopleProcessService,
|
||||
private identityUserService: IdentityUserService,
|
||||
private basicAlfrescoAuthService: BasicAlfrescoAuthService,
|
||||
private authService: AuthenticationService) {
|
||||
}
|
||||
|
||||
@ -77,7 +84,7 @@ export class UserInfoComponent implements OnInit {
|
||||
}
|
||||
|
||||
get isLoggedIn(): boolean {
|
||||
if (this.authService.isKerberosEnabled()) {
|
||||
if (this.basicAlfrescoAuthService.isKerberosEnabled()) {
|
||||
return true;
|
||||
}
|
||||
return this.authService.isLoggedIn();
|
||||
@ -96,15 +103,15 @@ export class UserInfoComponent implements OnInit {
|
||||
}
|
||||
|
||||
private isAllLoggedIn() {
|
||||
return (this.authService.isEcmLoggedIn() && this.authService.isBpmLoggedIn()) || (this.authService.isALLProvider() && this.authService.isKerberosEnabled());
|
||||
return (this.authService.isEcmLoggedIn() && this.authService.isBpmLoggedIn()) || (this.authService.isALLProvider() && this.basicAlfrescoAuthService.isKerberosEnabled());
|
||||
}
|
||||
|
||||
private isBpmLoggedIn() {
|
||||
return this.authService.isBpmLoggedIn() || (this.authService.isECMProvider() && this.authService.isKerberosEnabled());
|
||||
return this.authService.isBpmLoggedIn() || (this.authService.isECMProvider() && this.basicAlfrescoAuthService.isKerberosEnabled());
|
||||
}
|
||||
|
||||
private isEcmLoggedIn() {
|
||||
return this.authService.isEcmLoggedIn() || (this.authService.isECMProvider() && this.authService.isKerberosEnabled());
|
||||
return this.authService.isEcmLoggedIn() || (this.authService.isECMProvider() && this.basicAlfrescoAuthService.isKerberosEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@
|
||||
<mat-slide-toggle class="adf-full-width" name="implicitFlow" formControlName="implicitFlow">
|
||||
</mat-slide-toggle>
|
||||
|
||||
<ng-container *ngIf="supportsCodeFlow">
|
||||
<ng-container *ngIf="isOAUTH">
|
||||
<mat-label>Code Flow</mat-label>
|
||||
<mat-slide-toggle class="adf-full-width" name="codeFlow" formControlName="codeFlow">
|
||||
</mat-slide-toggle>
|
||||
|
@ -65,7 +65,7 @@ export class HostSettingsComponent implements OnInit {
|
||||
private storageService: StorageService,
|
||||
private alfrescoApiService: AlfrescoApiService,
|
||||
private appConfig: AppConfigService,
|
||||
private auth: AuthenticationService
|
||||
private authenticationService: AuthenticationService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@ -191,8 +191,8 @@ export class HostSettingsComponent implements OnInit {
|
||||
this.storageService.setItem(AppConfigValues.AUTHTYPE, values.authType);
|
||||
|
||||
this.alfrescoApiService.reset();
|
||||
this.auth.reset();
|
||||
this.alfrescoApiService.getInstance().invalidateSession();
|
||||
this.authenticationService.reset();
|
||||
this.authenticationService.logout();
|
||||
this.success.emit(true);
|
||||
}
|
||||
|
||||
@ -235,10 +235,6 @@ export class HostSettingsComponent implements OnInit {
|
||||
return this.form.get('authType').value === 'OAUTH';
|
||||
}
|
||||
|
||||
get supportsCodeFlow(): boolean {
|
||||
return this.auth.supportCodeFlow;
|
||||
}
|
||||
|
||||
get providersControl(): UntypedFormControl {
|
||||
return this.form.get('providersControl') as UntypedFormControl;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import { ContentServicesPage } from '../../core/pages/content-services.page';
|
||||
import { FileModel } from '../../models/ACS/file.model';
|
||||
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
|
||||
import { VersionManagePage } from '../pages/version-manager.page';
|
||||
import { MetadataViewPage } from '../pages/metadata-view.page';
|
||||
import { MetadataViewPage } from '../../core/pages/metadata-view.page';
|
||||
|
||||
describe('Content Services Viewer', () => {
|
||||
const acsUser = new UserModel();
|
94
e2e/content-services/components/viewer-vesion.e2e.ts
Normal file
94
e2e/content-services/components/viewer-vesion.e2e.ts
Normal 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);
|
||||
});
|
||||
});
|
@ -31,7 +31,7 @@ import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
|
||||
import { FolderModel } from '../../models/ACS/folder.model';
|
||||
import { browser } from 'protractor';
|
||||
import { FileModel } from '../../models/ACS/file.model';
|
||||
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
|
||||
import { UploadDialogPage } from '../pages/upload-dialog.page';
|
||||
|
||||
describe('Document List - Pagination', () => {
|
||||
const pagination = {
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { browser } from 'protractor';
|
||||
import { createApiService, LoginPage, UploadActions, UserModel, UsersActions } from '@alfresco/adf-testing';
|
||||
import { ContentServicesPage } from '../../core/pages/content-services.page';
|
||||
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
|
||||
import { UploadDialogPage } from '../pages/upload-dialog.page';
|
||||
import { UploadTogglesPage } from '../../core/pages/dialog/upload-toggles.page';
|
||||
import { FileModel } from '../../models/ACS/file.model';
|
||||
|
||||
|
@ -24,7 +24,7 @@ import { createApiService,
|
||||
UsersActions
|
||||
} from '@alfresco/adf-testing';
|
||||
import { ContentServicesPage } from '../../core/pages/content-services.page';
|
||||
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
|
||||
import { UploadDialogPage } from '../pages/upload-dialog.page';
|
||||
import { UploadTogglesPage } from '../../core/pages/dialog/upload-toggles.page';
|
||||
import { FileModel } from '../../models/ACS/file.model';
|
||||
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
|
||||
|
@ -17,11 +17,11 @@
|
||||
|
||||
import { createApiService, LoginPage, UploadActions, UserModel, UsersActions } from '@alfresco/adf-testing';
|
||||
import { ContentServicesPage } from '../../core/pages/content-services.page';
|
||||
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
|
||||
import { UploadDialogPage } from '../pages/upload-dialog.page';
|
||||
import { UploadTogglesPage } from '../../core/pages/dialog/upload-toggles.page';
|
||||
import { FileModel } from '../../models/ACS/file.model';
|
||||
import { browser } from 'protractor';
|
||||
import { VersionManagePage } from '../../core/pages/version-manager.page';
|
||||
import { VersionManagePage } from '../pages/version-manager.page';
|
||||
|
||||
describe('Upload component', () => {
|
||||
|
||||
|
@ -19,7 +19,7 @@ import { browser, by, element } from 'protractor';
|
||||
|
||||
import { createApiService, DropActions, LoginPage, StringUtil, UploadActions, UserModel, UsersActions } from '@alfresco/adf-testing';
|
||||
import { ContentServicesPage } from '../../core/pages/content-services.page';
|
||||
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
|
||||
import { UploadDialogPage } from '../pages/upload-dialog.page';
|
||||
import { UploadTogglesPage } from '../../core/pages/dialog/upload-toggles.page';
|
||||
import { FileModel } from '../../models/ACS/file.model';
|
||||
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { browser } from 'protractor';
|
||||
import { createApiService, LoginPage, SnackbarPage, StringUtil, UserModel, UsersActions } from '@alfresco/adf-testing';
|
||||
import { ContentServicesPage } from '../../core/pages/content-services.page';
|
||||
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
|
||||
import { UploadDialogPage } from '../pages/upload-dialog.page';
|
||||
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
|
||||
import { FileModel } from '../../models/ACS/file.model';
|
||||
import CONSTANTS = require('../../util/constants');
|
||||
|
@ -28,9 +28,9 @@ import { createApiService,
|
||||
import { browser, by, element } from 'protractor';
|
||||
import { FileModel } from '../../models/ACS/file.model';
|
||||
import { ContentServicesPage } from '../../core/pages/content-services.page';
|
||||
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
|
||||
import { UploadDialogPage } from '../pages/upload-dialog.page';
|
||||
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
|
||||
import { VersionManagePage } from '../../core/pages/version-manager.page';
|
||||
import { VersionManagePage } from '../pages/version-manager.page';
|
||||
|
||||
describe('Version component actions', () => {
|
||||
|
||||
|
@ -26,8 +26,8 @@ import {
|
||||
UsersActions
|
||||
} from '@alfresco/adf-testing';
|
||||
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
|
||||
import { VersionManagePage } from '../../core/pages/version-manager.page';
|
||||
import { UploadDialogPage } from '../../core/pages/dialog/upload-dialog.page';
|
||||
import { VersionManagePage } from '../pages/version-manager.page';
|
||||
import { UploadDialogPage } from '../pages/upload-dialog.page';
|
||||
import { ContentServicesPage } from '../../core/pages/content-services.page';
|
||||
import { FileModel } from '../../models/ACS/file.model';
|
||||
import CONSTANTS = require('../../util/constants');
|
||||
|
@ -24,7 +24,7 @@ import { createApiService,
|
||||
UsersActions, ViewerPage
|
||||
} from '@alfresco/adf-testing';
|
||||
import { ContentServicesPage } from '../../core/pages/content-services.page';
|
||||
import { VersionManagePage } from '../../core/pages/version-manager.page';
|
||||
import { VersionManagePage } from '../pages/version-manager.page';
|
||||
import { FileModel } from '../../models/ACS/file.model';
|
||||
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { browser } from 'protractor';
|
||||
import { createApiService, LoginPage, UploadActions, UserModel, UsersActions } from '@alfresco/adf-testing';
|
||||
import { ContentServicesPage } from '../../core/pages/content-services.page';
|
||||
import { VersionManagePage } from '../../core/pages/version-manager.page';
|
||||
import { VersionManagePage } from '../pages/version-manager.page';
|
||||
import { FileModel } from '../../models/ACS/file.model';
|
||||
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
@ -285,7 +285,7 @@ exports.config = {
|
||||
|
||||
// @ts-ignore
|
||||
if (browser.params.testConfig.appConfig.authType === 'OAUTH') {
|
||||
|
||||
Logger.info(`Configure demo shell OAUTH`);
|
||||
// @ts-ignore
|
||||
await LocalStorageUtil.setStorageItem('identityHost', browser.params.testConfig.appConfig.identityHost);
|
||||
// @ts-ignore
|
||||
|
@ -270,7 +270,7 @@ describe('Search Number Range Filter', () => {
|
||||
for (const currentResult of results) {
|
||||
const currentSize = await BrowserActions.getAttribute(currentResult, 'title');
|
||||
if (currentSize && currentSize.trim() !== '') {
|
||||
await expect(currentSize === '0').toBe(true);
|
||||
await expect((currentSize === '0' || currentSize === '1')).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -9,14 +9,14 @@ const HOST = process.env.URL_HOST_ADF;
|
||||
|
||||
const LOG = process.env.E2E_LOG_LEVEL;
|
||||
|
||||
const HOST_ECM = process.env.PROXY_HOST_ECM || HOST || 'ecm';
|
||||
const HOST_BPM = process.env.PROXY_HOST_BPM || HOST || 'bpm';
|
||||
const HOST_ECM = process.env.PROXY_HOST_ECM || process.env.PROXY_HOST_ADF || HOST || 'ecm';
|
||||
const HOST_BPM = process.env.PROXY_HOST_BPM || process.env.PROXY_HOST_ADF || HOST || 'bpm';
|
||||
const HOST_SSO = process.env.HOST_SSO || process.env.PROXY_HOST_ADF || HOST || 'oauth';
|
||||
const IDENTITY_HOST = process.env.IDENTITY_HOST || process.env.HOST_SSO + '/auth/admin/realms/alfresco';
|
||||
|
||||
const PROVIDER = process.env.PROVIDER ? process.env.PROVIDER : 'ALL';
|
||||
const AUTH_TYPE = process.env.AUTH_TYPE ? process.env.AUTH_TYPE : 'BASIC';
|
||||
|
||||
const HOST_SSO = process.env.HOST_SSO || process.env.PROXY_HOST_ADF || HOST || 'oauth';
|
||||
const IDENTITY_HOST = process.env.IDENTITY_HOST || process.env.HOST_SSO + '/auth/admin/realms/alfresco';
|
||||
const OAUTH_CLIENT_ID = process.env.OAUTH_CLIENDID || 'alfresco';
|
||||
|
||||
const IDENTITY_ADMIN_EMAIL = process.env.IDENTITY_ADMIN_EMAIL || "defaultadmin";
|
||||
|
@ -21,16 +21,11 @@ import { AppConfigService, AuthenticationService, StorageService, CoreTestingMod
|
||||
import { Node, PermissionsInfo } from '@alfresco/js-api';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('ContentService', () => {
|
||||
|
||||
let contentService: ContentService;
|
||||
let authService: AuthenticationService;
|
||||
let storage: StorageService;
|
||||
let node: any;
|
||||
|
||||
const nodeId = 'fake-node-id';
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -44,14 +39,6 @@ describe('ContentService', () => {
|
||||
storage = TestBed.inject(StorageService);
|
||||
storage.clear();
|
||||
|
||||
node = {
|
||||
entry: {
|
||||
id: nodeId
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.Ajax.install();
|
||||
|
||||
const appConfig: AppConfigService = TestBed.inject(AppConfigService);
|
||||
appConfig.config = {
|
||||
ecmHost: 'http://localhost:9876/ecm',
|
||||
@ -59,24 +46,6 @@ describe('ContentService', () => {
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('should return a valid content URL', (done) => {
|
||||
authService.login('fake-username', 'fake-password').subscribe(() => {
|
||||
expect(contentService.getContentUrl(node)).toContain('/ecm/alfresco/api/' +
|
||||
'-default-/public/alfresco/versions/1/nodes/fake-node-id/content?attachment=false&alf_ticket=fake-post-ticket');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 201,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({ entry: { id: 'fake-post-ticket', userId: 'admin' } })
|
||||
});
|
||||
});
|
||||
|
||||
describe('AllowableOperations', () => {
|
||||
|
||||
it('should hasAllowableOperations be false if allowableOperation is not present in the node', () => {
|
||||
|
@ -18,16 +18,27 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { from, Observable, throwError, Subject } from 'rxjs';
|
||||
import { catchError, map, switchMap, filter, take } from 'rxjs/operators';
|
||||
import { RepositoryInfo, SystemPropertiesRepresentation } from '@alfresco/js-api';
|
||||
import {
|
||||
RepositoryInfo,
|
||||
SystemPropertiesRepresentation,
|
||||
DiscoveryApi,
|
||||
AboutApi,
|
||||
SystemPropertiesApi
|
||||
} from '@alfresco/js-api';
|
||||
|
||||
import { BpmProductVersionModel, AuthenticationService } from '@alfresco/adf-core';
|
||||
import { ApiClientsService } from '@alfresco/adf-core/api';
|
||||
import { AlfrescoApiService, BpmProductVersionModel, AuthenticationService } from '@alfresco/adf-core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DiscoveryApiService {
|
||||
|
||||
private _discoveryApi: DiscoveryApi;
|
||||
get discoveryApi(): DiscoveryApi {
|
||||
this._discoveryApi = this._discoveryApi ?? new DiscoveryApi(this.alfrescoApiService.getInstance());
|
||||
return this._discoveryApi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets product information for Content Services.
|
||||
*/
|
||||
@ -35,15 +46,17 @@ export class DiscoveryApiService {
|
||||
|
||||
constructor(
|
||||
private authenticationService: AuthenticationService,
|
||||
private apiClientsService: ApiClientsService
|
||||
private alfrescoApiService: AlfrescoApiService
|
||||
) {
|
||||
this.authenticationService.onLogin
|
||||
.pipe(
|
||||
this.authenticationService.onLogin.subscribe(() => {
|
||||
this.alfrescoApiService.alfrescoApiInitialized.pipe(
|
||||
filter(() => this.authenticationService.isEcmLoggedIn()),
|
||||
take(1),
|
||||
switchMap(() => this.getEcmProductInfo())
|
||||
)
|
||||
.subscribe((info) => this.ecmProductInfo$.next(info));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -53,9 +66,8 @@ export class DiscoveryApiService {
|
||||
* @returns ProductVersionModel containing product details
|
||||
*/
|
||||
getEcmProductInfo(): Observable<RepositoryInfo> {
|
||||
const discoveryApi = this.apiClientsService.get('DiscoveryClient.discovery');
|
||||
|
||||
return from(discoveryApi.getRepositoryInformation())
|
||||
return from(this.discoveryApi.getRepositoryInformation())
|
||||
.pipe(
|
||||
map((res) => res.entry.repository),
|
||||
catchError((err) => throwError(err))
|
||||
@ -68,7 +80,7 @@ export class DiscoveryApiService {
|
||||
* @returns ProductVersionModel containing product details
|
||||
*/
|
||||
getBpmProductInfo(): Observable<BpmProductVersionModel> {
|
||||
const aboutApi = this.apiClientsService.get('ActivitiClient.about');
|
||||
const aboutApi = new AboutApi(this.alfrescoApiService.getInstance());
|
||||
|
||||
return from(aboutApi.getAppVersion())
|
||||
.pipe(
|
||||
@ -78,7 +90,7 @@ export class DiscoveryApiService {
|
||||
}
|
||||
|
||||
getBPMSystemProperties(): Observable<SystemPropertiesRepresentation> {
|
||||
const systemPropertiesApi = this.apiClientsService.get('ActivitiClient.system-properties');
|
||||
const systemPropertiesApi = new SystemPropertiesApi(this.alfrescoApiService.getInstance());
|
||||
|
||||
return from(systemPropertiesApi.getProperties())
|
||||
.pipe(
|
||||
|
@ -25,7 +25,6 @@ import {
|
||||
import {
|
||||
AlfrescoApiService,
|
||||
AlfrescoApiServiceMock,
|
||||
AuthenticationService,
|
||||
CoreTestingModule
|
||||
} from '@alfresco/adf-core';
|
||||
import { PeopleContentQueryRequestModel, PeopleContentService } from './people-content.service';
|
||||
@ -34,7 +33,6 @@ import { TestBed } from '@angular/core/testing';
|
||||
|
||||
describe('PeopleContentService', () => {
|
||||
let peopleContentService: PeopleContentService;
|
||||
let authenticationService: AuthenticationService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -47,7 +45,6 @@ describe('PeopleContentService', () => {
|
||||
]
|
||||
});
|
||||
|
||||
authenticationService = TestBed.inject(AuthenticationService);
|
||||
peopleContentService = TestBed.inject(PeopleContentService);
|
||||
});
|
||||
|
||||
@ -130,17 +127,6 @@ describe('PeopleContentService', () => {
|
||||
expect(getCurrentPersonSpy.calls.count()).toEqual(1);
|
||||
});
|
||||
|
||||
it('should reset the admin cache upon logout', async () => {
|
||||
spyOn(peopleContentService.peopleApi, 'getPerson').and.returnValue(Promise.resolve({ entry: fakeEcmAdminUser } as any));
|
||||
|
||||
const user = await peopleContentService.getCurrentUserInfo().toPromise();
|
||||
expect(user.id).toEqual('fake-id');
|
||||
expect(peopleContentService.isCurrentUserAdmin()).toBe(true);
|
||||
|
||||
authenticationService.onLogout.next(true);
|
||||
expect(peopleContentService.isCurrentUserAdmin()).toBe(false);
|
||||
});
|
||||
|
||||
it('should not change current user on every getPerson call', async () => {
|
||||
const getCurrentPersonSpy = spyOn(peopleContentService.peopleApi, 'getPerson').and.returnValue(Promise.resolve({entry: fakeEcmAdminUser} as any));
|
||||
await peopleContentService.getCurrentUserInfo().toPromise();
|
||||
|
@ -18,8 +18,7 @@
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { LibraryFavoriteDirective } from './library-favorite.directive';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AlfrescoApiServiceMock, CoreModule, AlfrescoApiService } from '@alfresco/adf-core';
|
||||
import { CoreTestingModule } from '@alfresco/adf-core';
|
||||
import { LibraryEntity } from '../interfaces/library-entity.interface';
|
||||
|
||||
@Component({
|
||||
@ -40,10 +39,7 @@ describe('LibraryFavoriteDirective', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), CoreModule.forRoot()],
|
||||
providers: [
|
||||
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }
|
||||
],
|
||||
imports: [CoreTestingModule],
|
||||
declarations: [TestComponent, LibraryFavoriteDirective]
|
||||
});
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
|
@ -18,7 +18,6 @@
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange, QueryList, Component, ViewChild, SimpleChanges } from '@angular/core';
|
||||
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||
import {
|
||||
AlfrescoApiService,
|
||||
DataColumnListComponent,
|
||||
DataColumnComponent,
|
||||
DataColumn,
|
||||
@ -27,7 +26,8 @@ import {
|
||||
ObjectDataTableAdapter,
|
||||
ShowHeaderMode,
|
||||
ThumbnailService,
|
||||
AppConfigService
|
||||
AppConfigService,
|
||||
AuthenticationService
|
||||
} from '@alfresco/adf-core';
|
||||
import { ContentService } from '../../common/services/content.service';
|
||||
import { Subject, of, throwError } from 'rxjs';
|
||||
@ -70,7 +70,6 @@ const mockDialog = {
|
||||
describe('DocumentList', () => {
|
||||
let documentList: DocumentListComponent;
|
||||
let documentListService: DocumentListService;
|
||||
let apiService: AlfrescoApiService;
|
||||
let customResourcesService: CustomResourcesService;
|
||||
let thumbnailService: ThumbnailService;
|
||||
let contentService: ContentService;
|
||||
@ -82,6 +81,7 @@ describe('DocumentList', () => {
|
||||
let spyFavorite: any;
|
||||
let spyFolder: any;
|
||||
let spyFolderNode: any;
|
||||
let authenticationService: AuthenticationService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -99,11 +99,11 @@ describe('DocumentList', () => {
|
||||
documentList = fixture.componentInstance;
|
||||
|
||||
documentListService = TestBed.inject(DocumentListService);
|
||||
apiService = TestBed.inject(AlfrescoApiService);
|
||||
customResourcesService = TestBed.inject(CustomResourcesService);
|
||||
thumbnailService = TestBed.inject(ThumbnailService);
|
||||
contentService = TestBed.inject(ContentService);
|
||||
appConfigService = TestBed.inject(AppConfigService);
|
||||
authenticationService = TestBed.inject(AuthenticationService);
|
||||
|
||||
spyFolder = spyOn(documentListService, 'getFolder').and.returnValue(of({ list: {} }));
|
||||
spyFolderNode = spyOn(documentListService, 'getFolderNode').and.returnValue(of(new NodeEntry({ entry: new Node() })));
|
||||
@ -611,7 +611,7 @@ describe('DocumentList', () => {
|
||||
title: 'FileAction'
|
||||
});
|
||||
|
||||
spyOn(apiService.getInstance(), 'getEcmUsername').and.returnValue('lockOwner');
|
||||
spyOn(authenticationService, 'getEcmUsername').and.returnValue('lockOwner');
|
||||
|
||||
documentList.actions = [documentMenu];
|
||||
|
||||
@ -642,7 +642,7 @@ describe('DocumentList', () => {
|
||||
title: 'FileAction'
|
||||
});
|
||||
|
||||
spyOn(apiService.getInstance(), 'getEcmUsername').and.returnValue('jerryTheKillerCow');
|
||||
spyOn(authenticationService, 'getEcmUsername').and.returnValue('jerryTheKillerCow');
|
||||
|
||||
documentList.actions = [documentMenu];
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { LockService } from './lock.service';
|
||||
import { CoreTestingModule, AlfrescoApiService } from '@alfresco/adf-core';
|
||||
import { CoreTestingModule, AuthenticationService } from '@alfresco/adf-core';
|
||||
import { Node } from '@alfresco/js-api';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { addDays, subDays } from 'date-fns';
|
||||
@ -25,7 +25,7 @@ import { addDays, subDays } from 'date-fns';
|
||||
describe('PeopleProcessService', () => {
|
||||
|
||||
let service: LockService;
|
||||
let apiService: AlfrescoApiService;
|
||||
let authenticationService: AuthenticationService;
|
||||
|
||||
const fakeNodeUnlocked: Node = { name: 'unlocked', isLocked: false, isFile: true } as Node;
|
||||
const fakeFolderNode: Node = { name: 'unlocked', isLocked: false, isFile: false, isFolder: true } as Node;
|
||||
@ -39,7 +39,7 @@ describe('PeopleProcessService', () => {
|
||||
]
|
||||
});
|
||||
service = TestBed.inject(LockService);
|
||||
apiService = TestBed.inject(AlfrescoApiService);
|
||||
authenticationService = TestBed.inject(AuthenticationService);
|
||||
});
|
||||
|
||||
it('should return false when no lock is configured', () => {
|
||||
@ -145,22 +145,22 @@ describe('PeopleProcessService', () => {
|
||||
} as Node;
|
||||
|
||||
it('should return false when the user is the lock owner', () => {
|
||||
spyOn(apiService.getInstance(), 'getEcmUsername').and.returnValue('lock-owner-user');
|
||||
spyOn(authenticationService, 'getEcmUsername').and.returnValue('lock-owner-user');
|
||||
expect(service.isLocked(nodeOwnerAllowedLock)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return true when the user is not the lock owner', () => {
|
||||
spyOn(apiService.getInstance(), 'getEcmUsername').and.returnValue('banana-user');
|
||||
spyOn(authenticationService, 'getEcmUsername').and.returnValue('banana-user');
|
||||
expect(service.isLocked(nodeOwnerAllowedLock)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false when the user is not the lock owner but the lock is expired', () => {
|
||||
spyOn(apiService.getInstance(), 'getEcmUsername').and.returnValue('banana-user');
|
||||
spyOn(authenticationService, 'getEcmUsername').and.returnValue('banana-user');
|
||||
expect(service.isLocked(nodeOwnerAllowedLockWithExpiredDate)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return true when is not the lock owner and the expiration date is valid', () => {
|
||||
spyOn(apiService.getInstance(), 'getEcmUsername').and.returnValue('banana-user');
|
||||
spyOn(authenticationService, 'getEcmUsername').and.returnValue('banana-user');
|
||||
expect(service.isLocked(nodeOwnerAllowedLockWithActiveExpiration)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
@ -27,7 +27,8 @@ import {
|
||||
AlfrescoApiServiceMock,
|
||||
AppConfigServiceMock,
|
||||
TranslationMock,
|
||||
CookieServiceMock
|
||||
CookieServiceMock,
|
||||
AuthModule
|
||||
} from '@alfresco/adf-core';
|
||||
import { ContentModule } from '../content.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
@ -37,6 +38,7 @@ import { MatIconTestingModule } from '@angular/material/icon/testing';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
AuthModule.forRoot({ useHash: true }),
|
||||
NoopAnimationsModule,
|
||||
RouterTestingModule,
|
||||
TranslateModule,
|
||||
|
@ -15,9 +15,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './lib/api-client.factory';
|
||||
export * from './lib/api-clients.service';
|
||||
export * from './lib/clients';
|
||||
export * from './lib/types';
|
||||
export * from './lib/adf-http-client.service';
|
||||
export * from './lib/interfaces';
|
||||
|
@ -321,4 +321,86 @@ describe('AdfHttpClient', () => {
|
||||
req.flush(null, { status: 200, statusText: 'Ok' });
|
||||
});
|
||||
|
||||
it('should set Content-type to multipart/form-data if contentTypes array contains only multipart/form-data element', () => {
|
||||
const options: RequestOptions = {
|
||||
path: '',
|
||||
httpMethod: 'POST',
|
||||
contentTypes: ['multipart/form-data'],
|
||||
queryParams: {
|
||||
lastModifiedFrom: new Date('2022-08-17T00:00:00.000Z')
|
||||
}
|
||||
};
|
||||
|
||||
angularHttpClient.request('http://example.com', options, securityOptions, emitters).catch(error =>
|
||||
fail(error)
|
||||
);
|
||||
|
||||
const req = controller.expectOne('http://example.com?lastModifiedFrom=2022-08-17T00%3A00%3A00.000Z');
|
||||
|
||||
expect(req.request.headers.get('Content-Type')).toEqual('multipart/form-data');
|
||||
|
||||
req.flush(null, { status: 200, statusText: 'Ok' });
|
||||
});
|
||||
|
||||
it('should set Content-type header to application/json if contentTypes array contains application/json', () => {
|
||||
const options: RequestOptions = {
|
||||
path: '',
|
||||
httpMethod: 'POST',
|
||||
contentTypes: ['multipart/form-data', 'application/json'],
|
||||
queryParams: {
|
||||
lastModifiedFrom: new Date('2022-08-17T00:00:00.000Z')
|
||||
}
|
||||
};
|
||||
|
||||
angularHttpClient.request('http://example.com', options, securityOptions, emitters).catch(error =>
|
||||
fail(error)
|
||||
);
|
||||
|
||||
const req = controller.expectOne('http://example.com?lastModifiedFrom=2022-08-17T00%3A00%3A00.000Z');
|
||||
|
||||
expect(req.request.headers.get('Content-Type')).toEqual('application/json');
|
||||
|
||||
req.flush(null, { status: 200, statusText: 'Ok' });
|
||||
});
|
||||
|
||||
it('should set Content-type to application/json if contentTypes is not passed to the request options', () => {
|
||||
const options: RequestOptions = {
|
||||
path: '',
|
||||
httpMethod: 'POST',
|
||||
queryParams: {
|
||||
lastModifiedFrom: new Date('2022-08-17T00:00:00.000Z')
|
||||
}
|
||||
};
|
||||
|
||||
angularHttpClient.request('http://example.com', options, securityOptions, emitters).catch(error =>
|
||||
fail(error)
|
||||
);
|
||||
|
||||
const req = controller.expectOne('http://example.com?lastModifiedFrom=2022-08-17T00%3A00%3A00.000Z');
|
||||
|
||||
expect(req.request.headers.get('Content-Type')).toEqual('application/json');
|
||||
|
||||
req.flush(null, { status: 200, statusText: 'Ok' });
|
||||
});
|
||||
|
||||
it('should set Accept header to application/json if accepts is not passed to the request options', () => {
|
||||
const options: RequestOptions = {
|
||||
path: '',
|
||||
httpMethod: 'POST',
|
||||
queryParams: {
|
||||
lastModifiedFrom: new Date('2022-08-17T00:00:00.000Z')
|
||||
}
|
||||
};
|
||||
|
||||
angularHttpClient.request('http://example.com', options, securityOptions, emitters).catch(error =>
|
||||
fail(error)
|
||||
);
|
||||
|
||||
const req = controller.expectOne('http://example.com?lastModifiedFrom=2022-08-17T00%3A00%3A00.000Z');
|
||||
|
||||
expect(req.request.headers.get('Accept')).toEqual('application/json');
|
||||
|
||||
req.flush(null, { status: 200, statusText: 'Ok' });
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -57,17 +57,10 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
|
||||
on: ee.EmitterMethod;
|
||||
off: ee.EmitterMethod;
|
||||
once: ee.EmitterMethod;
|
||||
_disableCsrf: boolean;
|
||||
|
||||
emit: (type: string, ...args: any[]) => void;
|
||||
|
||||
private _disableCsrf = false;
|
||||
|
||||
private defaultSecurityOptions = {
|
||||
withCredentials: true,
|
||||
isBpmRequest: false,
|
||||
authentications: {},
|
||||
defaultHeaders: {}
|
||||
};
|
||||
|
||||
get disableCsrf(): boolean {
|
||||
return this._disableCsrf;
|
||||
}
|
||||
@ -76,8 +69,14 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
|
||||
this._disableCsrf = disableCsrf;
|
||||
}
|
||||
|
||||
constructor(private httpClient: HttpClient
|
||||
) {
|
||||
private defaultSecurityOptions = {
|
||||
withCredentials: true,
|
||||
isBpmRequest: false,
|
||||
authentications: {},
|
||||
defaultHeaders: {}
|
||||
};
|
||||
|
||||
constructor(private httpClient: HttpClient) {
|
||||
ee(this);
|
||||
}
|
||||
|
||||
@ -217,7 +216,7 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
|
||||
}
|
||||
|
||||
eventEmitter.emit('error', err);
|
||||
apiClientEmitter.emit('error', err);
|
||||
apiClientEmitter.emit('error', { ...err, response: { req: err } });
|
||||
|
||||
if (err.status === 401) {
|
||||
eventEmitter.emit('unauthorized');
|
||||
@ -232,10 +231,10 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
|
||||
// for backwards compatibility to handle cases in code where we try read response.error.response.body;
|
||||
|
||||
const error = {
|
||||
response: {...err, body: err.error}
|
||||
...err, body: err.error
|
||||
};
|
||||
|
||||
const alfrescoApiError = new AlfrescoApiResponseError(msg, err.status, error.response);
|
||||
const alfrescoApiError = new AlfrescoApiResponseError(msg, err.status, error);
|
||||
return throwError(alfrescoApiError);
|
||||
}),
|
||||
takeUntil(abort$)
|
||||
@ -252,7 +251,7 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
|
||||
}
|
||||
|
||||
private static getBody(options: RequestOptions): any {
|
||||
const contentType = options.contentType;
|
||||
const contentType = options.contentType ? options.contentType : AdfHttpClient.jsonPreferredMime(options.contentTypes);
|
||||
const isFormData = contentType === 'multipart/form-data';
|
||||
const isFormUrlEncoded = contentType === 'application/x-www-form-urlencoded';
|
||||
const body = options.bodyParam;
|
||||
@ -269,20 +268,58 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
|
||||
}
|
||||
|
||||
private getHeaders(options: RequestOptions): HttpHeaders {
|
||||
const contentType = options.contentType || AdfHttpClient.jsonPreferredMime(options.contentTypes);
|
||||
const accept = options.accept || AdfHttpClient.jsonPreferredMime(options.accepts);
|
||||
|
||||
const optionsHeaders = {
|
||||
...options.headerParams,
|
||||
...(options.accept && {Accept: options.accept}),
|
||||
...((options.contentType) && {'Content-Type': options.contentType})
|
||||
...(accept && {Accept: accept}),
|
||||
...((contentType) && {'Content-Type': contentType})
|
||||
};
|
||||
|
||||
if (!this.disableCsrf) {
|
||||
this.setCsrfToken(optionsHeaders);
|
||||
|
||||
}
|
||||
|
||||
return new HttpHeaders(optionsHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses a content type from the given array, with JSON preferred; i.e. return JSON if included, otherwise return the first.
|
||||
*
|
||||
* @param contentTypes a contentType array
|
||||
* @returns The chosen content type, preferring JSON.
|
||||
*/
|
||||
private static jsonPreferredMime(contentTypes: readonly string[]): string {
|
||||
if (!contentTypes?.length) {
|
||||
return 'application/json';
|
||||
}
|
||||
|
||||
for (let i = 0; i < contentTypes.length; i++) {
|
||||
if (AdfHttpClient.isJsonMime(contentTypes[i])) {
|
||||
return contentTypes[i];
|
||||
}
|
||||
}
|
||||
return contentTypes[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given content type represents JSON.<br>
|
||||
* JSON content type examples:<br>
|
||||
* <ul>
|
||||
* <li>application/json</li>
|
||||
* <li>application/json; charset=UTF8</li>
|
||||
* <li>APPLICATION/JSON</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param contentType The MIME content type to check.
|
||||
* @returns <code>true</code> if <code>contentType</code> represents JSON, otherwise <code>false</code>.
|
||||
*/
|
||||
private static isJsonMime(contentType: string): boolean {
|
||||
return Boolean(contentType?.match(/^application\/json(;.*)?$/i));
|
||||
}
|
||||
|
||||
|
||||
private setCsrfToken(optionsHeaders: any) {
|
||||
const token = this.createCSRFToken();
|
||||
optionsHeaders['X-CSRF-TOKEN'] = token;
|
||||
|
@ -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');
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 { }
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -15,22 +15,41 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export interface SecurityOptions {
|
||||
readonly withCredentials?: boolean;
|
||||
readonly authentications?: Authentication;
|
||||
readonly defaultHeaders?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface Oauth2 {
|
||||
refreshToken?: string;
|
||||
accessToken?: string;
|
||||
}
|
||||
|
||||
export interface BasicAuth {
|
||||
username?: string;
|
||||
password?: string;
|
||||
ticket?: string;
|
||||
}
|
||||
|
||||
export interface Authentication {
|
||||
basicAuth?: BasicAuth;
|
||||
oauth2?: Oauth2;
|
||||
cookie?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface RequestOptions {
|
||||
httpMethod?: string;
|
||||
pathParams?: any;
|
||||
queryParams?: any;
|
||||
headerParams?: any;
|
||||
formParams?: any;
|
||||
bodyParam?: any;
|
||||
returnType?: any;
|
||||
responseType?: string;
|
||||
accepts?: string[];
|
||||
contentTypes?: string[];
|
||||
readonly accept?: string;
|
||||
readonly contentType?: string;
|
||||
}
|
||||
|
||||
export interface SecurityOptions {
|
||||
readonly isBpmRequest: boolean;
|
||||
readonly enableCsrf?: boolean;
|
||||
readonly withCredentials?: boolean;
|
||||
readonly authentications: any;
|
||||
readonly defaultHeaders: Record<string, string>;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import { Authentication } from '../authentication';
|
||||
import { AuthenticationInterceptor, SHOULD_ADD_AUTH_TOKEN } from './authentication.interceptor';
|
||||
|
||||
class MockAuthentication extends Authentication {
|
||||
addTokenToHeader(httpHeaders: HttpHeaders): Observable<HttpHeaders> {
|
||||
addTokenToHeader(_: string, httpHeaders: HttpHeaders): Observable<HttpHeaders> {
|
||||
return of(httpHeaders);
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ export class AuthenticationInterceptor implements HttpInterceptor {
|
||||
Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
|
||||
|
||||
if (req.context.get(SHOULD_ADD_AUTH_TOKEN)) {
|
||||
return this.authService.addTokenToHeader(req.headers).pipe(
|
||||
return this.authService.addTokenToHeader(req.url, req.headers).pipe(
|
||||
mergeMap((headersWithBearer) => {
|
||||
const headerWithContentType = this.appendJsonContentType(headersWithBearer);
|
||||
const kcReq = req.clone({ headers: headerWithContentType});
|
||||
|
@ -19,5 +19,5 @@ import { HttpHeaders } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export abstract class Authentication {
|
||||
public abstract addTokenToHeader(headers: HttpHeaders): Observable<HttpHeaders>;
|
||||
public abstract addTokenToHeader(requestUrl: string, headers: HttpHeaders): Observable<HttpHeaders>;
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ import { AlfrescoApiConfig } from '@alfresco/js-api';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AppConfigService, AppConfigValues } from '../app-config/app-config.service';
|
||||
import { AlfrescoApiService } from '../services/alfresco-api.service';
|
||||
import { StorageService } from '../common/services/storage.service';
|
||||
import { AuthenticationService, BasicAlfrescoAuthService } from '../auth';
|
||||
|
||||
/**
|
||||
* Create a factory to resolve an api service instance
|
||||
@ -34,10 +36,22 @@ export function createAlfrescoApiInstance(angularAlfrescoApiService: AlfrescoApi
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AlfrescoApiLoaderService {
|
||||
constructor(private readonly appConfig: AppConfigService, private readonly apiService: AlfrescoApiService) {}
|
||||
constructor(private readonly appConfig: AppConfigService,
|
||||
private readonly apiService: AlfrescoApiService,
|
||||
private readonly basicAlfrescoAuthService: BasicAlfrescoAuthService,
|
||||
private readonly authService: AuthenticationService,
|
||||
private storageService: StorageService) {
|
||||
}
|
||||
|
||||
async init(): Promise<any> {
|
||||
await this.appConfig.load();
|
||||
|
||||
this.authService.onLogin.subscribe(async () => {
|
||||
if (this.authService.isOauth() && (this.authService.isALLProvider() || this.authService.isECMProvider())) {
|
||||
await this.basicAlfrescoAuthService.requireAlfTicket();
|
||||
}
|
||||
});
|
||||
|
||||
return this.initAngularAlfrescoApi();
|
||||
}
|
||||
|
||||
@ -59,6 +73,8 @@ export class AlfrescoApiLoaderService {
|
||||
disableCsrf: this.appConfig.get<boolean>(AppConfigValues.DISABLECSRF),
|
||||
withCredentials: this.appConfig.get<boolean>(AppConfigValues.AUTH_WITH_CREDENTIALS, false),
|
||||
domainPrefix: this.appConfig.get<string>(AppConfigValues.STORAGE_PREFIX),
|
||||
ticketEcm: this.storageService.getItem(AppConfigValues.CONTENT_TICKET_STORAGE_LABEL),
|
||||
ticketBpm: this.storageService.getItem(AppConfigValues.PROCESS_TICKET_STORAGE_LABEL),
|
||||
oauth2: oauth
|
||||
});
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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 { }
|
@ -28,8 +28,14 @@ import { AdfHttpClient } from '@alfresco/adf-core/api';
|
||||
* @returns factory function
|
||||
*/
|
||||
export function loadAppConfig(appConfigService: AppConfigService, storageService: StorageService, adfHttpClient: AdfHttpClient) {
|
||||
return () => appConfigService.load().then(() => {
|
||||
|
||||
const init = () => {
|
||||
adfHttpClient.disableCsrf = appConfigService.get<boolean>(AppConfigValues.DISABLECSRF, true);
|
||||
storageService.prefix = appConfigService.get<string>(AppConfigValues.STORAGE_PREFIX, '');
|
||||
|
||||
appConfigService.select(AppConfigValues.STORAGE_PREFIX).subscribe((property) => {
|
||||
storageService.prefix = property;
|
||||
});
|
||||
}
|
||||
};
|
||||
return () => appConfigService.load(init);
|
||||
};
|
||||
|
@ -188,4 +188,14 @@ describe('AppConfigService', () => {
|
||||
|
||||
expect(appConfigService.get('files.excluded')[0]).toBe('excluded');
|
||||
});
|
||||
|
||||
it('should execute callback function if is passed to the load method', async () => {
|
||||
const fakeCallBack = jasmine.createSpy('fakeCallBack');
|
||||
fakeCallBack.and.returnValue(()=>{});
|
||||
|
||||
await appConfigService.load(fakeCallBack);
|
||||
|
||||
expect(fakeCallBack).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -18,13 +18,14 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ObjectUtils } from '../common/utils/object-utils';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { Observable, ReplaySubject } from 'rxjs';
|
||||
import { map, distinctUntilChanged, take } from 'rxjs/operators';
|
||||
import { ExtensionConfig, ExtensionService, mergeObjects } from '@alfresco/adf-extensions';
|
||||
import { OpenidConfiguration } from '../auth/interfaces/openid-configuration.interface';
|
||||
import { OauthConfigModel } from '../auth/models/oauth-config.model';
|
||||
|
||||
/* spellchecker: disable */
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
export enum AppConfigValues {
|
||||
APP_CONFIG_LANGUAGES_KEY = 'languages',
|
||||
@ -44,7 +45,9 @@ export enum AppConfigValues {
|
||||
AUTH_WITH_CREDENTIALS = 'auth.withCredentials',
|
||||
APPLICATION = 'application',
|
||||
STORAGE_PREFIX = 'application.storagePrefix',
|
||||
NOTIFY_DURATION = 'notificationDefaultDuration'
|
||||
NOTIFY_DURATION = 'notificationDefaultDuration',
|
||||
CONTENT_TICKET_STORAGE_LABEL = 'ticket-ECM',
|
||||
PROCESS_TICKET_STORAGE_LABEL = 'ticket-BPM'
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
@ -71,11 +74,15 @@ export class AppConfigService {
|
||||
};
|
||||
|
||||
status: Status = Status.INIT;
|
||||
protected onLoadSubject: Subject<any>;
|
||||
protected onLoadSubject: ReplaySubject<any>;
|
||||
onLoad: Observable<any>;
|
||||
|
||||
get isLoaded() {
|
||||
return this.status === Status.LOADED;
|
||||
}
|
||||
|
||||
constructor(protected http: HttpClient, protected extensionService: ExtensionService) {
|
||||
this.onLoadSubject = new Subject();
|
||||
this.onLoadSubject = new ReplaySubject();
|
||||
this.onLoad = this.onLoadSubject.asObservable();
|
||||
|
||||
extensionService.setup$.subscribe((config) => {
|
||||
@ -92,7 +99,7 @@ export class AppConfigService {
|
||||
select(property: string): Observable<any> {
|
||||
return this.onLoadSubject
|
||||
.pipe(
|
||||
map((config) => config[property]),
|
||||
map((config) => ObjectUtils.getValue(config, property)),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
}
|
||||
@ -160,8 +167,7 @@ export class AppConfigService {
|
||||
this.onLoadSubject.next(this.config);
|
||||
}
|
||||
|
||||
protected onDataLoaded(data: any) {
|
||||
this.config = Object.assign({}, this.config, data || {});
|
||||
protected onDataLoaded() {
|
||||
this.onLoadSubject.next(this.config);
|
||||
|
||||
this.extensionService.setup$
|
||||
@ -182,9 +188,10 @@ export class AppConfigService {
|
||||
/**
|
||||
* Loads the config file.
|
||||
*
|
||||
* @param callback an optional callback to execute when configuration is loaded
|
||||
* @returns Notification when loading is complete
|
||||
*/
|
||||
load(): Promise<any> {
|
||||
load(callback?: (...args: any[]) => any): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
const configUrl = `app.config.json?v=${Date.now()}`;
|
||||
|
||||
@ -193,8 +200,10 @@ export class AppConfigService {
|
||||
this.http.get(configUrl).subscribe(
|
||||
(data: any) => {
|
||||
this.status = Status.LOADED;
|
||||
this.config = Object.assign({}, this.config, data || {});
|
||||
callback?.();
|
||||
resolve(data);
|
||||
this.onDataLoaded(data);
|
||||
this.onDataLoaded();
|
||||
},
|
||||
() => {
|
||||
// eslint-disable-next-line no-console
|
||||
@ -227,6 +236,8 @@ export class AppConfigService {
|
||||
resolve(res);
|
||||
},
|
||||
error: (err: any) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('hostIdp not correctly configured or unreachable');
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
@ -262,4 +273,5 @@ export class AppConfigService {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,9 +17,10 @@
|
||||
|
||||
import { HttpClient, HttpHandler, HttpRequest } from '@angular/common/http';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { EMPTY, Observable, of } from 'rxjs';
|
||||
import { AuthBearerInterceptor } from './auth-bearer.interceptor';
|
||||
import { AuthenticationService } from '../services/authentication.service';
|
||||
import { RedirectAuthService } from '../oidc/redirect-auth.service';
|
||||
|
||||
const mockNext: HttpHandler = {
|
||||
handle: () => new Observable(subscriber => {
|
||||
@ -40,7 +41,8 @@ describe('AuthBearerInterceptor', () => {
|
||||
HttpClient,
|
||||
HttpHandler,
|
||||
AuthBearerInterceptor,
|
||||
AuthenticationService
|
||||
AuthenticationService,
|
||||
{ provide: RedirectAuthService, useValue: { onLogin: EMPTY } }
|
||||
]
|
||||
});
|
||||
|
||||
@ -85,7 +87,7 @@ describe('AuthBearerInterceptor', () => {
|
||||
});
|
||||
|
||||
it('should interceptor add auth token to every URL if excluded URLs array is empty', () => {
|
||||
spyOn(authService, 'getBearerExcludedUrls').and.returnValue([]);
|
||||
spyOnProperty<any>(interceptor, 'bearerExcludedUrls').and.returnValue([]);
|
||||
|
||||
const mockUrls = [
|
||||
'http://example.com/auth/realms/testpath',
|
||||
|
@ -16,40 +16,37 @@
|
||||
*/
|
||||
|
||||
import { throwError as observableThrowError, Observable } from 'rxjs';
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
HttpHandler, HttpInterceptor, HttpRequest,
|
||||
HttpSentEvent, HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpUserEvent, HttpHeaders
|
||||
} from '@angular/common/http';
|
||||
import { AuthenticationService } from '../services/authentication.service';
|
||||
import { catchError, mergeMap } from 'rxjs/operators';
|
||||
import { AuthenticationService } from '../services/authentication.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthBearerInterceptor implements HttpInterceptor {
|
||||
private _bearerExcludedUrls: readonly string[] = ['resources/', 'assets/', 'auth/realms', 'idp/'];
|
||||
|
||||
private excludedUrlsRegex: RegExp[];
|
||||
|
||||
constructor(private injector: Injector, private authService: AuthenticationService) { }
|
||||
constructor(private authenticationService: AuthenticationService) { }
|
||||
|
||||
private loadExcludedUrlsRegex() {
|
||||
const excludedUrls = this.authService.getBearerExcludedUrls();
|
||||
const excludedUrls = this.bearerExcludedUrls;
|
||||
this.excludedUrlsRegex = excludedUrls.map((urlPattern) => new RegExp(`^https?://[^/]+/${urlPattern}`, 'i')) || [];
|
||||
}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler):
|
||||
Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
|
||||
|
||||
this.authService = this.injector.get(AuthenticationService);
|
||||
|
||||
if (!this.authService?.getBearerExcludedUrls()) {
|
||||
return next.handle(req);
|
||||
}
|
||||
|
||||
if (!this.excludedUrlsRegex) {
|
||||
this.loadExcludedUrlsRegex();
|
||||
}
|
||||
|
||||
const urlRequest = req.url;
|
||||
const shallPass: boolean = this.excludedUrlsRegex.some((regex) => regex.test(urlRequest));
|
||||
const requestUrl = req.url;
|
||||
const shallPass: boolean = this.excludedUrlsRegex.some((regex) => regex.test(requestUrl));
|
||||
if (shallPass) {
|
||||
return next.handle(req)
|
||||
.pipe(
|
||||
@ -57,10 +54,10 @@ export class AuthBearerInterceptor implements HttpInterceptor {
|
||||
);
|
||||
}
|
||||
|
||||
return this.authService.addTokenToHeader(req.headers)
|
||||
return this.authenticationService.addTokenToHeader(requestUrl, req.headers)
|
||||
.pipe(
|
||||
mergeMap((headersWithBearer) => {
|
||||
const headerWithContentType = this.appendJsonContentType(headersWithBearer);
|
||||
const headerWithContentType = this.appendJsonContentType(headersWithBearer, req.body);
|
||||
const kcReq = req.clone({ headers: headerWithContentType});
|
||||
return next.handle(kcReq)
|
||||
.pipe(
|
||||
@ -70,7 +67,7 @@ export class AuthBearerInterceptor implements HttpInterceptor {
|
||||
);
|
||||
}
|
||||
|
||||
private appendJsonContentType(headers: HttpHeaders): HttpHeaders {
|
||||
private appendJsonContentType(headers: HttpHeaders, reqBody: any): HttpHeaders {
|
||||
|
||||
// prevent adding any content type, to properly handle formData with boundary browser generated value,
|
||||
// as adding any Content-Type its going to break the upload functionality
|
||||
@ -79,11 +76,15 @@ export class AuthBearerInterceptor implements HttpInterceptor {
|
||||
return headers.delete('Content-Type');
|
||||
}
|
||||
|
||||
if (!headers.get('Content-Type')) {
|
||||
if (!headers.get('Content-Type') && !(reqBody instanceof FormData)) {
|
||||
return headers.set('Content-Type', 'application/json;charset=UTF-8');
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
protected get bearerExcludedUrls(): readonly string[] {
|
||||
return this._bearerExcludedUrls;
|
||||
}
|
||||
|
||||
}
|
||||
|
374
lib/core/src/lib/auth/basic-auth/basic-alfresco-auth.service.ts
Normal file
374
lib/core/src/lib/auth/basic-auth/basic-alfresco-auth.service.ts
Normal 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;
|
||||
}
|
||||
}
|
220
lib/core/src/lib/auth/basic-auth/content-auth.ts
Normal file
220
lib/core/src/lib/auth/basic-auth/content-auth.ts
Normal 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-');
|
||||
}
|
||||
|
||||
}
|
210
lib/core/src/lib/auth/basic-auth/process-auth.ts
Normal file
210
lib/core/src/lib/auth/basic-auth/process-auth.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -17,24 +17,35 @@
|
||||
|
||||
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild, UrlTree } from '@angular/router';
|
||||
import { AuthenticationService } from '../services/authentication.service';
|
||||
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
|
||||
import {
|
||||
AppConfigService,
|
||||
AppConfigValues
|
||||
} from '../../app-config/app-config.service';
|
||||
import { OauthConfigModel } from '../models/oauth-config.model';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { StorageService } from '../../common/services/storage.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { inject } from '@angular/core';
|
||||
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
|
||||
import { OidcAuthenticationService } from '../services/oidc-authentication.service';
|
||||
|
||||
|
||||
export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
|
||||
protected authenticationService = inject(AuthenticationService);
|
||||
protected router = inject(Router);
|
||||
protected appConfigService = inject(AppConfigService);
|
||||
protected dialog = inject(MatDialog);
|
||||
private storageService = inject(StorageService);
|
||||
|
||||
protected get withCredentials(): boolean {
|
||||
return this.appConfigService.get<boolean>('auth.withCredentials', false);
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected authenticationService: AuthenticationService,
|
||||
protected basicAlfrescoAuthService: BasicAlfrescoAuthService,
|
||||
protected oidcAuthenticationService: OidcAuthenticationService,
|
||||
protected router: Router,
|
||||
protected appConfigService: AppConfigService,
|
||||
protected dialog: MatDialog,
|
||||
private storageService: StorageService
|
||||
) {
|
||||
}
|
||||
|
||||
abstract checkLogin(
|
||||
activeRoute: ActivatedRouteSnapshot,
|
||||
redirectUrl: string
|
||||
@ -78,15 +89,17 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
|
||||
let urlToRedirect = `/${this.getLoginRoute()}`;
|
||||
|
||||
if (!this.authenticationService.isOauth()) {
|
||||
this.authenticationService.setRedirect({
|
||||
this.basicAlfrescoAuthService.setRedirect({
|
||||
provider: this.getProvider(),
|
||||
url
|
||||
});
|
||||
|
||||
urlToRedirect = `${urlToRedirect}?redirectUrl=${url}`;
|
||||
return this.navigate(urlToRedirect);
|
||||
} else if (this.getOauthConfig().silentLogin && !this.authenticationService.isPublicUrl()) {
|
||||
this.authenticationService.ssoImplicitLogin();
|
||||
} else if (this.getOauthConfig().silentLogin && !this.oidcAuthenticationService.isPublicUrl()) {
|
||||
if (!this.oidcAuthenticationService.hasValidIdToken() || !this.oidcAuthenticationService.hasValidAccessToken()) {
|
||||
this.oidcAuthenticationService.ssoImplicitLogin();
|
||||
}
|
||||
} else {
|
||||
return this.navigate(urlToRedirect);
|
||||
}
|
||||
@ -101,7 +114,13 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
|
||||
}
|
||||
|
||||
protected getOauthConfig(): OauthConfigModel {
|
||||
return this.appConfigService.oauth2;
|
||||
return (
|
||||
this.appConfigService &&
|
||||
this.appConfigService.get<OauthConfigModel>(
|
||||
AppConfigValues.OAUTHCONFIG,
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected getLoginRoute(): string {
|
||||
@ -113,12 +132,21 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
|
||||
}
|
||||
|
||||
protected isOAuthWithoutSilentLogin(): boolean {
|
||||
const oauth = this.appConfigService.oauth2;
|
||||
return this.authenticationService.isOauth() && !!oauth && !oauth.silentLogin;
|
||||
const oauth = this.appConfigService.get<OauthConfigModel>(
|
||||
AppConfigValues.OAUTHCONFIG,
|
||||
null
|
||||
);
|
||||
return (
|
||||
this.authenticationService.isOauth() && !!oauth && !oauth.silentLogin
|
||||
);
|
||||
}
|
||||
|
||||
protected isSilentLogin(): boolean {
|
||||
const oauth = this.appConfigService.oauth2;
|
||||
const oauth = this.appConfigService.get<OauthConfigModel>(
|
||||
AppConfigValues.OAUTHCONFIG,
|
||||
null
|
||||
);
|
||||
|
||||
return this.authenticationService.isOauth() && oauth && oauth.silentLogin;
|
||||
}
|
||||
}
|
||||
|
@ -23,11 +23,16 @@ import { RouterStateSnapshot, Router } from '@angular/router';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
|
||||
import { OidcAuthenticationService } from '../services/oidc-authentication.service';
|
||||
|
||||
describe('AuthGuardService BPM', () => {
|
||||
|
||||
let authGuard: AuthGuardBpm;
|
||||
let authService: AuthenticationService;
|
||||
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
|
||||
let oidcAuthenticationService: OidcAuthenticationService;
|
||||
|
||||
let router: Router;
|
||||
let appConfigService: AppConfigService;
|
||||
|
||||
@ -36,9 +41,21 @@ describe('AuthGuardService BPM', () => {
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: OidcAuthenticationService, useValue: {
|
||||
ssoImplicitLogin: () => { },
|
||||
isPublicUrl: () => false,
|
||||
hasValidIdToken: () => false,
|
||||
isLoggedIn: () => false
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
localStorage.clear();
|
||||
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
|
||||
oidcAuthenticationService = TestBed.inject(OidcAuthenticationService);
|
||||
authService = TestBed.inject(AuthenticationService);
|
||||
authGuard = TestBed.inject(AuthGuardBpm);
|
||||
router = TestBed.inject(Router);
|
||||
@ -53,8 +70,8 @@ describe('AuthGuardService BPM', () => {
|
||||
spyOn(router, 'navigateByUrl').and.stub();
|
||||
spyOn(authService, 'isBpmLoggedIn').and.returnValue(false);
|
||||
spyOn(authService, 'isOauth').and.returnValue(true);
|
||||
spyOn(authService, 'isPublicUrl').and.returnValue(false);
|
||||
spyOn(authService, 'ssoImplicitLogin').and.stub();
|
||||
spyOn(oidcAuthenticationService, 'isPublicUrl').and.returnValue(false);
|
||||
spyOn(oidcAuthenticationService, 'ssoImplicitLogin').and.stub();
|
||||
|
||||
appConfigService.config.oauth2 = {
|
||||
silentLogin: true,
|
||||
@ -69,7 +86,7 @@ describe('AuthGuardService BPM', () => {
|
||||
const route = { url: 'abc' } as RouterStateSnapshot;
|
||||
|
||||
expect(await authGuard.canActivate(null, route)).toBeFalsy();
|
||||
expect(authService.ssoImplicitLogin).toHaveBeenCalledTimes(1);
|
||||
expect(oidcAuthenticationService.ssoImplicitLogin).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('if the alfresco js api is logged in should canActivate be true', async () => {
|
||||
@ -130,53 +147,53 @@ describe('AuthGuardService BPM', () => {
|
||||
});
|
||||
|
||||
it('should set redirect url', () => {
|
||||
spyOn(authService, 'setRedirect').and.callThrough();
|
||||
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
|
||||
spyOn(router, 'navigateByUrl').and.stub();
|
||||
const route = { url: 'some-url' } as RouterStateSnapshot;
|
||||
|
||||
authGuard.canActivate(null, route);
|
||||
|
||||
expect(authService.setRedirect).toHaveBeenCalledWith({
|
||||
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
|
||||
provider: 'BPM', url: 'some-url'
|
||||
});
|
||||
expect(authService.getRedirect()).toEqual('some-url');
|
||||
expect(basicAlfrescoAuthService.getRedirect()).toEqual('some-url');
|
||||
});
|
||||
|
||||
it('should set redirect navigation commands with query params', () => {
|
||||
spyOn(authService, 'setRedirect').and.callThrough();
|
||||
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
|
||||
spyOn(router, 'navigateByUrl').and.stub();
|
||||
const route = { url: 'some-url;q=123' } as RouterStateSnapshot;
|
||||
|
||||
authGuard.canActivate(null, route);
|
||||
|
||||
expect(authService.setRedirect).toHaveBeenCalledWith({
|
||||
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
|
||||
provider: 'BPM', url: 'some-url;q=123'
|
||||
});
|
||||
expect(authService.getRedirect()).toEqual('some-url;q=123');
|
||||
expect(basicAlfrescoAuthService.getRedirect()).toEqual('some-url;q=123');
|
||||
});
|
||||
|
||||
it('should set redirect navigation commands with query params', () => {
|
||||
spyOn(authService, 'setRedirect').and.callThrough();
|
||||
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
|
||||
spyOn(router, 'navigateByUrl').and.stub();
|
||||
const route = { url: '/' } as RouterStateSnapshot;
|
||||
|
||||
authGuard.canActivate(null, route);
|
||||
|
||||
expect(authService.setRedirect).toHaveBeenCalledWith({
|
||||
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
|
||||
provider: 'BPM', url: '/'
|
||||
});
|
||||
expect(authService.getRedirect()).toEqual('/');
|
||||
expect(basicAlfrescoAuthService.getRedirect()).toEqual('/');
|
||||
});
|
||||
|
||||
it('should get redirect url from config if there is one configured', () => {
|
||||
appConfigService.config.loginRoute = 'fakeLoginRoute';
|
||||
spyOn(authService, 'setRedirect').and.callThrough();
|
||||
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
|
||||
spyOn(router, 'navigateByUrl').and.stub();
|
||||
const route = { url: 'some-url' } as RouterStateSnapshot;
|
||||
|
||||
authGuard.canActivate(null, route);
|
||||
|
||||
expect(authService.setRedirect).toHaveBeenCalledWith({
|
||||
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
|
||||
provider: 'BPM', url: 'some-url'
|
||||
});
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith(router.parseUrl('/fakeLoginRoute?redirectUrl=some-url'));
|
||||
@ -187,13 +204,13 @@ describe('AuthGuardService BPM', () => {
|
||||
|
||||
spyOn(materialDialog, 'closeAll');
|
||||
|
||||
spyOn(authService, 'setRedirect').and.callThrough();
|
||||
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
|
||||
spyOn(router, 'navigateByUrl').and.stub();
|
||||
const route = { url: 'some-url' } as RouterStateSnapshot;
|
||||
|
||||
authGuard.canActivate(null, route);
|
||||
|
||||
expect(authService.setRedirect).toHaveBeenCalledWith({
|
||||
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
|
||||
provider: 'BPM', url: 'some-url'
|
||||
});
|
||||
|
||||
|
@ -16,13 +16,30 @@
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, UrlTree } from '@angular/router';
|
||||
import { ActivatedRouteSnapshot, Router, UrlTree } from '@angular/router';
|
||||
import { AppConfigService } from '../../app-config/app-config.service';
|
||||
import { AuthenticationService } from '../services/authentication.service';
|
||||
import { AuthGuardBase } from './auth-guard-base';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { StorageService } from '../../common/services/storage.service';
|
||||
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
|
||||
import { OidcAuthenticationService } from '../services/oidc-authentication.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthGuardBpm extends AuthGuardBase {
|
||||
|
||||
constructor(authenticationService: AuthenticationService,
|
||||
basicAlfrescoAuthService: BasicAlfrescoAuthService,
|
||||
oidcAuthenticationService: OidcAuthenticationService,
|
||||
router: Router,
|
||||
appConfigService: AppConfigService,
|
||||
dialog: MatDialog,
|
||||
storageService: StorageService) {
|
||||
super(authenticationService,basicAlfrescoAuthService, oidcAuthenticationService,router, appConfigService, dialog, storageService);
|
||||
}
|
||||
|
||||
async checkLogin(_: ActivatedRouteSnapshot, redirectUrl: string): Promise<boolean | UrlTree> {
|
||||
if (this.authenticationService.isBpmLoggedIn() || this.withCredentials) {
|
||||
return true;
|
||||
|
@ -23,11 +23,15 @@ import { RouterStateSnapshot, Router } from '@angular/router';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { OidcAuthenticationService } from '../services/oidc-authentication.service';
|
||||
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
|
||||
|
||||
describe('AuthGuardService ECM', () => {
|
||||
|
||||
let authGuard: AuthGuardEcm;
|
||||
let authService: AuthenticationService;
|
||||
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
|
||||
let oidcAuthenticationService: OidcAuthenticationService;
|
||||
let router: Router;
|
||||
let appConfigService: AppConfigService;
|
||||
|
||||
@ -36,9 +40,21 @@ describe('AuthGuardService ECM', () => {
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: OidcAuthenticationService, useValue: {
|
||||
ssoImplicitLogin: () => { },
|
||||
isPublicUrl: () => false,
|
||||
hasValidIdToken: () => false,
|
||||
isLoggedIn: () => false
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
localStorage.clear();
|
||||
oidcAuthenticationService = TestBed.inject(OidcAuthenticationService);
|
||||
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
|
||||
authService = TestBed.inject(AuthenticationService);
|
||||
authGuard = TestBed.inject(AuthGuardEcm);
|
||||
router = TestBed.inject(Router);
|
||||
@ -98,8 +114,8 @@ describe('AuthGuardService ECM', () => {
|
||||
it('should redirect url if the alfresco js api is NOT logged in and isOAuth with silentLogin', async () => {
|
||||
spyOn(authService, 'isEcmLoggedIn').and.returnValue(false);
|
||||
spyOn(authService, 'isOauth').and.returnValue(true);
|
||||
spyOn(authService, 'isPublicUrl').and.returnValue(false);
|
||||
spyOn(authService, 'ssoImplicitLogin').and.stub();
|
||||
spyOn(oidcAuthenticationService, 'isPublicUrl').and.returnValue(false);
|
||||
spyOn(oidcAuthenticationService, 'ssoImplicitLogin').and.stub();
|
||||
|
||||
appConfigService.config.oauth2 = {
|
||||
silentLogin: true,
|
||||
@ -113,7 +129,7 @@ describe('AuthGuardService ECM', () => {
|
||||
const route = {url : 'abc'} as RouterStateSnapshot;
|
||||
|
||||
expect(await authGuard.canActivate(null, route)).toBeFalsy();
|
||||
expect(authService.ssoImplicitLogin).toHaveBeenCalledTimes(1);
|
||||
expect(oidcAuthenticationService.ssoImplicitLogin).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not redirect url if NOT logged in and isOAuth but no silentLogin configured', async () => {
|
||||
@ -128,53 +144,53 @@ describe('AuthGuardService ECM', () => {
|
||||
});
|
||||
|
||||
it('should set redirect navigation commands', () => {
|
||||
spyOn(authService, 'setRedirect').and.callThrough();
|
||||
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
|
||||
spyOn(router, 'navigateByUrl').and.stub();
|
||||
const route = { url: 'some-url' } as RouterStateSnapshot;
|
||||
|
||||
authGuard.canActivate(null, route);
|
||||
|
||||
expect(authService.setRedirect).toHaveBeenCalledWith({
|
||||
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
|
||||
provider: 'ECM', url: 'some-url'
|
||||
});
|
||||
expect(authService.getRedirect()).toEqual('some-url');
|
||||
expect(basicAlfrescoAuthService.getRedirect()).toEqual('some-url');
|
||||
});
|
||||
|
||||
it('should set redirect navigation commands with query params', () => {
|
||||
spyOn(authService, 'setRedirect').and.callThrough();
|
||||
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
|
||||
spyOn(router, 'navigateByUrl').and.stub();
|
||||
const route = { url: 'some-url;q=123' } as RouterStateSnapshot;
|
||||
|
||||
authGuard.canActivate(null, route);
|
||||
|
||||
expect(authService.setRedirect).toHaveBeenCalledWith({
|
||||
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
|
||||
provider: 'ECM', url: 'some-url;q=123'
|
||||
});
|
||||
expect(authService.getRedirect()).toEqual('some-url;q=123');
|
||||
expect(basicAlfrescoAuthService.getRedirect()).toEqual('some-url;q=123');
|
||||
});
|
||||
|
||||
it('should set redirect navigation commands with query params', () => {
|
||||
spyOn(authService, 'setRedirect').and.callThrough();
|
||||
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
|
||||
spyOn(router, 'navigateByUrl').and.stub();
|
||||
const route = { url: '/' } as RouterStateSnapshot;
|
||||
|
||||
authGuard.canActivate(null, route);
|
||||
|
||||
expect(authService.setRedirect).toHaveBeenCalledWith({
|
||||
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
|
||||
provider: 'ECM', url: '/'
|
||||
});
|
||||
expect(authService.getRedirect()).toEqual('/');
|
||||
expect(basicAlfrescoAuthService.getRedirect()).toEqual('/');
|
||||
});
|
||||
|
||||
it('should get redirect url from config if there is one configured', () => {
|
||||
appConfigService.config.loginRoute = 'fakeLoginRoute';
|
||||
spyOn(authService, 'setRedirect').and.callThrough();
|
||||
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
|
||||
spyOn(router, 'navigateByUrl').and.stub();
|
||||
const route = { url: 'some-url' } as RouterStateSnapshot;
|
||||
|
||||
authGuard.canActivate(null, route);
|
||||
|
||||
expect(authService.setRedirect).toHaveBeenCalledWith({
|
||||
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
|
||||
provider: 'ECM', url: 'some-url'
|
||||
});
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith(router.parseUrl('/fakeLoginRoute?redirectUrl=some-url'));
|
||||
@ -185,13 +201,13 @@ describe('AuthGuardService ECM', () => {
|
||||
|
||||
spyOn(materialDialog, 'closeAll');
|
||||
|
||||
spyOn(authService, 'setRedirect').and.callThrough();
|
||||
spyOn(basicAlfrescoAuthService, 'setRedirect').and.callThrough();
|
||||
spyOn(router, 'navigateByUrl').and.stub();
|
||||
const route = { url: 'some-url' } as RouterStateSnapshot;
|
||||
|
||||
authGuard.canActivate(null, route);
|
||||
|
||||
expect(authService.setRedirect).toHaveBeenCalledWith({
|
||||
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
|
||||
provider: 'ECM', url: 'some-url'
|
||||
});
|
||||
|
||||
|
@ -16,13 +16,33 @@
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, UrlTree } from '@angular/router';
|
||||
import {
|
||||
ActivatedRouteSnapshot, Router, UrlTree
|
||||
} from '@angular/router';
|
||||
import { AuthenticationService } from '../services/authentication.service';
|
||||
import { AppConfigService } from '../../app-config/app-config.service';
|
||||
import { AuthGuardBase } from './auth-guard-base';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { StorageService } from '../../common/services/storage.service';
|
||||
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
|
||||
import { OidcAuthenticationService } from '../services/oidc-authentication.service';
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthGuardEcm extends AuthGuardBase {
|
||||
|
||||
constructor(authenticationService: AuthenticationService,
|
||||
basicAlfrescoAuthService: BasicAlfrescoAuthService,
|
||||
oidcAuthenticationService: OidcAuthenticationService,
|
||||
router: Router,
|
||||
appConfigService: AppConfigService,
|
||||
dialog: MatDialog,
|
||||
storageService: StorageService) {
|
||||
super(authenticationService, basicAlfrescoAuthService, oidcAuthenticationService, router, appConfigService, dialog, storageService);
|
||||
}
|
||||
|
||||
async checkLogin(_: ActivatedRouteSnapshot, redirectUrl: string): Promise<boolean | UrlTree> {
|
||||
if (this.authenticationService.isEcmLoggedIn() || this.withCredentials) {
|
||||
return true;
|
||||
|
@ -23,6 +23,8 @@ import { AuthenticationService } from '../services/authentication.service';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { StorageService } from '../../common/services/storage.service';
|
||||
import { OidcAuthenticationService } from '../services/oidc-authentication.service';
|
||||
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
|
||||
|
||||
describe('AuthGuardService', () => {
|
||||
let state;
|
||||
@ -31,17 +33,30 @@ describe('AuthGuardService', () => {
|
||||
let authGuard: AuthGuard;
|
||||
let storageService: StorageService;
|
||||
let appConfigService: AppConfigService;
|
||||
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
|
||||
let oidcAuthenticationService: OidcAuthenticationService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: OidcAuthenticationService, useValue: {
|
||||
ssoImplicitLogin: () => { },
|
||||
isPublicUrl: () => false,
|
||||
hasValidIdToken: () => false
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
localStorage.clear();
|
||||
state = { url: '' };
|
||||
authService = TestBed.inject(AuthenticationService);
|
||||
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
|
||||
oidcAuthenticationService = TestBed.inject(OidcAuthenticationService);
|
||||
router = TestBed.inject(Router);
|
||||
authGuard = TestBed.inject(AuthGuard);
|
||||
appConfigService = TestBed.inject(AppConfigService);
|
||||
@ -110,13 +125,13 @@ describe('AuthGuardService', () => {
|
||||
});
|
||||
|
||||
it('should NOT redirect url if the User is NOT logged in and isOAuth but with silentLogin configured', async () => {
|
||||
spyOn(authService, 'ssoImplicitLogin').and.stub();
|
||||
spyOn(oidcAuthenticationService, 'ssoImplicitLogin').and.stub();
|
||||
spyOn(authService, 'isLoggedIn').and.returnValue(false);
|
||||
spyOn(authService, 'isOauth').and.returnValue(true);
|
||||
appConfigService.config.oauth2.silentLogin = true;
|
||||
|
||||
expect(await authGuard.canActivate(null, state)).toBeFalsy();
|
||||
expect(authService.ssoImplicitLogin).toHaveBeenCalledTimes(1);
|
||||
expect(oidcAuthenticationService.ssoImplicitLogin).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should set redirect url', async () => {
|
||||
@ -124,11 +139,11 @@ describe('AuthGuardService', () => {
|
||||
appConfigService.config.loginRoute = 'login';
|
||||
|
||||
spyOn(router, 'navigateByUrl');
|
||||
spyOn(authService, 'setRedirect');
|
||||
spyOn(basicAlfrescoAuthService, 'setRedirect');
|
||||
|
||||
await authGuard.canActivate(null, state);
|
||||
|
||||
expect(authService.setRedirect).toHaveBeenCalledWith({
|
||||
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
|
||||
provider: 'ALL', url: 'some-url'
|
||||
});
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith(router.parseUrl('/login?redirectUrl=some-url'));
|
||||
@ -140,11 +155,11 @@ describe('AuthGuardService', () => {
|
||||
appConfigService.config.provider = 'ALL';
|
||||
|
||||
spyOn(router, 'navigateByUrl');
|
||||
spyOn(authService, 'setRedirect');
|
||||
spyOn(basicAlfrescoAuthService, 'setRedirect');
|
||||
|
||||
await authGuard.canActivate(null, state);
|
||||
|
||||
expect(authService.setRedirect).toHaveBeenCalledWith({
|
||||
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
|
||||
provider: 'ALL', url: 'some-url;q=query'
|
||||
});
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith(router.parseUrl('/login?redirectUrl=some-url;q=query'));
|
||||
@ -155,11 +170,11 @@ describe('AuthGuardService', () => {
|
||||
appConfigService.config.loginRoute = 'fakeLoginRoute';
|
||||
|
||||
spyOn(router, 'navigateByUrl');
|
||||
spyOn(authService, 'setRedirect');
|
||||
spyOn(basicAlfrescoAuthService, 'setRedirect');
|
||||
|
||||
await authGuard.canActivate(null, state);
|
||||
|
||||
expect(authService.setRedirect).toHaveBeenCalledWith({
|
||||
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
|
||||
provider: 'ALL', url: 'some-url'
|
||||
});
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith(router.parseUrl('/fakeLoginRoute?redirectUrl=some-url'));
|
||||
@ -169,11 +184,11 @@ describe('AuthGuardService', () => {
|
||||
state.url = '/';
|
||||
|
||||
spyOn(router, 'navigateByUrl');
|
||||
spyOn(authService, 'setRedirect');
|
||||
spyOn(basicAlfrescoAuthService, 'setRedirect');
|
||||
|
||||
await authGuard.canActivate(null, state);
|
||||
|
||||
expect(authService.setRedirect).toHaveBeenCalledWith({
|
||||
expect(basicAlfrescoAuthService.setRedirect).toHaveBeenCalledWith({
|
||||
provider: 'ALL', url: '/'
|
||||
});
|
||||
});
|
||||
|
@ -16,9 +16,16 @@
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, UrlTree } from '@angular/router';
|
||||
import { ActivatedRouteSnapshot, Router, UrlTree } from '@angular/router';
|
||||
import { AuthenticationService } from '../services/authentication.service';
|
||||
import { AppConfigService } from '../../app-config/app-config.service';
|
||||
import { AuthGuardBase } from './auth-guard-base';
|
||||
import { JwtHelperService } from '../services/jwt-helper.service';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { StorageService } from '../../common/services/storage.service';
|
||||
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
|
||||
import { OidcAuthenticationService } from '../services/oidc-authentication.service';
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -27,8 +34,15 @@ export class AuthGuard extends AuthGuardBase {
|
||||
|
||||
ticketChangeBind: any;
|
||||
|
||||
constructor(private jwtHelperService: JwtHelperService) {
|
||||
super();
|
||||
constructor(private jwtHelperService: JwtHelperService,
|
||||
authenticationService: AuthenticationService,
|
||||
basicAlfrescoAuthService: BasicAlfrescoAuthService,
|
||||
oidcAuthenticationService: OidcAuthenticationService,
|
||||
router: Router,
|
||||
appConfigService: AppConfigService,
|
||||
dialog: MatDialog,
|
||||
storageService: StorageService) {
|
||||
super(authenticationService, basicAlfrescoAuthService, oidcAuthenticationService, router, appConfigService, dialog, storageService);
|
||||
this.ticketChangeBind = this.ticketChange.bind(this);
|
||||
|
||||
window.addEventListener('storage', this.ticketChangeBind);
|
||||
|
@ -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;
|
||||
}
|
@ -15,6 +15,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './activiti/activiti-client.types';
|
||||
export * from './alfresco-js-clients.module';
|
||||
export * from './discovery/discovery-client.types';
|
||||
export interface Authentication {
|
||||
basicAuth?: BasicAuth;
|
||||
cookie?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface BasicAuth {
|
||||
username?: string;
|
||||
password?: string;
|
||||
ticket?: string;
|
||||
}
|
@ -19,17 +19,12 @@ import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { AuthConfig, AUTH_CONFIG, OAuthModule, OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
|
||||
import { AlfrescoApiNoAuthService } from '../../api-factories/alfresco-api-no-auth.service';
|
||||
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||
import { AuthGuardBpm } from '../guard/auth-guard-bpm.service';
|
||||
import { AuthGuardEcm } from '../guard/auth-guard-ecm.service';
|
||||
import { AuthGuard } from '../guard/auth-guard.service';
|
||||
import { AuthenticationService } from '../services/authentication.service';
|
||||
import { StorageService } from '../../common/services/storage.service';
|
||||
import { AuthModuleConfig, AUTH_MODULE_CONFIG } from './auth-config';
|
||||
import { authConfigFactory, AuthConfigService } from './auth-config.service';
|
||||
import { AuthRoutingModule } from './auth-routing.module';
|
||||
import { AuthService } from './auth.service';
|
||||
import { OidcAuthGuard } from './oidc-auth.guard';
|
||||
import { OIDCAuthenticationService } from './oidc-authentication.service';
|
||||
import { RedirectAuthService } from './redirect-auth.service';
|
||||
import { AuthenticationConfirmationComponent } from './view/authentication-confirmation/authentication-confirmation.component';
|
||||
|
||||
@ -51,10 +46,10 @@ export function loginFactory(oAuthService: OAuthService, storage: OAuthStorage,
|
||||
imports: [AuthRoutingModule, OAuthModule.forRoot()],
|
||||
providers: [
|
||||
{ provide: OAuthStorage, useExisting: StorageService },
|
||||
{ provide: AuthGuard, useClass: OidcAuthGuard },
|
||||
{ provide: AuthGuardEcm, useClass: OidcAuthGuard },
|
||||
{ provide: AuthGuardBpm, useClass: OidcAuthGuard },
|
||||
{ provide: AuthenticationService, useClass: OIDCAuthenticationService },
|
||||
// { provide: AuthGuard, useClass: OidcAuthGuard },
|
||||
// { provide: AuthGuardEcm, useClass: OidcAuthGuard },
|
||||
// { provide: AuthGuardBpm, useClass: OidcAuthGuard },
|
||||
{ provide: AuthenticationService},
|
||||
{ provide: AlfrescoApiService, useClass: AlfrescoApiNoAuthService },
|
||||
{
|
||||
provide: AUTH_CONFIG,
|
||||
|
@ -22,6 +22,8 @@ import { Observable } from 'rxjs';
|
||||
* Provide authentication/authorization through OAuth2/OIDC protocol.
|
||||
*/
|
||||
export abstract class AuthService {
|
||||
abstract onLogin: Observable<any>;
|
||||
|
||||
/** Subscribe to whether the user has valid Id/Access tokens. */
|
||||
abstract authenticated$: Observable<boolean>;
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -19,13 +19,16 @@ import { Inject, Injectable } from '@angular/core';
|
||||
import { AuthConfig, AUTH_CONFIG, OAuthErrorEvent, OAuthService, OAuthStorage, TokenResponse } from 'angular-oauth2-oidc';
|
||||
import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, filter, map, shareReplay, startWith } from 'rxjs/operators';
|
||||
import { distinctUntilChanged, filter, map, shareReplay } from 'rxjs/operators';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
const isPromise = <T>(value: T | Promise<T>): value is Promise<T> => value && typeof (value as Promise<T>).then === 'function';
|
||||
|
||||
@Injectable()
|
||||
export class RedirectAuthService extends AuthService {
|
||||
|
||||
onLogin: Observable<any>;
|
||||
|
||||
private _loadDiscoveryDocumentPromise = Promise.resolve(false);
|
||||
|
||||
/** Subscribe to whether the user has valid Id/Access tokens. */
|
||||
@ -52,29 +55,32 @@ export class RedirectAuthService extends AuthService {
|
||||
) {
|
||||
super();
|
||||
this.authConfig = authConfig;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.oauthService.clearHashAfterLogin = true;
|
||||
|
||||
this.authenticated$ = this.oauthService.events.pipe(
|
||||
startWith(undefined),
|
||||
map(() => this.authenticated),
|
||||
distinctUntilChanged(),
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
this.onLogin = this.authenticated$.pipe(
|
||||
filter((authenticated) => authenticated),
|
||||
map(() => undefined)
|
||||
);
|
||||
|
||||
this.idpUnreachable$ = this.oauthService.events.pipe(
|
||||
filter((event): event is OAuthErrorEvent => event.type === 'discovery_document_load_error'),
|
||||
map((event) => event.reason as Error)
|
||||
);
|
||||
}
|
||||
|
||||
init() {
|
||||
if (isPromise(this.authConfig)) {
|
||||
return this.authConfig.then((config) => this.configureAuth(config));
|
||||
}
|
||||
|
||||
return this.configureAuth(this.authConfig);
|
||||
|
||||
}
|
||||
|
||||
logout() {
|
||||
|
@ -24,7 +24,7 @@ import { AuthService } from '../../auth.service';
|
||||
const ROUTE_DEFAULT = '/';
|
||||
|
||||
@Component({
|
||||
template: '',
|
||||
template: '<div data-automation-id="auth-confirmation"></div>',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AuthenticationConfirmationComponent {
|
||||
|
@ -31,6 +31,10 @@ export * from './services/jwt-helper.service';
|
||||
export * from './services/oauth2.service';
|
||||
export * from './services/user-access.service';
|
||||
|
||||
export * from './basic-auth/basic-alfresco-auth.service';
|
||||
export * from './basic-auth/process-auth';
|
||||
export * from './basic-auth/content-auth';
|
||||
|
||||
export * from './interfaces/identity-user.service.interface';
|
||||
export * from './interfaces/identity-group.interface';
|
||||
export * from './interfaces/openid-configuration.interface';
|
||||
|
@ -16,21 +16,23 @@
|
||||
*/
|
||||
|
||||
import { fakeAsync, TestBed } from '@angular/core/testing';
|
||||
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||
import { AuthenticationService } from './authentication.service';
|
||||
import { CookieService } from '../../common/services/cookie.service';
|
||||
import { AppConfigService } from '../../app-config/app-config.service';
|
||||
import { setupTestBed } from '../../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
|
||||
import { OidcAuthenticationService } from './oidc-authentication.service';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('AuthenticationService', () => {
|
||||
let apiService: AlfrescoApiService;
|
||||
let authService: AuthenticationService;
|
||||
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
|
||||
let appConfigService: AppConfigService;
|
||||
let cookie: CookieService;
|
||||
let oidcAuthenticationService: OidcAuthenticationService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
@ -42,8 +44,9 @@ describe('AuthenticationService', () => {
|
||||
beforeEach(() => {
|
||||
sessionStorage.clear();
|
||||
localStorage.clear();
|
||||
apiService = TestBed.inject(AlfrescoApiService);
|
||||
authService = TestBed.inject(AuthenticationService);
|
||||
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
|
||||
oidcAuthenticationService = TestBed.inject(OidcAuthenticationService);
|
||||
|
||||
cookie = TestBed.inject(CookieService);
|
||||
cookie.clear();
|
||||
@ -73,6 +76,23 @@ describe('AuthenticationService', () => {
|
||||
});
|
||||
appConfigService.load();
|
||||
});
|
||||
|
||||
it('should kerberos be disabled if is oauth', () => {
|
||||
spyOn(authService, 'isOauth').and.returnValue(true);
|
||||
expect(authService.isKerberosEnabled()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should kerberos not enabled if is oauth is false and basic auth return false', () => {
|
||||
spyOn(authService, 'isOauth').and.returnValue(false);
|
||||
spyOn(basicAlfrescoAuthService, 'isKerberosEnabled').and.returnValue(false);
|
||||
expect(authService.isKerberosEnabled()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should kerberos be enabled if is oauth is false and basic auth return true', () => {
|
||||
spyOn(authService, 'isOauth').and.returnValue(false);
|
||||
spyOn(basicAlfrescoAuthService, 'isKerberosEnabled').and.returnValue(true);
|
||||
expect(authService.isKerberosEnabled()).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the setting is ECM', () => {
|
||||
@ -83,40 +103,30 @@ describe('AuthenticationService', () => {
|
||||
appConfigService.config.auth = { withCredentials: false };
|
||||
appConfigService.config.providers = 'ECM';
|
||||
appConfigService.load();
|
||||
apiService.reset();
|
||||
});
|
||||
|
||||
it('should not require cookie service enabled for ECM check', () => {
|
||||
spyOn(cookie, 'isEnabled').and.returnValue(false);
|
||||
spyOn(authService, 'isRememberMeSet').and.returnValue(false);
|
||||
spyOn(basicAlfrescoAuthService, 'isRememberMeSet').and.returnValue(false);
|
||||
spyOn(authService, 'isECMProvider').and.returnValue(true);
|
||||
spyOn(authService, 'isOauth').and.returnValue(false);
|
||||
spyOn(apiService, 'getInstance').and.callThrough();
|
||||
|
||||
expect(authService.isEcmLoggedIn()).toBeFalsy();
|
||||
expect(apiService.getInstance).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should check if loggedin on ECM in case the provider is ECM', () => {
|
||||
spyOn(authService, 'isEcmLoggedIn').and.returnValue(true);
|
||||
expect(authService.isLoggedInWith('ECM')).toBe(true);
|
||||
});
|
||||
|
||||
it('should require remember me set for ECM check', () => {
|
||||
spyOn(cookie, 'isEnabled').and.returnValue(true);
|
||||
spyOn(authService, 'isRememberMeSet').and.returnValue(false);
|
||||
spyOn(basicAlfrescoAuthService, 'isRememberMeSet').and.returnValue(false);
|
||||
spyOn(authService, 'isECMProvider').and.returnValue(true);
|
||||
spyOn(authService, 'isOauth').and.returnValue(false);
|
||||
spyOn(apiService, 'getInstance').and.callThrough();
|
||||
|
||||
expect(authService.isEcmLoggedIn()).toBeFalsy();
|
||||
expect(apiService.getInstance).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('[ECM] should return an ECM ticket after the login done', (done) => {
|
||||
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(() => {
|
||||
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(() => {
|
||||
expect(authService.isLoggedIn()).toBe(true);
|
||||
expect(authService.getTicketEcm()).toEqual('fake-post-ticket');
|
||||
expect(authService.getToken()).toEqual('fake-post-ticket');
|
||||
expect(authService.isEcmLoggedIn()).toBe(true);
|
||||
disposableLogin.unsubscribe();
|
||||
done();
|
||||
@ -130,7 +140,7 @@ describe('AuthenticationService', () => {
|
||||
});
|
||||
|
||||
it('[ECM] should login in the ECM if no provider are defined calling the login', fakeAsync(() => {
|
||||
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe((loginResponse) => {
|
||||
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe((loginResponse) => {
|
||||
expect(loginResponse).toEqual(fakeECMLoginResponse);
|
||||
disposableLogin.unsubscribe();
|
||||
});
|
||||
@ -143,10 +153,10 @@ describe('AuthenticationService', () => {
|
||||
}));
|
||||
|
||||
it('[ECM] should return a ticket undefined after logout', fakeAsync(() => {
|
||||
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(() => {
|
||||
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(() => {
|
||||
const disposableLogout = authService.logout().subscribe(() => {
|
||||
expect(authService.isLoggedIn()).toBe(false);
|
||||
expect(authService.getTicketEcm()).toBe(null);
|
||||
expect(authService.getToken()).toBe(null);
|
||||
expect(authService.isEcmLoggedIn()).toBe(false);
|
||||
disposableLogin.unsubscribe();
|
||||
disposableLogout.unsubscribe();
|
||||
@ -170,21 +180,21 @@ describe('AuthenticationService', () => {
|
||||
});
|
||||
|
||||
it('[ECM] should set/get redirectUrl when provider is ECM', () => {
|
||||
authService.setRedirect({ provider: 'ECM', url: 'some-url' });
|
||||
basicAlfrescoAuthService.setRedirect({ provider: 'ECM', url: 'some-url' });
|
||||
|
||||
expect(authService.getRedirect()).toEqual('some-url');
|
||||
expect(basicAlfrescoAuthService.getRedirect()).toEqual('some-url');
|
||||
});
|
||||
|
||||
it('[ECM] should set/get redirectUrl when provider is BPM', () => {
|
||||
authService.setRedirect({ provider: 'BPM', url: 'some-url' });
|
||||
basicAlfrescoAuthService.setRedirect({ provider: 'BPM', url: 'some-url' });
|
||||
|
||||
expect(authService.getRedirect()).toBeNull();
|
||||
expect(basicAlfrescoAuthService.getRedirect()).toBeNull();
|
||||
});
|
||||
|
||||
it('[ECM] should return null as redirectUrl when redirectUrl field is not set', () => {
|
||||
authService.setRedirect(null);
|
||||
basicAlfrescoAuthService.setRedirect(null);
|
||||
|
||||
expect(authService.getRedirect()).toBeNull();
|
||||
expect(basicAlfrescoAuthService.getRedirect()).toBeNull();
|
||||
});
|
||||
|
||||
it('[ECM] should return isECMProvider true', () => {
|
||||
@ -209,40 +219,30 @@ describe('AuthenticationService', () => {
|
||||
beforeEach(() => {
|
||||
appConfigService.config.providers = 'BPM';
|
||||
appConfigService.load();
|
||||
apiService.reset();
|
||||
});
|
||||
|
||||
it('should require remember me set for BPM check', () => {
|
||||
spyOn(cookie, 'isEnabled').and.returnValue(true);
|
||||
spyOn(authService, 'isRememberMeSet').and.returnValue(false);
|
||||
spyOn(basicAlfrescoAuthService, 'isRememberMeSet').and.returnValue(false);
|
||||
spyOn(authService, 'isBPMProvider').and.returnValue(true);
|
||||
spyOn(authService, 'isOauth').and.returnValue(false);
|
||||
spyOn(apiService, 'getInstance').and.callThrough();
|
||||
|
||||
expect(authService.isBpmLoggedIn()).toBeFalsy();
|
||||
expect(apiService.getInstance).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should check if loggedin on BPM in case the provider is BPM', () => {
|
||||
spyOn(authService, 'isBpmLoggedIn').and.returnValue(true);
|
||||
expect(authService.isLoggedInWith('BPM')).toBe(true);
|
||||
});
|
||||
|
||||
it('should not require cookie service enabled for BPM check', () => {
|
||||
spyOn(cookie, 'isEnabled').and.returnValue(false);
|
||||
spyOn(authService, 'isRememberMeSet').and.returnValue(false);
|
||||
spyOn(basicAlfrescoAuthService, 'isRememberMeSet').and.returnValue(false);
|
||||
spyOn(authService, 'isBPMProvider').and.returnValue(true);
|
||||
spyOn(apiService, 'getInstance').and.callThrough();
|
||||
|
||||
expect(authService.isBpmLoggedIn()).toBeFalsy();
|
||||
expect(apiService.getInstance).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('[BPM] should return an BPM ticket after the login done', (done) => {
|
||||
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(() => {
|
||||
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(() => {
|
||||
expect(authService.isLoggedIn()).toBe(true);
|
||||
// cspell: disable-next
|
||||
expect(authService.getTicketBpm()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk');
|
||||
expect(authService.getToken()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk');
|
||||
expect(authService.isBpmLoggedIn()).toBe(true);
|
||||
disposableLogin.unsubscribe();
|
||||
done();
|
||||
@ -255,10 +255,10 @@ describe('AuthenticationService', () => {
|
||||
});
|
||||
|
||||
it('[BPM] should return a ticket undefined after logout', (done) => {
|
||||
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(() => {
|
||||
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(() => {
|
||||
const disposableLogout = authService.logout().subscribe(() => {
|
||||
expect(authService.isLoggedIn()).toBe(false);
|
||||
expect(authService.getTicketBpm()).toBe(null);
|
||||
expect(authService.getToken()).toBe(null);
|
||||
expect(authService.isBpmLoggedIn()).toBe(false);
|
||||
disposableLogout.unsubscribe();
|
||||
disposableLogin.unsubscribe();
|
||||
@ -281,7 +281,7 @@ describe('AuthenticationService', () => {
|
||||
},
|
||||
(err: any) => {
|
||||
expect(err).toBeDefined();
|
||||
expect(authService.getTicketBpm()).toBe(undefined);
|
||||
expect(authService.getToken()).toBe(null);
|
||||
done();
|
||||
});
|
||||
|
||||
@ -291,21 +291,21 @@ describe('AuthenticationService', () => {
|
||||
});
|
||||
|
||||
it('[BPM] should set/get redirectUrl when provider is BPM', () => {
|
||||
authService.setRedirect({ provider: 'BPM', url: 'some-url' });
|
||||
basicAlfrescoAuthService.setRedirect({ provider: 'BPM', url: 'some-url' });
|
||||
|
||||
expect(authService.getRedirect()).toEqual('some-url');
|
||||
expect(basicAlfrescoAuthService.getRedirect()).toEqual('some-url');
|
||||
});
|
||||
|
||||
it('[BPM] should set/get redirectUrl when provider is ECM', () => {
|
||||
authService.setRedirect({ provider: 'ECM', url: 'some-url' });
|
||||
basicAlfrescoAuthService.setRedirect({ provider: 'ECM', url: 'some-url' });
|
||||
|
||||
expect(authService.getRedirect()).toBeNull();
|
||||
expect(basicAlfrescoAuthService.getRedirect()).toBeNull();
|
||||
});
|
||||
|
||||
it('[BPM] should return null as redirectUrl when redirectUrl field is not set', () => {
|
||||
authService.setRedirect(null);
|
||||
basicAlfrescoAuthService.setRedirect(null);
|
||||
|
||||
expect(authService.getRedirect()).toBeNull();
|
||||
expect(basicAlfrescoAuthService.getRedirect()).toBeNull();
|
||||
});
|
||||
|
||||
it('[BPM] should return isECMProvider false', () => {
|
||||
@ -326,11 +326,10 @@ describe('AuthenticationService', () => {
|
||||
beforeEach(() => {
|
||||
appConfigService.config.providers = 'ECM';
|
||||
appConfigService.load();
|
||||
apiService.reset();
|
||||
});
|
||||
|
||||
it('[ECM] should save the remember me cookie as a session cookie after successful login', (done) => {
|
||||
const disposableLogin = authService.login('fake-username', 'fake-password', false).subscribe(() => {
|
||||
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password', false).subscribe(() => {
|
||||
expect(cookie['ALFRESCO_REMEMBER_ME']).not.toBeUndefined();
|
||||
expect(cookie['ALFRESCO_REMEMBER_ME'].expiration).toBeNull();
|
||||
disposableLogin.unsubscribe();
|
||||
@ -345,7 +344,7 @@ describe('AuthenticationService', () => {
|
||||
});
|
||||
|
||||
it('[ECM] should save the remember me cookie as a persistent cookie after successful login', (done) => {
|
||||
const disposableLogin = authService.login('fake-username', 'fake-password', true).subscribe(() => {
|
||||
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password', true).subscribe(() => {
|
||||
expect(cookie['ALFRESCO_REMEMBER_ME']).not.toBeUndefined();
|
||||
expect(cookie['ALFRESCO_REMEMBER_ME'].expiration).not.toBeNull();
|
||||
disposableLogin.unsubscribe();
|
||||
@ -361,7 +360,7 @@ describe('AuthenticationService', () => {
|
||||
});
|
||||
|
||||
it('[ECM] should not save the remember me cookie after failed login', (done) => {
|
||||
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(
|
||||
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(
|
||||
() => {},
|
||||
() => {
|
||||
expect(cookie['ALFRESCO_REMEMBER_ME']).toBeUndefined();
|
||||
@ -390,15 +389,14 @@ describe('AuthenticationService', () => {
|
||||
beforeEach(() => {
|
||||
appConfigService.config.providers = 'ALL';
|
||||
appConfigService.load();
|
||||
apiService.reset();
|
||||
});
|
||||
|
||||
it('[ALL] should return both ECM and BPM tickets after the login done', (done) => {
|
||||
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(() => {
|
||||
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(() => {
|
||||
expect(authService.isLoggedIn()).toBe(true);
|
||||
expect(authService.getTicketEcm()).toEqual('fake-post-ticket');
|
||||
expect(basicAlfrescoAuthService.getTicketEcm()).toEqual('fake-post-ticket');
|
||||
// cspell: disable-next
|
||||
expect(authService.getTicketBpm()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk');
|
||||
expect(basicAlfrescoAuthService.getTicketBpm()).toEqual('Basic ZmFrZS11c2VybmFtZTpmYWtlLXBhc3N3b3Jk');
|
||||
expect(authService.isBpmLoggedIn()).toBe(true);
|
||||
expect(authService.isEcmLoggedIn()).toBe(true);
|
||||
disposableLogin.unsubscribe();
|
||||
@ -417,13 +415,13 @@ describe('AuthenticationService', () => {
|
||||
});
|
||||
|
||||
it('[ALL] should return login fail if only ECM call fail', (done) => {
|
||||
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(
|
||||
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(
|
||||
() => {},
|
||||
() => {
|
||||
expect(authService.isLoggedIn()).toBe(false, 'isLoggedIn');
|
||||
expect(authService.getTicketEcm()).toBe(null, 'getTicketEcm');
|
||||
expect(authService.getToken()).toBe(null, 'getTicketEcm');
|
||||
// cspell: disable-next
|
||||
expect(authService.getTicketBpm()).toBe(null, 'getTicketBpm');
|
||||
expect(authService.getToken()).toBe(null, 'getTicketBpm');
|
||||
expect(authService.isEcmLoggedIn()).toBe(false, 'isEcmLoggedIn');
|
||||
disposableLogin.unsubscribe();
|
||||
done();
|
||||
@ -439,12 +437,12 @@ describe('AuthenticationService', () => {
|
||||
});
|
||||
|
||||
it('[ALL] should return login fail if only BPM call fail', (done) => {
|
||||
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(
|
||||
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(
|
||||
() => {},
|
||||
() => {
|
||||
expect(authService.isLoggedIn()).toBe(false);
|
||||
expect(authService.getTicketEcm()).toBe(null);
|
||||
expect(authService.getTicketBpm()).toBe(null);
|
||||
expect(authService.getToken()).toBe(null);
|
||||
expect(authService.getToken()).toBe(null);
|
||||
expect(authService.isBpmLoggedIn()).toBe(false);
|
||||
disposableLogin.unsubscribe();
|
||||
done();
|
||||
@ -462,12 +460,12 @@ describe('AuthenticationService', () => {
|
||||
});
|
||||
|
||||
it('[ALL] should return ticket undefined when the credentials are wrong', (done) => {
|
||||
const disposableLogin = authService.login('fake-username', 'fake-password').subscribe(
|
||||
const disposableLogin = basicAlfrescoAuthService.login('fake-username', 'fake-password').subscribe(
|
||||
() => {},
|
||||
() => {
|
||||
expect(authService.isLoggedIn()).toBe(false);
|
||||
expect(authService.getTicketEcm()).toBe(null);
|
||||
expect(authService.getTicketBpm()).toBe(null);
|
||||
expect(authService.getToken()).toBe(null);
|
||||
expect(authService.getToken()).toBe(null);
|
||||
expect(authService.isBpmLoggedIn()).toBe(false);
|
||||
expect(authService.isEcmLoggedIn()).toBe(false);
|
||||
disposableLogin.unsubscribe();
|
||||
@ -483,30 +481,6 @@ describe('AuthenticationService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('[ALL] should set/get redirectUrl when provider is ALL', () => {
|
||||
authService.setRedirect({ provider: 'ALL', url: 'some-url' });
|
||||
|
||||
expect(authService.getRedirect()).toEqual('some-url');
|
||||
});
|
||||
|
||||
it('[ALL] should set/get redirectUrl when provider is BPM', () => {
|
||||
authService.setRedirect({ provider: 'BPM', url: 'some-url' });
|
||||
|
||||
expect(authService.getRedirect()).toEqual('some-url');
|
||||
});
|
||||
|
||||
it('[ALL] should set/get redirectUrl when provider is ECM', () => {
|
||||
authService.setRedirect({ provider: 'ECM', url: 'some-url' });
|
||||
|
||||
expect(authService.getRedirect()).toEqual('some-url');
|
||||
});
|
||||
|
||||
it('[ALL] should return null as redirectUrl when redirectUrl field is not set', () => {
|
||||
authService.setRedirect(null);
|
||||
|
||||
expect(authService.getRedirect()).toBeNull();
|
||||
});
|
||||
|
||||
it('[ALL] should return isECMProvider false', () => {
|
||||
expect(authService.isECMProvider()).toBe(false);
|
||||
});
|
||||
@ -519,4 +493,22 @@ describe('AuthenticationService', () => {
|
||||
expect(authService.isALLProvider()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUsername', () => {
|
||||
it('should get the username of the authenticated user if isOAuth is true', () => {
|
||||
spyOn(authService, 'isOauth').and.returnValue(true);
|
||||
spyOn(oidcAuthenticationService, 'getUsername').and.returnValue('mike.portnoy');
|
||||
const username = authService.getUsername();
|
||||
expect(username).toEqual('mike.portnoy');
|
||||
});
|
||||
|
||||
it('should get the username of the authenticated user if isOAuth is false', () => {
|
||||
spyOn(authService, 'isOauth').and.returnValue(false);
|
||||
spyOn(oidcAuthenticationService, 'getUsername').and.returnValue('mike.portnoy');
|
||||
spyOn(basicAlfrescoAuthService, 'getUsername').and.returnValue('john.petrucci');
|
||||
const username = authService.getUsername();
|
||||
expect(username).toEqual('john.petrucci');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -15,184 +15,195 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable, from } from 'rxjs';
|
||||
import { AppConfigValues } from '../../app-config/app-config.service';
|
||||
import { map, catchError, tap } from 'rxjs/operators';
|
||||
import { JwtHelperService } from './jwt-helper.service';
|
||||
import { StorageService } from '../../common/services/storage.service';
|
||||
import { BaseAuthenticationService } from './base-authentication.service';
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { OidcAuthenticationService } from './oidc-authentication.service';
|
||||
import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
|
||||
import { Observable, Subject, from } from 'rxjs';
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
import { AuthenticationServiceInterface } from '../interfaces/authentication-service.interface';
|
||||
import ee from 'event-emitter';
|
||||
import { RedirectAuthService } from '../oidc/redirect-auth.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthenticationService extends BaseAuthenticationService {
|
||||
private storageService = inject(StorageService);
|
||||
readonly supportCodeFlow = false;
|
||||
export class AuthenticationService implements AuthenticationServiceInterface, ee.Emitter {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.alfrescoApi.alfrescoApiInitialized.subscribe(() => {
|
||||
this.alfrescoApi.getInstance().reply('logged-in', () => {
|
||||
this.onLogin.next();
|
||||
});
|
||||
});
|
||||
}
|
||||
onLogin: Subject<any> = new Subject<any>();
|
||||
onLogout: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Checks if the user logged in.
|
||||
*
|
||||
* @returns True if logged in, false otherwise
|
||||
*/
|
||||
isLoggedIn(): boolean {
|
||||
if (!this.isOauth() && this.cookie.isEnabled() && !this.isRememberMeSet()) {
|
||||
return false;
|
||||
}
|
||||
return this.alfrescoApi.getInstance().isLoggedIn();
|
||||
}
|
||||
constructor(
|
||||
private injector: Injector,
|
||||
private redirectAuthService: RedirectAuthService
|
||||
) {
|
||||
this.redirectAuthService.onLogin.subscribe(
|
||||
(value) => this.onLogin.next(value)
|
||||
);
|
||||
|
||||
isLoggedInWith(provider: string): boolean {
|
||||
if (provider === 'BPM') {
|
||||
return this.isBpmLoggedIn();
|
||||
} else if (provider === 'ECM') {
|
||||
return this.isEcmLoggedIn();
|
||||
this.basicAlfrescoAuthService.onLogin.subscribe(
|
||||
(value) => this.onLogin.next(value)
|
||||
);
|
||||
|
||||
if (this.isOauth()) {
|
||||
this.oidcAuthenticationService.onLogout.subscribe(
|
||||
(value) => this.onLogout.next(value)
|
||||
);
|
||||
} else {
|
||||
return this.isLoggedIn();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the provider support OAuth?
|
||||
*
|
||||
* @returns True if supported, false otherwise
|
||||
*/
|
||||
isOauth(): boolean {
|
||||
return this.alfrescoApi.getInstance().isOauthConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the user in.
|
||||
*
|
||||
* @param username Username for the login
|
||||
* @param password Password for the login
|
||||
* @param rememberMe Stores the user's login details if true
|
||||
* @returns Object with auth type ("ECM", "BPM" or "ALL") and auth ticket
|
||||
*/
|
||||
login(username: string, password: string, rememberMe: boolean = false): Observable<{ type: string; ticket: any }> {
|
||||
return from(this.alfrescoApi.getInstance().login(username, password)).pipe(
|
||||
map((response: any) => {
|
||||
this.saveRememberMeCookie(rememberMe);
|
||||
this.onLogin.next(response);
|
||||
return {
|
||||
type: this.appConfig.get(AppConfigValues.PROVIDERS),
|
||||
ticket: response
|
||||
};
|
||||
}),
|
||||
catchError((err) => this.handleError(err))
|
||||
this.basicAlfrescoAuthService.onLogout.subscribe(
|
||||
(value) => this.onLogout.next(value)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the user in with SSO
|
||||
*/
|
||||
ssoImplicitLogin() {
|
||||
this.alfrescoApi.getInstance().implicitLogin();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Logs the user out.
|
||||
*
|
||||
* @returns Response event called when logout is complete
|
||||
*/
|
||||
logout() {
|
||||
return from(this.callApiLogout()).pipe(
|
||||
tap((response) => {
|
||||
this.onLogout.next(response);
|
||||
return response;
|
||||
}),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
get on(): ee.EmitterMethod {
|
||||
return this.isOauth() ? this.oidcAuthenticationService.on : this.basicAlfrescoAuthService.on;
|
||||
}
|
||||
|
||||
private callApiLogout(): Promise<any> {
|
||||
if (this.alfrescoApi.getInstance()) {
|
||||
return this.alfrescoApi.getInstance().logout();
|
||||
}
|
||||
return Promise.resolve();
|
||||
get off(): ee.EmitterMethod {
|
||||
return this.isOauth() ? this.oidcAuthenticationService.off : this.basicAlfrescoAuthService.off;
|
||||
}
|
||||
|
||||
get once(): ee.EmitterMethod {
|
||||
return this.isOauth() ? this.oidcAuthenticationService.once : this.basicAlfrescoAuthService.once;
|
||||
}
|
||||
|
||||
get emit(): (type: string, ...args: any[]) => void {
|
||||
return this.isOauth() ? this.oidcAuthenticationService.emit : this.basicAlfrescoAuthService.emit;
|
||||
}
|
||||
|
||||
get onError(): Observable<any> {
|
||||
return this.isOauth() ? this.oidcAuthenticationService.onError : this.basicAlfrescoAuthService.onError;
|
||||
}
|
||||
|
||||
addTokenToHeader(requestUrl: string, headersArg?: HttpHeaders): Observable<HttpHeaders> {
|
||||
return this.isOauth() ? this.oidcAuthenticationService.addTokenToHeader(requestUrl, headersArg) : this.basicAlfrescoAuthService.addTokenToHeader(requestUrl, headersArg);
|
||||
}
|
||||
|
||||
isECMProvider(): boolean {
|
||||
return this.isOauth() ? this.oidcAuthenticationService.isECMProvider() : this.basicAlfrescoAuthService.isECMProvider();
|
||||
}
|
||||
|
||||
isBPMProvider(): boolean {
|
||||
return this.isOauth() ? this.oidcAuthenticationService.isBPMProvider() : this.basicAlfrescoAuthService.isBPMProvider();
|
||||
}
|
||||
|
||||
isALLProvider(): boolean {
|
||||
return this.isOauth() ? this.oidcAuthenticationService.isALLProvider() : this.basicAlfrescoAuthService.isALLProvider();
|
||||
}
|
||||
|
||||
private get oidcAuthenticationService(): OidcAuthenticationService {
|
||||
return this.injector.get(OidcAuthenticationService);
|
||||
}
|
||||
|
||||
private get basicAlfrescoAuthService(): BasicAlfrescoAuthService {
|
||||
return this.injector.get(BasicAlfrescoAuthService);
|
||||
}
|
||||
|
||||
getToken(): string {
|
||||
if (this.isOauth()) {
|
||||
return this.oidcAuthenticationService.getToken();
|
||||
} else {
|
||||
return this.basicAlfrescoAuthService.getToken();
|
||||
}
|
||||
}
|
||||
|
||||
isLoggedIn(): boolean {
|
||||
if (this.isOauth()) {
|
||||
return this.oidcAuthenticationService.isLoggedIn();
|
||||
} else {
|
||||
return this.basicAlfrescoAuthService.isLoggedIn();
|
||||
}
|
||||
}
|
||||
|
||||
logout(): Observable<any> {
|
||||
if (this.isOauth()) {
|
||||
return this.oidcAuthenticationService.logout();
|
||||
} else {
|
||||
return from(this.basicAlfrescoAuthService.logout());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is logged in on an ECM provider.
|
||||
*
|
||||
* @returns True if logged in, false otherwise
|
||||
*/
|
||||
isEcmLoggedIn(): boolean {
|
||||
if (this.isECMProvider() || this.isALLProvider()) {
|
||||
if (!this.isOauth() && this.cookie.isEnabled() && !this.isRememberMeSet()) {
|
||||
return false;
|
||||
if (this.isOauth()) {
|
||||
return this.oidcAuthenticationService.isEcmLoggedIn();
|
||||
} else {
|
||||
return this.basicAlfrescoAuthService.isEcmLoggedIn();
|
||||
}
|
||||
return this.alfrescoApi.getInstance().isEcmLoggedIn();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is logged in on a BPM provider.
|
||||
*
|
||||
* @returns True if logged in, false otherwise
|
||||
*/
|
||||
isBpmLoggedIn(): boolean {
|
||||
if (this.isBPMProvider() || this.isALLProvider()) {
|
||||
if (!this.isOauth() && this.cookie.isEnabled() && !this.isRememberMeSet()) {
|
||||
return false;
|
||||
if (this.isOauth()) {
|
||||
return this.oidcAuthenticationService.isBpmLoggedIn();
|
||||
} else {
|
||||
return this.basicAlfrescoAuthService.isBpmLoggedIn();
|
||||
}
|
||||
return this.alfrescoApi.getInstance().isBpmLoggedIn();
|
||||
}
|
||||
return false;
|
||||
|
||||
reset(): void {
|
||||
if (this.isOauth()) {
|
||||
return this.oidcAuthenticationService.reset();
|
||||
} else {
|
||||
return this.basicAlfrescoAuthService.reset();
|
||||
}
|
||||
}
|
||||
|
||||
login(username: string, password: string, rememberMe?: boolean): Observable<{ type: string; ticket: any }> {
|
||||
if (this.isOauth()) {
|
||||
return this.oidcAuthenticationService.loginWithPassword(username, password);
|
||||
} else {
|
||||
return this.basicAlfrescoAuthService.login(username, password, rememberMe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ECM username.
|
||||
*
|
||||
* @returns The ECM username
|
||||
* @returns the username of the authenticated user
|
||||
*/
|
||||
getUsername(): string {
|
||||
if (this.isOauth()) {
|
||||
return this.oidcAuthenticationService.getUsername();
|
||||
} else {
|
||||
return this.basicAlfrescoAuthService.getUsername();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @returns the logged username
|
||||
*/
|
||||
getEcmUsername(): string {
|
||||
return this.alfrescoApi.getInstance().getEcmUsername();
|
||||
if (this.isOauth()) {
|
||||
return this.oidcAuthenticationService.getUsername();
|
||||
} else {
|
||||
return this.basicAlfrescoAuthService.getEcmUsername();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the BPM username
|
||||
*
|
||||
* @returns The BPM username
|
||||
* @deprecated
|
||||
* @returns the logged username
|
||||
*/
|
||||
getBpmUsername(): string {
|
||||
return this.alfrescoApi.getInstance().getBpmUsername();
|
||||
if (this.isOauth()) {
|
||||
return this.oidcAuthenticationService.getUsername();
|
||||
} else {
|
||||
return this.basicAlfrescoAuthService.getBpmUsername();
|
||||
}
|
||||
}
|
||||
|
||||
isImplicitFlow(): boolean {
|
||||
return !!this.appConfig.oauth2?.implicitFlow;
|
||||
getAuthHeaders(requestUrl: string, headers: HttpHeaders): HttpHeaders {
|
||||
if (this.isOauth()) {
|
||||
return this.oidcAuthenticationService.getAuthHeaders(requestUrl, headers);
|
||||
} else {
|
||||
return this.basicAlfrescoAuthService.getAuthHeaders(requestUrl, headers);
|
||||
}
|
||||
}
|
||||
|
||||
isAuthCodeFlow(): boolean {
|
||||
return false;
|
||||
isOauth(): boolean {
|
||||
return this.basicAlfrescoAuthService.isOauth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the auth token.
|
||||
*
|
||||
* @returns Auth token string
|
||||
*/
|
||||
getToken(): string {
|
||||
return this.storageService.getItem(JwtHelperService.USER_ACCESS_TOKEN);
|
||||
isKerberosEnabled(): boolean {
|
||||
return !this.isOauth() ? this.basicAlfrescoAuthService.isKerberosEnabled() : false;
|
||||
}
|
||||
|
||||
reset() { }
|
||||
|
||||
once(event: string): Observable<any> {
|
||||
const alfrescoApiEvent = event === 'token_received' ? 'token_issued' : event;
|
||||
return new Observable((subscriber) => {
|
||||
this.alfrescoApi.getInstance().oauth2Auth.once(alfrescoApiEvent, () => subscriber.next());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -15,76 +15,70 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { PeopleApi, UserProfileApi } from '@alfresco/js-api';
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
import { RedirectionModel } from '../models/redirection.model';
|
||||
import { Observable, Observer, ReplaySubject, throwError } from 'rxjs';
|
||||
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
|
||||
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||
import { CookieService } from '../../common/services/cookie.service';
|
||||
import { LogService } from '../../common/services/log.service';
|
||||
import { inject } from '@angular/core';
|
||||
import { AuthenticationServiceInterface } from '../interfaces/authentication-service.interface';
|
||||
import ee from 'event-emitter';
|
||||
|
||||
const REMEMBER_ME_COOKIE_KEY = 'ALFRESCO_REMEMBER_ME';
|
||||
const REMEMBER_ME_UNTIL = 1000 * 60 * 60 * 24 * 30;
|
||||
export abstract class BaseAuthenticationService implements AuthenticationServiceInterface, ee.Emitter {
|
||||
|
||||
export abstract class BaseAuthenticationService {
|
||||
protected alfrescoApi = inject(AlfrescoApiService);
|
||||
protected appConfig = inject(AppConfigService);
|
||||
protected cookie = inject(CookieService);
|
||||
private logService = inject(LogService);
|
||||
on: ee.EmitterMethod;
|
||||
off: ee.EmitterMethod;
|
||||
once: ee.EmitterMethod;
|
||||
emit: (type: string, ...args: any[]) => void;
|
||||
|
||||
protected bearerExcludedUrls: readonly string[] = ['resources/', 'assets/', 'auth/realms', 'idp/'];
|
||||
protected redirectUrl: RedirectionModel = null;
|
||||
|
||||
onError = new ReplaySubject<any>(1);
|
||||
onLogin = new ReplaySubject<any>(1);
|
||||
onLogout = new ReplaySubject<any>(1);
|
||||
|
||||
private _peopleApi: PeopleApi;
|
||||
get peopleApi(): PeopleApi {
|
||||
this._peopleApi = this._peopleApi ?? new PeopleApi(this.alfrescoApi.getInstance());
|
||||
return this._peopleApi;
|
||||
constructor(
|
||||
protected appConfig: AppConfigService,
|
||||
protected cookie: CookieService,
|
||||
private logService: LogService
|
||||
) {
|
||||
ee(this);
|
||||
}
|
||||
|
||||
private _profileApi: UserProfileApi;
|
||||
get profileApi(): UserProfileApi {
|
||||
this._profileApi = this._profileApi ?? new UserProfileApi(this.alfrescoApi.getInstance());
|
||||
return this._profileApi;
|
||||
}
|
||||
abstract getAuthHeaders(requestUrl: string, header: HttpHeaders): HttpHeaders;
|
||||
|
||||
abstract readonly supportCodeFlow: boolean;
|
||||
abstract getToken(): string;
|
||||
abstract isLoggedIn(): boolean;
|
||||
abstract isLoggedInWith(provider: string): boolean;
|
||||
abstract isOauth(): boolean;
|
||||
abstract login(username: string, password: string, rememberMe?: boolean): Observable<{ type: string; ticket: any }>;
|
||||
abstract ssoImplicitLogin(): void;
|
||||
abstract logout(): Observable<any>;
|
||||
abstract isEcmLoggedIn(): boolean;
|
||||
abstract isBpmLoggedIn(): boolean;
|
||||
abstract getEcmUsername(): string;
|
||||
abstract getBpmUsername(): string;
|
||||
abstract reset(): void;
|
||||
abstract once(event: string): Observable<any>;
|
||||
|
||||
getBearerExcludedUrls(): readonly string[] {
|
||||
return this.bearerExcludedUrls;
|
||||
}
|
||||
abstract isLoggedIn(): boolean;
|
||||
|
||||
abstract logout(): any;
|
||||
|
||||
abstract isEcmLoggedIn(): boolean;
|
||||
|
||||
abstract isBpmLoggedIn(): boolean;
|
||||
|
||||
abstract reset(): void;
|
||||
|
||||
abstract getEcmUsername(): string;
|
||||
|
||||
abstract getBpmUsername(): string;
|
||||
|
||||
/**
|
||||
* Adds the auth token to an HTTP header using the 'bearer' scheme.
|
||||
*
|
||||
* @param requestUrl the request url
|
||||
* @param headersArg Header that will receive the token
|
||||
* @returns The new header with the token added
|
||||
*/
|
||||
addTokenToHeader(headersArg?: HttpHeaders): Observable<HttpHeaders> {
|
||||
addTokenToHeader(requestUrl: string, headersArg?: HttpHeaders): Observable<HttpHeaders> {
|
||||
return new Observable((observer: Observer<any>) => {
|
||||
let headers = headersArg;
|
||||
if (!headers) {
|
||||
headers = new HttpHeaders();
|
||||
}
|
||||
try {
|
||||
const header = this.getAuthHeaders(headers);
|
||||
|
||||
const header = this.getAuthHeaders(requestUrl, headers);
|
||||
|
||||
observer.next(header);
|
||||
observer.complete();
|
||||
@ -94,50 +88,9 @@ export abstract class BaseAuthenticationService {
|
||||
});
|
||||
}
|
||||
|
||||
private getAuthHeaders(header: HttpHeaders): HttpHeaders {
|
||||
const authType = this.appConfig.get<string>(AppConfigValues.AUTHTYPE, 'BASIC');
|
||||
|
||||
switch (authType) {
|
||||
case 'OAUTH':
|
||||
return this.addBearerToken(header);
|
||||
case 'BASIC':
|
||||
return this.addBasicAuth(header);
|
||||
default:
|
||||
return header;
|
||||
}
|
||||
}
|
||||
|
||||
private addBearerToken(header: HttpHeaders): HttpHeaders {
|
||||
const token: string = this.getToken();
|
||||
|
||||
if (!token) {
|
||||
return header;
|
||||
}
|
||||
|
||||
return header.set('Authorization', 'bearer ' + token);
|
||||
}
|
||||
|
||||
private addBasicAuth(header: HttpHeaders): HttpHeaders {
|
||||
const ticket: string = this.getTicketEcmBase64();
|
||||
|
||||
if (!ticket) {
|
||||
return header;
|
||||
}
|
||||
|
||||
return header.set('Authorization', ticket);
|
||||
}
|
||||
|
||||
isPublicUrl(): boolean {
|
||||
return this.alfrescoApi.getInstance().isPublicUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the provider support ECM?
|
||||
*
|
||||
* @returns True if supported, false otherwise
|
||||
*/
|
||||
isECMProvider(): boolean {
|
||||
return this.alfrescoApi.getInstance().isEcmConfiguration();
|
||||
const provider = this.appConfig.get('providers') as string;
|
||||
return provider && provider.toUpperCase() === 'ECM';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -146,7 +99,12 @@ export abstract class BaseAuthenticationService {
|
||||
* @returns True if supported, false otherwise
|
||||
*/
|
||||
isBPMProvider(): boolean {
|
||||
return this.alfrescoApi.getInstance().isBpmConfiguration();
|
||||
const provider = this.appConfig.get('providers');
|
||||
if (provider && (typeof provider === 'string' || provider instanceof String)) {
|
||||
return provider.toUpperCase() === 'BPM';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,38 +113,13 @@ export abstract class BaseAuthenticationService {
|
||||
* @returns True if both are supported, false otherwise
|
||||
*/
|
||||
isALLProvider(): boolean {
|
||||
return this.alfrescoApi.getInstance().isEcmBpmConfiguration();
|
||||
const provider = this.appConfig.get('providers') as string;
|
||||
return provider && provider.toUpperCase() === 'ALL';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ECM ticket stored in the Storage.
|
||||
*
|
||||
* @returns The ticket or `null` if none was found
|
||||
*/
|
||||
getTicketEcm(): string | null {
|
||||
return this.alfrescoApi.getInstance()?.getTicketEcm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the BPM ticket stored in the Storage.
|
||||
*
|
||||
* @returns The ticket or `null` if none was found
|
||||
*/
|
||||
getTicketBpm(): string | null {
|
||||
return this.alfrescoApi.getInstance()?.getTicketBpm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the BPM ticket from the Storage in Base 64 format.
|
||||
*
|
||||
* @returns The ticket or `null` if none was found
|
||||
*/
|
||||
getTicketEcmBase64(): string | null {
|
||||
const ticket = this.alfrescoApi.getInstance()?.getTicketEcm();
|
||||
if (ticket) {
|
||||
return 'Basic ' + btoa(ticket);
|
||||
}
|
||||
return null;
|
||||
isOauthConfiguration(): boolean {
|
||||
const authType = this.appConfig.get('authType') as string;
|
||||
return authType === 'OAUTH';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,64 +129,12 @@ export abstract class BaseAuthenticationService {
|
||||
* @returns Object representing the error message
|
||||
*/
|
||||
handleError(error: any): Observable<any> {
|
||||
this.onError.next(error || 'Server error');
|
||||
this.logService.error('Error when logging in', error);
|
||||
return throwError(error || 'Server error');
|
||||
}
|
||||
|
||||
/**
|
||||
* Does kerberos enabled?
|
||||
*
|
||||
* @returns True if enabled, false otherwise
|
||||
*/
|
||||
isKerberosEnabled(): boolean {
|
||||
return this.appConfig.get<boolean>(AppConfigValues.AUTH_WITH_CREDENTIALS, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the "remember me" cookie as either a long-life cookie or a session cookie.
|
||||
*
|
||||
* @param rememberMe Enables a long-life cookie
|
||||
*/
|
||||
saveRememberMeCookie(rememberMe: boolean): void {
|
||||
let expiration = null;
|
||||
|
||||
if (rememberMe) {
|
||||
expiration = new Date();
|
||||
const time = expiration.getTime();
|
||||
const expireTime = time + REMEMBER_ME_UNTIL;
|
||||
expiration.setTime(expireTime);
|
||||
}
|
||||
this.cookie.setItem(REMEMBER_ME_COOKIE_KEY, '1', expiration, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the "remember me" cookie was set or not.
|
||||
*
|
||||
* @returns True if set, false otherwise
|
||||
*/
|
||||
isRememberMeSet(): boolean {
|
||||
return this.cookie.getItem(REMEMBER_ME_COOKIE_KEY) !== null;
|
||||
}
|
||||
|
||||
setRedirect(url?: RedirectionModel) {
|
||||
this.redirectUrl = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL to redirect to after login.
|
||||
*
|
||||
* @returns The redirect URL
|
||||
*/
|
||||
getRedirect(): string {
|
||||
const provider = this.appConfig.get<string>(AppConfigValues.PROVIDERS);
|
||||
return this.hasValidRedirection(provider) ? this.redirectUrl.url : null;
|
||||
}
|
||||
|
||||
private hasValidRedirection(provider: string): boolean {
|
||||
return this.redirectUrl && (this.redirectUrl.provider === provider || this.hasSelectedProviderAll(provider));
|
||||
}
|
||||
|
||||
private hasSelectedProviderAll(provider: string): boolean {
|
||||
return this.redirectUrl && (this.redirectUrl.provider === 'ALL' || provider === 'ALL');
|
||||
isOauth(): boolean {
|
||||
return this.appConfig.get(AppConfigValues.AUTHTYPE) === 'OAUTH';
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import { IdentityRoleModel } from '../models/identity-role.model';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AdfHttpClient } from '../../../../api/src';
|
||||
import { StorageService } from '../../common/services/storage.service';
|
||||
|
||||
describe('IdentityUserService', () => {
|
||||
|
||||
@ -46,6 +47,7 @@ describe('IdentityUserService', () => {
|
||||
{ id: 'id-5', name: 'MOCK-ROLE-2'}
|
||||
];
|
||||
|
||||
let storageService: StorageService;
|
||||
let service: IdentityUserService;
|
||||
let adfHttpClient: AdfHttpClient;
|
||||
let requestSpy: jasmine.Spy;
|
||||
@ -57,18 +59,14 @@ describe('IdentityUserService', () => {
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
storageService = TestBed.inject(StorageService);
|
||||
service = TestBed.inject(IdentityUserService);
|
||||
adfHttpClient = TestBed.inject(AdfHttpClient);
|
||||
requestSpy = spyOn(adfHttpClient, 'request');
|
||||
|
||||
const store = {};
|
||||
|
||||
spyOn(localStorage, 'getItem').and.callFake( (key: string): string => store[key] || null);
|
||||
spyOn(localStorage, 'setItem').and.callFake((key: string, value: string): string => store[key] = value);
|
||||
});
|
||||
|
||||
it('should fetch identity user info from Jwt id token', () => {
|
||||
localStorage.setItem(JwtHelperService.USER_ID_TOKEN, mockToken);
|
||||
storageService.setItem(JwtHelperService.USER_ID_TOKEN, mockToken);
|
||||
const user = service.getCurrentUserInfo();
|
||||
expect(user).toBeDefined();
|
||||
expect(user.firstName).toEqual('John');
|
||||
@ -78,7 +76,7 @@ describe('IdentityUserService', () => {
|
||||
});
|
||||
|
||||
it('should fallback on Jwt access token for identity user info', () => {
|
||||
localStorage.setItem(JwtHelperService.USER_ACCESS_TOKEN, mockToken);
|
||||
storageService.setItem(JwtHelperService.USER_ACCESS_TOKEN, mockToken);
|
||||
const user = service.getCurrentUserInfo();
|
||||
expect(user).toBeDefined();
|
||||
expect(user.firstName).toEqual('John');
|
||||
|
194
lib/core/src/lib/auth/services/oidc-authentication.service.ts
Normal file
194
lib/core/src/lib/auth/services/oidc-authentication.service.ts
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { CardViewKeyValuePairsItemModel } from '../../models/card-view-keyvaluepairs.model';
|
||||
import { CardViewKeyValuePairsItemComponent } from './card-view-keyvaluepairsitem.component';
|
||||
@ -122,57 +122,5 @@ describe('CardViewKeyValuePairsItemComponent', () => {
|
||||
expect(cardViewUpdateService.update).toHaveBeenCalled();
|
||||
expect(component.property.value.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should update property on input blur', waitForAsync(() => {
|
||||
spyOn(cardViewUpdateService, 'update');
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
|
||||
const addButton = fixture.debugElement.query(By.css('.adf-card-view__key-value-pairs__add-btn'));
|
||||
addButton.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
|
||||
const nameInput = fixture.debugElement.query(By.css(`[data-automation-id="card-${component.property.key}-name-input-0"]`));
|
||||
const valueInput = fixture.debugElement.query(By.css(`[data-automation-id="card-${component.property.key}-value-input-0"]`));
|
||||
|
||||
nameInput.nativeElement.value = mockData[0].name;
|
||||
nameInput.nativeElement.dispatchEvent(new Event('input'));
|
||||
valueInput.nativeElement.value = mockData[0].value;
|
||||
valueInput.nativeElement.dispatchEvent(new Event('input'));
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
valueInput.triggerEventHandler('blur', null);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(cardViewUpdateService.update).toHaveBeenCalled();
|
||||
expect(JSON.stringify(component.property.value)).toBe(JSON.stringify(mockData));
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not update property if at least one input is empty on blur', waitForAsync(() => {
|
||||
spyOn(cardViewUpdateService, 'update');
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
|
||||
const addButton = fixture.debugElement.query(By.css('.adf-card-view__key-value-pairs__add-btn'));
|
||||
addButton.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
|
||||
const valueInput = fixture.debugElement.query(By.css(`[data-automation-id="card-${component.property.key}-value-input-0"]`));
|
||||
|
||||
valueInput.nativeElement.value = mockData[0].value;
|
||||
valueInput.nativeElement.dispatchEvent(new Event('input'));
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
valueInput.triggerEventHandler('blur', null);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(cardViewUpdateService.update).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -19,12 +19,14 @@ import { Injectable } from '@angular/core';
|
||||
import { AppConfigService, Status } from '../../app-config/app-config.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ExtensionService } from '@alfresco/adf-extensions';
|
||||
|
||||
@Injectable()
|
||||
export class AppConfigServiceMock extends AppConfigService {
|
||||
|
||||
config: any = {
|
||||
application: {
|
||||
name: 'Alfresco ADF Application'
|
||||
name: 'Alfresco ADF Application',
|
||||
storagePrefix: 'ADF_APP'
|
||||
},
|
||||
ecmHost: 'http://{hostname}{:port}/ecm',
|
||||
bpmHost: 'http://{hostname}{:port}/bpm',
|
||||
@ -35,11 +37,12 @@ export class AppConfigServiceMock extends AppConfigService {
|
||||
super(http, extensionService);
|
||||
}
|
||||
|
||||
load(): Promise<any> {
|
||||
load(callback?: (...args: any[]) => any): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
this.status = Status.LOADED;
|
||||
this.onDataLoaded(this.config);
|
||||
callback?.();
|
||||
resolve(this.config);
|
||||
this.onDataLoaded();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import { StorageService } from '../../common/services/storage.service';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { AppConfigServiceMock } from '../mock/app-config.service.mock';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreModule } from '../../core.module';
|
||||
|
||||
describe('StorageService', () => {
|
||||
|
||||
@ -34,8 +33,6 @@ describe('StorageService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
@ -87,6 +84,7 @@ describe('StorageService', () => {
|
||||
]
|
||||
});
|
||||
appConfig = TestBed.inject(AppConfigService);
|
||||
|
||||
appConfig.config = {
|
||||
application: {
|
||||
storagePrefix: ''
|
||||
|
@ -81,7 +81,7 @@ export class StorageService {
|
||||
*/
|
||||
removeItem(key: string) {
|
||||
if (this.useLocalStorage) {
|
||||
localStorage.removeItem(this.prefix + key);
|
||||
localStorage.removeItem(`${this.prefix}` + key);
|
||||
} else {
|
||||
delete this.memoryStore[this.prefix + key];
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ export class ObjectUtils {
|
||||
*/
|
||||
static getValue(target: any, key: string): any {
|
||||
|
||||
if (!target) {
|
||||
if (!target || !key) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -53,9 +53,8 @@ import { ExtensionsModule } from '@alfresco/adf-extensions';
|
||||
import { directionalityConfigFactory } from './common/services/directionality-config-factory';
|
||||
import { DirectionalityConfigService } from './common/services/directionality-config.service';
|
||||
import { SearchTextModule } from './search-text/search-text-input.module';
|
||||
import { AdfHttpClient, AlfrescoJsClientsModule } from '@alfresco/adf-core/api';
|
||||
import { AdfHttpClient } from '@alfresco/adf-core/api';
|
||||
import { AuthenticationInterceptor, Authentication } from '@alfresco/adf-core/auth';
|
||||
import { LegacyApiClientModule } from './api-factories/legacy-api-client.module';
|
||||
import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { AuthenticationService } from './auth/services/authentication.service';
|
||||
import { MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar';
|
||||
@ -101,8 +100,6 @@ import { AdfDateTimeFnsAdapter } from './common/utils/datetime-fns-adapter';
|
||||
NotificationHistoryModule,
|
||||
SearchTextModule,
|
||||
BlankPageModule,
|
||||
LegacyApiClientModule,
|
||||
AlfrescoJsClientsModule,
|
||||
HttpClientModule,
|
||||
HttpClientXsrfModule.withOptions({
|
||||
cookieName: 'CSRF-TOKEN',
|
||||
|
@ -37,7 +37,7 @@ export class LogoutDirective implements OnInit {
|
||||
private renderer: Renderer2,
|
||||
private router: Router,
|
||||
private appConfig: AppConfigService,
|
||||
private auth: AuthenticationService) {
|
||||
private authenticationService: AuthenticationService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@ -58,14 +58,14 @@ export class LogoutDirective implements OnInit {
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.auth.logout().subscribe(
|
||||
this.authenticationService.logout().subscribe(
|
||||
() => this.redirectToUri(),
|
||||
() => this.redirectToUri()
|
||||
);
|
||||
}
|
||||
|
||||
redirectToUri() {
|
||||
if (this.enableRedirect && !this.auth.isOauth()) {
|
||||
if (this.enableRedirect && !this.authenticationService.isOauth()) {
|
||||
const redirectRoute = this.getRedirectUri();
|
||||
this.router.navigate([redirectRoute]);
|
||||
}
|
||||
|
@ -16,11 +16,12 @@
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { AuthenticationService } from '../../auth/services/authentication.service';
|
||||
import { LoginDialogPanelComponent } from './login-dialog-panel.component';
|
||||
import { of } from 'rxjs';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { BasicAlfrescoAuthService } from '../../auth/basic-auth/basic-alfresco-auth.service';
|
||||
import { OidcAuthenticationService } from '../../auth/services/oidc-authentication.service';
|
||||
|
||||
describe('LoginDialogPanelComponent', () => {
|
||||
let component: LoginDialogPanelComponent;
|
||||
@ -28,19 +29,23 @@ describe('LoginDialogPanelComponent', () => {
|
||||
let element: HTMLElement;
|
||||
let usernameInput: HTMLInputElement;
|
||||
let passwordInput: HTMLInputElement;
|
||||
let authService: AuthenticationService;
|
||||
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: OidcAuthenticationService, useValue: {}}
|
||||
]
|
||||
});
|
||||
fixture = TestBed.createComponent(LoginDialogPanelComponent);
|
||||
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
|
||||
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
authService = TestBed.inject(AuthenticationService);
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
@ -76,7 +81,7 @@ describe('LoginDialogPanelComponent', () => {
|
||||
expect(event.token.ticket).toBe('ticket');
|
||||
done();
|
||||
});
|
||||
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
|
||||
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
|
||||
loginWithCredentials('fake-username', 'fake-password');
|
||||
});
|
||||
|
||||
|
@ -25,10 +25,11 @@ import { AuthenticationService } from '../../auth/services/authentication.servic
|
||||
import { LoginErrorEvent } from '../models/login-error.event';
|
||||
import { LoginSuccessEvent } from '../models/login-success.event';
|
||||
import { LoginComponent } from './login.component';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||
import { EMPTY, of, throwError } from 'rxjs';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { LogService } from '../../common/services/log.service';
|
||||
import { BasicAlfrescoAuthService } from '../../auth/basic-auth/basic-alfresco-auth.service';
|
||||
import { OidcAuthenticationService } from '../../auth/services/oidc-authentication.service';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
@ -38,7 +39,7 @@ describe('LoginComponent', () => {
|
||||
let router: Router;
|
||||
let userPreferences: UserPreferencesService;
|
||||
let appConfigService: AppConfigService;
|
||||
let alfrescoApiService: AlfrescoApiService;
|
||||
let basicAlfrescoAuthService: BasicAlfrescoAuthService;
|
||||
|
||||
let usernameInput;
|
||||
let passwordInput;
|
||||
@ -60,6 +61,16 @@ describe('LoginComponent', () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CoreTestingModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: OidcAuthenticationService, useValue: {
|
||||
ssoImplicitLogin: () => { },
|
||||
isPublicUrl: () => false,
|
||||
hasValidIdToken: () => false,
|
||||
isLoggedIn: () => false
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
@ -69,11 +80,11 @@ describe('LoginComponent', () => {
|
||||
component.showRememberMe = true;
|
||||
component.showLoginActions = true;
|
||||
|
||||
basicAlfrescoAuthService = TestBed.inject(BasicAlfrescoAuthService);
|
||||
authService = TestBed.inject(AuthenticationService);
|
||||
router = TestBed.inject(Router);
|
||||
userPreferences = TestBed.inject(UserPreferencesService);
|
||||
appConfigService = TestBed.inject(AppConfigService);
|
||||
alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
||||
|
||||
const logService = TestBed.inject(LogService);
|
||||
spyOn(logService, 'error');
|
||||
@ -111,7 +122,7 @@ describe('LoginComponent', () => {
|
||||
});
|
||||
|
||||
it('should redirect to route on successful login', () => {
|
||||
spyOn(authService, 'login').and.returnValue(
|
||||
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(
|
||||
of({ type: 'type', ticket: 'ticket' })
|
||||
);
|
||||
const redirect = '/home';
|
||||
@ -161,10 +172,10 @@ describe('LoginComponent', () => {
|
||||
appConfigService.config = {};
|
||||
appConfigService.config.providers = 'ECM';
|
||||
|
||||
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
|
||||
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
|
||||
const redirect = '/home';
|
||||
component.successRoute = redirect;
|
||||
authService.setRedirect({ provider: 'ECM', url: 'some-route' });
|
||||
basicAlfrescoAuthService.setRedirect({ provider: 'ECM', url: 'some-route' });
|
||||
|
||||
spyOn(router, 'navigateByUrl');
|
||||
|
||||
@ -174,8 +185,7 @@ describe('LoginComponent', () => {
|
||||
|
||||
it('should update user preferences upon login', async () => {
|
||||
spyOn(userPreferences, 'setStoragePrefix').and.callThrough();
|
||||
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
|
||||
spyOn(alfrescoApiService.getInstance(), 'login').and.returnValue(Promise.resolve());
|
||||
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
|
||||
|
||||
component.success.subscribe(() => {
|
||||
expect(userPreferences.setStoragePrefix).toHaveBeenCalledWith('fake-username');
|
||||
@ -206,14 +216,14 @@ describe('LoginComponent', () => {
|
||||
});
|
||||
|
||||
it('should be changed back to the default after a failed login attempt', () => {
|
||||
spyOn(authService, 'login').and.returnValue(throwError('Fake server error'));
|
||||
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(throwError('Fake server error'));
|
||||
loginWithCredentials('fake-wrong-username', 'fake-wrong-password');
|
||||
|
||||
expect(getLoginButtonText()).toEqual('LOGIN.BUTTON.LOGIN');
|
||||
});
|
||||
|
||||
it('should be changed to the "welcome key" after a successful login attempt', () => {
|
||||
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
|
||||
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
|
||||
loginWithCredentials('fake-username', 'fake-password');
|
||||
|
||||
expect(getLoginButtonText()).toEqual('LOGIN.BUTTON.WELCOME');
|
||||
@ -295,12 +305,12 @@ describe('LoginComponent', () => {
|
||||
});
|
||||
|
||||
it('should be taken into consideration during login attempt', fakeAsync(() => {
|
||||
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
|
||||
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
|
||||
component.rememberMe = false;
|
||||
|
||||
loginWithCredentials('fake-username', 'fake-password');
|
||||
|
||||
expect(authService.login).toHaveBeenCalledWith('fake-username', 'fake-password', false);
|
||||
expect(basicAlfrescoAuthService.login).toHaveBeenCalledWith('fake-username', 'fake-password', false);
|
||||
}));
|
||||
});
|
||||
|
||||
@ -469,7 +479,7 @@ describe('LoginComponent', () => {
|
||||
});
|
||||
|
||||
it('should return error with a wrong username', (done) => {
|
||||
spyOn(alfrescoApiService.getInstance(), 'login').and.returnValue(Promise.reject(new Error('login error')));
|
||||
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(throwError(new Error()));
|
||||
|
||||
component.error.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
@ -484,7 +494,7 @@ describe('LoginComponent', () => {
|
||||
});
|
||||
|
||||
it('should return error with a wrong password', (done) => {
|
||||
spyOn(alfrescoApiService.getInstance(), 'login').and.returnValue(Promise.reject(new Error('login error')));
|
||||
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(throwError(new Error()));
|
||||
|
||||
component.error.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
@ -500,7 +510,7 @@ describe('LoginComponent', () => {
|
||||
});
|
||||
|
||||
it('should return error with a wrong username and password', (done) => {
|
||||
spyOn(alfrescoApiService.getInstance(), 'login').and.returnValue(Promise.reject(new Error('login error')));
|
||||
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(throwError(new Error()));
|
||||
|
||||
component.error.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
@ -516,7 +526,7 @@ describe('LoginComponent', () => {
|
||||
});
|
||||
|
||||
it('should return CORS error when server CORS error occurs', (done) => {
|
||||
spyOn(authService, 'login').and.returnValue(throwError({
|
||||
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(throwError({
|
||||
error: {
|
||||
crossDomain: true,
|
||||
message: 'ERROR: the network is offline, Origin is not allowed by Access-Control-Allow-Origin'
|
||||
@ -537,7 +547,7 @@ describe('LoginComponent', () => {
|
||||
});
|
||||
|
||||
it('should return CSRF error when server CSRF error occurs', fakeAsync(() => {
|
||||
spyOn(authService, 'login')
|
||||
spyOn(basicAlfrescoAuthService, 'login')
|
||||
.and.returnValue(throwError({ message: 'ERROR: Invalid CSRF-token', status: 403 }));
|
||||
|
||||
component.error.subscribe(() => {
|
||||
@ -552,7 +562,7 @@ describe('LoginComponent', () => {
|
||||
}));
|
||||
|
||||
it('should return ECM read-only error when error occurs', fakeAsync(() => {
|
||||
spyOn(authService, 'login')
|
||||
spyOn(basicAlfrescoAuthService, 'login')
|
||||
.and.returnValue(
|
||||
throwError(
|
||||
{
|
||||
@ -600,7 +610,7 @@ describe('LoginComponent', () => {
|
||||
});
|
||||
|
||||
it('should return success event after the login have succeeded', (done) => {
|
||||
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
|
||||
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
|
||||
|
||||
expect(component.isError).toBe(false);
|
||||
|
||||
@ -616,7 +626,7 @@ describe('LoginComponent', () => {
|
||||
});
|
||||
|
||||
it('should emit success event after the login has succeeded and discard password', fakeAsync(() => {
|
||||
spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
|
||||
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket' }));
|
||||
|
||||
component.success.subscribe((event) => {
|
||||
fixture.detectChanges();
|
||||
@ -631,7 +641,7 @@ describe('LoginComponent', () => {
|
||||
}));
|
||||
|
||||
it('should emit error event after the login has failed', fakeAsync(() => {
|
||||
spyOn(authService, 'login').and.returnValue(throwError('Fake server error'));
|
||||
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(throwError('Fake server error'));
|
||||
|
||||
component.error.subscribe((error) => {
|
||||
fixture.detectChanges();
|
||||
@ -668,7 +678,7 @@ describe('LoginComponent', () => {
|
||||
});
|
||||
|
||||
it('should emit only the username and not the password as part of the executeSubmit', fakeAsync(() => {
|
||||
spyOn(alfrescoApiService.getInstance(), 'login').and.returnValue(Promise.resolve());
|
||||
spyOn(basicAlfrescoAuthService, 'login').and.returnValue(EMPTY);
|
||||
|
||||
component.executeSubmit.subscribe((res) => {
|
||||
fixture.detectChanges();
|
||||
@ -688,7 +698,6 @@ describe('LoginComponent', () => {
|
||||
beforeEach(() => {
|
||||
appConfigService.config.oauth2 = { implicitFlow: true, silentLogin: false };
|
||||
appConfigService.load();
|
||||
alfrescoApiService.reset();
|
||||
});
|
||||
|
||||
it('should not show login username and password if SSO implicit flow is active', fakeAsync(() => {
|
||||
|
@ -29,6 +29,8 @@ import { AppConfigService, AppConfigValues } from '../../app-config/app-config.s
|
||||
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { BasicAlfrescoAuthService } from '../../auth/basic-auth/basic-alfresco-auth.service';
|
||||
import { OidcAuthenticationService } from '../../auth/services/oidc-authentication.service';
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
enum LoginSteps {
|
||||
@ -129,6 +131,8 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private _fb: UntypedFormBuilder,
|
||||
private authService: AuthenticationService,
|
||||
private basicAlfrescoAuthService: BasicAlfrescoAuthService,
|
||||
private oidcAuthenticationService: OidcAuthenticationService,
|
||||
private translateService: TranslationService,
|
||||
private router: Router,
|
||||
private appConfig: AppConfigService,
|
||||
@ -160,7 +164,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
const url = params['redirectUrl'];
|
||||
const provider = this.appConfig.get<string>(AppConfigValues.PROVIDERS);
|
||||
|
||||
this.authService.setRedirect({ provider, url });
|
||||
this.basicAlfrescoAuthService.setRedirect({provider, url});
|
||||
});
|
||||
}
|
||||
|
||||
@ -181,7 +185,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
redirectToImplicitLogin() {
|
||||
this.authService.ssoImplicitLogin();
|
||||
this.oidcAuthenticationService.ssoImplicitLogin();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -199,6 +203,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
|
||||
if (!args.defaultPrevented) {
|
||||
this.actualLoginStep = LoginSteps.Checking;
|
||||
|
||||
this.performLogin(values);
|
||||
}
|
||||
}
|
||||
@ -207,7 +212,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
if (this.authService.isLoggedIn()) {
|
||||
this.router.navigate([this.successRoute]);
|
||||
}
|
||||
this.authService.ssoImplicitLogin();
|
||||
this.oidcAuthenticationService.ssoImplicitLogin();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -237,10 +242,11 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
performLogin(values: LoginFormValues) {
|
||||
this.authService.login(values.username, values.password, this.rememberMe).subscribe(
|
||||
(token) => {
|
||||
const redirectUrl = this.authService.getRedirect();
|
||||
performLogin(values: { username: string; password: string }) {
|
||||
this.authService.login(values.username, values.password, this.rememberMe)
|
||||
.subscribe(
|
||||
async (token: any) => {
|
||||
const redirectUrl = this.basicAlfrescoAuthService.getRedirect();
|
||||
|
||||
this.actualLoginStep = LoginSteps.Welcome;
|
||||
this.userPreferences.setStoragePrefix(values.username);
|
||||
@ -248,10 +254,10 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
this.success.emit(new LoginSuccessEvent(token, values.username, null));
|
||||
|
||||
if (redirectUrl) {
|
||||
this.authService.setRedirect(null);
|
||||
this.router.navigateByUrl(redirectUrl);
|
||||
this.basicAlfrescoAuthService.setRedirect(null);
|
||||
await this.router.navigateByUrl(redirectUrl);
|
||||
} else if (this.successRoute) {
|
||||
this.router.navigate([this.successRoute]);
|
||||
await this.router.navigate([this.successRoute]);
|
||||
}
|
||||
},
|
||||
(err: any) => {
|
||||
|
@ -20,6 +20,7 @@ import { LoginComponent } from '../components/login.component';
|
||||
import { LoginFooterDirective } from './login-footer.directive';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { OidcAuthenticationService } from '../../auth/services/oidc-authentication.service';
|
||||
|
||||
describe('LoginFooterDirective', () => {
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
@ -31,6 +32,11 @@ describe('LoginFooterDirective', () => {
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: OidcAuthenticationService, useValue: {}
|
||||
}
|
||||
]
|
||||
});
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
|
@ -20,6 +20,7 @@ import { LoginComponent } from '../components/login.component';
|
||||
import { LoginHeaderDirective } from './login-header.directive';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { OidcAuthenticationService } from '../../auth/services/oidc-authentication.service';
|
||||
|
||||
describe('LoginHeaderDirective', () => {
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
@ -31,6 +32,9 @@ describe('LoginHeaderDirective', () => {
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: OidcAuthenticationService, useValue: {} }
|
||||
]
|
||||
});
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
|
@ -16,12 +16,12 @@
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SnackbarContentComponent } from '@alfresco/adf-core';
|
||||
import { MatIcon, MatIconModule } from '@angular/material/icon';
|
||||
import { MAT_SNACK_BAR_DATA, MatSnackBarModule, MatSnackBarRef } from '@angular/material/snack-bar';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SnackbarContentComponent } from './snackbar-content.component';
|
||||
|
||||
describe('SnackbarContentComponent', () => {
|
||||
let component: SnackbarContentComponent;
|
||||
|
@ -36,7 +36,8 @@ export class CoreAutomationService {
|
||||
private userPreferencesService: UserPreferencesService,
|
||||
private storageService: StorageService,
|
||||
private auth: AuthenticationService
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
setup() {
|
||||
const adfProxy = window['adf'] || {};
|
||||
|
@ -20,9 +20,11 @@ import { CoreModule } from '../core.module';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { provideTranslations } from '../translation/translation.service';
|
||||
import { AuthModule } from '../../../src/lib/auth/oidc/auth.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
AuthModule.forRoot(),
|
||||
TranslateModule.forRoot(),
|
||||
CoreModule.forRoot(),
|
||||
BrowserAnimationsModule
|
||||
|
@ -32,9 +32,11 @@ import { CookieServiceMock } from '../mock/cookie.service.mock';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { directionalityConfigFactory } from '../common/services/directionality-config-factory';
|
||||
import { DirectionalityConfigService } from '../common/services/directionality-config.service';
|
||||
import { AuthModule } from '../auth';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
AuthModule.forRoot({ useHash: true }),
|
||||
NoopAnimationsModule,
|
||||
RouterTestingModule,
|
||||
HttpClientModule,
|
||||
|
@ -20,6 +20,7 @@ import { TranslateLoaderService } from './translate-loader.service';
|
||||
import { TranslationService } from './translation.service';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreModule } from '../core.module';
|
||||
import { AuthModule } from '../auth';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
@ -30,6 +31,7 @@ describe('TranslateLoader', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
AuthModule.forRoot({ useHash: true }),
|
||||
TranslateModule.forRoot(),
|
||||
CoreModule.forRoot()
|
||||
],
|
||||
|
@ -17,10 +17,12 @@
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CoreTestingModule, DownloadPromptDialogComponent, DownloadPromptActions } from '@alfresco/adf-core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DownloadPromptDialogComponent } from './download-prompt-dialog.component';
|
||||
import { CoreTestingModule } from '../../../testing/core.testing.module';
|
||||
import { DownloadPromptActions } from '../../models/download-prompt.actions';
|
||||
|
||||
const mockDialog = {
|
||||
close: jasmine.createSpy('close')
|
||||
|
@ -26,11 +26,12 @@ import {
|
||||
AppConfigServiceMock,
|
||||
TranslationService,
|
||||
TranslationMock,
|
||||
CoreModule
|
||||
CoreModule, AuthModule
|
||||
} from '@alfresco/adf-core';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
AuthModule.forRoot({ useHash: true }),
|
||||
NoopAnimationsModule,
|
||||
TranslateModule,
|
||||
CoreModule.forRoot(),
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
FormModel,
|
||||
FormOutcomeEvent,
|
||||
FormOutcomeModel, FormRenderingService, FormService,
|
||||
UploadWidgetContentLinkModel, WidgetVisibilityService, provideTranslations
|
||||
UploadWidgetContentLinkModel, WidgetVisibilityService, provideTranslations, AuthModule
|
||||
} from '@alfresco/adf-core';
|
||||
import { Node } from '@alfresco/js-api';
|
||||
import { ESCAPE } from '@angular/cdk/keycodes';
|
||||
@ -1151,6 +1151,7 @@ describe('Multilingual Form', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
AuthModule.forRoot({ useHash: true }),
|
||||
NoopAnimationsModule,
|
||||
TranslateModule.forRoot(),
|
||||
CoreModule.forRoot(),
|
||||
@ -1226,6 +1227,7 @@ describe('retrieve metadata on submit', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
AuthModule.forRoot({ useHash: true }),
|
||||
NoopAnimationsModule,
|
||||
TranslateModule.forRoot(),
|
||||
CoreModule.forRoot(),
|
||||
|
@ -17,9 +17,7 @@
|
||||
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { JwtHelperService } from '@alfresco/adf-core';
|
||||
import { IdentityUserService } from './identity-user.service';
|
||||
import { mockToken } from '../mock/jwt-helper.service.spec';
|
||||
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
|
||||
import {
|
||||
mockSearchUserByApp,
|
||||
@ -52,37 +50,6 @@ describe('IdentityUserService', () => {
|
||||
requestSpy = spyOn(adfHttpClient, 'request');
|
||||
});
|
||||
|
||||
describe('Current user info (JWT token)', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
const store = {};
|
||||
|
||||
spyOn(localStorage, 'getItem').and.callFake((key: string): string => store[key] || null);
|
||||
spyOn(localStorage, 'setItem').and.callFake((key: string, value: string): string => store[key] = value);
|
||||
});
|
||||
|
||||
it('should fetch identity user info from Jwt id token', () => {
|
||||
localStorage.setItem(JwtHelperService.USER_ID_TOKEN, mockToken);
|
||||
const user = service.getCurrentUserInfo();
|
||||
expect(user).toBeDefined();
|
||||
expect(user.firstName).toEqual('John');
|
||||
expect(user.lastName).toEqual('Doe');
|
||||
expect(user.email).toEqual('johnDoe@gmail.com');
|
||||
expect(user.username).toEqual('johnDoe1');
|
||||
});
|
||||
|
||||
it('should fallback on Jwt access token for identity user info', () => {
|
||||
localStorage.setItem(JwtHelperService.USER_ACCESS_TOKEN, mockToken);
|
||||
const user = service.getCurrentUserInfo();
|
||||
expect(user).toBeDefined();
|
||||
expect(user.firstName).toEqual('John');
|
||||
expect(user.lastName).toEqual('Doe');
|
||||
expect(user.email).toEqual('johnDoe@gmail.com');
|
||||
expect(user.username).toEqual('johnDoe1');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Search', () => {
|
||||
|
||||
it('should fetch users', (done) => {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user