mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-06-30 18:15:11 +00:00
[AAE-12511] implement OIDC authentication capabilities in ADF (#7856)
* feat: add custom AlfrescoApiHttpClient [ci:force] * feat: update configs * feat: move api to follow second entry point structure * feat: add auth module [ci:force] * Fix rebasing issues * Isolate oidc package as subfolder * Canary mode * [AAE-12498] Fix unit test should load external settings: resolve reponse data instead returning default config * [AAE-12498] Set @nrwl/eslint-plugin-nx@14.5.4 version to fix lint job that failed because of the 14.8.6 version (https://github.com/Alfresco/alfresco-ng2-components/actions/runs/4165060892/jobs/7207651856\#step:5:3379) * [AAE-12498] Fix stories:build-storybook:ci issues * [AAE-7991] cherry-pick e935f7b0b1f56d3bb124d566b248420de7bd0359 from repo https://github.com/Alfresco/alfresco-ng2-components/pull/7818: send onLogin to initialize acs version to fix [C362242] on canary configuration * [AAE-12498] Fix security hotspot: fix unsafe pseudorandom number generator * test: add missing tests for oidc-auth.guard * test: fix lint issues * chore: remove assignment in return * [AAE-12498] Remove warning comment because we already know we're doing breaking changes * [AAE-12498] Add auth-config.service unit tests * [AAE-12498] Remove getUserProfile from auth service --------- Co-authored-by: Andras Popovics <popovics@ndras.hu> Co-authored-by: Amedeo Lepore <amedeo.lepore@hyland.com>
This commit is contained in:
parent
dd91f2eeb6
commit
f4a8084f0c
11
angular.json
11
angular.json
@ -205,6 +205,14 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"canary": {
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "demo-shell/src/environments/environment.ts",
|
||||||
|
"with": "demo-shell/src/environments/environment.canary.ts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"e2e": {
|
"e2e": {
|
||||||
"budgets": [
|
"budgets": [
|
||||||
{
|
{
|
||||||
@ -242,6 +250,9 @@
|
|||||||
"production": {
|
"production": {
|
||||||
"browserTarget": "demoshell:build:production"
|
"browserTarget": "demoshell:build:production"
|
||||||
},
|
},
|
||||||
|
"canary": {
|
||||||
|
"browserTarget": "demoshell:build:canary"
|
||||||
|
},
|
||||||
"e2e": {
|
"e2e": {
|
||||||
"browserTarget": "demoshell:build:e2e"
|
"browserTarget": "demoshell:build:e2e"
|
||||||
}
|
}
|
||||||
|
@ -63,9 +63,11 @@
|
|||||||
"highlightable",
|
"highlightable",
|
||||||
"hotfix",
|
"hotfix",
|
||||||
"imgpreview",
|
"imgpreview",
|
||||||
|
"Inplace",
|
||||||
"intitem",
|
"intitem",
|
||||||
"jira",
|
"jira",
|
||||||
"jsons",
|
"jsons",
|
||||||
|
"jwks",
|
||||||
"keycodes",
|
"keycodes",
|
||||||
"keyvaluepairs",
|
"keyvaluepairs",
|
||||||
"keyvaluepairsitem",
|
"keyvaluepairsitem",
|
||||||
@ -77,8 +79,6 @@
|
|||||||
"mincount",
|
"mincount",
|
||||||
"minlength",
|
"minlength",
|
||||||
"minmax",
|
"minmax",
|
||||||
"jsons",
|
|
||||||
"Inplace",
|
|
||||||
"MLTEXT",
|
"MLTEXT",
|
||||||
"mousedrag",
|
"mousedrag",
|
||||||
"mouseenter",
|
"mouseenter",
|
||||||
@ -87,6 +87,7 @@
|
|||||||
"nginx",
|
"nginx",
|
||||||
"numbervisibilityprocess",
|
"numbervisibilityprocess",
|
||||||
"OAUTHCONFIG",
|
"OAUTHCONFIG",
|
||||||
|
"oidc",
|
||||||
"pdfjs",
|
"pdfjs",
|
||||||
"penta",
|
"penta",
|
||||||
"printf",
|
"printf",
|
||||||
|
@ -20,7 +20,7 @@ import { APP_INITIALIZER, NgModule } from '@angular/core';
|
|||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
import { ChartsModule } from 'ng2-charts';
|
import { ChartsModule } from 'ng2-charts';
|
||||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
@ -29,7 +29,7 @@ import {
|
|||||||
DebugAppConfigService,
|
DebugAppConfigService,
|
||||||
CoreModule,
|
CoreModule,
|
||||||
CoreAutomationService,
|
CoreAutomationService,
|
||||||
AuthBearerInterceptor
|
AuthModule
|
||||||
} from '@alfresco/adf-core';
|
} from '@alfresco/adf-core';
|
||||||
import { ExtensionsModule } from '@alfresco/adf-extensions';
|
import { ExtensionsModule } from '@alfresco/adf-extensions';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
@ -140,6 +140,7 @@ registerLocaleData(localeSv);
|
|||||||
environment.e2e ? NoopAnimationsModule : BrowserAnimationsModule,
|
environment.e2e ? NoopAnimationsModule : BrowserAnimationsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
RouterModule.forRoot(appRoutes, { useHash: true, relativeLinkResolution: 'legacy' }),
|
RouterModule.forRoot(appRoutes, { useHash: true, relativeLinkResolution: 'legacy' }),
|
||||||
|
...(environment.oidc ? [AuthModule.forRoot({ useHash: true })] : []),
|
||||||
FormsModule,
|
FormsModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
MaterialModule,
|
MaterialModule,
|
||||||
@ -211,10 +212,6 @@ registerLocaleData(localeSv);
|
|||||||
SearchFilterChipsComponent
|
SearchFilterChipsComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
|
||||||
provide: HTTP_INTERCEPTORS, useClass:
|
|
||||||
AuthBearerInterceptor, multi: true
|
|
||||||
},
|
|
||||||
{ provide: AppConfigService, useClass: DebugAppConfigService }, // not use this service in production
|
{ provide: AppConfigService, useClass: DebugAppConfigService }, // not use this service in production
|
||||||
{
|
{
|
||||||
provide: TRANSLATION_PROVIDER,
|
provide: TRANSLATION_PROVIDER,
|
||||||
|
@ -119,6 +119,12 @@
|
|||||||
formControlName="implicitFlow">
|
formControlName="implicitFlow">
|
||||||
</mat-slide-toggle>
|
</mat-slide-toggle>
|
||||||
|
|
||||||
|
<ng-container *ngIf="supportsCodeFlow">
|
||||||
|
<label for="codeFlow">{{ 'CORE.HOST_SETTINGS.CODE-FLOW'| translate }}</label>
|
||||||
|
<mat-slide-toggle class="adf-full-width" name="codeFlow" [color]="'primary'"
|
||||||
|
formControlName="codeFlow">
|
||||||
|
</mat-slide-toggle>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<mat-form-field class="adf-full-width">
|
<mat-form-field class="adf-full-width">
|
||||||
<mat-label>{{ 'APP.HOST_SETTINGS.REDIRECT'| translate }}</mat-label>
|
<mat-label>{{ 'APP.HOST_SETTINGS.REDIRECT'| translate }}</mat-label>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
import { Component, EventEmitter, Output, ViewEncapsulation, OnInit, Input } from '@angular/core';
|
import { Component, EventEmitter, Output, ViewEncapsulation, OnInit, Input } from '@angular/core';
|
||||||
import { Validators, UntypedFormGroup, UntypedFormBuilder, UntypedFormControl } from '@angular/forms';
|
import { Validators, UntypedFormGroup, UntypedFormBuilder, UntypedFormControl } from '@angular/forms';
|
||||||
import { AppConfigService, AppConfigValues, StorageService, AlfrescoApiService, OauthConfigModel } from '@alfresco/adf-core';
|
import { AppConfigService, AppConfigValues, StorageService, AlfrescoApiService, OauthConfigModel, AuthenticationService } from '@alfresco/adf-core';
|
||||||
import { ENTER } from '@angular/cdk/keycodes';
|
import { ENTER } from '@angular/cdk/keycodes';
|
||||||
|
|
||||||
export const HOST_REGEX = '^(http|https):\/\/.*[^/]$';
|
export const HOST_REGEX = '^(http|https):\/\/.*[^/]$';
|
||||||
@ -57,11 +57,13 @@ export class HostSettingsComponent implements OnInit {
|
|||||||
// eslint-disable-next-line @angular-eslint/no-output-native
|
// eslint-disable-next-line @angular-eslint/no-output-native
|
||||||
success = new EventEmitter<boolean>();
|
success = new EventEmitter<boolean>();
|
||||||
|
|
||||||
constructor(private formBuilder: UntypedFormBuilder,
|
constructor(
|
||||||
private storageService: StorageService,
|
private formBuilder: UntypedFormBuilder,
|
||||||
private alfrescoApiService: AlfrescoApiService,
|
private storageService: StorageService,
|
||||||
private appConfig: AppConfigService) {
|
private alfrescoApiService: AlfrescoApiService,
|
||||||
}
|
private appConfig: AppConfigService,
|
||||||
|
private auth: AuthenticationService
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.providers.length === 1) {
|
if (this.providers.length === 1) {
|
||||||
@ -146,6 +148,7 @@ export class HostSettingsComponent implements OnInit {
|
|||||||
secret: oauth.secret,
|
secret: oauth.secret,
|
||||||
silentLogin: oauth.silentLogin,
|
silentLogin: oauth.silentLogin,
|
||||||
implicitFlow: oauth.implicitFlow,
|
implicitFlow: oauth.implicitFlow,
|
||||||
|
codeFlow: oauth.codeFlow,
|
||||||
publicUrls: [oauth.publicUrls]
|
publicUrls: [oauth.publicUrls]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -185,6 +188,7 @@ export class HostSettingsComponent implements OnInit {
|
|||||||
this.storageService.setItem(AppConfigValues.AUTHTYPE, values.authType);
|
this.storageService.setItem(AppConfigValues.AUTHTYPE, values.authType);
|
||||||
|
|
||||||
this.alfrescoApiService.reset();
|
this.alfrescoApiService.reset();
|
||||||
|
this.auth.reset();
|
||||||
this.alfrescoApiService.getInstance().invalidateSession();
|
this.alfrescoApiService.getInstance().invalidateSession();
|
||||||
this.success.emit(true);
|
this.success.emit(true);
|
||||||
}
|
}
|
||||||
@ -228,6 +232,10 @@ export class HostSettingsComponent implements OnInit {
|
|||||||
return this.form.get('authType').value === 'OAUTH';
|
return this.form.get('authType').value === 'OAUTH';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get supportsCodeFlow(): boolean {
|
||||||
|
return this.auth.supportCodeFlow;
|
||||||
|
}
|
||||||
|
|
||||||
get providersControl(): UntypedFormControl {
|
get providersControl(): UntypedFormControl {
|
||||||
return this.form.get('providersControl') as UntypedFormControl;
|
return this.form.get('providersControl') as UntypedFormControl;
|
||||||
}
|
}
|
||||||
@ -264,6 +272,10 @@ export class HostSettingsComponent implements OnInit {
|
|||||||
return this.oauthConfig.get('implicitFlow') as UntypedFormControl;
|
return this.oauthConfig.get('implicitFlow') as UntypedFormControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get codeFlow(): UntypedFormControl {
|
||||||
|
return this.oauthConfig.get('codeFlow') as UntypedFormControl;
|
||||||
|
}
|
||||||
|
|
||||||
get silentLogin(): UntypedFormControl {
|
get silentLogin(): UntypedFormControl {
|
||||||
return this.oauthConfig.get('silentLogin') as UntypedFormControl;
|
return this.oauthConfig.get('silentLogin') as UntypedFormControl;
|
||||||
}
|
}
|
||||||
|
@ -15,5 +15,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './public-api';
|
export const environment = {
|
||||||
|
production: true,
|
||||||
|
e2e: false,
|
||||||
|
oidc: true
|
||||||
|
};
|
@ -17,5 +17,6 @@
|
|||||||
|
|
||||||
export const environment = {
|
export const environment = {
|
||||||
production: false,
|
production: false,
|
||||||
e2e: true
|
e2e: true,
|
||||||
|
oidc: false
|
||||||
};
|
};
|
||||||
|
@ -17,5 +17,6 @@
|
|||||||
|
|
||||||
export const environment = {
|
export const environment = {
|
||||||
production: true,
|
production: true,
|
||||||
e2e: false
|
e2e: false,
|
||||||
|
oidc: false
|
||||||
};
|
};
|
||||||
|
@ -22,5 +22,6 @@
|
|||||||
|
|
||||||
export const environment = {
|
export const environment = {
|
||||||
production: false,
|
production: false,
|
||||||
e2e: false
|
e2e: false,
|
||||||
|
oidc: false
|
||||||
};
|
};
|
||||||
|
3
lib/core/api/README.md
Normal file
3
lib/core/api/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# @alfresco/adf-core/api
|
||||||
|
|
||||||
|
Secondary entry point of `@alfresco/adf-core`. It can be used by importing from `@alfresco/adf-core/api`.
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../../../../node_modules/ng-packagr/ng-package.schema.json",
|
|
||||||
"lib": {
|
"lib": {
|
||||||
"entryFile": "public-api.ts"
|
"entryFile": "src/index.ts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './api-client.factory';
|
export * from './lib/api-client.factory';
|
||||||
export * from './api-clients.service';
|
export * from './lib/api-clients.service';
|
||||||
export * from './clients';
|
export * from './lib/clients';
|
||||||
export * from './types';
|
export * from './lib/types';
|
||||||
export * from './alfresco-api/alfresco-api.http-client';
|
export * from './lib/alfresco-api/alfresco-api.http-client';
|
@ -181,6 +181,9 @@ describe('AlfrescoApiHttpClient', () => {
|
|||||||
const req = controller.expectOne('http://example.com?autoRename=true&include=allowableOperations');
|
const req = controller.expectOne('http://example.com?autoRename=true&include=allowableOperations');
|
||||||
expect(req.request.method).toEqual('POST');
|
expect(req.request.method).toEqual('POST');
|
||||||
|
|
||||||
|
// filedata: (binary)
|
||||||
|
// include: allowableOperations
|
||||||
|
|
||||||
const body = req.request.body as HttpParams;
|
const body = req.request.body as HttpParams;
|
||||||
|
|
||||||
expect(body.get('relativePath')).toBe('');
|
expect(body.get('relativePath')).toBe('');
|
@ -15,6 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { ApiClientsService } from '../api-clients.service';
|
import { ApiClientsService } from '../api-clients.service';
|
||||||
import { ActivitiClientModule } from './activiti/activiti-client.module';
|
import { ActivitiClientModule } from './activiti/activiti-client.module';
|
||||||
@ -22,6 +23,11 @@ import { DiscoveryClientModule } from './discovery/discovery-client.module';
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
HttpClientModule,
|
||||||
|
HttpClientXsrfModule.withOptions({
|
||||||
|
cookieName: 'CSRF-TOKEN',
|
||||||
|
headerName: 'X-CSRF-TOKEN'
|
||||||
|
}),
|
||||||
ActivitiClientModule,
|
ActivitiClientModule,
|
||||||
DiscoveryClientModule
|
DiscoveryClientModule
|
||||||
],
|
],
|
@ -18,4 +18,3 @@
|
|||||||
export * from './activiti/activiti-client.types';
|
export * from './activiti/activiti-client.types';
|
||||||
export * from './alfresco-js-clients.module';
|
export * from './alfresco-js-clients.module';
|
||||||
export * from './discovery/discovery-client.types';
|
export * from './discovery/discovery-client.types';
|
||||||
|
|
@ -0,0 +1,38 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AlfrescoApiHttpClient } from '@alfresco/adf-core/api';
|
||||||
|
import { StorageService } from '../common/services/storage.service';
|
||||||
|
import { AlfrescoApi, AlfrescoApiConfig } from '@alfresco/js-api';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { AppConfigService } from '../app-config';
|
||||||
|
import { AlfrescoApiService } from '../services/alfresco-api.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AlfrescoApiServiceWithAngularBasedHttpClient extends AlfrescoApiService {
|
||||||
|
constructor(
|
||||||
|
storage: StorageService,
|
||||||
|
appConfig: AppConfigService,
|
||||||
|
private readonly alfrescoApiHttpClient: AlfrescoApiHttpClient
|
||||||
|
) {
|
||||||
|
super(appConfig, storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
override createInstance(config: AlfrescoApiConfig) {
|
||||||
|
return new AlfrescoApi(config, this.alfrescoApiHttpClient);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AlfrescoApiConfig } from '@alfresco/js-api';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { AppConfigService, AppConfigValues } from '../app-config/app-config.service';
|
||||||
|
import { OauthConfigModel } from '../auth/models/oauth-config.model';
|
||||||
|
import { AlfrescoApiService } from '../services/alfresco-api.service';
|
||||||
|
|
||||||
|
export function createAlfrescoApiInstance(angularAlfrescoApiService: AlfrescoApiLoaderService) {
|
||||||
|
return () => angularAlfrescoApiService.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AlfrescoApiLoaderService {
|
||||||
|
constructor(private readonly appConfig: AppConfigService, private readonly apiService: AlfrescoApiService) {}
|
||||||
|
|
||||||
|
async init(): Promise<any> {
|
||||||
|
await this.appConfig.load();
|
||||||
|
return this.initAngularAlfrescoApi();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initAngularAlfrescoApi() {
|
||||||
|
const oauth: OauthConfigModel = Object.assign({}, this.appConfig.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
|
||||||
|
|
||||||
|
if (oauth) {
|
||||||
|
oauth.redirectUri = window.location.origin + window.location.pathname;
|
||||||
|
oauth.redirectUriLogout = window.location.origin + window.location.pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = new AlfrescoApiConfig({
|
||||||
|
provider: this.appConfig.get<string>(AppConfigValues.PROVIDERS),
|
||||||
|
hostEcm: this.appConfig.get<string>(AppConfigValues.ECMHOST),
|
||||||
|
hostBpm: this.appConfig.get<string>(AppConfigValues.BPMHOST),
|
||||||
|
authType: this.appConfig.get<string>(AppConfigValues.AUTHTYPE, 'BASIC'),
|
||||||
|
contextRootBpm: this.appConfig.get<string>(AppConfigValues.CONTEXTROOTBPM),
|
||||||
|
contextRoot: this.appConfig.get<string>(AppConfigValues.CONTEXTROOTECM),
|
||||||
|
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),
|
||||||
|
oauth2: oauth
|
||||||
|
});
|
||||||
|
|
||||||
|
this.apiService.load(config);
|
||||||
|
}
|
||||||
|
}
|
25
lib/core/src/lib/app-config/app-config.loader.ts
Normal file
25
lib/core/src/lib/app-config/app-config.loader.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AppConfigService, AppConfigValues } from './app-config.service';
|
||||||
|
import { StorageService } from '../common/services/storage.service';
|
||||||
|
|
||||||
|
export function loadAppConfig(appConfigService: AppConfigService, storageService: StorageService) {
|
||||||
|
return () => appConfigService.load().then(() => {
|
||||||
|
storageService.prefix = appConfigService.get<string>(AppConfigValues.STORAGE_PREFIX, '');
|
||||||
|
});
|
||||||
|
}
|
@ -192,8 +192,8 @@ export class AppConfigService {
|
|||||||
this.http.get(configUrl).subscribe(
|
this.http.get(configUrl).subscribe(
|
||||||
(data: any) => {
|
(data: any) => {
|
||||||
this.status = Status.LOADED;
|
this.status = Status.LOADED;
|
||||||
|
resolve(data);
|
||||||
this.onDataLoaded(data);
|
this.onDataLoaded(data);
|
||||||
resolve(this.config);
|
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
resolve(this.config);
|
resolve(this.config);
|
||||||
|
@ -27,14 +27,12 @@ import { catchError, mergeMap } from 'rxjs/operators';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthBearerInterceptor implements HttpInterceptor {
|
export class AuthBearerInterceptor implements HttpInterceptor {
|
||||||
private excludedUrlsRegex: RegExp[];
|
private excludedUrlsRegex: RegExp[];
|
||||||
private authService: AuthenticationService;
|
|
||||||
|
|
||||||
constructor(private injector: Injector) { }
|
constructor(private injector: Injector, private authService: AuthenticationService) { }
|
||||||
|
|
||||||
private loadExcludedUrlsRegex() {
|
private loadExcludedUrlsRegex() {
|
||||||
const excludedUrls: string[] = this.authService.getBearerExcludedUrls();
|
const excludedUrls = this.authService.getBearerExcludedUrls();
|
||||||
this.excludedUrlsRegex = excludedUrls.map((urlPattern) => new RegExp(urlPattern, 'gi')) || [];
|
this.excludedUrlsRegex = excludedUrls.map((urlPattern) => new RegExp(urlPattern, 'i')) || [];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
intercept(req: HttpRequest<any>, next: HttpHandler):
|
intercept(req: HttpRequest<any>, next: HttpHandler):
|
||||||
@ -51,7 +49,7 @@ export class AuthBearerInterceptor implements HttpInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const urlRequest = req.url;
|
const urlRequest = req.url;
|
||||||
const shallPass: boolean = !!this.excludedUrlsRegex.find((regex) => regex.test(urlRequest));
|
const shallPass: boolean = this.excludedUrlsRegex.some((regex) => regex.test(urlRequest));
|
||||||
if (shallPass) {
|
if (shallPass) {
|
||||||
return next.handle(req)
|
return next.handle(req)
|
||||||
.pipe(
|
.pipe(
|
||||||
@ -73,7 +71,19 @@ export class AuthBearerInterceptor implements HttpInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private appendJsonContentType(headers: HttpHeaders): HttpHeaders {
|
private appendJsonContentType(headers: HttpHeaders): HttpHeaders {
|
||||||
return headers.set('Content-Type', 'application/json;charset=UTF-8');
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
if (headers.get('Content-Type') === 'multipart/form-data') {
|
||||||
|
return headers.delete('Content-Type');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!headers.get('Content-Type')) {
|
||||||
|
return headers.set('Content-Type', 'application/json;charset=UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,3 +16,4 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './public-api';
|
export * from './public-api';
|
||||||
|
export * from './oidc/public-api';
|
||||||
|
50
lib/core/src/lib/auth/mock/auth-config.service.mock.ts
Normal file
50
lib/core/src/lib/auth/mock/auth-config.service.mock.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const mockAuthConfigImplicitFlow = {
|
||||||
|
host: 'http://localhost:3000/auth/realms/alfresco',
|
||||||
|
clientId: 'alfresco',
|
||||||
|
scope: 'openid profile email',
|
||||||
|
secret: '',
|
||||||
|
implicitFlow: true,
|
||||||
|
silentLogin: true,
|
||||||
|
redirectSilentIframeUri: 'http://localhost:3000/assets/silent-refresh.html',
|
||||||
|
redirectUri: '/',
|
||||||
|
redirectUriLogout: '#/logout',
|
||||||
|
publicUrls: [
|
||||||
|
'**/preview/s/*',
|
||||||
|
'**/settings',
|
||||||
|
'**/logout'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockAuthConfigCodeFlow = {
|
||||||
|
host: 'http://localhost:3000/auth/realms/alfresco',
|
||||||
|
clientId: 'alfresco',
|
||||||
|
scope: 'openid profile email',
|
||||||
|
secret: '',
|
||||||
|
codeFlow: true,
|
||||||
|
silentLogin: true,
|
||||||
|
redirectSilentIframeUri: 'http://localhost:3000/assets/silent-refresh.html',
|
||||||
|
redirectUri: '/',
|
||||||
|
redirectUriLogout: '#/logout',
|
||||||
|
publicUrls: [
|
||||||
|
'**/preview/s/*',
|
||||||
|
'**/settings',
|
||||||
|
'**/logout'
|
||||||
|
]
|
||||||
|
};
|
@ -35,7 +35,7 @@ export class AuthenticationMock extends AuthenticationService {
|
|||||||
cookie: CookieService,
|
cookie: CookieService,
|
||||||
logService: LogService
|
logService: LogService
|
||||||
) {
|
) {
|
||||||
super(appConfig, storageService, alfrescoApi, cookie, logService);
|
super(alfrescoApi, appConfig, cookie, logService, storageService);
|
||||||
}
|
}
|
||||||
|
|
||||||
login(username: string, password: string): Observable<{ type: string; ticket: any }> {
|
login(username: string, password: string): Observable<{ type: string; ticket: any }> {
|
||||||
|
@ -20,6 +20,7 @@ export interface OauthConfigModel {
|
|||||||
clientId: string;
|
clientId: string;
|
||||||
scope: string;
|
scope: string;
|
||||||
implicitFlow: boolean;
|
implicitFlow: boolean;
|
||||||
|
codeFlow?: boolean;
|
||||||
redirectUri: string;
|
redirectUri: string;
|
||||||
silentLogin?: boolean;
|
silentLogin?: boolean;
|
||||||
secret?: string;
|
secret?: string;
|
||||||
|
84
lib/core/src/lib/auth/oidc/auth-config.service.spec.ts
Normal file
84
lib/core/src/lib/auth/oidc/auth-config.service.spec.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { EMPTY } from 'rxjs';
|
||||||
|
import { AppConfigService } from '../../app-config/app-config.service';
|
||||||
|
import { AUTH_MODULE_CONFIG } from './auth-config';
|
||||||
|
import { mockAuthConfigCodeFlow, mockAuthConfigImplicitFlow } from '../mock/auth-config.service.mock';
|
||||||
|
|
||||||
|
import { AuthConfigService } from './auth-config.service';
|
||||||
|
|
||||||
|
describe('AuthConfigService', () => {
|
||||||
|
let service: AuthConfigService;
|
||||||
|
let appConfigServiceMock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
appConfigServiceMock = jasmine.createSpyObj(['get'], { onLoad: EMPTY });
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [HttpClientTestingModule],
|
||||||
|
providers: [
|
||||||
|
{ provide: AUTH_MODULE_CONFIG, useValue: { useHash: true } },
|
||||||
|
{ provide: AppConfigService, useValue: appConfigServiceMock }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
service = TestBed.inject(AuthConfigService);
|
||||||
|
spyOn<any>(service, 'getLocationOrigin').and.returnValue('http://localhost:3000');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('load auth config using hash', () => {
|
||||||
|
it('should load configuration if implicit flow is true ', async () => {
|
||||||
|
appConfigServiceMock.get.and.returnValue(mockAuthConfigImplicitFlow);
|
||||||
|
const expectedConfig = {
|
||||||
|
oidc: true,
|
||||||
|
issuer: 'http://localhost:3000/auth/realms/alfresco',
|
||||||
|
redirectUri: 'http://localhost:3000/#/view/authentication-confirmation/?',
|
||||||
|
silentRefreshRedirectUri: 'http://localhost:3000/silent-refresh.html',
|
||||||
|
postLogoutRedirectUri: 'http://localhost:3000/#/logout',
|
||||||
|
clientId: 'alfresco',
|
||||||
|
scope: 'openid profile email',
|
||||||
|
dummyClientSecret: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(await service.loadConfig()).toEqual(expectedConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load configuration if code flow is true ', async () => {
|
||||||
|
appConfigServiceMock.get.and.returnValue(mockAuthConfigCodeFlow);
|
||||||
|
const expectedConfig = {
|
||||||
|
oidc: true,
|
||||||
|
issuer: 'http://localhost:3000/auth/realms/alfresco',
|
||||||
|
redirectUri: 'http://localhost:3000/#/view/authentication-confirmation',
|
||||||
|
silentRefreshRedirectUri: 'http://localhost:3000/silent-refresh.html',
|
||||||
|
postLogoutRedirectUri: 'http://localhost:3000/#/logout',
|
||||||
|
clientId: 'alfresco',
|
||||||
|
scope: 'openid profile email',
|
||||||
|
responseType: 'code',
|
||||||
|
dummyClientSecret: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(await service.loadConfig()).toEqual(expectedConfig);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
86
lib/core/src/lib/auth/oidc/auth-config.service.ts
Normal file
86
lib/core/src/lib/auth/oidc/auth-config.service.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@angular/core';
|
||||||
|
import { AuthConfig } from 'angular-oauth2-oidc';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
|
||||||
|
import { OauthConfigModel } from '../models/oauth-config.model';
|
||||||
|
import { AuthModuleConfig, AUTH_MODULE_CONFIG } from './auth-config';
|
||||||
|
|
||||||
|
export function authConfigFactory(authConfigService: AuthConfigService): Promise<AuthConfig> {
|
||||||
|
return authConfigService.loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AuthConfigService {
|
||||||
|
constructor(
|
||||||
|
private appConfigService: AppConfigService,
|
||||||
|
@Inject(AUTH_MODULE_CONFIG) private readonly authModuleConfig: AuthModuleConfig
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private _authConfig!: AuthConfig;
|
||||||
|
get authConfig(): AuthConfig {
|
||||||
|
return this._authConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadConfig(): Promise<AuthConfig> {
|
||||||
|
return this.appConfigService.onLoad.pipe(take(1)).toPromise().then(this.loadAppConfig.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAppConfig(): AuthConfig {
|
||||||
|
const oauth2: OauthConfigModel = Object.assign({}, this.appConfigService.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
|
||||||
|
const origin = this.getLocationOrigin();
|
||||||
|
const redirectUri = this.getRedirectUri();
|
||||||
|
|
||||||
|
const authConfig: AuthConfig = {
|
||||||
|
oidc: oauth2.implicitFlow || oauth2.codeFlow || false,
|
||||||
|
issuer: oauth2.host,
|
||||||
|
redirectUri,
|
||||||
|
silentRefreshRedirectUri: `${origin}/silent-refresh.html`,
|
||||||
|
postLogoutRedirectUri: `${origin}/${oauth2.redirectUriLogout}`,
|
||||||
|
clientId: oauth2.clientId,
|
||||||
|
scope: oauth2.scope,
|
||||||
|
dummyClientSecret: oauth2.secret || '',
|
||||||
|
...(oauth2.codeFlow && { responseType: 'code' })
|
||||||
|
};
|
||||||
|
|
||||||
|
return authConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRedirectUri(): string {
|
||||||
|
// required for this package as we handle the returned token on this view, with is provided by the AuthModule
|
||||||
|
const viewUrl = `view/authentication-confirmation`;
|
||||||
|
const useHash = this.authModuleConfig.useHash;
|
||||||
|
|
||||||
|
const redirectUri = useHash
|
||||||
|
? `${this.getLocationOrigin()}/#/${viewUrl}`
|
||||||
|
: `${this.getLocationOrigin()}/${viewUrl}`;
|
||||||
|
|
||||||
|
const oauth2: OauthConfigModel = Object.assign({}, this.appConfigService.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
|
||||||
|
|
||||||
|
// handle issue from the OIDC library with hashStrategy and implicitFlow, with would append &state to the url with would lead to error
|
||||||
|
// `cannot match any routes`, and displaying the wildcard ** error page
|
||||||
|
return oauth2.implicitFlow && useHash ? `${redirectUri}/?` : redirectUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLocationOrigin() {
|
||||||
|
return window.location.origin;
|
||||||
|
}
|
||||||
|
}
|
@ -15,19 +15,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
import { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
export interface AbstractAuthentication {
|
export interface AuthModuleConfig {
|
||||||
TYPE: string;
|
readonly useHash: boolean;
|
||||||
alfrescoApi: any;
|
|
||||||
|
|
||||||
login(username: string, password: string): Observable<any>;
|
|
||||||
|
|
||||||
logout(): Observable<any>;
|
|
||||||
|
|
||||||
isLoggedIn(): boolean ;
|
|
||||||
|
|
||||||
getTicket(): string;
|
|
||||||
|
|
||||||
saveTicket(ticket: any): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const AUTH_MODULE_CONFIG = new InjectionToken<AuthModuleConfig>('AUTH_MODULE_CONFIG');
|
30
lib/core/src/lib/auth/oidc/auth-routing.module.ts
Normal file
30
lib/core/src/lib/auth/oidc/auth-routing.module.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { AuthenticationConfirmationComponent } from './view/authentication-confirmation/authentication-confirmation.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: 'view/authentication-confirmation', component: AuthenticationConfirmationComponent }
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class AuthRoutingModule {}
|
73
lib/core/src/lib/auth/oidc/auth.module.ts
Normal file
73
lib/core/src/lib/auth/oidc/auth.module.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
|
||||||
|
import { AuthConfig, AUTH_CONFIG, OAuthModule, OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
|
||||||
|
import { AlfrescoApiServiceWithAngularBasedHttpClient } from '../../api-factories/alfresco-api-service-with-angular-based-http-client';
|
||||||
|
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';
|
||||||
|
|
||||||
|
export function loginFactory(oAuthService: OAuthService, storage: OAuthStorage, config: AuthConfig) {
|
||||||
|
const service = new RedirectAuthService(oAuthService, storage, config);
|
||||||
|
return () => service.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [AuthenticationConfirmationComponent],
|
||||||
|
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: AlfrescoApiService, useClass: AlfrescoApiServiceWithAngularBasedHttpClient },
|
||||||
|
{
|
||||||
|
provide: AUTH_CONFIG,
|
||||||
|
useFactory: authConfigFactory,
|
||||||
|
deps: [AuthConfigService]
|
||||||
|
},
|
||||||
|
RedirectAuthService,
|
||||||
|
{ provide: AuthService, useExisting: RedirectAuthService },
|
||||||
|
{
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
useFactory: loginFactory,
|
||||||
|
deps: [OAuthService, OAuthStorage, AUTH_CONFIG],
|
||||||
|
multi: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AuthModule {
|
||||||
|
static forRoot(config: AuthModuleConfig = { useHash: false }): ModuleWithProviders<AuthModule> {
|
||||||
|
return {
|
||||||
|
ngModule: AuthModule,
|
||||||
|
providers: [{ provide: AUTH_MODULE_CONFIG, useValue: config }]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
57
lib/core/src/lib/auth/oidc/auth.service.ts
Normal file
57
lib/core/src/lib/auth/oidc/auth.service.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TokenResponse } from 'angular-oauth2-oidc';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide authentication/authorization through OAuth2/OIDC protocol.
|
||||||
|
*/
|
||||||
|
export abstract class AuthService {
|
||||||
|
/** Subscribe to whether the user has valid Id/Access tokens. */
|
||||||
|
abstract authenticated$: Observable<boolean>;
|
||||||
|
|
||||||
|
/** Get whether the user has valid Id/Access tokens. */
|
||||||
|
abstract authenticated: boolean;
|
||||||
|
|
||||||
|
/** Subscribe to errors reaching the IdP. */
|
||||||
|
abstract idpUnreachable$: Observable<Error>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate the IdP login flow.
|
||||||
|
*/
|
||||||
|
abstract login(currentUrl?: string): Promise<void> | void;
|
||||||
|
|
||||||
|
abstract baseAuthLogin(username: string, password: string): Observable<TokenResponse> ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from IdP.
|
||||||
|
*
|
||||||
|
* @returns Promise may be returned depending on implementation
|
||||||
|
*/
|
||||||
|
abstract logout(): Promise<void> | void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete the login flow.
|
||||||
|
*
|
||||||
|
* In browsers, checks URL for auth and stored state. Call this once the application returns from IdP.
|
||||||
|
*
|
||||||
|
* @returns Promise, resolve with stored state, reject if unable to reach IdP
|
||||||
|
*/
|
||||||
|
abstract loginCallback(): Promise<string | undefined>;
|
||||||
|
abstract updateIDPConfiguration(...args: any[]): void;
|
||||||
|
}
|
72
lib/core/src/lib/auth/oidc/oidc-auth.guard.spec.ts
Normal file
72
lib/core/src/lib/auth/oidc/oidc-auth.guard.spec.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
import { OidcAuthGuard } from './oidc-auth.guard';
|
||||||
|
|
||||||
|
const state: RouterStateSnapshot = {
|
||||||
|
root: new ActivatedRouteSnapshot(),
|
||||||
|
url: 'http://example.com'
|
||||||
|
};
|
||||||
|
const routeSnapshot = new ActivatedRouteSnapshot();
|
||||||
|
|
||||||
|
describe('OidcAuthGuard', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [RouterTestingModule],
|
||||||
|
providers: [OidcAuthGuard]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#canActivate', () => {
|
||||||
|
it('should return false if the user is not authenticated, and call login method', () => {
|
||||||
|
const authService = { authenticated: false, login: jasmine.createSpy() } as unknown as AuthService;
|
||||||
|
const authGuard = new OidcAuthGuard(authService);
|
||||||
|
|
||||||
|
expect(authGuard.canActivate(routeSnapshot, state)).toEqual(false);
|
||||||
|
expect(authService.login).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if the user is authenticated', () => {
|
||||||
|
const authService = { authenticated: true } as unknown as AuthService;
|
||||||
|
const authGuard = new OidcAuthGuard(authService);
|
||||||
|
|
||||||
|
expect(authGuard.canActivate(routeSnapshot, state)).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#canActivateChild', () => {
|
||||||
|
it('should return false if the user is not authenticated, and call login method', () => {
|
||||||
|
const authService = { authenticated: false, login: jasmine.createSpy() } as unknown as AuthService;
|
||||||
|
const authGuard = new OidcAuthGuard(authService);
|
||||||
|
|
||||||
|
expect(authGuard.canActivateChild(routeSnapshot, state)).toEqual(false);
|
||||||
|
expect(authService.login).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if the user is authenticated', () => {
|
||||||
|
const authService = { authenticated: true } as unknown as AuthService;
|
||||||
|
const authGuard = new OidcAuthGuard(authService);
|
||||||
|
|
||||||
|
expect(authGuard.canActivateChild(routeSnapshot, state)).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
52
lib/core/src/lib/auth/oidc/oidc-auth.guard.ts
Normal file
52
lib/core/src/lib/auth/oidc/oidc-auth.guard.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class OidcAuthGuard implements CanActivate {
|
||||||
|
constructor(private auth: AuthService) {}
|
||||||
|
|
||||||
|
canActivate(
|
||||||
|
_route: ActivatedRouteSnapshot,
|
||||||
|
state: RouterStateSnapshot
|
||||||
|
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
||||||
|
return this._isAuthenticated(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
canActivateChild(_route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||||
|
return this._isAuthenticated(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isAuthenticated(state: RouterStateSnapshot) {
|
||||||
|
if (this.auth.authenticated) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginResult = this.auth.login(state.url);
|
||||||
|
|
||||||
|
if (loginResult instanceof Promise) {
|
||||||
|
return loginResult.then(() => true).catch(() => false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
130
lib/core/src/lib/auth/oidc/oidc-authentication.service.ts
Normal file
130
lib/core/src/lib/auth/oidc/oidc-authentication.service.ts
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
|
||||||
|
import { EMPTY, Observable } 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 { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||||
|
import { BaseAuthenticationService } from '../../services/base-authentication.service';
|
||||||
|
import { CookieService } from '../../common/services/cookie.service';
|
||||||
|
import { JwtHelperService } from '../services/jwt-helper.service';
|
||||||
|
import { LogService } from '../../common/services/log.service';
|
||||||
|
import { AuthConfigService } from '../oidc/auth-config.service';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class OIDCAuthenticationService extends BaseAuthenticationService {
|
||||||
|
readonly supportCodeFlow = true;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
alfrescoApi: AlfrescoApiService,
|
||||||
|
appConfig: AppConfigService,
|
||||||
|
cookie: CookieService,
|
||||||
|
logService: LogService,
|
||||||
|
private authStorage: OAuthStorage,
|
||||||
|
private oauthService: OAuthService,
|
||||||
|
private readonly authConfig: AuthConfigService,
|
||||||
|
private readonly auth: AuthService
|
||||||
|
) {
|
||||||
|
super(alfrescoApi, appConfig, cookie, logService);
|
||||||
|
this.alfrescoApi.alfrescoApiInitialized.subscribe(() => {
|
||||||
|
this.alfrescoApi.getInstance().reply('logged-in', () => {
|
||||||
|
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() {
|
||||||
|
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, 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))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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: OauthConfigModel = Object.assign({}, this.appConfig.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
|
||||||
|
|
||||||
|
if (config.oidc && oauth2.silentLogin) {
|
||||||
|
this.auth.login();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
lib/core/src/lib/auth/oidc/public-api.ts
Normal file
23
lib/core/src/lib/auth/oidc/public-api.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './auth-routing.module';
|
||||||
|
export * from './auth.module';
|
||||||
|
export * from './auth.service';
|
||||||
|
export * from './oidc-auth.guard';
|
||||||
|
export * from './redirect-auth.service';
|
||||||
|
export * from './view/authentication-confirmation/authentication-confirmation.component';
|
162
lib/core/src/lib/auth/oidc/redirect-auth.service.ts
Normal file
162
lib/core/src/lib/auth/oidc/redirect-auth.service.ts
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { 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 { 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 {
|
||||||
|
private _loadDiscoveryDocumentPromise = Promise.resolve(false);
|
||||||
|
|
||||||
|
/** Subscribe to whether the user has valid Id/Access tokens. */
|
||||||
|
authenticated$!: Observable<boolean>;
|
||||||
|
|
||||||
|
/** Subscribe to errors reaching the IdP. */
|
||||||
|
idpUnreachable$!: Observable<Error>;
|
||||||
|
|
||||||
|
/** Get whether the user has valid Id/Access tokens. */
|
||||||
|
get authenticated(): boolean {
|
||||||
|
return this.oauthService.hasValidIdToken() && this.oauthService.hasValidAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
private authConfig!: AuthConfig | Promise<AuthConfig>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private oauthService: OAuthService,
|
||||||
|
private _oauthStorage: OAuthStorage,
|
||||||
|
@Inject(AUTH_CONFIG) authConfig: AuthConfig
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.authConfig = authConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.oauthService.clearHashAfterLogin = true;
|
||||||
|
|
||||||
|
this.authenticated$ = this.oauthService.events.pipe(
|
||||||
|
startWith(undefined),
|
||||||
|
map(() => this.authenticated),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
shareReplay(1)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.idpUnreachable$ = this.oauthService.events.pipe(
|
||||||
|
filter((event): event is OAuthErrorEvent => event.type === 'discovery_document_load_error'),
|
||||||
|
map((event) => event.reason as Error)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isPromise(this.authConfig)) {
|
||||||
|
return this.authConfig.then((config) => this.configureAuth(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.configureAuth(this.authConfig);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
this.oauthService.logOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureDiscoveryDocument(): Promise<boolean> {
|
||||||
|
this._loadDiscoveryDocumentPromise = this._loadDiscoveryDocumentPromise
|
||||||
|
.catch(() => false)
|
||||||
|
.then((loaded) => {
|
||||||
|
if (!loaded) {
|
||||||
|
return this.oauthService.loadDiscoveryDocument().then(() => true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return this._loadDiscoveryDocumentPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
login(currentUrl?: string): void {
|
||||||
|
let stateKey: string | undefined;
|
||||||
|
|
||||||
|
if (currentUrl) {
|
||||||
|
const randomValue = window.crypto.getRandomValues(new Uint32Array(1))[0];
|
||||||
|
stateKey = `auth_state_${randomValue}${Date.now()}`;
|
||||||
|
this._oauthStorage.setItem(stateKey, JSON.stringify(currentUrl || {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// initLoginFlow will initialize the login flow in either code or implicit depending on the configuration
|
||||||
|
this.ensureDiscoveryDocument().then(() => void this.oauthService.initLoginFlow(stateKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
baseAuthLogin(username: string, password: string): Observable<TokenResponse> {
|
||||||
|
this.oauthService.useHttpBasicAuth = true;
|
||||||
|
|
||||||
|
return from(this.oauthService.fetchTokenUsingPasswordFlow(username, password)).pipe(
|
||||||
|
map((response) => {
|
||||||
|
const props = new Map<string, string>();
|
||||||
|
props.set('id_token', response.id_token);
|
||||||
|
// for backward compatibility we need to set the response in our storage
|
||||||
|
this.oauthService['storeAccessTokenResponse'](response.access_token, response.refresh_token, response.expires_in, response.scope, props);
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async loginCallback(): Promise<string | undefined> {
|
||||||
|
return this.ensureDiscoveryDocument()
|
||||||
|
.then(() => this.oauthService.tryLogin({ preventClearHashAfterLogin: false }))
|
||||||
|
.then(() => this._getRedirectUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getRedirectUrl() {
|
||||||
|
const DEFAULT_REDIRECT = '/';
|
||||||
|
const stateKey = this.oauthService.state;
|
||||||
|
|
||||||
|
if (stateKey) {
|
||||||
|
const stateStringified = this._oauthStorage.getItem(stateKey);
|
||||||
|
if (stateStringified) {
|
||||||
|
// cleanup state from storage
|
||||||
|
this._oauthStorage.removeItem(stateKey);
|
||||||
|
return JSON.parse(stateStringified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_REDIRECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private configureAuth(config: AuthConfig) {
|
||||||
|
this.oauthService.configure(config);
|
||||||
|
this.oauthService.tokenValidationHandler = new JwksValidationHandler();
|
||||||
|
|
||||||
|
if (config.sessionChecksEnabled) {
|
||||||
|
this.oauthService.events.pipe(filter((event) => event.type === 'session_terminated')).subscribe(() => {
|
||||||
|
this.oauthService.logOut();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.ensureDiscoveryDocument().then(() =>
|
||||||
|
void this.oauthService.setupAutomaticSilentRefresh()
|
||||||
|
).catch(() => {
|
||||||
|
// catch error to prevent the app from crashing when trying to access unprotected routes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateIDPConfiguration(config: AuthConfig) {
|
||||||
|
this.oauthService.configure(config);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { MockProvider } from 'ng-mocks';
|
||||||
|
import { AuthService } from '../../auth.service';
|
||||||
|
import { AuthenticationConfirmationComponent } from './authentication-confirmation.component';
|
||||||
|
|
||||||
|
describe('AuthenticationConfirmationComponent', () => {
|
||||||
|
let component: AuthenticationConfirmationComponent;
|
||||||
|
let fixture: ComponentFixture<AuthenticationConfirmationComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [AuthenticationConfirmationComponent],
|
||||||
|
providers: [
|
||||||
|
MockProvider(AuthService, {
|
||||||
|
loginCallback() {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
imports: [RouterTestingModule]
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AuthenticationConfirmationComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,41 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { from, of } from 'rxjs';
|
||||||
|
import { catchError, first, map } from 'rxjs/operators';
|
||||||
|
import { AuthService } from '../../auth.service';
|
||||||
|
|
||||||
|
const ROUTE_DEFAULT = '/';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: '',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class AuthenticationConfirmationComponent {
|
||||||
|
constructor(private auth: AuthService, private _router: Router) {
|
||||||
|
const routeStored$ = from(this.auth.loginCallback()).pipe(
|
||||||
|
map((route) => route || ROUTE_DEFAULT),
|
||||||
|
catchError(() => of(ROUTE_DEFAULT))
|
||||||
|
);
|
||||||
|
|
||||||
|
routeStored$.pipe(first()).subscribe((route) => {
|
||||||
|
this._router.navigateByUrl(route, { replaceUrl: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -15,46 +15,32 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Authentication } from '@alfresco/adf-core/auth';
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable, from, throwError, Observer, ReplaySubject } from 'rxjs';
|
import { Observable, from } from 'rxjs';
|
||||||
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||||
import { CookieService } from '../../common/services/cookie.service';
|
import { CookieService } from '../../common/services/cookie.service';
|
||||||
import { LogService } from '../../common/services/log.service';
|
import { LogService } from '../../common/services/log.service';
|
||||||
import { RedirectionModel } from '../models/redirection.model';
|
|
||||||
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
|
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
|
||||||
import { map, catchError, tap } from 'rxjs/operators';
|
import { map, catchError, tap } from 'rxjs/operators';
|
||||||
import { HttpHeaders } from '@angular/common/http';
|
|
||||||
import { JwtHelperService } from './jwt-helper.service';
|
import { JwtHelperService } from './jwt-helper.service';
|
||||||
import { StorageService } from '../../common/services/storage.service';
|
import { StorageService } from '../../common/services/storage.service';
|
||||||
|
import { OauthConfigModel } from '../models/oauth-config.model';
|
||||||
const REMEMBER_ME_COOKIE_KEY = 'ALFRESCO_REMEMBER_ME';
|
import { BaseAuthenticationService } from '../../services/base-authentication.service';
|
||||||
const REMEMBER_ME_UNTIL = 1000 * 60 * 60 * 24 * 30;
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class AuthenticationService extends Authentication {
|
export class AuthenticationService extends BaseAuthenticationService {
|
||||||
private redirectUrl: RedirectionModel = null;
|
readonly supportCodeFlow = false;
|
||||||
|
|
||||||
private bearerExcludedUrls: string[] = ['auth/realms', 'resources/', 'assets/'];
|
|
||||||
/**
|
|
||||||
* Emits login event
|
|
||||||
*/
|
|
||||||
onLogin: ReplaySubject<any> = new ReplaySubject<any>(1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emits logout event
|
|
||||||
*/
|
|
||||||
onLogout: ReplaySubject<any> = new ReplaySubject<any>(1);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private appConfig: AppConfigService,
|
alfrescoApi: AlfrescoApiService,
|
||||||
private storageService: StorageService,
|
appConfig: AppConfigService,
|
||||||
private alfrescoApi: AlfrescoApiService,
|
cookie: CookieService,
|
||||||
private cookie: CookieService,
|
logService: LogService,
|
||||||
private logService: LogService) {
|
private storageService: StorageService
|
||||||
super();
|
) {
|
||||||
|
super(alfrescoApi, appConfig, cookie, logService);
|
||||||
this.alfrescoApi.alfrescoApiInitialized.subscribe(() => {
|
this.alfrescoApi.alfrescoApiInitialized.subscribe(() => {
|
||||||
this.alfrescoApi.getInstance().reply('logged-in', () => {
|
this.alfrescoApi.getInstance().reply('logged-in', () => {
|
||||||
this.onLogin.next();
|
this.onLogin.next();
|
||||||
@ -84,15 +70,6 @@ export class AuthenticationService extends Authentication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Does kerberos enabled?
|
|
||||||
*
|
|
||||||
* @returns True if enabled, false otherwise
|
|
||||||
*/
|
|
||||||
isKerberosEnabled(): boolean {
|
|
||||||
return this.appConfig.get<boolean>(AppConfigValues.AUTH_WITH_CREDENTIALS, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the provider support OAuth?
|
* Does the provider support OAuth?
|
||||||
*
|
*
|
||||||
@ -102,37 +79,6 @@ export class AuthenticationService extends Authentication {
|
|||||||
return this.alfrescoApi.getInstance().isOauthConfiguration();
|
return this.alfrescoApi.getInstance().isOauthConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the provider support BPM?
|
|
||||||
*
|
|
||||||
* @returns True if supported, false otherwise
|
|
||||||
*/
|
|
||||||
isBPMProvider(): boolean {
|
|
||||||
return this.alfrescoApi.getInstance().isBpmConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the provider support both ECM and BPM?
|
|
||||||
*
|
|
||||||
* @returns True if both are supported, false otherwise
|
|
||||||
*/
|
|
||||||
isALLProvider(): boolean {
|
|
||||||
return this.alfrescoApi.getInstance().isEcmBpmConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs the user in.
|
* Logs the user in.
|
||||||
*
|
*
|
||||||
@ -142,18 +88,17 @@ export class AuthenticationService extends Authentication {
|
|||||||
* @returns Object with auth type ("ECM", "BPM" or "ALL") and auth ticket
|
* @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 }> {
|
login(username: string, password: string, rememberMe: boolean = false): Observable<{ type: string; ticket: any }> {
|
||||||
return from(this.alfrescoApi.getInstance().login(username, password))
|
return from(this.alfrescoApi.getInstance().login(username, password)).pipe(
|
||||||
.pipe(
|
map((response: any) => {
|
||||||
map((response: any) => {
|
this.saveRememberMeCookie(rememberMe);
|
||||||
this.saveRememberMeCookie(rememberMe);
|
this.onLogin.next(response);
|
||||||
this.onLogin.next(response);
|
return {
|
||||||
return {
|
type: this.appConfig.get(AppConfigValues.PROVIDERS),
|
||||||
type: this.appConfig.get(AppConfigValues.PROVIDERS),
|
ticket: response
|
||||||
ticket: response
|
};
|
||||||
};
|
}),
|
||||||
}),
|
catchError((err) => this.handleError(err))
|
||||||
catchError((err) => this.handleError(err))
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -163,31 +108,6 @@ export class AuthenticationService extends Authentication {
|
|||||||
this.alfrescoApi.getInstance().implicitLogin();
|
this.alfrescoApi.getInstance().implicitLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the "remember me" cookie as either a long-life cookie or a session cookie.
|
|
||||||
*
|
|
||||||
* @param rememberMe Enables a long-life cookie
|
|
||||||
*/
|
|
||||||
private saveRememberMeCookie(rememberMe: boolean): void {
|
|
||||||
let expiration = null;
|
|
||||||
|
|
||||||
if (rememberMe) {
|
|
||||||
expiration = new Date();
|
|
||||||
const time = expiration.getTime();
|
|
||||||
const expireTime = time + REMEMBER_ME_UNTIL;
|
|
||||||
expiration.setTime(expireTime);
|
|
||||||
}
|
|
||||||
this.cookie.setItem(REMEMBER_ME_COOKIE_KEY, '1', expiration, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs the user out.
|
* Logs the user out.
|
||||||
@ -195,14 +115,13 @@ export class AuthenticationService extends Authentication {
|
|||||||
* @returns Response event called when logout is complete
|
* @returns Response event called when logout is complete
|
||||||
*/
|
*/
|
||||||
logout() {
|
logout() {
|
||||||
return from(this.callApiLogout())
|
return from(this.callApiLogout()).pipe(
|
||||||
.pipe(
|
tap((response) => {
|
||||||
tap((response) => {
|
this.onLogout.next(response);
|
||||||
this.onLogout.next(response);
|
return response;
|
||||||
return response;
|
}),
|
||||||
}),
|
catchError((err) => this.handleError(err))
|
||||||
catchError((err) => this.handleError(err))
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private callApiLogout(): Promise<any> {
|
private callApiLogout(): Promise<any> {
|
||||||
@ -212,37 +131,6 @@ export class AuthenticationService extends Authentication {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the user is logged in on an ECM provider.
|
* Checks if the user is logged in on an ECM provider.
|
||||||
*
|
*
|
||||||
@ -273,67 +161,13 @@ export class AuthenticationService extends Authentication {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
isImplicitFlow(): boolean {
|
||||||
* Gets the ECM username.
|
const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
|
||||||
*
|
return !!oauth2?.implicitFlow;
|
||||||
* @returns The ECM username
|
|
||||||
*/
|
|
||||||
getEcmUsername(): string {
|
|
||||||
return this.alfrescoApi.getInstance().getEcmUsername();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
isAuthCodeFlow(): boolean {
|
||||||
* Gets the BPM username
|
return false;
|
||||||
*
|
|
||||||
* @returns The BPM username
|
|
||||||
*/
|
|
||||||
getBpmUsername(): string {
|
|
||||||
return this.alfrescoApi.getInstance().getBpmUsername();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sets the URL to redirect to after login.
|
|
||||||
*
|
|
||||||
* @param url URL to redirect to
|
|
||||||
*/
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prints an error message in the console browser
|
|
||||||
*
|
|
||||||
* @param error Error message
|
|
||||||
* @returns Object representing the error message
|
|
||||||
*/
|
|
||||||
handleError(error: any): Observable<any> {
|
|
||||||
this.logService.error('Error when logging in', error);
|
|
||||||
return throwError(error || 'Server error');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the set of URLs that the token bearer is excluded from.
|
|
||||||
*
|
|
||||||
* @returns Array of URL strings
|
|
||||||
*/
|
|
||||||
getBearerExcludedUrls(): string[] {
|
|
||||||
return this.bearerExcludedUrls;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -345,60 +179,5 @@ export class AuthenticationService extends Authentication {
|
|||||||
return this.storageService.getItem(JwtHelperService.USER_ACCESS_TOKEN);
|
return this.storageService.getItem(JwtHelperService.USER_ACCESS_TOKEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
reset() {}
|
||||||
* Adds the auth token to an HTTP header using the 'bearer' scheme.
|
|
||||||
*
|
|
||||||
* @param headersArg Header that will receive the token
|
|
||||||
* @returns The new header with the token added
|
|
||||||
*/
|
|
||||||
addTokenToHeader(headersArg?: HttpHeaders): Observable<HttpHeaders> {
|
|
||||||
return new Observable((observer: Observer<any>) => {
|
|
||||||
let headers = headersArg;
|
|
||||||
if (!headers) {
|
|
||||||
headers = new HttpHeaders();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const header = this.getAuthHeaders(headers);
|
|
||||||
|
|
||||||
observer.next(header);
|
|
||||||
observer.complete();
|
|
||||||
} catch (error) {
|
|
||||||
observer.error(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,6 @@ import { PipeModule } from './pipes/pipe.module';
|
|||||||
|
|
||||||
import { AlfrescoApiService } from './services/alfresco-api.service';
|
import { AlfrescoApiService } from './services/alfresco-api.service';
|
||||||
import { TranslationService } from './translation/translation.service';
|
import { TranslationService } from './translation/translation.service';
|
||||||
import { startupServiceFactory } from './services/startup-service-factory';
|
|
||||||
import { SortingPickerModule } from './sorting-picker/sorting-picker.module';
|
import { SortingPickerModule } from './sorting-picker/sorting-picker.module';
|
||||||
import { IconModule } from './icon/icon.module';
|
import { IconModule } from './icon/icon.module';
|
||||||
import { TranslateLoaderService } from './translation/translate-loader.service';
|
import { TranslateLoaderService } from './translation/translate-loader.service';
|
||||||
@ -64,6 +63,18 @@ import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angu
|
|||||||
import { AuthenticationService } from './auth/services/authentication.service';
|
import { AuthenticationService } from './auth/services/authentication.service';
|
||||||
import { MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar';
|
import { MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar';
|
||||||
import { IdentityUserInfoModule } from './identity-user-info/identity-user-info.module';
|
import { IdentityUserInfoModule } from './identity-user-info/identity-user-info.module';
|
||||||
|
import { AuthBearerInterceptor } from './auth/authentication-interceptor/auth-bearer.interceptor';
|
||||||
|
import { loadAppConfig } from './app-config/app-config.loader';
|
||||||
|
import { AppConfigService } from './app-config/app-config.service';
|
||||||
|
import { StorageService } from './common/services/storage.service';
|
||||||
|
import { AlfrescoApiLoaderService, createAlfrescoApiInstance } from './api-factories/alfresco-api-v2-loader.service';
|
||||||
|
import { AlfrescoApiServiceWithAngularBasedHttpClient } from './api-factories/alfresco-api-service-with-angular-based-http-client';
|
||||||
|
|
||||||
|
interface Config {
|
||||||
|
readonly useAngularBasedHttpClientInAlfrescoJs: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultConfig: Config = { useAngularBasedHttpClientInAlfrescoJs: false };
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -145,7 +156,7 @@ import { IdentityUserInfoModule } from './identity-user-info/identity-user-info.
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class CoreModule {
|
export class CoreModule {
|
||||||
static forRoot(): ModuleWithProviders<CoreModule> {
|
static forRoot(config: Config = defaultConfig): ModuleWithProviders<CoreModule> {
|
||||||
return {
|
return {
|
||||||
ngModule: CoreModule,
|
ngModule: CoreModule,
|
||||||
providers: [
|
providers: [
|
||||||
@ -154,11 +165,8 @@ export class CoreModule {
|
|||||||
{ provide: TranslateLoader, useClass: TranslateLoaderService },
|
{ provide: TranslateLoader, useClass: TranslateLoaderService },
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
useFactory: startupServiceFactory,
|
useFactory: loadAppConfig,
|
||||||
deps: [
|
deps: [ AppConfigService, StorageService ], multi: true
|
||||||
AlfrescoApiService
|
|
||||||
],
|
|
||||||
multi: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
@ -173,7 +181,18 @@ export class CoreModule {
|
|||||||
useValue: {
|
useValue: {
|
||||||
duration: 10000
|
duration: 10000
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
useFactory: createAlfrescoApiInstance,
|
||||||
|
deps: [ AlfrescoApiLoaderService ],
|
||||||
|
multi: true
|
||||||
|
},
|
||||||
|
{ provide: HTTP_INTERCEPTORS, useClass: AuthBearerInterceptor, multi: true },
|
||||||
|
...(config.useAngularBasedHttpClientInAlfrescoJs
|
||||||
|
? [{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceWithAngularBasedHttpClient }]
|
||||||
|
: []
|
||||||
|
)
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -169,6 +169,30 @@
|
|||||||
"ERROR_SINGULAR": "{{ name }} couldn't be deleted",
|
"ERROR_SINGULAR": "{{ name }} couldn't be deleted",
|
||||||
"ERROR_PLURAL": "{{ number }} items couldn't be deleted"
|
"ERROR_PLURAL": "{{ number }} items couldn't be deleted"
|
||||||
},
|
},
|
||||||
|
"HOST_SETTINGS": {
|
||||||
|
"TYPE-AUTH": "Authentication type",
|
||||||
|
"BASIC": "Basic Authentication",
|
||||||
|
"SSO": "SSO",
|
||||||
|
"IMPLICIT-FLOW": "Implicit Flow",
|
||||||
|
"CODE-FLOW": "Code Flow",
|
||||||
|
"PROVIDER": "Provider",
|
||||||
|
"REQUIRED": "This field is required",
|
||||||
|
"CS_URL_ERROR": "Content Services address doesn't match the URL format",
|
||||||
|
"PS_URL_ERROR": "Process Services address doesn't match the URL format",
|
||||||
|
"TITLE": "Settings",
|
||||||
|
"CS-HOST": "Content Services URL",
|
||||||
|
"BP-HOST": "Process Services URL",
|
||||||
|
"BACK": "Back",
|
||||||
|
"APPLY": "APPLY",
|
||||||
|
"NOT_VALID": "http(s)://host|ip:port(/path) not recognized, try a different URL.",
|
||||||
|
"REDIRECT": "Redirect URI",
|
||||||
|
"REDIRECT_LOGOUT": "Redirect URI Logout",
|
||||||
|
"SILENT": "Silent Login",
|
||||||
|
"SCOPE": "Scope",
|
||||||
|
"CLIENT": "Client ID",
|
||||||
|
"PUBLIC_URLS": "Public urls silent Login",
|
||||||
|
"SECRET": "Secret"
|
||||||
|
},
|
||||||
"CARDVIEW": {
|
"CARDVIEW": {
|
||||||
"KEYVALUEPAIRS": {
|
"KEYVALUEPAIRS": {
|
||||||
"ADD": "Add New",
|
"ADD": "Add New",
|
||||||
|
@ -43,23 +43,13 @@ export class AlfrescoApiService {
|
|||||||
return this.alfrescoApi;
|
return this.alfrescoApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(protected appConfig: AppConfigService, protected storageService: StorageService) {}
|
||||||
protected appConfig: AppConfigService,
|
|
||||||
protected storageService: StorageService) {
|
|
||||||
}
|
|
||||||
|
|
||||||
async load() {
|
async load(config: AlfrescoApiConfig): Promise<void> {
|
||||||
try {
|
this.currentAppConfig = config;
|
||||||
await this.appConfig.load();
|
|
||||||
this.storageService.prefix = this.appConfig.get<string>(AppConfigValues.STORAGE_PREFIX, '');
|
|
||||||
this.getCurrentAppConfig();
|
|
||||||
|
|
||||||
if (this.currentAppConfig.authType === 'OAUTH') {
|
if (config.authType === 'OAUTH') {
|
||||||
this.idpConfig = await this.appConfig.loadWellKnown(this.currentAppConfig.oauth2.host);
|
|
||||||
this.mapAlfrescoApiOpenIdConfig();
|
this.mapAlfrescoApiOpenIdConfig();
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
throw new Error('Something wrong happened when calling the app.config.json');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initAlfrescoApiWithConfig();
|
this.initAlfrescoApiWithConfig();
|
||||||
@ -69,7 +59,6 @@ export class AlfrescoApiService {
|
|||||||
async reset() {
|
async reset() {
|
||||||
this.getCurrentAppConfig();
|
this.getCurrentAppConfig();
|
||||||
if (this.currentAppConfig.authType === 'OAUTH') {
|
if (this.currentAppConfig.authType === 'OAUTH') {
|
||||||
this.idpConfig = await this.appConfig.loadWellKnown(this.currentAppConfig.oauth2.host);
|
|
||||||
this.mapAlfrescoApiOpenIdConfig();
|
this.mapAlfrescoApiOpenIdConfig();
|
||||||
}
|
}
|
||||||
this.initAlfrescoApiWithConfig();
|
this.initAlfrescoApiWithConfig();
|
||||||
@ -84,7 +73,8 @@ export class AlfrescoApiService {
|
|||||||
return oauth;
|
return oauth;
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapAlfrescoApiOpenIdConfig() {
|
private async mapAlfrescoApiOpenIdConfig() {
|
||||||
|
this.idpConfig = await this.appConfig.loadWellKnown(this.currentAppConfig.oauth2.host);
|
||||||
this.currentAppConfig.oauth2.tokenUrl = this.idpConfig.token_endpoint;
|
this.currentAppConfig.oauth2.tokenUrl = this.idpConfig.token_endpoint;
|
||||||
this.currentAppConfig.oauth2.authorizationUrl = this.idpConfig.authorization_endpoint;
|
this.currentAppConfig.oauth2.authorizationUrl = this.idpConfig.authorization_endpoint;
|
||||||
this.currentAppConfig.oauth2.logoutUrl = this.idpConfig.end_session_endpoint;
|
this.currentAppConfig.oauth2.logoutUrl = this.idpConfig.end_session_endpoint;
|
||||||
@ -117,11 +107,15 @@ export class AlfrescoApiService {
|
|||||||
if (this.alfrescoApi && this.isDifferentConfig(this.lastConfig, this.currentAppConfig)) {
|
if (this.alfrescoApi && this.isDifferentConfig(this.lastConfig, this.currentAppConfig)) {
|
||||||
this.alfrescoApi.setConfig(this.currentAppConfig);
|
this.alfrescoApi.setConfig(this.currentAppConfig);
|
||||||
} else {
|
} else {
|
||||||
this.alfrescoApi = new AlfrescoApi(this.currentAppConfig);
|
this.alfrescoApi = this.createInstance(this.currentAppConfig);
|
||||||
}
|
}
|
||||||
this.lastConfig = this.currentAppConfig;
|
this.lastConfig = this.currentAppConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createInstance(config: AlfrescoApiConfig): AlfrescoApi {
|
||||||
|
return new AlfrescoApi(config);
|
||||||
|
}
|
||||||
|
|
||||||
isDifferentConfig(lastConfig: AlfrescoApiConfig, newConfig: AlfrescoApiConfig) {
|
isDifferentConfig(lastConfig: AlfrescoApiConfig, newConfig: AlfrescoApiConfig) {
|
||||||
return JSON.stringify(lastConfig) !== JSON.stringify(newConfig);
|
return JSON.stringify(lastConfig) !== JSON.stringify(newConfig);
|
||||||
}
|
}
|
||||||
|
285
lib/core/src/lib/services/base-authentication.service.ts
Normal file
285
lib/core/src/lib/services/base-authentication.service.ts
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PeopleApi, UserProfileApi, UserRepresentation } from '@alfresco/js-api';
|
||||||
|
import { HttpHeaders } from '@angular/common/http';
|
||||||
|
import { RedirectionModel } from '../auth/models/redirection.model';
|
||||||
|
import { from, Observable, Observer, ReplaySubject, throwError } from 'rxjs';
|
||||||
|
import { AppConfigService, AppConfigValues } from '../app-config/app-config.service';
|
||||||
|
import { AlfrescoApiService } from './alfresco-api.service';
|
||||||
|
import { CookieService } from '../common/services/cookie.service';
|
||||||
|
import { LogService } from '../common/services/log.service';
|
||||||
|
|
||||||
|
const REMEMBER_ME_COOKIE_KEY = 'ALFRESCO_REMEMBER_ME';
|
||||||
|
const REMEMBER_ME_UNTIL = 1000 * 60 * 60 * 24 * 30;
|
||||||
|
|
||||||
|
export abstract class BaseAuthenticationService {
|
||||||
|
protected bearerExcludedUrls: readonly string[] = ['resources/', 'assets/', 'auth/realms', 'idp/'];
|
||||||
|
protected redirectUrl: RedirectionModel = null;
|
||||||
|
|
||||||
|
onLogin = new ReplaySubject<any>(1);
|
||||||
|
onLogout = new ReplaySubject<any>(1);
|
||||||
|
|
||||||
|
_peopleApi: PeopleApi;
|
||||||
|
get peopleApi(): PeopleApi {
|
||||||
|
this._peopleApi = this._peopleApi ?? new PeopleApi(this.alfrescoApi.getInstance());
|
||||||
|
return this._peopleApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
_profileApi: UserProfileApi;
|
||||||
|
get profileApi(): UserProfileApi {
|
||||||
|
this._profileApi = this._profileApi ?? new UserProfileApi(this.alfrescoApi.getInstance());
|
||||||
|
return this._profileApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected alfrescoApi: AlfrescoApiService,
|
||||||
|
protected appConfig: AppConfigService,
|
||||||
|
protected cookie: CookieService,
|
||||||
|
private logService: LogService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
abstract readonly supportCodeFlow: boolean;
|
||||||
|
abstract getToken(): string;
|
||||||
|
abstract isLoggedIn(): boolean;
|
||||||
|
abstract isLoggedInWith(provider: string): boolean;
|
||||||
|
abstract isOauth(): boolean;
|
||||||
|
abstract isImplicitFlow(): boolean;
|
||||||
|
abstract isAuthCodeFlow(): 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 reset(): void;
|
||||||
|
|
||||||
|
getBearerExcludedUrls(): readonly string[] {
|
||||||
|
return this.bearerExcludedUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the auth token to an HTTP header using the 'bearer' scheme.
|
||||||
|
*
|
||||||
|
* @param headersArg Header that will receive the token
|
||||||
|
* @returns The new header with the token added
|
||||||
|
*/
|
||||||
|
addTokenToHeader(headersArg?: HttpHeaders): Observable<HttpHeaders> {
|
||||||
|
return new Observable((observer: Observer<any>) => {
|
||||||
|
let headers = headersArg;
|
||||||
|
if (!headers) {
|
||||||
|
headers = new HttpHeaders();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const header = this.getAuthHeaders(headers);
|
||||||
|
|
||||||
|
observer.next(header);
|
||||||
|
observer.complete();
|
||||||
|
} catch (error) {
|
||||||
|
observer.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the ECM username.
|
||||||
|
*
|
||||||
|
* @returns The ECM username
|
||||||
|
*/
|
||||||
|
getEcmUsername(): string {
|
||||||
|
return this.alfrescoApi.getInstance().getEcmUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the BPM username
|
||||||
|
*
|
||||||
|
* @returns The BPM username
|
||||||
|
*/
|
||||||
|
getBpmUsername(): string {
|
||||||
|
return this.alfrescoApi.getInstance().getBpmUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the provider support BPM?
|
||||||
|
*
|
||||||
|
* @returns True if supported, false otherwise
|
||||||
|
*/
|
||||||
|
isBPMProvider(): boolean {
|
||||||
|
return this.alfrescoApi.getInstance().isBpmConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the provider support both ECM and BPM?
|
||||||
|
*
|
||||||
|
* @returns True if both are supported, false otherwise
|
||||||
|
*/
|
||||||
|
isALLProvider(): boolean {
|
||||||
|
return this.alfrescoApi.getInstance().isEcmBpmConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets information about the user currently logged into APS.
|
||||||
|
*
|
||||||
|
* @returns User information
|
||||||
|
*/
|
||||||
|
getBpmLoggedUser(): Observable<UserRepresentation> {
|
||||||
|
return from(this.profileApi.getProfile());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints an error message in the console browser
|
||||||
|
*
|
||||||
|
* @param error Error message
|
||||||
|
* @returns Object representing the error message
|
||||||
|
*/
|
||||||
|
handleError(error: any): Observable<any> {
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
@ -15,9 +15,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AlfrescoApiService } from './alfresco-api.service';
|
import { AppConfigService, AppConfigValues } from '../app-config/app-config.service';
|
||||||
|
import { StorageService } from '../common/services/storage.service';
|
||||||
|
|
||||||
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
export function loadAppConfig(appConfigService: AppConfigService, storageService: StorageService) {
|
||||||
export function startupServiceFactory(alfrescoApiService: AlfrescoApiService) {
|
return () =>
|
||||||
return () => alfrescoApiService.load();
|
appConfigService.load().then(() => {
|
||||||
|
storageService.prefix = appConfigService.get<string>(AppConfigValues.STORAGE_PREFIX, '');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import { AlfrescoApiService } from '../services/alfresco-api.service';
|
|||||||
import { StorageService } from '../common/services/storage.service';
|
import { StorageService } from '../common/services/storage.service';
|
||||||
import { UserPreferencesService } from '../common/services/user-preferences.service';
|
import { UserPreferencesService } from '../common/services/user-preferences.service';
|
||||||
import { DemoForm } from '../mock/form/demo-form.mock';
|
import { DemoForm } from '../mock/form/demo-form.mock';
|
||||||
|
import { AuthenticationService } from '../auth/services/authentication.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -29,11 +30,13 @@ export class CoreAutomationService {
|
|||||||
|
|
||||||
public forms = new DemoForm();
|
public forms = new DemoForm();
|
||||||
|
|
||||||
constructor(private appConfigService: AppConfigService,
|
constructor(
|
||||||
private alfrescoApiService: AlfrescoApiService,
|
private appConfigService: AppConfigService,
|
||||||
private userPreferencesService: UserPreferencesService,
|
private alfrescoApiService: AlfrescoApiService,
|
||||||
private storageService: StorageService) {
|
private userPreferencesService: UserPreferencesService,
|
||||||
}
|
private storageService: StorageService,
|
||||||
|
private auth: AuthenticationService
|
||||||
|
) {}
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const adfProxy = window['adf'] || {};
|
const adfProxy = window['adf'] || {};
|
||||||
@ -72,6 +75,7 @@ export class CoreAutomationService {
|
|||||||
|
|
||||||
adfProxy.apiReset = () => {
|
adfProxy.apiReset = () => {
|
||||||
this.alfrescoApiService.reset();
|
this.alfrescoApiService.reset();
|
||||||
|
this.auth.reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
window['adf'] = adfProxy;
|
window['adf'] = adfProxy;
|
||||||
|
@ -59,3 +59,4 @@ export * from './lib/common';
|
|||||||
|
|
||||||
export * from './lib/material.module';
|
export * from './lib/material.module';
|
||||||
export * from './lib/core.module';
|
export * from './lib/core.module';
|
||||||
|
export { AuthModule } from './lib/auth/oidc/auth.module';
|
||||||
|
30895
package-lock.json
generated
30895
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -76,6 +76,8 @@
|
|||||||
"@mat-datetimepicker/moment": "^9.0.68",
|
"@mat-datetimepicker/moment": "^9.0.68",
|
||||||
"@ngx-translate/core": "^14.0.0",
|
"@ngx-translate/core": "^14.0.0",
|
||||||
"@storybook/core-server": "6.4.22",
|
"@storybook/core-server": "6.4.22",
|
||||||
|
"angular-oauth2-oidc": "^13.0.1",
|
||||||
|
"angular-oauth2-oidc-jwks": "^13.0.1",
|
||||||
"apollo-angular": "^4.0.1",
|
"apollo-angular": "^4.0.1",
|
||||||
"chart.js": "2.9.4",
|
"chart.js": "2.9.4",
|
||||||
"cropperjs": "1.5.13",
|
"cropperjs": "1.5.13",
|
||||||
@ -99,13 +101,14 @@
|
|||||||
"@angular-eslint/eslint-plugin-template": "14.3.0",
|
"@angular-eslint/eslint-plugin-template": "14.3.0",
|
||||||
"@angular-eslint/template-parser": "14.0.2",
|
"@angular-eslint/template-parser": "14.0.2",
|
||||||
"@angular/cli": "~14.1.0",
|
"@angular/cli": "~14.1.0",
|
||||||
|
"@angular-devkit/schematics": "~14.1.0",
|
||||||
"@angular/compiler-cli": "14.1.3",
|
"@angular/compiler-cli": "14.1.3",
|
||||||
"@editorjs/code": "2.8.0",
|
"@editorjs/code": "2.8.0",
|
||||||
"@editorjs/inline-code": "1.4.0",
|
"@editorjs/inline-code": "1.4.0",
|
||||||
"@editorjs/marker": "1.2.2",
|
"@editorjs/marker": "1.2.2",
|
||||||
"@nrwl/angular": "14.8.6",
|
"@nrwl/angular": "14.8.6",
|
||||||
"@nrwl/cli": "14.5.4",
|
"@nrwl/cli": "14.5.4",
|
||||||
"@nrwl/eslint-plugin-nx": "^14.5.4",
|
"@nrwl/eslint-plugin-nx": "14.5.4",
|
||||||
"@nrwl/node": "14.5.4",
|
"@nrwl/node": "14.5.4",
|
||||||
"@nrwl/storybook": "14.5.4",
|
"@nrwl/storybook": "14.5.4",
|
||||||
"@nrwl/workspace": "14.5.4",
|
"@nrwl/workspace": "14.5.4",
|
||||||
@ -159,6 +162,7 @@
|
|||||||
"lint-staged": "^13.1.2",
|
"lint-staged": "^13.1.2",
|
||||||
"lite-server": "^2.6.1",
|
"lite-server": "^2.6.1",
|
||||||
"mini-css-extract-plugin": "^1.6.0",
|
"mini-css-extract-plugin": "^1.6.0",
|
||||||
|
"nconf": "^0.11.1",
|
||||||
"ng-mocks": "^14.2.3",
|
"ng-mocks": "^14.2.3",
|
||||||
"ng-packagr": "14.0.3",
|
"ng-packagr": "14.0.3",
|
||||||
"nx": "14.4.2",
|
"nx": "14.4.2",
|
||||||
@ -177,7 +181,8 @@
|
|||||||
"tsconfig-paths": "^4.1.1",
|
"tsconfig-paths": "^4.1.1",
|
||||||
"typescript": "4.7.4",
|
"typescript": "4.7.4",
|
||||||
"webdriver-manager": "12.1.8",
|
"webdriver-manager": "12.1.8",
|
||||||
"webpack-cli": "^5.0.1"
|
"webpack-cli": "^5.0.1",
|
||||||
|
"webpack": "^5.0.1"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bundlesize": [
|
"bundlesize": [
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"@alfresco/adf-core/*": ["lib/core/*/public-api.ts"],
|
"@alfresco/adf-core/*": ["lib/core/*/public-api.ts"],
|
||||||
"@alfresco/adf-core/auth": ["lib/core/auth/src/index.ts"],
|
"@alfresco/adf-core/auth": ["lib/core/auth/src/index.ts"],
|
||||||
"@alfresco/adf-core/shell": ["lib/core/shell/src/index.ts"],
|
"@alfresco/adf-core/shell": ["lib/core/shell/src/index.ts"],
|
||||||
|
"@alfresco/adf-core/api": ["lib/core/api/src/index.ts"],
|
||||||
"@alfresco/adf-extensions": ["lib/extensions"],
|
"@alfresco/adf-extensions": ["lib/extensions"],
|
||||||
"@alfresco/adf-insights": ["lib/insights"],
|
"@alfresco/adf-insights": ["lib/insights"],
|
||||||
"@alfresco/adf-process-services": ["lib/process-services"],
|
"@alfresco/adf-process-services": ["lib/process-services"],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user