Commit e3ec73c4 authored by Achilleas Pipinellis's avatar Achilleas Pipinellis
Browse files

Merge branch 'master' into 'doc_api_settings'

# Conflicts:
#   doc/api/settings.md
......@@ -2,6 +2,38 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
 
## 12.2.3
### Security (22 changes)
- Ensure only authorised users can create notes on Merge Requests and Issues.
- Gitaly: ignore git redirects.
- Add :login_recaptcha_protection_enabled setting to prevent bots from brute-force attacks.
- Speed up regexp in namespace format by failing fast after reaching maximum namespace depth.
- Limit the size of issuable description and comments.
- Send TODOs for comments on commits correctly.
- Restrict MergeRequests#test_reports to authenticated users with read-access on Builds.
- Added image proxy to mitigate potential stealing of IP addresses.
- Filter out old system notes for epics in notes api endpoint response.
- Avoid exposing unaccessible repo data upon GFM post processing.
- Fix HTML injection for label description.
- Make sure HTML text is always escaped when replacing label/milestone references.
- Prevent DNS rebind on JIRA service integration.
- Use admin_group authorization in Groups::RunnersController.
- Prevent disclosure of merge request ID via email.
- Show cross-referenced MR-id in issues' activities only to authorized users.
- Enforce max chars and max render time in markdown math.
- Check permissions before responding in MergeController#pipeline_status.
- Remove EXIF from users/personal snippet uploads.
- Fix project import restricted visibility bypass via API.
- Fix weak session management by clearing password reset tokens after login (username/email) are updated.
- Fix SSRF via DNS rebinding in Kubernetes Integration.
## 12.2.2
- Unreleased due to QA failure.
## 12.2.1
 
### Fixed (3 changes)
......@@ -591,6 +623,34 @@ entry.
- Removes EE differences for app/views/admin/users/show.html.haml.
 
 
## 12.0.7
### Security (22 changes)
- Ensure only authorised users can create notes on Merge Requests and Issues.
- Add :login_recaptcha_protection_enabled setting to prevent bots from brute-force attacks.
- Queries for Upload should be scoped by model.
- Speed up regexp in namespace format by failing fast after reaching maximum namespace depth.
- Limit the size of issuable description and comments.
- Send TODOs for comments on commits correctly.
- Restrict MergeRequests#test_reports to authenticated users with read-access on Builds.
- Added image proxy to mitigate potential stealing of IP addresses.
- Filter out old system notes for epics in notes api endpoint response.
- Avoid exposing unaccessible repo data upon GFM post processing.
- Fix HTML injection for label description.
- Make sure HTML text is always escaped when replacing label/milestone references.
- Prevent DNS rebind on JIRA service integration.
- Use admin_group authorization in Groups::RunnersController.
- Prevent disclosure of merge request ID via email.
- Show cross-referenced MR-id in issues' activities only to authorized users.
- Enforce max chars and max render time in markdown math.
- Check permissions before responding in MergeController#pipeline_status.
- Remove EXIF from users/personal snippet uploads.
- Fix project import restricted visibility bypass via API.
- Fix weak session management by clearing password reset tokens after login (username/email) are updated.
- Fix SSRF via DNS rebinding in Kubernetes Integration.
## 12.0.6
 
