diff options
author | Thiago Presa <tpresa@gitlab.com> | 2018-10-01 19:07:29 -0300 |
---|---|---|
committer | Thiago Presa <tpresa@gitlab.com> | 2018-10-01 19:07:29 -0300 |
commit | ec2dccd585d8517a78d94d663c7c25e7c8ac3050 (patch) | |
tree | 152cbd77b4a29703bbdc34a95282e59c583515de | |
parent | 39d2f186a7cceb75ee0b18a9bb21447efb5d4262 (diff) | |
parent | 0ef1060e14b8ac09159e466fe5f4ca3195e080c2 (diff) | |
download | gitlab-ce-ec2dccd585d8517a78d94d663c7c25e7c8ac3050.tar.gz |
Merge branch '11-4-stable-prepare-rc1' into 11-4-stable
227 files changed, 1879 insertions, 213 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 058cacb0774..9c5b40dce21 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -95,7 +95,7 @@ stages: # Skip all jobs except the ones that begin with 'docs/'. # Used for commits including ONLY documentation changes. -# https://docs.gitlab.com/ce/development/writing_documentation.html#testing +# https://docs.gitlab.com/ce/development/documentation/#testing .except-docs: &except-docs except: - /(^docs[\/-].*|.*-docs$)/ diff --git a/CHANGELOG.md b/CHANGELOG.md index e514a42108c..c9ab8599d99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 11.3.1 (2018-09-26) + +### Security (6 changes) + +- Redact confidential events in the API. +- Set timeout for syntax highlighting. +- Sanitize JSON data properly to fix XSS on Issue details page. +- Fix stored XSS in merge requests from imported repository. +- Fix xss vulnerability sourced from package.json. +- Block loopback addresses in UrlBlocker. + + ## 11.3.0 (2018-09-22) ### Security (5 changes, 1 of them is from the community) @@ -249,6 +261,18 @@ entry. - Creates Vue component for artifacts block on job page. +## 11.2.4 (2018-09-26) + +### Security (6 changes) + +- Redact confidential events in the API. +- Set timeout for syntax highlighting. +- Sanitize JSON data properly to fix XSS on Issue details page. +- Fix stored XSS in merge requests from imported repository. +- Fix xss vulnerability sourced from package.json. +- Block loopback addresses in UrlBlocker. + + ## 11.2.3 (2018-08-28) ### Fixed (1 change) @@ -516,6 +540,18 @@ entry. - Moves help_popover component to a common location. +## 11.1.7 (2018-09-26) + +### Security (6 changes) + +- Redact confidential events in the API. +- Set timeout for syntax highlighting. +- Sanitize JSON data properly to fix XSS on Issue details page. +- Fix stored XSS in merge requests from imported repository. +- Fix xss vulnerability sourced from package.json. +- Block loopback addresses in UrlBlocker. + + ## 11.1.6 (2018-08-28) ### Fixed (1 change) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index ee1e4d2aee5..3ba7bd5ba83 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.122.0 +0.123.0 @@ -420,7 +420,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 0.117.0', require: 'gitaly' +gem 'gitaly-proto', '~> 0.118.1', require: 'gitaly' gem 'grpc', '~> 1.11.0' # Locked until https://github.com/google/protobuf/issues/4210 is closed diff --git a/Gemfile.lock b/Gemfile.lock index 2f898de09eb..910e07f4a66 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -274,7 +274,7 @@ GEM gettext_i18n_rails (>= 0.7.1) po_to_json (>= 1.0.0) rails (>= 3.2.0) - gitaly-proto (0.117.0) + gitaly-proto (0.118.1) google-protobuf (~> 3.1) grpc (~> 1.10) github-linguist (5.3.3) @@ -1028,7 +1028,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly-proto (~> 0.117.0) + gitaly-proto (~> 0.118.1) github-linguist (~> 5.3.3) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-gollum-lib (~> 4.2) diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock index 60db7abb76d..5959dc0e114 100644 --- a/Gemfile.rails5.lock +++ b/Gemfile.rails5.lock @@ -277,7 +277,7 @@ GEM gettext_i18n_rails (>= 0.7.1) po_to_json (>= 1.0.0) rails (>= 3.2.0) - gitaly-proto (0.117.0) + gitaly-proto (0.118.1) google-protobuf (~> 3.1) grpc (~> 1.10) github-linguist (5.3.3) @@ -1037,7 +1037,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly-proto (~> 0.117.0) + gitaly-proto (~> 0.118.1) github-linguist (~> 5.3.3) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-gollum-lib (~> 4.2) diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue index d3666cf3980..8b5536200e1 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.vue +++ b/app/assets/javascripts/boards/components/issue_card_inner.vue @@ -149,7 +149,8 @@ <a :href="issue.path" :title="issue.title" - class="js-no-trigger">{{ issue.title }}</a> + class="js-no-trigger" + @mousemove.stop>{{ issue.title }}</a> <span v-if="issueId" class="board-card-number append-right-5" diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue index 715dc1bfb42..a04d09ef374 100644 --- a/app/assets/javascripts/ide/components/ide_status_bar.vue +++ b/app/assets/javascripts/ide/components/ide_status_bar.vue @@ -50,7 +50,9 @@ export default { this.stopPipelinePolling(); }, methods: { - ...mapActions(['setRightPane']), + ...mapActions('rightPane', { + openRightPane: 'open', + }), ...mapActions('pipelines', ['fetchLatestPipeline', 'stopPipelinePolling']), startTimer() { this.intervalId = setInterval(() => { @@ -88,7 +90,7 @@ export default { <button type="button" class="p-0 border-0 h-50" - @click="setRightPane($options.rightSidebarViews.pipelines)" + @click="openRightPane($options.rightSidebarViews.pipelines)" > <ci-icon v-tooltip diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue index 75a9a9e9b8f..bd07f372177 100644 --- a/app/assets/javascripts/ide/components/panes/right.vue +++ b/app/assets/javascripts/ide/components/panes/right.vue @@ -1,5 +1,6 @@ <script> import { mapActions, mapState, mapGetters } from 'vuex'; +import _ from 'underscore'; import { __ } from '~/locale'; import tooltip from '../../../vue_shared/directives/tooltip'; import Icon from '../../../vue_shared/components/icon.vue'; @@ -30,14 +31,10 @@ export default { }, }, computed: { - ...mapState(['rightPane', 'currentMergeRequestId', 'clientsidePreviewEnabled']), + ...mapState(['currentMergeRequestId', 'clientsidePreviewEnabled']), + ...mapState('rightPane', ['isOpen', 'currentView']), ...mapGetters(['packageJson']), - pipelinesActive() { - return ( - this.rightPane === rightSidebarViews.pipelines || - this.rightPane === rightSidebarViews.jobsDetail - ); - }, + ...mapGetters('rightPane', ['isActiveView', 'isAliveView']), showLivePreview() { return this.packageJson && this.clientsidePreviewEnabled; }, @@ -46,22 +43,26 @@ export default { { show: this.currentMergeRequestId, title: __('Merge Request'), - isActive: this.rightPane === rightSidebarViews.mergeRequestInfo, - view: rightSidebarViews.mergeRequestInfo, + views: [ + rightSidebarViews.mergeRequestInfo, + ], icon: 'text-description', }, { show: true, title: __('Pipelines'), - isActive: this.pipelinesActive, - view: rightSidebarViews.pipelines, + views: [ + rightSidebarViews.pipelines, + rightSidebarViews.jobsDetail, + ], icon: 'rocket', }, { show: this.showLivePreview, title: __('Live preview'), - isActive: this.rightPane === rightSidebarViews.clientSidePreview, - view: rightSidebarViews.clientSidePreview, + views: [ + rightSidebarViews.clientSidePreview, + ], icon: 'live-preview', }, ]; @@ -71,13 +72,26 @@ export default { .concat(this.extensionTabs) .filter(tab => tab.show); }, + tabViews() { + return _.flatten(this.tabs.map(tab => tab.views)); + }, + aliveTabViews() { + return this.tabViews.filter(view => this.isAliveView(view.name)); + }, }, methods: { - ...mapActions(['setRightPane']), - clickTab(e, view) { + ...mapActions('rightPane', ['toggleOpen', 'open']), + clickTab(e, tab) { e.target.blur(); - this.setRightPane(view); + if (this.isActiveTab(tab)) { + this.toggleOpen(); + } else { + this.open(tab.views[0]); + } + }, + isActiveTab(tab) { + return tab.views.some(view => this.isActiveView(view.name)); }, }, }; @@ -88,15 +102,22 @@ export default { class="multi-file-commit-panel ide-right-sidebar" > <resizable-panel - v-if="rightPane" + v-show="isOpen" :collapsible="false" :initial-width="350" :min-size="350" - :class="`ide-right-sidebar-${rightPane}`" + :class="`ide-right-sidebar-${currentView}`" side="right" class="multi-file-commit-panel-inner" > - <component :is="rightPane" /> + <div + v-for="tabView in aliveTabViews" + v-show="isActiveView(tabView.name)" + :key="tabView.name" + class="h-100" + > + <component :is="tabView.name" /> + </div> </resizable-panel> <nav class="ide-activity-bar"> <ul class="list-unstyled"> @@ -109,13 +130,13 @@ export default { :title="tab.title" :aria-label="tab.title" :class="{ - active: tab.isActive + active: isActiveTab(tab) && isOpen }" data-container="body" data-placement="left" class="ide-sidebar-link is-right" type="button" - @click="clickTab($event, tab.view)" + @click="clickTab($event, tab)" > <icon :size="16" diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue index d3a73e84cc7..b2599128213 100644 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -22,12 +22,14 @@ export default { }, }, computed: { + ...mapState('rightPane', { + rightPaneIsOpen: 'isOpen', + }), ...mapState([ 'rightPanelCollapsed', 'viewer', 'panelResizing', 'currentActivityView', - 'rightPane', ]), ...mapGetters([ 'currentMergeRequest', @@ -99,7 +101,7 @@ export default { this.editor.updateDimensions(); } }, - rightPane() { + rightPaneIsOpen() { this.editor.updateDimensions(); }, }, diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js index 8caa5b86a9b..3b201f006aa 100644 --- a/app/assets/javascripts/ide/constants.js +++ b/app/assets/javascripts/ide/constants.js @@ -29,10 +29,10 @@ export const diffModes = { }; export const rightSidebarViews = { - pipelines: 'pipelines-list', - jobsDetail: 'jobs-detail', - mergeRequestInfo: 'merge-request-info', - clientSidePreview: 'clientside', + pipelines: { name: 'pipelines-list', keepAlive: true }, + jobsDetail: { name: 'jobs-detail', keepAlive: false }, + mergeRequestInfo: { name: 'merge-request-info', keepAlive: true }, + clientSidePreview: { name: 'clientside', keepAlive: false }, }; export const stageKeys = { diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js index b8b64aead30..e10a132ab4b 100644 --- a/app/assets/javascripts/ide/stores/actions.js +++ b/app/assets/javascripts/ide/stores/actions.js @@ -184,10 +184,6 @@ export const burstUnusedSeal = ({ state, commit }) => { } }; -export const setRightPane = ({ commit }, view) => { - commit(types.SET_RIGHT_PANE, view); -}; - export const setLinks = ({ commit }, links) => commit(types.SET_LINKS, links); export const setErrorMessage = ({ commit }, errorMessage) => diff --git a/app/assets/javascripts/ide/stores/index.js b/app/assets/javascripts/ide/stores/index.js index 877d88bb060..f1f544b52b2 100644 --- a/app/assets/javascripts/ide/stores/index.js +++ b/app/assets/javascripts/ide/stores/index.js @@ -9,6 +9,7 @@ import pipelines from './modules/pipelines'; import mergeRequests from './modules/merge_requests'; import branches from './modules/branches'; import fileTemplates from './modules/file_templates'; +import paneModule from './modules/pane'; Vue.use(Vuex); @@ -24,6 +25,7 @@ export const createStore = () => mergeRequests, branches, fileTemplates: fileTemplates(), + rightPane: paneModule(), }, }); diff --git a/app/assets/javascripts/ide/stores/modules/pane/actions.js b/app/assets/javascripts/ide/stores/modules/pane/actions.js new file mode 100644 index 00000000000..7f5d167a14f --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/pane/actions.js @@ -0,0 +1,30 @@ +import * as types from './mutation_types'; + +export const toggleOpen = ({ dispatch, state }, view) => { + if (state.isOpen) { + dispatch('close'); + } else { + dispatch('open', view); + } +}; + +export const open = ({ commit }, view) => { + commit(types.SET_OPEN, true); + + if (view) { + const { name, keepAlive } = view; + + commit(types.SET_CURRENT_VIEW, name); + + if (keepAlive) { + commit(types.KEEP_ALIVE_VIEW, name); + } + } +}; + +export const close = ({ commit }) => { + commit(types.SET_OPEN, false); +}; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/pane/getters.js b/app/assets/javascripts/ide/stores/modules/pane/getters.js new file mode 100644 index 00000000000..c346cf13689 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/pane/getters.js @@ -0,0 +1,4 @@ +export const isActiveView = state => view => state.currentView === view; + +export const isAliveView = (state, getters) => view => + state.keepAliveViews[view] || (state.isOpen && getters.isActiveView(view)); diff --git a/app/assets/javascripts/ide/stores/modules/pane/index.js b/app/assets/javascripts/ide/stores/modules/pane/index.js new file mode 100644 index 00000000000..5f61cb732c8 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/pane/index.js @@ -0,0 +1,12 @@ +import * as actions from './actions'; +import * as getters from './getters'; +import mutations from './mutations'; +import state from './state'; + +export default () => ({ + namespaced: true, + state: state(), + actions, + getters, + mutations, +}); diff --git a/app/assets/javascripts/ide/stores/modules/pane/mutation_types.js b/app/assets/javascripts/ide/stores/modules/pane/mutation_types.js new file mode 100644 index 00000000000..abdebc4d913 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/pane/mutation_types.js @@ -0,0 +1,3 @@ +export const SET_OPEN = 'SET_OPEN'; +export const SET_CURRENT_VIEW = 'SET_CURRENT_VIEW'; +export const KEEP_ALIVE_VIEW = 'KEEP_ALIVE_VIEW'; diff --git a/app/assets/javascripts/ide/stores/modules/pane/mutations.js b/app/assets/javascripts/ide/stores/modules/pane/mutations.js new file mode 100644 index 00000000000..c16484b4402 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/pane/mutations.js @@ -0,0 +1,19 @@ +import * as types from './mutation_types'; + +export default { + [types.SET_OPEN](state, isOpen) { + Object.assign(state, { + isOpen, + }); + }, + [types.SET_CURRENT_VIEW](state, currentView) { + Object.assign(state, { + currentView, + }); + }, + [types.KEEP_ALIVE_VIEW](state, viewName) { + Object.assign(state.keepAliveViews, { + [viewName]: true, + }); + }, +}; diff --git a/app/assets/javascripts/ide/stores/modules/pane/state.js b/app/assets/javascripts/ide/stores/modules/pane/state.js new file mode 100644 index 00000000000..353065b5735 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/pane/state.js @@ -0,0 +1,5 @@ +export default () => ({ + isOpen: false, + currentView: null, + keepAliveViews: {}, +}); diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js index 3e67b222e66..8fa86995ef0 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js @@ -113,7 +113,7 @@ export const toggleStageCollapsed = ({ commit }, stageId) => export const setDetailJob = ({ commit, dispatch }, job) => { commit(types.SET_DETAIL_JOB, job); - dispatch('setRightPane', job ? rightSidebarViews.jobsDetail : rightSidebarViews.pipelines, { + dispatch('rightPane/open', job ? rightSidebarViews.jobsDetail : rightSidebarViews.pipelines, { root: true, }); }; diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js index 5a7991d2fa7..a5f8098dc17 100644 --- a/app/assets/javascripts/ide/stores/mutation_types.js +++ b/app/assets/javascripts/ide/stores/mutation_types.js @@ -68,8 +68,6 @@ export const UPDATE_TEMP_FLAG = 'UPDATE_TEMP_FLAG'; export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER'; export const BURST_UNUSED_SEAL = 'BURST_UNUSED_SEAL'; -export const SET_RIGHT_PANE = 'SET_RIGHT_PANE'; - export const CLEAR_PROJECTS = 'CLEAR_PROJECTS'; export const RESET_OPEN_FILES = 'RESET_OPEN_FILES'; diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js index 2c8535bda59..78cdfda74f0 100644 --- a/app/assets/javascripts/ide/stores/mutations.js +++ b/app/assets/javascripts/ide/stores/mutations.js @@ -166,11 +166,6 @@ export default { unusedSeal: false, }); }, - [types.SET_RIGHT_PANE](state, view) { - Object.assign(state, { - rightPane: state.rightPane === view ? null : view, - }); - }, [types.SET_LINKS](state, links) { Object.assign(state, { links }); }, diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js index 46b52fa00fc..d400b9831a9 100644 --- a/app/assets/javascripts/ide/stores/state.js +++ b/app/assets/javascripts/ide/stores/state.js @@ -23,7 +23,6 @@ export default () => ({ currentActivityView: activityBarViews.edit, unusedSeal: true, fileFindVisible: false, - rightPane: null, links: {}, errorMessage: null, entryModal: { diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js index 75dfdedcf1b..d08e8ba0c4b 100644 --- a/app/assets/javascripts/issue_show/index.js +++ b/app/assets/javascripts/issue_show/index.js @@ -1,10 +1,11 @@ import Vue from 'vue'; +import sanitize from 'sanitize-html'; import issuableApp from './components/app.vue'; import '../vue_shared/vue_resource_interceptor'; -document.addEventListener('DOMContentLoaded', () => { +export default function initIssueableApp() { const initialDataEl = document.getElementById('js-issuable-app-initial-data'); - const props = JSON.parse(initialDataEl.innerHTML.replace(/"/g, '"')); + const props = JSON.parse(sanitize(initialDataEl.textContent).replace(/"/g, '"')); return new Vue({ el: document.getElementById('js-issuable-app'), @@ -17,4 +18,4 @@ document.addEventListener('DOMContentLoaded', () => { }); }, }); -}); +} diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js index 74b3a515e84..ef65196872c 100644 --- a/app/assets/javascripts/pages/projects/issues/show.js +++ b/app/assets/javascripts/pages/projects/issues/show.js @@ -3,9 +3,10 @@ import Issue from '~/issue'; import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable'; import ZenMode from '~/zen_mode'; import '~/notes/index'; -import '~/issue_show/index'; +import initIssueableApp from '~/issue_show'; export default function () { + initIssueableApp(); new Issue(); // eslint-disable-line no-new new ShortcutsIssuable(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue index f1ef50d0e3d..a07d63a495d 100644 --- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue @@ -1,9 +1,11 @@ <script> +import { Link } from '@gitlab-org/gitlab-ui'; import Icon from '../../icon.vue'; import { numberToHumanSize } from '../../../../lib/utils/number_utils'; export default { components: { + 'gl-link': Link, Icon, }, props: { @@ -37,7 +39,7 @@ export default { ({{ fileSizeReadable }}) </template> </p> - <a + <gl-link :href="path" class="btn btn-default" rel="nofollow" @@ -49,7 +51,7 @@ export default { css-classes="float-left append-right-8" /> {{ __('Download') }} - </a> + </gl-link> </div> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue index 08e102e57c3..ee3157bcb1b 100644 --- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue +++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue @@ -18,12 +18,14 @@ */ +import { Link } from '@gitlab-org/gitlab-ui'; import userAvatarImage from './user_avatar_image.vue'; import tooltip from '../../directives/tooltip'; export default { name: 'UserAvatarLink', components: { + 'gl-link': Link, userAvatarImage, }, directives: { @@ -83,7 +85,7 @@ export default { </script> <template> - <a + <gl-link :href="linkHref" class="user-avatar-link"> <user-avatar-image @@ -99,5 +101,5 @@ export default { :title="tooltipText" :tooltip-placement="tooltipPlacement" >{{ username }}</span> - </a> + </gl-link> </template> diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 28dda65091d..3c9505a21d6 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -324,6 +324,16 @@ img.emoji { word-wrap: break-word; } +.checkbox-icon-inline-wrapper { + .checkbox { + display: inline; + + label { + display: inline; + } + } +} + /** COMMON CLASSES **/ .prepend-top-0 { margin-top: 0; } .prepend-top-2 { margin-top: 2px; } diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index 7d53a631cdf..f10eaedcc04 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -59,7 +59,7 @@ } @include media-breakpoint-up(sm) { - .btn:first-of-type { + .btn:nth-child(1) { margin-left: auto; } } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b87034d10b6..d7dbc712743 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -109,6 +109,15 @@ class ApplicationController < ActionController::Base request.env['rack.session.options'][:expire_after] = Settings.gitlab['unauthenticated_session_expire_delay'] end + def render(*args) + super.tap do + # Set a header for custom error pages to prevent them from being intercepted by gitlab-workhorse + if response.content_type == 'text/html' && (400..599).cover?(response.status) + response.headers['X-GitLab-Custom-Error'] = '1' + end + end + end + protected def append_info_to_payload(payload) diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb index 86583adc6a4..5639402a1e9 100644 --- a/app/controllers/projects/merge_requests/creations_controller.rb +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -106,6 +106,10 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap @commits = set_commits_for_rendering(@merge_request.commits) @commit = @merge_request.diff_head_commit + # FIXME: We have to assign a presenter to another instance variable + # due to class_name checks being made with issuable classes + @mr_presenter = @merge_request.present(current_user: current_user) + @labels = LabelsFinder.new(current_user, project_id: @project.id).execute set_pipeline_variables diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index dfb69de650b..d691744d72a 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -333,6 +333,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo @target_project = @merge_request.target_project @target_branches = @merge_request.target_project.repository.branch_names @noteable = @merge_request + + # FIXME: We have to assign a presenter to another instance variable + # due to class_name checks being made with issuable classes + @mr_presenter = @merge_request.present(current_user: current_user) end def finder_type diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 7352c5e9bec..a9417369ca2 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -16,6 +16,7 @@ class ProjectsController < Projects::ApplicationController before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?] before_action :lfs_blob_ids, only: [:show], if: [:repo_exists?, :project_view_files?] before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export] + before_action :present_project, only: [:edit] # Authorize before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] @@ -433,4 +434,8 @@ class ProjectsController < Projects::ApplicationController def whitelist_query_limiting Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42440') end + + def present_project + @project = @project.present(current_user: current_user) + end end diff --git a/app/finders/events_finder.rb b/app/finders/events_finder.rb index fd7aeca0d8b..2e82bda8730 100644 --- a/app/finders/events_finder.rb +++ b/app/finders/events_finder.rb @@ -12,6 +12,7 @@ class EventsFinder # Arguments: # source - which user or project to looks for events on # current_user - only return events for projects visible to this user + # WARNING: does not consider project feature visibility! # params: # action: string # target_type: string diff --git a/app/finders/joined_groups_finder.rb b/app/finders/joined_groups_finder.rb index 18cc6891ca4..4d8128dd824 100644 --- a/app/finders/joined_groups_finder.rb +++ b/app/finders/joined_groups_finder.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class JoinedGroupsFinder < UnionFinder +class JoinedGroupsFinder def initialize(user) @user = user end @@ -8,19 +8,8 @@ class JoinedGroupsFinder < UnionFinder # Finds the groups of the source user, optionally limited to those visible to # the current user. def execute(current_user = nil) - segments = all_groups(current_user) - - find_union(segments, Group).order_id_desc - end - - private - - def all_groups(current_user) - groups = [] - - groups << @user.authorized_groups.visible_to_user(current_user) if current_user - groups << @user.authorized_groups.public_to_user(current_user) - - groups + @user.authorized_groups + .public_or_visible_to_user(current_user) + .order_id_desc end end diff --git a/app/finders/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb index a4daf5b5841..eeca5026da1 100644 --- a/app/finders/user_recent_events_finder.rb +++ b/app/finders/user_recent_events_finder.rb @@ -3,6 +3,7 @@ # Get user activity feed for projects common for a user and a logged in user # # - current_user: The user viewing the events +# WARNING: does not consider project feature visibility! # - user: The user for which to load the events # - params: # - offset: The page of events to return diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 67af0a4eb98..be085496731 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -3,13 +3,14 @@ module Emails module MergeRequests def new_merge_request_email(recipient_id, merge_request_id, reason = nil) - setup_merge_request_mail(merge_request_id, recipient_id) + setup_merge_request_mail(merge_request_id, recipient_id, present: true) mail_new_thread(@merge_request, merge_request_thread_options(@merge_request.author_id, recipient_id, reason)) end def new_mention_in_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil) - setup_merge_request_mail(merge_request_id, recipient_id) + setup_merge_request_mail(merge_request_id, recipient_id, present: true) + mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason)) end @@ -75,11 +76,16 @@ module Emails private - def setup_merge_request_mail(merge_request_id, recipient_id) + def setup_merge_request_mail(merge_request_id, recipient_id, present: false) @merge_request = MergeRequest.find(merge_request_id) @project = @merge_request.project @target_url = project_merge_request_url(@project, @merge_request) + if present + recipient = User.find(recipient_id) + @mr_presenter = @merge_request.present(current_user: recipient) + end + @sent_notification = SentNotification.record(@merge_request, recipient_id, reply_key) end diff --git a/app/models/blob_viewer/package_json.rb b/app/models/blob_viewer/package_json.rb index d12dd93ce2e..7cae60a74d6 100644 --- a/app/models/blob_viewer/package_json.rb +++ b/app/models/blob_viewer/package_json.rb @@ -33,7 +33,8 @@ module BlobViewer end def homepage - json_data['homepage'] + url = json_data['homepage'] + url if Gitlab::UrlSanitizer.valid?(url) end def npm_url diff --git a/app/models/ci/pipeline_variable.rb b/app/models/ci/pipeline_variable.rb index 017ec0b145a..08514d6af4e 100644 --- a/app/models/ci/pipeline_variable.rb +++ b/app/models/ci/pipeline_variable.rb @@ -10,5 +10,9 @@ module Ci alias_attribute :secret_value, :value validates :key, uniqueness: { scope: :pipeline_id } + + def hook_attrs + { key: key, value: value } + end end end diff --git a/app/models/event.rb b/app/models/event.rb index 596155a9525..2e690f8c013 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -148,6 +148,8 @@ class Event < ActiveRecord::Base end end + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity def visible_to_user?(user = nil) if push? || commit_note? Ability.allowed?(user, :download_code, project) @@ -159,12 +161,18 @@ class Event < ActiveRecord::Base Ability.allowed?(user, :read_issue, note? ? note_target : target) elsif merge_request? || merge_request_note? Ability.allowed?(user, :read_merge_request, note? ? note_target : target) + elsif personal_snippet_note? + Ability.allowed?(user, :read_personal_snippet, note_target) + elsif project_snippet_note? + Ability.allowed?(user, :read_project_snippet, note_target) elsif milestone? - Ability.allowed?(user, :read_project, project) + Ability.allowed?(user, :read_milestone, project) else false # No other event types are visible end end + # rubocop:enable Metrics/PerceivedComplexity + # rubocop:enable Metrics/CyclomaticComplexity def project_name if project @@ -306,6 +314,10 @@ class Event < ActiveRecord::Base note? && target && target.for_snippet? end + def personal_snippet_note? + note? && target && target.for_personal_snippet? + end + def note_target target.noteable end diff --git a/app/models/group.rb b/app/models/group.rb index 62af20d2142..612c546ca57 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -82,8 +82,17 @@ class Group < Namespace User.reference_pattern end - def visible_to_user(user) - where(id: user.authorized_groups.select(:id).reorder(nil)) + # WARNING: This method should never be used on its own + # please do make sure the number of rows you are filtering is small + # enough for this query + def public_or_visible_to_user(user) + return public_to_user unless user + + public_for_user = public_to_user_arel(user) + visible_for_user = visible_to_user_arel(user) + public_or_visible = public_for_user.or(visible_for_user) + + where(public_or_visible) end def select_for_project_authorization @@ -95,6 +104,23 @@ class Group < Namespace super end end + + private + + def public_to_user_arel(user) + self.arel_table[:visibility_level] + .in(Gitlab::VisibilityLevel.levels_for_user(user)) + end + + def visible_to_user_arel(user) + groups_table = self.arel_table + authorized_groups = user.authorized_groups.as('authorized') + + groups_table.project(1) + .from(authorized_groups) + .where(authorized_groups[:id].eq(groups_table[:id])) + .exists + end end # Overrides notification_settings has_many association diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 771a61b090f..68ba4b213b2 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -3,6 +3,16 @@ class WebHook < ActiveRecord::Base include Sortable + attr_encrypted :token, + mode: :per_attribute_iv, + algorithm: 'aes-256-gcm', + key: Settings.attr_encrypted_db_key_base_truncated + + attr_encrypted :url, + mode: :per_attribute_iv, + algorithm: 'aes-256-gcm', + key: Settings.attr_encrypted_db_key_base_truncated + has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent validates :url, presence: true, public_url: { allow_localhost: lambda(&:allow_local_requests?), @@ -27,4 +37,38 @@ class WebHook < ActiveRecord::Base def allow_local_requests? false end + + # In 11.4, the web_hooks table has both `token` and `encrypted_token` fields. + # Ensure that the encrypted version always takes precedence if present. + alias_method :attr_encrypted_token, :token + def token + attr_encrypted_token.presence || read_attribute(:token) + end + + # In 11.4, the web_hooks table has both `token` and `encrypted_token` fields. + # Pending a background migration to encrypt all fields, we should just clear + # the unencrypted value whenever the new value is set. + alias_method :'attr_encrypted_token=', :'token=' + def token=(value) + self.attr_encrypted_token = value + + write_attribute(:token, nil) + end + + # In 11.4, the web_hooks table has both `url` and `encrypted_url` fields. + # Ensure that the encrypted version always takes precedence if present. + alias_method :attr_encrypted_url, :url + def url + attr_encrypted_url.presence || read_attribute(:url) + end + + # In 11.4, the web_hooks table has both `url` and `encrypted_url` fields. + # Pending a background migration to encrypt all fields, we should just clear + # the unencrypted value whenever the new value is set. + alias_method :'attr_encrypted_url=', :'url=' + def url=(value) + self.attr_encrypted_url = value + + write_attribute(:url, nil) + end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index dd5d494997d..0481a4a3d28 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -6,6 +6,7 @@ class MergeRequest < ActiveRecord::Base include Issuable include Noteable include Referable + include Presentable include IgnorableColumn include TimeTrackable include ManualInverseAssociation diff --git a/app/models/repository.rb b/app/models/repository.rb index 4fecdb3c1ad..a3a3ce179fc 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -668,6 +668,14 @@ class Repository end end + def list_last_commits_for_tree(sha, path, offset: 0, limit: 25) + commits = raw_repository.list_last_commits_for_tree(sha, path, offset: offset, limit: limit) + + commits.each do |path, commit| + commits[path] = ::Commit.new(commit, @project) + end + end + def last_commit_for_path(sha, path) commit = raw_repository.last_commit_for_path(sha, path) ::Commit.new(commit, @project) if commit diff --git a/app/models/user.rb b/app/models/user.rb index eeac87e2e52..cd3b1c95b7e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -674,10 +674,12 @@ class User < ActiveRecord::Base # Returns the groups a user has access to, either through a membership or a project authorization def authorized_groups - Group.from_union([ - groups, - authorized_projects.joins(:namespace).select('namespaces.*') - ]) + Group.unscoped do + Group.from_union([ + groups, + authorized_projects.joins(:namespace).select('namespaces.*') + ]) + end end # Returns the groups a user is a member of, either directly or through a parent group diff --git a/app/serializers/diff_line_entity.rb b/app/serializers/diff_line_entity.rb index 2119a1017d3..942714b7787 100644 --- a/app/serializers/diff_line_entity.rb +++ b/app/serializers/diff_line_entity.rb @@ -9,6 +9,6 @@ class DiffLineEntity < Grape::Entity expose :meta_positions, as: :meta_data expose :rich_text do |line| - line.rich_text || CGI.escapeHTML(line.text) + ERB::Util.html_escape(line.rich_text || line.text) end end diff --git a/app/services/clusters/applications/check_installation_progress_service.rb b/app/services/clusters/applications/check_installation_progress_service.rb index 35f5cff0e0c..5017fa093f3 100644 --- a/app/services/clusters/applications/check_installation_progress_service.rb +++ b/app/services/clusters/applications/check_installation_progress_service.rb @@ -14,8 +14,8 @@ module Clusters else check_timeout end - rescue Kubeclient::HttpError => ke - app.make_errored!("Kubernetes error: #{ke.message}") unless app.errored? + rescue Kubeclient::HttpError + app.make_errored!("Kubernetes error") unless app.errored? end private @@ -27,7 +27,7 @@ module Clusters end def on_failed - app.make_errored!(installation_errors || 'Installation silently failed') + app.make_errored!('Installation failed') ensure remove_installation_pod end diff --git a/app/services/clusters/applications/install_service.rb b/app/services/clusters/applications/install_service.rb index 7e3c0e77a83..dd8d2ed5eb6 100644 --- a/app/services/clusters/applications/install_service.rb +++ b/app/services/clusters/applications/install_service.rb @@ -12,10 +12,10 @@ module Clusters ClusterWaitForAppInstallationWorker.perform_in( ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id) - rescue Kubeclient::HttpError => ke - app.make_errored!("Kubernetes error: #{ke.message}") - rescue StandardError => e - app.make_errored!("Can't start installation process. #{e.message}") + rescue Kubeclient::HttpError + app.make_errored!("Kubernetes error.") + rescue StandardError + app.make_errored!("Can't start installation process.") end end end diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb index 9417c63c43a..de6ff92d1da 100644 --- a/app/services/users/build_service.rb +++ b/app/services/users/build_service.rb @@ -55,7 +55,6 @@ module Users :force_random_password, :hide_no_password, :hide_no_ssh_key, - :key_id, :linkedin, :name, :password, @@ -69,7 +68,10 @@ module Users :twitter, :username, :website_url, - :private_profile + :private_profile, + :organization, + :location, + :public_email ] end diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml index dd6a84e503d..5acd45b74a7 100644 --- a/app/views/notify/new_merge_request_email.html.haml +++ b/app/views/notify/new_merge_request_email.html.haml @@ -9,7 +9,7 @@ %p Assignee: #{@merge_request.assignee_name} -= render_if_exists 'notify/merge_request_approvers', merge_request: @merge_request += render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter - if @merge_request.description %div diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb index d5b8f8d764f..754f4bca1cd 100644 --- a/app/views/notify/new_merge_request_email.text.erb +++ b/app/views/notify/new_merge_request_email.text.erb @@ -5,6 +5,6 @@ New Merge Request <%= @merge_request.to_reference %> <%= merge_path_description(@merge_request, 'to') %> Author: <%= @merge_request.author_name %> Assignee: <%= @merge_request.assignee_name %> -<%= render_if_exists 'notify/merge_request_approvers', merge_request: @merge_request %> +<%= render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter %> <%= @merge_request.description %> diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 51f5ecf2166..ea215e3e718 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -109,10 +109,11 @@ = f.text_area :bio, rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters.") %hr %h5= ("Private profile") - - private_profile_label = capture do - = s_("Profiles|Don't display activity-related personal information on your profiles") + .checkbox-icon-inline-wrapper + - private_profile_label = capture do + = s_("Profiles|Don't display activity-related personal information on your profiles") + = f.check_box :private_profile, label: private_profile_label = link_to icon('question-circle'), help_page_path('user/profile/index.md', anchor: 'private-profile') - = f.check_box :private_profile, label: private_profile_label %h5= s_("Profiles|Private contributions") = f.check_box :include_private_contributions, label: 'Include private contributions on my profile' .help-block diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index d104608b2fe..75f35360e5e 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -9,7 +9,7 @@ .project-empty-note-panel %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } .prepend-top-20 - %h4 + %h4.append-bottom-20 = _('The repository for this project is empty') - if @project.can_current_user_push_code? diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index 5a59f956cb5..13b967beba1 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -1,4 +1,4 @@ = form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form common-note-form js-requires-input js-quick-submit' }, data: { markdown_version: @merge_request.cached_markdown_version } do |f| - = render 'shared/issuable/form', f: f, issuable: @merge_request + = render 'shared/issuable/form', f: f, issuable: @merge_request, presenter: @mr_presenter diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml index d5c4134dee2..464f8fa65e9 100644 --- a/app/views/projects/merge_requests/creations/_new_submit.html.haml +++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml @@ -11,7 +11,7 @@ = link_to 'Change branches', mr_change_branches_path(@merge_request) %hr = form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form common-note-form js-requires-input js-quick-submit' } do |f| - = render 'shared/issuable/form', f: f, issuable: @merge_request, commits: @commits + = render 'shared/issuable/form', f: f, issuable: @merge_request, commits: @commits, presenter: @mr_presenter = f.hidden_field :source_project_id = f.hidden_field :source_branch = f.hidden_field :target_project_id diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 5b28a43a361..b33c758b464 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -1,6 +1,7 @@ - form = local_assigns.fetch(:f) - commits = local_assigns[:commits] - project = @target_project || @project +- presenter = local_assigns.fetch(:presenter, nil) = form_errors(issuable) @@ -29,7 +30,7 @@ = render 'shared/issuable/form/metadata', issuable: issuable, form: form -= render_if_exists 'shared/issuable/approvals', issuable: issuable, form: form += render_if_exists 'shared/issuable/approvals', issuable: issuable, presenter: presenter, form: form = render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form diff --git a/changelogs/unreleased/-48445-Issue-card-in-board-view-is-too-sensitive-to-drag-event.yml b/changelogs/unreleased/-48445-Issue-card-in-board-view-is-too-sensitive-to-drag-event.yml new file mode 100644 index 00000000000..27c70318b92 --- /dev/null +++ b/changelogs/unreleased/-48445-Issue-card-in-board-view-is-too-sensitive-to-drag-event.yml @@ -0,0 +1,5 @@ +--- +title: Fix link handling for issue cards to avoid too sensitive drag events. +merge_request: 21910 +author: Johann Hubert Sonntagbauer +type: fixed diff --git a/changelogs/unreleased/23197-add-custom-header-for-error-responses.yml b/changelogs/unreleased/23197-add-custom-header-for-error-responses.yml new file mode 100644 index 00000000000..a5ffc197a0c --- /dev/null +++ b/changelogs/unreleased/23197-add-custom-header-for-error-responses.yml @@ -0,0 +1,6 @@ +--- +title: Set a header for custom error pages to prevent them from being intercepted + by gitlab-workhorse +merge_request: 21870 +author: David Piegza +type: fixed diff --git a/changelogs/unreleased/37433-solve-n-1-in-refs-controller-logs-tree.yml b/changelogs/unreleased/37433-solve-n-1-in-refs-controller-logs-tree.yml new file mode 100644 index 00000000000..04662a7cfe2 --- /dev/null +++ b/changelogs/unreleased/37433-solve-n-1-in-refs-controller-logs-tree.yml @@ -0,0 +1,6 @@ +--- +title: Adds support for Gitaly ListLastCommitsForTree RPC in order to make bulk-fetch + of commits more performant +merge_request: 21921 +author: +type: performance diff --git a/changelogs/unreleased/45453-fix-delete-protected-branch-btn.yml b/changelogs/unreleased/45453-fix-delete-protected-branch-btn.yml new file mode 100644 index 00000000000..64776abdc07 --- /dev/null +++ b/changelogs/unreleased/45453-fix-delete-protected-branch-btn.yml @@ -0,0 +1,5 @@ +--- +title: Fixes modal button alignment +merge_request: 22024 +author: Jacopo Beschi @jacopo-beschi +type: fixed diff --git a/changelogs/unreleased/51021-more-attr-encrypted.yml b/changelogs/unreleased/51021-more-attr-encrypted.yml new file mode 100644 index 00000000000..0e18c59f1bb --- /dev/null +++ b/changelogs/unreleased/51021-more-attr-encrypted.yml @@ -0,0 +1,5 @@ +--- +title: Encrypt webhook tokens and URLs in the database +merge_request: 21645 +author: +type: security diff --git a/changelogs/unreleased/51476-private-profile-help-url-should-not-toggle-checkbox.yml b/changelogs/unreleased/51476-private-profile-help-url-should-not-toggle-checkbox.yml new file mode 100644 index 00000000000..d4e4503508d --- /dev/null +++ b/changelogs/unreleased/51476-private-profile-help-url-should-not-toggle-checkbox.yml @@ -0,0 +1,5 @@ +--- +title: Prevents private profile help link from toggling checkbox +merge_request: 21757 +author: +type: other diff --git a/changelogs/unreleased/add-gl-link-to-download-viewer.yml b/changelogs/unreleased/add-gl-link-to-download-viewer.yml new file mode 100644 index 00000000000..ce3d916f045 --- /dev/null +++ b/changelogs/unreleased/add-gl-link-to-download-viewer.yml @@ -0,0 +1,5 @@ +--- +title: Add link component to DownloadViewer component +merge_request: 21987 +author: George Tsiolis +type: other diff --git a/changelogs/unreleased/add-gl-link-to-user-avatar-link.yml b/changelogs/unreleased/add-gl-link-to-user-avatar-link.yml new file mode 100644 index 00000000000..ef87ef541dd --- /dev/null +++ b/changelogs/unreleased/add-gl-link-to-user-avatar-link.yml @@ -0,0 +1,5 @@ +--- +title: Add link component to UserAvatarLink component +merge_request: 21986 +author: George Tsiolis +type: other diff --git a/changelogs/unreleased/feature-set-public-email-through-api.yml b/changelogs/unreleased/feature-set-public-email-through-api.yml new file mode 100644 index 00000000000..22fae71e9d8 --- /dev/null +++ b/changelogs/unreleased/feature-set-public-email-through-api.yml @@ -0,0 +1,5 @@ +--- +title: Add support for setting the public email through the api +merge_request: 21938 +author: Alexis Reigel +type: added diff --git a/changelogs/unreleased/fix-add-organization-and-location-to-allowed-parameters.yml b/changelogs/unreleased/fix-add-organization-and-location-to-allowed-parameters.yml new file mode 100644 index 00000000000..4d85e1b9af2 --- /dev/null +++ b/changelogs/unreleased/fix-add-organization-and-location-to-allowed-parameters.yml @@ -0,0 +1,5 @@ +--- +title: Allow setting user's organization and location attributes through the API by adding them to the list of allowed parameters +merge_request: 21938 +author: Alexis Reigel +type: fixed diff --git a/changelogs/unreleased/fix-events-finder-incomplete.yml b/changelogs/unreleased/fix-events-finder-incomplete.yml new file mode 100644 index 00000000000..f3a4e421d33 --- /dev/null +++ b/changelogs/unreleased/fix-events-finder-incomplete.yml @@ -0,0 +1,5 @@ +--- +title: Redact confidential events in the API +merge_request: +author: +type: security diff --git a/changelogs/unreleased/improve-empty-project-placeholder.yml b/changelogs/unreleased/improve-empty-project-placeholder.yml new file mode 100644 index 00000000000..11fe21e7710 --- /dev/null +++ b/changelogs/unreleased/improve-empty-project-placeholder.yml @@ -0,0 +1,5 @@ +--- +title: Improve empty project placeholder for non-members and members without write access +merge_request: 21977 +author: George Tsiolis +type: other diff --git a/changelogs/unreleased/lib-api-frozen-string-enable.yml b/changelogs/unreleased/lib-api-frozen-string-enable.yml new file mode 100644 index 00000000000..eb59f0bc2d1 --- /dev/null +++ b/changelogs/unreleased/lib-api-frozen-string-enable.yml @@ -0,0 +1,5 @@ +--- +title: Enable frozen string in lib/api and lib/backup +merge_request: +author: gfyoung +type: performance diff --git a/changelogs/unreleased/pipeline-event-variables.yml b/changelogs/unreleased/pipeline-event-variables.yml new file mode 100644 index 00000000000..90fd964efd5 --- /dev/null +++ b/changelogs/unreleased/pipeline-event-variables.yml @@ -0,0 +1,5 @@ +--- +title: pipeline webhook event now contain pipeline variables +merge_request: 18171 +author: Pierre Tardy +type: added diff --git a/changelogs/unreleased/security-2697-code-highlight-timeout.yml b/changelogs/unreleased/security-2697-code-highlight-timeout.yml new file mode 100644 index 00000000000..66ad9ff822b --- /dev/null +++ b/changelogs/unreleased/security-2697-code-highlight-timeout.yml @@ -0,0 +1,5 @@ +--- +title: Set timeout for syntax highlighting +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-acet-issue-details.yml b/changelogs/unreleased/security-acet-issue-details.yml new file mode 100644 index 00000000000..64147a9d6e8 --- /dev/null +++ b/changelogs/unreleased/security-acet-issue-details.yml @@ -0,0 +1,5 @@ +--- +title: Sanitize JSON data properly to fix XSS on Issue details page +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-fj-stored-xss-in-repository-imports.yml b/changelogs/unreleased/security-fj-stored-xss-in-repository-imports.yml new file mode 100644 index 00000000000..7520aa624c7 --- /dev/null +++ b/changelogs/unreleased/security-fj-stored-xss-in-repository-imports.yml @@ -0,0 +1,5 @@ +--- +title: Fix stored XSS in merge requests from imported repository +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-package-json-xss.yml b/changelogs/unreleased/security-package-json-xss.yml new file mode 100644 index 00000000000..6ab4854e44f --- /dev/null +++ b/changelogs/unreleased/security-package-json-xss.yml @@ -0,0 +1,5 @@ +--- +title: Fix xss vulnerability sourced from package.json +merge_request: +author: +type: security diff --git a/changelogs/unreleased/sh-fix-issue-52009.yml b/changelogs/unreleased/sh-fix-issue-52009.yml new file mode 100644 index 00000000000..fc22a58a66a --- /dev/null +++ b/changelogs/unreleased/sh-fix-issue-52009.yml @@ -0,0 +1,5 @@ +--- +title: Prevent Error 500s with invalid relative links +merge_request: 22001 +author: +type: fixed diff --git a/db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb b/db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb new file mode 100644 index 00000000000..72f5c8d653b --- /dev/null +++ b/db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class AddAttrEncryptedColumnsToWebHook < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :web_hooks, :encrypted_token, :string + add_column :web_hooks, :encrypted_token_iv, :string + + add_column :web_hooks, :encrypted_url, :string + add_column :web_hooks, :encrypted_url_iv, :string + end +end diff --git a/db/post_migrate/20180914162043_encrypt_web_hooks_columns.rb b/db/post_migrate/20180914162043_encrypt_web_hooks_columns.rb new file mode 100644 index 00000000000..05ec4864a9e --- /dev/null +++ b/db/post_migrate/20180914162043_encrypt_web_hooks_columns.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +class EncryptWebHooksColumns < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + BATCH_SIZE = 10000 + RANGE_SIZE = 100 + MIGRATION = 'EncryptColumns' + COLUMNS = [:token, :url] + + WebHook = ::Gitlab::BackgroundMigration::Models::EncryptColumns::WebHook + + disable_ddl_transaction! + + def up + WebHook.each_batch(of: BATCH_SIZE) do |relation, index| + delay = index * 2.minutes + + relation.each_batch(of: RANGE_SIZE) do |relation| + range = relation.pluck('MIN(id)', 'MAX(id)').first + args = [WebHook, COLUMNS, *range] + + BackgroundMigrationWorker.perform_in(delay, MIGRATION, args) + end + end + end + + def down + # noop + end +end diff --git a/db/schema.rb b/db/schema.rb index ecb9d4391d7..13c6d65255e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -2272,6 +2272,10 @@ ActiveRecord::Schema.define(version: 20180917172041) do t.boolean "job_events", default: false, null: false t.boolean "confidential_note_events" t.text "push_events_branch_filter" + t.string "encrypted_token" + t.string "encrypted_token_iv" + t.string "encrypted_url" + t.string "encrypted_url_iv" end add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree diff --git a/doc/api/users.md b/doc/api/users.md index 762ea53edee..3b41e0f7ec6 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -288,6 +288,7 @@ Parameters: - `provider` (optional) - External provider name - `bio` (optional) - User's biography - `location` (optional) - User's location +- `public_email` (optional) - The public email of the user - `admin` (optional) - User is admin - true or false (default) - `can_create_group` (optional) - User can create groups - true or false - `skip_confirmation` (optional) - Skip confirmation - true or false (default) @@ -305,26 +306,27 @@ PUT /users/:id Parameters: -- `email` - Email -- `username` - Username -- `name` - Name -- `password` - Password -- `skype` - Skype ID -- `linkedin` - LinkedIn -- `twitter` - Twitter account -- `website_url` - Website URL -- `organization` - Organization name -- `projects_limit` - Limit projects each user can create -- `extern_uid` - External UID -- `provider` - External provider name -- `bio` - User's biography -- `location` (optional) - User's location -- `admin` (optional) - User is admin - true or false (default) -- `can_create_group` (optional) - User can create groups - true or false +- `email` - Email +- `username` - Username +- `name` - Name +- `password` - Password +- `skype` - Skype ID +- `linkedin` - LinkedIn +- `twitter` - Twitter account +- `website_url` - Website URL +- `organization` - Organization name +- `projects_limit` - Limit projects each user can create +- `extern_uid` - External UID +- `provider` - External provider name +- `bio` - User's biography +- `location` (optional) - User's location +- `public_email` (optional) - The public email of the user +- `admin` (optional) - User is admin - true or false (default) +- `can_create_group` (optional) - User can create groups - true or false - `skip_reconfirmation` (optional) - Skip reconfirmation - true or false (default) -- `external` (optional) - Flags the user as external - true or false(default) -- `avatar` (optional) - Image file for user's avatar -- `private_profile` (optional) - User's profile is private - true or false +- `external` (optional) - Flags the user as external - true or false(default) +- `avatar` (optional) - Image file for user's avatar +- `private_profile` (optional) - User's profile is private - true or false On password update, user will be forced to change it upon next login. Note, at the moment this method does only return a `404` error, diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md index 7990917f809..df83f30fbb7 100644 --- a/doc/ci/interactive_web_terminal/index.md +++ b/doc/ci/interactive_web_terminal/index.md @@ -17,7 +17,7 @@ Two things need to be configured for the interactive web terminal to work: - The Runner needs to have [`[session_server]` configured properly][session-server] -- Web terminals need to be +- If you are using a reverse proxy with your GitLab instance, web terminals need to be [enabled](../../administration/integration/terminal.md#enabling-and-disabling-terminal-support) ## Debugging a running job diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md index 5a38f5d8aed..7d12cd8f7c2 100644 --- a/doc/user/project/integrations/webhooks.md +++ b/doc/user/project/integrations/webhooks.md @@ -943,7 +943,13 @@ X-Gitlab-Event: Pipeline Hook ], "created_at": "2016-08-12 15:23:28 UTC", "finished_at": "2016-08-12 15:26:29 UTC", - "duration": 63 + "duration": 63, + "variables": [ + { + "key": "NESTOR_PROD_ENVIRONMENT", + "value": "us-west-1" + } + ] }, "user":{ "name": "Administrator", diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index 18063fb20a2..cecff6d3b81 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class AccessRequests < Grape::API include PaginationParams diff --git a/lib/api/api.rb b/lib/api/api.rb index e89d9337853..06c8b48b8cc 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class API < Grape::API include APIGuard diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index 8ee7987cfff..61357b3f1d6 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Guard API with OAuth 2.0 Access Token require 'rack/oauth2' diff --git a/lib/api/applications.rb b/lib/api/applications.rb index b122cdefe4e..f29cd7fc003 100644 --- a/lib/api/applications.rb +++ b/lib/api/applications.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API # External applications API class Applications < Grape::API diff --git a/lib/api/avatar.rb b/lib/api/avatar.rb index 70219bc8ea0..0f14d003065 100644 --- a/lib/api/avatar.rb +++ b/lib/api/avatar.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Avatar < Grape::API resource :avatar do diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb index e334af22183..c2abf9155f3 100644 --- a/lib/api/award_emoji.rb +++ b/lib/api/award_emoji.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class AwardEmoji < Grape::API include PaginationParams diff --git a/lib/api/badges.rb b/lib/api/badges.rb index 8ceffe9c5ef..ab670988f47 100644 --- a/lib/api/badges.rb +++ b/lib/api/badges.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Badges < Grape::API include PaginationParams diff --git a/lib/api/boards.rb b/lib/api/boards.rb index 0f89414148b..c80e1c57864 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Boards < Grape::API include BoardsResponses diff --git a/lib/api/boards_responses.rb b/lib/api/boards_responses.rb index 3322b37c6ff..86d9b24802f 100644 --- a/lib/api/boards_responses.rb +++ b/lib/api/boards_responses.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module BoardsResponses extend ActiveSupport::Concern diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 5d106ed93a0..2735d410c8e 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'mime/types' module API diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb index d7138b2f2fe..19148758fc5 100644 --- a/lib/api/broadcast_messages.rb +++ b/lib/api/broadcast_messages.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class BroadcastMessages < Grape::API include PaginationParams diff --git a/lib/api/circuit_breakers.rb b/lib/api/circuit_breakers.rb index c13154dc0ec..6eddc5e5b61 100644 --- a/lib/api/circuit_breakers.rb +++ b/lib/api/circuit_breakers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class CircuitBreakers < Grape::API before { authenticated_as_admin! } diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 8e6f706afd4..99553d993ca 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'mime/types' module API diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 5aeffc8fb99..f0db1318146 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'mime/types' module API diff --git a/lib/api/custom_attributes_endpoints.rb b/lib/api/custom_attributes_endpoints.rb index b5864665cc3..2149e04451e 100644 --- a/lib/api/custom_attributes_endpoints.rb +++ b/lib/api/custom_attributes_endpoints.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module CustomAttributesEndpoints extend ActiveSupport::Concern diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index 501e9f64db0..ce35720d408 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class DeployKeys < Grape::API include PaginationParams diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb index b7892599295..6747e2e5005 100644 --- a/lib/api/deployments.rb +++ b/lib/api/deployments.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API # Deployments RESTful API endpoints class Deployments < Grape::API diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb index 88668992215..39c6d28391d 100644 --- a/lib/api/discussions.rb +++ b/lib/api/discussions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Discussions < Grape::API include PaginationParams diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 12c4340c1ba..a78a93cbfd9 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module Entities class WikiPageBasic < Grape::Entity diff --git a/lib/api/environments.rb b/lib/api/environments.rb index fa828f43001..c64217a6977 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API # Environments RESTfull API endpoints class Environments < Grape::API diff --git a/lib/api/events.rb b/lib/api/events.rb index dfe0e81af26..6e0b508be19 100644 --- a/lib/api/events.rb +++ b/lib/api/events.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Events < Grape::API include PaginationParams @@ -16,12 +18,27 @@ module API desc: 'Return events sorted in ascending and descending order' end + RedactedEvent = OpenStruct.new(target_title: 'Confidential event').freeze + + def redact_events(events) + events.map do |event| + if event.visible_to_user?(current_user) + event + else + RedactedEvent + end + end + end + # rubocop: disable CodeReuse/ActiveRecord - def present_events(events) + def present_events(events, redact: true) events = events.reorder(created_at: params[:sort]) .with_associations - present paginate(events), with: Entities::Event + events = paginate(events) + events = redact_events(events) if redact + + present events, with: Entities::Event end # rubocop: enable CodeReuse/ActiveRecord end @@ -44,7 +61,8 @@ module API events = EventsFinder.new(params.merge(source: current_user, current_user: current_user)).execute.preload(:author, :target) - present_events(events) + # Since we're viewing our own events, redaction is unnecessary + present_events(events, redact: false) end # rubocop: enable CodeReuse/ActiveRecord end diff --git a/lib/api/features.rb b/lib/api/features.rb index 79be8c1903e..6f2422af13a 100644 --- a/lib/api/features.rb +++ b/lib/api/features.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Features < Grape::API before { authenticated_as_admin! } diff --git a/lib/api/files.rb b/lib/api/files.rb index ac02488d30c..bcd2cd48a45 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Files < Grape::API FILE_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(file_path: API::NO_SLASH_URL_PART_REGEX) diff --git a/lib/api/group_boards.rb b/lib/api/group_boards.rb index 3832cdc10a8..dc30e868e2e 100644 --- a/lib/api/group_boards.rb +++ b/lib/api/group_boards.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class GroupBoards < Grape::API include BoardsResponses diff --git a/lib/api/group_milestones.rb b/lib/api/group_milestones.rb index 4b4352c2b27..b36436dbf43 100644 --- a/lib/api/group_milestones.rb +++ b/lib/api/group_milestones.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class GroupMilestones < Grape::API include MilestoneResponses diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb index b6610dd04b3..ae7241e9a30 100644 --- a/lib/api/group_variables.rb +++ b/lib/api/group_variables.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class GroupVariables < Grape::API include PaginationParams diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 018ca72c32a..64b998ab455 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Groups < Grape::API include PaginationParams diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 85e3e06e4fd..a7ba8066233 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module Helpers include Gitlab::Utils @@ -381,9 +383,10 @@ module API # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60 trace = exception.backtrace - message = "\n#{exception.class} (#{exception.message}):\n" + message = ["\n#{exception.class} (#{exception.message}):\n"] message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) message << " " << trace.join("\n ") + message = message.join API.logger.add Logger::FATAL, message diff --git a/lib/api/helpers/badges_helpers.rb b/lib/api/helpers/badges_helpers.rb index 1f8afbf3c90..46ce5b4e7b5 100644 --- a/lib/api/helpers/badges_helpers.rb +++ b/lib/api/helpers/badges_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module Helpers module BadgesHelpers diff --git a/lib/api/helpers/common_helpers.rb b/lib/api/helpers/common_helpers.rb index 9993caa5249..7551ca50a7f 100644 --- a/lib/api/helpers/common_helpers.rb +++ b/lib/api/helpers/common_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module Helpers module CommonHelpers diff --git a/lib/api/helpers/custom_attributes.rb b/lib/api/helpers/custom_attributes.rb index 3bbe827967e..88208226c40 100644 --- a/lib/api/helpers/custom_attributes.rb +++ b/lib/api/helpers/custom_attributes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module Helpers module CustomAttributes diff --git a/lib/api/helpers/custom_validators.rb b/lib/api/helpers/custom_validators.rb index dd4f6c41131..23b1cd1ad45 100644 --- a/lib/api/helpers/custom_validators.rb +++ b/lib/api/helpers/custom_validators.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module Helpers module CustomValidators diff --git a/lib/api/helpers/headers_helpers.rb b/lib/api/helpers/headers_helpers.rb index c9c44e3c218..7553af9d156 100644 --- a/lib/api/helpers/headers_helpers.rb +++ b/lib/api/helpers/headers_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module Helpers module HeadersHelpers diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index 83151be82ad..4eaaca96b49 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module Helpers module InternalHelpers diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index 518aaa62aef..73d58ee7f37 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # rubocop:disable GitlabSecurity/PublicSend module API diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb index 7b1f5c2584b..216b2c45741 100644 --- a/lib/api/helpers/notes_helpers.rb +++ b/lib/api/helpers/notes_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module Helpers module NotesHelpers diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb index 50bcd4e0437..d311cbb5f7e 100644 --- a/lib/api/helpers/pagination.rb +++ b/lib/api/helpers/pagination.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module Helpers module Pagination diff --git a/lib/api/helpers/project_snapshots_helpers.rb b/lib/api/helpers/project_snapshots_helpers.rb index 94798a8cb51..1b5dc281e38 100644 --- a/lib/api/helpers/project_snapshots_helpers.rb +++ b/lib/api/helpers/project_snapshots_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module Helpers module ProjectSnapshotsHelpers diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index 98672f2f765..e6a72b949f9 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module Helpers module ProjectsHelpers diff --git a/lib/api/helpers/related_resources_helpers.rb b/lib/api/helpers/related_resources_helpers.rb index bc7333ca4b3..793ae11b41d 100644 --- a/lib/api/helpers/related_resources_helpers.rb +++ b/lib/api/helpers/related_resources_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module Helpers module RelatedResourcesHelpers diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb index 61eb88d3331..45d0343bc89 100644 --- a/lib/api/helpers/runner.rb +++ b/lib/api/helpers/runner.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module Helpers module Runner diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 71b87f60bf6..6a264c4cc6d 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API # Internal access API class Internal < Grape::API diff --git a/lib/api/issues.rb b/lib/api/issues.rb index bcb03a0b540..25d78053c88 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Issues < Grape::API include PaginationParams diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb index ab4203c4e25..2229cbcd9d4 100644 --- a/lib/api/job_artifacts.rb +++ b/lib/api/job_artifacts.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class JobArtifacts < Grape::API before { authenticate_non_get! } diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index 27ffd42834c..63fab6b0abb 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Jobs < Grape::API include PaginationParams diff --git a/lib/api/keys.rb b/lib/api/keys.rb index fd93f797f72..d5280a0035d 100644 --- a/lib/api/keys.rb +++ b/lib/api/keys.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API # Keys API class Keys < Grape::API diff --git a/lib/api/labels.rb b/lib/api/labels.rb index 98c9818db39..28555454307 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Labels < Grape::API include PaginationParams diff --git a/lib/api/lint.rb b/lib/api/lint.rb index d202eaa4c49..0342a4b6654 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Lint < Grape::API namespace :ci do diff --git a/lib/api/markdown.rb b/lib/api/markdown.rb index 5d55224c1a7..50d8a1ac596 100644 --- a/lib/api/markdown.rb +++ b/lib/api/markdown.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Markdown < Grape::API params do diff --git a/lib/api/members.rb b/lib/api/members.rb index 4d8e23dee91..a8f67be3463 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Members < Grape::API include PaginationParams diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb index 95ef8f42954..e4fb890960a 100644 --- a/lib/api/merge_request_diffs.rb +++ b/lib/api/merge_request_diffs.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API # MergeRequestDiff API class MergeRequestDiffs < Grape::API diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index cad38271cbb..764905ca00f 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class MergeRequests < Grape::API include PaginationParams diff --git a/lib/api/milestone_responses.rb b/lib/api/milestone_responses.rb index a8eb137e46a..a0ca39b69d4 100644 --- a/lib/api/milestone_responses.rb +++ b/lib/api/milestone_responses.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module MilestoneResponses extend ActiveSupport::Concern diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb index 32b77aedba8..76639fbb031 100644 --- a/lib/api/namespaces.rb +++ b/lib/api/namespaces.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Namespaces < Grape::API include PaginationParams diff --git a/lib/api/notes.rb b/lib/api/notes.rb index dc9373bb3c2..9f323b87baf 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Notes < Grape::API include PaginationParams diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb index bf0d6b9e434..4d9a4629268 100644 --- a/lib/api/notification_settings.rb +++ b/lib/api/notification_settings.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API # notification_settings API class NotificationSettings < Grape::API diff --git a/lib/api/pages_domains.rb b/lib/api/pages_domains.rb index 8730c91b426..c9ad47e0f0d 100644 --- a/lib/api/pages_domains.rb +++ b/lib/api/pages_domains.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class PagesDomains < Grape::API include PaginationParams diff --git a/lib/api/pagination_params.rb b/lib/api/pagination_params.rb index f566eb3ed2b..ae03595eb25 100644 --- a/lib/api/pagination_params.rb +++ b/lib/api/pagination_params.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API # Concern for declare pagination params. # diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb index 5bd1ce8c5e1..ed0a38b9d70 100644 --- a/lib/api/pipeline_schedules.rb +++ b/lib/api/pipeline_schedules.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class PipelineSchedules < Grape::API include PaginationParams diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index 5cce96d5ae7..1cfb982c04b 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Pipelines < Grape::API include PaginationParams diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb index 8562ae6d737..e34ed0bdb44 100644 --- a/lib/api/project_export.rb +++ b/lib/api/project_export.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class ProjectExport < Grape::API before do diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 0fb454bc22e..4af4c6ac593 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class ProjectHooks < Grape::API include PaginationParams diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb index bc5152e539f..cbfa0c5bc1c 100644 --- a/lib/api/project_import.rb +++ b/lib/api/project_import.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class ProjectImport < Grape::API include PaginationParams diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb index 72cf32d7717..c7137ba5217 100644 --- a/lib/api/project_milestones.rb +++ b/lib/api/project_milestones.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class ProjectMilestones < Grape::API include PaginationParams diff --git a/lib/api/project_snapshots.rb b/lib/api/project_snapshots.rb index 71005acc587..175fbb2ce92 100644 --- a/lib/api/project_snapshots.rb +++ b/lib/api/project_snapshots.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class ProjectSnapshots < Grape::API helpers ::API::Helpers::ProjectSnapshotsHelpers diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index 1ef176b1320..f3a1b73b153 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class ProjectSnippets < Grape::API include PaginationParams diff --git a/lib/api/projects.rb b/lib/api/projects.rb index ee426f39523..00bad49ebdc 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_dependency 'declarative_policy' module API diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb index 9fd79c491c2..8edcfea7c93 100644 --- a/lib/api/projects_relation_builder.rb +++ b/lib/api/projects_relation_builder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module ProjectsRelationBuilder extend ActiveSupport::Concern diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb index 804f6fa9b73..47752f40e58 100644 --- a/lib/api/protected_branches.rb +++ b/lib/api/protected_branches.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class ProtectedBranches < Grape::API include PaginationParams diff --git a/lib/api/protected_tags.rb b/lib/api/protected_tags.rb index e406344e42d..ed1c5f0cc05 100644 --- a/lib/api/protected_tags.rb +++ b/lib/api/protected_tags.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class ProtectedTags < Grape::API include PaginationParams diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 79736107bbb..5125f302fbb 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'mime/types' module API diff --git a/lib/api/runner.rb b/lib/api/runner.rb index b2d46cef23c..d8768a54986 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Runner < Grape::API helpers ::API::Helpers::Runner diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 9bcdfc8cb15..60868821810 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Runners < Grape::API include PaginationParams diff --git a/lib/api/scope.rb b/lib/api/scope.rb index d5165b2e482..707775e5d15 100644 --- a/lib/api/scope.rb +++ b/lib/api/scope.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Encapsulate a scope used for authorization, such as `api`, or `read_user` module API class Scope diff --git a/lib/api/search.rb b/lib/api/search.rb index 37fbabe419c..12d97dcfe7f 100644 --- a/lib/api/search.rb +++ b/lib/api/search.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Search < Grape::API include PaginationParams diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 8d71bd9dff1..edbd134822c 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Settings < Grape::API before { authenticated_as_admin! } diff --git a/lib/api/sidekiq_metrics.rb b/lib/api/sidekiq_metrics.rb index 11f2b40269a..daa9598a204 100644 --- a/lib/api/sidekiq_metrics.rb +++ b/lib/api/sidekiq_metrics.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'sidekiq/api' module API diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb index 6352a9c8742..f1786c15f4f 100644 --- a/lib/api/snippets.rb +++ b/lib/api/snippets.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API # Snippets API class Snippets < Grape::API diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb index b3e1e23031a..077e9373ac4 100644 --- a/lib/api/subscriptions.rb +++ b/lib/api/subscriptions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Subscriptions < Grape::API before { authenticate! } diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb index 07552aa18e8..51fae0e54aa 100644 --- a/lib/api/system_hooks.rb +++ b/lib/api/system_hooks.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class SystemHooks < Grape::API include PaginationParams diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 5e0afc6a7e4..f739eacf9ba 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Tags < Grape::API include PaginationParams diff --git a/lib/api/templates.rb b/lib/api/templates.rb index 7bf0e0f5934..8ff3b2ac33c 100644 --- a/lib/api/templates.rb +++ b/lib/api/templates.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Templates < Grape::API include PaginationParams diff --git a/lib/api/time_tracking_endpoints.rb b/lib/api/time_tracking_endpoints.rb index 2bb451dea89..93fe06bec27 100644 --- a/lib/api/time_tracking_endpoints.rb +++ b/lib/api/time_tracking_endpoints.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API module TimeTrackingEndpoints extend ActiveSupport::Concern diff --git a/lib/api/todos.rb b/lib/api/todos.rb index c6dbcf84e3a..ed2cf2cc31b 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Todos < Grape::API include PaginationParams diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index 2339505b05b..f784c857883 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Triggers < Grape::API include PaginationParams diff --git a/lib/api/users.rb b/lib/api/users.rb index ac09ca7f7b7..11a7f4ef64d 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Users < Grape::API include PaginationParams @@ -42,12 +44,12 @@ module API optional :provider, type: String, desc: 'The external provider' optional :bio, type: String, desc: 'The biography of the user' optional :location, type: String, desc: 'The location of the user' + optional :public_email, type: String, desc: 'The public email of the user' optional :admin, type: Boolean, desc: 'Flag indicating the user is an administrator' optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups' optional :external, type: Boolean, desc: 'Flag indicating the user is an external user' optional :avatar, type: File, desc: 'Avatar image for user' optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile' - optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user' all_or_none_of :extern_uid, :provider end diff --git a/lib/api/variables.rb b/lib/api/variables.rb index 50e6fa6bcdf..c844ba321ed 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Variables < Grape::API include PaginationParams diff --git a/lib/api/version.rb b/lib/api/version.rb index 3b10bfa6a7d..74cd857f447 100644 --- a/lib/api/version.rb +++ b/lib/api/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Version < Grape::API before { authenticate! } diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb index e86ebc573f2..6e1d4eb335f 100644 --- a/lib/api/wikis.rb +++ b/lib/api/wikis.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module API class Wikis < Grape::API helpers do diff --git a/lib/backup/artifacts.rb b/lib/backup/artifacts.rb index 45a935ab352..33658ae225f 100644 --- a/lib/backup/artifacts.rb +++ b/lib/backup/artifacts.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'backup/files' module Backup diff --git a/lib/backup/builds.rb b/lib/backup/builds.rb index adf85ca4719..5e795a449de 100644 --- a/lib/backup/builds.rb +++ b/lib/backup/builds.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'backup/files' module Backup diff --git a/lib/backup/database.rb b/lib/backup/database.rb index 086ca5986bd..e6bf3d1856f 100644 --- a/lib/backup/database.rb +++ b/lib/backup/database.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'yaml' module Backup diff --git a/lib/backup/files.rb b/lib/backup/files.rb index e287aa1e392..0032ae8f84b 100644 --- a/lib/backup/files.rb +++ b/lib/backup/files.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'open3' require_relative 'helper' diff --git a/lib/backup/helper.rb b/lib/backup/helper.rb index 54b9ce10b4d..22f00aef569 100644 --- a/lib/backup/helper.rb +++ b/lib/backup/helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Backup module Helper def access_denied_error(path) diff --git a/lib/backup/lfs.rb b/lib/backup/lfs.rb index 185ff8ae6bd..0dfe56e214f 100644 --- a/lib/backup/lfs.rb +++ b/lib/backup/lfs.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'backup/files' module Backup diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index a3641505196..5d4a7efc456 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Backup class Manager ARCHIVES_TO_BACKUP = %w[uploads builds artifacts pages lfs registry].freeze diff --git a/lib/backup/pages.rb b/lib/backup/pages.rb index 542e35a7c7c..a4be728df08 100644 --- a/lib/backup/pages.rb +++ b/lib/backup/pages.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'backup/files' module Backup diff --git a/lib/backup/registry.rb b/lib/backup/registry.rb index 35821805797..d16ed2facf1 100644 --- a/lib/backup/registry.rb +++ b/lib/backup/registry.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'backup/files' module Backup diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index 906ed498026..c8a5377bfa0 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'yaml' module Backup diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb index 49b117a7ee3..9577df2634a 100644 --- a/lib/backup/uploads.rb +++ b/lib/backup/uploads.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'backup/files' module Backup diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb index 8e838d04bad..7acbc933adc 100644 --- a/lib/banzai/filter/relative_link_filter.rb +++ b/lib/banzai/filter/relative_link_filter.rb @@ -60,7 +60,11 @@ module Banzai path_parts.unshift(relative_url_root, project.full_path) end - path = Addressable::URI.escape(File.join(*path_parts)) + begin + path = Addressable::URI.escape(File.join(*path_parts)) + rescue Addressable::URI::InvalidURIError + return + end html_attr.value = if context[:only_path] diff --git a/lib/gitlab/background_migration/encrypt_columns.rb b/lib/gitlab/background_migration/encrypt_columns.rb new file mode 100644 index 00000000000..0d333e47e7b --- /dev/null +++ b/lib/gitlab/background_migration/encrypt_columns.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # EncryptColumn migrates data from an unencrypted column - `foo`, say - to + # an encrypted column - `encrypted_foo`, say. + # + # For this background migration to work, the table that is migrated _has_ to + # have an `id` column as the primary key. Additionally, the encrypted column + # should be managed by attr_encrypted, and map to an attribute with the same + # name as the unencrypted column (i.e., the unencrypted column should be + # shadowed). + # + # To avoid depending on a particular version of the model in app/, add a + # model to `lib/gitlab/background_migration/models/encrypt_columns` and use + # it in the migration that enqueues the jobs, so code can be shared. + class EncryptColumns + def perform(model, attributes, from, to) + model = model.constantize if model.is_a?(String) + attributes = expand_attributes(model, Array(attributes).map(&:to_sym)) + + model.transaction do + # Use SELECT ... FOR UPDATE to prevent the value being changed while + # we are encrypting it + relation = model.where(id: from..to).lock + + relation.each do |instance| + encrypt!(instance, attributes) + end + end + end + + private + + # Build a hash of { attribute => encrypted column name } + def expand_attributes(klass, attributes) + expanded = attributes.flat_map do |attribute| + attr_config = klass.encrypted_attributes[attribute] + crypt_column_name = attr_config&.fetch(:attribute) + + raise "Couldn't determine encrypted column for #{klass}##{attribute}" if + crypt_column_name.nil? + + [attribute, crypt_column_name] + end + + Hash[*expanded] + end + + # Generate ciphertext for each column and update the database + def encrypt!(instance, attributes) + to_clear = attributes + .map { |plain, crypt| apply_attribute!(instance, plain, crypt) } + .compact + .flat_map { |plain| [plain, nil] } + + to_clear = Hash[*to_clear] + + if instance.changed? + instance.save! + instance.update_columns(to_clear) + end + end + + def apply_attribute!(instance, plain_column, crypt_column) + plaintext = instance[plain_column] + ciphertext = instance[crypt_column] + + # No need to do anything if the plaintext is nil, or an encrypted + # value already exists + return nil unless plaintext.present? && !ciphertext.present? + + # attr_encrypted will calculate and set the expected value for us + instance.public_send("#{plain_column}=", plaintext) # rubocop:disable GitlabSecurity/PublicSend + + plain_column + end + end + end +end diff --git a/lib/gitlab/background_migration/models/encrypt_columns/web_hook.rb b/lib/gitlab/background_migration/models/encrypt_columns/web_hook.rb new file mode 100644 index 00000000000..bb76eb8ed48 --- /dev/null +++ b/lib/gitlab/background_migration/models/encrypt_columns/web_hook.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + module Models + module EncryptColumns + # This model is shared between synchronous and background migrations to + # encrypt the `token` and `url` columns + class WebHook < ActiveRecord::Base + include ::EachBatch + + self.table_name = 'web_hooks' + self.inheritance_column = :_type_disabled + + attr_encrypted :token, + mode: :per_attribute_iv, + algorithm: 'aes-256-gcm', + key: Settings.attr_encrypted_db_key_base_truncated + + attr_encrypted :url, + mode: :per_attribute_iv, + algorithm: 'aes-256-gcm', + key: Settings.attr_encrypted_db_key_base_truncated + end + end + end + end +end diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb index eb246d393a1..f382992cb0a 100644 --- a/lib/gitlab/data_builder/pipeline.rb +++ b/lib/gitlab/data_builder/pipeline.rb @@ -26,7 +26,8 @@ module Gitlab stages: pipeline.stages_names, created_at: pipeline.created_at, finished_at: pipeline.finished_at, - duration: pipeline.duration + duration: pipeline.duration, + variables: pipeline.variables.map(&:hook_attrs) } end diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb index 1f012043e56..a605ddb5c33 100644 --- a/lib/gitlab/diff/highlight.rb +++ b/lib/gitlab/diff/highlight.rb @@ -24,7 +24,7 @@ module Gitlab # ignore highlighting for "match" lines next diff_line if diff_line.meta? - rich_line = highlight_line(diff_line) || diff_line.text + rich_line = highlight_line(diff_line) || ERB::Util.html_escape(diff_line.text) if line_inline_diffs = inline_diffs[i] begin diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 3d5a63bdbac..45d42c7078f 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -953,6 +953,12 @@ module Gitlab end end + def list_last_commits_for_tree(sha, path, offset: 0, limit: 25) + wrapped_gitaly_errors do + gitaly_commit_client.list_last_commits_for_tree(sha, path, offset: offset, limit: limit) + end + end + def last_commit_for_path(sha, path) wrapped_gitaly_errors do gitaly_commit_client.last_commit_for_path(sha, path) diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index 07e5e204b68..085b2a127a5 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -148,6 +148,24 @@ module Gitlab GitalyClient.call(@repository.storage, :commit_service, :count_commits, request, timeout: GitalyClient.medium_timeout).count end + def list_last_commits_for_tree(revision, path, offset: 0, limit: 25) + request = Gitaly::ListLastCommitsForTreeRequest.new( + repository: @gitaly_repo, + revision: encode_binary(revision), + path: encode_binary(path.to_s), + offset: offset, + limit: limit + ) + + response = GitalyClient.call(@repository.storage, :commit_service, :list_last_commits_for_tree, request, timeout: GitalyClient.medium_timeout) + + response.each_with_object({}) do |gitaly_response, hsh| + gitaly_response.commits.each do |commit_for_tree| + hsh[commit_for_tree.path] = Gitlab::Git::Commit.new(@repository, commit_for_tree.commit) + end + end + end + def last_commit_for_path(revision, path) request = Gitaly::LastCommitForPathRequest.new( repository: @gitaly_repo, diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb index 5408a1a6838..0b6cc893db1 100644 --- a/lib/gitlab/highlight.rb +++ b/lib/gitlab/highlight.rb @@ -1,5 +1,8 @@ module Gitlab class Highlight + TIMEOUT_BACKGROUND = 30.seconds + TIMEOUT_FOREGROUND = 3.seconds + def self.highlight(blob_name, blob_content, repository: nil, plain: false) new(blob_name, blob_content, repository: repository) .highlight(blob_content, continue: false, plain: plain) @@ -51,11 +54,20 @@ module Gitlab end def highlight_rich(text, continue: true) - @formatter.format(lexer.lex(text, continue: continue), tag: lexer.tag).html_safe + tag = lexer.tag + tokens = lexer.lex(text, continue: continue) + Timeout.timeout(timeout_time) { @formatter.format(tokens, tag: tag).html_safe } + rescue Timeout::Error => e + Gitlab::Sentry.track_exception(e) + highlight_plain(text) rescue highlight_plain(text) end + def timeout_time + Sidekiq.server? ? TIMEOUT_BACKGROUND : TIMEOUT_FOREGROUND + end + def link_dependencies(text, highlighted_text) Gitlab::DependencyLinker.link(blob_name, text, highlighted_text) end diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index a19b3c88627..2bed470514b 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -147,6 +147,12 @@ excluded_attributes: - :reference - :reference_html - :epic_id + hooks: + - :token + - :encrypted_token + - :encrypted_token_iv + - :encrypted_url + - :encrypted_url_iv methods: labels: diff --git a/lib/gitlab/tree_summary.rb b/lib/gitlab/tree_summary.rb index b05d408b1c0..c2955cd374c 100644 --- a/lib/gitlab/tree_summary.rb +++ b/lib/gitlab/tree_summary.rb @@ -73,25 +73,29 @@ module Gitlab end def fill_last_commits!(entries) - # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37433 - Gitlab::GitalyClient.allow_n_plus_1_calls do - entries.each do |entry| - raw_commit = repository.last_commit_for_path(commit.id, entry_path(entry)) + # Ensure the path is in "path/" format + ensured_path = + if path + File.join(*[path, ""]) + end + + commits_hsh = repository.list_last_commits_for_tree(commit.id, ensured_path, offset: offset, limit: limit) - if raw_commit - commit = resolve_commit(raw_commit) + entries.each do |entry| + path_key = entry_path(entry) + commit = cache_commit(commits_hsh[path_key]) - entry[:commit] = commit - entry[:commit_path] = commit_path(commit) - end + if commit + entry[:commit] = commit + entry[:commit_path] = commit_path(commit) end end end - def resolve_commit(raw_commit) - return nil unless raw_commit.present? + def cache_commit(commit) + return nil unless commit.present? - resolved_commits[raw_commit.id] ||= ::Commit.new(raw_commit, project) + resolved_commits[commit.id] ||= commit end def commit_path(commit) diff --git a/rubocop/cop/group_public_or_visible_to_user.rb b/rubocop/cop/group_public_or_visible_to_user.rb new file mode 100644 index 00000000000..beda0b7f8ba --- /dev/null +++ b/rubocop/cop/group_public_or_visible_to_user.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true +# +module RuboCop + module Cop + # Cop that blacklists the usage of Group.public_or_visible_to_user + class GroupPublicOrVisibleToUser < RuboCop::Cop::Cop + MSG = '`Group.public_or_visible_to_user` should be used with extreme care. ' \ + 'Please ensure that you are not using it on its own and that the amount ' \ + 'of rows being filtered is reasonable.' + + def_node_matcher :public_or_visible_to_user?, <<~PATTERN + (send (const nil? :Group) :public_or_visible_to_user ...) + PATTERN + + def on_send(node) + return unless public_or_visible_to_user?(node) + + add_offense(node, location: :expression) + end + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index ff929c7b6ce..76d6037706e 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -38,3 +38,4 @@ require_relative 'cop/code_reuse/service_class' require_relative 'cop/code_reuse/presenter' require_relative 'cop/code_reuse/serializer' require_relative 'cop/code_reuse/active_record' +require_relative 'cop/group_public_or_visible_to_user' diff --git a/scripts/review_apps/automated_cleanup.rb b/scripts/review_apps/automated_cleanup.rb index ea53f89c844..a5f0ec372d8 100755 --- a/scripts/review_apps/automated_cleanup.rb +++ b/scripts/review_apps/automated_cleanup.rb @@ -97,13 +97,13 @@ end automated_cleanup = AutomatedCleanup.new timed('Review apps cleanup') do - automated_cleanup.perform_gitlab_environment_cleanup!(days_for_stop: 5, days_for_delete: 6) + automated_cleanup.perform_gitlab_environment_cleanup!(days_for_stop: 2, days_for_delete: 3) end puts timed('Helm releases cleanup') do - automated_cleanup.perform_helm_releases_cleanup!(days: 7) + automated_cleanup.perform_helm_releases_cleanup!(days: 3) end exit(0) diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 7202cee04ea..2b28cfd16cc 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -728,4 +728,80 @@ describe ApplicationController do end end end + + context 'X-GitLab-Custom-Error header' do + before do + sign_in user + end + + context 'given a 422 error page' do + controller do + def index + render 'errors/omniauth_error', layout: 'errors', status: 422 + end + end + + it 'sets a custom header' do + get :index + + expect(response.headers['X-GitLab-Custom-Error']).to eq '1' + end + end + + context 'given a 500 error page' do + controller do + def index + render 'errors/omniauth_error', layout: 'errors', status: 500 + end + end + + it 'sets a custom header' do + get :index + + expect(response.headers['X-GitLab-Custom-Error']).to eq '1' + end + end + + context 'given a 200 success page' do + controller do + def index + render 'errors/omniauth_error', layout: 'errors', status: 200 + end + end + + it 'does not set a custom header' do + get :index + + expect(response.headers['X-GitLab-Custom-Error']).to be_nil + end + end + + context 'given a json response' do + controller do + def index + render json: {}, status: :unprocessable_entity + end + end + + it 'does not set a custom header' do + get :index, format: :json + + expect(response.headers['X-GitLab-Custom-Error']).to be_nil + end + end + + context 'given a json response for an html request' do + controller do + def index + render json: {}, status: :unprocessable_entity + end + end + + it 'does not set a custom header' do + get :index + + expect(response.headers['X-GitLab-Custom-Error']).to be_nil + end + end + end end diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb index 088ab114df3..76bc93e9766 100644 --- a/spec/features/issues/issue_detail_spec.rb +++ b/spec/features/issues/issue_detail_spec.rb @@ -18,6 +18,23 @@ describe 'Issue Detail', :js do end end + context 'when issue description has xss snippet' do + before do + issue.update!(description: '![xss" onload=alert(1);//](a)') + sign_in(user) + visit project_issue_path(project, issue) + wait_for_requests + end + + it 'should encode the description to prevent xss issues' do + page.within('.issuable-details .detail-page-description') do + expect(page).to have_selector('img', count: 1) + expect(find('img')['onerror']).to be_nil + expect(find('img')['src']).to end_with('/a') + end + end + end + context 'when edited by a user who is later deleted' do before do sign_in(user) diff --git a/spec/javascripts/ide/components/ide_status_bar_spec.js b/spec/javascripts/ide/components/ide_status_bar_spec.js index 0e93c5193a1..47d6492a7a6 100644 --- a/spec/javascripts/ide/components/ide_status_bar_spec.js +++ b/spec/javascripts/ide/components/ide_status_bar_spec.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import store from '~/ide/stores'; import ideStatusBar from '~/ide/components/ide_status_bar.vue'; +import { rightSidebarViews } from '~/ide/constants'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { resetStore } from '../helpers'; import { projectData } from '../mock_data'; @@ -64,7 +65,7 @@ describe('ideStatusBar', () => { describe('pipeline status', () => { it('opens right sidebar on clicking icon', done => { - spyOn(vm, 'setRightPane'); + spyOn(vm, 'openRightPane'); Vue.set(vm.$store.state.pipelines, 'latestPipeline', { details: { status: { @@ -80,7 +81,7 @@ describe('ideStatusBar', () => { .then(() => { vm.$el.querySelector('.ide-status-pipeline button').click(); - expect(vm.setRightPane).toHaveBeenCalledWith('pipelines-list'); + expect(vm.openRightPane).toHaveBeenCalledWith(rightSidebarViews.pipelines); }) .then(done) .catch(done.fail); diff --git a/spec/javascripts/ide/components/panes/right_spec.js b/spec/javascripts/ide/components/panes/right_spec.js index c75975d2af6..4899f850cf4 100644 --- a/spec/javascripts/ide/components/panes/right_spec.js +++ b/spec/javascripts/ide/components/panes/right_spec.js @@ -25,7 +25,8 @@ describe('IDE right pane', () => { describe('active', () => { it('renders merge request button as active', done => { - vm.$store.state.rightPane = rightSidebarViews.mergeRequestInfo; + vm.$store.state.rightPane.isOpen = true; + vm.$store.state.rightPane.currentView = rightSidebarViews.mergeRequestInfo.name; vm.$store.state.currentMergeRequestId = '123'; vm.$store.state.currentProjectId = 'gitlab-ce'; vm.$store.state.currentMergeRequestId = 1; @@ -41,20 +42,21 @@ describe('IDE right pane', () => { }, }; - vm.$nextTick(() => { - expect(vm.$el.querySelector('.ide-sidebar-link.active')).not.toBe(null); - expect( - vm.$el.querySelector('.ide-sidebar-link.active').getAttribute('data-original-title'), - ).toBe('Merge Request'); - - done(); - }); + vm.$nextTick() + .then(() => { + expect(vm.$el.querySelector('.ide-sidebar-link.active')).not.toBe(null); + expect( + vm.$el.querySelector('.ide-sidebar-link.active').getAttribute('data-original-title'), + ).toBe('Merge Request'); + }) + .then(done) + .catch(done.fail); }); }); describe('click', () => { beforeEach(() => { - spyOn(vm, 'setRightPane'); + spyOn(vm, 'open'); }); it('sets view to merge request', done => { @@ -63,7 +65,7 @@ describe('IDE right pane', () => { vm.$nextTick(() => { vm.$el.querySelector('.ide-sidebar-link').click(); - expect(vm.setRightPane).toHaveBeenCalledWith(rightSidebarViews.mergeRequestInfo); + expect(vm.open).toHaveBeenCalledWith(rightSidebarViews.mergeRequestInfo); done(); }); diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js index 0e2e246defd..991fb750876 100644 --- a/spec/javascripts/ide/components/repo_editor_spec.js +++ b/spec/javascripts/ide/components/repo_editor_spec.js @@ -319,8 +319,8 @@ describe('RepoEditor', () => { }); }); - it('calls updateDimensions when rightPane is updated', done => { - vm.$store.state.rightPane = 'testing'; + it('calls updateDimensions when rightPane is opened', done => { + vm.$store.state.rightPane.isOpen = true; vm.$nextTick(() => { expect(vm.editor.updateDimensions).toHaveBeenCalled(); diff --git a/spec/javascripts/ide/helpers.js b/spec/javascripts/ide/helpers.js index 3ce9c9fcda1..7e107747346 100644 --- a/spec/javascripts/ide/helpers.js +++ b/spec/javascripts/ide/helpers.js @@ -6,6 +6,7 @@ import mergeRequestsState from '~/ide/stores/modules/merge_requests/state'; import pipelinesState from '~/ide/stores/modules/pipelines/state'; import branchesState from '~/ide/stores/modules/branches/state'; import fileTemplatesState from '~/ide/stores/modules/file_templates/state'; +import paneState from '~/ide/stores/modules/pane/state'; export const resetStore = store => { const newState = { @@ -15,6 +16,7 @@ export const resetStore = store => { pipelines: pipelinesState(), branches: branchesState(), fileTemplates: fileTemplatesState(), + rightPane: paneState(), }; store.replaceState(newState); }; diff --git a/spec/javascripts/ide/stores/modules/pane/actions_spec.js b/spec/javascripts/ide/stores/modules/pane/actions_spec.js new file mode 100644 index 00000000000..f150ded6df5 --- /dev/null +++ b/spec/javascripts/ide/stores/modules/pane/actions_spec.js @@ -0,0 +1,87 @@ +import * as actions from '~/ide/stores/modules/pane/actions'; +import * as types from '~/ide/stores/modules/pane/mutation_types'; +import testAction from 'spec/helpers/vuex_action_helper'; + +describe('IDE pane module actions', () => { + const TEST_VIEW = { name: 'test' }; + const TEST_VIEW_KEEP_ALIVE = { name: 'test-keep-alive', keepAlive: true }; + + describe('toggleOpen', () => { + it('dispatches open if closed', done => { + testAction( + actions.toggleOpen, + TEST_VIEW, + { isOpen: false }, + [], + [{ type: 'open', payload: TEST_VIEW }], + done, + ); + }); + + it('dispatches close if opened', done => { + testAction( + actions.toggleOpen, + TEST_VIEW, + { isOpen: true }, + [], + [{ type: 'close' }], + done, + ); + }); + }); + + describe('open', () => { + it('commits SET_OPEN', done => { + testAction( + actions.open, + null, + {}, + [{ type: types.SET_OPEN, payload: true }], + [], + done, + ); + }); + + it('commits SET_CURRENT_VIEW if view is given', done => { + testAction( + actions.open, + TEST_VIEW, + {}, + [ + { type: types.SET_OPEN, payload: true }, + { type: types.SET_CURRENT_VIEW, payload: TEST_VIEW.name }, + ], + [], + done, + ); + }); + + it('commits KEEP_ALIVE_VIEW if keepAlive is true', done => { + testAction( + actions.open, + TEST_VIEW_KEEP_ALIVE, + {}, + [ + { type: types.SET_OPEN, payload: true }, + { type: types.SET_CURRENT_VIEW, payload: TEST_VIEW_KEEP_ALIVE.name }, + { type: types.KEEP_ALIVE_VIEW, payload: TEST_VIEW_KEEP_ALIVE.name }, + ], + [], + done, + ); + }); + }); + + describe('close', () => { + it('commits SET_OPEN', done => { + testAction( + actions.close, + null, + {}, + [{ type: types.SET_OPEN, payload: false }], + [], + done, + ); + }); + }); +}); diff --git a/spec/javascripts/ide/stores/modules/pane/getters_spec.js b/spec/javascripts/ide/stores/modules/pane/getters_spec.js new file mode 100644 index 00000000000..2060863b5d6 --- /dev/null +++ b/spec/javascripts/ide/stores/modules/pane/getters_spec.js @@ -0,0 +1,61 @@ +import * as getters from '~/ide/stores/modules/pane/getters'; +import state from '~/ide/stores/modules/pane/state'; + +describe('IDE pane module getters', () => { + const TEST_VIEW = 'test-view'; + const TEST_KEEP_ALIVE_VIEWS = { + [TEST_VIEW]: true, + }; + + describe('isActiveView', () => { + it('returns true if given view matches currentView', () => { + const result = getters.isActiveView({ currentView: 'A' })('A'); + + expect(result).toBe(true); + }); + + it('returns false if given view does not match currentView', () => { + const result = getters.isActiveView({ currentView: 'A' })('B'); + + expect(result).toBe(false); + }); + }); + + describe('isAliveView', () => { + it('returns true if given view is in keepAliveViews', () => { + const result = getters.isAliveView( + { keepAliveViews: TEST_KEEP_ALIVE_VIEWS }, + {}, + )(TEST_VIEW); + + expect(result).toBe(true); + }); + + it('returns true if given view is active view and open', () => { + const result = getters.isAliveView( + { ...state(), isOpen: true }, + { isActiveView: () => true }, + )(TEST_VIEW); + + expect(result).toBe(true); + }); + + it('returns false if given view is active view and closed', () => { + const result = getters.isAliveView( + state(), + { isActiveView: () => true }, + )(TEST_VIEW); + + expect(result).toBe(false); + }); + + it('returns false if given view is not activeView', () => { + const result = getters.isAliveView( + { ...state(), isOpen: true }, + { isActiveView: () => false }, + )(TEST_VIEW); + + expect(result).toBe(false); + }); + }); +}); diff --git a/spec/javascripts/ide/stores/modules/pane/mutations_spec.js b/spec/javascripts/ide/stores/modules/pane/mutations_spec.js new file mode 100644 index 00000000000..b5fcd35912e --- /dev/null +++ b/spec/javascripts/ide/stores/modules/pane/mutations_spec.js @@ -0,0 +1,42 @@ +import state from '~/ide/stores/modules/pane/state'; +import mutations from '~/ide/stores/modules/pane/mutations'; +import * as types from '~/ide/stores/modules/pane/mutation_types'; + +describe('IDE pane module mutations', () => { + const TEST_VIEW = 'test-view'; + let mockedState; + + beforeEach(() => { + mockedState = state(); + }); + + describe('SET_OPEN', () => { + it('sets isOpen', () => { + mockedState.isOpen = false; + + mutations[types.SET_OPEN](mockedState, true); + + expect(mockedState.isOpen).toBe(true); + }); + }); + + describe('SET_CURRENT_VIEW', () => { + it('sets currentView', () => { + mockedState.currentView = null; + + mutations[types.SET_CURRENT_VIEW](mockedState, TEST_VIEW); + + expect(mockedState.currentView).toEqual(TEST_VIEW); + }); + }); + + describe('KEEP_ALIVE_VIEW', () => { + it('adds entry to keepAliveViews', () => { + mutations[types.KEEP_ALIVE_VIEW](mockedState, TEST_VIEW); + + expect(mockedState.keepAliveViews).toEqual({ + [TEST_VIEW]: true, + }); + }); + }); +}); diff --git a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js index 91edb388791..d85354c3681 100644 --- a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js @@ -315,29 +315,29 @@ describe('IDE pipelines actions', () => { 'job', mockedState, [{ type: types.SET_DETAIL_JOB, payload: 'job' }], - [{ type: 'setRightPane', payload: 'jobs-detail' }], + [{ type: 'rightPane/open', payload: rightSidebarViews.jobsDetail }], done, ); }); - it('dispatches setRightPane as pipeline when job is null', done => { + it('dispatches rightPane/open as pipeline when job is null', done => { testAction( setDetailJob, null, mockedState, [{ type: types.SET_DETAIL_JOB, payload: null }], - [{ type: 'setRightPane', payload: rightSidebarViews.pipelines }], + [{ type: 'rightPane/open', payload: rightSidebarViews.pipelines }], done, ); }); - it('dispatches setRightPane as job', done => { + it('dispatches rightPane/open as job', done => { testAction( setDetailJob, 'job', mockedState, [{ type: types.SET_DETAIL_JOB, payload: 'job' }], - [{ type: 'setRightPane', payload: rightSidebarViews.jobsDetail }], + [{ type: 'rightPane/open', payload: rightSidebarViews.jobsDetail }], done, ); }); diff --git a/spec/javascripts/issue_show/index_spec.js b/spec/javascripts/issue_show/index_spec.js new file mode 100644 index 00000000000..fa0b426c06c --- /dev/null +++ b/spec/javascripts/issue_show/index_spec.js @@ -0,0 +1,19 @@ +import initIssueableApp from '~/issue_show'; + +describe('Issue show index', () => { + describe('initIssueableApp', () => { + it('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: '<img src=x onerror=alert(1)>', + }); + document.body.appendChild(d); + + const alertSpy = spyOn(window, 'alert'); + initIssueableApp(); + + expect(alertSpy).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb index ca319679e80..9633caac788 100644 --- a/spec/lib/backup/manager_spec.rb +++ b/spec/lib/backup/manager_spec.rb @@ -11,10 +11,6 @@ describe Backup::Manager do allow(progress).to receive(:puts) allow(progress).to receive(:print) - allow_any_instance_of(String).to receive(:color) do |string, _color| - string - end - @old_progress = $progress # rubocop:disable Style/GlobalVars $progress = progress # rubocop:disable Style/GlobalVars end diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb index c5a854b5660..fdeea814bb2 100644 --- a/spec/lib/backup/repository_spec.rb +++ b/spec/lib/backup/repository_spec.rb @@ -11,10 +11,6 @@ describe Backup::Repository do allow(FileUtils).to receive(:mkdir_p).and_return(true) allow(FileUtils).to receive(:mv).and_return(true) - allow_any_instance_of(String).to receive(:color) do |string, _color| - string - end - allow_any_instance_of(described_class).to receive(:progress).and_return(progress) end diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index ba8dc68ceda..ed1ebe9ebf6 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -83,6 +83,11 @@ describe Banzai::Filter::RelativeLinkFilter do expect { filter(act) }.not_to raise_error end + it 'does not raise an exception with a space in the path' do + act = link("/uploads/d18213acd3732630991986120e167e3d/Landscape_8.jpg \nBut here's some more unexpected text :smile:)") + expect { filter(act) }.not_to raise_error + end + it 'ignores ref if commit is passed' do doc = filter(link('non/existent.file'), commit: project.commit('empty-branch') ) expect(doc.at_css('a')['href']) diff --git a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb new file mode 100644 index 00000000000..2a869446753 --- /dev/null +++ b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::EncryptColumns, :migration, schema: 20180910115836 do + let(:model) { Gitlab::BackgroundMigration::Models::EncryptColumns::WebHook } + let(:web_hooks) { table(:web_hooks) } + + let(:plaintext_attrs) do + { + 'encrypted_token' => nil, + 'encrypted_url' => nil, + 'token' => 'secret', + 'url' => 'http://example.com?access_token=secret' + } + end + + let(:encrypted_attrs) do + { + 'encrypted_token' => be_present, + 'encrypted_url' => be_present, + 'token' => nil, + 'url' => nil + } + end + + describe '#perform' do + it 'encrypts columns for the specified range' do + hooks = web_hooks.create([plaintext_attrs] * 5).sort_by(&:id) + + # Encrypt all but the first and last rows + subject.perform(model, [:token, :url], hooks[1].id, hooks[3].id) + + hooks = web_hooks.where(id: hooks.map(&:id)).order(:id) + + aggregate_failures do + expect(hooks[0]).to have_attributes(plaintext_attrs) + expect(hooks[1]).to have_attributes(encrypted_attrs) + expect(hooks[2]).to have_attributes(encrypted_attrs) + expect(hooks[3]).to have_attributes(encrypted_attrs) + expect(hooks[4]).to have_attributes(plaintext_attrs) + end + end + + it 'acquires an exclusive lock for the update' do + relation = double('relation', each: nil) + + expect(model).to receive(:where) { relation } + expect(relation).to receive(:lock) { relation } + + subject.perform(model, [:token, :url], 1, 1) + end + + it 'skips already-encrypted columns' do + values = { + 'encrypted_token' => 'known encrypted token', + 'encrypted_url' => 'known encrypted url', + 'token' => 'token', + 'url' => 'url' + } + + hook = web_hooks.create(values) + + subject.perform(model, [:token, :url], hook.id, hook.id) + + hook.reload + + expect(hook).to have_attributes(values) + end + end +end diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb index 9ca960502c8..98f1696badb 100644 --- a/spec/lib/gitlab/data_builder/pipeline_spec.rb +++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb @@ -6,10 +6,10 @@ describe Gitlab::DataBuilder::Pipeline do let(:pipeline) do create(:ci_pipeline, - project: project, - status: 'success', - sha: project.commit.sha, - ref: project.default_branch) + project: project, + status: 'success', + sha: project.commit.sha, + ref: project.default_branch) end let!(:build) { create(:ci_build, pipeline: pipeline) } @@ -20,18 +20,35 @@ describe Gitlab::DataBuilder::Pipeline do let(:build_data) { data[:builds].first } let(:project_data) { data[:project] } - it { expect(attributes).to be_a(Hash) } - it { expect(attributes[:ref]).to eq(pipeline.ref) } - it { expect(attributes[:sha]).to eq(pipeline.sha) } - it { expect(attributes[:tag]).to eq(pipeline.tag) } - it { expect(attributes[:id]).to eq(pipeline.id) } - it { expect(attributes[:status]).to eq(pipeline.status) } - it { expect(attributes[:detailed_status]).to eq('passed') } + it 'has correct attributes' do + expect(attributes).to be_a(Hash) + expect(attributes[:ref]).to eq(pipeline.ref) + expect(attributes[:sha]).to eq(pipeline.sha) + expect(attributes[:tag]).to eq(pipeline.tag) + expect(attributes[:id]).to eq(pipeline.id) + expect(attributes[:status]).to eq(pipeline.status) + expect(attributes[:detailed_status]).to eq('passed') + expect(build_data).to be_a(Hash) + expect(build_data[:id]).to eq(build.id) + expect(build_data[:status]).to eq(build.status) + expect(project_data).to eq(project.hook_attrs(backward: false)) + end - it { expect(build_data).to be_a(Hash) } - it { expect(build_data[:id]).to eq(build.id) } - it { expect(build_data[:status]).to eq(build.status) } + context 'pipeline without variables' do + it 'has empty variables hash' do + expect(attributes[:variables]).to be_a(Array) + expect(attributes[:variables]).to be_empty() + end + end - it { expect(project_data).to eq(project.hook_attrs(backward: false)) } + context 'pipeline with variables' do + let(:build) { create(:ci_build, pipeline: pipeline) } + let(:data) { described_class.build(pipeline) } + let(:attributes) { data[:object_attributes] } + let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1') } + + it { expect(attributes[:variables]).to be_a(Array) } + it { expect(attributes[:variables]).to contain_exactly({ key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1' }) } + end end end diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb index 3c8cf9c56cc..5d0a603d11d 100644 --- a/spec/lib/gitlab/diff/highlight_spec.rb +++ b/spec/lib/gitlab/diff/highlight_spec.rb @@ -8,6 +8,20 @@ describe Gitlab::Diff::Highlight do let(:diff) { commit.raw_diffs.first } let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) } + shared_examples 'without inline diffs' do + let(:code) { '<h2 onmouseover="alert(2)">Test</h2>' } + + before do + allow(Gitlab::Diff::InlineDiff).to receive(:for_lines).and_return([]) + allow_any_instance_of(Gitlab::Diff::Line).to receive(:text).and_return(code) + end + + it 'returns html escaped diff text' do + expect(subject[1].rich_text).to eq html_escape(code) + expect(subject[1].rich_text).to be_html_safe + end + end + describe '#highlight' do context "with a diff file" do let(:subject) { described_class.new(diff_file, repository: project.repository).highlight } @@ -38,6 +52,16 @@ describe Gitlab::Diff::Highlight do expect(subject[5].rich_text).to eq(code) end + + context 'when no diff_refs' do + before do + allow(diff_file).to receive(:diff_refs).and_return(nil) + end + + context 'when no inline diffs' do + it_behaves_like 'without inline diffs' + end + end end context "with diff lines" do @@ -93,6 +117,10 @@ describe Gitlab::Diff::Highlight do expect { subject }. to raise_exception(RangeError) end end + + context 'when no inline diffs' do + it_behaves_like 'without inline diffs' + end end end end diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index 29e61d15726..88f7099ff3c 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -56,5 +56,22 @@ describe Gitlab::Highlight do described_class.highlight('file.name', 'Contents') end + + context 'timeout' do + subject { described_class.new('file.name', 'Contents') } + + it 'utilizes timeout for web' do + expect(Timeout).to receive(:timeout).with(described_class::TIMEOUT_FOREGROUND).and_call_original + + subject.highlight("Content") + end + + it 'utilizes longer timeout for sidekiq' do + allow(Sidekiq).to receive(:server?).and_return(true) + expect(Timeout).to receive(:timeout).with(described_class::TIMEOUT_BACKGROUND).and_call_original + + subject.highlight("Content") + end + end end end diff --git a/spec/models/blob_viewer/package_json_spec.rb b/spec/models/blob_viewer/package_json_spec.rb index 5ed2f4400bc..fbaa8d47a71 100644 --- a/spec/models/blob_viewer/package_json_spec.rb +++ b/spec/models/blob_viewer/package_json_spec.rb @@ -40,13 +40,14 @@ describe BlobViewer::PackageJson do end context 'when package.json has "private": true' do + let(:homepage) { 'http://example.com' } let(:data) do <<-SPEC.strip_heredoc { "name": "module-name", "version": "10.3.1", "private": true, - "homepage": "myawesomepackage.com" + "homepage": #{homepage.to_json} } SPEC end @@ -54,10 +55,22 @@ describe BlobViewer::PackageJson do subject { described_class.new(blob) } describe '#package_url' do - it 'returns homepage if any' do - expect(subject).to receive(:prepare!) + context 'when the homepage has a valid URL' do + it 'returns homepage URL' do + expect(subject).to receive(:prepare!) + + expect(subject.package_url).to eq(homepage) + end + end + + context 'when the homepage has an invalid URL' do + let(:homepage) { 'javascript:alert()' } + + it 'returns nil' do + expect(subject).to receive(:prepare!) - expect(subject.package_url).to eq('myawesomepackage.com') + expect(subject.package_url).to be_nil + end end end diff --git a/spec/models/ci/pipeline_variable_spec.rb b/spec/models/ci/pipeline_variable_spec.rb index 889c243c8d8..03d09cb31d6 100644 --- a/spec/models/ci/pipeline_variable_spec.rb +++ b/spec/models/ci/pipeline_variable_spec.rb @@ -5,4 +5,13 @@ describe Ci::PipelineVariable do it { is_expected.to include_module(HasVariable) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:pipeline_id) } + + describe '#hook_attrs' do + let(:variable) { create(:ci_pipeline_variable, key: 'foo', value: 'bar') } + + subject { variable.hook_attrs } + + it { is_expected.to be_a(Hash) } + it { is_expected.to eq({ key: 'foo', value: 'bar' }) } + end end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index c1eac4fa489..81748681528 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -148,9 +148,14 @@ describe Event do let(:admin) { create(:admin) } let(:issue) { create(:issue, project: project, author: author, assignees: [assignee]) } let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignees: [assignee]) } + let(:project_snippet) { create(:project_snippet, :public, project: project, author: author) } + let(:personal_snippet) { create(:personal_snippet, :public, author: author) } let(:note_on_commit) { create(:note_on_commit, project: project) } let(:note_on_issue) { create(:note_on_issue, noteable: issue, project: project) } let(:note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project) } + let(:note_on_project_snippet) { create(:note_on_project_snippet, author: author, noteable: project_snippet, project: project) } + let(:note_on_personal_snippet) { create(:note_on_personal_snippet, author: author, noteable: personal_snippet, project: nil) } + let(:milestone_on_project) { create(:milestone, project: project) } let(:event) { described_class.new(project: project, target: target, author_id: author.id) } before do @@ -268,6 +273,125 @@ describe Event do end end end + + context 'milestone event' do + let(:target) { milestone_on_project } + + it do + expect(event.visible_to_user?(nil)).to be_truthy + expect(event.visible_to_user?(non_member)).to be_truthy + expect(event.visible_to_user?(member)).to be_truthy + expect(event.visible_to_user?(guest)).to be_truthy + expect(event.visible_to_user?(admin)).to be_truthy + end + + context 'on public project with private issue tracker and merge requests' do + let(:project) { create(:project, :public, :issues_private, :merge_requests_private) } + + it do + expect(event.visible_to_user?(nil)).to be_falsy + expect(event.visible_to_user?(non_member)).to be_falsy + expect(event.visible_to_user?(member)).to be_truthy + expect(event.visible_to_user?(guest)).to be_truthy + expect(event.visible_to_user?(admin)).to be_truthy + end + end + + context 'on private project' do + let(:project) { create(:project, :private) } + + it do + expect(event.visible_to_user?(nil)).to be_falsy + expect(event.visible_to_user?(non_member)).to be_falsy + expect(event.visible_to_user?(member)).to be_truthy + expect(event.visible_to_user?(guest)).to be_truthy + expect(event.visible_to_user?(admin)).to be_truthy + end + end + end + + context 'project snippet note event' do + let(:target) { note_on_project_snippet } + + it do + expect(event.visible_to_user?(nil)).to be_truthy + expect(event.visible_to_user?(non_member)).to be_truthy + expect(event.visible_to_user?(author)).to be_truthy + expect(event.visible_to_user?(member)).to be_truthy + expect(event.visible_to_user?(guest)).to be_truthy + expect(event.visible_to_user?(admin)).to be_truthy + end + + context 'on public project with private snippets' do + let(:project) { create(:project, :public, :snippets_private) } + + it do + expect(event.visible_to_user?(nil)).to be_falsy + expect(event.visible_to_user?(non_member)).to be_falsy + + # Normally, we'd expect the author of a comment to be able to view it. + # However, this doesn't seem to be the case for comments on snippets. + expect(event.visible_to_user?(author)).to be_falsy + + expect(event.visible_to_user?(member)).to be_truthy + expect(event.visible_to_user?(guest)).to be_truthy + expect(event.visible_to_user?(admin)).to be_truthy + end + end + + context 'on private project' do + let(:project) { create(:project, :private) } + + it do + expect(event.visible_to_user?(nil)).to be_falsy + expect(event.visible_to_user?(non_member)).to be_falsy + + # Normally, we'd expect the author of a comment to be able to view it. + # However, this doesn't seem to be the case for comments on snippets. + expect(event.visible_to_user?(author)).to be_falsy + + expect(event.visible_to_user?(member)).to be_truthy + expect(event.visible_to_user?(guest)).to be_truthy + expect(event.visible_to_user?(admin)).to be_truthy + end + end + end + + context 'personal snippet note event' do + let(:target) { note_on_personal_snippet } + + it do + expect(event.visible_to_user?(nil)).to be_truthy + expect(event.visible_to_user?(non_member)).to be_truthy + expect(event.visible_to_user?(author)).to be_truthy + expect(event.visible_to_user?(admin)).to be_truthy + end + + context 'on internal snippet' do + let(:personal_snippet) { create(:personal_snippet, :internal, author: author) } + + it do + expect(event.visible_to_user?(nil)).to be_falsy + expect(event.visible_to_user?(non_member)).to be_truthy + expect(event.visible_to_user?(author)).to be_truthy + expect(event.visible_to_user?(admin)).to be_truthy + end + end + + context 'on private snippet' do + let(:personal_snippet) { create(:personal_snippet, :private, author: author) } + + it do + expect(event.visible_to_user?(nil)).to be_falsy + expect(event.visible_to_user?(non_member)).to be_falsy + expect(event.visible_to_user?(author)).to be_truthy + + # It is very unexpected that a private personal snippet is not visible + # to an instance administrator. This should be fixed in the future. + expect(event.visible_to_user?(admin)).to be_falsy + end + end + end end describe '.limit_recent' do diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 0729eb99e78..1bf8f89e126 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -169,22 +169,42 @@ describe Group do end end - describe '.visible_to_user' do - let!(:group) { create(:group) } - let!(:user) { create(:user) } + describe '.public_or_visible_to_user' do + let!(:private_group) { create(:group, :private) } + let!(:internal_group) { create(:group, :internal) } - subject { described_class.visible_to_user(user) } + subject { described_class.public_or_visible_to_user(user) } - describe 'when the user has access to a group' do - before do - group.add_user(user, Gitlab::Access::MAINTAINER) - end + context 'when user is nil' do + let!(:user) { nil } - it { is_expected.to eq([group]) } + it { is_expected.to match_array([group]) } end - describe 'when the user does not have access to any groups' do - it { is_expected.to eq([]) } + context 'when user' do + let!(:user) { create(:user) } + + context 'when user does not have access to any private group' do + it { is_expected.to match_array([internal_group, group]) } + end + + context 'when user is a member of private group' do + before do + private_group.add_user(user, Gitlab::Access::DEVELOPER) + end + + it { is_expected.to match_array([private_group, internal_group, group]) } + end + + context 'when user is a member of private subgroup', :postgresql do + let!(:private_subgroup) { create(:group, :private, parent: private_group) } + + before do + private_subgroup.add_user(user, Gitlab::Access::DEVELOPER) + end + + it { is_expected.to match_array([private_subgroup, internal_group, group]) } + end end end diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index a4181631f01..a308ac6e33a 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -57,6 +57,12 @@ describe WebHook do end end + describe 'encrypted attributes' do + subject { described_class.encrypted_attributes.keys } + + it { is_expected.to contain_exactly(:token, :url) } + end + describe 'execute' do let(:data) { { key: 'value' } } let(:hook_name) { 'project hook' } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 7e1b7c35517..784d17e271e 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -188,6 +188,57 @@ describe Repository do end end + describe '#list_last_commits_for_tree' do + let(:path_to_commit) do + { + "encoding" => "913c66a37b4a45b9769037c55c2d238bd0942d2e", + "files" => "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", + ".gitignore" => "c1acaa58bbcbc3eafe538cb8274ba387047b69f8", + ".gitmodules" => "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9", + "CHANGELOG" => "913c66a37b4a45b9769037c55c2d238bd0942d2e", + "CONTRIBUTING.md" => "6d394385cf567f80a8fd85055db1ab4c5295806f", + "Gemfile.zip" => "ae73cb07c9eeaf35924a10f713b364d32b2dd34f", + "LICENSE" => "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863", + "MAINTENANCE.md" => "913c66a37b4a45b9769037c55c2d238bd0942d2e", + "PROCESS.md" => "913c66a37b4a45b9769037c55c2d238bd0942d2e", + "README.md" => "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863", + "VERSION" => "913c66a37b4a45b9769037c55c2d238bd0942d2e", + "gitlab-shell" => "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9", + "six" => "cfe32cf61b73a0d5e9f13e774abde7ff789b1660" + } + end + + subject { repository.list_last_commits_for_tree(sample_commit.id, '.').id } + + it 'returns the last commits for every entry in the current path' do + result = repository.list_last_commits_for_tree(sample_commit.id, '.') + + result.each do |key, value| + result[key] = value.id + end + + expect(result).to include(path_to_commit) + end + + it 'returns the last commits for every entry in the current path starting from the offset' do + result = repository.list_last_commits_for_tree(sample_commit.id, '.', offset: path_to_commit.size - 1) + + expect(result.size).to eq(1) + end + + it 'returns a limited number of last commits for every entry in the current path starting from the offset' do + result = repository.list_last_commits_for_tree(sample_commit.id, '.', limit: 1) + + expect(result.size).to eq(1) + end + + it 'returns an empty hash when offset is out of bounds' do + result = repository.list_last_commits_for_tree(sample_commit.id, '.', offset: path_to_commit.size) + + expect(result.size).to eq(0) + end + end + describe '#last_commit_for_path' do shared_examples 'getting last commit for path' do subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id } diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 3a8948f8477..3802b5c6848 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -155,7 +155,7 @@ describe API::Groups do expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(response_groups).to eq(Group.visible_to_user(user1).order(:name).pluck(:name)) + expect(response_groups).to eq(groups_visible_to_user(user1).order(:name).pluck(:name)) end it "sorts in descending order when passed" do @@ -164,7 +164,7 @@ describe API::Groups do expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(response_groups).to eq(Group.visible_to_user(user1).order(name: :desc).pluck(:name)) + expect(response_groups).to eq(groups_visible_to_user(user1).order(name: :desc).pluck(:name)) end it "sorts by path in order_by param" do @@ -173,7 +173,7 @@ describe API::Groups do expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(response_groups).to eq(Group.visible_to_user(user1).order(:path).pluck(:name)) + expect(response_groups).to eq(groups_visible_to_user(user1).order(:path).pluck(:name)) end it "sorts by id in the order_by param" do @@ -182,7 +182,7 @@ describe API::Groups do expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(response_groups).to eq(Group.visible_to_user(user1).order(:id).pluck(:name)) + expect(response_groups).to eq(groups_visible_to_user(user1).order(:id).pluck(:name)) end it "sorts also by descending id with pagination fix" do @@ -191,7 +191,7 @@ describe API::Groups do expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(response_groups).to eq(Group.visible_to_user(user1).order(id: :desc).pluck(:name)) + expect(response_groups).to eq(groups_visible_to_user(user1).order(id: :desc).pluck(:name)) end it "sorts identical keys by id for good pagination" do @@ -211,6 +211,10 @@ describe API::Groups do expect(json_response).to be_an Array expect(response_groups_ids).to eq(Group.select { |group| group['name'] == 'same-name' }.map { |group| group['id'] }.sort) end + + def groups_visible_to_user(user) + Group.where(id: user.authorized_groups.select(:id).reorder(nil)) + end end context 'when using owned in the request' do diff --git a/spec/requests/api/redacted_events_spec.rb b/spec/requests/api/redacted_events_spec.rb new file mode 100644 index 00000000000..086dd3df9ba --- /dev/null +++ b/spec/requests/api/redacted_events_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +describe 'Redacted events in API::Events' do + shared_examples 'private events are redacted' do + it 'redacts events the user does not have access to' do + expect_any_instance_of(Event).to receive(:visible_to_user?).and_call_original + + get api(path), user + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to contain_exactly( + 'project_id' => nil, + 'action_name' => nil, + 'target_id' => nil, + 'target_iid' => nil, + 'target_type' => nil, + 'author_id' => nil, + 'target_title' => 'Confidential event', + 'created_at' => nil, + 'author_username' => nil + ) + end + end + + describe '/users/:id/events' do + let(:project) { create(:project, :public) } + let(:path) { "/users/#{project.owner.id}/events" } + let(:issue) { create(:issue, :confidential, project: project) } + + before do + EventCreateService.new.open_issue(issue, issue.author) + end + + context 'unauthenticated user views another user with private events' do + let(:user) { nil } + + include_examples 'private events are redacted' + end + + context 'authenticated user without access views another user with private events' do + let(:user) { create(:user) } + + include_examples 'private events are redacted' + end + end + + describe '/projects/:id/events' do + let(:project) { create(:project, :public) } + let(:path) { "/projects/#{project.id}/events" } + let(:issue) { create(:issue, :confidential, project: project) } + + before do + EventCreateService.new.open_issue(issue, issue.author) + end + + context 'unauthenticated user views public project' do + let(:user) { nil } + + include_examples 'private events are redacted' + end + + context 'authenticated user without access views public project' do + let(:user) { create(:user) } + + include_examples 'private events are redacted' + end + end +end diff --git a/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb b/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb new file mode 100644 index 00000000000..7b5235a3da7 --- /dev/null +++ b/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' +require 'rubocop' +require 'rubocop/rspec/support' +require_relative '../../../rubocop/cop/group_public_or_visible_to_user' + +describe RuboCop::Cop::GroupPublicOrVisibleToUser do + include CopHelper + + subject(:cop) { described_class.new } + + it 'flags the use of Group.public_or_visible_to_user with a constant receiver' do + inspect_source('Group.public_or_visible_to_user') + + expect(cop.offenses.size).to eq(1) + end + + it 'does not flat the use of public_or_visible_to_user with a constant that is not Group' do + inspect_source('Project.public_or_visible_to_user') + + expect(cop.offenses.size).to eq(0) + end + + it 'does not flag the use of Group.public_or_visible_to_user with a send receiver' do + inspect_source('foo.public_or_visible_to_user') + + expect(cop.offenses.size).to eq(0) + end +end diff --git a/spec/serializers/diff_line_entity_spec.rb b/spec/serializers/diff_line_entity_spec.rb new file mode 100644 index 00000000000..2549f64bcd3 --- /dev/null +++ b/spec/serializers/diff_line_entity_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe DiffLineEntity do + include RepoHelpers + + let(:code) { 'hello world' } + let(:line) { Gitlab::Diff::Line.new(code, 'new', 1, nil, 1) } + let(:entity) { described_class.new(line, request: {}) } + + subject { entity.as_json } + + it 'exposes correct attributes' do + expect(subject).to include( + :line_code, :type, :old_line, :new_line, :text, :meta_data, :rich_text + ) + end + + describe '#rich_text' do + let(:code) { '<h2 onmouseover="alert(2)">Test</h2>' } + let(:rich_text_value) { nil } + + before do + line.instance_variable_set(:@rich_text, rich_text_value) + end + + shared_examples 'escapes html tags' do + it do + expect(subject[:rich_text]).to eq html_escape(code) + expect(subject[:rich_text]).to be_html_safe + end + end + + context 'when rich_line is present' do + let(:rich_text_value) { code } + + it_behaves_like 'escapes html tags' + end + + context 'when rich_line is not present' do + it_behaves_like 'escapes html tags' + end + end +end diff --git a/spec/services/clusters/applications/check_installation_progress_service_spec.rb b/spec/services/clusters/applications/check_installation_progress_service_spec.rb index 986f11410fd..1a565bb734d 100644 --- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb +++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb @@ -82,7 +82,7 @@ describe Clusters::Applications::CheckInstallationProgressService do service.execute expect(application).to be_errored - expect(application.status_reason).to eq(errors) + expect(application.status_reason).to eq("Installation failed") end end diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb index a744ec30b65..4bd19f5bd79 100644 --- a/spec/services/clusters/applications/install_service_spec.rb +++ b/spec/services/clusters/applications/install_service_spec.rb @@ -42,7 +42,7 @@ describe Clusters::Applications::InstallService do service.execute expect(application).to be_errored - expect(application.status_reason).to match(/kubernetes error:/i) + expect(application.status_reason).to match('Kubernetes error.') end end diff --git a/spec/services/users/build_service_spec.rb b/spec/services/users/build_service_spec.rb index b987fe45138..051e8c87f39 100644 --- a/spec/services/users/build_service_spec.rb +++ b/spec/services/users/build_service_spec.rb @@ -14,6 +14,49 @@ describe Users::BuildService do expect(service.execute).to be_valid end + context 'allowed params' do + let(:params) do + { + access_level: 1, + admin: 1, + avatar: anything, + bio: 1, + can_create_group: 1, + color_scheme_id: 1, + email: 1, + external: 1, + force_random_password: 1, + hide_no_password: 1, + hide_no_ssh_key: 1, + linkedin: 1, + name: 1, + password: 1, + password_automatically_set: 1, + password_expires_at: 1, + projects_limit: 1, + remember_me: 1, + skip_confirmation: 1, + skype: 1, + theme_id: 1, + twitter: 1, + username: 1, + website_url: 1, + private_profile: 1, + organization: 1, + location: 1, + public_email: 1 + } + end + + it 'sets all allowed attributes' do + admin_user # call first so the admin gets created before setting `expect` + + expect(User).to receive(:new).with(hash_including(params)).and_call_original + + service.execute + end + end + context 'with "user_default_external" application setting' do using RSpec::Parameterized::TableSyntax diff --git a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb index 8befae39d3a..0206928a211 100644 --- a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb @@ -12,6 +12,7 @@ describe 'projects/merge_requests/creations/_new_submit.html.haml' do assign(:hidden_commit_count, 0) assign(:total_commit_count, merge_request.commits.count) assign(:project, merge_request.target_project) + assign(:mr_presenter, merge_request.present(current_user: merge_request.author)) allow(view).to receive(:can?).and_return(true) allow(view).to receive(:url_for).and_return('#') diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb index 9b74a7e1946..c13eab30054 100644 --- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb @@ -24,6 +24,7 @@ describe 'projects/merge_requests/edit.html.haml' do before do assign(:project, project) assign(:merge_request, closed_merge_request) + assign(:mr_presenter, closed_merge_request.present(current_user: user)) allow(view).to receive(:can?).and_return(true) allow(view).to receive(:current_user) |