From 654acd553f09275556db39db6d8e9affb44cea80 Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Fri, 2 Jun 2023 12:02:50 +0100 Subject: [PATCH] [ACS-5279] enhanced oath2 configuration handling (#8575) * [ACS-5279] enhanced oath2 configuration handling * fix tests * fix schema --- .../settings/host-settings.component.ts | 4 +- .../alfresco-api-v2-loader.service.ts | 3 +- .../src/lib/app-config/app-config.service.ts | 18 ++++++ .../src/lib/app-config/app.config.schema.json | 2 +- .../src/lib/auth/guard/auth-guard-base.ts | 23 ++------ .../lib/auth/mock/auth-config.service.mock.ts | 50 ---------------- .../src/lib/auth/models/oauth-config.model.ts | 1 + .../lib/auth/oidc/auth-config.service.spec.ts | 57 ++++++++++++++----- .../src/lib/auth/oidc/auth-config.service.ts | 7 +-- .../auth/oidc/oidc-authentication.service.ts | 13 ++--- .../auth/services/authentication.service.ts | 4 +- .../lib/login/components/login.component.ts | 3 +- .../src/lib/services/alfresco-api.service.ts | 3 +- 13 files changed, 83 insertions(+), 105 deletions(-) delete mode 100644 lib/core/src/lib/auth/mock/auth-config.service.mock.ts diff --git a/demo-shell/src/app/components/settings/host-settings.component.ts b/demo-shell/src/app/components/settings/host-settings.component.ts index 0df6c574ac..44ca02a74d 100644 --- a/demo-shell/src/app/components/settings/host-settings.component.ts +++ b/demo-shell/src/app/components/settings/host-settings.component.ts @@ -17,7 +17,7 @@ import { Component, EventEmitter, Output, ViewEncapsulation, OnInit, Input } from '@angular/core'; import { Validators, UntypedFormGroup, UntypedFormBuilder, UntypedFormControl } from '@angular/forms'; -import { AppConfigService, AppConfigValues, StorageService, AlfrescoApiService, OauthConfigModel, AuthenticationService } from '@alfresco/adf-core'; +import { AppConfigService, AppConfigValues, StorageService, AlfrescoApiService, AuthenticationService } from '@alfresco/adf-core'; import { ENTER } from '@angular/cdk/keycodes'; export const HOST_REGEX = '^(http|https):\/\/.*[^/]$'; @@ -137,7 +137,7 @@ export class HostSettingsComponent implements OnInit { } private createOAuthFormGroup(): UntypedFormGroup { - const oauth = this.appConfig.get(AppConfigValues.OAUTHCONFIG, {} as any); + const oauth = this.appConfig.oauth2; return this.formBuilder.group({ host: [oauth.host, [Validators.required, Validators.pattern(HOST_REGEX)]], diff --git a/lib/core/src/lib/api-factories/alfresco-api-v2-loader.service.ts b/lib/core/src/lib/api-factories/alfresco-api-v2-loader.service.ts index 39794ce078..098c16e9ba 100644 --- a/lib/core/src/lib/api-factories/alfresco-api-v2-loader.service.ts +++ b/lib/core/src/lib/api-factories/alfresco-api-v2-loader.service.ts @@ -18,7 +18,6 @@ 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) { @@ -37,7 +36,7 @@ export class AlfrescoApiLoaderService { } private async initAngularAlfrescoApi() { - const oauth: OauthConfigModel = Object.assign({}, this.appConfig.get(AppConfigValues.OAUTHCONFIG, null)); + const oauth = this.appConfig.oauth2; if (oauth) { oauth.redirectUri = window.location.origin + window.location.pathname; diff --git a/lib/core/src/lib/app-config/app-config.service.ts b/lib/core/src/lib/app-config/app-config.service.ts index 03faa4fb6c..806da3f2c1 100644 --- a/lib/core/src/lib/app-config/app-config.service.ts +++ b/lib/core/src/lib/app-config/app-config.service.ts @@ -22,6 +22,7 @@ import { Observable, Subject } from 'rxjs'; import { map, distinctUntilChanged, take } from 'rxjs/operators'; import { ExtensionConfig, ExtensionService, mergeObjects } from '@alfresco/adf-extensions'; import { OpenidConfiguration } from '../auth/interfaces/openid-configuration.interface'; +import { OauthConfigModel } from '../auth/models/oauth-config.model'; /* spellchecker: disable */ // eslint-disable-next-line no-shadow @@ -229,6 +230,23 @@ export class AppConfigService { }); } + /** + * OAuth2 configuration + */ + get oauth2(): OauthConfigModel { + const config = this.get(AppConfigValues.OAUTHCONFIG, {}); + const implicitFlow = config['implicitFlow'] === true || config['implicitFlow'] === 'true'; + const silentLogin = config['silentLogin'] === true || config['silentLogin'] === 'true'; + const codeFlow = config['codeFlow'] === true || config['codeFlow'] === 'true'; + + return { + ...(config as OauthConfigModel), + implicitFlow, + silentLogin, + codeFlow + }; + } + private formatString(str: string, keywords: Map): string { let result = str; diff --git a/lib/core/src/lib/app-config/app.config.schema.json b/lib/core/src/lib/app-config/app.config.schema.json index 44ed4df302..0d17ff326a 100644 --- a/lib/core/src/lib/app-config/app.config.schema.json +++ b/lib/core/src/lib/app-config/app.config.schema.json @@ -1517,7 +1517,7 @@ "type": "string" }, "silentLogin": { - "type": "boolean" + "type": ["boolean", "string"] }, "authPath": { "type": "string" diff --git a/lib/core/src/lib/auth/guard/auth-guard-base.ts b/lib/core/src/lib/auth/guard/auth-guard-base.ts index 3ddba3a0a2..2b37795ca1 100644 --- a/lib/core/src/lib/auth/guard/auth-guard-base.ts +++ b/lib/core/src/lib/auth/guard/auth-guard-base.ts @@ -118,13 +118,7 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild { } protected getOauthConfig(): OauthConfigModel { - return ( - this.appConfigService && - this.appConfigService.get( - AppConfigValues.OAUTHCONFIG, - null - ) - ); + return this.appConfigService.oauth2; } protected getLoginRoute(): string { @@ -148,21 +142,12 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild { } protected isOAuthWithoutSilentLogin(): boolean { - const oauth = this.appConfigService.get( - AppConfigValues.OAUTHCONFIG, - null - ); - return ( - this.authenticationService.isOauth() && !!oauth && !oauth.silentLogin - ); + const oauth = this.appConfigService.oauth2; + return this.authenticationService.isOauth() && !!oauth && !oauth.silentLogin; } protected isSilentLogin(): boolean { - const oauth = this.appConfigService.get( - AppConfigValues.OAUTHCONFIG, - null - ); - + const oauth = this.appConfigService.oauth2;; return this.authenticationService.isOauth() && oauth && oauth.silentLogin; } diff --git a/lib/core/src/lib/auth/mock/auth-config.service.mock.ts b/lib/core/src/lib/auth/mock/auth-config.service.mock.ts deleted file mode 100644 index 2ca8811594..0000000000 --- a/lib/core/src/lib/auth/mock/auth-config.service.mock.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*! - * @license - * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -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' - ] -}; diff --git a/lib/core/src/lib/auth/models/oauth-config.model.ts b/lib/core/src/lib/auth/models/oauth-config.model.ts index ec143a1c58..c52d0c1047 100644 --- a/lib/core/src/lib/auth/models/oauth-config.model.ts +++ b/lib/core/src/lib/auth/models/oauth-config.model.ts @@ -25,6 +25,7 @@ export interface OauthConfigModel { silentLogin?: boolean; secret?: string; redirectUriLogout?: string; + redirectSilentIframeUri?: string; refreshTokenTimeout?: number; publicUrls: string[]; } diff --git a/lib/core/src/lib/auth/oidc/auth-config.service.spec.ts b/lib/core/src/lib/auth/oidc/auth-config.service.spec.ts index 90392c0c1b..e9f53fdc7a 100644 --- a/lib/core/src/lib/auth/oidc/auth-config.service.spec.ts +++ b/lib/core/src/lib/auth/oidc/auth-config.service.spec.ts @@ -20,36 +20,67 @@ 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'; +import { AuthConfig } from 'angular-oauth2-oidc'; +import { OauthConfigModel } from '../models/oauth-config.model'; describe('AuthConfigService', () => { let service: AuthConfigService; - let appConfigServiceMock; + let appConfigService: AppConfigService; + + const mockAuthConfigImplicitFlow: OauthConfigModel = { + 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' + ] + }; + + const mockAuthConfigCodeFlow: OauthConfigModel = { + host: 'http://localhost:3000/auth/realms/alfresco', + clientId: 'alfresco', + scope: 'openid profile email', + secret: '', + implicitFlow: false, + codeFlow: true, + silentLogin: true, + redirectSilentIframeUri: 'http://localhost:3000/assets/silent-refresh.html', + redirectUri: '/', + redirectUriLogout: '#/logout', + publicUrls: [ + '**/preview/s/*', + '**/settings', + '**/logout' + ] + }; beforeEach(() => { - appConfigServiceMock = jasmine.createSpyObj(['get'], { onLoad: EMPTY }); - TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [ - { provide: AUTH_MODULE_CONFIG, useValue: { useHash: true } }, - { provide: AppConfigService, useValue: appConfigServiceMock } + { provide: AUTH_MODULE_CONFIG, useValue: { useHash: true } } ] }); service = TestBed.inject(AuthConfigService); spyOn(service, 'getLocationOrigin').and.returnValue('http://localhost:3000'); - }); - it('should be created', () => { - expect(service).toBeTruthy(); + appConfigService = TestBed.inject(AppConfigService); + appConfigService.onLoad = EMPTY; }); describe('load auth config using hash', () => { it('should load configuration if implicit flow is true ', async () => { - appConfigServiceMock.get.and.returnValue(mockAuthConfigImplicitFlow); - const expectedConfig = { + spyOnProperty(appConfigService, 'oauth2').and.returnValue(mockAuthConfigImplicitFlow); + const expectedConfig: AuthConfig = { oidc: true, issuer: 'http://localhost:3000/auth/realms/alfresco', redirectUri: 'http://localhost:3000/#/view/authentication-confirmation/?', @@ -64,7 +95,7 @@ describe('AuthConfigService', () => { }); it('should load configuration if code flow is true ', async () => { - appConfigServiceMock.get.and.returnValue(mockAuthConfigCodeFlow); + spyOnProperty(appConfigService, 'oauth2').and.returnValue(mockAuthConfigCodeFlow); const expectedConfig = { oidc: true, issuer: 'http://localhost:3000/auth/realms/alfresco', diff --git a/lib/core/src/lib/auth/oidc/auth-config.service.ts b/lib/core/src/lib/auth/oidc/auth-config.service.ts index cf46384d3b..a33b05a436 100644 --- a/lib/core/src/lib/auth/oidc/auth-config.service.ts +++ b/lib/core/src/lib/auth/oidc/auth-config.service.ts @@ -18,8 +18,7 @@ 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 { AppConfigService } from '../../app-config/app-config.service'; import { AuthModuleConfig, AUTH_MODULE_CONFIG } from './auth-config'; export function authConfigFactory(authConfigService: AuthConfigService): Promise { @@ -45,7 +44,7 @@ export class AuthConfigService { } loadAppConfig(): AuthConfig { - const oauth2: OauthConfigModel = Object.assign({}, this.appConfigService.get(AppConfigValues.OAUTHCONFIG, null)); + const oauth2 = this.appConfigService.oauth2; const origin = this.getLocationOrigin(); const redirectUri = this.getRedirectUri(); @@ -73,7 +72,7 @@ export class AuthConfigService { ? `${this.getLocationOrigin()}/#/${viewUrl}` : `${this.getLocationOrigin()}/${viewUrl}`; - const oauth2: OauthConfigModel = Object.assign({}, this.appConfigService.get(AppConfigValues.OAUTHCONFIG, null)); + const oauth2 = this.appConfigService.oauth2; // 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 diff --git a/lib/core/src/lib/auth/oidc/oidc-authentication.service.ts b/lib/core/src/lib/auth/oidc/oidc-authentication.service.ts index b27bfa702e..8e3add6753 100644 --- a/lib/core/src/lib/auth/oidc/oidc-authentication.service.ts +++ b/lib/core/src/lib/auth/oidc/oidc-authentication.service.ts @@ -20,7 +20,6 @@ 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'; @@ -73,14 +72,12 @@ export class OIDCAuthenticationService extends BaseAuthenticationService { return this.appConfig.get(AppConfigValues.AUTHTYPE) === 'OAUTH'; } - isImplicitFlow() { - const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get(AppConfigValues.OAUTHCONFIG, null)); - return !!oauth2?.implicitFlow; + isImplicitFlow(): boolean { + return !!this.appConfig.oauth2?.implicitFlow; } - isAuthCodeFlow() { - const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get(AppConfigValues.OAUTHCONFIG, null)); - return !!oauth2?.codeFlow; + isAuthCodeFlow(): boolean { + return !!this.appConfig.oauth2?.codeFlow; } login(username: string, password: string, rememberMe: boolean = false): Observable<{ type: string; ticket: any }> { @@ -121,7 +118,7 @@ export class OIDCAuthenticationService extends BaseAuthenticationService { reset(): void { const config = this.authConfig.loadAppConfig(); this.auth.updateIDPConfiguration(config); - const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get(AppConfigValues.OAUTHCONFIG, null)); + const oauth2 = this.appConfig.oauth2; if (config.oidc && oauth2.silentLogin) { this.auth.login(); diff --git a/lib/core/src/lib/auth/services/authentication.service.ts b/lib/core/src/lib/auth/services/authentication.service.ts index 3570e98a49..2694c03e8c 100644 --- a/lib/core/src/lib/auth/services/authentication.service.ts +++ b/lib/core/src/lib/auth/services/authentication.service.ts @@ -24,7 +24,6 @@ import { AppConfigService, AppConfigValues } from '../../app-config/app-config.s import { map, catchError, tap } from 'rxjs/operators'; import { JwtHelperService } from './jwt-helper.service'; import { StorageService } from '../../common/services/storage.service'; -import { OauthConfigModel } from '../models/oauth-config.model'; import { BaseAuthenticationService } from '../../services/base-authentication.service'; @Injectable({ @@ -162,8 +161,7 @@ export class AuthenticationService extends BaseAuthenticationService { } isImplicitFlow(): boolean { - const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get(AppConfigValues.OAUTHCONFIG, null)); - return !!oauth2?.implicitFlow; + return !!this.appConfig.oauth2?.implicitFlow; } isAuthCodeFlow(): boolean { diff --git a/lib/core/src/lib/login/components/login.component.ts b/lib/core/src/lib/login/components/login.component.ts index 64ee05e308..c8a61048ed 100644 --- a/lib/core/src/lib/login/components/login.component.ts +++ b/lib/core/src/lib/login/components/login.component.ts @@ -22,7 +22,6 @@ import { import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { Router, ActivatedRoute, Params } from '@angular/router'; import { AuthenticationService } from '../../auth/services/authentication.service'; -import { OauthConfigModel } from '../../auth/models/oauth-config.model'; import { TranslationService } from '../../translation/translation.service'; import { UserPreferencesService } from '../../common/services/user-preferences.service'; import { AlfrescoApiService } from '../../services/alfresco-api.service'; @@ -154,7 +153,7 @@ export class LoginComponent implements OnInit, OnDestroy { } else { if (this.authService.isOauth()) { - const oauth: OauthConfigModel = this.appConfig.get(AppConfigValues.OAUTHCONFIG, null); + const oauth = this.appConfig.oauth2; if (oauth && oauth.silentLogin) { this.redirectToImplicitLogin(); } else if (oauth && oauth.implicitFlow) { diff --git a/lib/core/src/lib/services/alfresco-api.service.ts b/lib/core/src/lib/services/alfresco-api.service.ts index 85b771f91e..4d14a059e0 100644 --- a/lib/core/src/lib/services/alfresco-api.service.ts +++ b/lib/core/src/lib/services/alfresco-api.service.ts @@ -65,7 +65,8 @@ export class AlfrescoApiService { } private getAuthWithFixedOriginLocation(): OauthConfigModel { - const oauth: OauthConfigModel = Object.assign({}, this.appConfig.get(AppConfigValues.OAUTHCONFIG, null)); + const oauth = this.appConfig.oauth2; + if (oauth) { oauth.redirectUri = window.location.origin + window.location.pathname; oauth.redirectUriLogout = window.location.origin + window.location.pathname;