Skip to content

Commit

Permalink
[Enterprise Search] Update shared API request handler (#77112)
Browse files Browse the repository at this point in the history
* Add user auth check for /ent/select redirects

- Recent Enterprise Search CSRF changes have made it so redirects can occur to /ent/select and not just /login

* Fix request.query typing

- API endpoints passing in custom request.query params were seeing {} type errors - this change works around them

* Add Accept and Content-Type JSON headers to Enterprise Search requests

- Without the Accept header, Enterprise Search APIs will kick back a CSRF error
- Without the Content-Type header, APIs will not load JSON bodies as parameters per Ruby on Rails docs
  • Loading branch information
Constance authored Sep 10, 2020
1 parent 33bc443 commit f56fcb3
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 16 deletions.
5 changes: 4 additions & 1 deletion x-pack/plugins/enterprise_search/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ export const WORKPLACE_SEARCH_PLUGIN = {

export const LICENSED_SUPPORT_URL = 'https://support.elastic.co';

export const JSON_HEADER = { 'Content-Type': 'application/json' }; // This needs specific casing or Chrome throws a 415 error
export const JSON_HEADER = {
'Content-Type': 'application/json', // This needs specific casing or Chrome throws a 415 error
Accept: 'application/json', // Required for Enterprise Search APIs
};

export const ENGINES_PAGE_SIZE = 10;
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { mockConfig, mockLogger } from '../__mocks__';
import { JSON_HEADER } from '../../common/constants';

import { EnterpriseSearchRequestHandler } from './enterprise_search_request_handler';

Expand Down Expand Up @@ -150,18 +151,26 @@ describe('EnterpriseSearchRequestHandler', () => {
);
});

it('returns an error when user authentication to Enterprise Search fails', async () => {
EnterpriseSearchAPI.mockReturn({}, { url: 'http://localhost:3002/login' });
const requestHandler = enterpriseSearchRequestHandler.createRequest({
path: '/api/unauthenticated',
describe('user authentication errors', () => {
afterEach(async () => {
const requestHandler = enterpriseSearchRequestHandler.createRequest({
path: '/api/unauthenticated',
});
await makeAPICall(requestHandler);

EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/unauthenticated');
expect(responseMock.customError).toHaveBeenCalledWith({
body: 'Error connecting to Enterprise Search: Cannot authenticate Enterprise Search user',
statusCode: 502,
});
});

await makeAPICall(requestHandler);
EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/unauthenticated');
it('errors when redirected to /login', async () => {
EnterpriseSearchAPI.mockReturn({}, { url: 'http://localhost:3002/login' });
});

expect(responseMock.customError).toHaveBeenCalledWith({
body: 'Error connecting to Enterprise Search: Cannot authenticate Enterprise Search user',
statusCode: 502,
it('errors when redirected to /ent/select', async () => {
EnterpriseSearchAPI.mockReturn({}, { url: 'http://localhost:3002/ent/select' });
});
});
});
Expand All @@ -185,7 +194,7 @@ const makeAPICall = (handler: Function, params = {}) => {
const EnterpriseSearchAPI = {
shouldHaveBeenCalledWith(expectedUrl: string, expectedParams = {}) {
expect(fetchMock).toHaveBeenCalledWith(expectedUrl, {
headers: { Authorization: 'Basic 123' },
headers: { Authorization: 'Basic 123', ...JSON_HEADER },
method: 'GET',
body: undefined,
...expectedParams,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
Logger,
} from 'src/core/server';
import { ConfigType } from '../index';
import { JSON_HEADER } from '../../common/constants';

interface IConstructorDependencies {
config: ConfigType;
Expand All @@ -25,7 +26,7 @@ interface IRequestParams<ResponseBody> {
hasValidData?: (body?: ResponseBody) => boolean;
}
export interface IEnterpriseSearchRequestHandler {
createRequest(requestParams?: object): RequestHandler<unknown, Readonly<{}>, unknown>;
createRequest(requestParams?: object): RequestHandler<unknown, unknown, unknown>;
}

/**
Expand All @@ -52,28 +53,28 @@ export class EnterpriseSearchRequestHandler {
}: IRequestParams<ResponseBody>) {
return async (
_context: RequestHandlerContext,
request: KibanaRequest<unknown, Readonly<{}>, unknown>,
request: KibanaRequest<unknown, unknown, unknown>,
response: KibanaResponseFactory
) => {
try {
// Set up API URL
const queryParams = { ...request.query, ...params };
const queryParams = { ...(request.query as object), ...params };
const queryString = !this.isEmptyObj(queryParams)
? `?${querystring.stringify(queryParams)}`
: '';
const url = encodeURI(this.enterpriseSearchUrl + path + queryString);

// Set up API options
const { method } = request.route;
const headers = { Authorization: request.headers.authorization as string };
const headers = { Authorization: request.headers.authorization as string, ...JSON_HEADER };
const body = !this.isEmptyObj(request.body as object)
? JSON.stringify(request.body)
: undefined;

// Call the Enterprise Search API and pass back response to the front-end
const apiResponse = await fetch(url, { method, headers, body });

if (apiResponse.url.endsWith('/login')) {
if (apiResponse.url.endsWith('/login') || apiResponse.url.endsWith('/ent/select')) {
throw new Error('Cannot authenticate Enterprise Search user');
}

Expand Down

0 comments on commit f56fcb3

Please sign in to comment.