Skip to content

Commit

Permalink
[Backport 4.4-2.3-wzd] Add centralized request service (#4854)
Browse files Browse the repository at this point in the history
* Add centralized request service (#4831)

* create request handler

* Add interceptor initialization to app mount

* Implement request handling service

* Add request disabling to server

* Modify changelog

* Use core plugin as parameter

* Move services to public

* Remove service from backend

* Modify imports of the service

* Add validation for unauthorized requests

* Improve code quality

* Remove duplicated logic

* Add reload on unauthorized requests

* Change when check-wazuh is executed

* Remove unnecessary verifications

* Improve code quality

* fix: condition on request service and replace http status code by constant

* fix: remove unused import

* fix: default value of parameter in request service

Co-authored-by: Federico Rodriguez <[email protected]>
Co-authored-by: Antonio David Gutiérrez <[email protected]>
(cherry picked from commit 99fbab1)

* Added unregister and request interceptor

Co-authored-by: Nico Guevara <[email protected]>
Co-authored-by: Álex <[email protected]>
Co-authored-by: Federico Rodriguez <[email protected]>
  • Loading branch information
4 people authored Nov 17, 2022
1 parent 2f2960a commit a4489a4
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 118 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ All notable changes to the Wazuh app project will be documented in this file.
- Added a centralized service to handle the requests [#4831](https:/wazuh/wazuh-kibana-app/pull/4831)
- Added data-test-subj create policy [#4873](https:/wazuh/wazuh-kibana-app/pull/4873)


### Changed

- Changed the HTTP verb from `GET` to `POST` in the requests to login to the Wazuh API [#4103](https:/wazuh/wazuh-kibana-app/pull/4103)
Expand Down
150 changes: 77 additions & 73 deletions public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ import { AppState } from './react-services/app-state';
import { setErrorOrchestrator } from './react-services/common-services';
import { ErrorOrchestratorService } from './react-services/error-orchestrator/error-orchestrator.service';
import { getThemeAssetURL, getAssetURL } from './utils/assets';
import { WzRequest } from './react-services/wz-request';
import store from './redux/store';
import { updateAppConfig } from './redux/actions/appConfigActions';
import { initializeInterceptor, unregisterInterceptor } from './services/request-handler';

const SIDEBAR_LOGO = 'customization.logo.sidebar';
const innerAngularName = 'app/wazuh';

export class WazuhPlugin implements Plugin<WazuhSetup, WazuhStart, WazuhSetupPlugins, WazuhStartPlugins> {
constructor(private readonly initializerContext: PluginInitializerContext) {}
constructor(private readonly initializerContext: PluginInitializerContext) { }
public initializeInnerAngular?: () => void;
private innerAngularInitialized: boolean = false;
private stateUpdater = new BehaviorSubject<AppUpdater>(() => ({}));
Expand All @@ -45,91 +45,95 @@ export class WazuhPlugin implements Plugin<WazuhSetup, WazuhStart, WazuhSetupPlu
const UI_THEME = core.uiSettings.get('theme:darkMode') ? 'dark' : 'light';

// Get custom logos configuration to start up the app with the correct logos
let logosInitialState={};
try{
let logosInitialState = {};
try {
logosInitialState = await core.http.get(`/api/logos`);
}
catch(error){
catch (error) {
console.error('plugin.ts: Error getting logos configuration', error);
}

core.application.register({
id: `wazuh`,
title: 'Wazuh',
icon: core.http.basePath.prepend(
logosInitialState?.logos?.[SIDEBAR_LOGO] ?
getAssetURL(logosInitialState?.logos?.[SIDEBAR_LOGO]) :
getThemeAssetURL('icon.svg', UI_THEME)),
mount: async (params: AppMountParameters) => {
try {
if (!this.initializeInnerAngular) {
throw Error('Wazuh plugin method initializeInnerAngular is undefined');
}
//Check if user has wazuh disabled and avoid registering the application if not
let response = { isWazuhDisabled: 1 };
try {
response = await core.http.get('/api/check-wazuh');
}
catch (error) {
console.error('plugin.ts: Error checking if Wazuh is enabled', error);
}

// Update redux app state logos with the custom logos
if (logosInitialState?.logos) {
store.dispatch(updateAppConfig(logosInitialState.logos));
}
// hide the telemetry banner.
// Set the flag in the telemetry saved object as the notice was seen and dismissed
this.hideTelemetryBanner && await this.hideTelemetryBanner();
setScopedHistory(params.history);
// Load application bundle
const { renderApp } = await import('./application');
// Get start services as specified in kibana.json
const [coreStart, depsStart] = await core.getStartServices();
setErrorOrchestrator(ErrorOrchestratorService);
setHttp(core.http);
setCookies(new Cookies());
if(!AppState.checkCookies() || params.history.parentHistory.action === 'PUSH') {
window.location.reload();
}
await this.initializeInnerAngular();
//Check is user has Wazuh disabled
const response = await WzRequest.genericReq(
'GET',
`/api/check-wazuh`,
)
if (!response.isWazuhDisabled) {
core.application.register({
id: `wazuh`,
title: 'Wazuh',
icon: core.http.basePath.prepend(
logosInitialState?.logos?.[SIDEBAR_LOGO] ?
getAssetURL(logosInitialState?.logos?.[SIDEBAR_LOGO]) :
getThemeAssetURL('icon.svg', UI_THEME)),
mount: async (params: AppMountParameters) => {
try {
initializeInterceptor(core);
if (!this.initializeInnerAngular) {
throw Error('Wazuh plugin method initializeInnerAngular is undefined');
}

params.element.classList.add('dscAppWrapper', 'wz-app');
const unmount = await renderApp(innerAngularName, params.element);
//Update if user has Wazuh disabled
this.stateUpdater.next(() => {
if (response.data.isWazuhDisabled) {
unmount();
// Update redux app state logos with the custom logos
if (logosInitialState?.logos) {
store.dispatch(updateAppConfig(logosInitialState.logos));
}
// hide the telemetry banner.
// Set the flag in the telemetry saved object as the notice was seen and dismissed
this.hideTelemetryBanner && await this.hideTelemetryBanner();
setScopedHistory(params.history);
// Load application bundle
const { renderApp } = await import('./application');
// Get start services as specified in kibana.json
const [coreStart, depsStart] = await core.getStartServices();
setErrorOrchestrator(ErrorOrchestratorService);
setHttp(core.http);
setCookies(new Cookies());
if (!AppState.checkCookies() || params.history.parentHistory.action === 'PUSH') {
window.location.reload();
}
return {
status: response.data.isWazuhDisabled,
category: {
id: 'wazuh',
label: 'Wazuh',
order: 0,
euiIconType: core.http.basePath.prepend(logosInitialState?.logos?.[SIDEBAR_LOGO] ? getAssetURL(logosInitialState?.logos?.[SIDEBAR_LOGO]) : getThemeAssetURL('icon.svg', UI_THEME)),
}}
})
return () => {
unmount();
};
}catch(error){
console.debug(error);
}
},
category: {
id: 'wazuh',
label: 'Wazuh',
order: 0,
euiIconType: core.http.basePath.prepend(logosInitialState?.logos?.[SIDEBAR_LOGO] ? getAssetURL(logosInitialState?.logos?.[SIDEBAR_LOGO]) : getThemeAssetURL('icon.svg', UI_THEME)),
},
updater$: this.stateUpdater
});
await this.initializeInnerAngular();
params.element.classList.add('dscAppWrapper', 'wz-app');
const unmount = await renderApp(innerAngularName, params.element);
this.stateUpdater.next(() => {
return {
status: response.isWazuhDisabled,
category: {
id: 'wazuh',
label: 'Wazuh',
order: 0,
euiIconType: core.http.basePath.prepend(logosInitialState?.logos?.[SIDEBAR_LOGO] ? getAssetURL(logosInitialState?.logos?.[SIDEBAR_LOGO]) : getThemeAssetURL('icon.svg', UI_THEME)),
}
}
})
return () => {
unmount();
unregisterInterceptor();
};
} catch (error) {
console.debug(error);
}
},
category: {
id: 'wazuh',
label: 'Wazuh',
order: 0,
euiIconType: core.http.basePath.prepend(logosInitialState?.logos?.[SIDEBAR_LOGO] ? getAssetURL(logosInitialState?.logos?.[SIDEBAR_LOGO]) : getThemeAssetURL('icon.svg', UI_THEME)),
},
updater$: this.stateUpdater
});
}
return {};
}
public start(core: CoreStart, plugins: AppPluginStartDependencies): WazuhStart {
// hide security alert
if(plugins.securityOss) {
if (plugins.securityOss) {
plugins.securityOss.insecureCluster.hideAlert(true);
};
if(plugins?.telemetry?.telemetryNotifications?.setOptedInNoticeSeen) {
if (plugins?.telemetry?.telemetryNotifications?.setOptedInNoticeSeen) {
// assign to a method to hide the telemetry banner used when the app is mounted
this.hideTelemetryBanner = () => plugins.telemetry.telemetryNotifications.setOptedInNoticeSeen();
};
Expand Down
6 changes: 2 additions & 4 deletions public/react-services/generic-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@
* Find more information about this on the LICENSE file.
*/

import axios from 'axios';
import { AppState } from './app-state';
import { WazuhConfig } from './wazuh-config';
import { ApiCheck } from './wz-api-check';
import { WzMisc } from '../factories/misc';
import { OdfeUtils } from '../utils';
import { getHttp, getDataPlugin } from '../kibana-services';
import { PLUGIN_PLATFORM_REQUEST_HEADERS } from '../../common/constants';
import { request } from '../services/request-handler';

export class GenericRequest {
static async request(method, path, payload = null, returnError = false) {
Expand Down Expand Up @@ -81,7 +80,7 @@ export class GenericRequest {
};
}

Object.assign(data, await axios(options));
Object.assign(data, await request(options));
if (!data) {
throw new Error(
`Error doing a request to ${tmpUrl}, method: ${method}.`
Expand All @@ -90,7 +89,6 @@ export class GenericRequest {

return data;
} catch (err) {
OdfeUtils.checkOdfeSessionExpired(err);
//if the requests fails, we need to check if the API is down
const currentApi = JSON.parse(AppState.getCurrentAPI() || '{}');
if (currentApi && currentApi.id) {
Expand Down
6 changes: 3 additions & 3 deletions public/react-services/wz-api-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
* Find more information about this on the LICENSE file.
*/
import { WazuhConfig } from './wazuh-config';
import axios from 'axios';
import { AppState } from './app-state';
import { WzMisc } from '../factories/misc';
import { getHttp } from '../kibana-services';
import { PLUGIN_PLATFORM_REQUEST_HEADERS } from '../../common/constants';
import { request } from '../services/request-handler';

export class ApiCheck {
static async checkStored(data, idChanged = false) {
Expand All @@ -40,7 +40,7 @@ export class ApiCheck {
AppState.setPatternSelector(configuration['ip.selector']);
}

const response = await axios(options);
const response = await request(options);

if (response.error) {
return Promise.reject(response);
Expand Down Expand Up @@ -79,7 +79,7 @@ export class ApiCheck {
timeout: timeout || 20000
};

const response = await axios(options);
const response = await request(options);

if (response.error) {
return Promise.reject(response);
Expand Down
7 changes: 3 additions & 4 deletions public/react-services/wz-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@
*
* Find more information about this on the LICENSE file.
*/
import axios from 'axios';
import { AppState } from './app-state';
import { ApiCheck } from './wz-api-check';
import { WzAuthentication } from './wz-authentication';
import { WzMisc } from '../factories/misc';
import { WazuhConfig } from './wazuh-config';
import { OdfeUtils } from '../utils';
import IApiResponse from './interfaces/api-response.interface';
import { getHttp } from '../kibana-services';
import { PLUGIN_PLATFORM_REQUEST_HEADERS } from '../../common/constants';
import { request } from '../services/request-handler';

export class WzRequest {
static wazuhConfig: any;

Expand Down Expand Up @@ -58,15 +58,14 @@ export class WzRequest {
timeout: timeout,
};

const data = await axios(options);
const data = await request(options);

if (data['error']) {
throw new Error(data['error']);
}

return Promise.resolve(data);
} catch (error) {
OdfeUtils.checkOdfeSessionExpired(error);
//if the requests fails, we need to check if the API is down
if(checkCurrentApiIsUp){
const currentApi = JSON.parse(AppState.getCurrentAPI() || '{}');
Expand Down
57 changes: 57 additions & 0 deletions public/services/request-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import axios from 'axios';
import { HTTP_STATUS_CODES } from '../../common/constants';

let allow = true;
export let unregisterInterceptor = () => { };
const source = axios.CancelToken.source();

const disableRequests = () => {
allow = false;
source.cancel('Requests cancelled');
return;
}

export const initializeInterceptor = (core) => {
unregisterInterceptor = core.http.intercept({
responseError: (httpErrorResponse, controller) => {
if (
httpErrorResponse.response?.status === HTTP_STATUS_CODES.UNAUTHORIZED
) {
disableRequests();
}
},
request: (current, controller) => {
if (
!allow
) {
throw new Error("Disable request");
};
},
});
}

export const request = async (options = {}) => {
if (!allow) {
return Promise.reject('Requests are disabled');
};
if (!options.method || !options.url) {
return Promise.reject("Missing parameters");
};
options = {
...options, cancelToken: source.token
};

if (allow) {
try {
const requestData = await axios(options);
return Promise.resolve(requestData);
}
catch (e) {
if (e.response?.data?.message === 'Unauthorized' || e.response?.data?.message === 'Authentication required') {
disableRequests();
window.location.reload();
}
return Promise.reject(e);
}
}
}
2 changes: 0 additions & 2 deletions public/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export * as OdfeUtils from './odfe-utils';

export { checkPluginVersion } from './check-plugin-version';

export { addHelpMenuToAppChrome } from './add_help_menu_to_app';
32 changes: 0 additions & 32 deletions public/utils/odfe-utils.ts

This file was deleted.

0 comments on commit a4489a4

Please sign in to comment.