- No changes.
......
import $ from 'jquery';
import { __ } from '~/locale';
import flash from '~/flash';
import { s__, sprintf } from '~/locale';
// Renders math using KaTeX in any element with the
// `js-render-math` class
......@@ -10,21 +9,131 @@ import flash from '~/flash';
// <code class="js-render-math"></div>
//
// Loop over all math elements and render math
function renderWithKaTeX(elements, katex) {
elements.each(function katexElementsLoop() {
const mathNode = $('<span></span>');
const $this = $(this);
const display = $this.attr('data-math-style') === 'display';
try {
katex.render($this.text(), mathNode.get(0), { displayMode: display, throwOnError: false });
mathNode.insertAfter($this);
$this.remove();
} catch (err) {
throw err;
const MAX_MATH_CHARS = 1000;
const MAX_RENDER_TIME_MS = 2000;
// These messages might be used with inline errors in the future. Keep them around. For now, we will
// display a single error message using flash().
// const CHAR_LIMIT_EXCEEDED_MSG = sprintf(
// s__(
// 'math|The following math is too long. For performance reasons, math blocks are limited to %{maxChars} characters. Try splitting up this block, or include an image instead.',
// ),
// { maxChars: MAX_MATH_CHARS },
// );
// const RENDER_TIME_EXCEEDED_MSG = s__(
// "math|The math in this entry is taking too long to render. Any math below this point won't be shown. Consider splitting it among multiple entries.",
// );
const RENDER_FLASH_MSG = sprintf(
s__(
'math|The math in this entry is taking too long to render and may not be displayed as expected. For performance reasons, math blocks are also limited to %{maxChars} characters. Consider splitting up large formulae, splitting math blocks among multiple entries, or using an image instead.',
),
{ maxChars: MAX_MATH_CHARS },
);
// Wait for the browser to reflow the layout. Reflowing SVG takes time.
// This has to wrap the inner function, otherwise IE/Edge throw "invalid calling object".
const waitForReflow = fn => {
window.requestAnimationFrame(fn);
};
/**
* Renders math blocks sequentially while protecting against DoS attacks. Math blocks have a maximum character limit of MAX_MATH_CHARS. If rendering math takes longer than MAX_RENDER_TIME_MS, all subsequent math blocks are skipped and an error message is shown.
*/
class SafeMathRenderer {
/*
How this works:
The performance bottleneck in rendering math is in the browser trying to reflow the generated SVG.
During this time, the JS is blocked and the page becomes unresponsive.
We want to render math blocks one by one until a certain time is exceeded, after which we stop
rendering subsequent math blocks, to protect against DoS. However, browsers do reflowing in an
asynchronous task, so we can't time it synchronously.
SafeMathRenderer essentially does the following:
1. Replaces all math blocks with placeholders so that they're not mistakenly rendered twice.
2. Places each placeholder element in a queue.
3. Renders the element at the head of the queue and waits for reflow.
4. After reflow, gets the elapsed time since step 3 and repeats step 3 until the queue is empty.
*/
queue = [];
totalMS = 0;
constructor(elements, katex) {
this.elements = elements;
this.katex = katex;
this.renderElement = this.renderElement.bind(this);
this.render = this.render.bind(this);
}
renderElement() {
if (!this.queue.length) {
return;
}
});
const el = this.queue.shift();
const text = el.textContent;
el.removeAttribute('style');
if (this.totalMS >= MAX_RENDER_TIME_MS || text.length > MAX_MATH_CHARS) {
if (!this.flashShown) {
flash(RENDER_FLASH_MSG);
this.flashShown = true;
}
// Show unrendered math code
const codeElement = document.createElement('pre');
codeElement.className = 'code';
codeElement.textContent = el.textContent;
el.parentNode.replaceChild(codeElement, el);
// Render the next math
this.renderElement();
} else {
this.startTime = Date.now();
try {
el.innerHTML = this.katex.renderToString(text, {
displayMode: el.getAttribute('data-math-style') === 'display',
throwOnError: true,
maxSize: 20,
maxExpand: 20,
});
} catch {
// Don't show a flash for now because it would override an existing flash message
el.textContent = s__('math|There was an error rendering this math block');
// el.style.color = '#d00';
el.className = 'katex-error';
}
// Give the browser time to reflow the svg
waitForReflow(() => {
const deltaTime = Date.now() - this.startTime;
this.totalMS += deltaTime;
this.renderElement();
});
}
}
render() {
// Replace math blocks with a placeholder so they aren't rendered twice
this.elements.forEach(el => {
const placeholder = document.createElement('span');
placeholder.style.display = 'none';
placeholder.setAttribute('data-math-style', el.getAttribute('data-math-style'));
placeholder.textContent = el.textContent;
el.parentNode.replaceChild(placeholder, el);
this.queue.push(placeholder);
});
// If we wait for the browser thread to settle down a bit, math rendering becomes 5-10x faster
// and less prone to timeouts.
setTimeout(this.renderElement, 400);
}
}
export default function renderMath($els) {
......@@ -34,7 +143,8 @@ export default function renderMath($els) {
import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.min.css'),
])
.then(([katex]) => {
renderWithKaTeX($els, katex);
const renderer = new SafeMathRenderer($els.get(), katex);
renderer.render();
})
.catch(() => flash(__('An error occurred while rendering KaTeX')));
.catch(() => {});
}
<script>
import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import Flash from '../../../flash';
import { __ } from '../../../locale';
import boardsStore from '../../stores/boards_store';
export default Vue.extend({
export default {
props: {
issue: {
type: Object,
......@@ -35,7 +35,7 @@ export default Vue.extend({
}
// Post the remove data
Vue.http.patch(this.updateUrl, data).catch(() => {
axios.patch(this.updateUrl, data).catch(() => {
Flash(__('Failed to remove issue from board, please try again.'));
lists.forEach(list => {
......@@ -71,7 +71,7 @@ export default Vue.extend({
return req;
},
},
});
};
</script>
<template>
<div class="block list">
......
......@@ -55,7 +55,7 @@ export default class ClusterStore {
...applicationInitialState,
title: s__('ClusterIntegration|GitLab Runner'),
version: null,
chartRepo: 'https://gitlab.com/charts/gitlab-runner',
chartRepo: 'https://gitlab.com/gitlab-org/charts/gitlab-runner',
updateAvailable: null,
updateSuccessful: false,
updateFailed: false,
......
......@@ -60,7 +60,7 @@ class DropLab {
addEvents() {
this.eventWrapper.documentClicked = this.documentClicked.bind(this);
document.addEventListener('click', this.eventWrapper.documentClicked);
document.addEventListener('mousedown', this.eventWrapper.documentClicked);
}
documentClicked(e) {
......@@ -74,7 +74,7 @@ class DropLab {
}
removeEvents() {
document.removeEventListener('click', this.eventWrapper.documentClicked);
document.removeEventListener('mousedown', this.eventWrapper.documentClicked);
}
changeHookList(trigger, list, plugins, config) {
......
......@@ -44,7 +44,7 @@ export default {
<template>
<div class="flash-container flash-container-page" @click="clickFlash">
<div class="flash-alert">
<div class="flash-alert" data-qa-selector="flash_alert">
<span v-html="message.text"> </span>
<button
v-if="message.action"
......
......@@ -89,7 +89,7 @@ export default {
</script>
<template>
<div class="multi-file-commit-panel ide-right-sidebar">
<div class="multi-file-commit-panel ide-right-sidebar" data-qa-selector="ide_right_sidebar">
<resizable-panel
v-show="isOpen"
:collapsible="false"
......@@ -120,6 +120,7 @@ export default {
}"
data-container="body"
data-placement="left"
:data-qa-selector="`${tab.title.toLowerCase()}_tab_button`"
class="ide-sidebar-link is-right"
type="button"
@click="clickTab($event, tab)"
......
......@@ -300,9 +300,9 @@ export default {
this.closeRecaptcha();
},
deleteIssuable() {
deleteIssuable(payload) {
this.service
.deleteIssuable()
.deleteIssuable(payload)
.then(res => res.data)
.then(data => {
// Stop the poll so we don't get 404's with the issuable not existing
......
......@@ -55,7 +55,7 @@ export default {
if (window.confirm(confirmMessage)) {
this.deleteLoading = true;
eventHub.$emit('delete.issuable');
eventHub.$emit('delete.issuable', { destroy_confirm: true });
}
},
},
......
......@@ -10,8 +10,8 @@ export default class Service {
return axios.get(this.realtimeEndpoint);
}
deleteIssuable() {
return axios.delete(this.endpoint);
deleteIssuable(payload) {
return axios.delete(this.endpoint, { params: payload });
}
updateIssuable(data) {
......
......@@ -113,7 +113,7 @@ export default class ProjectFindFile {
if (searchText) {
matches = fuzzaldrinPlus.match(filePath, searchText);
}
blobItemUrl = this.options.blobUrlTemplate + '/' + filePath;
blobItemUrl = this.options.blobUrlTemplate + '/' + encodeURIComponent(filePath);
html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl);
results.push(this.element.find('.tree-table > tbody').append(html));
}
......
......@@ -53,7 +53,7 @@ export default {
};
</script>
<template>
<div class="card">
<div :id="release.tag_name" class="card">
<div class="card-body">
<h2 class="card-title mt-0">
{{ release.name }}
......
......@@ -5,11 +5,7 @@ export const WARNING_MESSAGE_CLASS = 'warning_message';
export const DANGER_MESSAGE_CLASS = 'danger_message';
export const MWPS_MERGE_STRATEGY = 'merge_when_pipeline_succeeds';
export const ATMTWPS_MERGE_STRATEGY = 'add_to_merge_train_when_pipeline_succeeds';
export const MTWPS_MERGE_STRATEGY = 'add_to_merge_train_when_pipeline_succeeds';
export const MT_MERGE_STRATEGY = 'merge_train';
export const AUTO_MERGE_STRATEGIES = [
MWPS_MERGE_STRATEGY,
ATMTWPS_MERGE_STRATEGY,
MT_MERGE_STRATEGY,
];
export const AUTO_MERGE_STRATEGIES = [MWPS_MERGE_STRATEGY, MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY];
......@@ -3,7 +3,7 @@ import _ from 'underscore';
import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key';
import { stateKey } from './state_maps';
import { formatDate } from '../../lib/utils/datetime_utility';
import { ATMTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY, MWPS_MERGE_STRATEGY } from '../constants';
import { MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY, MWPS_MERGE_STRATEGY } from '../constants';
export default class MergeRequestStore {
constructor(data) {
......@@ -217,8 +217,8 @@ export default class MergeRequestStore {
}
static getPreferredAutoMergeStrategy(availableAutoMergeStrategies) {
if (_.includes(availableAutoMergeStrategies, ATMTWPS_MERGE_STRATEGY)) {
return ATMTWPS_MERGE_STRATEGY;
if (_.includes(availableAutoMergeStrategies, MTWPS_MERGE_STRATEGY)) {
return MTWPS_MERGE_STRATEGY;
} else if (_.includes(availableAutoMergeStrategies, MT_MERGE_STRATEGY)) {
return MT_MERGE_STRATEGY;
} else if (_.includes(availableAutoMergeStrategies, MWPS_MERGE_STRATEGY)) {
......
......@@ -45,8 +45,7 @@ input[type='checkbox']:hover {
border: 0;
border-radius: $border-radius-default;
transition: border-color ease-in-out $default-transition-duration,
background-color ease-in-out $default-transition-duration,
width ease-in-out $default-transition-duration;
background-color ease-in-out $default-transition-duration;
@include media-breakpoint-up(xl) {
width: $search-input-xl-width;
......
......@@ -6,6 +6,7 @@ module IssuableActions
included do
before_action :authorize_destroy_issuable!, only: :destroy
before_action :check_destroy_confirmation!, only: :destroy
before_action :authorize_admin_issuable!, only: :bulk_update
before_action only: :show do
push_frontend_feature_flag(:scoped_labels, default_enabled: true)
......@@ -91,6 +92,33 @@ def destroy
end
end
def check_destroy_confirmation!
return true if params[:destroy_confirm]
error_message = "Destroy confirmation not provided for #{issuable.human_class_name}"
exception = RuntimeError.new(error_message)
Gitlab::Sentry.track_acceptable_exception(
exception,
extra: {
project_path: issuable.project.full_path,
issuable_type: issuable.class.name,
issuable_id: issuable.id
}
)
index_path = polymorphic_path([parent, issuable.class])
respond_to do |format|
format.html do
flash[:notice] = error_message
redirect_to index_path
end
format.json do
render json: { errors: error_message }, status: :unprocessable_entity
end
end
end
def bulk_update
result = Issuable::BulkUpdateService.new(current_user, bulk_update_params).execute(resource_name)
quantity = result[:count]
......@@ -110,7 +138,7 @@ def discussions
end
notes = prepare_notes_for_rendering(notes)
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
notes = notes.select { |n| n.visible_for?(current_user) }
discussions = Discussion.build_collection(notes, issuable)
......
......@@ -29,7 +29,7 @@ def index
end
notes = prepare_notes_for_rendering(notes)
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
notes = notes.select { |n| n.visible_for?(current_user) }
notes_json[:notes] =
if use_note_serializer?
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment