Merge branch 'master' of dev.gitlab.org:gitlab/gitlabhq
This commit is contained in:
commit
7698d40550
60
CHANGELOG.md
60
CHANGELOG.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.
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
1.60.0
|
||||
1.61.0
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
8.9.0
|
||||
8.8.1
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
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 MAX_MATH_CHARS = 1000;
|
||||
const MAX_RENDER_TIME_MS = 2000;
|
||||
|
||||
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;
|
||||
// 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(() => {});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ module IssuableActions
|
|||
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 @@ module NotesActions
|
|||
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?
|
||||
|
|
|
|||
|
|
@ -127,4 +127,8 @@ module UploadsActions
|
|||
def model
|
||||
strong_memoize(:model) { find_model }
|
||||
end
|
||||
|
||||
def workhorse_authorize_request?
|
||||
action_name == 'authorize'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
class Groups::RunnersController < Groups::ApplicationController
|
||||
# Proper policies should be implemented per
|
||||
# https://gitlab.com/gitlab-org/gitlab-ce/issues/45894
|
||||
before_action :authorize_admin_pipeline!
|
||||
before_action :authorize_admin_group!
|
||||
|
||||
before_action :runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
|
||||
|
||||
|
|
@ -50,10 +50,6 @@ class Groups::RunnersController < Groups::ApplicationController
|
|||
@runner ||= @group.runners.find(params[:id])
|
||||
end
|
||||
|
||||
def authorize_admin_pipeline!
|
||||
return render_404 unless can?(current_user, :admin_pipeline, group)
|
||||
end
|
||||
|
||||
def runner_params
|
||||
params.require(:runner).permit(Ci::Runner::FORM_EDITABLE)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
skip_before_action :merge_request, only: [:index, :bulk_update]
|
||||
before_action :whitelist_query_limiting, only: [:assign_related_issues, :update]
|
||||
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
|
||||
before_action :authorize_test_reports!, only: [:test_reports]
|
||||
before_action :set_issuables_index, only: [:index]
|
||||
before_action :authenticate_user!, only: [:assign_related_issues]
|
||||
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
|
||||
|
|
@ -189,7 +190,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
def pipeline_status
|
||||
render json: PipelineSerializer
|
||||
.new(project: @project, current_user: @current_user)
|
||||
.represent_status(@merge_request.head_pipeline)
|
||||
.represent_status(head_pipeline)
|
||||
end
|
||||
|
||||
def ci_environments_status
|
||||
|
|
@ -239,6 +240,13 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
|
||||
private
|
||||
|
||||
def head_pipeline
|
||||
strong_memoize(:head_pipeline) do
|
||||
pipeline = @merge_request.head_pipeline
|
||||
pipeline if can?(current_user, :read_pipeline, pipeline)
|
||||
end
|
||||
end
|
||||
|
||||
def ci_environments_status_on_merge_result?
|
||||
params[:environment_target] == 'merge_commit'
|
||||
end
|
||||
|
|
@ -337,4 +345,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
render json: { status_reason: 'Unknown error' }, status: :internal_server_error
|
||||
end
|
||||
end
|
||||
|
||||
def authorize_test_reports!
|
||||
# MergeRequest#actual_head_pipeline is the pipeline accessed in MergeRequest#compare_reports.
|
||||
return render_404 unless can?(current_user, :read_build, merge_request.actual_head_pipeline)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,10 +21,13 @@ class SessionsController < Devise::SessionsController
|
|||
prepend_before_action :ensure_password_authentication_enabled!, if: -> { action_name == 'create' && password_based_login? }
|
||||
|
||||
before_action :auto_sign_in_with_provider, only: [:new]
|
||||
before_action :store_unauthenticated_sessions, only: [:new]
|
||||
before_action :save_failed_login, if: :action_new_and_failed_login?
|
||||
before_action :load_recaptcha
|
||||
|
||||
after_action :log_failed_login, if: -> { action_name == 'new' && failed_login? }
|
||||
helper_method :captcha_enabled?
|
||||
after_action :log_failed_login, if: :action_new_and_failed_login?
|
||||
|
||||
helper_method :captcha_enabled?, :captcha_on_login_required?
|
||||
|
||||
# protect_from_forgery is already prepended in ApplicationController but
|
||||
# authenticate_with_two_factor which signs in the user is prepended before
|
||||
|
|
@ -38,6 +41,7 @@ class SessionsController < Devise::SessionsController
|
|||
protect_from_forgery with: :exception, prepend: true
|
||||
|
||||
CAPTCHA_HEADER = 'X-GitLab-Show-Login-Captcha'.freeze
|
||||
MAX_FAILED_LOGIN_ATTEMPTS = 5
|
||||
|
||||
def new
|
||||
set_minimum_password_length
|
||||
|
|
@ -81,10 +85,14 @@ class SessionsController < Devise::SessionsController
|
|||
request.headers[CAPTCHA_HEADER] && Gitlab::Recaptcha.enabled?
|
||||
end
|
||||
|
||||
def captcha_on_login_required?
|
||||
Gitlab::Recaptcha.enabled_on_login? && unverified_anonymous_user?
|
||||
end
|
||||
|
||||
# From https://github.com/plataformatec/devise/wiki/How-To:-Use-Recaptcha-with-Devise#devisepasswordscontroller
|
||||
def check_captcha
|
||||
return unless user_params[:password].present?
|
||||
return unless captcha_enabled?
|
||||
return unless captcha_enabled? || captcha_on_login_required?
|
||||
return unless Gitlab::Recaptcha.load_configurations!
|
||||
|
||||
if verify_recaptcha
|
||||
|
|
@ -126,10 +134,28 @@ class SessionsController < Devise::SessionsController
|
|||
Gitlab::AppLogger.info("Failed Login: username=#{user_params[:login]} ip=#{request.remote_ip}")
|
||||
end
|
||||
|
||||
def action_new_and_failed_login?
|
||||
action_name == 'new' && failed_login?
|
||||
end
|
||||
|
||||
def save_failed_login
|
||||
session[:failed_login_attempts] ||= 0
|
||||
session[:failed_login_attempts] += 1
|
||||
end
|
||||
|
||||
def failed_login?
|
||||
(options = request.env["warden.options"]) && options[:action] == "unauthenticated"
|
||||
end
|
||||
|
||||
# storing sessions per IP lets us check if there are associated multiple
|
||||
# anonymous sessions with one IP and prevent situations when there are
|
||||
# multiple attempts of logging in
|
||||
def store_unauthenticated_sessions
|
||||
return if current_user
|
||||
|
||||
Gitlab::AnonymousSession.new(request.remote_ip, session_id: request.session.id).store_session_id_per_ip
|
||||
end
|
||||
|
||||
# Handle an "initial setup" state, where there's only one user, it's an admin,
|
||||
# and they require a password change.
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
|
|
@ -240,6 +266,18 @@ class SessionsController < Devise::SessionsController
|
|||
@ldap_servers ||= Gitlab::Auth::LDAP::Config.available_servers
|
||||
end
|
||||
|
||||
def unverified_anonymous_user?
|
||||
exceeded_failed_login_attempts? || exceeded_anonymous_sessions?
|
||||
end
|
||||
|
||||
def exceeded_failed_login_attempts?
|
||||
session.fetch(:failed_login_attempts, 0) > MAX_FAILED_LOGIN_ATTEMPTS
|
||||
end
|
||||
|
||||
def exceeded_anonymous_sessions?
|
||||
Gitlab::AnonymousSession.new(request.remote_ip).stored_sessions >= MAX_FAILED_LOGIN_ATTEMPTS
|
||||
end
|
||||
|
||||
def authentication_method
|
||||
if user_params[:otp_attempt]
|
||||
"two-factor"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
class UploadsController < ApplicationController
|
||||
include UploadsActions
|
||||
include WorkhorseRequest
|
||||
|
||||
UnknownUploadModelError = Class.new(StandardError)
|
||||
|
||||
|
|
@ -21,7 +22,8 @@ class UploadsController < ApplicationController
|
|||
before_action :upload_mount_satisfied?
|
||||
before_action :find_model
|
||||
before_action :authorize_access!, only: [:show]
|
||||
before_action :authorize_create_access!, only: [:create]
|
||||
before_action :authorize_create_access!, only: [:create, :authorize]
|
||||
before_action :verify_workhorse_api!, only: [:authorize]
|
||||
|
||||
def uploader_class
|
||||
PersonalFileUploader
|
||||
|
|
@ -72,7 +74,7 @@ class UploadsController < ApplicationController
|
|||
end
|
||||
|
||||
def render_unauthorized
|
||||
if current_user
|
||||
if current_user || workhorse_authorize_request?
|
||||
render_404
|
||||
else
|
||||
authenticate_user!
|
||||
|
|
|
|||
|
|
@ -164,6 +164,10 @@ module ApplicationSettingsHelper
|
|||
:allow_local_requests_from_system_hooks,
|
||||
:dns_rebinding_protection_enabled,
|
||||
:archive_builds_in_human_readable,
|
||||
:asset_proxy_enabled,
|
||||
:asset_proxy_secret_key,
|
||||
:asset_proxy_url,
|
||||
:asset_proxy_whitelist,
|
||||
:authorized_keys_enabled,
|
||||
:auto_devops_enabled,
|
||||
:auto_devops_domain,
|
||||
|
|
@ -231,6 +235,7 @@ module ApplicationSettingsHelper
|
|||
:recaptcha_enabled,
|
||||
:recaptcha_private_key,
|
||||
:recaptcha_site_key,
|
||||
:login_recaptcha_protection_enabled,
|
||||
:receive_max_input_size,
|
||||
:repository_checks_enabled,
|
||||
:repository_storages,
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ module EmailsHelper
|
|||
when MergeRequest
|
||||
merge_request = MergeRequest.find(closed_via[:id]).present
|
||||
|
||||
return "" unless Ability.allowed?(@recipient, :read_merge_request, merge_request)
|
||||
|
||||
case format
|
||||
when :html
|
||||
merge_request_link = link_to(merge_request.to_reference, merge_request.web_url)
|
||||
|
|
@ -102,6 +104,8 @@ module EmailsHelper
|
|||
# Technically speaking this should be Commit but per
|
||||
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15610#note_163812339
|
||||
# we can't deserialize Commit without custom serializer for ActiveJob
|
||||
return "" unless Ability.allowed?(@recipient, :download_code, @project)
|
||||
|
||||
_("via %{closed_via}") % { closed_via: closed_via }
|
||||
else
|
||||
""
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ module LabelsHelper
|
|||
end
|
||||
|
||||
def label_tooltip_title(label)
|
||||
label.description
|
||||
Sanitize.clean(label.description)
|
||||
end
|
||||
|
||||
def suggested_colors
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ module Emails
|
|||
setup_issue_mail(issue_id, recipient_id, closed_via: closed_via)
|
||||
|
||||
@updated_by = User.find(updated_by_user_id)
|
||||
@recipient = User.find(recipient_id)
|
||||
|
||||
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -18,12 +18,19 @@ class ApplicationSetting < ApplicationRecord
|
|||
# fix a lot of tests using allow_any_instance_of
|
||||
include ApplicationSettingImplementation
|
||||
|
||||
attr_encrypted :asset_proxy_secret_key,
|
||||
mode: :per_attribute_iv,
|
||||
insecure_mode: true,
|
||||
key: Settings.attr_encrypted_db_key_base_truncated,
|
||||
algorithm: 'aes-256-cbc'
|
||||
|
||||
serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :domain_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :asset_proxy_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
|
||||
|
||||
ignore_column :koding_url
|
||||
ignore_column :koding_enabled
|
||||
|
|
@ -75,11 +82,11 @@ class ApplicationSetting < ApplicationRecord
|
|||
|
||||
validates :recaptcha_site_key,
|
||||
presence: true,
|
||||
if: :recaptcha_enabled
|
||||
if: :recaptcha_or_login_protection_enabled
|
||||
|
||||
validates :recaptcha_private_key,
|
||||
presence: true,
|
||||
if: :recaptcha_enabled
|
||||
if: :recaptcha_or_login_protection_enabled
|
||||
|
||||
validates :akismet_api_key,
|
||||
presence: true,
|
||||
|
|
@ -192,6 +199,17 @@ class ApplicationSetting < ApplicationRecord
|
|||
allow_nil: true,
|
||||
numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than: 65536 }
|
||||
|
||||
validates :asset_proxy_url,
|
||||
presence: true,
|
||||
allow_blank: false,
|
||||
url: true,
|
||||
if: :asset_proxy_enabled?
|
||||
|
||||
validates :asset_proxy_secret_key,
|
||||
presence: true,
|
||||
allow_blank: false,
|
||||
if: :asset_proxy_enabled?
|
||||
|
||||
SUPPORTED_KEY_TYPES.each do |type|
|
||||
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
|
||||
end
|
||||
|
|
@ -292,4 +310,8 @@ class ApplicationSetting < ApplicationRecord
|
|||
def self.cache_backend
|
||||
Gitlab::ThreadMemoryCache.cache_backend
|
||||
end
|
||||
|
||||
def recaptcha_or_login_protection_enabled
|
||||
recaptcha_enabled || login_recaptcha_protection_enabled
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,8 +23,9 @@ module ApplicationSettingImplementation
|
|||
akismet_enabled: false,
|
||||
allow_local_requests_from_web_hooks_and_services: false,
|
||||
allow_local_requests_from_system_hooks: true,
|
||||
dns_rebinding_protection_enabled: true,
|
||||
asset_proxy_enabled: false,
|
||||
authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand
|
||||
commit_email_hostname: default_commit_email_hostname,
|
||||
container_registry_token_expire_delay: 5,
|
||||
default_artifacts_expire_in: '30 days',
|
||||
default_branch_protection: Settings.gitlab['default_branch_protection'],
|
||||
|
|
@ -33,7 +34,9 @@ module ApplicationSettingImplementation
|
|||
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
|
||||
default_projects_limit: Settings.gitlab['default_projects_limit'],
|
||||
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
|
||||
diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
|
||||
disabled_oauth_sign_in_sources: [],
|
||||
dns_rebinding_protection_enabled: true,
|
||||
domain_whitelist: Settings.gitlab['domain_whitelist'],
|
||||
dsa_key_restriction: 0,
|
||||
ecdsa_key_restriction: 0,
|
||||
|
|
@ -52,9 +55,11 @@ module ApplicationSettingImplementation
|
|||
housekeeping_gc_period: 200,
|
||||
housekeeping_incremental_repack_period: 10,
|
||||
import_sources: Settings.gitlab['import_sources'],
|
||||
local_markdown_version: 0,
|
||||
max_artifacts_size: Settings.artifacts['max_size'],
|
||||
max_attachment_size: Settings.gitlab['max_attachment_size'],
|
||||
mirror_available: true,
|
||||
outbound_local_requests_whitelist: [],
|
||||
password_authentication_enabled_for_git: true,
|
||||
password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'],
|
||||
performance_bar_allowed_group_id: nil,
|
||||
|
|
@ -63,7 +68,10 @@ module ApplicationSettingImplementation
|
|||
plantuml_url: nil,
|
||||
polling_interval_multiplier: 1,
|
||||
project_export_enabled: true,
|
||||
protected_ci_variables: false,
|
||||
raw_blob_request_limit: 300,
|
||||
recaptcha_enabled: false,
|
||||
login_recaptcha_protection_enabled: false,
|
||||
repository_checks_enabled: true,
|
||||
repository_storages: ['default'],
|
||||
require_two_factor_authentication: false,
|
||||
|
|
@ -95,16 +103,10 @@ module ApplicationSettingImplementation
|
|||
user_default_internal_regex: nil,
|
||||
user_show_add_ssh_key_message: true,
|
||||
usage_stats_set_by_user_id: nil,
|
||||
diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
|
||||
commit_email_hostname: default_commit_email_hostname,
|
||||
snowplow_collector_hostname: nil,
|
||||
snowplow_cookie_domain: nil,
|
||||
snowplow_enabled: false,
|
||||
snowplow_site_id: nil,
|
||||
protected_ci_variables: false,
|
||||
local_markdown_version: 0,
|
||||
outbound_local_requests_whitelist: [],
|
||||
raw_blob_request_limit: 300
|
||||
snowplow_site_id: nil
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -198,6 +200,15 @@ module ApplicationSettingImplementation
|
|||
end
|
||||
end
|
||||
|
||||
def asset_proxy_whitelist=(values)
|
||||
values = domain_strings_to_array(values) if values.is_a?(String)
|
||||
|
||||
# make sure we always whitelist the running host
|
||||
values << Gitlab.config.gitlab.host unless values.include?(Gitlab.config.gitlab.host)
|
||||
|
||||
self[:asset_proxy_whitelist] = values
|
||||
end
|
||||
|
||||
def repository_storages
|
||||
Array(read_attribute(:repository_storages))
|
||||
end
|
||||
|
|
@ -306,6 +317,7 @@ module ApplicationSettingImplementation
|
|||
|
||||
values
|
||||
.split(DOMAIN_LIST_SEPARATOR)
|
||||
.map(&:strip)
|
||||
.reject(&:empty?)
|
||||
.uniq
|
||||
end
|
||||
|
|
|
|||
|
|
@ -203,6 +203,7 @@ module Ci
|
|||
scope :for_sha, -> (sha) { where(sha: sha) }
|
||||
scope :for_source_sha, -> (source_sha) { where(source_sha: source_sha) }
|
||||
scope :for_sha_or_source_sha, -> (sha) { for_sha(sha).or(for_source_sha(sha)) }
|
||||
scope :created_after, -> (time) { where('ci_pipelines.created_at > ?', time) }
|
||||
|
||||
scope :triggered_by_merge_request, -> (merge_request) do
|
||||
where(source: :merge_request_event, merge_request: merge_request)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ module Issuable
|
|||
|
||||
validates :author, presence: true
|
||||
validates :title, presence: true, length: { maximum: 255 }
|
||||
validates :description, length: { maximum: Gitlab::Database::MAX_TEXT_SIZE_LIMIT }, allow_blank: true
|
||||
validate :milestone_is_valid
|
||||
|
||||
scope :authored, ->(user) { where(author_id: user) }
|
||||
|
|
|
|||
|
|
@ -365,6 +365,8 @@ class Group < Namespace
|
|||
end
|
||||
|
||||
def max_member_access_for_user(user)
|
||||
return GroupMember::NO_ACCESS unless user
|
||||
|
||||
return GroupMember::OWNER if user.admin?
|
||||
|
||||
members_with_parents
|
||||
|
|
|
|||
|
|
@ -199,7 +199,11 @@ class Label < ApplicationRecord
|
|||
end
|
||||
|
||||
def title=(value)
|
||||
write_attribute(:title, sanitize_title(value)) if value.present?
|
||||
write_attribute(:title, sanitize_value(value)) if value.present?
|
||||
end
|
||||
|
||||
def description=(value)
|
||||
write_attribute(:description, sanitize_value(value)) if value.present?
|
||||
end
|
||||
|
||||
##
|
||||
|
|
@ -260,7 +264,7 @@ class Label < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def sanitize_title(value)
|
||||
def sanitize_value(value)
|
||||
CGI.unescapeHTML(Sanitize.clean(value.to_s))
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ class Note < ApplicationRecord
|
|||
delegate :title, to: :noteable, allow_nil: true
|
||||
|
||||
validates :note, presence: true
|
||||
validates :note, length: { maximum: Gitlab::Database::MAX_TEXT_SIZE_LIMIT }
|
||||
validates :project, presence: true, if: :for_project_noteable?
|
||||
|
||||
# Attachments are deprecated and are handled by Markdown uploader
|
||||
|
|
@ -331,6 +332,10 @@ class Note < ApplicationRecord
|
|||
cross_reference? && !all_referenced_mentionables_allowed?(user)
|
||||
end
|
||||
|
||||
def visible_for?(user)
|
||||
!cross_reference_not_visible_for?(user)
|
||||
end
|
||||
|
||||
def award_emoji?
|
||||
can_be_award_emoji? && contains_emoji_only?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -64,7 +64,12 @@ class JiraService < IssueTrackerService
|
|||
end
|
||||
|
||||
def client
|
||||
@client ||= JIRA::Client.new(options)
|
||||
@client ||= begin
|
||||
JIRA::Client.new(options).tap do |client|
|
||||
# Replaces JIRA default http client with our implementation
|
||||
client.request_client = Gitlab::Jira::HttpClient.new(client.options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def help
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class SystemNoteMetadata < ApplicationRecord
|
|||
TYPES_WITH_CROSS_REFERENCES = %w[
|
||||
commit cross_reference
|
||||
close duplicate
|
||||
moved
|
||||
moved merge
|
||||
].freeze
|
||||
|
||||
ICON_TYPES = %w[
|
||||
|
|
|
|||
|
|
@ -645,6 +645,13 @@ class User < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
# will_save_change_to_attribute? is used by Devise to check if it is necessary
|
||||
# to clear any existing reset_password_tokens before updating an authentication_key
|
||||
# and login in our case is a virtual attribute to allow login by username or email.
|
||||
def will_save_change_to_login?
|
||||
will_save_change_to_username? || will_save_change_to_email?
|
||||
end
|
||||
|
||||
def unique_email
|
||||
if !emails.exists?(email: email) && Email.exists?(email: email)
|
||||
errors.add(:email, _('has already been taken'))
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ class IssuePolicy < IssuablePolicy
|
|||
# Make sure to sync this class checks with issue.rb to avoid security problems.
|
||||
# Check commit 002ad215818450d2cbbc5fa065850a953dc7ada8 for more information.
|
||||
|
||||
extend ProjectPolicy::ClassMethods
|
||||
|
||||
desc "User can read confidential issues"
|
||||
condition(:can_read_confidential) do
|
||||
@user && IssueCollection.new([@subject]).visible_to(@user).any?
|
||||
|
|
@ -14,13 +16,12 @@ class IssuePolicy < IssuablePolicy
|
|||
condition(:confidential, scope: :subject) { @subject.confidential? }
|
||||
|
||||
rule { confidential & ~can_read_confidential }.policy do
|
||||
prevent :read_issue
|
||||
prevent(*create_read_update_admin_destroy(:issue))
|
||||
prevent :read_issue_iid
|
||||
prevent :update_issue
|
||||
prevent :admin_issue
|
||||
prevent :create_note
|
||||
end
|
||||
|
||||
rule { ~can?(:read_issue) }.prevent :create_note
|
||||
|
||||
rule { locked }.policy do
|
||||
prevent :reopen_issue
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,4 +4,10 @@ class MergeRequestPolicy < IssuablePolicy
|
|||
rule { locked }.policy do
|
||||
prevent :reopen_merge_request
|
||||
end
|
||||
|
||||
# Only users who can read the merge request can comment.
|
||||
# Although :read_merge_request is computed in the policy context,
|
||||
# it would not be safe to prevent :create_note there, since
|
||||
# note permissions are shared, and this would apply too broadly.
|
||||
rule { ~can?(:read_merge_request) }.prevent :create_note
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ module ApplicationSettings
|
|||
|
||||
attr_reader :params, :application_setting
|
||||
|
||||
MARKDOWN_CACHE_INVALIDATING_PARAMS = %w(asset_proxy_enabled asset_proxy_url asset_proxy_secret_key asset_proxy_whitelist).freeze
|
||||
|
||||
def execute
|
||||
validate_classification_label(application_setting, :external_authorization_service_default_label) unless bypass_external_auth?
|
||||
|
||||
|
|
@ -25,7 +27,13 @@ module ApplicationSettings
|
|||
params[:usage_stats_set_by_user_id] = current_user.id
|
||||
end
|
||||
|
||||
@application_setting.update(@params)
|
||||
@application_setting.assign_attributes(params)
|
||||
|
||||
if invalidate_markdown_cache?
|
||||
@application_setting[:local_markdown_version] = @application_setting.local_markdown_version + 1
|
||||
end
|
||||
|
||||
@application_setting.save
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -41,6 +49,11 @@ module ApplicationSettings
|
|||
@application_setting.add_to_outbound_local_requests_whitelist(values_array)
|
||||
end
|
||||
|
||||
def invalidate_markdown_cache?
|
||||
!params.key?(:local_markdown_version) &&
|
||||
(@application_setting.changes.keys & MARKDOWN_CACHE_INVALIDATING_PARAMS).any?
|
||||
end
|
||||
|
||||
def update_terms(terms)
|
||||
return unless terms.present?
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ module Ci
|
|||
Gitlab::Ci::Pipeline::Chain::Limit::Size,
|
||||
Gitlab::Ci::Pipeline::Chain::Populate,
|
||||
Gitlab::Ci::Pipeline::Chain::Create,
|
||||
Gitlab::Ci::Pipeline::Chain::Limit::Activity].freeze
|
||||
Gitlab::Ci::Pipeline::Chain::Limit::Activity,
|
||||
Gitlab::Ci::Pipeline::Chain::Limit::JobActivity].freeze
|
||||
|
||||
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, **options, &block)
|
||||
@pipeline = Ci::Pipeline.new
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ module Projects
|
|||
include ValidatesClassificationLabel
|
||||
|
||||
def initialize(user, params)
|
||||
@current_user, @params = user, params.dup
|
||||
@skip_wiki = @params.delete(:skip_wiki)
|
||||
@current_user, @params = user, params.dup
|
||||
@skip_wiki = @params.delete(:skip_wiki)
|
||||
@initialize_with_readme = Gitlab::Utils.to_boolean(@params.delete(:initialize_with_readme))
|
||||
@import_data = @params.delete(:import_data)
|
||||
@relations_block = @params.delete(:relations_block)
|
||||
end
|
||||
|
||||
def execute
|
||||
|
|
@ -15,14 +17,11 @@ module Projects
|
|||
return ::Projects::CreateFromTemplateService.new(current_user, params).execute
|
||||
end
|
||||
|
||||
import_data = params.delete(:import_data)
|
||||
relations_block = params.delete(:relations_block)
|
||||
|
||||
@project = Project.new(params)
|
||||
|
||||
# Make sure that the user is allowed to use the specified visibility level
|
||||
unless Gitlab::VisibilityLevel.allowed_for?(current_user, @project.visibility_level)
|
||||
deny_visibility_level(@project)
|
||||
if project_visibility.restricted?
|
||||
deny_visibility_level(@project, project_visibility.visibility_level)
|
||||
return @project
|
||||
end
|
||||
|
||||
|
|
@ -44,7 +43,7 @@ module Projects
|
|||
@project.namespace_id = current_user.namespace_id
|
||||
end
|
||||
|
||||
relations_block&.call(@project)
|
||||
@relations_block&.call(@project)
|
||||
yield(@project) if block_given?
|
||||
|
||||
validate_classification_label(@project, :external_authorization_classification_label)
|
||||
|
|
@ -54,7 +53,7 @@ module Projects
|
|||
|
||||
@project.creator = current_user
|
||||
|
||||
save_project_and_import_data(import_data)
|
||||
save_project_and_import_data
|
||||
|
||||
after_create_actions if @project.persisted?
|
||||
|
||||
|
|
@ -129,9 +128,9 @@ module Projects
|
|||
!@project.feature_available?(:wiki, current_user) || @skip_wiki
|
||||
end
|
||||
|
||||
def save_project_and_import_data(import_data)
|
||||
def save_project_and_import_data
|
||||
Project.transaction do
|
||||
@project.create_or_update_import_data(data: import_data[:data], credentials: import_data[:credentials]) if import_data
|
||||
@project.create_or_update_import_data(data: @import_data[:data], credentials: @import_data[:credentials]) if @import_data
|
||||
|
||||
if @project.save
|
||||
unless @project.gitlab_project_import?
|
||||
|
|
@ -192,5 +191,11 @@ module Projects
|
|||
fail(error: @project.errors.full_messages.join(', '))
|
||||
end
|
||||
end
|
||||
|
||||
def project_visibility
|
||||
@project_visibility ||= Gitlab::VisibilityLevelChecker
|
||||
.new(current_user, @project, project_params: { import_data: @import_data })
|
||||
.level_restricted?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -314,11 +314,9 @@ class TodoService
|
|||
end
|
||||
|
||||
def reject_users_without_access(users, parent, target)
|
||||
if target.is_a?(Note) && target.for_issuable?
|
||||
target = target.noteable
|
||||
end
|
||||
target = target.noteable if target.is_a?(Note)
|
||||
|
||||
if target.is_a?(Issuable)
|
||||
if target.respond_to?(:to_ability_name)
|
||||
select_users(users, :"read_#{target.to_ability_name}", target)
|
||||
else
|
||||
select_users(users, :read_project, parent)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ class PersonalFileUploader < FileUploader
|
|||
options.storage_path
|
||||
end
|
||||
|
||||
def self.workhorse_local_upload_path
|
||||
File.join(options.storage_path, 'uploads', TMP_UPLOAD_PATH)
|
||||
end
|
||||
|
||||
def self.base_dir(model, _store = nil)
|
||||
# base_dir is the path seen by the user when rendering Markdown, so
|
||||
# it should be the same for both local and object storage. It is
|
||||
|
|
|
|||
|
|
@ -7,11 +7,15 @@
|
|||
= f.check_box :recaptcha_enabled, class: 'form-check-input'
|
||||
= f.label :recaptcha_enabled, class: 'form-check-label' do
|
||||
Enable reCAPTCHA
|
||||
- recaptcha_v2_link_url = 'https://developers.google.com/recaptcha/docs/versions'
|
||||
- recaptcha_v2_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: recaptcha_v2_link_url }
|
||||
%span.form-text.text-muted#recaptcha_help_block
|
||||
= _('Helps prevent bots from creating accounts. We currently only support %{recaptcha_v2_link_start}reCAPTCHA v2%{recaptcha_v2_link_end}').html_safe % { recaptcha_v2_link_start: recaptcha_v2_link_start, recaptcha_v2_link_end: '</a>'.html_safe }
|
||||
|
||||
= _('Helps prevent bots from creating accounts.')
|
||||
.form-group
|
||||
.form-check
|
||||
= f.check_box :login_recaptcha_protection_enabled, class: 'form-check-input'
|
||||
= f.label :login_recaptcha_protection_enabled, class: 'form-check-label' do
|
||||
Enable reCAPTCHA for login
|
||||
%span.form-text.text-muted#recaptcha_help_block
|
||||
= _('Helps prevent bots from brute-force attacks.')
|
||||
.form-group
|
||||
= f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'label-bold'
|
||||
= f.text_field :recaptcha_site_key, class: 'form-control'
|
||||
|
|
@ -21,6 +25,7 @@
|
|||
|
||||
.form-group
|
||||
= f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'label-bold'
|
||||
.form-group
|
||||
= f.text_field :recaptcha_private_key, class: 'form-control'
|
||||
|
||||
.form-group
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@
|
|||
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
|
||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||
%p
|
||||
= _('Enable reCAPTCHA or Akismet and set IP limits.')
|
||||
- recaptcha_v2_link_url = 'https://developers.google.com/recaptcha/docs/versions'
|
||||
- recaptcha_v2_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: recaptcha_v2_link_url }
|
||||
= _('Enable reCAPTCHA or Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}').html_safe % { recaptcha_v2_link_start: recaptcha_v2_link_start, recaptcha_v2_link_end: '</a>'.html_safe }
|
||||
.settings-content
|
||||
= render 'spam'
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
- else
|
||||
= link_to _('Forgot your password?'), new_password_path(:user)
|
||||
%div
|
||||
- if captcha_enabled?
|
||||
- if captcha_enabled? || captcha_on_login_required?
|
||||
= recaptcha_tags
|
||||
|
||||
.submit-container.move-submit-down
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
title: Ensure only authorised users can create notes on Merge Requests and Issues
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add :login_recaptcha_protection_enabled setting to prevent bots from brute-force attacks.
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Speed up regexp in namespace format by failing fast after reaching maximum namespace depth
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Limit the size of issuable description and comments
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Send TODOs for comments on commits correctly
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Restrict MergeRequests#test_reports to authenticated users with read-access
|
||||
on Builds
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Added image proxy to mitigate potential stealing of IP addresses
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Filter out old system notes for epics in notes api endpoint response
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Avoid exposing unaccessible repo data upon GFM post processing
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix HTML injection for label description
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Make sure HTML text is always escaped when replacing label/milestone references.
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Prevent DNS rebind on JIRA service integration
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: "Gitaly: ignore git redirects"
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Use admin_group authorization in Groups::RunnersController
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Prevent disclosure of merge request ID via email
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Show cross-referenced MR-id in issues' activities only to authorized users
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Enforce max chars and max render time in markdown math
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Check permissions before responding in MergeController#pipeline_status
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove EXIF from users/personal snippet uploads.
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix project import restricted visibility bypass via API
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Fix weak session management by clearing password reset tokens after login (username/email)
|
||||
are updated
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix SSRF via DNS rebinding in Kubernetes Integration
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#
|
||||
# Asset proxy settings
|
||||
#
|
||||
ActiveSupport.on_load(:active_record) do
|
||||
Banzai::Filter::AssetProxyFilter.initialize_settings
|
||||
end
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
if Shard.connected? && !Gitlab::Database.read_only?
|
||||
# The `table_exists?` check is needed because during our migration rollback testing,
|
||||
# `Shard.connected?` could be cached and return true even though the table doesn't exist
|
||||
if Shard.connected? && Shard.table_exists? && !Gitlab::Database.read_only?
|
||||
Shard.populate!
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RestClient
|
||||
class Request
|
||||
attr_accessor :hostname_override
|
||||
|
||||
module UrlBlocker
|
||||
def transmit(uri, req, payload, &block)
|
||||
begin
|
||||
ip, hostname_override = Gitlab::UrlBlocker.validate!(uri, allow_local_network: allow_settings_local_requests?,
|
||||
allow_localhost: allow_settings_local_requests?,
|
||||
dns_rebind_protection: dns_rebind_protection?)
|
||||
|
||||
self.hostname_override = hostname_override
|
||||
rescue Gitlab::UrlBlocker::BlockedUrlError => e
|
||||
raise ArgumentError, "URL '#{uri}' is blocked: #{e.message}"
|
||||
end
|
||||
|
||||
# Gitlab::UrlBlocker returns a Addressable::URI which we need to coerce
|
||||
# to URI so that rest-client can use it to determine if it's a
|
||||
# URI::HTTPS or not. It uses it to set `net.use_ssl` to true or not:
|
||||
#
|
||||
# https://github.com/rest-client/rest-client/blob/f450a0f086f1cd1049abbef2a2c66166a1a9ba71/lib/restclient/request.rb#L656
|
||||
ip_as_uri = URI.parse(ip)
|
||||
super(ip_as_uri, req, payload, &block)
|
||||
end
|
||||
|
||||
def net_http_object(hostname, port)
|
||||
super.tap do |http|
|
||||
http.hostname_override = hostname_override if hostname_override
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dns_rebind_protection?
|
||||
return false if Gitlab.http_proxy_env?
|
||||
|
||||
Gitlab::CurrentSettings.dns_rebinding_protection_enabled?
|
||||
end
|
||||
|
||||
def allow_settings_local_requests?
|
||||
Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
|
||||
end
|
||||
end
|
||||
|
||||
prepend UrlBlocker
|
||||
end
|
||||
end
|
||||
|
|
@ -19,6 +19,7 @@ Rails.application.configure do |config|
|
|||
|
||||
Warden::Manager.after_authentication(scope: :user) do |user, auth, opts|
|
||||
ActiveSession.cleanup(user)
|
||||
Gitlab::AnonymousSession.new(auth.request.remote_ip, session_id: auth.request.session.id).cleanup_session_per_ip_entries
|
||||
end
|
||||
|
||||
Warden::Manager.after_set_user(scope: :user, only: :fetch) do |user, auth, opts|
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ scope path: :uploads do
|
|||
to: 'uploads#create',
|
||||
constraints: { model: /personal_snippet|user/, id: /\d+/ },
|
||||
as: 'upload'
|
||||
|
||||
post ':model/authorize',
|
||||
to: 'uploads#authorize',
|
||||
constraints: { model: /personal_snippet|user/ }
|
||||
end
|
||||
|
||||
# Redirect old note attachments path to new uploads path.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddAssetProxySettings < ActiveRecord::Migration[5.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
# Set this constant to true if this migration requires downtime.
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column :application_settings, :asset_proxy_enabled, :boolean, default: false, null: false
|
||||
add_column :application_settings, :asset_proxy_url, :string # rubocop:disable Migration/AddLimitToStringColumns
|
||||
add_column :application_settings, :asset_proxy_whitelist, :text
|
||||
add_column :application_settings, :encrypted_asset_proxy_secret_key, :text
|
||||
add_column :application_settings, :encrypted_asset_proxy_secret_key_iv, :string # rubocop:disable Migration/AddLimitToStringColumns
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class AddLoginRecaptchaProtectionEnabledToApplicationSettings < ActiveRecord::Migration[5.1]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column :application_settings, :login_recaptcha_protection_enabled, :boolean, default: false, null: false
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddActiveJobsLimitToPlans < ActiveRecord::Migration[5.2]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_column_with_default :plans, :active_jobs_limit, :integer, default: 0
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :plans, :active_jobs_limit
|
||||
end
|
||||
end
|
||||
|
|
@ -272,12 +272,18 @@ ActiveRecord::Schema.define(version: 2019_08_28_083843) do
|
|||
t.boolean "lock_memberships_to_ldap", default: false, null: false
|
||||
t.boolean "time_tracking_limit_to_hours", default: false, null: false
|
||||
t.string "grafana_url", default: "/-/grafana", null: false
|
||||
t.boolean "login_recaptcha_protection_enabled", default: false, null: false
|
||||
t.string "outbound_local_requests_whitelist", limit: 255, default: [], null: false, array: true
|
||||
t.integer "raw_blob_request_limit", default: 300, null: false
|
||||
t.boolean "allow_local_requests_from_web_hooks_and_services", default: false, null: false
|
||||
t.boolean "allow_local_requests_from_system_hooks", default: true, null: false
|
||||
t.bigint "instance_administration_project_id"
|
||||
t.string "snowplow_collector_hostname"
|
||||
t.boolean "asset_proxy_enabled", default: false, null: false
|
||||
t.string "asset_proxy_url"
|
||||
t.text "asset_proxy_whitelist"
|
||||
t.text "encrypted_asset_proxy_secret_key"
|
||||
t.string "encrypted_asset_proxy_secret_key_iv"
|
||||
t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id"
|
||||
t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id"
|
||||
t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id"
|
||||
|
|
@ -2514,6 +2520,7 @@ ActiveRecord::Schema.define(version: 2019_08_28_083843) do
|
|||
t.string "title"
|
||||
t.integer "active_pipelines_limit"
|
||||
t.integer "pipeline_size_limit"
|
||||
t.integer "active_jobs_limit", default: 0
|
||||
t.index ["name"], name: "index_plans_on_name"
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ Parameter | Type | Description
|
|||
`stop_id` | integer | Only uploads with equal or smaller ID will be processed
|
||||
`dry_run` | boolean | Do not remove EXIF data, only check if EXIF data are present or not, default: true
|
||||
`sleep_time` | float | Pause for number of seconds after processing each image, default: 0.3 seconds
|
||||
`uploader` | string | Run sanitization only for uploads of the given uploader (`FileUploader`, `PersonalFileUploader`, `NamespaceFileUploader`)
|
||||
`since` | date | Run sanitization only for uploads newer than given date (e.g. `2019-05-01`)
|
||||
|
||||
If you have too many uploads, you can speed up sanitization by setting
|
||||
`sleep_time` to a lower value or by running multiple rake tasks in parallel,
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ POST /groups/:id/epics
|
|||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `title` | string | yes | The title of the epic |
|
||||
| `labels` | string | no | The comma separated list of labels |
|
||||
| `description` | string | no | The description of the epic |
|
||||
| `description` | string | no | The description of the epic. Limited to 1 000 000 characters. |
|
||||
| `start_date_is_fixed` | boolean | no | Whether start date should be sourced from `start_date_fixed` or from milestones (since 11.3) |
|
||||
| `start_date_fixed` | string | no | The fixed start date of an epic (since 11.3) |
|
||||
| `due_date_is_fixed` | boolean | no | Whether due date should be sourced from `due_date_fixed` or from milestones (since 11.3) |
|
||||
|
|
@ -231,7 +231,7 @@ PUT /groups/:id/epics/:epic_iid
|
|||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `epic_iid` | integer/string | yes | The internal ID of the epic |
|
||||
| `title` | string | no | The title of an epic |
|
||||
| `description` | string | no | The description of an epic |
|
||||
| `description` | string | no | The description of an epic. Limited to 1 000 000 characters. |
|
||||
| `labels` | string | no | The comma separated list of labels |
|
||||
| `start_date_is_fixed` | boolean | no | Whether start date should be sourced from `start_date_fixed` or from milestones (since 11.3) |
|
||||
| `start_date_fixed` | string | no | The fixed start date of an epic (since 11.3) |
|
||||
|
|
|
|||
|
|
@ -590,7 +590,7 @@ POST /projects/:id/issues
|
|||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `iid` | integer/string | no | The internal ID of the project's issue (requires admin or project owner rights) |
|
||||
| `title` | string | yes | The title of an issue |
|
||||
| `description` | string | no | The description of an issue |
|
||||
| `description` | string | no | The description of an issue. Limited to 1 000 000 characters. |
|
||||
| `confidential` | boolean | no | Set an issue to be confidential. Default is `false`. |
|
||||
| `assignee_ids` | integer array | no | The ID of a user to assign issue |
|
||||
| `milestone_id` | integer | no | The global ID of a milestone to assign issue |
|
||||
|
|
@ -691,7 +691,7 @@ PUT /projects/:id/issues/:issue_iid
|
|||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `issue_iid` | integer | yes | The internal ID of a project's issue |
|
||||
| `title` | string | no | The title of an issue |
|
||||
| `description` | string | no | The description of an issue |
|
||||
| `description` | string | no | The description of an issue. Limited to 1 000 000 characters. |
|
||||
| `confidential` | boolean | no | Updates an issue to be confidential |
|
||||
| `assignee_ids` | integer array | no | The ID of the user(s) to assign the issue to. Set to `0` or provide an empty value to unassign all assignees. |
|
||||
| `milestone_id` | integer | no | The global ID of a milestone to assign the issue to. Set to `0` or provide an empty value to unassign a milestone.|
|
||||
|
|
|
|||
|
|
@ -837,7 +837,7 @@ POST /projects/:id/merge_requests
|
|||
| `title` | string | yes | Title of MR |
|
||||
| `assignee_id` | integer | no | Assignee user ID |
|
||||
| `assignee_ids` | integer array | no | The ID of the user(s) to assign the MR to. Set to `0` or provide an empty value to unassign all assignees. |
|
||||
| `description` | string | no | Description of MR |
|
||||
| `description` | string | no | Description of MR. Limited to 1 000 000 characters. |
|
||||
| `target_project_id` | integer | no | The target project (numeric id) |
|
||||
| `labels` | string | no | Labels for MR as a comma-separated list |
|
||||
| `milestone_id` | integer | no | The global ID of a milestone |
|
||||
|
|
@ -990,7 +990,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid
|
|||
| `assignee_ids` | integer array | no | The ID of the user(s) to assign the MR to. Set to `0` or provide an empty value to unassign all assignees. |
|
||||
| `milestone_id` | integer | no | The global ID of a milestone to assign the merge request to. Set to `0` or provide an empty value to unassign a milestone.|
|
||||
| `labels` | string | no | Comma-separated label names for a merge request. Set to an empty string to unassign all labels. |
|
||||
| `description` | string | no | Description of MR |
|
||||
| `description` | string | no | Description of MR. Limited to 1 000 000 characters. |
|
||||
| `state_event` | string | no | New state (close/reopen) |
|
||||
| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging |
|
||||
| `squash` | boolean | no | Squash commits into a single commit when merging |
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ Parameters:
|
|||
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
|
||||
- `issue_iid` (required) - The IID of an issue
|
||||
- `body` (required) - The content of a note
|
||||
- `body` (required) - The content of a note. Limited to 1 000 000 characters.
|
||||
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights)
|
||||
|
||||
```bash
|
||||
|
|
@ -133,7 +133,7 @@ Parameters:
|
|||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
|
||||
- `issue_iid` (required) - The IID of an issue
|
||||
- `note_id` (required) - The ID of a note
|
||||
- `body` (required) - The content of a note
|
||||
- `body` (required) - The content of a note. Limited to 1 000 000 characters.
|
||||
|
||||
```bash
|
||||
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/notes?body=note
|
||||
|
|
@ -231,7 +231,7 @@ Parameters:
|
|||
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
|
||||
- `snippet_id` (required) - The ID of a snippet
|
||||
- `body` (required) - The content of a note
|
||||
- `body` (required) - The content of a note. Limited to 1 000 000 characters.
|
||||
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z
|
||||
|
||||
```bash
|
||||
|
|
@ -251,7 +251,7 @@ Parameters:
|
|||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
|
||||
- `snippet_id` (required) - The ID of a snippet
|
||||
- `note_id` (required) - The ID of a note
|
||||
- `body` (required) - The content of a note
|
||||
- `body` (required) - The content of a note. Limited to 1 000 000 characters.
|
||||
|
||||
```bash
|
||||
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/notes?body=note
|
||||
|
|
@ -354,7 +354,7 @@ Parameters:
|
|||
|
||||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
|
||||
- `merge_request_iid` (required) - The IID of a merge request
|
||||
- `body` (required) - The content of a note
|
||||
- `body` (required) - The content of a note. Limited to 1 000 000 characters.
|
||||
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z
|
||||
|
||||
### Modify existing merge request note
|
||||
|
|
@ -370,7 +370,7 @@ Parameters:
|
|||
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
|
||||
- `merge_request_iid` (required) - The IID of a merge request
|
||||
- `note_id` (required) - The ID of a note
|
||||
- `body` (required) - The content of a note
|
||||
- `body` (required) - The content of a note. Limited to 1 000 000 characters.
|
||||
|
||||
```bash
|
||||
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/notes?body=note
|
||||
|
|
@ -472,7 +472,7 @@ Parameters:
|
|||
| --------- | -------------- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
|
||||
| `epic_id` | integer | yes | The ID of an epic |
|
||||
| `body` | string | yes | The content of a note |
|
||||
| `body` | string | yes | The content of a note. Limited to 1 000 000 characters. |
|
||||
|
||||
```bash
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note
|
||||
|
|
@ -493,7 +493,7 @@ Parameters:
|
|||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
|
||||
| `epic_id` | integer | yes | The ID of an epic |
|
||||
| `note_id` | integer | yes | The ID of a note |
|
||||
| `body` | string | yes | The content of a note |
|
||||
| `body` | string | yes | The content of a note. Limited to 1 000 000 characters. |
|
||||
|
||||
```bash
|
||||
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note
|
||||
|
|
|
|||
|
|
@ -68,6 +68,9 @@ Example response:
|
|||
"allow_local_requests_from_hooks_and_services": true,
|
||||
"allow_local_requests_from_web_hooks_and_services": true,
|
||||
"allow_local_requests_from_system_hooks": false
|
||||
"asset_proxy_enabled": true,
|
||||
"asset_proxy_url": "https://assets.example.com",
|
||||
"asset_proxy_whitelist": ["example.com", "*.example.com", "your-instance.com"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -141,6 +144,9 @@ Example response:
|
|||
"user_show_add_ssh_key_message": true,
|
||||
"file_template_project_id": 1,
|
||||
"local_markdown_version": 0,
|
||||
"asset_proxy_enabled": true,
|
||||
"asset_proxy_url": "https://assets.example.com",
|
||||
"asset_proxy_whitelist": ["example.com", "*.example.com", "your-instance.com"],
|
||||
"geo_node_allowed_ips": "0.0.0.0/0, ::/0",
|
||||
"allow_local_requests_from_hooks_and_services": true,
|
||||
"allow_local_requests_from_web_hooks_and_services": true,
|
||||
|
|
@ -186,6 +192,10 @@ are listed in the descriptions of the relevant settings.
|
|||
| `allow_local_requests_from_hooks_and_services` | boolean | no | (Deprecated: Use `allow_local_requests_from_web_hooks_and_services` instead) Allow requests to the local network from hooks and services. |
|
||||
| `allow_local_requests_from_web_hooks_and_services` | boolean | no | Allow requests to the local network from web hooks and services. |
|
||||
| `allow_local_requests_from_system_hooks` | boolean | no | Allow requests to the local network from system hooks. |
|
||||
| `asset_proxy_enabled` | boolean | no | (**If enabled, requires:** `asset_proxy_url`) Enable proxying of assets. GitLab restart is required to apply changes. |
|
||||
| `asset_proxy_secret_key` | string | no | Shared secret with the asset proxy server. GitLab restart is required to apply changes. |
|
||||
| `asset_proxy_url` | string | no | URL of the asset proxy server. GitLab restart is required to apply changes. |
|
||||
| `asset_proxy_whitelist` | string or array of strings | no | Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted. GitLab restart is required to apply changes. |
|
||||
| `authorized_keys_enabled` | boolean | no | By default, we write to the `authorized_keys` file to support Git over SSH without additional configuration. GitLab can be optimized to authenticate SSH keys via the database file. Only disable this if you have configured your OpenSSH server to use the AuthorizedKeysCommand. |
|
||||
| `auto_devops_domain` | string | no | Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages. |
|
||||
| `auto_devops_enabled` | boolean | no | Enable Auto DevOps for projects by default. It will automatically build, test, and deploy applications based on a predefined CI/CD configuration. |
|
||||
|
|
|
|||
|
|
@ -18,3 +18,4 @@ type: index
|
|||
- [Enforce Two-factor authentication](two_factor_authentication.md)
|
||||
- [Send email confirmation on sign-up](user_email_confirmation.md)
|
||||
- [Security of running jobs](https://docs.gitlab.com/runner/security/)
|
||||
- [Proxying images](asset_proxy.md)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
A possible security concern when managing a public facing GitLab instance is
|
||||
the ability to steal a users IP address by referencing images in issues, comments, etc.
|
||||
|
||||
For example, adding `` to
|
||||
an issue description will cause the image to be loaded from the external
|
||||
server in order to be displayed. However this also allows the external server
|
||||
to log the IP address of the user.
|
||||
|
||||
One way to mitigate this is by proxying any external images to a server you
|
||||
control. GitLab handles this by allowing you to run the "Camo" server
|
||||
[cactus/go-camo](https://github.com/cactus/go-camo#how-it-works).
|
||||
The image request is sent to the Camo server, which then makes the request for
|
||||
the original image. This way an attacker only ever seems the IP address
|
||||
of your Camo server.
|
||||
|
||||
Once you have your Camo server up and running, you can configure GitLab to
|
||||
proxy image requests to it. The following settings are supported:
|
||||
|
||||
| Attribute | Description |
|
||||
| ------------------------ | ----------- |
|
||||
| `asset_proxy_enabled` | (**If enabled, requires:** `asset_proxy_url`) Enable proxying of assets. |
|
||||
| `asset_proxy_secret_key` | Shared secret with the asset proxy server. |
|
||||
| `asset_proxy_url` | URL of the asset proxy server. |
|
||||
| `asset_proxy_whitelist` | Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted. |
|
||||
|
||||
These can be set via the [Application setting API](../api/settings.md)
|
||||
|
||||
Note that a GitLab restart is required to apply any changes.
|
||||
|
|
@ -239,7 +239,7 @@ module API
|
|||
# because notes are redacted if they point to projects that
|
||||
# cannot be accessed by the user.
|
||||
notes = prepare_notes_for_rendering(notes)
|
||||
notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
|
||||
notes.select { |n| n.visible_for?(current_user) }
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1174,6 +1174,9 @@ module API
|
|||
attributes.delete(:performance_bar_enabled)
|
||||
attributes.delete(:allow_local_requests_from_hooks_and_services)
|
||||
|
||||
# let's not expose the secret key in a response
|
||||
attributes.delete(:asset_proxy_secret_key)
|
||||
|
||||
attributes
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ module API
|
|||
end
|
||||
|
||||
def update_note(noteable, note_id)
|
||||
note = noteable.notes.find(params[:note_id])
|
||||
note = noteable.notes.find(note_id)
|
||||
|
||||
authorize! :admin_note, note
|
||||
|
||||
|
|
@ -61,8 +61,8 @@ module API
|
|||
end
|
||||
|
||||
def get_note(noteable, note_id)
|
||||
note = noteable.notes.with_metadata.find(params[:note_id])
|
||||
can_read_note = !note.cross_reference_not_visible_for?(current_user)
|
||||
note = noteable.notes.with_metadata.find(note_id)
|
||||
can_read_note = note.visible_for?(current_user)
|
||||
|
||||
if can_read_note
|
||||
present note, with: Entities::Note
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ module API
|
|||
# array returned, but this is really a edge-case.
|
||||
notes = paginate(raw_notes)
|
||||
notes = prepare_notes_for_rendering(notes)
|
||||
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
|
||||
notes = notes.select { |note| note.visible_for?(current_user) }
|
||||
present notes, with: Entities::Note
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ module API
|
|||
given akismet_enabled: ->(val) { val } do
|
||||
requires :akismet_api_key, type: String, desc: 'Generate API key at http://www.akismet.com'
|
||||
end
|
||||
optional :asset_proxy_enabled, type: Boolean, desc: 'Enable proxying of assets'
|
||||
optional :asset_proxy_url, type: String, desc: 'URL of the asset proxy server'
|
||||
optional :asset_proxy_secret_key, type: String, desc: 'Shared secret with the asset proxy server'
|
||||
optional :asset_proxy_whitelist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted.'
|
||||
optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
|
||||
optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts"
|
||||
optional :default_project_creation, type: Integer, values: ::Gitlab::Access.project_creation_values, desc: 'Determine if developers can create projects in the group'
|
||||
|
|
@ -104,6 +108,11 @@ module API
|
|||
requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha'
|
||||
requires :recaptcha_private_key, type: String, desc: 'Generate private key at http://www.google.com/recaptcha'
|
||||
end
|
||||
optional :login_recaptcha_protection_enabled, type: Boolean, desc: 'Helps prevent brute-force attacks'
|
||||
given login_recaptcha_protection_enabled: ->(val) { val } do
|
||||
requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha'
|
||||
requires :recaptcha_private_key, type: String, desc: 'Generate private key at http://www.google.com/recaptcha'
|
||||
end
|
||||
optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues."
|
||||
optional :repository_storages, type: Array[String], desc: 'Storage paths for new projects'
|
||||
optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to set up Two-factor authentication'
|
||||
|
|
@ -123,7 +132,7 @@ module API
|
|||
optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
|
||||
optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.'
|
||||
optional :instance_statistics_visibility_private, type: Boolean, desc: 'When set to `true` Instance statistics will only be available to admins'
|
||||
optional :local_markdown_version, type: Integer, desc: "Local markdown version, increase this value when any cached markdown should be invalidated"
|
||||
optional :local_markdown_version, type: Integer, desc: 'Local markdown version, increase this value when any cached markdown should be invalidated'
|
||||
optional :allow_local_requests_from_hooks_and_services, type: Boolean, desc: 'Deprecated: Use :allow_local_requests_from_web_hooks_and_services instead. Allow requests to the local network from hooks and services.' # support legacy names, can be removed in v5
|
||||
optional :snowplow_enabled, type: Grape::API::Boolean, desc: 'Enable Snowplow tracking'
|
||||
given snowplow_enabled: ->(val) { val } do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module Validations
|
||||
module Types
|
||||
class CommaSeparatedToArray
|
||||
def self.coerce
|
||||
lambda do |value|
|
||||
case value
|
||||
when String
|
||||
value.split(',').map(&:strip)
|
||||
when Array
|
||||
value.map { |v| v.to_s.split(',').map(&:strip) }.flatten
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -7,6 +7,14 @@ module Banzai
|
|||
class AbstractReferenceFilter < ReferenceFilter
|
||||
include CrossProjectReference
|
||||
|
||||
# REFERENCE_PLACEHOLDER is used for re-escaping HTML text except found
|
||||
# reference (which we replace with placeholder during re-scaping). The
|
||||
# random number helps ensure it's pretty close to unique. Since it's a
|
||||
# transitory value (it never gets saved) we can initialize once, and it
|
||||
# doesn't matter if it changes on a restart.
|
||||
REFERENCE_PLACEHOLDER = "_reference_#{SecureRandom.hex(16)}_"
|
||||
REFERENCE_PLACEHOLDER_PATTERN = %r{#{REFERENCE_PLACEHOLDER}(\d+)}.freeze
|
||||
|
||||
def self.object_class
|
||||
# Implement in child class
|
||||
# Example: MergeRequest
|
||||
|
|
@ -389,6 +397,14 @@ module Banzai
|
|||
def escape_html_entities(text)
|
||||
CGI.escapeHTML(text.to_s)
|
||||
end
|
||||
|
||||
def escape_with_placeholders(text, placeholder_data)
|
||||
escaped = escape_html_entities(text)
|
||||
|
||||
escaped.gsub(REFERENCE_PLACEHOLDER_PATTERN) do |match|
|
||||
placeholder_data[$1.to_i]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Banzai
|
||||
module Filter
|
||||
# Proxy's images/assets to another server. Reduces mixed content warnings
|
||||
# as well as hiding the customer's IP address when requesting images.
|
||||
# Copies the original img `src` to `data-canonical-src` then replaces the
|
||||
# `src` with a new url to the proxy server.
|
||||
class AssetProxyFilter < HTML::Pipeline::CamoFilter
|
||||
def initialize(text, context = nil, result = nil)
|
||||
super
|
||||
end
|
||||
|
||||
def validate
|
||||
needs(:asset_proxy, :asset_proxy_secret_key) if asset_proxy_enabled?
|
||||
end
|
||||
|
||||
def asset_host_whitelisted?(host)
|
||||
context[:asset_proxy_domain_regexp] ? context[:asset_proxy_domain_regexp].match?(host) : false
|
||||
end
|
||||
|
||||
def self.transform_context(context)
|
||||
context[:disable_asset_proxy] = !Gitlab.config.asset_proxy.enabled
|
||||
|
||||
unless context[:disable_asset_proxy]
|
||||
context[:asset_proxy_enabled] = !context[:disable_asset_proxy]
|
||||
context[:asset_proxy] = Gitlab.config.asset_proxy.url
|
||||
context[:asset_proxy_secret_key] = Gitlab.config.asset_proxy.secret_key
|
||||
context[:asset_proxy_domain_regexp] = Gitlab.config.asset_proxy.domain_regexp
|
||||
end
|
||||
|
||||
context
|
||||
end
|
||||
|
||||
# called during an initializer. Caching the values in Gitlab.config significantly increased
|
||||
# performance, rather than querying Gitlab::CurrentSettings.current_application_settings
|
||||
# over and over. However, this does mean that the Rails servers need to get restarted
|
||||
# whenever the application settings are changed
|
||||
def self.initialize_settings
|
||||
application_settings = Gitlab::CurrentSettings.current_application_settings
|
||||
Gitlab.config['asset_proxy'] ||= Settingslogic.new({})
|
||||
|
||||
if application_settings.respond_to?(:asset_proxy_enabled)
|
||||
Gitlab.config.asset_proxy['enabled'] = application_settings.asset_proxy_enabled
|
||||
Gitlab.config.asset_proxy['url'] = application_settings.asset_proxy_url
|
||||
Gitlab.config.asset_proxy['secret_key'] = application_settings.asset_proxy_secret_key
|
||||
Gitlab.config.asset_proxy['whitelist'] = application_settings.asset_proxy_whitelist || [Gitlab.config.gitlab.host]
|
||||
Gitlab.config.asset_proxy['domain_regexp'] = compile_whitelist(Gitlab.config.asset_proxy.whitelist)
|
||||
else
|
||||
Gitlab.config.asset_proxy['enabled'] = ::ApplicationSetting.defaults[:asset_proxy_enabled]
|
||||
end
|
||||
end
|
||||
|
||||
def self.compile_whitelist(domain_list)
|
||||
return if domain_list.empty?
|
||||
|
||||
escaped = domain_list.map { |domain| Regexp.escape(domain).gsub('\*', '.*?') }
|
||||
Regexp.new("^(#{escaped.join('|')})$", Regexp::IGNORECASE)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -14,10 +14,10 @@ module Banzai
|
|||
# such as on `mailto:` links. Since we've been using it, do an
|
||||
# initial parse for validity and then use Addressable
|
||||
# for IDN support, etc
|
||||
uri = uri_strict(node['href'].to_s)
|
||||
uri = uri_strict(node_src(node))
|
||||
if uri
|
||||
node.set_attribute('href', uri.to_s)
|
||||
addressable_uri = addressable_uri(node['href'])
|
||||
node.set_attribute(node_src_attribute(node), uri.to_s)
|
||||
addressable_uri = addressable_uri(node_src(node))
|
||||
else
|
||||
addressable_uri = nil
|
||||
end
|
||||
|
|
@ -35,6 +35,16 @@ module Banzai
|
|||
|
||||
private
|
||||
|
||||
# if this is a link to a proxied image, then `src` is already the correct
|
||||
# proxied url, so work with the `data-canonical-src`
|
||||
def node_src_attribute(node)
|
||||
node['data-canonical-src'] ? 'data-canonical-src' : 'href'
|
||||
end
|
||||
|
||||
def node_src(node)
|
||||
node[node_src_attribute(node)]
|
||||
end
|
||||
|
||||
def uri_strict(href)
|
||||
URI.parse(href)
|
||||
rescue URI::Error
|
||||
|
|
@ -72,7 +82,7 @@ module Banzai
|
|||
return unless uri
|
||||
return unless context[:emailable_links]
|
||||
|
||||
unencoded_uri_str = Addressable::URI.unencode(node['href'])
|
||||
unencoded_uri_str = Addressable::URI.unencode(node_src(node))
|
||||
|
||||
if unencoded_uri_str == node.content && idn?(uri)
|
||||
node.content = uri.normalize
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ module Banzai
|
|||
rel: 'noopener noreferrer'
|
||||
)
|
||||
|
||||
# make sure the original non-proxied src carries over to the link
|
||||
link['data-canonical-src'] = img['data-canonical-src'] if img['data-canonical-src']
|
||||
|
||||
link.children = img.clone
|
||||
|
||||
img.replace(link)
|
||||
|
|
|
|||
|
|
@ -14,24 +14,24 @@ module Banzai
|
|||
find_labels(parent_object).find(id)
|
||||
end
|
||||
|
||||
def self.references_in(text, pattern = Label.reference_pattern)
|
||||
unescape_html_entities(text).gsub(pattern) do |match|
|
||||
yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~[:namespace], $~
|
||||
end
|
||||
end
|
||||
|
||||
def references_in(text, pattern = Label.reference_pattern)
|
||||
unescape_html_entities(text).gsub(pattern) do |match|
|
||||
labels = {}
|
||||
unescaped_html = unescape_html_entities(text).gsub(pattern) do |match|
|
||||
namespace, project = $~[:namespace], $~[:project]
|
||||
project_path = full_project_path(namespace, project)
|
||||
label = find_label(project_path, $~[:label_id], $~[:label_name])
|
||||
|
||||
if label
|
||||
yield match, label.id, project, namespace, $~
|
||||
labels[label.id] = yield match, label.id, project, namespace, $~
|
||||
"#{REFERENCE_PLACEHOLDER}#{label.id}"
|
||||
else
|
||||
escape_html_entities(match)
|
||||
match
|
||||
end
|
||||
end
|
||||
|
||||
return text if labels.empty?
|
||||
|
||||
escape_with_placeholders(unescaped_html, labels)
|
||||
end
|
||||
|
||||
def find_label(parent_ref, label_id, label_name)
|
||||
|
|
|
|||
|
|
@ -51,15 +51,21 @@ module Banzai
|
|||
# default implementation.
|
||||
return super(text, pattern) if pattern != Milestone.reference_pattern
|
||||
|
||||
unescape_html_entities(text).gsub(pattern) do |match|
|
||||
milestones = {}
|
||||
unescaped_html = unescape_html_entities(text).gsub(pattern) do |match|
|
||||
milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name])
|
||||
|
||||
if milestone
|
||||
yield match, milestone.id, $~[:project], $~[:namespace], $~
|
||||
milestones[milestone.id] = yield match, milestone.id, $~[:project], $~[:namespace], $~
|
||||
"#{REFERENCE_PLACEHOLDER}#{milestone.id}"
|
||||
else
|
||||
escape_html_entities(match)
|
||||
match
|
||||
end
|
||||
end
|
||||
|
||||
return text if milestones.empty?
|
||||
|
||||
escape_with_placeholders(unescaped_html, milestones)
|
||||
end
|
||||
|
||||
def find_milestone(project_ref, namespace_ref, milestone_id, milestone_name)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ module Banzai
|
|||
# Context options:
|
||||
# :commit
|
||||
# :group
|
||||
# :current_user
|
||||
# :project
|
||||
# :project_wiki
|
||||
# :ref
|
||||
|
|
@ -18,6 +19,7 @@ module Banzai
|
|||
|
||||
def call
|
||||
return doc if context[:system_note]
|
||||
return doc unless visible_to_user?
|
||||
|
||||
@uri_types = {}
|
||||
clear_memoization(:linkable_files)
|
||||
|
|
@ -166,6 +168,16 @@ module Banzai
|
|||
Gitlab.config.gitlab.relative_url_root.presence || '/'
|
||||
end
|
||||
|
||||
def visible_to_user?
|
||||
if project
|
||||
Ability.allowed?(current_user, :download_code, project)
|
||||
elsif group
|
||||
Ability.allowed?(current_user, :read_group, group)
|
||||
else # Objects detached from projects or groups, e.g. Personal Snippets.
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def ref
|
||||
context[:ref] || project.default_branch
|
||||
end
|
||||
|
|
@ -178,6 +190,10 @@ module Banzai
|
|||
context[:project]
|
||||
end
|
||||
|
||||
def current_user
|
||||
context[:current_user]
|
||||
end
|
||||
|
||||
def repository
|
||||
@repository ||= project&.repository
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,6 +23,14 @@ module Banzai
|
|||
"'.#{ext}' = substring(@src, string-length(@src) - #{ext.size})"
|
||||
end
|
||||
|
||||
if context[:asset_proxy_enabled].present?
|
||||
src_query.concat(
|
||||
UploaderHelper::VIDEO_EXT.map do |ext|
|
||||
"'.#{ext}' = substring(@data-canonical-src, string-length(@data-canonical-src) - #{ext.size})"
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
"descendant-or-self::img[not(ancestor::a) and (#{src_query.join(' or ')})]"
|
||||
end
|
||||
end
|
||||
|
|
@ -48,6 +56,13 @@ module Banzai
|
|||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
title: "Download '#{element['title'] || element['alt']}'")
|
||||
|
||||
# make sure the original non-proxied src carries over
|
||||
if element['data-canonical-src']
|
||||
video['data-canonical-src'] = element['data-canonical-src']
|
||||
link['data-canonical-src'] = element['data-canonical-src']
|
||||
end
|
||||
|
||||
download_paragraph = doc.document.create_element('p')
|
||||
download_paragraph.children = link
|
||||
|
||||
|
|
|
|||
|
|
@ -6,12 +6,17 @@ module Banzai
|
|||
def self.filters
|
||||
FilterArray[
|
||||
Filter::AsciiDocSanitizationFilter,
|
||||
Filter::AssetProxyFilter,
|
||||
Filter::SyntaxHighlightFilter,
|
||||
Filter::ExternalLinkFilter,
|
||||
Filter::PlantumlFilter,
|
||||
Filter::AsciiDocPostProcessingFilter
|
||||
]
|
||||
end
|
||||
|
||||
def self.transform_context(context)
|
||||
Filter::AssetProxyFilter.transform_context(context)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ module Banzai
|
|||
Filter::SpacedLinkFilter,
|
||||
|
||||
Filter::SanitizationFilter,
|
||||
Filter::AssetProxyFilter,
|
||||
Filter::SyntaxHighlightFilter,
|
||||
|
||||
Filter::MathFilter,
|
||||
|
|
@ -60,7 +61,7 @@ module Banzai
|
|||
def self.transform_context(context)
|
||||
context[:only_path] = true unless context.key?(:only_path)
|
||||
|
||||
context
|
||||
Filter::AssetProxyFilter.transform_context(context)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,11 +6,16 @@ module Banzai
|
|||
def self.filters
|
||||
@filters ||= FilterArray[
|
||||
Filter::SanitizationFilter,
|
||||
Filter::AssetProxyFilter,
|
||||
Filter::ExternalLinkFilter,
|
||||
Filter::PlantumlFilter,
|
||||
Filter::SyntaxHighlightFilter
|
||||
]
|
||||
end
|
||||
|
||||
def self.transform_context(context)
|
||||
Filter::AssetProxyFilter.transform_context(context)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ module Banzai
|
|||
@filters ||= FilterArray[
|
||||
Filter::HtmlEntityFilter,
|
||||
Filter::SanitizationFilter,
|
||||
Filter::AssetProxyFilter,
|
||||
|
||||
Filter::EmojiFilter,
|
||||
Filter::AutolinkFilter,
|
||||
|
|
@ -29,6 +30,8 @@ module Banzai
|
|||
end
|
||||
|
||||
def self.transform_context(context)
|
||||
context = Filter::AssetProxyFilter.transform_context(context)
|
||||
|
||||
super(context).merge(
|
||||
no_sourcepos: true
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
class AnonymousSession
|
||||
def initialize(remote_ip, session_id: nil)
|
||||
@remote_ip = remote_ip
|
||||
@session_id = session_id
|
||||
end
|
||||
|
||||
def store_session_id_per_ip
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.pipelined do
|
||||
redis.sadd(session_lookup_name, session_id)
|
||||
redis.expire(session_lookup_name, 24.hours)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def stored_sessions
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.scard(session_lookup_name)
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup_session_per_ip_entries
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.srem(session_lookup_name, session_id)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :remote_ip, :session_id
|
||||
|
||||
def session_lookup_name
|
||||
@session_lookup_name ||= "#{Gitlab::Redis::SharedState::IP_SESSIONS_LOOKUP_NAMESPACE}:#{remote_ip}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Ci
|
||||
module Pipeline
|
||||
module Chain
|
||||
module Limit
|
||||
class JobActivity < Chain::Base
|
||||
def perform!
|
||||
# to be overridden in EE
|
||||
end
|
||||
|
||||
def break?
|
||||
false # to be overridden in EE
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -13,6 +13,10 @@ module Gitlab
|
|||
# FIXME: this should just be the max value of timestampz
|
||||
MAX_TIMESTAMP_VALUE = Time.at((1 << 31) - 1).freeze
|
||||
|
||||
# The maximum number of characters for text fields, to avoid DoS attacks via parsing huge text fields
|
||||
# https://gitlab.com/gitlab-org/gitlab-ce/issues/61974
|
||||
MAX_TEXT_SIZE_LIMIT = 1_000_000
|
||||
|
||||
# Minimum schema version from which migrations are supported
|
||||
# Migrations before this version may have been removed
|
||||
MIN_SCHEMA_VERSION = 20190506135400
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Jira
|
||||
# Gitlab JIRA HTTP client to be used with jira-ruby gem, this subclasses JIRA::HTTPClient.
|
||||
# Uses Gitlab::HTTP to make requests to JIRA REST API.
|
||||
# The parent class implementation can be found at: https://github.com/sumoheavy/jira-ruby/blob/v1.4.0/lib/jira/http_client.rb
|
||||
class HttpClient < JIRA::HttpClient
|
||||
extend ::Gitlab::Utils::Override
|
||||
|
||||
override :request
|
||||
def request(*args)
|
||||
result = make_request(*args)
|
||||
|
||||
raise JIRA::HTTPError.new(result) unless result.response.is_a?(Net::HTTPSuccess)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
override :make_cookie_auth_request
|
||||
def make_cookie_auth_request
|
||||
body = {
|
||||
username: @options.delete(:username),
|
||||
password: @options.delete(:password)
|
||||
}.to_json
|
||||
|
||||
make_request(:post, @options[:context_path] + '/rest/auth/1/session', body, { 'Content-Type' => 'application/json' })
|
||||
end
|
||||
|
||||
override :make_request
|
||||
def make_request(http_method, path, body = '', headers = {})
|
||||
request_params = { headers: headers }
|
||||
request_params[:body] = body if body.present?
|
||||
request_params[:headers][:Cookie] = get_cookies if options[:use_cookies]
|
||||
request_params[:timeout] = options[:read_timeout] if options[:read_timeout]
|
||||
request_params[:base_uri] = uri.to_s
|
||||
request_params.merge!(auth_params)
|
||||
|
||||
result = Gitlab::HTTP.public_send(http_method, path, **request_params) # rubocop:disable GitlabSecurity/PublicSend
|
||||
@authenticated = result.response.is_a?(Net::HTTPOK)
|
||||
store_cookies(result) if options[:use_cookies]
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def auth_params
|
||||
return {} unless @options[:username] && @options[:password]
|
||||
|
||||
{
|
||||
basic_auth: {
|
||||
username: @options[:username],
|
||||
password: @options[:password]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_cookies
|
||||
cookie_array = @cookies.values.map { |cookie| "#{cookie.name}=#{cookie.value[0]}" }
|
||||
cookie_array += Array(@options[:additional_cookies]) if @options.key?(:additional_cookies)
|
||||
cookie_array.join('; ') if cookie_array.any?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -3,8 +3,8 @@
|
|||
module Gitlab
|
||||
module MarkdownCache
|
||||
# Increment this number every time the renderer changes its output
|
||||
CACHE_COMMONMARK_VERSION = 17
|
||||
CACHE_COMMONMARK_VERSION_START = 10
|
||||
CACHE_COMMONMARK_VERSION = 16
|
||||
|
||||
BaseError = Class.new(StandardError)
|
||||
UnsupportedClassError = Class.new(BaseError)
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ module Gitlab
|
|||
NO_SUFFIX_REGEX = /(?<!\.git|\.atom)/.freeze
|
||||
NAMESPACE_FORMAT_REGEX = /(?:#{NAMESPACE_FORMAT_REGEX_JS})#{NO_SUFFIX_REGEX}/.freeze
|
||||
PROJECT_PATH_FORMAT_REGEX = /(?:#{PATH_REGEX_STR})#{NO_SUFFIX_REGEX}/.freeze
|
||||
FULL_NAMESPACE_FORMAT_REGEX = %r{(#{NAMESPACE_FORMAT_REGEX}/)*#{NAMESPACE_FORMAT_REGEX}}.freeze
|
||||
FULL_NAMESPACE_FORMAT_REGEX = %r{(#{NAMESPACE_FORMAT_REGEX}/){,#{Namespace::NUMBER_OF_ANCESTORS_ALLOWED}}#{NAMESPACE_FORMAT_REGEX}}.freeze
|
||||
|
||||
def root_namespace_route_regex
|
||||
@root_namespace_route_regex ||= begin
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
module Gitlab
|
||||
module Recaptcha
|
||||
def self.load_configurations!
|
||||
if Gitlab::CurrentSettings.recaptcha_enabled
|
||||
if Gitlab::CurrentSettings.recaptcha_enabled || enabled_on_login?
|
||||
::Recaptcha.configure do |config|
|
||||
config.site_key = Gitlab::CurrentSettings.recaptcha_site_key
|
||||
config.secret_key = Gitlab::CurrentSettings.recaptcha_private_key
|
||||
|
|
@ -16,5 +16,9 @@ module Gitlab
|
|||
def self.enabled?
|
||||
Gitlab::CurrentSettings.recaptcha_enabled
|
||||
end
|
||||
|
||||
def self.enabled_on_login?
|
||||
Gitlab::CurrentSettings.login_recaptcha_protection_enabled
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ module Gitlab
|
|||
SESSION_NAMESPACE = 'session:gitlab'.freeze
|
||||
USER_SESSIONS_NAMESPACE = 'session:user:gitlab'.freeze
|
||||
USER_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:user:gitlab'.freeze
|
||||
IP_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:ip:gitlab'.freeze
|
||||
DEFAULT_REDIS_SHARED_STATE_URL = 'redis://localhost:6382'.freeze
|
||||
REDIS_SHARED_STATE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_SHARED_STATE_CONFIG_FILE'.freeze
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue