[ACS-5279] enhanced oath2 configuration handling (#8575)

* [ACS-5279] enhanced oath2 configuration handling

* fix tests

* fix schema
This commit is contained in:
Denys Vuika
2023-06-02 12:02:50 +01:00
committed by GitHub
parent ea5c3466ef
commit 654acd553f
13 changed files with 83 additions and 105 deletions

View File

@@ -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<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
const oauth = this.appConfig.oauth2;
if (oauth) {
oauth.redirectUri = window.location.origin + window.location.pathname;

View File

@@ -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, string>): string {
let result = str;

View File

@@ -1517,7 +1517,7 @@
"type": "string"
},
"silentLogin": {
"type": "boolean"
"type": ["boolean", "string"]
},
"authPath": {
"type": "string"

View File

@@ -118,13 +118,7 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
}
protected getOauthConfig(): OauthConfigModel {
return (
this.appConfigService &&
this.appConfigService.get<OauthConfigModel>(
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<OauthConfigModel>(
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<OauthConfigModel>(
AppConfigValues.OAUTHCONFIG,
null
);
const oauth = this.appConfigService.oauth2;;
return this.authenticationService.isOauth() && oauth && oauth.silentLogin;
}

View File

@@ -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'
]
};

View File

@@ -25,6 +25,7 @@ export interface OauthConfigModel {
silentLogin?: boolean;
secret?: string;
redirectUriLogout?: string;
redirectSilentIframeUri?: string;
refreshTokenTimeout?: number;
publicUrls: string[];
}

View File

@@ -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<any>(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',

View File

@@ -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<AuthConfig> {
@@ -45,7 +44,7 @@ export class AuthConfigService {
}
loadAppConfig(): AuthConfig {
const oauth2: OauthConfigModel = Object.assign({}, this.appConfigService.get<OauthConfigModel>(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<OauthConfigModel>(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

View File

@@ -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<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
return !!oauth2?.implicitFlow;
isImplicitFlow(): boolean {
return !!this.appConfig.oauth2?.implicitFlow;
}
isAuthCodeFlow() {
const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get<OauthConfigModel>(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<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
const oauth2 = this.appConfig.oauth2;
if (config.oidc && oauth2.silentLogin) {
this.auth.login();

View File

@@ -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<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
return !!oauth2?.implicitFlow;
return !!this.appConfig.oauth2?.implicitFlow;
}
isAuthCodeFlow(): boolean {

View File

@@ -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<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null);
const oauth = this.appConfig.oauth2;
if (oauth && oauth.silentLogin) {
this.redirectToImplicitLogin();
} else if (oauth && oauth.implicitFlow) {

View File

@@ -65,7 +65,8 @@ export class AlfrescoApiService {
}
private getAuthWithFixedOriginLocation(): OauthConfigModel {
const oauth: OauthConfigModel = Object.assign({}, this.appConfig.get<OauthConfigModel>(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;