Skip to content

Commit

Permalink
[8.6] [Cases] Escape special characters in Parent issue field of Jira…
Browse files Browse the repository at this point in the history
… connector (#145610) (#146009)

# Backport

This will backport the following commits from `main` to `8.6`:
- [[Cases] Escape special characters in Parent issue field of Jira
connector (#145610)](#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":"[email protected]"},"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![image](https://user-images.githubusercontent.com/117571355/202526389-a3428c44-45b5-498c-98af-4ca709ae6937.png)\r\n\r\n**After**\r\n\r\n![image](https://user-images.githubusercontent.com/117571355/202525304-5023f27c-c3df-4839-8c5d-231dcd4a74e6.png)\r\n\r\n\r\n![image](https://user-images.githubusercontent.com/117571355/202526111-4324ce5b-ea13-4bb6-8d96-388178b1b60d.png)\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
<[email protected]>","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![image](https://user-images.githubusercontent.com/117571355/202526389-a3428c44-45b5-498c-98af-4ca709ae6937.png)\r\n\r\n**After**\r\n\r\n![image](https://user-images.githubusercontent.com/117571355/202525304-5023f27c-c3df-4839-8c5d-231dcd4a74e6.png)\r\n\r\n\r\n![image](https://user-images.githubusercontent.com/117571355/202526111-4324ce5b-ea13-4bb6-8d96-388178b1b60d.png)\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
<[email protected]>","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![image](https://user-images.githubusercontent.com/117571355/202526389-a3428c44-45b5-498c-98af-4ca709ae6937.png)\r\n\r\n**After**\r\n\r\n![image](https://user-images.githubusercontent.com/117571355/202525304-5023f27c-c3df-4839-8c5d-231dcd4a74e6.png)\r\n\r\n\r\n![image](https://user-images.githubusercontent.com/117571355/202526111-4324ce5b-ea13-4bb6-8d96-388178b1b60d.png)\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
<[email protected]>","sha":"a42314d27046c34c7eebc4570f548870636ffe93"}}]}]
BACKPORT-->

Co-authored-by: Janki Salvi <[email protected]>
  • Loading branch information
kibanamachine and js-jankisalvi authored Nov 22, 2022
1 parent 07884bf commit 2a20152
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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({
Expand All @@ -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');
Expand All @@ -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' } })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
ResponseError,
UpdateIncidentParams,
} from './types';
import { escapeJqlSpecialCharacters } from './utils';

import * as i18n from './translations';

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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♥');
});
});
Original file line number Diff line number Diff line change
@@ -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);
};

0 comments on commit 2a20152

Please sign in to comment.