diff --git a/demo-shell/src/app.config.json b/demo-shell/src/app.config.json index 1ea970f269..3eda79471f 100644 --- a/demo-shell/src/app.config.json +++ b/demo-shell/src/app.config.json @@ -16,6 +16,7 @@ "threads": 1 }, "oauth2": { + "handler": "oidc", "host": "{protocol}//{hostname}{:port}/auth/realms/alfresco", "clientId": "alfresco", "scope": "openid", diff --git a/demo-shell/src/app/app.component.ts b/demo-shell/src/app/app.component.ts index b7f088aea7..e7197a8102 100644 --- a/demo-shell/src/app/app.component.ts +++ b/demo-shell/src/app/app.component.ts @@ -17,12 +17,12 @@ import { Component, ViewEncapsulation, OnInit } from '@angular/core'; import { - AuthenticationService, - AlfrescoApiService, + // AuthenticationService, + // AlfrescoApiService, PageTitleService } from '@alfresco/adf-core'; -import { Router } from '@angular/router'; -import { MatDialog } from '@angular/material/dialog'; +// import { Router } from '@angular/router'; +// import { MatDialog } from '@angular/material/dialog'; @Component({ selector: 'app-root', @@ -33,30 +33,31 @@ import { MatDialog } from '@angular/material/dialog'; export class AppComponent implements OnInit { constructor(private pageTitleService: PageTitleService, - private alfrescoApiService: AlfrescoApiService, - private authenticationService: AuthenticationService, - private router: Router, - private dialogRef: MatDialog) { + // private alfrescoApiService: AlfrescoApiService, + // private authenticationService: AuthenticationService, + // private router: Router, + // private dialogRef: MatDialog + ) { } ngOnInit() { this.pageTitleService.setTitle('title'); - this.alfrescoApiService.getInstance().on('error', (error) => { - if (error.status === 401) { - if (!this.authenticationService.isLoggedIn()) { - this.dialogRef.closeAll(); - this.router.navigate(['/login']); - } - } + // this.alfrescoApiService.getInstance().on('error', (error) => { + // if (error.status === 401) { + // if (!this.authenticationService.isLoggedIn()) { + // this.dialogRef.closeAll(); + // this.router.navigate(['/login']); + // } + // } - if (error.status === 507) { - if (!this.authenticationService.isLoggedIn()) { - this.dialogRef.closeAll(); - this.router.navigate(['error/507']); - } - } - }); + // if (error.status === 507) { + // if (!this.authenticationService.isLoggedIn()) { + // this.dialogRef.closeAll(); + // this.router.navigate(['error/507']); + // } + // } + // }); } } diff --git a/demo-shell/src/app/app.module.ts b/demo-shell/src/app/app.module.ts index 6be93f9082..ea88811961 100644 --- a/demo-shell/src/app/app.module.ts +++ b/demo-shell/src/app/app.module.ts @@ -28,7 +28,8 @@ import { TRANSLATION_PROVIDER, DebugAppConfigService, CoreModule, - CoreAutomationService + CoreAutomationService, + AppConfigModule } from '@alfresco/adf-core'; import { ExtensionsModule } from '@alfresco/adf-extensions'; import { AppComponent } from './app.component'; @@ -115,6 +116,7 @@ import { setupAppNotifications } from './services/app-notifications-factory'; import { AppNotificationsService } from './services/app-notifications.service'; import { SearchFilterChipsComponent } from './components/search/search-filter-chips.component'; import { AlfrescoApiModule } from '@alfresco/adf-core/alfresco-api'; +import { OIDCAuthModule } from '@alfresco/adf-core/authentication'; registerLocaleData(localeFr); registerLocaleData(localeDe); @@ -138,13 +140,15 @@ registerLocaleData(localeSv); BrowserModule, environment.e2e ? NoopAnimationsModule : BrowserAnimationsModule, ReactiveFormsModule, - RouterModule.forRoot(appRoutes, { useHash: true, relativeLinkResolution: 'legacy' }), + // initialNavigation: false needs because of the OIDC package!!! + // https://manfredsteyer.github.io/angular-oauth2-oidc/docs/additional-documentation/routing-with-the-hashstrategy.html + RouterModule.forRoot(appRoutes, { useHash: true, relativeLinkResolution: 'legacy', initialNavigation: false }), FormsModule, HttpClientModule, MaterialModule, FlexLayoutModule, TranslateModule.forRoot(), - CoreModule.forRoot(), + CoreModule.forRoot({ legacyAlfrescoApiService: false }), ContentModule.forRoot(), InsightsModule.forRoot(), ProcessModule.forRoot(), @@ -154,6 +158,8 @@ registerLocaleData(localeSv); ChartsModule, AppCloudSharedModule, AlfrescoApiModule, + AppConfigModule.forRoot(), + OIDCAuthModule, MonacoEditorModule.forRoot() ], declarations: [ diff --git a/demo-shell/src/app/app.routes.ts b/demo-shell/src/app/app.routes.ts index 2145690894..3b072bae46 100644 --- a/demo-shell/src/app/app.routes.ts +++ b/demo-shell/src/app/app.routes.ts @@ -56,6 +56,7 @@ import { ProcessCloudLayoutComponent } from './components/cloud/process-cloud-la import { ServiceTaskListCloudDemoComponent } from './components/cloud/service-task-list-cloud-demo.component'; import { AspectListSampleComponent } from './components/aspect-list-sample/aspect-list-sample.component'; import { SearchFilterChipsComponent } from './components/search/search-filter-chips.component'; +import { OIDCAuthGuard } from '@alfresco/adf-core/authentication'; export const appRoutes: Routes = [ { path: 'login', loadChildren: () => import('./components/login/login.module').then(m => m.AppLoginModule) }, @@ -106,7 +107,7 @@ export const appRoutes: Routes = [ { path: '', component: AppLayoutComponent, - canActivate: [AuthGuard], + canActivate: [AuthGuard, OIDCAuthGuard], children: [ { path: '', diff --git a/demo-shell/src/app/components/app-layout/app-layout.component.ts b/demo-shell/src/app/components/app-layout/app-layout.component.ts index 6a029f246f..5982c0369e 100644 --- a/demo-shell/src/app/components/app-layout/app-layout.component.ts +++ b/demo-shell/src/app/components/app-layout/app-layout.component.ts @@ -16,7 +16,12 @@ */ import { Component, ViewEncapsulation, OnInit, OnDestroy } from '@angular/core'; -import { UserPreferencesService, AppConfigService, AlfrescoApiService, UserPreferenceValues } from '@alfresco/adf-core'; +import { + UserPreferencesService, + AppConfigService, + // AlfrescoApiService, + UserPreferenceValues +} from '@alfresco/adf-core'; import { HeaderDataService } from '../header-data/header-data.service'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -169,11 +174,11 @@ export class AppLayoutComponent implements OnInit, OnDestroy { constructor( private userPreferences: UserPreferencesService, private config: AppConfigService, - private alfrescoApiService: AlfrescoApiService, + // private alfrescoApiService: AlfrescoApiService, private headerService: HeaderDataService) { - if (this.alfrescoApiService.getInstance().isOauthConfiguration()) { - this.enableRedirect = false; - } + // if (this.alfrescoApiService.getInstance().isOauthConfiguration()) { + // this.enableRedirect = false; + // } } setState(state) { diff --git a/lib/core/alfresco-api/alfresco-api.module.ts b/lib/core/alfresco-api/alfresco-api.module.ts index 586edc1d0a..3f002e5b82 100644 --- a/lib/core/alfresco-api/alfresco-api.module.ts +++ b/lib/core/alfresco-api/alfresco-api.module.ts @@ -19,10 +19,12 @@ import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { APP_INITIALIZER, NgModule } from '@angular/core'; -import { AlfrescoApiV2Service } from './services/alfresco-api-v2.service'; +import { AlfrescoApiV2 } from './services/alfresco-api-v2'; import { AlfrescoApiClientFactory } from './services/alfresco-api-client.factory'; import { AlfrescoApiV2LoaderService, createAlfrescoApiV2Service } from './services/alfresco-api-v2-loader.service'; import { AuthBearerInterceptor } from './services/auth-bearer.interceptor'; +import { LegacyAlfrescoApiServiceFacade } from './services/legacy-alfresco-api-service.facade'; +import { AlfrescoApiService } from '../services/alfresco-api.service'; @NgModule({ imports: [ @@ -33,8 +35,9 @@ import { AuthBearerInterceptor } from './services/auth-bearer.interceptor'; }) ], providers: [ - AlfrescoApiV2Service, + AlfrescoApiV2, AlfrescoApiV2LoaderService, + LegacyAlfrescoApiServiceFacade, AlfrescoApiClientFactory, { provide: APP_INITIALIZER, @@ -47,6 +50,11 @@ import { AuthBearerInterceptor } from './services/auth-bearer.interceptor'; { provide: HTTP_INTERCEPTORS, useClass: AuthBearerInterceptor, multi: true + }, + // Reproviding legacy like service for older code + { + provide: AlfrescoApiService, + useExisting: LegacyAlfrescoApiServiceFacade } ] }) diff --git a/lib/core/alfresco-api/index.ts b/lib/core/alfresco-api/index.ts index a17d1e547b..df24cededb 100644 --- a/lib/core/alfresco-api/index.ts +++ b/lib/core/alfresco-api/index.ts @@ -16,5 +16,5 @@ */ export * from './alfresco-api.module'; -export * from './services/alfresco-api-v2.service'; +export * from './services/alfresco-api-v2'; export * from './services/alfresco-api-client.factory'; diff --git a/lib/core/alfresco-api/js-api/alfresco-api-type.ts b/lib/core/alfresco-api/js-api/alfresco-api-type.ts new file mode 100644 index 0000000000..0dfa3915c4 --- /dev/null +++ b/lib/core/alfresco-api/js-api/alfresco-api-type.ts @@ -0,0 +1,67 @@ +/*! +* @license +* Copyright 2018 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. +*/ + +/*tslint:disable*/ // => because of ADF file naming problems... Try to remove it, if you don't believe me :P + +import { AlfrescoApiConfig } from '@alfresco/js-api'; +import { JsApiHttpClient } from './js-api-http-client'; + +export interface LegacyTicketApi { + getAlfTicket(ticket: string): string; +} + +// Extracted from existing AlfrescoApi: +export interface AlfrescoApiType { + config: AlfrescoApiConfig; + contentClient: JsApiHttpClient & LegacyTicketApi; + contentPrivateClient: JsApiHttpClient & LegacyTicketApi; + processClient: JsApiHttpClient; + searchClient: JsApiHttpClient; + discoveryClient: JsApiHttpClient; + gsClient: JsApiHttpClient; + authClient: JsApiHttpClient; + processAuth?: JsApiHttpClient; + + setConfig(config: AlfrescoApiConfig): void; + changeWithCredentialsConfig(withCredentials: boolean) : void; + changeCsrfConfig(disableCsrf: boolean): void; + changeEcmHost(hostEcm: string): void; + changeBpmHost(hostBpm: string): void; + login(username: string, password: string): Promise; + isCredentialValid(credential: string): boolean; + implicitLogin(): Promise; + loginTicket(ticketEcm: string, ticketBpm: string): Promise; + logout(): Promise; + isLoggedIn(): boolean; + isBpmLoggedIn(): boolean; + isEcmLoggedIn(): boolean; + getBpmUsername(): string; + getEcmUsername(): string; + refreshToken(): Promise; + getTicketAuth(): string; + setTicket(ticketEcm: string, TicketBpm: string): void; + invalidateSession(): void; + getTicketBpm(): string; + getTicketEcm(): string; + getTicket(): string[]; + isBpmConfiguration(): boolean; + isEcmConfiguration(): boolean; + isOauthConfiguration(): boolean; + isPublicUrl(): boolean; + isEcmBpmConfiguration(): boolean; + reply(event: string, callback?: any): void; +} diff --git a/lib/core/alfresco-api/services/alfresco-api-client.factory.ts b/lib/core/alfresco-api/services/alfresco-api-client.factory.ts index 381d9e1f2e..022cd91306 100644 --- a/lib/core/alfresco-api/services/alfresco-api-client.factory.ts +++ b/lib/core/alfresco-api/services/alfresco-api-client.factory.ts @@ -18,31 +18,45 @@ /*tslint:disable*/ // => because of ADF file naming problems... Try to remove it, if you don't believe me :P import { Injectable } from '@angular/core'; -import { DiscoveryApi, NodesApi } from '@alfresco/js-api'; -import { AlfrescoApiV2Service } from './alfresco-api-v2.service'; +import { DiscoveryApi, NodesApi, PeopleApi, UserProfileApi } from '@alfresco/js-api'; +import { AlfrescoApiV2 } from './alfresco-api-v2'; @Injectable() export class AlfrescoApiClientFactory { - // Here we should all the APIs from js-api + // Here we should have all the APIs from js-api private discoveryApi: DiscoveryApi = null; private nodesApi: NodesApi = null; + private peopleApi: PeopleApi = null; + private profileApi: UserProfileApi = null; constructor( - private angularAlfrescoApi?: AlfrescoApiV2Service) { + private angularAlfrescoApi?: AlfrescoApiV2) { } - getDiscoveryApi() { + getDiscoveryApi(): DiscoveryApi { // DiscoveryApi needs to rely on a lot thinner interface: JsApiHttpClient; For now: "as any" this.discoveryApi = this.discoveryApi ?? new DiscoveryApi(this.angularAlfrescoApi as any); return this.discoveryApi; } - getNodesApi () { + getNodesApi(): NodesApi { // NodesApi needs to rely on a lot thinner interface: JsApiHttpClient; For now: "as any" this.nodesApi = this.nodesApi ?? new NodesApi(this.angularAlfrescoApi as any); return this.nodesApi; } + getPeopleApi(): PeopleApi { + // PeopleApi needs to rely on a lot thinner interface: JsApiHttpClient; For now: "as any" + this.peopleApi = this.peopleApi ?? new PeopleApi(this.angularAlfrescoApi as any); + return this.peopleApi; + } + + getProfileApi(): UserProfileApi { + // PeopleApi needs to rely on a lot thinner interface: JsApiHttpClient; For now: "as any" + this.profileApi = this.profileApi ?? new UserProfileApi(this.angularAlfrescoApi as any); + return this.profileApi; + } + getSearchApi() { // TODO } diff --git a/lib/core/alfresco-api/services/alfresco-api-v2-loader.service.ts b/lib/core/alfresco-api/services/alfresco-api-v2-loader.service.ts index 1e713d9ff5..ab148c6d8d 100644 --- a/lib/core/alfresco-api/services/alfresco-api-v2-loader.service.ts +++ b/lib/core/alfresco-api/services/alfresco-api-v2-loader.service.ts @@ -21,8 +21,9 @@ import { Injectable } from '@angular/core'; import { AlfrescoApi, AlfrescoApiConfig } from '@alfresco/js-api'; import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service'; import { OauthConfigModel } from '../../models/oauth-config.model'; -import { StorageService } from '../../services/storage.service'; -import { AlfrescoApiV2Service } from './alfresco-api-v2.service'; +import { AlfrescoApiV2 } from './alfresco-api-v2'; +import { LegacyAlfrescoApiServiceFacade } from './legacy-alfresco-api-service.facade'; +import { take } from 'rxjs/operators'; export function createAlfrescoApiV2Service(angularAlfrescoApiService: AlfrescoApiV2LoaderService) { return () => angularAlfrescoApiService.load(); @@ -35,13 +36,12 @@ export class AlfrescoApiV2LoaderService { constructor( protected appConfig: AppConfigService, - protected storageService: StorageService, - private alfrescoApiV2Service?: AlfrescoApiV2Service) { + private legacyAlfrescoApiServiceFacade: LegacyAlfrescoApiServiceFacade, + private alfrescoApiV2Service?: AlfrescoApiV2) { } load(): Promise { - return this.appConfig.load().then(() => { - this.storageService.prefix = this.appConfig.get(AppConfigValues.STORAGE_PREFIX, ''); + return this.appConfig.onLoad.pipe(take(1)).toPromise().then(() => { this.initAngularAlfrescoApi(); }); } @@ -67,5 +67,6 @@ export class AlfrescoApiV2LoaderService { }); this.alfrescoApiV2Service.init(config); + this.legacyAlfrescoApiServiceFacade.init(); } } diff --git a/lib/core/alfresco-api/services/alfresco-api-v2.service.ts b/lib/core/alfresco-api/services/alfresco-api-v2.service.ts deleted file mode 100644 index 67e33c884f..0000000000 --- a/lib/core/alfresco-api/services/alfresco-api-v2.service.ts +++ /dev/null @@ -1,87 +0,0 @@ -/*! - * @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. - */ - -/*tslint:disable*/ // => because of ADF file naming problems... Try to remove it, if you don't believe me :P - -import { AlfrescoApiConfig } from '@alfresco/js-api'; -import { Injectable } from '@angular/core'; -import { JsApiHttpClient } from '../js-api/js-api-http-client'; -import { JsApiAngularHttpClient } from './js-api-angular-http-client'; -import { HttpClient } from '@angular/common/http'; - -@Injectable() -export class AlfrescoApiV2Service { - public contentPrivateClient: JsApiHttpClient; - public contentClient: JsApiHttpClient; - public authClient: JsApiHttpClient; - public searchClient: JsApiHttpClient; - public discoveryClient: JsApiHttpClient; - public gsClient: JsApiHttpClient; - public processClient: JsApiHttpClient; - - constructor(private httpClient: HttpClient) {} - - init(config: AlfrescoApiConfig) { - this.contentPrivateClient = new JsApiAngularHttpClient( - config.hostEcm, - config.contextRoot, - `/api/${config.tenant}/private/alfresco/versions/1`, - this.httpClient - ); - - this.contentClient = new JsApiAngularHttpClient( - config.hostEcm, - config.contextRoot, - `/api/${config.tenant}/public/alfresco/versions/1`, - this.httpClient - ); - - this.authClient = new JsApiAngularHttpClient( - config.hostEcm, - config.contextRoot, - `/api/${config.tenant}/public/authentication/versions/1`, - this.httpClient - ); - - this.searchClient = new JsApiAngularHttpClient( - config.hostEcm, - config.contextRoot, - `/api/${config.tenant}/public/search/versions/1`, - this.httpClient - ); - this.discoveryClient = new JsApiAngularHttpClient( - config.hostEcm, - config.contextRoot, - `/api`, - this.httpClient - ); - - this.gsClient = new JsApiAngularHttpClient( - config.hostEcm, - config.contextRoot, - `/api/${config.tenant}/public/gs/versions/1`, - this.httpClient - ); - - this.processClient = new JsApiAngularHttpClient( - config.hostBpm, - config.contextRootBpm, - '', - this.httpClient - ); - } -} diff --git a/lib/core/alfresco-api/services/alfresco-api-v2.ts b/lib/core/alfresco-api/services/alfresco-api-v2.ts new file mode 100644 index 0000000000..d50a7ecfdb --- /dev/null +++ b/lib/core/alfresco-api/services/alfresco-api-v2.ts @@ -0,0 +1,236 @@ +/*! + * @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. + */ + +/*tslint:disable*/ // => because of ADF file naming problems... Try to remove it, if you don't believe me :P +/* eslint-disable */ + +import { AlfrescoApiConfig } from '@alfresco/js-api'; +import { Injectable } from '@angular/core'; +import { JsApiHttpClient } from '../js-api/js-api-http-client'; +import { JsApiAngularHttpClient } from './js-api-angular-http-client'; +import { HttpClient } from '@angular/common/http'; +import { AlfrescoApiType, LegacyTicketApi } from '../js-api/alfresco-api-type'; + +@Injectable() +export class AlfrescoApiV2 implements AlfrescoApiType { + public config: AlfrescoApiConfig; + public contentPrivateClient: JsApiHttpClient & LegacyTicketApi; + public contentClient: JsApiHttpClient & LegacyTicketApi; + public authClient: JsApiHttpClient; + public searchClient: JsApiHttpClient; + public discoveryClient: JsApiHttpClient; + public gsClient: JsApiHttpClient; + public processClient: JsApiHttpClient; + + constructor( + private httpClient: HttpClient + ) {} + + init(config: AlfrescoApiConfig) { + this.config = config; + + this.contentPrivateClient = new JsApiAngularHttpClient( + config.hostEcm, + config.contextRoot, + `/api/${config.tenant}/private/alfresco/versions/1`, + this.httpClient + ) as unknown as JsApiHttpClient & LegacyTicketApi; + + this.contentClient = new JsApiAngularHttpClient( + config.hostEcm, + config.contextRoot, + `/api/${config.tenant}/public/alfresco/versions/1`, + this.httpClient + ) as unknown as JsApiHttpClient & LegacyTicketApi; + + this.authClient = new JsApiAngularHttpClient( + config.hostEcm, + config.contextRoot, + `/api/${config.tenant}/public/authentication/versions/1`, + this.httpClient + ); + + this.searchClient = new JsApiAngularHttpClient( + config.hostEcm, + config.contextRoot, + `/api/${config.tenant}/public/search/versions/1`, + this.httpClient + ); + this.discoveryClient = new JsApiAngularHttpClient( + config.hostEcm, + config.contextRoot, + `/api`, + this.httpClient + ); + + this.gsClient = new JsApiAngularHttpClient( + config.hostEcm, + config.contextRoot, + `/api/${config.tenant}/public/gs/versions/1`, + this.httpClient + ); + + this.processClient = new JsApiAngularHttpClient( + config.hostBpm, + config.contextRootBpm, + '', + this.httpClient + ); + } + + setConfig(config: AlfrescoApiConfig) { + this.config = config; + } + + changeWithCredentialsConfig(withCredentials: boolean) { + console.log(withCredentials); + debugger; + return false; + } + + changeCsrfConfig(disableCsrf: boolean) { + console.log(disableCsrf); + debugger; + } + + changeEcmHost(hostEcm: string) { + console.log(hostEcm); + debugger; + } + + changeBpmHost(hostBpm: string) { + console.log(hostBpm); + debugger; + } + + login(username: string, password: string) { + console.log(username, password); + debugger; + return Promise.reject(); + } + + isCredentialValid(credential: string) { + console.log(credential); + debugger; + return false; + } + + implicitLogin() { + debugger; + return Promise.reject(); + } + + loginTicket(ticketEcm: string, ticketBpm: string) { + console.log(ticketEcm, ticketBpm); + debugger; + return Promise.reject(); + } + + logout() { + debugger; + return Promise.resolve(); + } + + isLoggedIn() { + debugger; + return false; + } + + isBpmLoggedIn() { + debugger; + return false; + } + + isEcmLoggedIn() { + debugger; + return false; + } + + getBpmUsername() { + debugger; + return 'Kakarot'; + } + + getEcmUsername() { + debugger; + return 'Vegeta'; + } + + refreshToken() { + debugger; + return Promise.reject(); + } + + getTicketAuth() { + debugger; + return 'xyz-123'; + } + + setTicket(ticketEcm: string, TicketBpm: string) { + console.log(ticketEcm, TicketBpm); + debugger; + } + + invalidateSession() { + debugger; + } + + getTicketBpm() { + debugger; + return 'xyz-456'; + } + + getTicketEcm() { + debugger; + return 'xyz-789'; + } + + getTicket() { + debugger; + return ['xyz-000']; + } + + isBpmConfiguration() { + debugger; + return false; + } + + isEcmConfiguration() { + debugger; + return false; + } + + isOauthConfiguration() { + debugger; + return false; + } + + isPublicUrl() { + debugger; + return false; + } + + isEcmBpmConfiguration() { + debugger; + return false; + } + + reply(event: string, callback?: any) { + console.log(event, callback); + debugger; + } +} diff --git a/lib/core/alfresco-api/services/legacy-alfresco-api-service.facade.ts b/lib/core/alfresco-api/services/legacy-alfresco-api-service.facade.ts new file mode 100644 index 0000000000..2af2d390b8 --- /dev/null +++ b/lib/core/alfresco-api/services/legacy-alfresco-api-service.facade.ts @@ -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. + */ + +/*tslint:disable*/ // => because of ADF file naming problems... Try to remove it, if you don't believe me :P + +import { Injectable } from '@angular/core'; +import { ReplaySubject } from 'rxjs/internal/ReplaySubject'; +import { AlfrescoApiType } from '../js-api/alfresco-api-type'; +import { AlfrescoApiV2 } from './alfresco-api-v2'; + +@Injectable() +export class LegacyAlfrescoApiServiceFacade { + constructor(private alfrescoApiV2: AlfrescoApiV2) {} + + alfrescoApiInitialized: ReplaySubject = new ReplaySubject(1); + + getInstance(): AlfrescoApiType { + return this.alfrescoApiV2; + } + + init() { + this.alfrescoApiInitialized.next(true); + } +} diff --git a/lib/core/app-config/app-config.loader.ts b/lib/core/app-config/app-config.loader.ts new file mode 100644 index 0000000000..c3860f82b7 --- /dev/null +++ b/lib/core/app-config/app-config.loader.ts @@ -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 { StorageService } from '../services/storage.service'; +import { AppConfigService, AppConfigValues } from './app-config.service'; + +export function loadAppConfig(appConfigService: AppConfigService, storageService: StorageService) { + return () => appConfigService.load().then(() => { + storageService.prefix = appConfigService.get(AppConfigValues.STORAGE_PREFIX, ''); + }); +} diff --git a/lib/core/app-config/app-config.module.ts b/lib/core/app-config/app-config.module.ts index 7a140d5981..c8dbc0e1a1 100644 --- a/lib/core/app-config/app-config.module.ts +++ b/lib/core/app-config/app-config.module.ts @@ -16,8 +16,19 @@ */ import { HttpClientModule } from '@angular/common/http'; -import { NgModule } from '@angular/core'; +import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core'; +import { StorageService } from '../services/storage.service'; +import { loadAppConfig } from './app-config.loader'; import { AppConfigPipe } from './app-config.pipe'; +import { AppConfigService } from './app-config.service'; + +interface AppConfigModuleConfig { + loadConfig: boolean; +} + +const defaultConfig: AppConfigModuleConfig = { + loadConfig: true +}; @NgModule({ imports: [ @@ -31,4 +42,18 @@ import { AppConfigPipe } from './app-config.pipe'; ] }) export class AppConfigModule { + static forRoot(config: AppConfigModuleConfig = defaultConfig): ModuleWithProviders { + return { + ngModule: AppConfigModule, + providers: [ + ...(config.loadConfig ? + [{ + provide: APP_INITIALIZER, + useFactory: loadAppConfig, + deps: [ AppConfigService, StorageService ], multi: true } + ] : [] + ) + ] + }; + } } diff --git a/lib/core/app-config/app-config.service.ts b/lib/core/app-config/app-config.service.ts index 11bd711c17..5dd967d613 100644 --- a/lib/core/app-config/app-config.service.ts +++ b/lib/core/app-config/app-config.service.ts @@ -191,8 +191,9 @@ export class AppConfigService { this.http.get(configUrl).subscribe( (data: any) => { this.status = Status.LOADED; - this.onDataLoaded(data); resolve(this.config); + // WARNING: Risky change! Despite the fact that this would be the right order, this is a breaking change... + this.onDataLoaded(data); }, () => { resolve(this.config); diff --git a/lib/core/authentication/authentication.interface.ts b/lib/core/authentication/authentication.interface.ts new file mode 100644 index 0000000000..342e6d9470 --- /dev/null +++ b/lib/core/authentication/authentication.interface.ts @@ -0,0 +1,60 @@ +/*! + * @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 { Observable, ReplaySubject } from 'rxjs'; +import { RedirectionModel } from '../models/redirection.model'; +import { PeopleApi, UserProfileApi, UserRepresentation } from '@alfresco/js-api'; +import { HttpHeaders } from '@angular/common/http'; + +export interface ADFAuthenticationService { + onLogin: ReplaySubject; + onLogout: ReplaySubject; + + peopleApi: PeopleApi; + profileApi: UserProfileApi; + + isLoggedIn(): boolean; + isLoggedInWith(provider: string): boolean; + isKerberosEnabled(): boolean; + isOauth(): boolean; + oidcHandlerEnabled(): boolean; + isImplicitFlow(): boolean; + isAuthCodeFlow(): boolean; + isPublicUrl(): boolean; + isECMProvider(): boolean; + isBPMProvider(): boolean; + isALLProvider(): boolean; + login(username: string, password: string, rememberMe?: boolean): Observable<{ type: string; ticket: any }>; + ssoImplicitLogin(): void; + ssoCodeFlowLogin?(): void; + isRememberMeSet(): boolean; + logout(): Observable; + getTicketEcm(): string | null; + getTicketBpm(): string | null; + getTicketEcmBase64(): string | null; + isEcmLoggedIn(): boolean; + isBpmLoggedIn(): boolean; + getEcmUsername(): string; + getBpmUsername(): string; + setRedirect(url: RedirectionModel): void; + getRedirect(): string; + getBpmLoggedUser(): Observable; + handleError(error: any): Observable; + getBearerExcludedUrls(): string[]; + getToken(): string; + addTokenToHeader(headersArg?: HttpHeaders): Observable; +} diff --git a/lib/core/authentication/base-authentication.service.ts b/lib/core/authentication/base-authentication.service.ts new file mode 100644 index 0000000000..1a24a9f1d8 --- /dev/null +++ b/lib/core/authentication/base-authentication.service.ts @@ -0,0 +1,46 @@ +/*! + * @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 { Observable, Observer } from 'rxjs'; +import { HttpHeaders } from '@angular/common/http'; + +export abstract class BaseAuthenticationService { + protected bearerExcludedUrls: string[] = ['auth/realms', 'resources/', 'assets/']; + + abstract getToken(): string; + + getBearerExcludedUrls(): string[] { + return this.bearerExcludedUrls; + } + + addTokenToHeader(headersArg?: HttpHeaders): Observable { + return new Observable((observer: Observer) => { + let headers = headersArg; + if (!headers) { + headers = new HttpHeaders(); + } + try { + const token: string = this.getToken(); + headers = headers.set('Authorization', 'Bearer ' + token); + observer.next(headers); + observer.complete(); + } catch (error) { + observer.error(error); + } + }); + } +} diff --git a/lib/core/authentication/index.ts b/lib/core/authentication/index.ts new file mode 100644 index 0000000000..391d471ece --- /dev/null +++ b/lib/core/authentication/index.ts @@ -0,0 +1,21 @@ +/*! + * @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 { OIDCAuthModule } from './oidc/oidc.module'; +export { OIDCAuthGuard } from './oidc/oidc-auth.guard'; +export { BaseAuthenticationService } from './base-authentication.service'; +export { ADFAuthenticationService } from './authentication.interface'; diff --git a/lib/core/authentication/oidc/oidc-auth.guard.ts b/lib/core/authentication/oidc/oidc-auth.guard.ts new file mode 100644 index 0000000000..92f0f9a949 --- /dev/null +++ b/lib/core/authentication/oidc/oidc-auth.guard.ts @@ -0,0 +1,55 @@ +/*! + * @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 { CanActivate, CanActivateChild } from '@angular/router'; +import { OAuthService } from 'angular-oauth2-oidc'; +import { AuthenticationService } from '../../services/authentication.service'; + +@Injectable() +export class OIDCAuthGuard implements CanActivate, CanActivateChild { + + constructor( + private authenticationService: AuthenticationService, + private oauthService: OAuthService + ) {} + + canActivate(): boolean { + if (this.shouldBeBypassed()) { + return true; + } + + if (!this.oauthService.hasValidAccessToken()) { + if (this.authenticationService.isAuthCodeFlow()) { + this.oauthService.initCodeFlow(); + } else { + this.oauthService.initLoginFlow(); + } + return false; + } + + return true; + } + + shouldBeBypassed() { + return !this.authenticationService.oidcHandlerEnabled(); + } + + canActivateChild(): boolean { + return this.canActivate(); + } +} diff --git a/lib/core/authentication/oidc/oidc-authentication.service.ts b/lib/core/authentication/oidc/oidc-authentication.service.ts new file mode 100644 index 0000000000..eba7e347a6 --- /dev/null +++ b/lib/core/authentication/oidc/oidc-authentication.service.ts @@ -0,0 +1,203 @@ +/*! + * @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 { Observable, from, throwError, ReplaySubject, of } from 'rxjs'; +import { LogService } from '../../services/log.service'; +import { RedirectionModel } from '../../models/redirection.model'; +import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service'; +import { PeopleApi, UserProfileApi, UserRepresentation } from '@alfresco/js-api'; +import { JwtHelperService } from '../../services/jwt-helper.service'; +import { StorageService } from '../../services/storage.service'; +import { OauthConfigModel } from '../../models/oauth-config.model'; +import { BaseAuthenticationService } from '../base-authentication.service'; +import { ADFAuthenticationService } from '../authentication.interface'; +import { AlfrescoApiClientFactory } from '../../alfresco-api'; +import { OAuthService } from 'angular-oauth2-oidc'; +import minimatch from 'minimatch'; + +@Injectable({ + providedIn: 'root' +}) +export class OIDCAuthenticationService extends BaseAuthenticationService implements ADFAuthenticationService { + onLogin: ReplaySubject = new ReplaySubject(1); + onLogout: ReplaySubject = new ReplaySubject(1); + + get peopleApi(): PeopleApi { + return this.alfrescoApiClientFactory.getPeopleApi(); + } + get profileApi(): UserProfileApi { + return this.alfrescoApiClientFactory.getProfileApi(); + } + + constructor( + private alfrescoApiClientFactory: AlfrescoApiClientFactory, + private appConfig: AppConfigService, + private storageService: StorageService, + private oauthService: OAuthService, + private logService: LogService) { + super(); + // this.alfrescoApi.alfrescoApiInitialized.subscribe(() => { + // this.alfrescoApi.getInstance().reply('logged-in', () => { + // this.onLogin.next(); + // }); + + // if (this.isKerberosEnabled()) { + // this.loadUserDetails(); + // } + // }); + } + + // private loadUserDetails() { + // const ecmUser$ = from(this.peopleApi.getPerson('-me-')); + // const bpmUser$ = this.getBpmLoggedUser(); + + // if (this.isALLProvider()) { + // forkJoin([ecmUser$, bpmUser$]).subscribe(() => this.onLogin.next()); + // } else if (this.isECMProvider()) { + // ecmUser$.subscribe(() => this.onLogin.next()); + // } else { + // bpmUser$.subscribe(() => this.onLogin.next()); + // } + // } + + isLoggedIn(): boolean { + return this.oauthService.hasValidAccessToken() && this.oauthService.hasValidIdToken(); + } + + isLoggedInWith(provider?: string): boolean { + console.log(provider); + return this.isLoggedIn(); + } + + isKerberosEnabled(): boolean { + return this.appConfig.get(AppConfigValues.AUTH_WITH_CREDENTIALS, false); + } + + isOauth(): boolean { + return this.appConfig.get(AppConfigValues.AUTHTYPE) === 'OAUTH'; + } + + oidcHandlerEnabled(): boolean { + const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get(AppConfigValues.OAUTHCONFIG, null)); + return oauth2?.handler === 'oidc'; + } + + isImplicitFlow() { + const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get(AppConfigValues.OAUTHCONFIG, null)); + return !!oauth2?.implicitFlow; + } + + isAuthCodeFlow() { + const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get(AppConfigValues.OAUTHCONFIG, null)); + return !!oauth2?.codeFlow; + } + + isPublicUrl(): boolean { + const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get(AppConfigValues.OAUTHCONFIG, null)); + const publicUrls = oauth2.publicUrls || []; + + if (Array.isArray(publicUrls)) { + return publicUrls.length > 0 && + publicUrls.some((urlPattern: string) => minimatch(window.location.href, urlPattern)); + } + return false; + } + + isECMProvider(): boolean { + return this.appConfig.get(AppConfigValues.PROVIDERS).toUpperCase() === 'ECM'; + } + isBPMProvider(): boolean { + return this.appConfig.get(AppConfigValues.PROVIDERS).toUpperCase() === 'BPM'; + } + + isALLProvider(): boolean { + return this.appConfig.get(AppConfigValues.PROVIDERS).toUpperCase() === 'ALL'; + } + + login(): Observable<{ type: string; ticket: any }> { + return of(); + } + + ssoImplicitLogin() { + this.oauthService.initLoginFlow(); + } + + ssoCodeFlowLogin() { + this.oauthService.initCodeFlow(); + } + + isRememberMeSet(): boolean { + return true; + } + + logout() { + this.oauthService.logOut(); + return of(); + } + + getTicketEcm(): string | null { + return null; + } + + getTicketBpm(): string | null { + return null; + } + + getTicketEcmBase64(): string | null { + return null; + } + + isEcmLoggedIn(): boolean { + return this.isLoggedIn(); + } + + isBpmLoggedIn(): boolean { + return this.isLoggedIn(); + } + + getEcmUsername(): string { + return 'To Be Implemented'; + } + + getBpmUsername(): string { + return 'To Be Implemented'; + } + + setRedirect(url?: RedirectionModel) { + console.log(url); + // noop + } + + getRedirect(): string { + // noop + return 'noop'; + } + + getBpmLoggedUser(): Observable { + return from(this.profileApi.getProfile()); + } + + handleError(error: any): Observable { + this.logService.error('Error when logging in', error); + return throwError(error || 'Server error'); + } + + getToken(): string { + return this.storageService.getItem(JwtHelperService.USER_ACCESS_TOKEN); + } +} diff --git a/lib/core/authentication/oidc/oidc-authentication.ts b/lib/core/authentication/oidc/oidc-authentication.ts new file mode 100644 index 0000000000..8be7712f24 --- /dev/null +++ b/lib/core/authentication/oidc/oidc-authentication.ts @@ -0,0 +1,78 @@ +/*! + * @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 { AuthConfig, OAuthService } from 'angular-oauth2-oidc'; +import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks'; +import { OauthConfigModel } from '../../models/oauth-config.model'; +import { take } from 'rxjs/operators'; +import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service'; +import { Router } from '@angular/router'; +import { StorageService } from '../../services/storage.service'; +import { AuthenticationService } from '../../services/authentication.service'; + +export const configureOIDCAuthentication = (oidcAuthentication: OIDCAuthentication) => () => oidcAuthentication.init(); + +@Injectable() +export class OIDCAuthentication { + + constructor( + private appConfigService: AppConfigService, + private storageService: StorageService, + private authenticationService: AuthenticationService, + private oauthService: OAuthService, + private router: Router + ) {} + + public init() { + this.appConfigService.onLoad + .pipe(take(1)) + .toPromise() + .then(this.configure.bind(this)); + } + + private configure() { + if (this.authenticationService.oidcHandlerEnabled()) { + const authConfig = this.getAuthConfig(this.authenticationService.isAuthCodeFlow()); + this.oauthService.configure(authConfig); + this.oauthService.tokenValidationHandler = new JwksValidationHandler(); + this.oauthService.setStorage(this.storageService); + this.oauthService.setupAutomaticSilentRefresh(); + + // This is what deald with the responded code and does the magic + this.oauthService.loadDiscoveryDocumentAndTryLogin().then(() => { + // initialNavigation: false needs because of the OIDC package!!! + // https://manfredsteyer.github.io/angular-oauth2-oidc/docs/additional-documentation/routing-with-the-hashstrategy.html + this.router.navigate(['/']); + }); + } + } + + private getAuthConfig(codeFlow = false): AuthConfig { + const oauth2: OauthConfigModel = Object.assign({}, this.appConfigService.get(AppConfigValues.OAUTHCONFIG, null)); + return { + issuer: oauth2.host, + loginUrl: `${oauth2.host}/protocol/openid-connect/auth`, + silentRefreshRedirectUri: oauth2.redirectSilentIframeUri, + redirectUri: window.location.origin + oauth2.redirectUri, + postLogoutRedirectUri: window.location.origin + oauth2.redirectUriLogout, + clientId: oauth2.clientId, + scope: oauth2.scope, + ...(codeFlow ? { responseType: 'code' } : {}) + }; + } +} diff --git a/lib/core/authentication/oidc/oidc.module.ts b/lib/core/authentication/oidc/oidc.module.ts new file mode 100644 index 0000000000..92424616b5 --- /dev/null +++ b/lib/core/authentication/oidc/oidc.module.ts @@ -0,0 +1,44 @@ +/*! + * @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, NgModule } from '@angular/core'; +import { OAuthModule } from 'angular-oauth2-oidc'; +import { AuthenticationService } from '../../services'; +import { OIDCAuthGuard } from './oidc-auth.guard'; +import { OIDCAuthentication, configureOIDCAuthentication } from './oidc-authentication'; +import { OIDCAuthenticationService } from './oidc-authentication.service'; + +@NgModule({ + imports: [ + OAuthModule.forRoot() + ], + providers: [ + OIDCAuthGuard, + OIDCAuthentication, + OIDCAuthenticationService, + { + provide: APP_INITIALIZER, + useFactory: configureOIDCAuthentication, + deps: [ OIDCAuthentication ], multi: true + }, + { + provide: AuthenticationService, + useExisting: OIDCAuthenticationService + } + ] +}) +export class OIDCAuthModule {} diff --git a/lib/core/core.module.ts b/lib/core/core.module.ts index 3f466dd708..2744740191 100644 --- a/lib/core/core.module.ts +++ b/lib/core/core.module.ts @@ -60,6 +60,14 @@ import { DirectionalityConfigService } from './services/directionality-config.se import { SearchTextModule } from './search-text/search-text-input.module'; import { versionCompatibilityFactory } from './services/version-compatibility-factory'; import { VersionCompatibilityService } from './services/version-compatibility.service'; + +interface MonolithCoreModuleConfig { + legacyAlfrescoApiService: boolean; +} + +const defaultConfig: MonolithCoreModuleConfig = { + legacyAlfrescoApiService: true +}; @NgModule({ imports: [ TranslateModule, @@ -133,21 +141,20 @@ import { VersionCompatibilityService } from './services/version-compatibility.se ] }) export class CoreModule { - static forRoot(): ModuleWithProviders { + static forRoot(config: MonolithCoreModuleConfig = defaultConfig): ModuleWithProviders { return { ngModule: CoreModule, providers: [ TranslateStore, TranslateService, { provide: TranslateLoader, useClass: TranslateLoaderService }, - { - provide: APP_INITIALIZER, - useFactory: startupServiceFactory, - deps: [ - AlfrescoApiService - ], - multi: true - }, + ...(config.legacyAlfrescoApiService ? + [{ + provide: APP_INITIALIZER, + useFactory: startupServiceFactory, + deps: [ AlfrescoApiService ], multi: true } + ] : [] + ), { provide: APP_INITIALIZER, useFactory: directionalityConfigFactory, diff --git a/lib/core/models/oauth-config.model.ts b/lib/core/models/oauth-config.model.ts index 505d80e9b4..6998769590 100644 --- a/lib/core/models/oauth-config.model.ts +++ b/lib/core/models/oauth-config.model.ts @@ -16,14 +16,17 @@ */ export interface OauthConfigModel { + handler?: 'legacy' | 'oidc'; host: string; clientId: string; scope: string; implicitFlow: boolean; + codeFlow?: boolean; redirectUri: string; silentLogin?: boolean; secret?: string; redirectUriLogout?: string; + redirectSilentIframeUri?: string; refreshTokenTimeout?: number; publicUrls: string[]; } diff --git a/lib/core/services/auth-guard-base.ts b/lib/core/services/auth-guard-base.ts index 1af17943fa..1b712672c5 100644 --- a/lib/core/services/auth-guard-base.ts +++ b/lib/core/services/auth-guard-base.ts @@ -60,6 +60,9 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild { route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable | Promise | boolean | UrlTree { + if (this.shouldBeBypassed()) { + return true; + } if (this.authenticationService.isLoggedIn() && this.authenticationService.isOauth() && this.isLoginFragmentPresent()) { return this.redirectSSOSuccessURL(); @@ -75,6 +78,10 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild { return this.canActivate(route, state); } + shouldBeBypassed() { + return this.authenticationService.oidcHandlerEnabled(); + } + protected async redirectSSOSuccessURL(): Promise { const redirectFragment = this.storageService.getItem('loginFragment'); diff --git a/lib/core/services/authentication.service.ts b/lib/core/services/authentication.service.ts index a6ace14636..2fd5d294f6 100644 --- a/lib/core/services/authentication.service.ts +++ b/lib/core/services/authentication.service.ts @@ -16,7 +16,7 @@ */ import { Injectable } from '@angular/core'; -import { Observable, from, throwError, Observer, ReplaySubject, forkJoin } from 'rxjs'; +import { Observable, from, throwError, ReplaySubject, forkJoin } from 'rxjs'; import { AlfrescoApiService } from './alfresco-api.service'; import { CookieService } from './cookie.service'; import { LogService } from './log.service'; @@ -24,9 +24,11 @@ import { RedirectionModel } from '../models/redirection.model'; import { AppConfigService, AppConfigValues } from '../app-config/app-config.service'; import { PeopleApi, UserProfileApi, UserRepresentation } from '@alfresco/js-api'; import { map, catchError, tap } from 'rxjs/operators'; -import { HttpHeaders } from '@angular/common/http'; import { JwtHelperService } from './jwt-helper.service'; import { StorageService } from './storage.service'; +import { OauthConfigModel } from '../models/oauth-config.model'; +import { BaseAuthenticationService } from '../authentication/base-authentication.service'; +import { ADFAuthenticationService } from '../authentication/authentication.interface'; const REMEMBER_ME_COOKIE_KEY = 'ALFRESCO_REMEMBER_ME'; const REMEMBER_ME_UNTIL = 1000 * 60 * 60 * 24 * 30; @@ -34,10 +36,9 @@ const REMEMBER_ME_UNTIL = 1000 * 60 * 60 * 24 * 30; @Injectable({ providedIn: 'root' }) -export class AuthenticationService { +export class AuthenticationService extends BaseAuthenticationService implements ADFAuthenticationService { private redirectUrl: RedirectionModel = null; - private bearerExcludedUrls: string[] = ['auth/realms', 'resources/', 'assets/']; /** * Emits login event */ @@ -66,15 +67,16 @@ export class AuthenticationService { private alfrescoApi: AlfrescoApiService, private cookie: CookieService, private logService: LogService) { - this.alfrescoApi.alfrescoApiInitialized.subscribe(() => { - this.alfrescoApi.getInstance().reply('logged-in', () => { - this.onLogin.next(); - }); + super(); + this.alfrescoApi.alfrescoApiInitialized.subscribe(() => { + this.alfrescoApi.getInstance().reply('logged-in', () => { + this.onLogin.next(); + }); - if (this.isKerberosEnabled()) { - this.loadUserDetails(); - } - }); + if (this.isKerberosEnabled()) { + this.loadUserDetails(); + } + }); } private loadUserDetails() { @@ -127,7 +129,20 @@ export class AuthenticationService { * @returns True if supported, false otherwise */ isOauth(): boolean { - return this.alfrescoApi.getInstance().isOauthConfiguration(); + return this?.alfrescoApi?.getInstance()?.isOauthConfiguration() || this.appConfig.get(AppConfigValues.AUTHTYPE) === 'OAUTH'; + } + + oidcHandlerEnabled(): boolean { + return false; + } + + isImplicitFlow(): boolean { + const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get(AppConfigValues.OAUTHCONFIG, null)); + return !!oauth2?.implicitFlow; + } + + isAuthCodeFlow(): boolean { + return false; } isPublicUrl(): boolean { @@ -140,7 +155,8 @@ export class AuthenticationService { * @returns True if supported, false otherwise */ isECMProvider(): boolean { - return this.alfrescoApi.getInstance().isEcmConfiguration(); + return this?.alfrescoApi?.getInstance()?.isEcmConfiguration() || + this.appConfig.get(AppConfigValues.PROVIDERS).toUpperCase() === 'ECM'; } /** @@ -149,7 +165,8 @@ export class AuthenticationService { * @returns True if supported, false otherwise */ isBPMProvider(): boolean { - return this.alfrescoApi.getInstance().isBpmConfiguration(); + return this?.alfrescoApi?.getInstance()?.isBpmConfiguration() || + this.appConfig.get(AppConfigValues.PROVIDERS).toUpperCase() === 'BPM'; } /** @@ -158,7 +175,8 @@ export class AuthenticationService { * @returns True if both are supported, false otherwise */ isALLProvider(): boolean { - return this.alfrescoApi.getInstance().isEcmBpmConfiguration(); + return this?.alfrescoApi?.getInstance()?.isEcmBpmConfiguration() || + this.appConfig.get(AppConfigValues.PROVIDERS).toUpperCase() === 'ALL'; } /** @@ -364,15 +382,6 @@ export class AuthenticationService { 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; - } - /** * Gets the auth token. * @@ -381,27 +390,4 @@ export class AuthenticationService { getToken(): string { return this.storageService.getItem(JwtHelperService.USER_ACCESS_TOKEN); } - - /** - * 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 { - return new Observable((observer: Observer) => { - let headers = headersArg; - if (!headers) { - headers = new HttpHeaders(); - } - try { - const token: string = this.getToken(); - headers = headers.set('Authorization', 'bearer ' + token); - observer.next(headers); - observer.complete(); - } catch (error) { - observer.error(error); - } - }); - } } diff --git a/lib/core/settings/host-settings.component.html b/lib/core/settings/host-settings.component.html index 169b5522a7..6bdd26eb24 100644 --- a/lib/core/settings/host-settings.component.html +++ b/lib/core/settings/host-settings.component.html @@ -103,6 +103,15 @@ + + OAUTH2 Handler (legacy | oidc) + + + Error... + + + @@ -113,6 +122,11 @@ formControlName="implicitFlow"> + + + + {{ 'CORE.HOST_SETTINGS.REDIRECT'| translate }} diff --git a/lib/core/settings/host-settings.component.ts b/lib/core/settings/host-settings.component.ts index ec0c7139f1..3ff3eec386 100644 --- a/lib/core/settings/host-settings.component.ts +++ b/lib/core/settings/host-settings.component.ts @@ -149,6 +149,8 @@ export class HostSettingsComponent implements OnInit { secret: oauth.secret, silentLogin: oauth.silentLogin, implicitFlow: oauth.implicitFlow, + codeFlow: oauth.codeFlow, + handler: oauth.handler, publicUrls: [oauth.publicUrls] }); } @@ -263,10 +265,18 @@ export class HostSettingsComponent implements OnInit { return this.oauthConfig.get('secretId') as FormControl; } + get handler(): FormControl { + return this.oauthConfig.get('handler') as FormControl; + } + get implicitFlow(): FormControl { return this.oauthConfig.get('implicitFlow') as FormControl; } + get codeFlow(): FormControl { + return this.oauthConfig.get('codeFlow') as FormControl; + } + get silentLogin(): FormControl { return this.oauthConfig.get('silentLogin') as FormControl; } diff --git a/package-lock.json b/package-lock.json index 1107698403..893cbe6515 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24658,6 +24658,24 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "angular-oauth2-oidc": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/angular-oauth2-oidc/-/angular-oauth2-oidc-12.1.0.tgz", + "integrity": "sha512-YUsNTWx8lccgo4e3qHvqJ6DOR9nfPVd+riyXzcy5nI4CB9cc2+SxaxiqQMGPAaKI2EOIfdxNobmUqUSEO+IXxA==", + "requires": { + "fast-sha256": "^1.3.0", + "tslib": "^2.0.0" + } + }, + "angular-oauth2-oidc-jwks": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/angular-oauth2-oidc-jwks/-/angular-oauth2-oidc-jwks-12.0.0.tgz", + "integrity": "sha512-9zXnzQD5yCsmMhL0lk8YOgn74TUNeeB9dpO6QhxQnO9vnjfZetu4j3TMFd4Hvo7s8WmCN6bzycapp2Qh6NN8vw==", + "requires": { + "jsrsasign": "^10.3.0", + "tslib": "^2.0.0" + } + }, "ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -31264,9 +31282,9 @@ }, "dependencies": { "@types/yargs": { - "version": "17.0.8", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.8.tgz", - "integrity": "sha512-wDeUwiUmem9FzsyysEwRukaEdDNcwbROvQ9QGRKaLI6t+IltNzbn4/i4asmB10auvZGQCzSQ6t0GSczEThlUXw==", + "version": "17.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.9.tgz", + "integrity": "sha512-Ci8+4/DOtkHRylcisKmVMtmVO5g7weUVCKcsu1sJvF1bn0wExTmbHmhFKj7AnEm0de800iovGhdSKzYnzbaHpg==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -31661,9 +31679,9 @@ }, "dependencies": { "@types/yargs": { - "version": "17.0.8", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.8.tgz", - "integrity": "sha512-wDeUwiUmem9FzsyysEwRukaEdDNcwbROvQ9QGRKaLI6t+IltNzbn4/i4asmB10auvZGQCzSQ6t0GSczEThlUXw==", + "version": "17.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.9.tgz", + "integrity": "sha512-Ci8+4/DOtkHRylcisKmVMtmVO5g7weUVCKcsu1sJvF1bn0wExTmbHmhFKj7AnEm0de800iovGhdSKzYnzbaHpg==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -32552,6 +32570,11 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, + "fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==" + }, "fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", @@ -38296,6 +38319,11 @@ "verror": "1.10.0" } }, + "jsrsasign": { + "version": "10.5.8", + "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-10.5.8.tgz", + "integrity": "sha512-ewFUGPZJujIR9j84Q5LEzPTG4D1qQZ4CjJrgHfMEAAiArkC3xfdgNP0ZAXXxXbb+K8Phw15soOIJ8bX3+usEdQ==" + }, "jszip": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", diff --git a/package.json b/package.json index defb62530d..338494a751 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,8 @@ "@mat-datetimepicker/moment": "^5.1.1", "@ngx-translate/core": "^13.0.0", "adf-tslint-rules": "0.0.7", + "angular-oauth2-oidc": "^12.1.0", + "angular-oauth2-oidc-jwks": "^12.0.0", "apollo-angular": "^2.6.0", "chart.js": "2.9.4", "classlist.js": "1.1.20150312",