diff options
author | Felipe Artur Cardozo <fcardozo@gitlab.com> | 2018-07-27 00:16:09 +0300 |
---|---|---|
committer | Felipe Artur Cardozo <fcardozo@gitlab.com> | 2018-07-27 00:16:09 +0300 |
commit | dae85363e363cc92e335808b2bd4e0608d92d760 (patch) | |
tree | c6bdb651c734ed1dcb7a347651cf1e596302a8eb /spec/javascripts/pipelines | |
parent | bc1b4d9fef7e55f9afffcfaa4f784b984ca97f31 (diff) | |
parent | 0e7aa236c8c8143770b6602fa99cb4197c65fe70 (diff) |
Merge branch 'security-2690-fix-tooltip' into 'master'
[master] Escapes job name used in tooltips
See merge request gitlab/gitlabhq!2427
Diffstat (limited to 'spec/javascripts/pipelines')
5 files changed, 170 insertions, 31 deletions
diff --git a/spec/javascripts/pipelines/graph/dropdown_job_component_spec.js b/spec/javascripts/pipelines/graph/dropdown_job_component_spec.js new file mode 100644 index 00000000000..608a0d4be67 --- /dev/null +++ b/spec/javascripts/pipelines/graph/dropdown_job_component_spec.js @@ -0,0 +1,93 @@ +import Vue from 'vue'; +import component from '~/pipelines/components/graph/dropdown_job_component.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; + +describe('dropdown job component', () => { + const Component = Vue.extend(component); + let vm; + + const mock = { + jobs: [ + { + id: 4256, + name: '<img src=x onerror=alert(document.domain)>', + status: { + icon: 'icon_status_success', + text: 'passed', + label: 'passed', + tooltip: 'passed', + group: 'success', + details_path: '/root/ci-mock/builds/4256', + has_details: true, + action: { + icon: 'retry', + title: 'Retry', + path: '/root/ci-mock/builds/4256/retry', + method: 'post', + }, + }, + }, + { + id: 4299, + name: 'test', + status: { + icon: 'icon_status_success', + text: 'passed', + label: 'passed', + tooltip: 'passed', + group: 'success', + details_path: '/root/ci-mock/builds/4299', + has_details: true, + action: { + icon: 'retry', + title: 'Retry', + path: '/root/ci-mock/builds/4299/retry', + method: 'post', + }, + }, + }, + ], + name: 'rspec:linux', + size: 2, + status: { + icon: 'icon_status_success', + text: 'passed', + label: 'passed', + tooltip: 'passed', + group: 'success', + details_path: '/root/ci-mock/builds/4256', + has_details: true, + action: { + icon: 'retry', + title: 'Retry', + path: '/root/ci-mock/builds/4256/retry', + method: 'post', + }, + }, + }; + + afterEach(() => { + vm.$destroy(); + }); + + beforeEach(() => { + vm = mountComponent(Component, { job: mock }); + }); + + it('renders button with job name and size', () => { + expect(vm.$el.querySelector('button').textContent).toContain(mock.name); + expect(vm.$el.querySelector('button').textContent).toContain(mock.size); + }); + + it('renders dropdown with jobs', () => { + expect(vm.$el.querySelectorAll('.scrollable-menu>ul>li').length).toEqual(mock.jobs.length); + }); + + it('escapes tooltip title', () => { + expect( + vm.$el.querySelector('.js-pipeline-graph-job-link').getAttribute('data-original-title'), + ).toEqual( + '<img src=x onerror=alert(document.domain)> - passed', + ); + }); +}); diff --git a/spec/javascripts/pipelines/graph/graph_component_spec.js b/spec/javascripts/pipelines/graph/graph_component_spec.js index 713baa65a17..b6fa4272c8b 100644 --- a/spec/javascripts/pipelines/graph/graph_component_spec.js +++ b/spec/javascripts/pipelines/graph/graph_component_spec.js @@ -1,37 +1,33 @@ import Vue from 'vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; import graphComponent from '~/pipelines/components/graph/graph_component.vue'; import graphJSON from './mock_data'; describe('graph component', () => { - preloadFixtures('static/graph.html.raw'); + const GraphComponent = Vue.extend(graphComponent); + let component; - let GraphComponent; - - beforeEach(() => { - loadFixtures('static/graph.html.raw'); - GraphComponent = Vue.extend(graphComponent); + afterEach(() => { + component.$destroy(); }); describe('while is loading', () => { it('should render a loading icon', () => { - const component = new GraphComponent({ - propsData: { - isLoading: true, - pipeline: {}, - }, - }).$mount('#js-pipeline-graph-vue'); + component = mountComponent(GraphComponent, { + isLoading: true, + pipeline: {}, + }); + expect(component.$el.querySelector('.loading-icon')).toBeDefined(); }); }); describe('with data', () => { it('should render the graph', () => { - const component = new GraphComponent({ - propsData: { - isLoading: false, - pipeline: graphJSON, - }, - }).$mount('#js-pipeline-graph-vue'); + component = mountComponent(GraphComponent, { + isLoading: false, + pipeline: graphJSON, + }); expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true); @@ -52,4 +48,15 @@ describe('graph component', () => { expect(component.$el.querySelector('.stage-column-list')).toBeDefined(); }); }); + + describe('capitalizeStageName', () => { + it('capitalizes and escapes stage name', () => { + component = mountComponent(GraphComponent, { + isLoading: false, + pipeline: graphJSON, + }); + + expect(component.$el.querySelector('.stage-column:nth-child(2) .stage-name').textContent.trim()).toEqual('Deploy <img src=x onerror=alert(document.domain)>'); + }); + }); }); diff --git a/spec/javascripts/pipelines/graph/job_component_spec.js b/spec/javascripts/pipelines/graph/job_component_spec.js index 56476253ad0..59f18d9397d 100644 --- a/spec/javascripts/pipelines/graph/job_component_spec.js +++ b/spec/javascripts/pipelines/graph/job_component_spec.js @@ -3,7 +3,7 @@ import jobComponent from '~/pipelines/components/graph/job_component.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; describe('pipeline graph job component', () => { - let JobComponent; + const JobComponent = Vue.extend(jobComponent); let component; const mockJob = { @@ -26,10 +26,6 @@ describe('pipeline graph job component', () => { }, }; - beforeEach(() => { - JobComponent = Vue.extend(jobComponent); - }); - afterEach(() => { component.$destroy(); }); @@ -165,4 +161,24 @@ describe('pipeline graph job component', () => { expect(component.$el.querySelector(tooltipBoundary)).toBeNull(); }); }); + + describe('tooltipText', () => { + it('escapes job name', () => { + component = mountComponent(JobComponent, { + job: { + id: 4259, + name: '<img src=x onerror=alert(document.domain)>', + status: { + icon: 'icon_status_success', + label: 'success', + tooltip: 'failed', + }, + }, + }); + + expect( + component.$el.querySelector('.js-job-component-tooltip').getAttribute('data-original-title'), + ).toEqual('<img src=x onerror=alert(document.domain)> - failed'); + }); + }); }); diff --git a/spec/javascripts/pipelines/graph/mock_data.js b/spec/javascripts/pipelines/graph/mock_data.js index b2161d54bce..a4a5d78f906 100644 --- a/spec/javascripts/pipelines/graph/mock_data.js +++ b/spec/javascripts/pipelines/graph/mock_data.js @@ -91,7 +91,7 @@ export default { dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=test', }, { - name: 'deploy', + name: 'deploy <img src=x onerror=alert(document.domain)>', title: 'deploy: passed', groups: [ { diff --git a/spec/javascripts/pipelines/graph/stage_column_component_spec.js b/spec/javascripts/pipelines/graph/stage_column_component_spec.js index 9d1e71fd117..f6e6bd3132e 100644 --- a/spec/javascripts/pipelines/graph/stage_column_component_spec.js +++ b/spec/javascripts/pipelines/graph/stage_column_component_spec.js @@ -1,8 +1,11 @@ import Vue from 'vue'; import stageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; describe('stage column component', () => { let component; + const StageColumnComponent = Vue.extend(stageColumnComponent); + const mockJob = { id: 4250, name: 'test', @@ -22,7 +25,6 @@ describe('stage column component', () => { }; beforeEach(() => { - const StageColumnComponent = Vue.extend(stageColumnComponent); const mockJobs = []; for (let i = 0; i < 3; i += 1) { @@ -31,12 +33,10 @@ describe('stage column component', () => { mockJobs.push(mockedJob); } - component = new StageColumnComponent({ - propsData: { - title: 'foo', - jobs: mockJobs, - }, - }).$mount(); + component = mountComponent(StageColumnComponent, { + title: 'foo', + jobs: mockJobs, + }); }); it('should render provided title', () => { @@ -46,4 +46,27 @@ describe('stage column component', () => { it('should render the provided jobs', () => { expect(component.$el.querySelectorAll('.builds-container > ul > li').length).toEqual(3); }); + + describe('jobId', () => { + it('escapes job name', () => { + component = mountComponent(StageColumnComponent, { + jobs: [ + { + id: 4259, + name: '<img src=x onerror=alert(document.domain)>', + status: { + icon: 'icon_status_success', + label: 'success', + tooltip: '<img src=x onerror=alert(document.domain)>', + }, + }, + ], + title: 'test', + }); + + expect( + component.$el.querySelector('.builds-container li').getAttribute('id'), + ).toEqual('ci-badge-<img src=x onerror=alert(document.domain)>'); + }); + }); }); |