Commit d243a2e6 authored by Tristan Read's avatar Tristan Read

Sanitize issue fields

parent 448371a3
...@@ -4,7 +4,12 @@ export const parseIssuableData = () => { ...@@ -4,7 +4,12 @@ export const parseIssuableData = () => {
try { try {
const initialDataEl = document.getElementById('js-issuable-app-initial-data'); const initialDataEl = document.getElementById('js-issuable-app-initial-data');
return JSON.parse(sanitize(initialDataEl.textContent).replace(/"/g, '"')); const parsedData = JSON.parse(initialDataEl.textContent.replace(/"/g, '"'));
parsedData.initialTitleHtml = sanitize(parsedData.initialTitleHtml);
parsedData.initialDescriptionHtml = sanitize(parsedData.initialDescriptionHtml);
return parsedData;
} catch (e) { } catch (e) {
console.error(e); // eslint-disable-line no-console console.error(e); // eslint-disable-line no-console
......
import { GlIntersectionObserver } from '@gitlab/ui'; import { GlIntersectionObserver } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer'; import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import '~/behaviors/markdown/render_gfm'; import '~/behaviors/markdown/render_gfm';
import IssuableApp from '~/issue_show/components/app.vue'; import IssuableApp from '~/issue_show/components/app.vue';
import eventHub from '~/issue_show/event_hub'; import eventHub from '~/issue_show/event_hub';
import { initialRequest, secondRequest } from '../mock_data'; import {
appProps,
initialRequest,
publishedIncidentUrl,
secondRequest,
zoomMeetingUrl,
} from '../mock_data';
import IncidentTabs from '~/issue_show/components/incident_tabs.vue'; import IncidentTabs from '~/issue_show/components/incident_tabs.vue';
import DescriptionComponent from '~/issue_show/components/description.vue'; import DescriptionComponent from '~/issue_show/components/description.vue';
import PinnedLinks from '~/issue_show/components/pinned_links.vue'; import PinnedLinks from '~/issue_show/components/pinned_links.vue';
...@@ -22,30 +27,6 @@ jest.mock('~/issue_show/event_hub'); ...@@ -22,30 +27,6 @@ jest.mock('~/issue_show/event_hub');
const REALTIME_REQUEST_STACK = [initialRequest, secondRequest]; const REALTIME_REQUEST_STACK = [initialRequest, secondRequest];
const zoomMeetingUrl = 'https://gitlab.zoom.us/j/95919234811';
const publishedIncidentUrl = 'https://status.com/';
const defaultProps = {
canUpdate: true,
canDestroy: true,
endpoint: '/gitlab-org/gitlab-shell/-/issues/9/realtime_changes',
updateEndpoint: TEST_HOST,
issuableRef: '#1',
issuableStatus: 'opened',
initialTitleHtml: '',
initialTitleText: '',
initialDescriptionHtml: 'test',
initialDescriptionText: 'test',
lockVersion: 1,
markdownPreviewPath: '/',
markdownDocsPath: '/',
projectNamespace: '/',
projectPath: '/',
issuableTemplateNamesPath: '/issuable-templates-path',
zoomMeetingUrl,
publishedIncidentUrl,
};
describe('Issuable output', () => { describe('Issuable output', () => {
useMockIntersectionObserver(); useMockIntersectionObserver();
...@@ -57,7 +38,7 @@ describe('Issuable output', () => { ...@@ -57,7 +38,7 @@ describe('Issuable output', () => {
const mountComponent = (props = {}) => { const mountComponent = (props = {}) => {
wrapper = mount(IssuableApp, { wrapper = mount(IssuableApp, {
propsData: { ...defaultProps, ...props }, propsData: { ...appProps, ...props },
}); });
}; };
......
import MockAdapter from 'axios-mock-adapter';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import initIssuableApp from '~/issue_show/issue'; import initIssuableApp from '~/issue_show/issue';
import { parseIssuableData } from '~/issue_show/utils/parse_data'; import * as parseData from '~/issue_show/utils/parse_data';
import { appProps } from './mock_data';
const mock = new MockAdapter(axios);
mock.onGet().reply(200);
useMockIntersectionObserver();
jest.mock('~/lib/utils/poll');
const setupHTML = initialData => {
document.body.innerHTML = `
<div id="js-issuable-app"></div>
<script id="js-issuable-app-initial-data" type="application/json">
${JSON.stringify(initialData)}
</script>
`;
};
describe('Issue show index', () => { describe('Issue show index', () => {
describe('initIssueableApp', () => { describe('initIssueableApp', () => {
// Warning: this test is currently faulty. it('should initialize app with no potential XSS attack', async () => {
// More details at https://gitlab.com/gitlab-org/gitlab/-/issues/241717 const alertSpy = jest.spyOn(window, 'alert').mockImplementation(() => {});
// eslint-disable-next-line jest/no-disabled-tests const parseDataSpy = jest.spyOn(parseData, 'parseIssuableData');
it.skip('should initialize app with no potential XSS attack', () => {
const d = document.createElement('div');
d.id = 'js-issuable-app-initial-data';
d.innerHTML = JSON.stringify({
initialDescriptionHtml: '&lt;img src=x onerror=alert(1)&gt;',
});
document.body.appendChild(d); setupHTML({
...appProps,
initialDescriptionHtml: '<svg onload=window.alert(1)>',
});
const alertSpy = jest.spyOn(window, 'alert'); const issuableData = parseData.parseIssuableData();
const issuableData = parseIssuableData();
initIssuableApp(issuableData); initIssuableApp(issuableData);
await waitForPromises();
expect(parseDataSpy).toHaveBeenCalled();
expect(alertSpy).not.toHaveBeenCalled(); expect(alertSpy).not.toHaveBeenCalled();
}); });
}); });
......
...@@ -31,3 +31,28 @@ export const descriptionProps = { ...@@ -31,3 +31,28 @@ export const descriptionProps = {
taskStatus: '', taskStatus: '',
updateUrl: TEST_HOST, updateUrl: TEST_HOST,
}; };
export const publishedIncidentUrl = 'https://status.com/';
export const zoomMeetingUrl = 'https://gitlab.zoom.us/j/95919234811';
export const appProps = {
canUpdate: true,
canDestroy: true,
endpoint: '/gitlab-org/gitlab-shell/-/issues/9/realtime_changes',
updateEndpoint: TEST_HOST,
issuableRef: '#1',
issuableStatus: 'opened',
initialTitleHtml: '',
initialTitleText: '',
initialDescriptionHtml: 'test',
initialDescriptionText: 'test',
lockVersion: 1,
markdownPreviewPath: '/',
markdownDocsPath: '/',
projectNamespace: '/',
projectPath: '/',
issuableTemplateNamesPath: '/issuable-templates-path',
zoomMeetingUrl,
publishedIncidentUrl,
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment