Pass down the requestUrl for request interception

bring back check from js-api
fixing isLogin issues part1
some fix around emit
Narrow access for methods
fix sso username issue
Switch to dynamic service injection
add emitters
move auth inside ADF
This commit is contained in:
eromano
2023-06-21 18:14:45 +02:00
parent 7fba49a2db
commit 5b235212eb
28 changed files with 1520 additions and 569 deletions

View File

@@ -18,11 +18,11 @@
import { Component, ViewEncapsulation, OnInit } from '@angular/core'; import { Component, ViewEncapsulation, OnInit } from '@angular/core';
import { import {
AuthenticationService, AuthenticationService,
AlfrescoApiService,
PageTitleService PageTitleService
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { AdfHttpClient } from '@alfresco/adf-core/api';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@@ -33,7 +33,7 @@ import { MatDialog } from '@angular/material/dialog';
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
constructor(private pageTitleService: PageTitleService, constructor(private pageTitleService: PageTitleService,
private alfrescoApiService: AlfrescoApiService, private adfHttpClient: AdfHttpClient,
private authenticationService: AuthenticationService, private authenticationService: AuthenticationService,
private router: Router, private router: Router,
private dialogRef: MatDialog) { private dialogRef: MatDialog) {
@@ -43,7 +43,7 @@ export class AppComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.pageTitleService.setTitle('title'); this.pageTitleService.setTitle('title');
this.alfrescoApiService.getInstance().on('error', (error) => { this.adfHttpClient.on('error', (error) => {
if (error.status === 401) { if (error.status === 401) {
if (!this.authenticationService.isLoggedIn()) { if (!this.authenticationService.isLoggedIn()) {
this.dialogRef.closeAll(); this.dialogRef.closeAll();

View File

@@ -17,7 +17,13 @@
import { EcmUserModel, PeopleContentService } from '@alfresco/adf-content-services'; import { EcmUserModel, PeopleContentService } from '@alfresco/adf-content-services';
import { BpmUserModel, PeopleProcessService } from '@alfresco/adf-process-services'; import { BpmUserModel, PeopleProcessService } from '@alfresco/adf-process-services';
import { AuthenticationService, IdentityUserModel, IdentityUserService, UserInfoMode } from '@alfresco/adf-core'; import {
AuthenticationService,
BasicAlfrescoAuthService,
IdentityUserModel,
IdentityUserService,
UserInfoMode
} from '@alfresco/adf-core';
import { Component, OnInit, Input } from '@angular/core'; import { Component, OnInit, Input } from '@angular/core';
import { MenuPositionX, MenuPositionY } from '@angular/material/menu'; import { MenuPositionX, MenuPositionY } from '@angular/material/menu';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
@@ -46,6 +52,7 @@ export class UserInfoComponent implements OnInit {
constructor(private peopleContentService: PeopleContentService, constructor(private peopleContentService: PeopleContentService,
private peopleProcessService: PeopleProcessService, private peopleProcessService: PeopleProcessService,
private identityUserService: IdentityUserService, private identityUserService: IdentityUserService,
private basicAlfrescoAuthService: BasicAlfrescoAuthService,
private authService: AuthenticationService) { private authService: AuthenticationService) {
} }
@@ -77,7 +84,7 @@ export class UserInfoComponent implements OnInit {
} }
get isLoggedIn(): boolean { get isLoggedIn(): boolean {
if (this.authService.isKerberosEnabled()) { if (this.basicAlfrescoAuthService.isKerberosEnabled()) {
return true; return true;
} }
return this.authService.isLoggedIn(); return this.authService.isLoggedIn();
@@ -96,15 +103,15 @@ export class UserInfoComponent implements OnInit {
} }
private isAllLoggedIn() { private isAllLoggedIn() {
return (this.authService.isEcmLoggedIn() && this.authService.isBpmLoggedIn()) || (this.authService.isALLProvider() && this.authService.isKerberosEnabled()); return (this.authService.isEcmLoggedIn() && this.authService.isBpmLoggedIn()) || (this.authService.isALLProvider() && this.basicAlfrescoAuthService.isKerberosEnabled());
} }
private isBpmLoggedIn() { private isBpmLoggedIn() {
return this.authService.isBpmLoggedIn() || (this.authService.isECMProvider() && this.authService.isKerberosEnabled()); return this.authService.isBpmLoggedIn() || (this.authService.isECMProvider() && this.basicAlfrescoAuthService.isKerberosEnabled());
} }
private isEcmLoggedIn() { private isEcmLoggedIn() {
return this.authService.isEcmLoggedIn() || (this.authService.isECMProvider() && this.authService.isKerberosEnabled()); return this.authService.isEcmLoggedIn() || (this.authService.isECMProvider() && this.basicAlfrescoAuthService.isKerberosEnabled());
} }
} }

View File

@@ -65,7 +65,7 @@ export class HostSettingsComponent implements OnInit {
private storageService: StorageService, private storageService: StorageService,
private alfrescoApiService: AlfrescoApiService, private alfrescoApiService: AlfrescoApiService,
private appConfig: AppConfigService, private appConfig: AppConfigService,
private auth: AuthenticationService private authenticationService: AuthenticationService
) {} ) {}
ngOnInit() { ngOnInit() {
@@ -191,7 +191,7 @@ export class HostSettingsComponent implements OnInit {
this.storageService.setItem(AppConfigValues.AUTHTYPE, values.authType); this.storageService.setItem(AppConfigValues.AUTHTYPE, values.authType);
this.alfrescoApiService.reset(); this.alfrescoApiService.reset();
this.auth.reset(); this.authenticationService.reset();
this.alfrescoApiService.getInstance().invalidateSession(); this.alfrescoApiService.getInstance().invalidateSession();
this.success.emit(true); this.success.emit(true);
} }
@@ -235,10 +235,6 @@ export class HostSettingsComponent implements OnInit {
return this.form.get('authType').value === 'OAUTH'; return this.form.get('authType').value === 'OAUTH';
} }
get supportsCodeFlow(): boolean {
return this.auth.supportCodeFlow;
}
get providersControl(): UntypedFormControl { get providersControl(): UntypedFormControl {
return this.form.get('providersControl') as UntypedFormControl; return this.form.get('providersControl') as UntypedFormControl;
} }

View File

@@ -42,7 +42,9 @@ import { AlfrescoApiParamEncoder } from './alfresco-api/alfresco-api.param-encod
import { AlfrescoApiResponseError } from './alfresco-api/alfresco-api.response-error'; import { AlfrescoApiResponseError } from './alfresco-api/alfresco-api.response-error';
import { Constructor } from './types'; import { Constructor } from './types';
import { RequestOptions, SecurityOptions } from './interfaces'; import { RequestOptions, SecurityOptions } from './interfaces';
import ee, { Emitter } from 'event-emitter'; import { AppConfigService, AppConfigValues } from '../../../src/lib/app-config/app-config.service';
import ee from 'event-emitter';
import { Emitter } from 'event-emitter';
export interface Emitters { export interface Emitters {
readonly eventEmitter: Emitter; readonly eventEmitter: Emitter;
@@ -59,15 +61,6 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
once: ee.EmitterMethod; once: ee.EmitterMethod;
emit: (type: string, ...args: any[]) => void; emit: (type: string, ...args: any[]) => void;
private _disableCsrf = false;
private defaultSecurityOptions = {
withCredentials: true,
isBpmRequest: false,
authentications: {},
defaultHeaders: {}
};
get disableCsrf(): boolean { get disableCsrf(): boolean {
return this._disableCsrf; return this._disableCsrf;
} }
@@ -76,8 +69,14 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
this._disableCsrf = disableCsrf; this._disableCsrf = disableCsrf;
} }
constructor(private httpClient: HttpClient private defaultSecurityOptions = {
) { withCredentials: true,
isBpmRequest: false,
authentications: {},
defaultHeaders: {}
};
constructor(private httpClient: HttpClient, private appConfig: AppConfigService) {
ee(this); ee(this);
} }
@@ -271,7 +270,9 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
...((options.contentType) && {'Content-Type': options.contentType}) ...((options.contentType) && {'Content-Type': options.contentType})
}; };
if (!this.disableCsrf) { const disableCsrf = this.appConfig.get<boolean>(AppConfigValues.DISABLECSRF);
if (!disableCsrf) {
this.setCsrfToken(optionsHeaders); this.setCsrfToken(optionsHeaders);
} }
@@ -291,8 +292,7 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
} }
private createCSRFToken(a?: any): string { private createCSRFToken(a?: any): string {
const randomValue = window.crypto.getRandomValues(new Uint32Array(1))[0]; return a ? (a ^ ((Math.random() * 16) >> (a / 4))).toString(16) : ([1e16] + (1e16).toString()).replace(/[01]/g, this.createCSRFToken);
return a ? (a ^ ((randomValue * 16) >> (a / 4))).toString(16) : ([1e16] + (1e16).toString()).replace(/[01]/g, this.createCSRFToken);
} }
private static getResponseType(options: RequestOptions): 'blob' | 'json' | 'text' { private static getResponseType(options: RequestOptions): 'blob' | 'json' | 'text' {

View File

@@ -15,6 +15,32 @@
* limitations under the License. * limitations under the License.
*/ */
export interface SecurityOptions {
// readonly isBpmRequest: boolean;
// readonly enableCsrf?: boolean;
readonly withCredentials?: boolean;
readonly authentications?: Authentication;
readonly defaultHeaders?: Record<string, string>;
}
export interface Oauth2 {
refreshToken?: string;
accessToken?: string;
}
export interface BasicAuth {
username?: string;
password?: string;
ticket?: string;
}
export interface Authentication {
basicAuth?: BasicAuth;
oauth2?: Oauth2;
cookie?: string;
type?: string;
}
export interface RequestOptions { export interface RequestOptions {
httpMethod?: string; httpMethod?: string;
queryParams?: any; queryParams?: any;
@@ -23,14 +49,6 @@ export interface RequestOptions {
bodyParam?: any; bodyParam?: any;
returnType?: any; returnType?: any;
responseType?: string; responseType?: string;
readonly accept?: string; accept?: string;
readonly contentType?: string; contentType?: string;
}
export interface SecurityOptions {
readonly isBpmRequest: boolean;
readonly enableCsrf?: boolean;
readonly withCredentials?: boolean;
readonly authentications: any;
readonly defaultHeaders: Record<string, string>;
} }

View File

@@ -34,8 +34,7 @@ export class AlfrescoApiNoAuthService extends AlfrescoApiService {
override createInstance(config: AlfrescoApiConfig) { override createInstance(config: AlfrescoApiConfig) {
return new AlfrescoApi({ return new AlfrescoApi({
...config, ...config
oauthInit: false
}, this.adfHttpClient); }, this.adfHttpClient);
} }
} }

View File

@@ -25,6 +25,7 @@ import { OpenidConfiguration } from '../auth/interfaces/openid-configuration.int
import { OauthConfigModel } from '../auth/models/oauth-config.model'; import { OauthConfigModel } from '../auth/models/oauth-config.model';
/* spellchecker: disable */ /* spellchecker: disable */
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
export enum AppConfigValues { export enum AppConfigValues {
APP_CONFIG_LANGUAGES_KEY = 'languages', APP_CONFIG_LANGUAGES_KEY = 'languages',
@@ -74,6 +75,10 @@ export class AppConfigService {
protected onLoadSubject: Subject<any>; protected onLoadSubject: Subject<any>;
onLoad: Observable<any>; onLoad: Observable<any>;
get isLoaded() {
return this.status = Status.LOADED;
}
constructor(protected http: HttpClient, protected extensionService: ExtensionService) { constructor(protected http: HttpClient, protected extensionService: ExtensionService) {
this.onLoadSubject = new Subject(); this.onLoadSubject = new Subject();
this.onLoad = this.onLoadSubject.asObservable(); this.onLoad = this.onLoadSubject.asObservable();
@@ -259,4 +264,5 @@ export class AppConfigService {
return result; return result;
} }
} }

View File

@@ -16,40 +16,36 @@
*/ */
import { throwError as observableThrowError, Observable } from 'rxjs'; import { throwError as observableThrowError, Observable } from 'rxjs';
import { Injectable, Injector } from '@angular/core'; import { Injectable } from '@angular/core';
import { import {
HttpHandler, HttpInterceptor, HttpRequest, HttpHandler, HttpInterceptor, HttpRequest,
HttpSentEvent, HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpUserEvent, HttpHeaders HttpSentEvent, HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpUserEvent, HttpHeaders
} from '@angular/common/http'; } from '@angular/common/http';
import { AuthenticationService } from '../services/authentication.service';
import { catchError, mergeMap } from 'rxjs/operators'; import { catchError, mergeMap } from 'rxjs/operators';
import { AuthenticationService } from "../services/authentication.service";
@Injectable() @Injectable()
export class AuthBearerInterceptor implements HttpInterceptor { export class AuthBearerInterceptor implements HttpInterceptor {
protected bearerExcludedUrls: readonly string[] = ['resources/', 'assets/', 'auth/realms', 'idp/'];
private excludedUrlsRegex: RegExp[]; private excludedUrlsRegex: RegExp[];
constructor(private injector: Injector, private authService: AuthenticationService) { } constructor(private authenticationService: AuthenticationService) { }
private loadExcludedUrlsRegex() { private loadExcludedUrlsRegex() {
const excludedUrls = this.authService.getBearerExcludedUrls(); this.excludedUrlsRegex = this.bearerExcludedUrls.map((urlPattern) => new RegExp(urlPattern, 'i')) || [];
this.excludedUrlsRegex = excludedUrls.map((urlPattern) => new RegExp(urlPattern, 'i')) || [];
} }
intercept(req: HttpRequest<any>, next: HttpHandler): intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> { Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
this.authService = this.injector.get(AuthenticationService);
if (!this.authService || !this.authService.getBearerExcludedUrls()) {
return next.handle(req);
}
if (!this.excludedUrlsRegex) { if (!this.excludedUrlsRegex) {
this.loadExcludedUrlsRegex(); this.loadExcludedUrlsRegex();
} }
const urlRequest = req.url; const requestUrl = req.url;
const shallPass: boolean = this.excludedUrlsRegex.some((regex) => regex.test(urlRequest)); const shallPass: boolean = this.excludedUrlsRegex.some((regex) => regex.test(requestUrl));
if (shallPass) { if (shallPass) {
return next.handle(req) return next.handle(req)
.pipe( .pipe(
@@ -57,7 +53,7 @@ export class AuthBearerInterceptor implements HttpInterceptor {
); );
} }
return this.authService.addTokenToHeader(req.headers) return this.authenticationService.addTokenToHeader(requestUrl, req.headers)
.pipe( .pipe(
mergeMap((headersWithBearer) => { mergeMap((headersWithBearer) => {
const headerWithContentType = this.appendJsonContentType(headersWithBearer); const headerWithContentType = this.appendJsonContentType(headersWithBearer);

View File

@@ -0,0 +1,333 @@
/*!
* @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 { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
import { Authentication } from '../interfaces/authentication.interface';
import { CookieService } from '../../common/services/cookie.service';
import { ContentAuth } from './contentAuth';
import { ProcessAuth } from './processAuth';
import { map } from 'rxjs/operators';
import { from, Observable } from 'rxjs';
import { RedirectionModel } from "../index";
import { BaseAuthenticationService } from "../services/base-authentication.service";
import { LogService } from "../../common";
import { HttpHeaders } from "@angular/common/http";
const REMEMBER_ME_COOKIE_KEY = 'ALFRESCO_REMEMBER_ME';
const REMEMBER_ME_UNTIL = 1000 * 60 * 60 * 24 * 30;
@Injectable({
providedIn: 'root'
})
export class BasicAlfrescoAuthService extends BaseAuthenticationService {
protected redirectUrl: RedirectionModel = null;
authentications: Authentication = {
basicAuth: {
ticket: '',
},
type: 'basic',
};
constructor(
logService: LogService,
appConfig: AppConfigService,
cookie: CookieService,
private contentAuth: ContentAuth,
private processAuth: ProcessAuth
) {
super(appConfig, cookie, logService);
this.contentAuth.onLogout.pipe(map((event) => {
this.onLogout.next(event)
}));
this.contentAuth.onLogin.pipe(map((event) => {
this.onLogout.next(event)
}));
this.contentAuth.onError.pipe(map((event) => {
this.onLogout.next(event)
}));
this.processAuth.onLogout.pipe(map((event) => {
this.onLogout.next(event)
}));
this.processAuth.onLogin.pipe(map((event) => {
this.onLogin.next(event)
}));
this.processAuth.onError.pipe(map((event) => {
this.onError.next(event)
}));
}
/**
* Logs the user in.
*
* @param username Username for the login
* @param password Password for the login
* @param rememberMe Stores the user's login details if true
* @returns Object with auth type ("ECM", "BPM" or "ALL") and auth ticket
*/
login(username: string, password: string, rememberMe: boolean = false): Observable<{ type: string; ticket: any }> {
return from(this.executeLogin(username, password)).pipe(
map((response: any) => {
this.saveRememberMeCookie(rememberMe);
this.onLogin.next(response);
return {
type: this.appConfig.get(AppConfigValues.PROVIDERS),
ticket: response
};
})
);
}
/**
* login Alfresco API
* @param username: // Username to login
* @param password: // Password to login
*
* @returns {Promise} A promise that returns {new authentication ticket} if resolved and {error} if rejected.
* */
async executeLogin(username: string, password: string): Promise<any> {
if (!this.isCredentialValid(username) || !this.isCredentialValid(password)) {
return Promise.reject('missing username or password');
}
if (username) {
username = username.trim();
}
if (this.isBPMProvider()) {
try {
return await this.processAuth.login(username, password);
} catch (e) {
console.log('login BPM error');
}
} else if (this.isECMProvider()) {
try {
return await this.contentAuth.login(username, password);
} catch (e) {
console.log('login BPM error');
}
} else if (this.isALLProvider()) {
return this.loginBPMECM(username, password);
} else {
return Promise.reject('Unknown configuration');
}
}
private loginBPMECM(username: string, password: string): Promise<any> {
const contentPromise = this.contentAuth.login(username, password);
const processPromise = this.processAuth.login(username, password);
return new Promise((resolve, reject) => {
Promise.all([contentPromise, processPromise]).then(
(data) => {
this.onLogin.next('success');
resolve(data);
},
(error) => {
this.contentAuth.invalidateSession();
this.processAuth.invalidateSession();
if (error.status === 401) {
this.onError.next('unauthorized');
}
this.onError.next('error');
reject(error);
});
});
}
/**
* Checks whether the "remember me" cookie was set or not.
*
* @returns True if set, false otherwise
*/
isRememberMeSet(): boolean {
return this.cookie.getItem(REMEMBER_ME_COOKIE_KEY) !== null;
}
/**
* Saves the "remember me" cookie as either a long-life cookie or a session cookie.
*
* @param rememberMe Enables a long-life cookie
*/
saveRememberMeCookie(rememberMe: boolean): void {
let expiration = null;
if (rememberMe) {
expiration = new Date();
const time = expiration.getTime();
const expireTime = time + REMEMBER_ME_UNTIL;
expiration.setTime(expireTime);
}
this.cookie.setItem(REMEMBER_ME_COOKIE_KEY, '1', expiration, null);
}
isCredentialValid(credential: string): boolean {
return credential !== undefined && credential !== null && credential !== '';
}
getToken(): string {
return "";
}
isBpmLoggedIn(): boolean {
return this.processAuth.isLoggedIn();
}
isEcmLoggedIn(): boolean {
return this.contentAuth.isLoggedIn();
}
isLoggedIn(): boolean {
const authWithCredentials = this.isKerberosEnabled();
if (this.isBPMProvider()) {
return this.processAuth.isLoggedIn();
} else if (this.isECMProvider()) {
return authWithCredentials ? true : this.contentAuth.isLoggedIn();
} else if (this.isALLProvider()) {
return authWithCredentials ? true : (this.contentAuth.isLoggedIn() && this.processAuth.isLoggedIn());
} else {
return false;
}
}
/**
* logout Alfresco API
* */
logout(): Promise<any> {
if (this.isBPMProvider()) {
return this.processAuth.logout();
} else if (this.isECMProvider()) {
const contentPromise = this.contentAuth.logout();
contentPromise.then(
() => this.contentAuth.ticket = undefined,
() => {
}
);
return contentPromise;
} else if (this.isALLProvider()) {
return this.logoutBPMECM();
}
return Promise.resolve();
}
private logoutBPMECM(): Promise<any> {
const contentPromise = this.contentAuth.logout();
const processPromise = this.processAuth.logout();
return new Promise((resolve, reject) => {
Promise.all([contentPromise, processPromise]).then(
() => {
this.contentAuth.ticket = undefined;
this.processAuth.ticket = undefined;
this.onLogout.next('logout');
resolve('logout');
},
(error) => {
if (error.status === 401) {
this.onError.next('unauthorized');
}
this.onError.next('error');
reject(error);
});
});
}
reset(): void {
}
/** Gets the URL to redirect to after login.
*
* @returns The redirect URL
*/
getRedirect(): string {
const provider = this.appConfig.get<string>(AppConfigValues.PROVIDERS);
return this.hasValidRedirection(provider) ? this.redirectUrl.url : null;
}
setRedirect(url?: RedirectionModel) {
this.redirectUrl = url;
}
private hasValidRedirection(provider: string): boolean {
return this.redirectUrl && (this.redirectUrl.provider === provider || this.hasSelectedProviderAll(provider));
}
private hasSelectedProviderAll(provider: string): boolean {
return this.redirectUrl && (this.redirectUrl.provider === 'ALL' || provider === 'ALL');
}
getBpmUsername(): string {
return this.contentAuth.authentications.basicAuth.username;
}
getEcmUsername(): string {
return this.processAuth.authentications.basicAuth.username;
}
/**
* Does kerberos enabled?
*
* @returns True if enabled, false otherwise
*/
isKerberosEnabled(): boolean {
return this.appConfig.get<boolean>(AppConfigValues.AUTH_WITH_CREDENTIALS, false);
}
getAuthHeaders(requestUrl: string, header: HttpHeaders): HttpHeaders {
return this.addBasicAuth(requestUrl, header);
}
private addBasicAuth(requestUrl: string, header: HttpHeaders): HttpHeaders {
const ticket = this.getTicketEcmBase64(requestUrl);
if (!ticket) {
return header;
}
return header.set('Authorization', ticket);
}
/**
* Gets the BPM ticket from the Storage in Base 64 format.
*
* @returns The ticket or `null` if none was found
*/
private getTicketEcmBase64(requestUrl: string): string | null {
let ticket = null;
const contextRootBpm = this.appConfig.get<string>(AppConfigValues.CONTEXTROOTBPM);
const contextRoot = this.appConfig.get<string>(AppConfigValues.CONTEXTROOTECM);
if (contextRoot && requestUrl.indexOf(contextRoot) !== -1) {
ticket = 'Basic ' + btoa(this.contentAuth.getTicket());
} else if (contextRootBpm && requestUrl.indexOf(contextRootBpm) !== -1) {
ticket = 'Basic ' + this.processAuth.getTicket();
}
return ticket;
}
}

View File

@@ -0,0 +1,221 @@
/*!
* @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.
*/
import { Injectable } from '@angular/core';
import { AdfHttpClient } from '@alfresco/adf-core/api';
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
import { StorageService } from '../../common/services/storage.service';
import { ReplaySubject, Subject } from 'rxjs';
import { Authentication } from '../interfaces/authentication.interface';
export interface TicketBody {
userId?: string;
password?: string;
}
export interface TicketEntry {
entry: {
id?: string;
userId?: string;
};
}
export const CONTENT_TICKET_STORAGE_LABEL = 'ticket-ECM';
@Injectable({
providedIn: 'root'
})
export class ContentAuth {
onLogin = new ReplaySubject<any>(1);
onLogout = new ReplaySubject<any>(1);
onError = new Subject<any>();
ticket: string;
config = {
ticketEcm: null
};
authentications: Authentication = {
basicAuth: {
ticket: '',
},
type: 'basic',
};
get basePath(): string {
const contextRootEcm = this.appConfigService.get<string>(AppConfigValues.CONTEXTROOTECM) || 'alfresco';
return this.appConfigService.get<string>(AppConfigValues.ECMHOST) + '/' + contextRootEcm + '/api/-default-/public/authentication/versions/1';
}
constructor(private appConfigService: AppConfigService,
private adfHttpClient: AdfHttpClient,
private storageService: StorageService) {
this.appConfigService.onLoad.subscribe(() => {
this.setConfig();
});
}
private setConfig() {
if (this.storageService.getItem(CONTENT_TICKET_STORAGE_LABEL)) {
this.setTicket(this.storageService.getItem(CONTENT_TICKET_STORAGE_LABEL));
}
}
saveUsername(username: string) {
this.storageService.setItem('ACS_USERNAME', username);
}
/**
* login Alfresco API
* @param username: // Username to login
* @param password: // Password to login
*
* @returns A promise that returns {new authentication ticket} if resolved and {error} if rejected.
* */
login(username: string, password: string): Promise<any> {
this.authentications.basicAuth.username = username;
this.authentications.basicAuth.password = password;
let loginRequest: any = {};
loginRequest.userId = this.authentications.basicAuth.username;
loginRequest.password = this.authentications.basicAuth.password;
return new Promise((resolve, reject) => {
this.createTicket(loginRequest)
.then((data: any) => {
this.saveUsername(username);
this.setTicket(data.entry.id);
this.adfHttpClient.emit('success');
this.onLogin.next('success');
resolve(data.entry.id);
})
.catch((error) => {
this.saveUsername('');
if (error.status === 401) {
this.adfHttpClient.emit('unauthorized');
this.onError.next('unauthorized');
} else if (error.status === 403) {
this.adfHttpClient.emit('forbidden');
this.onError.next('forbidden');
} else {
this.adfHttpClient.emit('error');
this.onError.next('error');
}
reject(error);
});
});
}
/**
* logout Alfresco API
*
* @returns {Promise} A promise that returns { authentication ticket} if resolved and {error} if rejected.
* */
logout(): Promise<any> {
this.saveUsername('');
return new Promise((resolve, reject) => {
this.deleteTicket().then(
() => {
this.adfHttpClient.emit('logout');
this.onLogout.next('logout');
this.invalidateSession();
resolve('logout');
},
(error) => {
if (error.status === 401) {
this.adfHttpClient.emit('unauthorized');
this.onError.next('unauthorized');
}
this.adfHttpClient.emit('error');
this.onError.next('error');
reject(error);
});
});
}
/**
* Set the current Ticket
* */
setTicket(ticket: string) {
this.authentications.basicAuth.username = 'ROLE_TICKET';
this.authentications.basicAuth.password = ticket;
this.config.ticketEcm = ticket;
this.storageService.setItem(CONTENT_TICKET_STORAGE_LABEL, ticket);
this.ticket = ticket;
}
/**
* Get the current Ticket
* */
getTicket(): string {
if(!this.ticket){
this.onError.next('error');
}
return this.ticket;
}
invalidateSession() {
this.storageService.removeItem(CONTENT_TICKET_STORAGE_LABEL);
this.authentications.basicAuth.username = null;
this.authentications.basicAuth.password = null;
this.config.ticketEcm = null;
this.ticket = null;
}
/**
* If the client is logged in return true
*/
isLoggedIn(): boolean {
return !!this.ticket;
}
/**
* return the Authentication
* */
getAuthentication() {
return this.authentications;
}
createTicket(ticketBodyCreate: TicketBody): Promise<TicketEntry> {
if (ticketBodyCreate === null || ticketBodyCreate === undefined) {
this.onError.next((`Missing param ticketBodyCreate`));
throw new Error(`Missing param '${name}'`);
}
return this.adfHttpClient.post(this.basePath + '/tickets', {bodyParam: ticketBodyCreate});
}
/**
* Delete ticket (logout)
*
* **Note:** this endpoint is available in Alfresco 5.2 and newer versions.
Deletes logged in ticket (logout).
*
* @return Promise<{}>
*/
deleteTicket(): Promise<any> {
return this.adfHttpClient.delete(this.basePath + '/tickets/-me-');
}
}

View File

@@ -0,0 +1,206 @@
/*!
* @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.
*/
import { Injectable } from '@angular/core';
import { AdfHttpClient } from '@alfresco/adf-core/api';
import { Authentication } from '../interfaces/authentication.interface';
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
import { StorageService } from '../../common/services/storage.service';
import { ReplaySubject, Subject } from 'rxjs';
export const PROCESS_TICKET_STORAGE_LABEL = 'ticket-BPM';
@Injectable({
providedIn: 'root'
})
export class ProcessAuth {
onLogin = new ReplaySubject<any>(1);
onLogout = new ReplaySubject<any>(1);
onError = new Subject<any>();
ticket: string;
config = {
ticketBpm: null
};
authentications: Authentication = {
basicAuth: {ticket: ''}, type: 'activiti'
};
get basePath(): string {
const contextRootBpm = this.appConfigService.get<string>(AppConfigValues.CONTEXTROOTBPM) || 'activiti-app';
return this.appConfigService.get<string>(AppConfigValues.BPMHOST) + '/' + contextRootBpm;
}
constructor(private appConfigService: AppConfigService,
private adfHttpClient: AdfHttpClient,
private storageService: StorageService) {
this.appConfigService.onLoad.subscribe(() => {
this.setConfig();
});
}
private setConfig() {
this.ticket = undefined;
this.setTicket(this.storageService.getItem(PROCESS_TICKET_STORAGE_LABEL));
}
saveUsername(username: string) {
this.storageService.setItem('APS_USERNAME', username);
}
/**
* login Activiti API
* @param username: // Username to login
* @param password: // Password to login
*
* @returns A promise that returns {new authentication ticket} if resolved and {error} if rejected.
* */
login(username: string, password: string): Promise<any> {
this.authentications.basicAuth.username = username;
this.authentications.basicAuth.password = password;
const options = {
headerParams: {
'Content-Type': 'application/x-www-form-urlencoded',
'Cache-Control': 'no-cache'
},
formParams: {
j_username: this.authentications.basicAuth.username,
j_password: this.authentications.basicAuth.password,
_spring_security_remember_me: true,
submit: 'Login'
},
contentType: 'application/x-www-form-urlencoded',
accept: 'application/json',
};
let promise: any = new Promise((resolve, reject) => {
this.adfHttpClient.post(this.basePath + '/app/authentication', options).then(
() => {
this.saveUsername(username);
let ticket = this.basicAuth(this.authentications.basicAuth.username, this.authentications.basicAuth.password);
this.setTicket(ticket);
this.onLogin.next('success');
this.adfHttpClient.emit('success');
this.adfHttpClient.emit('logged-in');
resolve(ticket);
},
(error) => {
this.saveUsername('');
if (error.status === 401) {
this.adfHttpClient.emit('unauthorized');
this.onError.next('unauthorized');
} else if (error.status === 403) {
this.adfHttpClient.emit('forbidden');
this.onError.next('forbidden');
} else {
this.adfHttpClient.emit('error');
this.onError.next('error');
}
reject(error);
});
});
return promise;
}
/**
* logout Alfresco API
*
* @returns {Promise} A promise that returns {new authentication ticket} if resolved and {error} if rejected.
* */
async logout(): Promise<any> {
this.saveUsername('');
return new Promise((resolve, reject) => {
this.adfHttpClient.get(this.basePath + `/app/logout`, {}).then(
() => {
this.invalidateSession();
this.onLogout.next('logout');
this.adfHttpClient.emit('logout');
resolve('logout');
},
(error) => {
if (error.status === 401) {
this.adfHttpClient.emit('unauthorized');
this.onError.next('unauthorized');
}
this.adfHttpClient.emit('error');
this.onError.next('error');
reject(error);
});
});
}
basicAuth(username: string, password: string): string {
const str: any = username + ':' + password;
let base64;
if (typeof Buffer === 'function') {
base64 = Buffer.from(str.toString(), 'binary').toString('base64');
} else {
base64 = btoa(str);
}
return base64;
}
/**
* Set the current Ticket
*
* @param ticket
* */
setTicket(ticket: string) {
if(ticket && ticket !=='null') {
this.authentications.basicAuth.ticket = ticket;
this.authentications.basicAuth.password = null;
this.config.ticketBpm = ticket;
this.storageService.setItem(PROCESS_TICKET_STORAGE_LABEL, ticket);
this.ticket = ticket;
}
}
invalidateSession() {
this.storageService.removeItem(PROCESS_TICKET_STORAGE_LABEL);
this.authentications.basicAuth.ticket = null;
this.authentications.basicAuth.password = null;
this.authentications.basicAuth.username = null;
this.config.ticketBpm = null;
this.ticket = null;
}
/**
* Get the current Ticket
* */
getTicket(): string {
if(!this.ticket){
this.onError.next('error');
return null;
}
return this.ticket;
}
/**
* If the client is logged in return true
*/
isLoggedIn(): boolean {
return !!this.ticket;
}
}

View File

@@ -1,6 +1,6 @@
/*! /*!
* @license * @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. * Copyright 2019 Alfresco Software, Ltd.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -24,19 +24,18 @@ import {
UrlTree UrlTree
} from '@angular/router'; } from '@angular/router';
import { AuthenticationService } from '../services/authentication.service'; import { AuthenticationService } from '../services/authentication.service';
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service'; import {
AppConfigService,
AppConfigValues
} from '../../app-config/app-config.service';
import { OauthConfigModel } from '../models/oauth-config.model'; import { OauthConfigModel } from '../models/oauth-config.model';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { StorageService } from '../../common/services/storage.service'; import { StorageService } from '../../common/services/storage.service';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { inject } from '@angular/core'; import { BasicAlfrescoAuthService } from "../basic-auth/basic-alfresco-auth.service";
import { OidcAuthenticationService } from "../services/oidc-authentication.service";
export abstract class AuthGuardBase implements CanActivate, CanActivateChild { export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
protected authenticationService = inject(AuthenticationService);
protected router = inject(Router);
protected appConfigService = inject(AppConfigService);
protected dialog = inject(MatDialog);
private storageService = inject(StorageService);
protected get withCredentials(): boolean { protected get withCredentials(): boolean {
return this.appConfigService.get<boolean>( return this.appConfigService.get<boolean>(
@@ -45,6 +44,17 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
); );
} }
constructor(
protected authenticationService: AuthenticationService,
protected basicAlfrescoAuthService: BasicAlfrescoAuthService,
protected oidcAuthenticationService: OidcAuthenticationService,
protected router: Router,
protected appConfigService: AppConfigService,
protected dialog: MatDialog,
private storageService: StorageService
) {
}
abstract checkLogin( abstract checkLogin(
activeRoute: ActivatedRouteSnapshot, activeRoute: ActivatedRouteSnapshot,
redirectUrl: string redirectUrl: string
@@ -89,15 +99,17 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
let urlToRedirect = `/${this.getLoginRoute()}`; let urlToRedirect = `/${this.getLoginRoute()}`;
if (!this.authenticationService.isOauth()) { if (!this.authenticationService.isOauth()) {
this.authenticationService.setRedirect({ this.basicAlfrescoAuthService.setRedirect({
provider: this.getProvider(), provider: this.getProvider(),
url url
}); });
urlToRedirect = `${urlToRedirect}?redirectUrl=${url}`; urlToRedirect = `${urlToRedirect}?redirectUrl=${url}`;
return this.navigate(urlToRedirect); return this.navigate(urlToRedirect);
} else if (this.getOauthConfig().silentLogin && !this.authenticationService.isPublicUrl()) { } else if (this.getOauthConfig().silentLogin && !this.oidcAuthenticationService.isPublicUrl()) {
this.authenticationService.ssoImplicitLogin(); if (!this.oidcAuthenticationService.hasValidIdToken() || !this.oidcAuthenticationService.hasValidAccessToken()) {
this.oidcAuthenticationService.ssoImplicitLogin();
}
} else { } else {
return this.navigate(urlToRedirect); return this.navigate(urlToRedirect);
} }
@@ -112,7 +124,13 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
} }
protected getOauthConfig(): OauthConfigModel { protected getOauthConfig(): OauthConfigModel {
return this.appConfigService.oauth2; return (
this.appConfigService &&
this.appConfigService.get<OauthConfigModel>(
AppConfigValues.OAUTHCONFIG,
null
)
);
} }
protected getLoginRoute(): string { protected getLoginRoute(): string {
@@ -136,12 +154,21 @@ export abstract class AuthGuardBase implements CanActivate, CanActivateChild {
} }
protected isOAuthWithoutSilentLogin(): boolean { protected isOAuthWithoutSilentLogin(): boolean {
const oauth = this.appConfigService.oauth2; const oauth = this.appConfigService.get<OauthConfigModel>(
return this.authenticationService.isOauth() && !!oauth && !oauth.silentLogin; AppConfigValues.OAUTHCONFIG,
null
);
return (
this.authenticationService.isOauth() && !!oauth && !oauth.silentLogin
);
} }
protected isSilentLogin(): boolean { protected isSilentLogin(): boolean {
const oauth = this.appConfigService.oauth2;; const oauth = this.appConfigService.get<OauthConfigModel>(
AppConfigValues.OAUTHCONFIG,
null
);
return this.authenticationService.isOauth() && oauth && oauth.silentLogin; return this.authenticationService.isOauth() && oauth && oauth.silentLogin;
} }

View File

@@ -1,6 +1,6 @@
/*! /*!
* @license * @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. * Copyright 2019 Alfresco Software, Ltd.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,13 +16,30 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, UrlTree } from '@angular/router'; import { ActivatedRouteSnapshot, Router, UrlTree } from '@angular/router';
import { AppConfigService } from '../../app-config/app-config.service';
import { AuthenticationService } from '../services/authentication.service';
import { AuthGuardBase } from './auth-guard-base'; import { AuthGuardBase } from './auth-guard-base';
import { MatDialog } from '@angular/material/dialog';
import { StorageService } from '../../common/services/storage.service';
import { BasicAlfrescoAuthService } from "../basic-auth/basic-alfresco-auth.service";
import { OidcAuthenticationService } from "../services/oidc-authentication.service";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AuthGuardBpm extends AuthGuardBase { export class AuthGuardBpm extends AuthGuardBase {
constructor(authenticationService: AuthenticationService,
basicAlfrescoAuthService: BasicAlfrescoAuthService,
oidcAuthenticationService: OidcAuthenticationService,
router: Router,
appConfigService: AppConfigService,
dialog: MatDialog,
storageService: StorageService) {
super(authenticationService,basicAlfrescoAuthService, oidcAuthenticationService,router, appConfigService, dialog, storageService);
}
async checkLogin(_: ActivatedRouteSnapshot, redirectUrl: string): Promise<boolean | UrlTree> { async checkLogin(_: ActivatedRouteSnapshot, redirectUrl: string): Promise<boolean | UrlTree> {
if (this.authenticationService.isBpmLoggedIn() || this.withCredentials) { if (this.authenticationService.isBpmLoggedIn() || this.withCredentials) {
return true; return true;

View File

@@ -1,6 +1,6 @@
/*! /*!
* @license * @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. * Copyright 2019 Alfresco Software, Ltd.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,13 +16,32 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, UrlTree } from '@angular/router'; import {
ActivatedRouteSnapshot, Router, UrlTree
} from '@angular/router';
import { AuthenticationService } from '../services/authentication.service';
import { AppConfigService } from '../../app-config/app-config.service';
import { AuthGuardBase } from './auth-guard-base'; import { AuthGuardBase } from './auth-guard-base';
import { MatDialog } from '@angular/material/dialog';
import { StorageService } from '../../common/services/storage.service';
import { BasicAlfrescoAuthService } from "../basic-auth/basic-alfresco-auth.service";
import { OidcAuthenticationService } from "../services/oidc-authentication.service";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AuthGuardEcm extends AuthGuardBase { export class AuthGuardEcm extends AuthGuardBase {
constructor(authenticationService: AuthenticationService,
basicAlfrescoAuthService: BasicAlfrescoAuthService,
oidcAuthenticationService: OidcAuthenticationService,
router: Router,
appConfigService: AppConfigService,
dialog: MatDialog,
storageService: StorageService) {
super(authenticationService, basicAlfrescoAuthService, oidcAuthenticationService, router, appConfigService, dialog, storageService);
}
async checkLogin(_: ActivatedRouteSnapshot, redirectUrl: string): Promise<boolean | UrlTree> { async checkLogin(_: ActivatedRouteSnapshot, redirectUrl: string): Promise<boolean | UrlTree> {
if (this.authenticationService.isEcmLoggedIn() || this.withCredentials) { if (this.authenticationService.isEcmLoggedIn() || this.withCredentials) {
return true; return true;

View File

@@ -1,6 +1,6 @@
/*! /*!
* @license * @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. * Copyright 2019 Alfresco Software, Ltd.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,9 +16,15 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, UrlTree } from '@angular/router'; import { ActivatedRouteSnapshot, Router, UrlTree } from '@angular/router';
import { AuthenticationService } from '../services/authentication.service';
import { AppConfigService } from '../../app-config/app-config.service';
import { AuthGuardBase } from './auth-guard-base'; import { AuthGuardBase } from './auth-guard-base';
import { JwtHelperService } from '../services/jwt-helper.service'; import { JwtHelperService } from '../services/jwt-helper.service';
import { MatDialog } from '@angular/material/dialog';
import { StorageService } from '../../common/services/storage.service';
import { BasicAlfrescoAuthService } from "../basic-auth/basic-alfresco-auth.service";
import { OidcAuthenticationService } from "../services/oidc-authentication.service";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@@ -27,8 +33,15 @@ export class AuthGuard extends AuthGuardBase {
ticketChangeBind: any; ticketChangeBind: any;
constructor(private jwtHelperService: JwtHelperService) { constructor(private jwtHelperService: JwtHelperService,
super(); authenticationService: AuthenticationService,
basicAlfrescoAuthService: BasicAlfrescoAuthService,
oidcAuthenticationService: OidcAuthenticationService,
router: Router,
appConfigService: AppConfigService,
dialog: MatDialog,
storageService: StorageService) {
super(authenticationService, basicAlfrescoAuthService, oidcAuthenticationService, router, appConfigService, dialog, storageService);
this.ticketChangeBind = this.ticketChange.bind(this); this.ticketChangeBind = this.ticketChange.bind(this);
window.addEventListener('storage', this.ticketChangeBind); window.addEventListener('storage', this.ticketChangeBind);

View File

@@ -0,0 +1,57 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { HttpHeaders } from '@angular/common/http';
import ee from 'event-emitter';
export interface AuthenticationServiceInterface {
onError: any;
onLogin: any;
onLogout: any;
on: ee.EmitterMethod;
off: ee.EmitterMethod;
once: ee.EmitterMethod;
emit: (type: string, ...args: any[]) => void;
getToken(): string;
isLoggedIn(): boolean;
isOauth(): boolean;
logout(): any;
isEcmLoggedIn(): boolean;
isBpmLoggedIn(): boolean;
isECMProvider(): boolean;
isBPMProvider(): boolean;
isALLProvider(): boolean;
getEcmUsername(): string;
getBpmUsername(): string;
getAuthHeaders(requestUrl: string, header: HttpHeaders): HttpHeaders;
reset(): void;
}

View File

@@ -0,0 +1,28 @@
/*!
* @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 Authentication {
basicAuth?: BasicAuth;
cookie?: string;
type?: string;
}
export interface BasicAuth {
username?: string;
password?: string;
ticket?: string;
}

View File

@@ -19,20 +19,16 @@ import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
import { AuthConfig, AUTH_CONFIG, OAuthModule, OAuthService, OAuthStorage } from 'angular-oauth2-oidc'; import { AuthConfig, AUTH_CONFIG, OAuthModule, OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
import { AlfrescoApiNoAuthService } from '../../api-factories/alfresco-api-no-auth.service'; import { AlfrescoApiNoAuthService } from '../../api-factories/alfresco-api-no-auth.service';
import { AlfrescoApiService } from '../../services/alfresco-api.service'; import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { AuthGuardBpm } from '../guard/auth-guard-bpm.service';
import { AuthGuardEcm } from '../guard/auth-guard-ecm.service';
import { AuthGuard } from '../guard/auth-guard.service';
import { AuthenticationService } from '../services/authentication.service'; import { AuthenticationService } from '../services/authentication.service';
import { StorageService } from '../../common/services/storage.service'; import { StorageService } from '../../common/services/storage.service';
import { AuthModuleConfig, AUTH_MODULE_CONFIG } from './auth-config'; import { AuthModuleConfig, AUTH_MODULE_CONFIG } from './auth-config';
import { authConfigFactory, AuthConfigService } from './auth-config.service'; import { authConfigFactory, AuthConfigService } from './auth-config.service';
import { AuthRoutingModule } from './auth-routing.module'; import { AuthRoutingModule } from './auth-routing.module';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { OidcAuthGuard } from './oidc-auth.guard';
import { OIDCAuthenticationService } from './oidc-authentication.service';
import { RedirectAuthService } from './redirect-auth.service'; import { RedirectAuthService } from './redirect-auth.service';
import { AuthenticationConfirmationComponent } from './view/authentication-confirmation/authentication-confirmation.component'; import { AuthenticationConfirmationComponent } from './view/authentication-confirmation/authentication-confirmation.component';
export function loginFactory(oAuthService: OAuthService, storage: OAuthStorage, config: AuthConfig) { export function loginFactory(oAuthService: OAuthService, storage: OAuthStorage, config: AuthConfig) {
const service = new RedirectAuthService(oAuthService, storage, config); const service = new RedirectAuthService(oAuthService, storage, config);
return () => service.init(); return () => service.init();
@@ -43,10 +39,10 @@ export function loginFactory(oAuthService: OAuthService, storage: OAuthStorage,
imports: [AuthRoutingModule, OAuthModule.forRoot()], imports: [AuthRoutingModule, OAuthModule.forRoot()],
providers: [ providers: [
{ provide: OAuthStorage, useExisting: StorageService }, { provide: OAuthStorage, useExisting: StorageService },
{ provide: AuthGuard, useClass: OidcAuthGuard }, // { provide: AuthGuard, useClass: OidcAuthGuard },
{ provide: AuthGuardEcm, useClass: OidcAuthGuard }, // { provide: AuthGuardEcm, useClass: OidcAuthGuard },
{ provide: AuthGuardBpm, useClass: OidcAuthGuard }, // { provide: AuthGuardBpm, useClass: OidcAuthGuard },
{ provide: AuthenticationService, useClass: OIDCAuthenticationService }, { provide: AuthenticationService},
{ provide: AlfrescoApiService, useClass: AlfrescoApiNoAuthService }, { provide: AlfrescoApiService, useClass: AlfrescoApiNoAuthService },
{ {
provide: AUTH_CONFIG, provide: AUTH_CONFIG,

View File

@@ -1,134 +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.
*/
import { Injectable, inject } from '@angular/core';
import { OAuthEvent, OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
import { EMPTY, Observable } from 'rxjs';
import { catchError, filter, map } from 'rxjs/operators';
import { AppConfigValues } from '../../app-config/app-config.service';
import { BaseAuthenticationService } from '../services/base-authentication.service';
import { JwtHelperService } from '../services/jwt-helper.service';
import { AuthConfigService } from '../oidc/auth-config.service';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class OIDCAuthenticationService extends BaseAuthenticationService {
private authStorage = inject(OAuthStorage);
private oauthService = inject(OAuthService);
private readonly authConfig = inject(AuthConfigService);
private readonly auth = inject(AuthService);
readonly supportCodeFlow = true;
constructor() {
super();
this.alfrescoApi.alfrescoApiInitialized.subscribe(() => {
this.oauthService.events.pipe(
filter((event)=> event.type === 'token_received')
).subscribe(()=>{
this.onLogin.next();
});
});
}
isEcmLoggedIn(): boolean {
return this.isLoggedIn();
}
isBpmLoggedIn(): boolean {
return this.isLoggedIn();
}
isLoggedIn(): boolean {
return this.oauthService.hasValidAccessToken() && this.oauthService.hasValidIdToken();
}
isLoggedInWith(_provider?: string): boolean {
return this.isLoggedIn();
}
isOauth(): boolean {
return this.appConfig.get(AppConfigValues.AUTHTYPE) === 'OAUTH';
}
isImplicitFlow(): boolean {
return !!this.appConfig.oauth2?.implicitFlow;
}
isAuthCodeFlow(): boolean {
return !!this.appConfig.oauth2?.codeFlow;
}
login(username: string, password: string, rememberMe: boolean = false): Observable<{ type: string; ticket: any }> {
return this.auth.baseAuthLogin(username, password).pipe(
map((response) => {
this.saveRememberMeCookie(rememberMe);
this.onLogin.next(response);
return {
type: this.appConfig.get(AppConfigValues.PROVIDERS),
ticket: response
};
}),
catchError((err) => this.handleError(err))
);
}
getEcmUsername(): string {
return (this.oauthService.getIdentityClaims() as any).preferred_username;
}
getBpmUsername(): string {
return (this.oauthService.getIdentityClaims() as any).preferred_username;
}
ssoImplicitLogin() {
this.oauthService.initLoginFlow();
}
ssoCodeFlowLogin() {
this.oauthService.initCodeFlow();
}
isRememberMeSet(): boolean {
return true;
}
logout() {
this.oauthService.logOut();
return EMPTY;
}
getToken(): string {
return this.authStorage.getItem(JwtHelperService.USER_ACCESS_TOKEN);
}
reset(): void {
const config = this.authConfig.loadAppConfig();
this.auth.updateIDPConfiguration(config);
const oauth2 = this.appConfig.oauth2;
if (config.oidc && oauth2.silentLogin) {
this.auth.login();
}
}
once(event: string): Observable<OAuthEvent> {
return this.oauthService.events.pipe(filter(_event => _event.type === event));
}
}

View File

@@ -31,6 +31,10 @@ export * from './services/jwt-helper.service';
export * from './services/oauth2.service'; export * from './services/oauth2.service';
export * from './services/user-access.service'; export * from './services/user-access.service';
export * from './basic-auth/basic-alfresco-auth.service';
export * from './basic-auth/processAuth';
export * from './basic-auth/contentAuth';
export * from './interfaces/identity-user.service.interface'; export * from './interfaces/identity-user.service.interface';
export * from './interfaces/identity-group.interface'; export * from './interfaces/identity-group.interface';
export * from './interfaces/openid-configuration.interface'; export * from './interfaces/openid-configuration.interface';

View File

@@ -1,6 +1,6 @@
/*! /*!
* @license * @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. * Copyright 2019 Alfresco Software, Ltd.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,184 +15,104 @@
* limitations under the License. * limitations under the License.
*/ */
import { Injectable, inject } from '@angular/core'; import { Injectable, Injector } from '@angular/core';
import { Observable, from } from 'rxjs'; import { OidcAuthenticationService } from './oidc-authentication.service';
import { AppConfigValues } from '../../app-config/app-config.service'; import { BasicAlfrescoAuthService } from '../basic-auth/basic-alfresco-auth.service';
import { map, catchError, tap } from 'rxjs/operators'; import { Observable, of } from 'rxjs';
import { JwtHelperService } from './jwt-helper.service';
import { StorageService } from '../../common/services/storage.service';
import { BaseAuthenticationService } from './base-authentication.service'; import { BaseAuthenticationService } from './base-authentication.service';
import { AppConfigService } from '../../app-config';
import { CookieService, LogService } from '../../common';
import { HttpHeaders } from '@angular/common/http';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AuthenticationService extends BaseAuthenticationService { export class AuthenticationService extends BaseAuthenticationService {
private storageService = inject(StorageService);
readonly supportCodeFlow = false;
constructor() { constructor(appConfig: AppConfigService,
super(); cookie: CookieService,
this.alfrescoApi.alfrescoApiInitialized.subscribe(() => { logService: LogService,
this.alfrescoApi.getInstance().reply('logged-in', () => { private injector: Injector) {
this.onLogin.next(); super(appConfig, cookie, logService);
});
});
} }
/** private get oidcAuthenticationService(): OidcAuthenticationService {
* Checks if the user logged in. return this.injector.get(OidcAuthenticationService);
*
* @returns True if logged in, false otherwise
*/
isLoggedIn(): boolean {
if (!this.isOauth() && this.cookie.isEnabled() && !this.isRememberMeSet()) {
return false;
}
return this.alfrescoApi.getInstance().isLoggedIn();
} }
isLoggedInWith(provider: string): boolean { private get basicAlfrescoAuthService(): BasicAlfrescoAuthService {
if (provider === 'BPM') { return this.injector.get(BasicAlfrescoAuthService);
return this.isBpmLoggedIn();
} else if (provider === 'ECM') {
return this.isEcmLoggedIn();
} else {
return this.isLoggedIn();
}
} }
/**
* Does the provider support OAuth?
*
* @returns True if supported, false otherwise
*/
isOauth(): boolean {
return this.alfrescoApi.getInstance().isOauthConfiguration();
}
/**
* Logs the user in.
*
* @param username Username for the login
* @param password Password for the login
* @param rememberMe Stores the user's login details if true
* @returns Object with auth type ("ECM", "BPM" or "ALL") and auth ticket
*/
login(username: string, password: string, rememberMe: boolean = false): Observable<{ type: string; ticket: any }> {
return from(this.alfrescoApi.getInstance().login(username, password)).pipe(
map((response: any) => {
this.saveRememberMeCookie(rememberMe);
this.onLogin.next(response);
return {
type: this.appConfig.get(AppConfigValues.PROVIDERS),
ticket: response
};
}),
catchError((err) => this.handleError(err))
);
}
/**
* Logs the user in with SSO
*/
ssoImplicitLogin() {
this.alfrescoApi.getInstance().implicitLogin();
}
/**
* Logs the user out.
*
* @returns Response event called when logout is complete
*/
logout() {
return from(this.callApiLogout()).pipe(
tap((response) => {
this.onLogout.next(response);
return response;
}),
catchError((err) => this.handleError(err))
);
}
private callApiLogout(): Promise<any> {
if (this.alfrescoApi.getInstance()) {
return this.alfrescoApi.getInstance().logout();
}
return Promise.resolve();
}
/**
* Checks if the user is logged in on an ECM provider.
*
* @returns True if logged in, false otherwise
*/
isEcmLoggedIn(): boolean {
if (this.isECMProvider() || this.isALLProvider()) {
if (!this.isOauth() && this.cookie.isEnabled() && !this.isRememberMeSet()) {
return false;
}
return this.alfrescoApi.getInstance().isEcmLoggedIn();
}
return false;
}
/**
* Checks if the user is logged in on a BPM provider.
*
* @returns True if logged in, false otherwise
*/
isBpmLoggedIn(): boolean {
if (this.isBPMProvider() || this.isALLProvider()) {
if (!this.isOauth() && this.cookie.isEnabled() && !this.isRememberMeSet()) {
return false;
}
return this.alfrescoApi.getInstance().isBpmLoggedIn();
}
return false;
}
/**
* Gets the ECM username.
*
* @returns The ECM username
*/
getEcmUsername(): string {
return this.alfrescoApi.getInstance().getEcmUsername();
}
/**
* Gets the BPM username
*
* @returns The BPM username
*/
getBpmUsername(): string {
return this.alfrescoApi.getInstance().getBpmUsername();
}
isImplicitFlow(): boolean {
return !!this.appConfig.oauth2?.implicitFlow;
}
isAuthCodeFlow(): boolean {
return false;
}
/**
* Gets the auth token.
*
* @returns Auth token string
*/
getToken(): string { getToken(): string {
return this.storageService.getItem(JwtHelperService.USER_ACCESS_TOKEN); if (this.isOauth()) {
return this.oidcAuthenticationService.getToken();
} else {
return this.basicAlfrescoAuthService.getToken();
}
} }
reset() { } isLoggedIn(): boolean {
if (this.isOauth()) {
return this.oidcAuthenticationService.isLoggedIn();
} else {
return this.basicAlfrescoAuthService.isLoggedIn();
}
}
once(event: string): Observable<any> { logout(): Observable<any> {
const alfrescoApiEvent = event === 'token_received' ? 'token_issued' : event; if (this.isOauth()) {
return new Observable((subscriber) => { return this.oidcAuthenticationService.logout();
this.alfrescoApi.getInstance().oauth2Auth.once(alfrescoApiEvent, () => subscriber.next()); } else {
}); return of(this.basicAlfrescoAuthService.logout());
}
}
isEcmLoggedIn(): boolean {
if (this.isOauth()) {
return this.oidcAuthenticationService.isEcmLoggedIn();
} else {
return this.basicAlfrescoAuthService.isEcmLoggedIn();
}
}
isBpmLoggedIn(): boolean {
if (this.isOauth()) {
return this.oidcAuthenticationService.isBpmLoggedIn();
} else {
return this.basicAlfrescoAuthService.isBpmLoggedIn();
}
}
reset(): void {
if (this.isOauth()) {
return this.oidcAuthenticationService.reset();
} else {
return this.basicAlfrescoAuthService.reset();
}
}
getEcmUsername(): string {
if (this.isOauth()) {
return this.oidcAuthenticationService.getEcmUsername();
} else {
return this.basicAlfrescoAuthService.getEcmUsername();
}
}
getBpmUsername(): string {
if (this.isOauth()) {
return this.oidcAuthenticationService.getBpmUsername();
} else {
return this.basicAlfrescoAuthService.getBpmUsername();
}
}
getAuthHeaders(requestUrl: string, headers: HttpHeaders): HttpHeaders {
if (this.isOauth()) {
return this.oidcAuthenticationService.getAuthHeaders(requestUrl, headers);
} else {
return this.basicAlfrescoAuthService.getAuthHeaders(requestUrl, headers);
}
} }
} }

View File

@@ -1,6 +1,6 @@
/*! /*!
* @license * @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. * Copyright 2019 Alfresco Software, Ltd.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,61 +15,53 @@
* limitations under the License. * limitations under the License.
*/ */
import { PeopleApi, UserProfileApi } from '@alfresco/js-api';
import { HttpHeaders } from '@angular/common/http'; import { HttpHeaders } from '@angular/common/http';
import { RedirectionModel } from '../models/redirection.model'; import { RedirectionModel } from '../models/redirection.model';
import { Observable, Observer, ReplaySubject, throwError } from 'rxjs'; import { Observable, Observer, ReplaySubject, throwError } from 'rxjs';
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service'; import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { CookieService } from '../../common/services/cookie.service'; import { CookieService } from '../../common/services/cookie.service';
import { LogService } from '../../common/services/log.service'; import { LogService } from '../../common/services/log.service';
import { inject } from '@angular/core'; import { AuthenticationServiceInterface } from '../interfaces/authentication-service.interface';
import ee from 'event-emitter';
const REMEMBER_ME_COOKIE_KEY = 'ALFRESCO_REMEMBER_ME'; export abstract class BaseAuthenticationService implements AuthenticationServiceInterface, ee.Emitter {
const REMEMBER_ME_UNTIL = 1000 * 60 * 60 * 24 * 30;
export abstract class BaseAuthenticationService { on: ee.EmitterMethod;
protected alfrescoApi = inject(AlfrescoApiService); off: ee.EmitterMethod;
protected appConfig = inject(AppConfigService); once: ee.EmitterMethod;
protected cookie = inject(CookieService); emit: (type: string, ...args: any[]) => void;
private logService = inject(LogService);
protected bearerExcludedUrls: readonly string[] = ['resources/', 'assets/', 'auth/realms', 'idp/'];
protected redirectUrl: RedirectionModel = null; protected redirectUrl: RedirectionModel = null;
onError = new ReplaySubject<any>(1);
onLogin = new ReplaySubject<any>(1); onLogin = new ReplaySubject<any>(1);
onLogout = new ReplaySubject<any>(1); onLogout = new ReplaySubject<any>(1);
private _peopleApi: PeopleApi; constructor(
get peopleApi(): PeopleApi { protected appConfig: AppConfigService,
this._peopleApi = this._peopleApi ?? new PeopleApi(this.alfrescoApi.getInstance()); protected cookie: CookieService,
return this._peopleApi; private logService: LogService
) {
ee(this);
} }
private _profileApi: UserProfileApi; abstract getAuthHeaders(requestUrl: string, header: HttpHeaders): HttpHeaders;
get profileApi(): UserProfileApi {
this._profileApi = this._profileApi ?? new UserProfileApi(this.alfrescoApi.getInstance());
return this._profileApi;
}
abstract readonly supportCodeFlow: boolean;
abstract getToken(): string; abstract getToken(): string;
abstract isLoggedIn(): boolean;
abstract isLoggedInWith(provider: string): boolean;
abstract isOauth(): boolean;
abstract login(username: string, password: string, rememberMe?: boolean): Observable<{ type: string; ticket: any }>;
abstract ssoImplicitLogin(): void;
abstract logout(): Observable<any>;
abstract isEcmLoggedIn(): boolean;
abstract isBpmLoggedIn(): boolean;
abstract getEcmUsername(): string;
abstract getBpmUsername(): string;
abstract reset(): void;
abstract once(event: string): Observable<any>;
getBearerExcludedUrls(): readonly string[] { abstract isLoggedIn(): boolean;
return this.bearerExcludedUrls;
} abstract logout(): any;
abstract isEcmLoggedIn(): boolean;
abstract isBpmLoggedIn(): boolean;
abstract reset(): void;
abstract getEcmUsername(): string;
abstract getBpmUsername(): string;
/** /**
* Adds the auth token to an HTTP header using the 'bearer' scheme. * Adds the auth token to an HTTP header using the 'bearer' scheme.
@@ -77,14 +69,15 @@ export abstract class BaseAuthenticationService {
* @param headersArg Header that will receive the token * @param headersArg Header that will receive the token
* @returns The new header with the token added * @returns The new header with the token added
*/ */
addTokenToHeader(headersArg?: HttpHeaders): Observable<HttpHeaders> { addTokenToHeader(requestUrl: string, headersArg?: HttpHeaders): Observable<HttpHeaders> {
return new Observable((observer: Observer<any>) => { return new Observable((observer: Observer<any>) => {
let headers = headersArg; let headers = headersArg;
if (!headers) { if (!headers) {
headers = new HttpHeaders(); headers = new HttpHeaders();
} }
try { try {
const header = this.getAuthHeaders(headers);
const header = this.getAuthHeaders(requestUrl, headers);
observer.next(header); observer.next(header);
observer.complete(); observer.complete();
@@ -94,50 +87,9 @@ export abstract class BaseAuthenticationService {
}); });
} }
private getAuthHeaders(header: HttpHeaders): HttpHeaders {
const authType = this.appConfig.get<string>(AppConfigValues.AUTHTYPE, 'BASIC');
switch (authType) {
case 'OAUTH':
return this.addBearerToken(header);
case 'BASIC':
return this.addBasicAuth(header);
default:
return header;
}
}
private addBearerToken(header: HttpHeaders): HttpHeaders {
const token: string = this.getToken();
if (!token) {
return header;
}
return header.set('Authorization', 'bearer ' + token);
}
private addBasicAuth(header: HttpHeaders): HttpHeaders {
const ticket: string = this.getTicketEcmBase64();
if (!ticket) {
return header;
}
return header.set('Authorization', ticket);
}
isPublicUrl(): boolean {
return this.alfrescoApi.getInstance().isPublicUrl();
}
/**
* Does the provider support ECM?
*
* @returns True if supported, false otherwise
*/
isECMProvider(): boolean { isECMProvider(): boolean {
return this.alfrescoApi.getInstance().isEcmConfiguration(); const provider = <string>this.appConfig.get('providers');
return provider && provider.toUpperCase() === 'ECM';
} }
/** /**
@@ -146,7 +98,8 @@ export abstract class BaseAuthenticationService {
* @returns True if supported, false otherwise * @returns True if supported, false otherwise
*/ */
isBPMProvider(): boolean { isBPMProvider(): boolean {
return this.alfrescoApi.getInstance().isBpmConfiguration(); const provider = <string>this.appConfig.get('providers');
return provider && provider.toUpperCase() === 'BPM';
} }
/** /**
@@ -155,38 +108,13 @@ export abstract class BaseAuthenticationService {
* @returns True if both are supported, false otherwise * @returns True if both are supported, false otherwise
*/ */
isALLProvider(): boolean { isALLProvider(): boolean {
return this.alfrescoApi.getInstance().isEcmBpmConfiguration(); const provider = <string>this.appConfig.get('providers');
return provider && provider.toUpperCase() === 'ALL';
} }
/** isOauthConfiguration(): boolean {
* Gets the ECM ticket stored in the Storage. const authType = <string>this.appConfig.get('authType');
* return authType === 'OAUTH';
* @returns The ticket or `null` if none was found
*/
getTicketEcm(): string | null {
return this.alfrescoApi.getInstance()?.getTicketEcm();
}
/**
* Gets the BPM ticket stored in the Storage.
*
* @returns The ticket or `null` if none was found
*/
getTicketBpm(): string | null {
return this.alfrescoApi.getInstance()?.getTicketBpm();
}
/**
* Gets the BPM ticket from the Storage in Base 64 format.
*
* @returns The ticket or `null` if none was found
*/
getTicketEcmBase64(): string | null {
const ticket = this.alfrescoApi.getInstance()?.getTicketEcm();
if (ticket) {
return 'Basic ' + btoa(ticket);
}
return null;
} }
/** /**
@@ -196,63 +124,12 @@ export abstract class BaseAuthenticationService {
* @returns Object representing the error message * @returns Object representing the error message
*/ */
handleError(error: any): Observable<any> { handleError(error: any): Observable<any> {
this.onError.next(error || 'Server error')
this.logService.error('Error when logging in', error); this.logService.error('Error when logging in', error);
return throwError(error || 'Server error'); return throwError(error || 'Server error');
} }
/** isOauth(): boolean {
* Does kerberos enabled? return this.appConfig.get(AppConfigValues.AUTHTYPE) === 'OAUTH';
*
* @returns True if enabled, false otherwise
*/
isKerberosEnabled(): boolean {
return this.appConfig.get<boolean>(AppConfigValues.AUTH_WITH_CREDENTIALS, false);
}
/**
* Saves the "remember me" cookie as either a long-life cookie or a session cookie.
*
* @param rememberMe Enables a long-life cookie
*/
saveRememberMeCookie(rememberMe: boolean): void {
let expiration = null;
if (rememberMe) {
expiration = new Date();
const time = expiration.getTime();
const expireTime = time + REMEMBER_ME_UNTIL;
expiration.setTime(expireTime);
}
this.cookie.setItem(REMEMBER_ME_COOKIE_KEY, '1', expiration, null);
}
/**
* Checks whether the "remember me" cookie was set or not.
*
* @returns True if set, false otherwise
*/
isRememberMeSet(): boolean {
return this.cookie.getItem(REMEMBER_ME_COOKIE_KEY) !== null;
}
setRedirect(url?: RedirectionModel) {
this.redirectUrl = url;
}
/** Gets the URL to redirect to after login.
*
* @returns The redirect URL
*/
getRedirect(): string {
const provider = this.appConfig.get<string>(AppConfigValues.PROVIDERS);
return this.hasValidRedirection(provider) ? this.redirectUrl.url : null;
}
private hasValidRedirection(provider: string): boolean {
return this.redirectUrl && (this.redirectUrl.provider === provider || this.hasSelectedProviderAll(provider));
}
private hasSelectedProviderAll(provider: string): boolean {
return this.redirectUrl && (this.redirectUrl.provider === 'ALL' || provider === 'ALL');
} }
} }

View File

@@ -0,0 +1,176 @@
// /*!
// * @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 } from 'rxjs';
// import { catchError, tap } from 'rxjs/operators';
// import { AlfrescoApiService } from '../../services/alfresco-api.service';
// import { CookieService } from '../../common/services/cookie.service';
// import { LogService } from '../../common/services/log.service';
// import { AppConfigService } from '../../app-config/app-config.service';
// import { StorageService } from '../../common/services/storage.service';
// import { JwtHelperService } from './jwt-helper.service';
// import { BaseAuthenticationService } from './base-authentication.service';
// import { ContentAuth } from "../basic-auth/contentAuth";
//
// @Injectable({
// providedIn: 'root'
// })
// export class AuthenticationService extends BaseAuthenticationService {
// readonly supportCodeFlow = false;
//
// constructor(
// appConfig: AppConfigService,
// cookie: CookieService,
// logService: LogService,
// contentAuth: ContentAuth,
// private alfrescoApi: AlfrescoApiService,
// private storageService: StorageService
// ) {
// super(appConfig, cookie, contentAuth, logService);
// this.alfrescoApi.alfrescoApiInitialized.subscribe(() => {
// this.alfrescoApi.getInstance().reply('logged-in', () => {
// this.onLogin.next();
// });
// });
// }
//
// /**
// * Checks if the user logged in.
// *
// * @returns True if logged in, false otherwise
// */
// isLoggedIn(): boolean {
// if (!this.isOauth() && this.cookie.isEnabled() && !this.isRememberMeSet()) {
// return false;
// }
// return this.alfrescoApi.getInstance().isLoggedIn();
// }
//
// isLoggedInWith(provider: string): boolean {
// if (provider === 'BPM') {
// return this.isBpmLoggedIn();
// } else if (provider === 'ECM') {
// return this.isEcmLoggedIn();
// } else {
// return this.isLoggedIn();
// }
// }
//
// /**
// * Does the provider support OAuth?
// *
// * @returns True if supported, false otherwise
// */
// isOauth(): boolean {
// return this.alfrescoApi.getInstance().isOauthConfiguration();
// }
//
// /**
// * Logs the user in with SSO
// */
// ssoImplicitLogin() {
// this.alfrescoApi.getInstance().implicitLogin();
// }
//
// /**
// * Logs the user out.
// *
// * @returns Response event called when logout is complete
// */
// logout() {
// return from(this.callApiLogout()).pipe(
// tap((response) => {
// this.onLogout.next(response);
// return response;
// }),
// catchError((err) => this.handleError(err))
// );
// }
//
// private callApiLogout(): Promise<any> {
// if (this.alfrescoApi.getInstance()) {
// return this.alfrescoApi.getInstance().logout();
// }
// return Promise.resolve();
// }
//
// /**
// * Checks if the user is logged in on an ECM provider.
// *
// * @returns True if logged in, false otherwise
// */
// isEcmLoggedIn(): boolean {
// if (this.isECMProvider() || this.isALLProvider()) {
// if (!this.isOauth() && this.cookie.isEnabled() && !this.isRememberMeSet()) {
// return false;
// }
// return this.alfrescoApi.getInstance().isEcmLoggedIn();
// }
// return false;
// }
//
// /**
// * Checks if the user is logged in on a BPM provider.
// *
// * @returns True if logged in, false otherwise
// */
// isBpmLoggedIn(): boolean {
// if (this.isBPMProvider() || this.isALLProvider()) {
// if (!this.isOauth() && this.cookie.isEnabled() && !this.isRememberMeSet()) {
// return false;
// }
// return this.alfrescoApi.getInstance().isBpmLoggedIn();
// }
// return false;
// }
//
// /**
// * Gets the ECM username.
// *
// * @returns The ECM username
// */
// getEcmUsername(): string {
// return this.alfrescoApi.getInstance().getEcmUsername();
// }
//
// /**
// * Gets the BPM username
// *
// * @returns The BPM username
// */
// getBpmUsername(): string {
// return this.alfrescoApi.getInstance().getBpmUsername();
// }
//
// /**
// * Gets the auth token.
// *
// * @returns Auth token string
// */
// getToken(): string {
// return this.storageService.getItem(JwtHelperService.USER_ACCESS_TOKEN);
// }
//
// reset() { }
//
// once(event: string): Observable<any> {
// return new Observable((subscriber) => {
// this.alfrescoApi.getInstance().once(event, () => subscriber.next());
// });
// }
// }

View File

@@ -0,0 +1,167 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
import { EMPTY, Observable } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
import { OauthConfigModel } from '../models/oauth-config.model';
import { BaseAuthenticationService } from './base-authentication.service';
import { CookieService } from '../../common/services/cookie.service';
import { JwtHelperService } from './jwt-helper.service';
import { LogService } from '../../common/services/log.service';
import { AuthConfigService } from '../oidc/auth-config.service';
import { AuthService } from '../oidc/auth.service';
import { Minimatch } from "minimatch";
import { HttpHeaders } from "@angular/common/http";
@Injectable({
providedIn: 'root'
})
export class OidcAuthenticationService extends BaseAuthenticationService {
constructor(
appConfig: AppConfigService,
cookie: CookieService,
logService: LogService,
private jwtHelperService: JwtHelperService,
private authStorage: OAuthStorage,
private oauthService: OAuthService,
private readonly authConfig: AuthConfigService,
private readonly auth: AuthService,
) {
super(appConfig, cookie, logService);
}
isEcmLoggedIn(): boolean {
if (this.isECMProvider() || this.isALLProvider()) {
return this.isLoggedIn();
}
return false;
}
isBpmLoggedIn(): boolean {
if (this.isBPMProvider() || this.isALLProvider()) {
return this.isLoggedIn();
}
return false;
}
isLoggedIn(): boolean {
return this.oauthService.hasValidAccessToken() && this.oauthService.hasValidIdToken();
}
hasValidAccessToken(): boolean {
return this.oauthService.hasValidAccessToken();
}
hasValidIdToken(): boolean {
return this.oauthService.hasValidAccessToken();
}
isImplicitFlow() {
const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
return !!oauth2?.implicitFlow;
}
isAuthCodeFlow() {
const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
return !!oauth2?.codeFlow;
}
login(username: string, password: string): Observable<{ type: string; ticket: any }> {
return this.auth.baseAuthLogin(username, password).pipe(
map((response) => {
this.onLogin.next(response);
return {
type: this.appConfig.get(AppConfigValues.PROVIDERS),
ticket: response
};
}),
catchError((err) => this.handleError(err))
);
}
getEcmUsername(): string {
return this.jwtHelperService.getValueFromLocalToken<string>(JwtHelperService.USER_PREFERRED_USERNAME);
}
getBpmUsername(): string {
return this.jwtHelperService.getValueFromLocalToken<string>(JwtHelperService.USER_PREFERRED_USERNAME);
}
ssoImplicitLogin() {
this.oauthService.initLoginFlow();
}
ssoCodeFlowLogin() {
this.oauthService.initCodeFlow();
}
isRememberMeSet(): boolean {
return true;
}
logout() {
this.oauthService.logOut();
return EMPTY;
}
getToken(): string {
return this.authStorage.getItem(JwtHelperService.USER_ACCESS_TOKEN);
}
reset(): void {
const config = this.authConfig.loadAppConfig();
this.auth.updateIDPConfiguration(config);
const oauth2: OauthConfigModel = Object.assign({}, this.appConfig.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
if (config.oidc && oauth2.silentLogin) {
this.auth.login();
}
}
isPublicUrl(): boolean {
const oauth2 = this.appConfig.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null);
if (Array.isArray(oauth2.publicUrls)) {
return oauth2.publicUrls.length > 0 &&
oauth2.publicUrls.some((urlPattern: string) => {
const minimatch = new Minimatch(urlPattern);
return minimatch.match(window.location.href);
});
}
return false;
}
getAuthHeaders(_requestUrl: string, header: HttpHeaders): HttpHeaders {
return this.addBearerToken(header);
}
private addBearerToken(header: HttpHeaders): HttpHeaders {
const token: string = this.getToken();
if (!token) {
return header;
}
return header.set('Authorization', 'bearer ' + token);
}
}

View File

@@ -82,7 +82,7 @@ export class StorageService {
*/ */
removeItem(key: string) { removeItem(key: string) {
if (this.useLocalStorage) { if (this.useLocalStorage) {
localStorage.removeItem(this.prefix + key); localStorage.removeItem(`${this.prefix}` + key);
} else { } else {
delete this.memoryStore[this.prefix + key]; delete this.memoryStore[this.prefix + key];
} }

View File

@@ -25,7 +25,7 @@ export class ObjectUtils {
*/ */
static getValue(target: any, key: string): any { static getValue(target: any, key: string): any {
if (!target) { if (!target || !key) {
return undefined; return undefined;
} }

View File

@@ -37,7 +37,7 @@ export class LogoutDirective implements OnInit {
private renderer: Renderer2, private renderer: Renderer2,
private router: Router, private router: Router,
private appConfig: AppConfigService, private appConfig: AppConfigService,
private auth: AuthenticationService) { private authenticationService: AuthenticationService) {
} }
ngOnInit() { ngOnInit() {
@@ -58,14 +58,14 @@ export class LogoutDirective implements OnInit {
} }
logout() { logout() {
this.auth.logout().subscribe( this.authenticationService.logout().subscribe(
() => this.redirectToUri(), () => this.redirectToUri(),
() => this.redirectToUri() () => this.redirectToUri()
); );
} }
redirectToUri() { redirectToUri() {
if (this.enableRedirect && !this.auth.isOauth()) { if (this.enableRedirect && !this.authenticationService.isOauth()) {
const redirectRoute = this.getRedirectUri(); const redirectRoute = this.getRedirectUri();
this.router.navigate([redirectRoute]); this.router.navigate([redirectRoute]);
} }

View File

@@ -35,6 +35,8 @@ import {
import { DomSanitizer, SafeStyle } from '@angular/platform-browser'; import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { BasicAlfrescoAuthService } from '../../auth/basic-auth/basic-alfresco-auth.service';
import { OidcAuthenticationService } from "../../auth/services/oidc-authentication.service";
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
enum LoginSteps { enum LoginSteps {
@@ -130,6 +132,8 @@ export class LoginComponent implements OnInit, OnDestroy {
constructor( constructor(
private _fb: UntypedFormBuilder, private _fb: UntypedFormBuilder,
private authService: AuthenticationService, private authService: AuthenticationService,
private basicAlfrescoAuthService: BasicAlfrescoAuthService,
private oidcAuthenticationService: OidcAuthenticationService,
private translateService: TranslationService, private translateService: TranslationService,
private router: Router, private router: Router,
private appConfig: AppConfigService, private appConfig: AppConfigService,
@@ -163,7 +167,7 @@ export class LoginComponent implements OnInit, OnDestroy {
const url = params['redirectUrl']; const url = params['redirectUrl'];
const provider = this.appConfig.get<string>(AppConfigValues.PROVIDERS); const provider = this.appConfig.get<string>(AppConfigValues.PROVIDERS);
this.authService.setRedirect({ provider, url }); this.basicAlfrescoAuthService.setRedirect({ provider, url });
}); });
} }
@@ -186,14 +190,13 @@ export class LoginComponent implements OnInit, OnDestroy {
} }
redirectToImplicitLogin() { redirectToImplicitLogin() {
this.authService.ssoImplicitLogin(); this.oidcAuthenticationService.ssoImplicitLogin();
} }
/** /**
* Method called on submit form * Method called on submit form
* *
* @param values * @param values
* @param event
*/ */
onSubmit(values: any): void { onSubmit(values: any): void {
this.disableError(); this.disableError();
@@ -213,7 +216,7 @@ export class LoginComponent implements OnInit, OnDestroy {
if (this.authService.isLoggedIn()) { if (this.authService.isLoggedIn()) {
this.router.navigate([this.successRoute]); this.router.navigate([this.successRoute]);
} }
this.authService.ssoImplicitLogin(); this.oidcAuthenticationService.ssoImplicitLogin();
} }
/** /**
@@ -246,11 +249,10 @@ export class LoginComponent implements OnInit, OnDestroy {
} }
performLogin(values: { username: string; password: string }) { performLogin(values: { username: string; password: string }) {
this.authService this.basicAlfrescoAuthService.login(values.username, values.password, this.rememberMe)
.login(values.username, values.password, this.rememberMe)
.subscribe( .subscribe(
(token: any) => { async (token: any) => {
const redirectUrl = this.authService.getRedirect(); const redirectUrl = this.basicAlfrescoAuthService.getRedirect();
this.actualLoginStep = LoginSteps.Welcome; this.actualLoginStep = LoginSteps.Welcome;
this.userPreferences.setStoragePrefix(values.username); this.userPreferences.setStoragePrefix(values.username);
@@ -260,10 +262,10 @@ export class LoginComponent implements OnInit, OnDestroy {
); );
if (redirectUrl) { if (redirectUrl) {
this.authService.setRedirect(null); this.basicAlfrescoAuthService.setRedirect(null);
this.router.navigateByUrl(redirectUrl); await this.router.navigateByUrl(redirectUrl);
} else if (this.successRoute) { } else if (this.successRoute) {
this.router.navigate([this.successRoute]); await this.router.navigate([this.successRoute]);
} }
}, },
(err: any) => { (err: any) => {