mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
# Backport This will backport the following commits from `main` to `8.6`: - [[Cases] Escape special characters in Parent issue field of Jira connector (#145610)](https://github.com/elastic/kibana/pull/145610) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Janki Salvi","email":"117571355+js-jankisalvi@users.noreply.github.com"},"sourceCommit":{"committedDate":"2022-11-22T15:06:29Z","message":"[Cases] Escape special characters in Parent issue field of Jira connector (#145610)\n\n## Summary\r\n\r\nFixes #131281\r\n \r\nEscapes special characters `+ - & | ! ( ) { } [ ] ^ ~ * ? \\ :` from\r\nparent issue field.\r\n\r\n**Before**\r\n\r\n\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"a42314d27046c34c7eebc4570f548870636ffe93","branchLabelMapping":{"^v8.7.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","Team:ResponseOps","Feature:Cases","v8.6.0","v8.7.0"],"number":145610,"url":"https://github.com/elastic/kibana/pull/145610","mergeCommit":{"message":"[Cases] Escape special characters in Parent issue field of Jira connector (#145610)\n\n## Summary\r\n\r\nFixes #131281\r\n \r\nEscapes special characters `+ - & | ! ( ) { } [ ] ^ ~ * ? \\ :` from\r\nparent issue field.\r\n\r\n**Before**\r\n\r\n\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"a42314d27046c34c7eebc4570f548870636ffe93"}},"sourceBranch":"main","suggestedTargetBranches":["8.6"],"targetPullRequestStates":[{"branch":"8.6","label":"v8.6.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.7.0","labelRegex":"^v8.7.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/145610","number":145610,"mergeCommit":{"message":"[Cases] Escape special characters in Parent issue field of Jira connector (#145610)\n\n## Summary\r\n\r\nFixes #131281\r\n \r\nEscapes special characters `+ - & | ! ( ) { } [ ] ^ ~ * ? \\ :` from\r\nparent issue field.\r\n\r\n**Before**\r\n\r\n\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"a42314d27046c34c7eebc4570f548870636ffe93"}}]}] BACKPORT--> Co-authored-by: Janki Salvi <117571355+js-jankisalvi@users.noreply.github.com>
This commit is contained in:
parent
07884bf0a1
commit
2a20152880
4 changed files with 159 additions and 6 deletions
|
@ -16,7 +16,7 @@ import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.moc
|
|||
const logger = loggingSystemMock.create().get() as jest.Mocked<Logger>;
|
||||
|
||||
interface ResponseError extends Error {
|
||||
response?: { data: { errors: Record<string, string> } };
|
||||
response?: { data: { errors: Record<string, string>; errorMessages?: string[] } };
|
||||
}
|
||||
|
||||
jest.mock('axios');
|
||||
|
@ -1096,6 +1096,33 @@ describe('Jira service', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
test('it should return correct issue when special characters are used', async () => {
|
||||
const specialCharacterIssuesResponse = [
|
||||
{
|
||||
id: '77145',
|
||||
key: 'RJ-5696',
|
||||
fields: { summary: '[th!s^is()a-te+st-{~is*s&ue?or|and\\bye:}]"}]' },
|
||||
},
|
||||
];
|
||||
requestMock.mockImplementation(() =>
|
||||
createAxiosResponse({
|
||||
data: {
|
||||
issues: specialCharacterIssuesResponse,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const res = await service.getIssues('[th!s^is()a-te+st-{~is*s&ue?or|and\\bye:}]"}]');
|
||||
|
||||
expect(res).toEqual([
|
||||
{
|
||||
id: '77145',
|
||||
key: 'RJ-5696',
|
||||
title: '[th!s^is()a-te+st-{~is*s&ue?or|and\\bye:}]"}]',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('it should call request with correct arguments', async () => {
|
||||
requestMock.mockImplementation(() =>
|
||||
createAxiosResponse({
|
||||
|
@ -1115,6 +1142,32 @@ describe('Jira service', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('it should escape JQL special characters', async () => {
|
||||
const specialCharacterIssuesResponse = [
|
||||
{
|
||||
id: '77145',
|
||||
key: 'RJ-5696',
|
||||
fields: { summary: '[th!s^is()a-te+st-{~is*s&ue?or|and\\bye:}]"}]' },
|
||||
},
|
||||
];
|
||||
requestMock.mockImplementation(() =>
|
||||
createAxiosResponse({
|
||||
data: {
|
||||
issues: specialCharacterIssuesResponse,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
await service.getIssues('[th!s^is()a-te+st-{~is*s&ue?or|and\\bye:}]"}]');
|
||||
expect(requestMock).toHaveBeenLastCalledWith({
|
||||
axios,
|
||||
logger,
|
||||
method: 'get',
|
||||
configurationUtilities,
|
||||
url: `https://coolsite.net/rest/api/2/search?jql=project%3D%22CK%22%20and%20summary%20~%22%5C%5C%5Bth%5C%5C!s%5C%5C%5Eis%5C%5C(%5C%5C)a%5C%5C-te%5C%5C%2Bst%5C%5C-%5C%5C%7B%5C%5C~is%5C%5C*s%5C%5C%26ue%5C%5C%3For%5C%5C%7Cand%5C%5Cbye%5C%5C%3A%5C%5C%7D%5C%5C%5D%5C%5C%7D%5C%5C%5D%22`,
|
||||
});
|
||||
});
|
||||
|
||||
test('it should throw an error', async () => {
|
||||
requestMock.mockImplementation(() => {
|
||||
const error: ResponseError = new Error('An error has occurred');
|
||||
|
@ -1127,6 +1180,25 @@ describe('Jira service', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('it should show an error from errorMessages', async () => {
|
||||
requestMock.mockImplementation(() => {
|
||||
const error: ResponseError = new Error('An error has occurred');
|
||||
error.response = {
|
||||
data: {
|
||||
errors: {
|
||||
issuestypes: 'My second error',
|
||||
},
|
||||
errorMessages: ['My first error'],
|
||||
},
|
||||
};
|
||||
throw error;
|
||||
});
|
||||
|
||||
await expect(service.getIssues('<hj>"')).rejects.toThrow(
|
||||
'[Action][Jira]: Unable to get issues. Error: An error has occurred. Reason: My first error'
|
||||
);
|
||||
});
|
||||
|
||||
test('it should throw if the request is not a JSON', async () => {
|
||||
requestMock.mockImplementation(() =>
|
||||
createAxiosResponse({ data: { id: '1' }, headers: { ['content-type']: 'text/html' } })
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
ResponseError,
|
||||
UpdateIncidentParams,
|
||||
} from './types';
|
||||
import { escapeJqlSpecialCharacters } from './utils';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
|
@ -122,14 +123,14 @@ export const createExternalService = (
|
|||
|
||||
const { errorMessages, errors } = errorResponse;
|
||||
|
||||
if (errors == null) {
|
||||
return 'unknown: errorResponse.errors was null';
|
||||
}
|
||||
|
||||
if (Array.isArray(errorMessages) && errorMessages.length > 0) {
|
||||
return `${errorMessages.join(', ')}`;
|
||||
}
|
||||
|
||||
if (errors == null) {
|
||||
return 'unknown: errorResponse.errors was null';
|
||||
}
|
||||
|
||||
return Object.entries(errors).reduce((errorMessage, [, value]) => {
|
||||
const msg = errorMessage.length > 0 ? `${errorMessage} ${value}` : value;
|
||||
return msg;
|
||||
|
@ -498,8 +499,9 @@ export const createExternalService = (
|
|||
};
|
||||
|
||||
const getIssues = async (title: string) => {
|
||||
const jqlEscapedTitle = escapeJqlSpecialCharacters(title);
|
||||
const query = `${searchUrl}?jql=${encodeURIComponent(
|
||||
`project="${projectKey}" and summary ~"${title}"`
|
||||
`project="${projectKey}" and summary ~"${jqlEscapedTitle}"`
|
||||
)}`;
|
||||
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { escapeJqlSpecialCharacters } from './utils';
|
||||
|
||||
describe('escapeJqlSpecialCharacters', () => {
|
||||
it('should escape jql special characters', () => {
|
||||
const str = '[th!s^is()a-te+st-{~is*s&ue?or|and\\bye:}]"}]';
|
||||
const escapedStr = escapeJqlSpecialCharacters(str);
|
||||
expect(escapedStr).toEqual(
|
||||
'\\\\[th\\\\!s\\\\^is\\\\(\\\\)a\\\\-te\\\\+st\\\\-\\\\{\\\\~is\\\\*s\\\\&ue\\\\?or\\\\|and\\\\bye\\\\:\\\\}\\\\]\\\\}\\\\]'
|
||||
);
|
||||
});
|
||||
|
||||
it('should remove double quotes', () => {
|
||||
const str = '"Hello"';
|
||||
const escapedStr = escapeJqlSpecialCharacters(str);
|
||||
expect(escapedStr).toEqual('Hello');
|
||||
});
|
||||
|
||||
it('should replace single quotes with backslash', () => {
|
||||
const str = "Javascript's beauty is simplicity!";
|
||||
const escapedStr = escapeJqlSpecialCharacters(str);
|
||||
expect(escapedStr).toEqual('Javascript\\\\s beauty is simplicity\\\\!');
|
||||
});
|
||||
|
||||
it('should replace single backslash with four backslash', () => {
|
||||
const str = '\\I have one backslash';
|
||||
const escapedStr = escapeJqlSpecialCharacters(str);
|
||||
expect(escapedStr).toEqual('\\\\I have one backslash');
|
||||
});
|
||||
|
||||
it('should not escape other special characters', () => {
|
||||
const str = '<it is, a test.>';
|
||||
const escapedStr = escapeJqlSpecialCharacters(str);
|
||||
expect(escapedStr).toEqual('<it is, a test.>');
|
||||
});
|
||||
|
||||
it('should not escape alpha numeric characters', () => {
|
||||
const str = 'here is a case 29';
|
||||
const escapedStr = escapeJqlSpecialCharacters(str);
|
||||
expect(escapedStr).toEqual('here is a case 29');
|
||||
});
|
||||
|
||||
it('should not escape unicode spaces', () => {
|
||||
const str = 'comm\u2000=\u2001"hello"\u3000';
|
||||
const escapedStr = escapeJqlSpecialCharacters(str);
|
||||
expect(escapedStr).toEqual('comm = hello ');
|
||||
});
|
||||
|
||||
it('should not escape non ASCII characters', () => {
|
||||
const str = 'Apple’s amazing idea♥';
|
||||
const escapedStr = escapeJqlSpecialCharacters(str);
|
||||
expect(escapedStr).toEqual('Apple’s amazing idea♥');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
// These characters need to be escaped per Jira's search syntax, see for more details: https://confluence.atlassian.com/jirasoftwareserver/search-syntax-for-text-fields-939938747.html
|
||||
export const JQL_SPECIAL_CHARACTERS_REGEX = /[-!^+&*()[\]/{}|:?~]/;
|
||||
|
||||
const DOUBLE_BACKSLASH_REGEX = '\\\\$&';
|
||||
|
||||
export const escapeJqlSpecialCharacters = (str: string) => {
|
||||
return str
|
||||
.replaceAll('"', '')
|
||||
.replaceAll(/\\/g, '\\\\')
|
||||
.replaceAll(/'/g, '\\\\')
|
||||
.replaceAll(new RegExp(JQL_SPECIAL_CHARACTERS_REGEX, 'g'), DOUBLE_BACKSLASH_REGEX);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue