Skip to content

Commit 427cd1a

Browse files
committed
Add centralized request service (#4758)
* create service * Improve service functionality * add timeout handling * Improve code quality * Fix issue that caused infinite loop * separate interceptor initialization * add interceptor to plugin start * fix double reload and add abort * Add function to disable requests externally * Add auth validation on server to disable requests * Update changelog * Add missing parameters verification * Add missing parameter validation * Add reload to interceptor * Change structure of code * Implement new service * Fix bug * Fix errors * Create unit tests * Fix bad reference to previous PR in changelog * Add missing previous change in changelog (cherry picked from commit 34c3fae)
1 parent 72ba21a commit 427cd1a

File tree

8 files changed

+201
-37
lines changed

8 files changed

+201
-37
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
All notable changes to the Wazuh app project will be documented in this file.
44

5+
## Wazuh v4.3.10 - Kibana 7.10.2, 7.16.x, 7.17.x - Revision 4311
6+
7+
### Added
8+
- Added a centralized service to handle the requests [#4758](https://github.com/wazuh/wazuh-kibana-app/pull/4758)
9+
510
## Wazuh v4.3.9 - Kibana 7.10.2, 7.16.x, 7.17.x - Revision 4310
611

712
### Added

public/plugin.ts

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import { setErrorOrchestrator } from './react-services/common-services';
2929
import { ErrorOrchestratorService } from './react-services/error-orchestrator/error-orchestrator.service';
3030
import { getThemeAssetURL, getAssetURL } from './utils/assets';
3131
import { WzRequest } from './react-services/wz-request';
32+
import { initializeInterceptor } from './services/request-handler';
33+
3234
const innerAngularName = 'app/wazuh';
3335
export class WazuhPlugin implements Plugin<WazuhSetup, WazuhStart, WazuhSetupPlugins, WazuhStartPlugins> {
3436
constructor(private readonly initializerContext: PluginInitializerContext) {}
@@ -141,6 +143,7 @@ export class WazuhPlugin implements Plugin<WazuhSetup, WazuhStart, WazuhSetupPlu
141143
setSavedObjects(core.savedObjects);
142144
setOverlays(core.overlays);
143145
setErrorOrchestrator(ErrorOrchestratorService);
146+
initializeInterceptor();
144147
return {};
145148
}
146149
}

public/react-services/generic-request.js

+11-15
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
* Find more information about this on the LICENSE file.
1111
*/
1212

13-
import axios from 'axios';
1413
import { AppState } from './app-state';
1514
import { WazuhConfig } from './wazuh-config';
1615
import { ApiCheck } from './wz-api-check';
1716
import { WzMisc } from '../factories/misc';
1817
import { OdfeUtils } from '../utils';
1918
import { getHttp, getDataPlugin } from '../kibana-services';
2019
import { PLUGIN_PLATFORM_REQUEST_HEADERS } from '../../common/constants';
20+
import { request } from '../services/request-handler';
2121

2222
export class GenericRequest {
2323
static async request(method, path, payload = null, returnError = false) {
@@ -33,9 +33,9 @@ export class GenericRequest {
3333
};
3434
const tmpUrl = getHttp().basePath.prepend(path);
3535

36-
try{
36+
try {
3737
requestHeaders.pattern = (await getDataPlugin().indexPatterns.get(AppState.getCurrentPattern())).title;
38-
}catch(error){};
38+
} catch (error) { };
3939

4040
try {
4141
requestHeaders.id = JSON.parse(AppState.getCurrentAPI()).id;
@@ -48,40 +48,36 @@ export class GenericRequest {
4848
if (method === 'GET') {
4949
options = {
5050
method: method,
51-
headers: requestHeaders,
52-
url: tmpUrl,
51+
path: path,
5352
timeout: timeout || 20000
5453
};
5554
}
5655
if (method === 'PUT') {
5756
options = {
5857
method: method,
59-
headers: requestHeaders,
6058
data: payload,
61-
url: tmpUrl,
59+
path: path,
6260
timeout: timeout || 20000
6361
};
6462
}
6563
if (method === 'POST') {
6664
options = {
6765
method: method,
68-
headers: requestHeaders,
6966
data: payload,
70-
url: tmpUrl,
67+
path: path,
7168
timeout: timeout || 20000
7269
};
7370
}
7471
if (method === 'DELETE') {
7572
options = {
7673
method: method,
77-
headers: requestHeaders,
7874
data: payload,
79-
url: tmpUrl,
75+
path: path,
8076
timeout: timeout || 20000
8177
};
8278
}
8379

84-
Object.assign(data, await axios(options));
80+
Object.assign(data, await request(options));
8581
if (!data) {
8682
throw new Error(
8783
`Error doing a request to ${tmpUrl}, method: ${method}.`
@@ -100,9 +96,9 @@ export class GenericRequest {
10096
const wzMisc = new WzMisc();
10197
wzMisc.setApiIsDown(true);
10298

103-
if (!window.location.hash.includes('#/settings') &&
104-
!window.location.hash.includes('#/health-check') &&
105-
!window.location.hash.includes('#/blank-screen')) {
99+
if (!window.location.hash.includes('#/settings') &&
100+
!window.location.hash.includes('#/health-check') &&
101+
!window.location.hash.includes('#/blank-screen')) {
106102
window.location.href = getHttp().basePath.prepend('/app/wazuh#/health-check');
107103
}
108104
}

public/react-services/wz-api-check.js

+7-10
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@
1010
* Find more information about this on the LICENSE file.
1111
*/
1212
import { WazuhConfig } from './wazuh-config';
13-
import axios from 'axios';
1413
import { AppState } from './app-state';
1514
import { WzMisc } from '../factories/misc';
1615
import { getHttp } from '../kibana-services';
17-
import { PLUGIN_PLATFORM_REQUEST_HEADERS } from '../../common/constants';
16+
import { request } from '../services/request-handler';
1817

1918
export class ApiCheck {
2019
static async checkStored(data, idChanged = false) {
@@ -30,8 +29,7 @@ export class ApiCheck {
3029
const url = getHttp().basePath.prepend('/api/check-stored-api');
3130
const options = {
3231
method: 'POST',
33-
headers: { ...PLUGIN_PLATFORM_REQUEST_HEADERS, 'content-type': 'application/json' },
34-
url: url,
32+
path: '/api/check-stored-api',
3533
data: payload,
3634
timeout: timeout || 20000
3735
};
@@ -40,7 +38,7 @@ export class ApiCheck {
4038
AppState.setPatternSelector(configuration['ip.selector']);
4139
}
4240

43-
const response = await axios(options);
41+
const response = await request(options);
4442

4543
if (response.error) {
4644
return Promise.reject(response);
@@ -65,21 +63,20 @@ export class ApiCheck {
6563
* Check the status of an API entry
6664
* @param {String} apiObject
6765
*/
68-
static async checkApi(apiEntry, forceRefresh=false) {
66+
static async checkApi(apiEntry, forceRefresh = false) {
6967
try {
7068
const wazuhConfig = new WazuhConfig();
7169
const { timeout } = wazuhConfig.getConfig();
7270
const url = getHttp().basePath.prepend('/api/check-api');
7371

7472
const options = {
7573
method: 'POST',
76-
headers: { ...PLUGIN_PLATFORM_REQUEST_HEADERS, 'content-type': 'application/json' },
77-
url: url,
78-
data: {...apiEntry, forceRefresh},
74+
path: '/api/check-api',
75+
data: { ...apiEntry, forceRefresh },
7976
timeout: timeout || 20000
8077
};
8178

82-
const response = await axios(options);
79+
const response = await request(options);
8380

8481
if (response.error) {
8582
return Promise.reject(response);

public/react-services/wz-request.ts

+10-12
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
*
1010
* Find more information about this on the LICENSE file.
1111
*/
12-
import axios from 'axios';
1312
import { AppState } from './app-state';
1413
import { ApiCheck } from './wz-api-check';
1514
import { WzAuthentication } from './wz-authentication';
@@ -18,7 +17,7 @@ import { WazuhConfig } from './wazuh-config';
1817
import { OdfeUtils } from '../utils';
1918
import IApiResponse from './interfaces/api-response.interface';
2019
import { getHttp } from '../kibana-services';
21-
import { PLUGIN_PLATFORM_REQUEST_HEADERS } from '../../common/constants';
20+
import { request } from '../services/request-handler';
2221
export class WzRequest {
2322
static wazuhConfig: any;
2423

@@ -50,13 +49,12 @@ export class WzRequest {
5049
const url = getHttp().basePath.prepend(path);
5150
const options = {
5251
method: method,
53-
headers: { ...PLUGIN_PLATFORM_REQUEST_HEADERS, 'content-type': 'application/json' },
54-
url: url,
52+
path: path,
5553
data: payload,
5654
timeout: timeout,
5755
};
5856

59-
const data = await axios(options);
57+
const data = await request(options);
6058

6159
if (data['error']) {
6260
throw new Error(data['error']);
@@ -66,7 +64,7 @@ export class WzRequest {
6664
} catch (error) {
6765
OdfeUtils.checkOdfeSessionExpired(error);
6866
//if the requests fails, we need to check if the API is down
69-
if(checkCurrentApiIsUp){
67+
if (checkCurrentApiIsUp) {
7068
const currentApi = JSON.parse(AppState.getCurrentAPI() || '{}');
7169
if (currentApi && currentApi.id) {
7270
try {
@@ -100,7 +98,7 @@ export class WzRequest {
10098
}
10199
return errorMessage
102100
? Promise.reject(this.returnErrorInstance(error, errorMessage))
103-
: Promise.reject(this.returnErrorInstance(error,'Server did not respond'));
101+
: Promise.reject(this.returnErrorInstance(error, 'Server did not respond'));
104102
}
105103
}
106104

@@ -111,9 +109,9 @@ export class WzRequest {
111109
* @param {Object} body Request body
112110
*/
113111
static async apiReq(
114-
method,
115-
path,
116-
body,
112+
method,
113+
path,
114+
body,
117115
options: { checkCurrentApiIsUp?: boolean } = { checkCurrentApiIsUp: true }
118116
): Promise<IApiResponse<any>> {
119117
try {
@@ -170,8 +168,8 @@ export class WzRequest {
170168
* @param message
171169
* @returns error
172170
*/
173-
static returnErrorInstance(error, message){
174-
if(!error || typeof error === 'string'){
171+
static returnErrorInstance(error, message) {
172+
if (!error || typeof error === 'string') {
175173
return new Error(message || error);
176174
}
177175
error.message = message

public/services/request-handler.js

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { getCore } from '../kibana-services';
2+
3+
let allow = true;
4+
let aborts = [];
5+
let currentid = 0;
6+
7+
const removeController = (id) => {
8+
const index = aborts.findIndex(object => {
9+
return object.id === id;
10+
});
11+
if (!id) {
12+
return;
13+
}
14+
aborts.splice(index);
15+
return;
16+
}
17+
18+
export const disableRequests = () => {
19+
allow = false;
20+
aborts.forEach(item => {
21+
item.controller.abort();
22+
})
23+
return;
24+
}
25+
26+
export const initializeInterceptor = () => {
27+
const core = getCore();
28+
core.http.intercept({
29+
responseError: (httpErrorResponse, controller) => {
30+
if (
31+
httpErrorResponse.response?.status === 401
32+
) {
33+
disableRequests();
34+
setTimeout(() => window.location.reload(), 1000);
35+
}
36+
},
37+
});
38+
}
39+
40+
export const request = async (info = '') => {
41+
if (!allow) {
42+
return Promise.reject('Requests are disabled');
43+
}
44+
45+
46+
if (!info.method | !info.path) {
47+
return Promise.reject("Missing parameters")
48+
}
49+
50+
let { method, path, headers, data, timeout } = info;
51+
const core = getCore();
52+
const url = path.split('?')[0]
53+
54+
const query = Object.fromEntries([... new URLSearchParams(path.split('?')[1])])
55+
const abort = new AbortController();
56+
let options = {
57+
method: method,
58+
headers: headers,
59+
query: query,
60+
signal: abort.signal,
61+
id: currentid
62+
}
63+
currentid++;
64+
65+
if (method !== 'GET') {
66+
options = { ...options, body: JSON.stringify(data) }
67+
}
68+
69+
if (allow) {
70+
try {
71+
aborts.push({ id: options.id, controller: abort })
72+
if (timeout && timeout !== 0) {
73+
const id = setTimeout(() => abort.abort(), timeout);
74+
const requestData = await core.http.fetch(url, options);
75+
clearTimeout(id);
76+
removeController(options.id);
77+
return Promise.resolve({ data: requestData, timeout: timeout });
78+
}
79+
else {
80+
const requestData = await core.http.fetch(url, options);
81+
removeController(options.id);
82+
return Promise.resolve({ data: requestData });
83+
}
84+
}
85+
catch (e) {
86+
return Promise.reject(e);
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)