mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[MNT-24614] Fixed APS basic auth login issue with ADF (#10364)
* [MNT-24614] Fixed APS basic auth login issue with ADF * [MNT-24614] Addressed code review findings - Using includes api, and removed unneeded functions. Added missing return type to functions * [MNT-24614] Added unit tests * [MNT-24614] Added unit tests * [MNT-24614] Fixed casing of unit test titles
This commit is contained in:
@@ -387,4 +387,36 @@ describe('AdfHttpClient', () => {
|
||||
|
||||
req.flush(null, { status: 200, statusText: 'Ok' });
|
||||
});
|
||||
|
||||
it('should set X-CSRF-TOKEN header if CSRF is enabled', () => {
|
||||
const options: RequestOptions = {
|
||||
path: '',
|
||||
httpMethod: 'GET'
|
||||
};
|
||||
angularHttpClient.disableCsrf = false;
|
||||
|
||||
angularHttpClient.request('http://example.com', options, securityOptions, emitters).catch((error) => fail(error));
|
||||
|
||||
const req = controller.expectOne('http://example.com');
|
||||
|
||||
expect(req.request.headers.get('X-CSRF-TOKEN')).toBeDefined();
|
||||
|
||||
req.flush(null, { status: 200, statusText: 'Ok' });
|
||||
});
|
||||
|
||||
it('should not set X-CSRF-TOKEN header if CSRF is disabled', () => {
|
||||
const options: RequestOptions = {
|
||||
path: '',
|
||||
httpMethod: 'GET'
|
||||
};
|
||||
angularHttpClient.disableCsrf = true;
|
||||
|
||||
angularHttpClient.request('http://example.com', options, securityOptions, emitters).catch((error) => fail(error));
|
||||
|
||||
const req = controller.expectOne('http://example.com');
|
||||
|
||||
expect(req.request.headers.get('X-CSRF-TOKEN')).toBeNull();
|
||||
|
||||
req.flush(null, { status: 200, statusText: 'Ok' });
|
||||
});
|
||||
});
|
||||
|
@@ -17,15 +17,7 @@
|
||||
|
||||
import { SHOULD_ADD_AUTH_TOKEN } from '@alfresco/adf-core/auth';
|
||||
import { Emitters as JsApiEmitters, HttpClient as JsApiHttpClient } from '@alfresco/js-api';
|
||||
import {
|
||||
HttpClient,
|
||||
HttpContext,
|
||||
HttpErrorResponse,
|
||||
HttpEvent,
|
||||
HttpHeaders,
|
||||
HttpParams,
|
||||
HttpResponse
|
||||
} from '@angular/common/http';
|
||||
import { HttpClient, HttpContext, HttpErrorResponse, HttpEvent, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, of, Subject, throwError } from 'rxjs';
|
||||
import { catchError, map, takeUntil } from 'rxjs/operators';
|
||||
@@ -52,8 +44,7 @@ export interface Emitters {
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
|
||||
|
||||
export class AdfHttpClient implements ee.Emitter, JsApiHttpClient {
|
||||
on: ee.EmitterMethod;
|
||||
off: ee.EmitterMethod;
|
||||
once: ee.EmitterMethod;
|
||||
@@ -107,47 +98,43 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
|
||||
const params = getQueryParamsWithCustomEncoder(options.queryParams, new AlfrescoApiParamEncoder());
|
||||
const responseType = AdfHttpClient.getResponseType(options);
|
||||
const context = new HttpContext().set(SHOULD_ADD_AUTH_TOKEN, true);
|
||||
const security: SecurityOptions = {...this.defaultSecurityOptions, ...sc};
|
||||
const security: SecurityOptions = { ...this.defaultSecurityOptions, ...sc };
|
||||
const headers = this.getHeaders(options);
|
||||
if (!emitters) {
|
||||
emitters = this.getEventEmitters();
|
||||
}
|
||||
|
||||
const request = this.httpClient.request(
|
||||
options.httpMethod,
|
||||
url,
|
||||
{
|
||||
context,
|
||||
...(body && {body}),
|
||||
...(responseType && {responseType}),
|
||||
...security,
|
||||
...(params && {params}),
|
||||
headers,
|
||||
observe: 'events',
|
||||
reportProgress: true
|
||||
}
|
||||
);
|
||||
const request = this.httpClient.request(options.httpMethod, url, {
|
||||
context,
|
||||
...(body && { body }),
|
||||
...(responseType && { responseType }),
|
||||
...security,
|
||||
...(params && { params }),
|
||||
headers,
|
||||
observe: 'events',
|
||||
reportProgress: true
|
||||
});
|
||||
|
||||
return this.requestWithLegacyEventEmitters<T>(request, emitters, options.returnType);
|
||||
}
|
||||
|
||||
post<T = any>(url: string, options?: RequestOptions, sc?: SecurityOptions, emitters?: JsApiEmitters): Promise<T> {
|
||||
return this.request<T>(url, {...options, httpMethod: 'POST'}, sc, emitters);
|
||||
return this.request<T>(url, { ...options, httpMethod: 'POST' }, sc, emitters);
|
||||
}
|
||||
|
||||
put<T = any>(url: string, options?: RequestOptions, sc?: SecurityOptions, emitters?: JsApiEmitters): Promise<T> {
|
||||
return this.request<T>(url, {...options, httpMethod: 'PUT'}, sc, emitters);
|
||||
return this.request<T>(url, { ...options, httpMethod: 'PUT' }, sc, emitters);
|
||||
}
|
||||
|
||||
get<T = any>(url: string, options?: RequestOptions, sc?: SecurityOptions, emitters?: JsApiEmitters): Promise<T> {
|
||||
return this.request<T>(url, {...options, httpMethod: 'GET'}, sc, emitters);
|
||||
return this.request<T>(url, { ...options, httpMethod: 'GET' }, sc, emitters);
|
||||
}
|
||||
|
||||
delete<T = void>(url: string, options?: RequestOptions, sc?: SecurityOptions, emitters?: JsApiEmitters): Promise<T> {
|
||||
return this.request<T>(url, {...options, httpMethod: 'DELETE'}, sc, emitters);
|
||||
return this.request<T>(url, { ...options, httpMethod: 'DELETE' }, sc, emitters);
|
||||
}
|
||||
|
||||
private addPromiseListeners<T = any>(promise: Promise<T>, eventEmitter: any) {
|
||||
private addPromiseListeners<T = any>(promise: Promise<T>, eventEmitter: any) {
|
||||
const eventPromise = Object.assign(promise, {
|
||||
on() {
|
||||
// eslint-disable-next-line prefer-spread, prefer-rest-params
|
||||
@@ -189,58 +176,59 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
|
||||
}
|
||||
|
||||
private requestWithLegacyEventEmitters<T = any>(request$: Observable<HttpEvent<T>>, emitters: JsApiEmitters, returnType: any): Promise<T> {
|
||||
|
||||
const abort$ = new Subject<void>();
|
||||
const {eventEmitter, apiClientEmitter} = emitters;
|
||||
const { eventEmitter, apiClientEmitter } = emitters;
|
||||
|
||||
const promise = request$.pipe(
|
||||
map((res) => {
|
||||
if (isHttpUploadProgressEvent(res)) {
|
||||
const percent = Math.round((res.loaded / res.total) * 100);
|
||||
eventEmitter.emit('progress', {loaded: res.loaded, total: res.total, percent});
|
||||
}
|
||||
const promise = request$
|
||||
.pipe(
|
||||
map((res) => {
|
||||
if (isHttpUploadProgressEvent(res)) {
|
||||
const percent = Math.round((res.loaded / res.total) * 100);
|
||||
eventEmitter.emit('progress', { loaded: res.loaded, total: res.total, percent });
|
||||
}
|
||||
|
||||
if (isHttpResponseEvent(res)) {
|
||||
eventEmitter.emit('success', res.body);
|
||||
return AdfHttpClient.deserialize(res, returnType);
|
||||
}
|
||||
}),
|
||||
catchError((err: HttpErrorResponse): Observable<AlfrescoApiResponseError> => {
|
||||
if (isHttpResponseEvent(res)) {
|
||||
eventEmitter.emit('success', res.body);
|
||||
return AdfHttpClient.deserialize(res, returnType);
|
||||
}
|
||||
}),
|
||||
catchError((err: HttpErrorResponse): Observable<AlfrescoApiResponseError> => {
|
||||
// since we can't always determinate ahead of time if the response is going to be xml or plain text response
|
||||
// we need to handle false positive cases here.
|
||||
|
||||
// since we can't always determinate ahead of time if the response is going to be xml or plain text response
|
||||
// we need to handle false positive cases here.
|
||||
if (err.status === 200) {
|
||||
eventEmitter.emit('success', err.error.text);
|
||||
return of(err.error.text);
|
||||
}
|
||||
|
||||
if (err.status === 200) {
|
||||
eventEmitter.emit('success', err.error.text);
|
||||
return of(err.error.text);
|
||||
}
|
||||
eventEmitter.emit('error', err);
|
||||
apiClientEmitter.emit('error', { ...err, response: { req: err } });
|
||||
|
||||
eventEmitter.emit('error', err);
|
||||
apiClientEmitter.emit('error', { ...err, response: { req: err } });
|
||||
if (err.status === 401) {
|
||||
eventEmitter.emit('unauthorized');
|
||||
apiClientEmitter.emit('unauthorized');
|
||||
}
|
||||
|
||||
if (err.status === 401) {
|
||||
eventEmitter.emit('unauthorized');
|
||||
apiClientEmitter.emit('unauthorized');
|
||||
}
|
||||
// for backwards compatibility we need to convert it to error class as the HttpErrorResponse only implements Error interface, not extending it,
|
||||
// and we need to be able to correctly pass instanceof Error conditions used inside repository
|
||||
// we also need to pass error as Stringify string as we are detecting statusCodes using JSON.parse(error.message) in some places
|
||||
const msg = typeof err.error === 'string' ? err.error : JSON.stringify(err.error);
|
||||
|
||||
// for backwards compatibility we need to convert it to error class as the HttpErrorResponse only implements Error interface, not extending it,
|
||||
// and we need to be able to correctly pass instanceof Error conditions used inside repository
|
||||
// we also need to pass error as Stringify string as we are detecting statusCodes using JSON.parse(error.message) in some places
|
||||
const msg = typeof err.error === 'string' ? err.error : JSON.stringify(err.error);
|
||||
// for backwards compatibility to handle cases in code where we try read response.error.response.body;
|
||||
|
||||
// for backwards compatibility to handle cases in code where we try read response.error.response.body;
|
||||
const error = {
|
||||
...err,
|
||||
body: err.error
|
||||
};
|
||||
|
||||
const error = {
|
||||
...err, body: err.error
|
||||
};
|
||||
const alfrescoApiError = new AlfrescoApiResponseError(msg, err.status, error);
|
||||
return throwError(alfrescoApiError);
|
||||
}),
|
||||
takeUntil(abort$)
|
||||
)
|
||||
.toPromise();
|
||||
|
||||
const alfrescoApiError = new AlfrescoApiResponseError(msg, err.status, error);
|
||||
return throwError(alfrescoApiError);
|
||||
}),
|
||||
takeUntil(abort$)
|
||||
).toPromise();
|
||||
|
||||
(promise as any).abort = function() {
|
||||
(promise as any).abort = function () {
|
||||
eventEmitter.emit('abort');
|
||||
abort$.next();
|
||||
abort$.complete();
|
||||
@@ -261,7 +249,7 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
|
||||
}
|
||||
|
||||
if (isFormUrlEncoded) {
|
||||
return new HttpParams({fromObject: removeNilValues(options.formParams)});
|
||||
return new HttpParams({ fromObject: removeNilValues(options.formParams) });
|
||||
}
|
||||
|
||||
return body;
|
||||
@@ -273,8 +261,8 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
|
||||
|
||||
const optionsHeaders = {
|
||||
...options.headerParams,
|
||||
...(accept && {Accept: accept}),
|
||||
...((contentType) && {'Content-Type': contentType})
|
||||
...(accept && { Accept: accept }),
|
||||
...(contentType && { 'Content-Type': contentType })
|
||||
};
|
||||
|
||||
if (!this.disableCsrf) {
|
||||
@@ -319,7 +307,6 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
|
||||
return Boolean(contentType?.match(/^application\/json(;.*)?$/i));
|
||||
}
|
||||
|
||||
|
||||
private setCsrfToken(optionsHeaders: any) {
|
||||
const token = this.createCSRFToken();
|
||||
optionsHeaders['X-CSRF-TOKEN'] = token;
|
||||
@@ -332,12 +319,16 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
|
||||
}
|
||||
|
||||
private createCSRFToken(a?: any): string {
|
||||
const randomValue = window.crypto.getRandomValues(new Uint32Array(1))[0];
|
||||
const randomValue = AdfHttpClient.getSecureRandomValue();
|
||||
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 getSecureRandomValue(): number {
|
||||
const max = Math.pow(2, 32);
|
||||
return window.crypto.getRandomValues(new Uint32Array(1))[0] / max;
|
||||
}
|
||||
|
||||
private static getResponseType(options: RequestOptions): 'blob' | 'json' | 'text' {
|
||||
const isBlobType = options.returnType?.toString().toLowerCase() === 'blob' || options.responseType?.toString().toLowerCase() === 'blob';
|
||||
|
||||
if (isBlobType) {
|
||||
@@ -359,7 +350,6 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
|
||||
* @returns deserialized object
|
||||
*/
|
||||
private static deserialize<T>(response: HttpResponse<T>, returnType?: Constructor<unknown> | 'blob'): any {
|
||||
|
||||
if (response === null) {
|
||||
return null;
|
||||
}
|
||||
@@ -390,9 +380,7 @@ export class AdfHttpClient implements ee.Emitter,JsApiHttpClient {
|
||||
return new returnType(body);
|
||||
}
|
||||
|
||||
|
||||
private static deserializeBlobResponse(response: HttpResponse<Blob>) {
|
||||
return new Blob([response.body], {type: response.headers.get('Content-Type')});
|
||||
return new Blob([response.body], { type: response.headers.get('Content-Type') });
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user