[AAE-8639] Discovery OpenId - Load discovery and pass info to jsapi (#7632)

* Load discovery and pass info to jsapi

* fix the roles empty scenario tests

* Make lint happier

* Rename the initApi method

* Add secret field

Co-authored-by: arditdomi <ardit.domi@hyland.com>
This commit is contained in:
Maurizio Vitale
2022-05-13 19:03:06 +01:00
committed by GitHub
parent 6fb1bda6a9
commit cec9297e14
10 changed files with 145 additions and 21 deletions

View File

@@ -3,7 +3,7 @@ require('dotenv').config();
const { getDeployedAppsProxy, getShareProxy, getApsProxy } = require('./proxy-helpers'); const { getDeployedAppsProxy, getShareProxy, getApsProxy } = require('./proxy-helpers');
const legacyHost = process.env.PROXY_HOST_ADF; const legacyHost = process.env.PROXY_HOST_ADF;
const cloudHost = process.env.CLOUD_PROXY_HOST_ADF; const cloudHost = process.env.CLOUD_PROXY_HOST_ADF || process.env.PROXY_HOST_ADF;
const cloudApps = process.env.APP_CONFIG_APPS_DEPLOYED; const cloudApps = process.env.APP_CONFIG_APPS_DEPLOYED;
const apsHost = process.env.PROXY_HOST_ADF; const apsHost = process.env.PROXY_HOST_ADF;

View File

@@ -21,6 +21,7 @@ import { ObjectUtils } from '../utils/object-utils';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { map, distinctUntilChanged, take } from 'rxjs/operators'; import { map, distinctUntilChanged, take } from 'rxjs/operators';
import { ExtensionConfig, ExtensionService, mergeObjects } from '@alfresco/adf-extensions'; import { ExtensionConfig, ExtensionService, mergeObjects } from '@alfresco/adf-extensions';
import { OpenidConfiguration } from '../services/openid-configuration.interface';
/* spellchecker: disable */ /* spellchecker: disable */
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
@@ -208,6 +209,26 @@ export class AppConfigService {
}); });
} }
/**
* Call the discovery API to fetch configuration
*
* @returns Discovery configuration
*/
loadWellKnown(hostIdp: string): Promise<OpenidConfiguration> {
return new Promise(async (resolve, reject) => {
this.http
.get<OpenidConfiguration>(`${hostIdp}/.well-known/openid-configuration`)
.subscribe({
next: (res: OpenidConfiguration) => {
resolve(res);
},
error: (err: any) => {
reject(err);
}
});
});
}
private formatString(str: string, keywords: Map<string, string>): string { private formatString(str: string, keywords: Map<string, string>): string {
let result = str; let result = str;

View File

@@ -186,7 +186,8 @@
"SILENT": "Silent Login", "SILENT": "Silent Login",
"SCOPE": "Scope", "SCOPE": "Scope",
"CLIENT": "Client ID", "CLIENT": "Client ID",
"PUBLIC_URLS": "Public urls silent Login" "PUBLIC_URLS": "Public urls silent Login",
"SECRET": "Secret"
}, },
"CARDVIEW": { "CARDVIEW": {
"KEYVALUEPAIRS": { "KEYVALUEPAIRS": {

View File

@@ -0,0 +1,20 @@
/*!
* @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 interface AlfrescoApiInterface {
load(): Promise<void> ;
}

View File

@@ -21,6 +21,7 @@ import { AppConfigService, AppConfigValues } from '../app-config/app-config.serv
import { Subject, ReplaySubject } from 'rxjs'; import { Subject, ReplaySubject } from 'rxjs';
import { OauthConfigModel } from '../models/oauth-config.model'; import { OauthConfigModel } from '../models/oauth-config.model';
import { StorageService } from './storage.service'; import { StorageService } from './storage.service';
import { OpenidConfiguration } from './openid-configuration.interface';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@@ -36,6 +37,9 @@ export class AlfrescoApiService {
protected alfrescoApi: AlfrescoApi; protected alfrescoApi: AlfrescoApi;
lastConfig: AlfrescoApiConfig; lastConfig: AlfrescoApiConfig;
currentAppConfig: AlfrescoApiConfig;
idpConfig: OpenidConfiguration;
private excludedErrorUrl: string[] = ['api/enterprise/system/properties']; private excludedErrorUrl: string[] = ['api/enterprise/system/properties'];
@@ -49,25 +53,52 @@ export class AlfrescoApiService {
} }
async load() { async load() {
await this.appConfig.load().then(() => { try {
await this.appConfig.load();
this.storageService.prefix = this.appConfig.get<string>(AppConfigValues.STORAGE_PREFIX, ''); this.storageService.prefix = this.appConfig.get<string>(AppConfigValues.STORAGE_PREFIX, '');
this.initAlfrescoApi(); this.getCurrentAppConfig();
if (this.currentAppConfig.authType === 'OAUTH') {
this.idpConfig = await this.appConfig.loadWellKnown(this.currentAppConfig.oauth2.host);
this.mapAlfrescoApiOpenIdConfig();
}
} catch {
throw new Error('Something wrong happened when calling the app.config.json');
}
this.initAlfrescoApiWithConfig();
this.alfrescoApiInitialized.next(true); this.alfrescoApiInitialized.next(true);
});
} }
reset() { async reset() {
this.initAlfrescoApi(); this.getCurrentAppConfig();
if (this.currentAppConfig.authType === 'OAUTH') {
this.idpConfig = await this.appConfig.loadWellKnown(this.currentAppConfig.oauth2.host);
this.mapAlfrescoApiOpenIdConfig();
}
this.initAlfrescoApiWithConfig();
} }
protected initAlfrescoApi() { private getAuthWithFixedOriginLocation(): OauthConfigModel {
const oauth: OauthConfigModel = Object.assign({}, this.appConfig.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null)); const oauth: OauthConfigModel = Object.assign({}, this.appConfig.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
if (oauth) { if (oauth) {
oauth.redirectUri = window.location.origin + window.location.pathname; oauth.redirectUri = window.location.origin + window.location.pathname;
oauth.redirectUriLogout = window.location.origin + window.location.pathname; oauth.redirectUriLogout = window.location.origin + window.location.pathname;
} }
return oauth;
}
const config = new AlfrescoApiConfig({ private mapAlfrescoApiOpenIdConfig() {
this.currentAppConfig.oauth2.tokenUrl = this.idpConfig.token_endpoint;
this.currentAppConfig.oauth2.authorizationUrl = this.idpConfig.authorization_endpoint;
this.currentAppConfig.oauth2.logoutUrl = this.idpConfig.end_session_endpoint;
this.currentAppConfig.oauth2.userinfoEndpoint = this.idpConfig.userinfo_endpoint;
}
private getCurrentAppConfig() {
const oauth = this.getAuthWithFixedOriginLocation();
this.currentAppConfig = new AlfrescoApiConfig({
provider: this.appConfig.get<string>(AppConfigValues.PROVIDERS), provider: this.appConfig.get<string>(AppConfigValues.PROVIDERS),
hostEcm: this.appConfig.get<string>(AppConfigValues.ECMHOST), hostEcm: this.appConfig.get<string>(AppConfigValues.ECMHOST),
hostBpm: this.appConfig.get<string>(AppConfigValues.BPMHOST), hostBpm: this.appConfig.get<string>(AppConfigValues.BPMHOST),
@@ -79,15 +110,20 @@ export class AlfrescoApiService {
domainPrefix : this.appConfig.get<string>(AppConfigValues.STORAGE_PREFIX), domainPrefix : this.appConfig.get<string>(AppConfigValues.STORAGE_PREFIX),
oauth2: oauth oauth2: oauth
}); });
if (this.alfrescoApi && this.isDifferentConfig(this.lastConfig, config)) {
this.lastConfig = config;
this.alfrescoApi.setConfig(config);
} else {
this.lastConfig = config;
this.alfrescoApi = new AlfrescoApi(config);
} }
protected initAlfrescoApi() {
this.getCurrentAppConfig();
this.initAlfrescoApiWithConfig();
}
private initAlfrescoApiWithConfig() {
if (this.alfrescoApi && this.isDifferentConfig(this.lastConfig, this.currentAppConfig)) {
this.alfrescoApi.setConfig(this.currentAppConfig);
} else {
this.alfrescoApi = new AlfrescoApi(this.currentAppConfig);
}
this.lastConfig = this.currentAppConfig;
} }
isDifferentConfig(lastConfig: AlfrescoApiConfig, newConfig: AlfrescoApiConfig) { isDifferentConfig(lastConfig: AlfrescoApiConfig, newConfig: AlfrescoApiConfig) {

View File

@@ -59,6 +59,13 @@ describe('Auth Guard SSO role service', () => {
expect(await authGuard.canActivate(router)).toBeTruthy(); expect(await authGuard.canActivate(router)).toBeTruthy();
}); });
it('Should canActivate be true if case of empty roles to check', async () => {
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
router.data = { roles: [] };
expect(await authGuard.canActivate(router)).toBeTruthy();
});
it('Should canActivate be false if the Role is not present int the JWT token', async () => { it('Should canActivate be false if the Role is not present int the JWT token', async () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token'); spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue({ realm_access: { roles: ['role3'] } }); spyOn(jwtHelperService, 'decodeToken').and.returnValue({ realm_access: { roles: ['role3'] } });

View File

@@ -38,10 +38,14 @@ export class AuthGuardSsoRoleService implements CanActivate {
if (route.data) { if (route.data) {
if (route.data['roles']) { if (route.data['roles']) {
const rolesToCheck: string[] = route.data['roles']; const rolesToCheck: string[] = route.data['roles'];
if (rolesToCheck.length === 0) {
hasRealmRole = true;
} else {
const excludedRoles = route.data['excludedRoles'] || []; const excludedRoles = route.data['excludedRoles'] || [];
const isContentAdmin = rolesToCheck.includes(ContentGroups.ALFRESCO_ADMINISTRATORS) || excludedRoles.includes(ContentGroups.ALFRESCO_ADMINISTRATORS) ? await this.peopleContentService.isContentAdmin() : false; const isContentAdmin = rolesToCheck.includes(ContentGroups.ALFRESCO_ADMINISTRATORS) || excludedRoles.includes(ContentGroups.ALFRESCO_ADMINISTRATORS) ? await this.peopleContentService.isContentAdmin() : false;
hasRealmRole = excludedRoles.length ? this.checkAccessWithExcludedRoles(rolesToCheck, excludedRoles, isContentAdmin) : this.hasRoles(rolesToCheck, isContentAdmin); hasRealmRole = excludedRoles.length ? this.checkAccessWithExcludedRoles(rolesToCheck, excludedRoles, isContentAdmin) : this.hasRoles(rolesToCheck, isContentAdmin);
} }
}
if (route.data['clientRoles']) { if (route.data['clientRoles']) {
const clientRoleName = route.params[route.data['clientRoles']]; const clientRoleName = route.params[route.data['clientRoles']];

View File

@@ -0,0 +1,26 @@
/*!
* @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 interface OpenidConfiguration {
authorization_endpoint: string;
token_endpoint: string;
userinfo_endpoint: string;
end_session_endpoint: string;
check_session_iframe: string;
revocation_endpoint: string;
introspection_endpoint: string;
}

View File

@@ -103,6 +103,15 @@
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field class="adf-full-width">
<mat-label>{{ 'CORE.HOST_SETTINGS.SECRET'| translate }}</mat-label>
<input matInput name="{{ 'CORE.HOST_SETTINGS.SECRET'| translate }}"
formControlName="secret" placeholder="{{ 'CORE.HOST_SETTINGS.SECRET'| translate }}">
<mat-error *ngIf="secret.hasError('required')">
{{ 'CORE.HOST_SETTINGS.REQUIRED'| translate }}
</mat-error>
</mat-form-field>
<label for="silentLogin">{{ 'CORE.HOST_SETTINGS.SILENT'| translate }}</label> <label for="silentLogin">{{ 'CORE.HOST_SETTINGS.SILENT'| translate }}</label>
<mat-slide-toggle class="adf-full-width" name="silentLogin" [color]="'primary'" <mat-slide-toggle class="adf-full-width" name="silentLogin" [color]="'primary'"
formControlName="silentLogin"> formControlName="silentLogin">

View File

@@ -259,8 +259,8 @@ export class HostSettingsComponent implements OnInit {
return this.oauthConfig.get('scope') as FormControl; return this.oauthConfig.get('scope') as FormControl;
} }
get secretId(): FormControl { get secret(): FormControl {
return this.oauthConfig.get('secretId') as FormControl; return this.oauthConfig.get('secret') as FormControl;
} }
get implicitFlow(): FormControl { get implicitFlow(): FormControl {