Skip to content

Commit

Permalink
chore(api): encode URL params
Browse files Browse the repository at this point in the history
Signed-off-by: Viktor Tsvetkov <[email protected]>
  • Loading branch information
vtsvetkov-splunk committed Oct 16, 2024
1 parent f420f64 commit a755011
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 74 deletions.
16 changes: 10 additions & 6 deletions ui/src/components/BaseFormView/BaseFormView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Validator, { SaveValidator } from '../../util/Validator';
import { getUnifiedConfigs, generateToast } from '../../util/util';
import { MODE_CLONE, MODE_CREATE, MODE_EDIT, MODE_CONFIG } from '../../constants/modes';
import { PAGE_INPUT, PAGE_CONF } from '../../constants/pages';
import { axiosCallWrapper } from '../../util/axiosCallWrapper';
import { axiosCallWrapper, generateEndPointUrl } from '../../util/axiosCallWrapper';
import { parseErrorMsg, getFormattedMessage } from '../../util/messageUtil';
import { getBuildDirPath } from '../../util/script';

Expand Down Expand Up @@ -862,7 +862,7 @@ class BaseFormView extends PureComponent<BaseFormProps, BaseFormState> {
}

axiosCallWrapper({
serviceName: this.endpoint,
endpointUrl: generateEndPointUrl(encodeURIComponent(this.endpoint)),
body,
customHeaders: { 'Content-Type': 'application/x-www-form-urlencoded' },
method: 'post',
Expand Down Expand Up @@ -1134,12 +1134,16 @@ class BaseFormView extends PureComponent<BaseFormProps, BaseFormState> {
return false;
}

const baseUrl = new URL(
`https://${this.datadict.endpoint || this.datadict.endpoint_token}`
);
baseUrl.pathname = this.oauthConf?.accessTokenEndpoint || '';
const url = baseUrl.toString();

const code = decodeURIComponent(message.code);
const data: Record<string, AcceptableFormValueOrNullish> = {
method: 'POST',
url: `https://${this.datadict.endpoint || this.datadict.endpoint_token}${
this.oauthConf?.accessTokenEndpoint
}`,
url,
grant_type: 'authorization_code',
client_id: this.datadict.client_id,
client_secret: this.datadict.client_secret,
Expand All @@ -1159,7 +1163,7 @@ class BaseFormView extends PureComponent<BaseFormProps, BaseFormState> {
}
});

const OAuthEndpoint = `${this.appName}_oauth/oauth`;
const OAuthEndpoint = `${encodeURIComponent(this.appName)}_oauth/oauth`;
// Internal handler call to get the access token and other values
axiosCallWrapper({
endpointUrl: OAuthEndpoint,
Expand Down
4 changes: 2 additions & 2 deletions ui/src/components/ConfigurationFormView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import WaitSpinner from '@splunk/react-ui/WaitSpinner';
import axios from 'axios';
import BaseFormView from './BaseFormView/BaseFormView';
import { StyledButton } from '../pages/EntryPageStyle';
import { axiosCallWrapper } from '../util/axiosCallWrapper';
import { axiosCallWrapper, generateEndPointUrl } from '../util/axiosCallWrapper';
import { MODE_CONFIG } from '../constants/modes';
import { WaitSpinnerWrapper } from './table/CustomTableStyle';
import { PAGE_CONF } from '../constants/pages';
Expand All @@ -28,7 +28,7 @@ function ConfigurationFormView({ serviceName }) {
useEffect(() => {
const abortController = new AbortController();
axiosCallWrapper({
serviceName: `settings/${serviceName}`,
endpointUrl: generateEndPointUrl(`settings/${encodeURIComponent(serviceName)}`),
handleError: true,
signal: abortController.signal,
callbackOnError: (err) => {
Expand Down
10 changes: 6 additions & 4 deletions ui/src/components/DeleteModal/DeleteModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { _ } from '@splunk/ui-utils/i18n';
import { generateToast } from '../../util/util';
import { StyledButton } from '../../pages/EntryPageStyle';

import { axiosCallWrapper } from '../../util/axiosCallWrapper';
import { axiosCallWrapper, generateEndPointUrl } from '../../util/axiosCallWrapper';
import TableContext from '../../context/TableContext';
import { parseErrorMsg, getFormattedMessage } from '../../util/messageUtil';
import { PAGE_INPUT } from '../../constants/pages';
Expand Down Expand Up @@ -52,9 +52,11 @@ class DeleteModal extends Component<DeleteModalProps, DeleteModalState> {
(prevState) => ({ ...prevState, isDeleting: true, ErrorMsg: '' }),
() => {
axiosCallWrapper({
serviceName: `${this.props.serviceName}/${encodeURIComponent(
this.props.stanzaName
)}`,
endpointUrl: generateEndPointUrl(
`${encodeURIComponent(this.props.serviceName)}/${encodeURIComponent(
this.props.stanzaName
)}`
),
customHeaders: { 'Content-Type': 'application/x-www-form-urlencoded' },
method: 'delete',
handleError: false,
Expand Down
20 changes: 11 additions & 9 deletions ui/src/components/MultiInputComponent/MultiInputComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import styled from 'styled-components';
import WaitSpinner from '@splunk/react-ui/WaitSpinner';
import { z } from 'zod';

import { AxiosCallType, axiosCallWrapper } from '../../util/axiosCallWrapper';
import { AxiosCallType, axiosCallWrapper, generateEndPointUrl } from '../../util/axiosCallWrapper';
import { filterResponse } from '../../util/util';
import { MultipleSelectCommonOptions } from '../../types/globalConfig/entities';
import { invariant } from '../../util/invariant';

const MultiSelectWrapper = styled(Multiselect)`
width: 320px !important;
Expand Down Expand Up @@ -79,19 +80,20 @@ function MultiInputComponent(props: MultiInputComponentProps) {
let current = true;
const abortController = new AbortController();

const url = referenceName
? generateEndPointUrl(encodeURIComponent(referenceName))
: endpointUrl;
invariant(
url,
'[MultiInputComponent] referenceName or endpointUrl or items must be provided'
);

const apiCallOptions = {
signal: abortController.signal,
handleError: true,
params: { count: -1 },
serviceName: '',
endpointUrl: '',
endpointUrl: url,
} satisfies AxiosCallType;
if (referenceName) {
apiCallOptions.serviceName = referenceName;
} else if (endpointUrl) {
apiCallOptions.endpointUrl = endpointUrl;
}

if (dependencyValues) {
apiCallOptions.params = { ...apiCallOptions.params, ...dependencyValues };
}
Expand Down
29 changes: 16 additions & 13 deletions ui/src/components/SingleInputComponent/SingleInputComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import Select from '@splunk/react-ui/Select';
import Button from '@splunk/react-ui/Button';
import ComboBox from '@splunk/react-ui/ComboBox';
import Clear from '@splunk/react-icons/enterprise/Clear';
import axios from 'axios';
import styled from 'styled-components';
import WaitSpinner from '@splunk/react-ui/WaitSpinner';
import { z } from 'zod';

import { axiosCallWrapper } from '../../util/axiosCallWrapper';
import { AxiosCallType, axiosCallWrapper, generateEndPointUrl } from '../../util/axiosCallWrapper';
import { SelectCommonOptions } from '../../types/globalConfig/entities';
import { filterResponse } from '../../util/util';
import { getValueMapTruthyFalse } from '../../util/considerFalseAndTruthy';
import { StandardPages } from '../../types/components/shareableTypes';
import { invariant } from '../../util/invariant';

const SelectWrapper = styled(Select)`
width: 320px !important;
Expand Down Expand Up @@ -115,20 +115,22 @@ function SingleInputComponent(props: SingleInputComponentProps) {
}

let current = true;
const source = axios.CancelToken.source();
const abortController = new AbortController();

const url = referenceName
? generateEndPointUrl(encodeURIComponent(referenceName))
: endpointUrl;
invariant(
url,
'[SingleInputComponent] referenceName or endpointUrl or autoCompleteFields must be provided'
);

const backendCallOptions = {
serviceName: '',
endpointUrl: '',
cancelToken: source.token,
signal: abortController.signal,
endpointUrl: url,
handleError: true,
params: { count: -1 },
};
if (referenceName) {
backendCallOptions.serviceName = referenceName;
} else if (endpointUrl) {
backendCallOptions.endpointUrl = endpointUrl;
}
} satisfies AxiosCallType;

if (dependencyValues) {
backendCallOptions.params = { ...backendCallOptions.params, ...dependencyValues };
Expand Down Expand Up @@ -161,9 +163,10 @@ function SingleInputComponent(props: SingleInputComponentProps) {
} else {
setOptions([]);
}

// eslint-disable-next-line consistent-return
return () => {
source.cancel('Operation canceled.');
abortController.abort('Operation canceled.');
current = false;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down
8 changes: 5 additions & 3 deletions ui/src/components/table/TableWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import update from 'immutability-helper';
import axios from 'axios';

import { WaitSpinnerWrapper } from './CustomTableStyle';
import { axiosCallWrapper } from '../../util/axiosCallWrapper';
import { axiosCallWrapper, generateEndPointUrl } from '../../util/axiosCallWrapper';
import { getUnifiedConfigs, generateToast } from '../../util/util';
import CustomTable from './CustomTable';
import TableHeader from './TableHeader';
Expand Down Expand Up @@ -143,7 +143,7 @@ const TableWrapper: React.FC<ITableWrapperProps> = ({
const requests =
services?.map((service) =>
axiosCallWrapper({
serviceName: service.name,
endpointUrl: generateEndPointUrl(encodeURIComponent(service.name)),
params: { count: -1 },
signal: abortController.signal,
})
Expand Down Expand Up @@ -204,7 +204,9 @@ const TableWrapper: React.FC<ITableWrapperProps> = ({
const body = new URLSearchParams();
body.append('disabled', String(!row.disabled));
axiosCallWrapper({
serviceName: `${row.serviceName}/${row.name}`,
endpointUrl: generateEndPointUrl(
`${encodeURIComponent(row.serviceName)}/${encodeURIComponent(row.name)}`
),
body,
customHeaders: { 'Content-Type': 'application/x-www-form-urlencoded' },
method: 'post',
Expand Down
2 changes: 1 addition & 1 deletion ui/src/public/mockServiceWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* - Please do NOT serve this file on production.
*/

const PACKAGE_VERSION = '2.4.8'
const PACKAGE_VERSION = '2.4.11'
const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
Expand Down
35 changes: 34 additions & 1 deletion ui/src/types/modules.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
declare module '@splunk/splunk-utils/config';
declare module '@splunk/splunk-utils/url';
declare module '@splunk/splunk-utils/url' {
export type Sharing = '' | 'app' | 'global' | 'system';

export interface NamespaceOptions {
app?: string;
owner?: string;
sharing?: Sharing;
}

export interface ConfigOptions {
/** Config options including `splunkdPath`. Defaults to the value provided by `@splunk/splunk-utils/config`. */
splunkdPath?: string;
}

/**
* Creates a fully qualified URL for the specified endpoint.
* For example:
* ```
* createRESTURL('server/info'); // "/en-US/splunkd/__raw/services/server/info"
* createRESTURL('saved/searches', {app: 'search'}); // "/en-US/splunkd/__raw/servicesNS/-/search/saved/searches"
* ```
* @param endpoint - An endpoint to a REST API.
* @param namespaceOptions - Optional namespace options.
* @param configOptions - Optional config options.
* @returns The URL of the REST API endpoint.
* @alias createRESTURL
*/
export function createRESTURL(
endpoint: string,
namespaceOptions?: NamespaceOptions,
configOptions?: ConfigOptions
): string;
}

declare module '@splunk/search-job';
// declaring modules as utils does not seem to have types

Expand Down
44 changes: 16 additions & 28 deletions ui/src/util/axiosCallWrapper.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import axios, { AxiosRequestConfig } from 'axios';
import { CSRFToken, app } from '@splunk/splunk-utils/config';
import { createRESTURL } from '@splunk/splunk-utils/url';
import { generateEndPointUrl, generateToast } from './util';
import { generateToast, getUnifiedConfigs } from './util';
import { parseErrorMsg } from './messageUtil';

interface axiosCallWithServiceName {
serviceName?: string;
export interface AxiosCallType {
endpointUrl: string;
}

interface axiosCallWithEndpointUrl {
serviceName: string;
endpointUrl?: string;
}

interface CommonAxiosCall {
params?: Record<string, string | number>;
signal?: AbortSignal;
customHeaders?: Record<string, string>;
Expand All @@ -24,12 +15,17 @@ interface CommonAxiosCall {
callbackOnError?: (error: unknown) => void;
}

export type AxiosCallType = (axiosCallWithServiceName | axiosCallWithEndpointUrl) & CommonAxiosCall;
export function generateEndPointUrl(name: string) {
const unifiedConfigs = getUnifiedConfigs();

return `${unifiedConfigs.meta.restRoot}_${name}`;
}

const DEFAULT_PARAMS = { output_mode: 'json' };

/**
*
* @param {Object} data The object containing required params for request
* @param {string} data.serviceName service name which is input name or tab name based on the page
* @param {string} data.endpointUrl rest endpoint path
* @param {object} data.params object with params as key value pairs
* @param {object} data.body object with body as key value pairs for post request
Expand All @@ -40,7 +36,6 @@ export type AxiosCallType = (axiosCallWithServiceName | axiosCallWithEndpointUrl
* @returns
*/
const axiosCallWrapper = ({
serviceName,
endpointUrl,
params,
body,
Expand All @@ -50,32 +45,25 @@ const axiosCallWrapper = ({
handleError = false,
callbackOnError = () => {},
}: AxiosCallType) => {
const endpoint = serviceName ? generateEndPointUrl(serviceName) : endpointUrl;
const appData = {
app,
owner: 'nobody',
};
const baseHeaders = {
'X-Splunk-Form-Key': CSRFToken,
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json',
};
const headers = Object.assign(baseHeaders, customHeaders);
const url = createRESTURL(endpoint, appData);
const url = createRESTURL(endpointUrl, { app, owner: 'nobody' });

let newParams = { output_mode: 'json' };
if (params) {
newParams = { ...newParams, ...params };
}

const options: Record<string, unknown> = {
params: newParams,
const options: AxiosRequestConfig = {
params: {
...DEFAULT_PARAMS,
...params,
},
method,
url,
withCredentials: true,
headers,
signal,
} satisfies AxiosRequestConfig;
};

if (method === 'post') {
options.data = body;
Expand Down
7 changes: 0 additions & 7 deletions ui/src/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,6 @@ export function getMetaInfo() {
};
}

export function generateEndPointUrl(name: string) {
if (!unifiedConfigs) {
throw new Error('No GlobalConfig set');
}
return `${unifiedConfigs.meta.restRoot}_${name}`;
}

export function setUnifiedConfig(unifiedConfig: GlobalConfig) {
const result = GlobalConfigSchema.safeParse(unifiedConfig);
if (!result.success) {
Expand Down

0 comments on commit a755011

Please sign in to comment.