diff --git a/CHANGELOG-EE.md b/CHANGELOG-EE.md index 4ec5e9c98fd..ab7d706fc7b 100644 --- a/CHANGELOG-EE.md +++ b/CHANGELOG-EE.md @@ -1,5 +1,9 @@ Please view this file on the master branch, on stable branches it's out of date. +## 12.7.3 + +- No changes. + ## 12.7.1 ### Fixed (1 change) @@ -95,6 +99,14 @@ Please view this file on the master branch, on stable branches it's out of date. - Remove "creations" in gitlab_subscription_histories on gitlab.com. !22278 +## 12.6.6 + +- No changes. + +## 12.6.5 + +- No changes. + ## 12.6.4 - No changes. @@ -207,6 +219,10 @@ Please view this file on the master branch, on stable branches it's out of date. - Update the alerts used in the Dependency List to follow GitLab design guidelines. !21760 +## 12.5.8 + +- No changes. + ## 12.5.5 - No changes. diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 401cc8dd3e7..ba624dacf9a 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -8.19.0 +8.20.0 diff --git a/Gemfile b/Gemfile index e816e1a1fcd..bb486a20ce4 100644 --- a/Gemfile +++ b/Gemfile @@ -65,7 +65,7 @@ gem 'u2f', '~> 0.2.1' # GitLab Pages gem 'validates_hostname', '~> 1.0.6' -gem 'rubyzip', '~> 1.3.0', require: 'zip' +gem 'rubyzip', '~> 2.0.0', require: 'zip' # GitLab Pages letsencrypt support gem 'acme-client', '~> 2.0.5' diff --git a/Gemfile.lock b/Gemfile.lock index c52150cc675..58a4f1ad53d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -274,7 +274,7 @@ GEM et-orbi (1.2.1) tzinfo eventmachine (1.2.7) - excon (0.62.0) + excon (0.71.1) execjs (2.6.0) expression_parser (0.9.0) extended-markdown-filter (0.6.0) @@ -961,7 +961,7 @@ GEM sexp_processor (~> 4.9) rubyntlm (0.6.2) rubypants (0.2.0) - rubyzip (1.3.0) + rubyzip (2.0.0) rugged (0.28.4.1) safe_yaml (1.0.4) sanitize (4.6.6) @@ -1361,7 +1361,7 @@ DEPENDENCIES ruby-prof (~> 1.0.0) ruby-progressbar ruby_parser (~> 3.8) - rubyzip (~> 1.3.0) + rubyzip (~> 2.0.0) rugged (~> 0.28) sanitize (~> 4.6) sassc-rails (~> 2.1.0) diff --git a/app/assets/javascripts/frequent_items/components/app.vue b/app/assets/javascripts/frequent_items/components/app.vue index 8cf939254c1..2ffecce0a56 100644 --- a/app/assets/javascripts/frequent_items/components/app.vue +++ b/app/assets/javascripts/frequent_items/components/app.vue @@ -5,7 +5,7 @@ import AccessorUtilities from '~/lib/utils/accessor'; import eventHub from '../event_hub'; import store from '../store/'; import { FREQUENT_ITEMS, STORAGE_KEY } from '../constants'; -import { isMobile, updateExistingFrequentItem } from '../utils'; +import { isMobile, updateExistingFrequentItem, sanitizeItem } from '../utils'; import FrequentItemsSearchInput from './frequent_items_search_input.vue'; import FrequentItemsList from './frequent_items_list.vue'; import frequentItemsMixin from './frequent_items_mixin'; @@ -64,7 +64,9 @@ export default { this.fetchFrequentItems(); } }, - logItemAccess(storageKey, item) { + logItemAccess(storageKey, unsanitizedItem) { + const item = sanitizeItem(unsanitizedItem); + if (!AccessorUtilities.isLocalStorageAccessSafe()) { return false; } diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list.vue index 67ffa97a046..0ece64692ae 100644 --- a/app/assets/javascripts/frequent_items/components/frequent_items_list.vue +++ b/app/assets/javascripts/frequent_items/components/frequent_items_list.vue @@ -1,6 +1,7 @@ @@ -59,7 +63,7 @@ export default { {{ listEmptyMessage }} ['md', 'sm', 'xs'].includes(bp.getBreakpointSize()); @@ -43,3 +44,9 @@ export const updateExistingFrequentItem = (frequentItem, item) => { lastAccessedOn: accessedOverHourAgo ? Date.now() : frequentItem.lastAccessedOn, }; }; + +export const sanitizeItem = item => ({ + ...item, + name: sanitize(item.name.toString(), { allowedTags: [] }), + namespace: sanitize(item.namespace.toString(), { allowedTags: [] }), +}); diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js index a5e38022b8d..4daa8c60e58 100644 --- a/app/assets/javascripts/groups_select.js +++ b/app/assets/javascripts/groups_select.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import axios from './lib/utils/axios_utils'; import Api from './api'; +import { escape } from 'lodash'; import { normalizeHeaders } from './lib/utils/common_utils'; import { __ } from '~/locale'; @@ -75,10 +76,12 @@ const groupsSelect = () => { } }, formatResult(object) { - return `
${object.full_name}
${object.full_path}
`; + return `
${escape( + object.full_name, + )}
${object.full_path}
`; }, formatSelection(object) { - return object.full_name; + return escape(object.full_name); }, dropdownCssClass: 'ajax-groups-dropdown select2-infinite', // we do not want to escape markup since we are displaying html in results diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index be2adb07526..762228dd138 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -14,7 +14,7 @@ import placeholderSystemNote from '../../vue_shared/components/notes/placeholder import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue'; import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user'; import { __ } from '~/locale'; -import initUserPopovers from '../../user_popovers'; +import initUserPopovers from '~/user_popovers'; export default { name: 'NotesApp', diff --git a/app/assets/javascripts/user_popovers.js b/app/assets/javascripts/user_popovers.js index 157d89a3a40..5b9e3817f3a 100644 --- a/app/assets/javascripts/user_popovers.js +++ b/app/assets/javascripts/user_popovers.js @@ -3,108 +3,92 @@ import Vue from 'vue'; import UsersCache from './lib/utils/users_cache'; import UserPopover from './vue_shared/components/user_popover/user_popover.vue'; -let renderedPopover; -let renderFn; +const removeTitle = el => { + // Removing titles so its not showing tooltips also -const handleUserPopoverMouseOut = event => { - const { target } = event; - target.removeEventListener('mouseleave', handleUserPopoverMouseOut); + el.dataset.originalTitle = ''; + el.setAttribute('title', ''); +}; - if (renderFn) { - clearTimeout(renderFn); - } - if (renderedPopover) { - renderedPopover.$destroy(); - renderedPopover = null; - } - target.removeAttribute('aria-describedby'); +const getPreloadedUserInfo = dataset => { + const userId = dataset.user || dataset.userId; + const { username, name, avatarUrl } = dataset; + + return { + userId, + username, + name, + avatarUrl, + }; }; /** * Adds a UserPopover component to the body, hands over as much data as the target element has in data attributes. * loads based on data-user-id more data about a user from the API and sets it on the popover */ -const handleUserPopoverMouseOver = event => { - const { target } = event; - // Add listener to actually remove it again - target.addEventListener('mouseleave', handleUserPopoverMouseOut); +const populateUserInfo = user => { + const { userId } = user; - renderFn = setTimeout(() => { - // Helps us to use current markdown setup without maybe breaking or duplicating for now - if (target.dataset.user) { - target.dataset.userId = target.dataset.user; - // Removing titles so its not showing tooltips also - target.dataset.originalTitle = ''; - target.setAttribute('title', ''); - } + return Promise.all([UsersCache.retrieveById(userId), UsersCache.retrieveStatusById(userId)]).then( + ([userData, status]) => { + if (userData) { + Object.assign(user, { + avatarUrl: userData.avatar_url, + username: userData.username, + name: userData.name, + location: userData.location, + bio: userData.bio, + organization: userData.organization, + loaded: true, + }); + } - const { userId, username, name, avatarUrl } = target.dataset; + if (status) { + Object.assign(user, { + status, + }); + } + + return user; + }, + ); +}; + +export default (elements = document.querySelectorAll('.js-user-link')) => { + const userLinks = Array.from(elements); + + return userLinks.map(el => { + const UserPopoverComponent = Vue.extend(UserPopover); const user = { - userId, - username, - name, - avatarUrl, location: null, bio: null, organization: null, status: null, loaded: false, }; - if (userId || username) { - const UserPopoverComponent = Vue.extend(UserPopover); - renderedPopover = new UserPopoverComponent({ - propsData: { - target, - user, - }, - }); + const renderedPopover = new UserPopoverComponent({ + propsData: { + target: el, + user, + }, + }); - renderedPopover.$mount(); + renderedPopover.$mount(); - UsersCache.retrieveById(userId) - .then(userData => { - if (!userData) { - return undefined; - } + el.addEventListener('mouseenter', ({ target }) => { + removeTitle(target); + const preloadedUserInfo = getPreloadedUserInfo(target.dataset); - Object.assign(user, { - avatarUrl: userData.avatar_url, - username: userData.username, - name: userData.name, - location: userData.location, - bio: userData.bio, - organization: userData.organization, - status: userData.status, - loaded: true, - }); + Object.assign(user, preloadedUserInfo); - if (userData.status) { - return Promise.resolve(); - } + if (preloadedUserInfo.userId) { + populateUserInfo(user); + } + }); + el.addEventListener('mouseleave', ({ target }) => { + target.removeAttribute('aria-describedby'); + }); - return UsersCache.retrieveStatusById(userId); - }) - .then(status => { - if (!status) { - return; - } - - Object.assign(user, { - status, - }); - }) - .catch(() => { - renderedPopover.$destroy(); - renderedPopover = null; - }); - } - }, 200); // 200ms delay so not every mouseover triggers Popover + API Call -}; - -export default elements => { - const userLinks = elements || [...document.querySelectorAll('.js-user-link')]; - - userLinks.forEach(el => { - el.addEventListener('mouseenter', handleUserPopoverMouseOver); + return renderedPopover; }); }; diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue index 37e3643bf6c..ca25d9ee738 100644 --- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue +++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue @@ -56,19 +56,16 @@ export default {