Commit dc46ae6b authored by Robert Speicher's avatar Robert Speicher

Merge branch 'ce-to-ee-2018-11-29' into 'master'

CE upstream - 2018-11-29 00:21 UTC

Closes gitlab-ce#38317, #8608, #2727, #2732, and #2742

See merge request gitlab-org/gitlab-ee!8634
parents a03f59d3 02d027a0
...@@ -1140,6 +1140,7 @@ review-deploy: ...@@ -1140,6 +1140,7 @@ review-deploy:
GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}" GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}" GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}" EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
QA_DEBUG: "true"
artifacts: artifacts:
paths: paths:
- ./qa/gitlab-qa-run-* - ./qa/gitlab-qa-run-*
...@@ -1155,6 +1156,7 @@ review-deploy: ...@@ -1155,6 +1156,7 @@ review-deploy:
review-qa-smoke: review-qa-smoke:
<<: *review-qa-base <<: *review-qa-base
retry: 2
script: script:
- gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}" - gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
......
...@@ -4,8 +4,9 @@ entry. ...@@ -4,8 +4,9 @@ entry.
## 11.5.1 (2018-11-26) ## 11.5.1 (2018-11-26)
### Security (16 changes) ### Security (17 changes)
- Escape user fullname while rendering autocomplete template to prevent XSS.
- Fix CRLF vulnerability in Project hooks. - Fix CRLF vulnerability in Project hooks.
- Fix possible XSS attack in Markdown urls with spaces. - Fix possible XSS attack in Markdown urls with spaces.
- Redact sensitive information on gitlab-workhorse log. - Redact sensitive information on gitlab-workhorse log.
...@@ -598,12 +599,13 @@ entry. ...@@ -598,12 +599,13 @@ entry.
## 11.3.11 (2018-11-26) ## 11.3.11 (2018-11-26)
### Security (32 changes) ### Security (33 changes)
- Filter user sensitive data from discussions JSON. !2537 - Filter user sensitive data from discussions JSON. !2537
- Escape entity title while autocomplete template rendering to prevent XSS. !2557 - Escape entity title while autocomplete template rendering to prevent XSS. !2557
- Resolve reflected XSS in Ouath authorize window. - Restrict Personal Access Tokens to API scope on web requests.
- Fix XSS in merge request source branch name. - Fix XSS in merge request source branch name.
- Escape user fullname while rendering autocomplete template to prevent XSS.
- Fix CRLF vulnerability in Project hooks. - Fix CRLF vulnerability in Project hooks.
- Fix possible XSS attack in Markdown urls with spaces. - Fix possible XSS attack in Markdown urls with spaces.
- Redact sensitive information on gitlab-workhorse log. - Redact sensitive information on gitlab-workhorse log.
...@@ -615,8 +617,8 @@ entry. ...@@ -615,8 +617,8 @@ entry.
- Markdown API no longer displays confidential title references unless authorized. - Markdown API no longer displays confidential title references unless authorized.
- Provide email notification when a user changes their email address. - Provide email notification when a user changes their email address.
- Properly filter private references from system notes. - Properly filter private references from system notes.
- Restrict Personal Access Tokens to API scope on web requests.
- Redact personal tokens in unsubscribe links. - Redact personal tokens in unsubscribe links.
- Resolve reflected XSS in Ouath authorize window.
- Fix SSRF in project integrations. - Fix SSRF in project integrations.
- Fix stored XSS in merge requests from imported repository. - Fix stored XSS in merge requests from imported repository.
- Fixed ability to comment on locked/confidential issues. - Fixed ability to comment on locked/confidential issues.
......
...@@ -7,6 +7,11 @@ gem_versions = {} ...@@ -7,6 +7,11 @@ gem_versions = {}
gem_versions['activerecord_sane_schema_dumper'] = rails5? ? '1.0' : '0.2' gem_versions['activerecord_sane_schema_dumper'] = rails5? ? '1.0' : '0.2'
gem_versions['rails'] = rails5? ? '5.0.7' : '4.2.10' gem_versions['rails'] = rails5? ? '5.0.7' : '4.2.10'
gem_versions['rails-i18n'] = rails5? ? '~> 5.1' : '~> 4.0.9' gem_versions['rails-i18n'] = rails5? ? '~> 5.1' : '~> 4.0.9'
# The 2.0.6 version of rack requires monkeypatch to be present in
# `config.ru`. This can be removed once a new update for Rack
# is available that contains https://github.com/rack/rack/pull/1201.
gem_versions['rack'] = rails5? ? '2.0.6' : '1.6.11'
# --- The end of special code for migrating to Rails 5.0 --- # --- The end of special code for migrating to Rails 5.0 ---
source 'https://rubygems.org' source 'https://rubygems.org'
...@@ -164,6 +169,8 @@ gem 'icalendar' ...@@ -164,6 +169,8 @@ gem 'icalendar'
gem 'diffy', '~> 3.1.0' gem 'diffy', '~> 3.1.0'
# Application server # Application server
gem 'rack', gem_versions['rack']
group :unicorn do group :unicorn do
gem 'unicorn', '~> 5.1.0' gem 'unicorn', '~> 5.1.0'
gem 'unicorn-worker-killer', '~> 0.4.4' gem 'unicorn-worker-killer', '~> 0.4.4'
...@@ -440,7 +447,7 @@ group :ed25519 do ...@@ -440,7 +447,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 1.1.0', require: 'gitaly' gem 'gitaly-proto', '~> 1.2.0', require: 'gitaly'
gem 'grpc', '~> 1.15.0' gem 'grpc', '~> 1.15.0'
gem 'google-protobuf', '~> 3.6' gem 'google-protobuf', '~> 3.6'
......
...@@ -297,7 +297,7 @@ GEM ...@@ -297,7 +297,7 @@ GEM
gettext_i18n_rails (>= 0.7.1) gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gitaly-proto (1.1.0) gitaly-proto (1.2.0)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab-default_value_for (3.1.1) gitlab-default_value_for (3.1.1)
...@@ -1040,7 +1040,7 @@ DEPENDENCIES ...@@ -1040,7 +1040,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 1.1.0) gitaly-proto (~> 1.2.0)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-default_value_for (~> 3.1.1) gitlab-default_value_for (~> 3.1.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
...@@ -1125,6 +1125,7 @@ DEPENDENCIES ...@@ -1125,6 +1125,7 @@ DEPENDENCIES
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
puma (~> 3.12) puma (~> 3.12)
puma_worker_killer puma_worker_killer
rack (= 2.0.6)
rack-attack (~> 4.4.1) rack-attack (~> 4.4.1)
rack-cors (~> 1.0.0) rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
......
...@@ -296,7 +296,7 @@ GEM ...@@ -296,7 +296,7 @@ GEM
gettext_i18n_rails (>= 0.7.1) gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gitaly-proto (1.1.0) gitaly-proto (1.2.0)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab-license (1.0.0) gitlab-license (1.0.0)
...@@ -1032,7 +1032,7 @@ DEPENDENCIES ...@@ -1032,7 +1032,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 1.1.0) gitaly-proto (~> 1.2.0)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
gitlab-markup (~> 1.6.5) gitlab-markup (~> 1.6.5)
...@@ -1116,6 +1116,7 @@ DEPENDENCIES ...@@ -1116,6 +1116,7 @@ DEPENDENCIES
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
puma (~> 3.12) puma (~> 3.12)
puma_worker_killer puma_worker_killer
rack (= 1.6.11)
rack-attack (~> 4.4.1) rack-attack (~> 4.4.1)
rack-cors (~> 1.0.0) rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
......
...@@ -26,6 +26,9 @@ export default function renderMermaid($els) { ...@@ -26,6 +26,9 @@ export default function renderMermaid($els) {
}, },
// mermaidAPI options // mermaidAPI options
theme: 'neutral', theme: 'neutral',
flowchart: {
htmlLabels: false,
},
}); });
$els.each((i, el) => { $els.each((i, el) => {
......
import bp from '../../../breakpoints'; import bp from '../../../breakpoints';
import { slugify } from '../../../lib/utils/text_utility';
import { parseQueryStringIntoObject } from '../../../lib/utils/common_utils'; import { parseQueryStringIntoObject } from '../../../lib/utils/common_utils';
import { mergeUrlParams, redirectTo } from '../../../lib/utils/url_utility'; import { mergeUrlParams, redirectTo } from '../../../lib/utils/url_utility';
...@@ -26,7 +25,8 @@ export default class Wikis { ...@@ -26,7 +25,8 @@ export default class Wikis {
if (!this.newWikiForm) return; if (!this.newWikiForm) return;
const slugInput = this.newWikiForm.querySelector('#new_wiki_path'); const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
const slug = slugify(slugInput.value);
const slug = slugInput.value;
if (slug.length > 0) { if (slug.length > 0) {
const wikisPath = slugInput.getAttribute('data-wikis-path'); const wikisPath = slugInput.getAttribute('data-wikis-path');
......
...@@ -5,23 +5,12 @@ class Admin::ImpersonationsController < Admin::ApplicationController ...@@ -5,23 +5,12 @@ class Admin::ImpersonationsController < Admin::ApplicationController
before_action :authenticate_impersonator! before_action :authenticate_impersonator!
def destroy def destroy
original_user = current_user original_user = stop_impersonation
warden.set_user(impersonator, scope: :user)
Gitlab::AppLogger.info("User #{impersonator.username} has stopped impersonating #{original_user.username}")
session[:impersonator_id] = nil
redirect_to admin_user_path(original_user), status: :found redirect_to admin_user_path(original_user), status: :found
end end
private private
def impersonator
@impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id]
end
def authenticate_impersonator! def authenticate_impersonator!
render_404 unless impersonator && impersonator.admin? && !impersonator.blocked? render_404 unless impersonator && impersonator.admin? && !impersonator.blocked?
end end
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
class Admin::UsersController < Admin::ApplicationController class Admin::UsersController < Admin::ApplicationController
before_action :user, except: [:index, :new, :create] before_action :user, except: [:index, :new, :create]
before_action :check_impersonation_availability, only: :impersonate
def index def index
@users = User.order_name_asc.filter(params[:filter]) @users = User.order_name_asc.filter(params[:filter])
...@@ -227,6 +228,10 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -227,6 +228,10 @@ class Admin::UsersController < Admin::ApplicationController
result[:status] == :success result[:status] == :success
end end
def check_impersonation_availability
access_denied! unless Gitlab.config.gitlab.impersonation_enabled
end
end end
Admin::UsersController.prepend(EE::Admin::UsersController) Admin::UsersController.prepend(EE::Admin::UsersController)
...@@ -28,6 +28,7 @@ class ApplicationController < ActionController::Base ...@@ -28,6 +28,7 @@ class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller? before_action :configure_permitted_parameters, if: :devise_controller?
before_action :require_email, unless: :devise_controller? before_action :require_email, unless: :devise_controller?
before_action :set_usage_stats_consent_flag before_action :set_usage_stats_consent_flag
before_action :check_impersonation_availability
around_action :set_locale around_action :set_locale
...@@ -470,4 +471,28 @@ class ApplicationController < ActionController::Base ...@@ -470,4 +471,28 @@ class ApplicationController < ActionController::Base
.new(settings, current_user, application_setting_params) .new(settings, current_user, application_setting_params)
.execute .execute
end end
def check_impersonation_availability
return unless session[:impersonator_id]
unless Gitlab.config.gitlab.impersonation_enabled
stop_impersonation
access_denied! _('Impersonation has been disabled')
end
end
def stop_impersonation
impersonated_user = current_user
Gitlab::AppLogger.info("User #{impersonator.username} has stopped impersonating #{impersonated_user.username}")
warden.set_user(impersonator, scope: :user)
session[:impersonator_id] = nil
impersonated_user
end
def impersonator
@impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id]
end
end end
...@@ -4,6 +4,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -4,6 +4,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
include ActionView::Helpers::NumberHelper include ActionView::Helpers::NumberHelper
before_action :authorize_read_project!, only: :index before_action :authorize_read_project!, only: :index
before_action :authorize_read_group!, only: :index
before_action :find_todos, only: [:index, :destroy_all] before_action :find_todos, only: [:index, :destroy_all]
def index def index
...@@ -60,6 +61,15 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -60,6 +61,15 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end end
end end
def authorize_read_group!
group_id = params[:group_id]
if group_id.present?
group = Group.find(group_id)
render_404 unless can?(current_user, :read_group, group)
end
end
def find_todos def find_todos
@todos ||= TodosFinder.new(current_user, todo_params).execute @todos ||= TodosFinder.new(current_user, todo_params).execute
end end
......
...@@ -9,7 +9,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController ...@@ -9,7 +9,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
before_action :verify_user_oauth_applications_enabled, except: :index before_action :verify_user_oauth_applications_enabled, except: :index
before_action :authenticate_user! before_action :authenticate_user!
before_action :add_gon_variables before_action :add_gon_variables
before_action :load_scopes, only: [:index, :create, :edit] before_action :load_scopes, only: [:index, :create, :edit, :update]
helper_method :can? helper_method :can?
......
...@@ -11,7 +11,10 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -11,7 +11,10 @@ class Projects::MilestonesController < Projects::ApplicationController
before_action :authorize_read_milestone! before_action :authorize_read_milestone!
# Allow admin milestone # Allow admin milestone
before_action :authorize_admin_milestone!, except: [:index, :show, :merge_requests, :participants, :labels, :promote] before_action :authorize_admin_milestone!, except: [:index, :show, :merge_requests, :participants, :labels]
# Allow to promote milestone
before_action :authorize_promote_milestone!, only: :promote
respond_to :html respond_to :html
...@@ -78,7 +81,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -78,7 +81,7 @@ class Projects::MilestonesController < Projects::ApplicationController
def promote def promote
promoted_milestone = Milestones::PromoteService.new(project, current_user).execute(milestone) promoted_milestone = Milestones::PromoteService.new(project, current_user).execute(milestone)
flash[:notice] = flash_notice_for(promoted_milestone, project.group) flash[:notice] = flash_notice_for(promoted_milestone, project_group)
respond_to do |format| respond_to do |format|
format.html do format.html do
...@@ -109,6 +112,12 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -109,6 +112,12 @@ class Projects::MilestonesController < Projects::ApplicationController
protected protected
def project_group
strong_memoize(:project_group) do
project.group
end
end
def milestones def milestones
strong_memoize(:milestones) do strong_memoize(:milestones) do
MilestonesFinder.new(search_params).execute MilestonesFinder.new(search_params).execute
...@@ -125,13 +134,17 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -125,13 +134,17 @@ class Projects::MilestonesController < Projects::ApplicationController
return render_404 unless can?(current_user, :admin_milestone, @project) return render_404 unless can?(current_user, :admin_milestone, @project)
end end
def authorize_promote_milestone!
return render_404 unless can?(current_user, :admin_milestone, project_group)
end
def milestone_params def milestone_params
params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event) params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event)
end end
def search_params def search_params
if request.format.json? && @project.group && can?(current_user, :read_group, @project.group) if request.format.json? && project_group && can?(current_user, :read_group, project_group)
groups = @project.group.self_and_ancestors_ids groups = project_group.self_and_ancestors_ids
end end
params.permit(:state).merge(project_ids: @project.id, group_ids: groups) params.permit(:state).merge(project_ids: @project.id, group_ids: groups)
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
module MilestonesHelper module MilestonesHelper
include EntityDateHelper include EntityDateHelper
include Gitlab::Utils::StrongMemoize
def milestones_filter_path(opts = {}) def milestones_filter_path(opts = {})
if @project if @project
...@@ -243,6 +244,18 @@ module MilestonesHelper ...@@ -243,6 +244,18 @@ module MilestonesHelper
dashboard_milestone_path(milestone.safe_title, title: milestone.title) dashboard_milestone_path(milestone.safe_title, title: milestone.title)
end end
end end
def can_admin_project_milestones?
strong_memoize(:can_admin_project_milestones) do
can?(current_user, :admin_milestone, @project)
end
end
def can_admin_group_milestones?
strong_memoize(:can_admin_group_milestones) do
can?(current_user, :admin_milestone, @project.group)
end
end
end end
MilestonesHelper.prepend(EE::MilestonesHelper) MilestonesHelper.prepend(EE::MilestonesHelper)
...@@ -72,6 +72,10 @@ module UsersHelper ...@@ -72,6 +72,10 @@ module UsersHelper
end end
end end
def impersonation_enabled?
Gitlab.config.gitlab.impersonation_enabled
end
private private
def get_profile_tabs def get_profile_tabs
......
...@@ -15,7 +15,7 @@ module CacheMarkdownField ...@@ -15,7 +15,7 @@ module CacheMarkdownField
# Increment this number every time the renderer changes its output # Increment this number every time the renderer changes its output
CACHE_REDCARPET_VERSION = 3 CACHE_REDCARPET_VERSION = 3
CACHE_COMMONMARK_VERSION_START = 10 CACHE_COMMONMARK_VERSION_START = 10
CACHE_COMMONMARK_VERSION = 11 CACHE_COMMONMARK_VERSION = 12
# changes to these attributes cause the cache to be invalidates # changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze INVALIDATED_BY = %w[author project].freeze
......
...@@ -71,7 +71,7 @@ class PrometheusService < MonitoringService ...@@ -71,7 +71,7 @@ class PrometheusService < MonitoringService
end end
def prometheus_client def prometheus_client
RestClient::Resource.new(api_url) if api_url && manual_configuration? && active? RestClient::Resource.new(api_url, max_redirects: 0) if api_url && manual_configuration? && active?
end end
def prometheus_available? def prometheus_available?
......
...@@ -15,8 +15,6 @@ class RemoteMirror < ActiveRecord::Base ...@@ -15,8 +15,6 @@ class RemoteMirror < ActiveRecord::Base
insecure_mode: true, insecure_mode: true,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
default_value_for :only_protected_branches, true
belongs_to :project, inverse_of: :remote_mirrors belongs_to :project, inverse_of: :remote_mirrors
validates :url, presence: true, url: { protocols: %w(ssh git http https), allow_blank: true, enforce_user: true } validates :url, presence: true, url: { protocols: %w(ssh git http https), allow_blank: true, enforce_user: true }
......
...@@ -85,6 +85,12 @@ class WikiPage ...@@ -85,6 +85,12 @@ class WikiPage
alias_method :to_param, :slug alias_method :to_param, :slug
def human_title
return 'Home' if title == 'home'
title
end
# The formatted title of this page. # The formatted title of this page.
def title def title
if @attributes[:title] if @attributes[:title]
......
...@@ -6,6 +6,7 @@ class AccessTokenValidationService ...@@ -6,6 +6,7 @@ class AccessTokenValidationService
EXPIRED = :expired EXPIRED = :expired
REVOKED = :revoked REVOKED = :revoked
INSUFFICIENT_SCOPE = :insufficient_scope INSUFFICIENT_SCOPE = :insufficient_scope
IMPERSONATION_DISABLED = :impersonation_disabled
attr_reader :token, :request attr_reader :token, :request
...@@ -24,6 +25,11 @@ class AccessTokenValidationService ...@@ -24,6 +25,11 @@ class AccessTokenValidationService
elsif !self.include_any_scope?(scopes) elsif !self.include_any_scope?(scopes)
return INSUFFICIENT_SCOPE return INSUFFICIENT_SCOPE
elsif token.respond_to?(:impersonation) &&
token.impersonation &&
!Gitlab.config.gitlab.impersonation_enabled
return IMPERSONATION_DISABLED
else else
return VALID return VALID
end end
......
# frozen_string_literal: true
module Ci
class ArchiveTraceService
def execute(job)
job.trace.archive!
rescue ::Gitlab::Ci::Trace::AlreadyArchivedError
# It's already archived, thus we can safely ignore this exception.
rescue => e
# Tracks this error with application logs, Sentry, and Prometheus.
# If `archive!` keeps failing for over a week, that could incur data loss.
# (See more https://docs.gitlab.com/ee/administration/job_traces.html#new-live-trace-architecture)
# In order to avoid interrupting the system, we do not raise an exception here.
archive_error(e, job)
end
private
def failed_archive_counter
@failed_archive_counter ||=
Gitlab::Metrics.counter(:job_trace_archive_failed_total,
"Counter of failed attempts of trace archiving")
end
def archive_error(error, job)
failed_archive_counter.increment
Rails.logger.error "Failed to archive trace. id: #{job.id} message: #{error.message}"
Gitlab::Sentry
.track_exception(error,
issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/51502',
extra: { job_id: job.id })
end
end
end
...@@ -8,6 +8,7 @@ module Files ...@@ -8,6 +8,7 @@ module Files
transformer = Lfs::FileTransformer.new(project, @branch_name) transformer = Lfs::FileTransformer.new(project, @branch_name)
actions = actions_after_lfs_transformation(transformer, params[:actions]) actions = actions_after_lfs_transformation(transformer, params[:actions])
actions = transform_move_actions(actions)
commit_actions!(actions) commit_actions!(actions)
end end
...@@ -26,6 +27,16 @@ module Files ...@@ -26,6 +27,16 @@ module Files
end end
end end
# When moving a file, `content: nil` means "use the contents of the previous
# file", while `content: ''` means "move the file and set it to empty"
def transform_move_actions(actions)
actions.map do |action|
action[:infer_content] = true if action[:content].nil?
action
end
end
def commit_actions!(actions) def commit_actions!(actions)
repository.multi_action( repository.multi_action(
current_user, current_user,
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
%span.cred (Auditor) %span.cred (Auditor)
.float-right .float-right
- if @user != current_user && @user.can?(:log_in) - if impersonation_enabled? && @user != current_user && @user.can?(:log_in)
= link_to 'Impersonate', impersonate_admin_user_path(@user), method: :post, class: "btn btn-nr btn-grouped btn-info" = link_to 'Impersonate', impersonate_admin_user_path(@user), method: :post, class: "btn btn-nr btn-grouped btn-info"
= link_to edit_admin_user_path(@user), class: "btn btn-nr btn-grouped" do = link_to edit_admin_user_path(@user), class: "btn btn-nr btn-grouped" do
%i.fa.fa-pencil-square-o %i.fa.fa-pencil-square-o
......
= email_default_heading("Hello, #{@resource.name}!")
- if @resource.try(:unconfirmed_email?)
%p
We're contacting you to notify you that your email is being changed to #{@resource.reload.unconfirmed_email}.
- else
%p
We're contacting you to notify you that your email has been changed to #{@resource.email}.
%p
If you did not initiate this change, please contact your administrator
immediately.
Hello, <%= @resource.name %>!
<% if @resource.try(:unconfirmed_email?) %>
We're contacting you to notify you that your email is being changed to <%= @resource.reload.unconfirmed_email %>.
<% else %>
We're contacting you to notify you that your email has been changed to <%= @resource.email %>.
<% end %>
If you did not initiate this change, please contact your administrator
immediately.
- page_title 'Edit', @label.name, 'Labels' - add_to_breadcrumbs _("Labels"), group_labels_path(@group)
- breadcrumb_title _("Edit")
- page_title "Edit", @label.name, _("Labels")
%h3.page-title %h3.page-title
Edit Label Edit Label
......
- breadcrumb_title "Labels" - add_to_breadcrumbs _("Labels"), group_labels_path(@group)
- page_title 'New Label' - breadcrumb_title _("New")
- page_title _("New Label")
%h3.page-title %h3.page-title
New Label New Label
......
- page_title "Milestones" - breadcrumb_title _("Edit")
- page_title _("Milestones")
- render "header_title" - render "header_title"
%h3.page-title %h3.page-title
Edit Milestone Edit Milestone
%hr
= render "form" = render "form"
- breadcrumb_title "Milestones" - @no_container = true
- page_title "Milestones" - add_to_breadcrumbs _("Milestones"), group_milestones_path(@group)
- breadcrumb_title _("New")
- page_title _("Milestones"), @milestone.name, _("Milestones")
%h3.page-title %div{ class: container_class }
New Milestone %h3.page-title
New Milestone
= render "form" %hr
= render "form"
...@@ -8,69 +8,57 @@ ...@@ -8,69 +8,57 @@
- ref = local_assigns.fetch(:ref) { merge_request&.source_branch } - ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
- link = commit_path(project, commit, merge_request: merge_request) - link = commit_path(project, commit, merge_request: merge_request)
- cache_key = [project.full_path,
ref,
commit.id,
Gitlab::CurrentSettings.current_application_settings,
@path.presence,
current_controller?(:commits),
merge_request&.iid,
view_details,
commit.status(ref),
I18n.locale].compact
-# EE-only -# EE-only
- show_project_name = local_assigns.fetch(:show_project_name, false) - show_project_name = local_assigns.fetch(:show_project_name, false)
- cache_key << show_project_name
= cache(cache_key, expires_in: 1.day) do %li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
%li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
.avatar-cell.d-none.d-sm-block .avatar-cell.d-none.d-sm-block
= author_avatar(commit, size: 36, has_tooltip: false) = author_avatar(commit, size: 36, has_tooltip: false)
.commit-detail.flex-list .commit-detail.flex-list
.commit-content.qa-commit-content .commit-content.qa-commit-content
- if view_details && merge_request - if view_details && merge_request
= link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title" = link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title"
- else - else
= link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title") = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
%span.commit-row-message.d-block.d-sm-none %span.commit-row-message.d-block.d-sm-none
&middot; &middot;
= commit.short_id = commit.short_id
- if commit.status(ref) - if commit.status(ref)
.d-block.d-sm-none .d-block.d-sm-none
= render_commit_status(commit, ref: ref) = render_commit_status(commit, ref: ref)
- if commit.description? - if commit.description?
%button.text-expander.js-toggle-button %button.text-expander.js-toggle-button
= sprite_icon('ellipsis_h', size: 12) = sprite_icon('ellipsis_h', size: 12)
.committer .committer
- commit_author_link = commit_author_link(commit, avatar: false, size: 24) - commit_author_link = commit_author_link(commit, avatar: false, size: 24)
- commit_timeago = time_ago_with_tooltip(commit.authored_date, placement: 'bottom') - commit_timeago = time_ago_with_tooltip(commit.authored_date, placement: 'bottom')
- commit_text = _('%{commit_author_link} authored %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago } - commit_text = _('%{commit_author_link} authored %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
#{ commit_text.html_safe } #{ commit_text.html_safe }
- if show_project_name - if show_project_name
%span.project_namespace %span.project_namespace
= project.full_name = project.full_name
- if commit.description? - if commit.description?
%pre.commit-row-description.js-toggle-content.append-bottom-8 %pre.commit-row-description.js-toggle-content.append-bottom-8
= preserve(markdown_field(commit, :description)) = preserve(markdown_field(commit, :description))
.commit-actions.flex-row.d-none.d-sm-flex .commit-actions.flex-row.d-none.d-sm-flex
- if request.xhr? - if request.xhr?
= render partial: 'projects/commit/signature', object: commit.signature = render partial: 'projects/commit/signature', object: commit.signature
- else - else
= render partial: 'projects/commit/ajax_signature', locals: { commit: commit } = render partial: 'projects/commit/ajax_signature', locals: { commit: commit }
- if commit.status(ref) - if commit.status(ref)
= render_commit_status(commit, ref: ref) = render_commit_status(commit, ref: ref)
.js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id, ref: ref) } } .js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id, ref: ref) } }
.commit-sha-group .commit-sha-group
.label.label-monospace .label.label-monospace
= commit.short_id = commit.short_id
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"), class: "btn btn-default", container: "body") = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"), class: "btn btn-default", container: "body")
= link_to_browse_code(project, commit) = link_to_browse_code(project, commit)
- @no_container = true - @no_container = true
- add_to_breadcrumbs "Labels", project_labels_path(@project)
- breadcrumb_title "Edit"
- page_title "Edit", @label.name, "Labels" - page_title "Edit", @label.name, "Labels"
%div{ class: container_class } %div{ class: container_class }
......
- @no_container = true - @no_container = true
- breadcrumb_title "Labels" - add_to_breadcrumbs "Labels", project_labels_path(@project)
- breadcrumb_title "New"
- page_title "New Label" - page_title "New Label"
%div{ class: container_class } %div{ class: container_class }
......
- @no_container = true - @no_container = true
- breadcrumb_title "Edit"
- add_to_breadcrumbs "Milestones", project_milestones_path(@project)
- page_title "Edit", @milestone.title, "Milestones" - page_title "Edit", @milestone.title, "Milestones"
%div{ class: container_class } %div{ class: container_class }
%h3.page-title %h3.page-title
......
- @no_container = true - @no_container = true
- breadcrumb_title "Milestones" - add_to_breadcrumbs "Milestones", project_milestones_path(@project)
- breadcrumb_title "New"
- page_title "New Milestone" - page_title "New Milestone"
%div{ class: container_class } %div{ class: container_class }
......
%li{ class: active_when(params[:id] == wiki_page.slug) } %li{ class: active_when(params[:id] == wiki_page.slug) }
= link_to project_wiki_path(@project, wiki_page) do = link_to project_wiki_path(@project, wiki_page) do
= wiki_page.title.capitalize = wiki_page.human_title
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
- page_title _("Edit"), @page.title.capitalize, _("Wiki") - add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, @page)
- breadcrumb_title @page.persisted? ? _("Edit") : _("New")
- page_title @page.persisted? ? _("Edit") : _("New"), @page.human_title, _("Wiki")
= wiki_page_errors(@error) = wiki_page_errors(@error)
...@@ -10,9 +12,9 @@ ...@@ -10,9 +12,9 @@
.nav-text .nav-text
%h2.wiki-page-title %h2.wiki-page-title
- if @page.persisted? - if @page.persisted?
= link_to @page.title.capitalize, project_wiki_path(@project, @page) = link_to @page.human_title, project_wiki_path(@project, @page)
- else - else
= @page.title.capitalize = @page.human_title
%span.light %span.light
&middot; &middot;
- if @page.persisted? - if @page.persisted?
...@@ -28,7 +30,7 @@ ...@@ -28,7 +30,7 @@
= link_to project_wiki_history_path(@project, @page), class: "btn" do = link_to project_wiki_history_path(@project, @page), class: "btn" do
= s_("Wiki|Page history") = s_("Wiki|Page history")
- if can?(current_user, :admin_wiki, @project) - if can?(current_user, :admin_wiki, @project)
#delete-wiki-modal-wrapper{ data: { delete_wiki_url: project_wiki_path(@project, @page), page_title: @page.title.capitalize } } #delete-wiki-modal-wrapper{ data: { delete_wiki_url: project_wiki_path(@project, @page), page_title: @page.human_title } }
= render 'form', uploads_path: wiki_attachment_upload_url = render 'form', uploads_path: wiki_attachment_upload_url
......
- page_title _("History"), @page.title.capitalize, _("Wiki") - page_title _("History"), @page.human_title, _("Wiki")
.wiki-page-header.has-sidebar-toggle .wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
.nav-text .nav-text
%h2.wiki-page-title %h2.wiki-page-title
= link_to @page.title.capitalize, project_wiki_path(@project, @page) = link_to @page.human_title, project_wiki_path(@project, @page)
%span.light %span.light
&middot; &middot;
= _("History") = _("History")
......
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
- breadcrumb_title @page.title.capitalize - breadcrumb_title @page.human_title
- wiki_breadcrumb_dropdown_links(@page.slug) - wiki_breadcrumb_dropdown_links(@page.slug)
- page_title @page.title.capitalize, _("Wiki") - page_title @page.human_title, _("Wiki")
- add_to_breadcrumbs _("Wiki"), get_project_wiki_path(@project) - add_to_breadcrumbs _("Wiki"), get_project_wiki_path(@project)
.wiki-page-header.has-sidebar-toggle .wiki-page-header.has-sidebar-toggle
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
= icon('angle-double-left') = icon('angle-double-left')
.nav-text .nav-text
%h2.wiki-page-title= @page.title.capitalize %h2.wiki-page-title= @page.human_title
%span.wiki-last-edit-by %span.wiki-last-edit-by
- if @page.last_version - if @page.last_version
= (_("Last edited by %{name}") % { name: "<strong>#{@page.last_version.author_name}</strong>" }).html_safe = (_("Last edited by %{name}") % { name: "<strong>#{@page.last_version.author_name}</strong>" }).html_safe
......
...@@ -35,8 +35,8 @@ ...@@ -35,8 +35,8 @@
.col-sm-2 .col-sm-2
.milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end .milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end
- if @project - if @project
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active? - if can_admin_project_milestones? and milestone.active?
- if @project.group - if can_admin_group_milestones?
%button.js-promote-project-milestone-button.btn.btn-blank.btn-sm.btn-grouped.has-tooltip{ title: _('Promote to Group Milestone'), %button.js-promote-project-milestone-button.btn.btn-blank.btn-sm.btn-grouped.has-tooltip{ title: _('Promote to Group Milestone'),
disabled: true, disabled: true,
type: 'button', type: 'button',
......
...@@ -3,31 +3,31 @@ ...@@ -3,31 +3,31 @@
.d-none.d-sm-block .d-none.d-sm-block
- if can?(current_user, :update_personal_snippet, @snippet) - if can?(current_user, :update_personal_snippet, @snippet)
= link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do = link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do
Edit = _("Edit")
- if can?(current_user, :admin_personal_snippet, @snippet) - if can?(current_user, :admin_personal_snippet, @snippet)
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do = link_to snippet_path(@snippet), method: :delete, data: { confirm: _("Are you sure?") }, class: "btn btn-grouped btn-inverted btn-remove", title: _('Delete Snippet') do
Delete = _("Delete")
= link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-success", title: "New snippet" do = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: _("New snippet") do
New snippet = _("New snippet")
- if @snippet.submittable_as_spam_by?(current_user) - if @snippet.submittable_as_spam_by?(current_user)
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam' = link_to _('Submit as spam'), mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: _('Submit as spam')
.d-block.d-sm-none.dropdown .d-block.d-sm-none.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
Options = _("Options")
= icon('caret-down') = icon('caret-down')
.dropdown-menu.dropdown-menu-full-width .dropdown-menu.dropdown-menu-full-width
%ul %ul
%li %li
= link_to new_snippet_path, title: "New snippet" do = link_to new_snippet_path, title: _("New snippet") do
New snippet = _("New snippet")
- if can?(current_user, :admin_personal_snippet, @snippet) - if can?(current_user, :admin_personal_snippet, @snippet)
%li %li
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do = link_to snippet_path(@snippet), method: :delete, data: { confirm: _("Are you sure?") }, title: _('Delete Snippet') do
Delete = _("Delete")
- if can?(current_user, :update_personal_snippet, @snippet) - if can?(current_user, :update_personal_snippet, @snippet)
%li %li
= link_to edit_snippet_path(@snippet) do = link_to edit_snippet_path(@snippet) do
Edit = _("Edit")
- if @snippet.submittable_as_spam_by?(current_user) - if @snippet.submittable_as_spam_by?(current_user)
%li %li
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post = link_to _('Submit as spam'), mark_as_spam_snippet_path(@snippet), method: :post
...@@ -5,6 +5,6 @@ ...@@ -5,6 +5,6 @@
= render partial: 'shared/snippets/snippet', collection: @snippets, locals: { link_project: link_project } = render partial: 'shared/snippets/snippet', collection: @snippets, locals: { link_project: link_project }
- if @snippets.empty? - if @snippets.empty?
%li %li
.nothing-here-block Nothing here. .nothing-here-block= _("Nothing here.")
= paginate @snippets, theme: 'gitlab' = paginate @snippets, theme: 'gitlab'
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.nav-links.snippet-scope-menu.mobile-separator.nav.nav-tabs .nav-links.snippet-scope-menu.mobile-separator.nav.nav-tabs
%li{ class: active_when(params[:scope].nil?) } %li{ class: active_when(params[:scope].nil?) }
= link_to subject_snippets_path(subject) do = link_to subject_snippets_path(subject) do
All = _("All")
%span.badge.badge-pill %span.badge.badge-pill
- if include_private - if include_private
= subject.snippets.count = subject.snippets.count
...@@ -14,18 +14,18 @@ ...@@ -14,18 +14,18 @@
- if include_private - if include_private
%li{ class: active_when(params[:scope] == "are_private") } %li{ class: active_when(params[:scope] == "are_private") }
= link_to subject_snippets_path(subject, scope: 'are_private') do = link_to subject_snippets_path(subject, scope: 'are_private') do
Private = _("Private")
%span.badge.badge-pill %span.badge.badge-pill
= subject.snippets.are_private.count = subject.snippets.are_private.count
%li{ class: active_when(params[:scope] == "are_internal") } %li{ class: active_when(params[:scope] == "are_internal") }
= link_to subject_snippets_path(subject, scope: 'are_internal') do = link_to subject_snippets_path(subject, scope: 'are_internal') do
Internal = _("Internal")
%span.badge.badge-pill %span.badge.badge-pill
= subject.snippets.are_internal.count = subject.snippets.are_internal.count
%li{ class: active_when(params[:scope] == "are_public") } %li{ class: active_when(params[:scope] == "are_public") }
= link_to subject_snippets_path(subject, scope: 'are_public') do = link_to subject_snippets_path(subject, scope: 'are_public') do
Public = _("Public")
%span.badge.badge-pill %span.badge.badge-pill
= subject.snippets.are_public.count = subject.snippets.are_public.count
- page_title "Edit", "#{@snippet.title} (#{@snippet.to_reference})", "Snippets" - page_title _("Edit"), "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
%h3.page-title %h3.page-title
Edit Snippet = _("Edit Snippet")
%hr %hr
= render 'shared/snippets/form', url: snippet_path(@snippet) = render 'shared/snippets/form', url: snippet_path(@snippet)
- page_title "By #{@user.name}", "Snippets" - page_title _("By %{user_name}") % { user_name: @user.name }, _("Snippets")
%ol.breadcrumb %ol.breadcrumb
%li.breadcrumb-item %li.breadcrumb-item
= link_to snippets_path do = link_to snippets_path do
Snippets = _("Snippets")
%li.breadcrumb-item %li.breadcrumb-item
= @user.name = @user.name
.float-right.d-none.d-sm-block .float-right.d-none.d-sm-block
= link_to user_path(@user) do = link_to user_path(@user) do
#{@user.name} profile page = _("%{user_name} profile page") % { user_name: @user.name }
= render 'snippets' = render 'snippets'
- @hide_top_links = true - @hide_top_links = true
- @hide_breadcrumbs = true - @hide_breadcrumbs = true
- page_title "New Snippet" - page_title _("New Snippet")
.page-title-holder .page-title-holder
%h1.page-title= _('New Snippet') %h1.page-title= _('New Snippet')
......
- if current_user - if current_user
- if note.emoji_awardable? - if note.emoji_awardable?
.note-actions-item .note-actions-item
= link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do = link_to '#', title: _('Add reaction'), class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do
= icon('spinner spin') = icon('spinner spin')
%span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face') %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
%span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley') %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
- if note_editable - if note_editable
.note-actions-item .note-actions-item
= button_tag title: 'Edit comment', class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body' } do = button_tag title: _('Edit comment'), class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body' } do
%span.link-highlight %span.link-highlight
= custom_icon('icon_pencil') = custom_icon('icon_pencil')
......
- @hide_top_links = true - @hide_top_links = true
- @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout - @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout
- add_to_breadcrumbs "Snippets", dashboard_snippets_path - add_to_breadcrumbs _("Snippets"), dashboard_snippets_path
- breadcrumb_title @snippet.to_reference - breadcrumb_title @snippet.to_reference
- page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets" - page_title "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
= render 'shared/snippets/header' = render 'shared/snippets/header'
......
...@@ -7,7 +7,7 @@ class ArchiveTraceWorker ...@@ -7,7 +7,7 @@ class ArchiveTraceWorker
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform(job_id) def perform(job_id)
Ci::Build.without_archived_trace.find_by(id: job_id).try do |job| Ci::Build.without_archived_trace.find_by(id: job_id).try do |job|
job.trace.archive! Ci::ArchiveTraceService.new.execute(job)
end end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -11,21 +11,9 @@ module Ci ...@@ -11,21 +11,9 @@ module Ci
# This could happen when ArchiveTraceWorker sidekiq jobs were lost by receiving SIGKILL # This could happen when ArchiveTraceWorker sidekiq jobs were lost by receiving SIGKILL
# More details in https://gitlab.com/gitlab-org/gitlab-ce/issues/36791 # More details in https://gitlab.com/gitlab-org/gitlab-ce/issues/36791
Ci::Build.finished.with_live_trace.find_each(batch_size: 100) do |build| Ci::Build.finished.with_live_trace.find_each(batch_size: 100) do |build|
begin Ci::ArchiveTraceService.new.execute(build)
build.trace.archive!
rescue ::Gitlab::Ci::Trace::AlreadyArchivedError
rescue => e
failed_archive_counter.increment
Rails.logger.error "Failed to archive stale live trace. id: #{build.id} message: #{e.message}"
end
end end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
private
def failed_archive_counter
@failed_archive_counter ||= Gitlab::Metrics.counter(:job_trace_archive_failed_total, "Counter of failed attempts of traces archiving")
end
end end
end end
---
title: Add config to prohibit impersonation
merge_request: 23338
author:
type: added
---
title: 'Commits API: Preserve file content in move operations if unspecified'
merge_request: 23387
author:
type: fixed
---
title: Externalize strings from `/app/views/snippets`
merge_request: 23351
author: Tao Wang
type: other
---
title: Include new link in breadcrumb for issues, merge requests, milestones, and labels
merge_request: 18515
author: George Tsiolis
type: changed
---
title: Redact sensitive information on gitlab-workhorse log
merge_request:
author:
type: security
---
title: Do not follow redirects in Prometheus service when making http requests to the configured api url
merge_request:
author:
type: security
---
title: Don't expose confidential information in commit message list
merge_request:
author:
type: security
---
title: Provide email notification when a user changes their email address
merge_request:
author:
type: security
---
title: Resolve reflected XSS in Ouath authorize window
merge_request:
author:
type: security
---
title: Fix SSRF in project integrations
merge_request:
author:
type: security
---
title: Fix CRLF vulnerability in Project hooks
merge_request:
author:
type: security
---
title: Fix milestone promotion authorization check
merge_request:
author:
type: security
---
title: Configure mermaid to not render HTML content in diagrams
merge_request:
author:
type: security
---
title: Fix a possible symlink time of check to time of use race condition in GitLab
Pages
merge_request:
author:
type: security
---
title: Removed ability to see private group names when the group id is entered in
the url.
merge_request:
author:
type: security
---
title: Fix stored XSS for Environments
merge_request:
author:
type: security
---
title: Fix possible XSS attack in Markdown urls with spaces
merge_request: 2599
author:
type: security
---
title: Remove needless auto-capitalization on Wiki page titles
merge_request: 23288
author:
type: fixed
---
title: Add monkey patch to unicorn to fix eof? problem
merge_request: 23385
author:
type: fixed
...@@ -13,6 +13,10 @@ if defined?(Unicorn) ...@@ -13,6 +13,10 @@ if defined?(Unicorn)
# Max memory size (RSS) per worker # Max memory size (RSS) per worker
use Unicorn::WorkerKiller::Oom, min, max use Unicorn::WorkerKiller::Oom, min, max
end end
# Monkey patch for fixing Rack 2.0.6 bug:
# https://gitlab.com/gitlab-org/gitlab-ee/issues/8539
Unicorn::StreamInput.send(:public, :eof?) # rubocop:disable GitlabSecurity/PublicSend
end end
require ::File.expand_path('../config/environment', __FILE__) require ::File.expand_path('../config/environment', __FILE__)
......
...@@ -117,6 +117,9 @@ module Gitlab ...@@ -117,6 +117,9 @@ module Gitlab
# - Webhook URLs (:hook) # - Webhook URLs (:hook)
# - Sentry DSN (:sentry_dsn) # - Sentry DSN (:sentry_dsn)
# - File content from Web Editor (:content) # - File content from Web Editor (:content)
#
# NOTE: It is **IMPORTANT** to also update gitlab-workhorse's filter when adding parameters here to not
# introduce another security vulnerability: https://gitlab.com/gitlab-org/gitlab-workhorse/issues/182
config.filter_parameters += [/token$/, /password/, /secret/, /key$/] config.filter_parameters += [/token$/, /password/, /secret/, /key$/]
config.filter_parameters += %i( config.filter_parameters += %i(
certificate certificate
......
...@@ -114,6 +114,9 @@ production: &base ...@@ -114,6 +114,9 @@ production: &base
# The default is 'shared/cache/archive/' relative to the root of the Rails app. # The default is 'shared/cache/archive/' relative to the root of the Rails app.
# repository_downloads_path: shared/cache/archive/ # repository_downloads_path: shared/cache/archive/
## Impersonation settings
impersonation_enabled: true
## Reply by email ## Reply by email
# Allow users to comment on issues and merge requests by replying to notification emails. # Allow users to comment on issues and merge requests by replying to notification emails.
# For documentation on how to set this up, see http://doc.gitlab.com/ce/administration/reply_by_email.html # For documentation on how to set this up, see http://doc.gitlab.com/ce/administration/reply_by_email.html
......
...@@ -166,6 +166,7 @@ Settings.gitlab['domain_whitelist'] ||= [] ...@@ -166,6 +166,7 @@ Settings.gitlab['domain_whitelist'] ||= []
Settings.gitlab['import_sources'] ||= Gitlab::ImportSources.values Settings.gitlab['import_sources'] ||= Gitlab::ImportSources.values
Settings.gitlab['trusted_proxies'] ||= [] Settings.gitlab['trusted_proxies'] ||= []
Settings.gitlab['no_todos_messages'] ||= YAML.load_file(Rails.root.join('config', 'no_todos_messages.yml')) Settings.gitlab['no_todos_messages'] ||= YAML.load_file(Rails.root.join('config', 'no_todos_messages.yml'))
Settings.gitlab['impersonation_enabled'] ||= true if Settings.gitlab['impersonation_enabled'].nil?
Settings.gitlab['usage_ping_enabled'] = true if Settings.gitlab['usage_ping_enabled'].nil? Settings.gitlab['usage_ping_enabled'] = true if Settings.gitlab['usage_ping_enabled'].nil?
# #
......
...@@ -103,6 +103,9 @@ Devise.setup do |config| ...@@ -103,6 +103,9 @@ Devise.setup do |config|
# Send a notification email when the user's password is changed # Send a notification email when the user's password is changed
config.send_password_change_notification = true config.send_password_change_notification = true
# Send a notification email when the user's email is changed
config.send_email_changed_notification = true
# ==> Configuration for :validatable # ==> Configuration for :validatable
# Range for password length. Default is 6..128. # Range for password length. Default is 6..128.
config.password_length = 8..128 config.password_length = 8..128
......
...@@ -48,6 +48,13 @@ Doorkeeper.configure do ...@@ -48,6 +48,13 @@ Doorkeeper.configure do
# #
force_ssl_in_redirect_uri false force_ssl_in_redirect_uri false
# Specify what redirect URI's you want to block during Application creation.
# Any redirect URI is whitelisted by default.
#
# You can use this option in order to forbid URI's with 'javascript' scheme
# for example.
forbid_redirect_uri { |uri| %w[data vbscript javascript].include?(uri.scheme.to_s.downcase) }
# Provide support for an owner to be assigned to each registered application (disabled by default) # Provide support for an owner to be assigned to each registered application (disabled by default)
# Optional parameter confirmation: true (default false) if you want to enforce ownership of # Optional parameter confirmation: true (default false) if you want to enforce ownership of
# a registered application # a registered application
......
# frozen_string_literal: true
class CleanupEnvironmentsExternalUrl < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
update_column_in_batches(:environments, :external_url, nil) do |table, query|
query.where(table[:external_url].matches('javascript://%'))
end
end
def down
end
end
# frozen_string_literal: true
class MigrateForbiddenRedirectUris < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
FORBIDDEN_SCHEMES = %w[data:// vbscript:// javascript://]
NEW_URI = 'http://forbidden-scheme-has-been-overwritten'
disable_ddl_transaction!
def up
update_forbidden_uris(:oauth_applications)
update_forbidden_uris(:oauth_access_grants)
end
def down
# noop
end
private
def update_forbidden_uris(table_name)
update_column_in_batches(table_name, :redirect_uri, NEW_URI) do |table, query|
where_clause = FORBIDDEN_SCHEMES.map do |scheme|
table[:redirect_uri].matches("#{scheme}%")
end.inject(&:or)
query.where(where_clause)
end
end
end
...@@ -232,6 +232,43 @@ For more information, refer to the ...@@ -232,6 +232,43 @@ For more information, refer to the
Impersonation tokens are used exactly like regular personal access tokens, and can be passed in either the Impersonation tokens are used exactly like regular personal access tokens, and can be passed in either the
`private_token` parameter or the `Private-Token` header. `private_token` parameter or the `Private-Token` header.
#### Disable impersonation
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/40385) in GitLab
11.6.
By default, impersonation is enabled. To disable impersonation, GitLab must be
reconfigured:
**For Omnibus installations**
1. Edit `/etc/gitlab/gitlab.rb`:
```ruby
gitlab_rails['impersonation_enabled'] = false
```
1. Save the file and [reconfigure](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure)
GitLab for the changes to take effect.
To re-enable impersonation, remove this configuration and reconfigure GitLab.
---
**For installations from source**
1. Edit `config/gitlab.yml`:
```yaml
gitlab:
impersonation_enabled: false
```
1. Save the file and [restart](../administration/restart_gitlab.md#installations-from-source)
GitLab for the changes to take effect.
To re-enable impersonation, remove this configuration and restart GitLab.
### Sudo ### Sudo
NOTE: **Note:** NOTE: **Note:**
...@@ -547,7 +584,7 @@ When you try to access an API URL that does not exist you will receive 404 Not F ...@@ -547,7 +584,7 @@ When you try to access an API URL that does not exist you will receive 404 Not F
``` ```
HTTP/1.1 404 Not Found HTTP/1.1 404 Not Found
Content-Type: application/json Content-Type: application/json
{ { f
"error": "404 Not Found" "error": "404 Not Found"
} }
``` ```
......
...@@ -87,7 +87,7 @@ POST /projects/:id/repository/commits ...@@ -87,7 +87,7 @@ POST /projects/:id/repository/commits
| `action` | string | yes | The action to perform, `create`, `delete`, `move`, `update`, `chmod`| | `action` | string | yes | The action to perform, `create`, `delete`, `move`, `update`, `chmod`|
| `file_path` | string | yes | Full path to the file. Ex. `lib/class.rb` | | `file_path` | string | yes | Full path to the file. Ex. `lib/class.rb` |
| `previous_path` | string | no | Original full path to the file being moved. Ex. `lib/class1.rb`. Only considered for `move` action. | | `previous_path` | string | no | Original full path to the file being moved. Ex. `lib/class1.rb`. Only considered for `move` action. |
| `content` | string | no | File content, required for all except `delete` and `chmod`. Optional for `move` | | `content` | string | no | File content, required for all except `delete`, `chmod`, and `move`. Move actions that do not specify `content` will preserve the existing file content, and any other value of `content` will overwrite the file content. |
| `encoding` | string | no | `text` or `base64`. `text` is default. | | `encoding` | string | no | `text` or `base64`. `text` is default. |
| `last_commit_id` | string | no | Last known file commit id. Will be only considered in update, move and delete actions. | | `last_commit_id` | string | no | Last known file commit id. Will be only considered in update, move and delete actions. |
| `execute_filemode` | boolean | no | When `true/false` enables/disables the execute flag on the file. Only considered for `chmod` action. | | `execute_filemode` | boolean | no | When `true/false` enables/disables the execute flag on the file. Only considered for `chmod` action. |
......
...@@ -1286,6 +1286,62 @@ If the rebase operation fails, the response will include the following: ...@@ -1286,6 +1286,62 @@ If the rebase operation fails, the response will include the following:
} }
``` ```
## Rebase a merge request
Automatically rebase the `source_branch` of the merge request against its
`target_branch`.
If you don't have permissions to push to the merge request's source branch -
you'll get a `403 Forbidden` response.
```
PUT /projects/:id/merge_requests/:merge_request_iid/rebase
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/76/merge_requests/1/rebase
```
This is an asynchronous request. The API will return an empty `202 Accepted`
response if the request is enqueued successfully.
You can poll the [Get single MR](#get-single-mr) endpoint with the
`include_rebase_in_progress` parameter to check the status of the
asynchronous request.
If the rebase operation is ongoing, the response will include the following:
```json
{
"rebase_in_progress": true
"merge_error": null
}
```
Once the rebase operation has completed successfully, the response will include
the following:
```json
{
"rebase_in_progress": false,
"merge_error": null,
}
```
If the rebase operation fails, the response will include the following:
```json
{
"rebase_in_progress": false,
"merge_error": "Rebase failed. Please rebase locally",
}
```
## Comments on merge requests ## Comments on merge requests
Comments are done via the [notes](notes.md) resource. Comments are done via the [notes](notes.md) resource.
......
...@@ -53,6 +53,8 @@ from teams other than your own. ...@@ -53,6 +53,8 @@ from teams other than your own.
#### Security requirements #### Security requirements
1. If your merge request is processing, storing, or transferring any kind of [RED or ORANGE data][https://docs.google.com/document/d/15eNKGA3zyZazsJMldqTBFbYMnVUSQSpU14lo22JMZQY/edit] (this is a confidential document), it must be
**approved by a [Security Engineer][team]**.
1. If your merge request involves implementing, utilizing, or is otherwise related to any type of authentication, authorization, or session handling mechanism, it must be 1. If your merge request involves implementing, utilizing, or is otherwise related to any type of authentication, authorization, or session handling mechanism, it must be
**approved by a [Security Engineer][team]**. **approved by a [Security Engineer][team]**.
1. If your merge request has a goal which requires a cryptographic function such as: confidentiality, integrity, authentication, or non-repudiation, it must be 1. If your merge request has a goal which requires a cryptographic function such as: confidentiality, integrity, authentication, or non-repudiation, it must be
...@@ -85,6 +87,23 @@ If an author is unsure if a merge request needs a domain expert's opinion, that' ...@@ -85,6 +87,23 @@ If an author is unsure if a merge request needs a domain expert's opinion, that'
usually a pretty good sign that it does, since without it the required level of usually a pretty good sign that it does, since without it the required level of
confidence in their solution will not have been reached. confidence in their solution will not have been reached.
Before the review, the author is requested to submit comments on the merge
request diff alerting the reviewer to anything important as well as for anything
that demands further explanation or attention. Examples of content that may
warrant a comment could be:
- The addition of a linting rule (Rubocop, JS etc)
- The addition of a library (Ruby gem, JS lib etc)
- Where not obvious, a link to the parent class or method
- Any benchmarking performed to complement the change
- Potentially insecure code
Do not add these comments directly to the source code, unless the
reviewer requires you to do so.
This
[saves reviewers time and helps authors catch mistakes earlier](https://www.ibm.com/developerworks/rational/library/11-proven-practices-for-peer-review/index.html#__RefHeading__97_174136755).
### The responsibility of the maintainer ### The responsibility of the maintainer
Maintainers are responsible for the overall health, quality, and consistency of Maintainers are responsible for the overall health, quality, and consistency of
......
# GraphQL
We use [Apollo] and [Vue Apollo][vue-apollo] for working with GraphQL
on the frontend.
In order to use GraphQL, you need to enable the `graphql` feature flag,
read more about [Feature Flags][feature-flags].
## Apollo Client
To save duplicated clients getting created in different apps, we have a
[default client][defualt-client] that should be used. This setups the
Apollo client with the correct URL and also sets the CSRF headers.
## GraphQL Queries
To save query compilation at runtime, webpack can directly import `.graphql`
files. This allows webpack to preprocess the query at compile time instead
of the client doing compilation of queries.
## Usage in Vue
To use Vue Apollo, import the [Vue Apollo][vue-apollo] plugin as well
as the default client. This should be created at the same point
the Vue application is mounted.
```javascript
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import defaultClient from '~/lib/graphql';
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient,
});
new Vue({
...,
apolloProvider,
...
});
```
Read more about [Vue Apollo][vue-apollo] in the [Vue Apollo documentation][vue-apollo-docs].
### Testing
With [Vue test utils][vue-test-utils] it is easy to quickly test components that
fetch GraphQL queries. The simplest way is to use `shallowMount` and then set
the data on the component
```javascript
it('tests apollo component', () => {
const vm = shallowMount(App);
vm.setData({
...mock data
});
});
```
## Usage outside of Vue
It is also possible to use GraphQL outside of Vue by directly importing
and using the default client with queries.
```javascript
import defaultClient from '~/lib/graphql';
import query from './query.graphql';
defaultClient.query(query)
.then(result => console.log(result));
```
Read more about the [Apollo] client in the [Apollo documentation][apollo-client-docs].
[Apollo]: https://www.apollographql.com/
[vue-apollo]: https://github.com/Akryum/vue-apollo/
[vue-apollo-docs]: https://akryum.github.io/vue-apollo/
[feature-flags]: ../feature_flags.md
[default-client]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/lib/graphql.js
[apollo-client-docs]: https://www.apollographql.com/docs/tutorial/client.html
[vue-test-utils]: https://vue-test-utils.vuejs.org/
...@@ -54,6 +54,9 @@ Vuex specific design patterns and practices. ...@@ -54,6 +54,9 @@ Vuex specific design patterns and practices.
## [Axios](axios.md) ## [Axios](axios.md)
Axios specific practices and gotchas. Axios specific practices and gotchas.
## [GraphQL](graphql.md)
How to use GraphQL
## [Icons and Illustrations](icons.md) ## [Icons and Illustrations](icons.md)
How we use SVG for our Icons and Illustrations. How we use SVG for our Icons and Illustrations.
......
...@@ -604,6 +604,8 @@ If you fail to restore this encryption key file along with the application data ...@@ -604,6 +604,8 @@ If you fail to restore this encryption key file along with the application data
backup, users with two-factor authentication enabled and GitLab Runners will backup, users with two-factor authentication enabled and GitLab Runners will
lose access to your GitLab server. lose access to your GitLab server.
You may also want to restore any TLS keys, certificates, or [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
Depending on your case, you might want to run the restore command with one or Depending on your case, you might want to run the restore command with one or
more of the following options: more of the following options:
......
...@@ -44,6 +44,5 @@ This page gathers all the resources for the topic **Authentication** within GitL ...@@ -44,6 +44,5 @@ This page gathers all the resources for the topic **Authentication** within GitL
- [Kanboard Plugin GitLab Authentication](https://github.com/kanboard/plugin-gitlab-auth) - [Kanboard Plugin GitLab Authentication](https://github.com/kanboard/plugin-gitlab-auth)
- [Jenkins GitLab OAuth Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+OAuth+Plugin) - [Jenkins GitLab OAuth Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+OAuth+Plugin)
- [Set up Gitlab CE with Active Directory authentication](https://www.caseylabs.com/setup-gitlab-ce-with-active-directory-authentication/)
- [How to customize GitLab to support OpenID authentication](http://eric.van-der-vlist.com/blog/2013/11/23/how-to-customize-gitlab-to-support-openid-authentication/) - [How to customize GitLab to support OpenID authentication](http://eric.van-der-vlist.com/blog/2013/11/23/how-to-customize-gitlab-to-support-openid-authentication/)
- [Openshift - Configuring Authentication and User Agent](https://docs.openshift.org/latest/install_config/configuring_authentication.html#GitLab) - [Openshift - Configuring Authentication and User Agent](https://docs.openshift.org/latest/install_config/configuring_authentication.html#GitLab)
...@@ -239,13 +239,10 @@ by GitLab before installing any of the above applications. ...@@ -239,13 +239,10 @@ by GitLab before installing any of the above applications.
## Getting the external IP address ## Getting the external IP address
NOTE: **Note:** NOTE: **Note:**
You need a load balancer installed in your cluster in order to obtain the With the following procedure, a load balancer must be installed in your cluster
external IP address with the following procedure. It can be deployed using the to obtain the external IP address. You can use either
[**Ingress** application](#installing-applications). [Ingress](#installing-applications), or Knative's own load balancer
([Istio](https://istio.io)) if using [Knative](#installing-applications).
NOTE: **Note:**
Knative will include its own load balancer in the form of [Istio](https://istio.io).
At this time, to determine the external IP address, you will need to follow the manual approach.
In order to publish your web application, you first need to find the external IP In order to publish your web application, you first need to find the external IP
address associated to your load balancer. address associated to your load balancer.
...@@ -254,7 +251,7 @@ address associated to your load balancer. ...@@ -254,7 +251,7 @@ address associated to your load balancer.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17052) in GitLab 10.6. > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17052) in GitLab 10.6.
If you installed the Ingress [via the **Applications**](#installing-applications), If you [installed Ingress or Knative](#installing-applications),
you should see the Ingress IP address on this same page within a few minutes. you should see the Ingress IP address on this same page within a few minutes.
If you don't see this, GitLab might not be able to determine the IP address of If you don't see this, GitLab might not be able to determine the IP address of
your ingress application in which case you should manually determine it. your ingress application in which case you should manually determine it.
......
# Merge conflict resolution # Merge request conflict resolution
> [Introduced][ce-5479] in GitLab 8.11. Merge conflicts occur when two branches have different changes that cannot be
merged automatically.
When a merge request has conflicts, GitLab may provide the option to resolve Git is able to automatically merge changes between branches in most cases, but
those conflicts in the GitLab UI. (See there are situations where Git will require your assistance to resolve the
[conflicts available for resolution](#conflicts-available-for-resolution) for conflicts manually. Typically, this is necessary when people change the same
more information on when this is available.) If this is an option, you will see parts of the same files.
a **resolve these conflicts** link in the merge request widget:
GitLab will prevent merge requests from being merged until all conflicts are
resolved. Conflicts can be resolved locally, or in many cases within GitLab
(see [conflicts available for resolution](#conflicts-available-for-resolution)
for information on when this is available).
![Merge request widget](img/merge_request_widget.png) ![Merge request widget](img/merge_request_widget.png)
NOTE: **Note:**
GitLab resolves conflicts by creating a merge commit in the source branch that
is not automatically merged into the target branch. This allows the merge
commit to be reviewed and tested before the changes are merged, preventing
unintended changes entering the target branch without review or breaking the
build.
## Resolve conflicts: interactive mode
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5479) in GitLab 8.11.
Clicking this will show a list of files with conflicts, with conflict sections Clicking this will show a list of files with conflicts, with conflict sections
highlighted: highlighted:
...@@ -21,9 +37,9 @@ request into the source branch, resolving the conflicts using the options ...@@ -21,9 +37,9 @@ request into the source branch, resolving the conflicts using the options
chosen. If the source branch is `feature` and the target branch is `master`, chosen. If the source branch is `feature` and the target branch is `master`,
this is similar to performing `git checkout feature; git merge master` locally. this is similar to performing `git checkout feature; git merge master` locally.
## Merge conflict editor ## Resolve conflicts: inline editor
> Introduced in GitLab 8.13. > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6374) in GitLab 8.13.
The merge conflict resolution editor allows for more complex merge conflicts, The merge conflict resolution editor allows for more complex merge conflicts,
which require the user to manually modify a file in order to resolve a conflict, which require the user to manually modify a file in order to resolve a conflict,
...@@ -50,5 +66,3 @@ Additionally, GitLab does not detect conflicts in renames away from a path. For ...@@ -50,5 +66,3 @@ Additionally, GitLab does not detect conflicts in renames away from a path. For
example, this will not create a conflict: on branch `a`, doing `git mv file1 example, this will not create a conflict: on branch `a`, doing `git mv file1
file2`; on branch `b`, doing `git mv file1 file3`. Instead, both files will be file2`; on branch `b`, doing `git mv file1 file3`. Instead, both files will be
present in the branch after the merge request is merged. present in the branch after the merge request is merged.
[ce-5479]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5479
...@@ -64,6 +64,8 @@ Below is the table of events users can be notified of: ...@@ -64,6 +64,8 @@ Below is the table of events users can be notified of:
|------------------------------|-------------------------------------------------------------------|------------------------------| |------------------------------|-------------------------------------------------------------------|------------------------------|
| New SSH key added | User | Security email, always sent. | | New SSH key added | User | Security email, always sent. |
| New email added | User | Security email, always sent. | | New email added | User | Security email, always sent. |
| Email changed | User | Security email, always sent. |
| Password changed | User | Security email, always sent. |
| New user created | User | Sent on user creation, except for omniauth (LDAP)| | New user created | User | Sent on user creation, except for omniauth (LDAP)|
| User added to project | User | Sent when user is added to project | | User added to project | User | Sent when user is added to project |
| Project access level changed | User | Sent when user project access level is changed | | Project access level changed | User | Sent when user project access level is changed |
......
...@@ -95,6 +95,7 @@ module API ...@@ -95,6 +95,7 @@ module API
Gitlab::Auth::TokenNotFoundError, Gitlab::Auth::TokenNotFoundError,
Gitlab::Auth::ExpiredError, Gitlab::Auth::ExpiredError,
Gitlab::Auth::RevokedError, Gitlab::Auth::RevokedError,
Gitlab::Auth::ImpersonationDisabled,
Gitlab::Auth::InsufficientScopeError] Gitlab::Auth::InsufficientScopeError]
base.__send__(:rescue_from, *error_classes, oauth2_bearer_token_error_handler) # rubocop:disable GitlabSecurity/PublicSend base.__send__(:rescue_from, *error_classes, oauth2_bearer_token_error_handler) # rubocop:disable GitlabSecurity/PublicSend
...@@ -122,6 +123,11 @@ module API ...@@ -122,6 +123,11 @@ module API
:invalid_token, :invalid_token,
"Token was revoked. You have to re-authorize from the user.") "Token was revoked. You have to re-authorize from the user.")
when Gitlab::Auth::ImpersonationDisabled
Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
:invalid_token,
"Token is an impersonation token but impersonation was disabled.")
when Gitlab::Auth::InsufficientScopeError when Gitlab::Auth::InsufficientScopeError
# FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2) # FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2)
# does not include WWW-Authenticate header, which breaks the standard. # does not include WWW-Authenticate header, which breaks the standard.
......
...@@ -17,6 +17,9 @@ module Banzai ...@@ -17,6 +17,9 @@ module Banzai
# This is a small extension to the CommonMark spec. If they start allowing # This is a small extension to the CommonMark spec. If they start allowing
# spaces in urls, we could then remove this filter. # spaces in urls, we could then remove this filter.
# #
# Note: Filter::SanitizationFilter should always be run sometime after this filter
# to prevent XSS attacks
#
class SpacedLinkFilter < HTML::Pipeline::Filter class SpacedLinkFilter < HTML::Pipeline::Filter
include ActionView::Helpers::TagHelper include ActionView::Helpers::TagHelper
......
...@@ -14,13 +14,16 @@ module Banzai ...@@ -14,13 +14,16 @@ module Banzai
def self.filters def self.filters
@filters ||= FilterArray[ @filters ||= FilterArray[
Filter::PlantumlFilter, Filter::PlantumlFilter,
# Must always be before the SanitizationFilter to prevent XSS attacks
Filter::SpacedLinkFilter,
Filter::SanitizationFilter, Filter::SanitizationFilter,
Filter::SyntaxHighlightFilter, Filter::SyntaxHighlightFilter,
Filter::MathFilter, Filter::MathFilter,
Filter::ColorFilter, Filter::ColorFilter,
Filter::MermaidFilter, Filter::MermaidFilter,
Filter::SpacedLinkFilter,
Filter::VideoLinkFilter, Filter::VideoLinkFilter,
Filter::ImageLazyLoadFilter, Filter::ImageLazyLoadFilter,
Filter::ImageLinkFilter, Filter::ImageLinkFilter,
......
...@@ -7,6 +7,7 @@ module Gitlab ...@@ -7,6 +7,7 @@ module Gitlab
TokenNotFoundError = Class.new(AuthenticationError) TokenNotFoundError = Class.new(AuthenticationError)
ExpiredError = Class.new(AuthenticationError) ExpiredError = Class.new(AuthenticationError)
RevokedError = Class.new(AuthenticationError) RevokedError = Class.new(AuthenticationError)
ImpersonationDisabled = Class.new(AuthenticationError)
UnauthorizedError = Class.new(AuthenticationError) UnauthorizedError = Class.new(AuthenticationError)
class InsufficientScopeError < AuthenticationError class InsufficientScopeError < AuthenticationError
...@@ -69,6 +70,8 @@ module Gitlab ...@@ -69,6 +70,8 @@ module Gitlab
raise ExpiredError raise ExpiredError
when AccessTokenValidationService::REVOKED when AccessTokenValidationService::REVOKED
raise RevokedError raise RevokedError
when AccessTokenValidationService::IMPERSONATION_DISABLED
raise ImpersonationDisabled
end end
end end
......
...@@ -385,7 +385,8 @@ module Gitlab ...@@ -385,7 +385,8 @@ module Gitlab
file_path: encode_binary(action[:file_path]), file_path: encode_binary(action[:file_path]),
previous_path: encode_binary(action[:previous_path]), previous_path: encode_binary(action[:previous_path]),
base64_content: action[:encoding] == 'base64', base64_content: action[:encoding] == 'base64',
execute_filemode: !!action[:execute_filemode] execute_filemode: !!action[:execute_filemode],
infer_content: !!action[:infer_content]
) )
rescue RangeError rescue RangeError
raise ArgumentError, "Unknown action '#{action[:action]}'" raise ArgumentError, "Unknown action '#{action[:action]}'"
......
# frozen_string_literal: true # frozen_string_literal: true
require 'resolv' require 'resolv'
require 'ipaddress'
module Gitlab module Gitlab
class UrlBlocker class UrlBlocker
...@@ -10,11 +11,8 @@ module Gitlab ...@@ -10,11 +11,8 @@ module Gitlab
def validate!(url, allow_localhost: false, allow_local_network: true, enforce_user: false, ports: [], protocols: []) def validate!(url, allow_localhost: false, allow_local_network: true, enforce_user: false, ports: [], protocols: [])
return true if url.nil? return true if url.nil?
begin # Param url can be a string, URI or Addressable::URI
uri = Addressable::URI.parse(url) uri = parse_url(url)
rescue Addressable::URI::InvalidURIError
raise BlockedUrlError, "URI is invalid"
end
# Allow imports from the GitLab instance itself but only from the configured ports # Allow imports from the GitLab instance itself but only from the configured ports
return true if internal?(uri) return true if internal?(uri)
...@@ -26,7 +24,9 @@ module Gitlab ...@@ -26,7 +24,9 @@ module Gitlab
validate_hostname!(uri.hostname) validate_hostname!(uri.hostname)
begin begin
addrs_info = Addrinfo.getaddrinfo(uri.hostname, port, nil, :STREAM) addrs_info = Addrinfo.getaddrinfo(uri.hostname, port, nil, :STREAM).map do |addr|
addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr
end
rescue SocketError rescue SocketError
return true return true
end end
...@@ -49,6 +49,18 @@ module Gitlab ...@@ -49,6 +49,18 @@ module Gitlab
private private
def parse_url(url)
raise Addressable::URI::InvalidURIError if multiline?(url)
Addressable::URI.parse(url)
rescue Addressable::URI::InvalidURIError, URI::InvalidURIError
raise BlockedUrlError, 'URI is invalid'
end
def multiline?(url)
CGI.unescape(url.to_s) =~ /\n|\r/
end
def validate_port!(port, ports) def validate_port!(port, ports)
return if port.blank? return if port.blank?
# Only ports under 1024 are restricted # Only ports under 1024 are restricted
...@@ -73,13 +85,14 @@ module Gitlab ...@@ -73,13 +85,14 @@ module Gitlab
def validate_hostname!(value) def validate_hostname!(value)
return if value.blank? return if value.blank?
return if IPAddress.valid?(value)
return if value =~ /\A\p{Alnum}/ return if value =~ /\A\p{Alnum}/
raise BlockedUrlError, "Hostname needs to start with an alphanumeric character" raise BlockedUrlError, "Hostname or IP address invalid"
end end
def validate_localhost!(addrs_info) def validate_localhost!(addrs_info)
local_ips = ["127.0.0.1", "::1", "0.0.0.0"] local_ips = ["::", "0.0.0.0"]
local_ips.concat(Socket.ip_address_list.map(&:ip_address)) local_ips.concat(Socket.ip_address_list.map(&:ip_address))
return if (local_ips & addrs_info.map(&:ip_address)).empty? return if (local_ips & addrs_info.map(&:ip_address)).empty?
...@@ -94,7 +107,7 @@ module Gitlab ...@@ -94,7 +107,7 @@ module Gitlab
end end
def validate_local_network!(addrs_info) def validate_local_network!(addrs_info)
return unless addrs_info.any? { |addr| addr.ipv4_private? || addr.ipv6_sitelocal? } return unless addrs_info.any? { |addr| addr.ipv4_private? || addr.ipv6_sitelocal? || addr.ipv6_unique_local? }
raise BlockedUrlError, "Requests to the local network are not allowed" raise BlockedUrlError, "Requests to the local network are not allowed"
end end
...@@ -111,12 +124,14 @@ module Gitlab ...@@ -111,12 +124,14 @@ module Gitlab
end end
def internal_web?(uri) def internal_web?(uri)
uri.hostname == config.gitlab.host && uri.scheme == config.gitlab.protocol &&
uri.hostname == config.gitlab.host &&
(uri.port.blank? || uri.port == config.gitlab.port) (uri.port.blank? || uri.port == config.gitlab.port)
end end
def internal_shell?(uri) def internal_shell?(uri)
uri.hostname == config.gitlab_shell.ssh_host && uri.scheme == 'ssh' &&
uri.hostname == config.gitlab_shell.ssh_host &&
(uri.port.blank? || uri.port == config.gitlab_shell.ssh_port) (uri.port.blank? || uri.port == config.gitlab_shell.ssh_port)
end end
......
...@@ -188,6 +188,9 @@ msgstr "" ...@@ -188,6 +188,9 @@ msgstr ""
msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc." msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
msgstr "" msgstr ""
msgid "%{user_name} profile page"
msgstr ""
msgid "+ %{count} more" msgid "+ %{count} more"
msgstr "" msgstr ""
...@@ -1363,6 +1366,9 @@ msgstr "" ...@@ -1363,6 +1366,9 @@ msgstr ""
msgid "Business metrics (Custom)" msgid "Business metrics (Custom)"
msgstr "" msgstr ""
msgid "By %{user_name}"
msgstr ""
msgid "ByAuthor|by" msgid "ByAuthor|by"
msgstr "" msgstr ""
...@@ -3016,6 +3022,9 @@ msgstr "" ...@@ -3016,6 +3022,9 @@ msgstr ""
msgid "Edit application" msgid "Edit application"
msgstr "" msgstr ""
msgid "Edit comment"
msgstr ""
msgid "Edit environment" msgid "Edit environment"
msgstr "" msgstr ""
...@@ -4470,6 +4479,9 @@ msgstr "" ...@@ -4470,6 +4479,9 @@ msgstr ""
msgid "ImageDiffViewer|Swipe" msgid "ImageDiffViewer|Swipe"
msgstr "" msgstr ""
msgid "Impersonation has been disabled"
msgstr ""
msgid "Import" msgid "Import"
msgstr "" msgstr ""
...@@ -4592,6 +4604,9 @@ msgstr "" ...@@ -4592,6 +4604,9 @@ msgstr ""
msgid "Interested parties can even contribute by pushing commits if they want to." msgid "Interested parties can even contribute by pushing commits if they want to."
msgstr "" msgstr ""
msgid "Internal"
msgstr ""
msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgid "Internal - The group and any internal projects can be viewed by any logged in user."
msgstr "" msgstr ""
...@@ -5738,6 +5753,9 @@ msgstr "" ...@@ -5738,6 +5753,9 @@ msgstr ""
msgid "Notes|Show history only" msgid "Notes|Show history only"
msgstr "" msgstr ""
msgid "Nothing here."
msgstr ""
msgid "Notification events" msgid "Notification events"
msgstr "" msgstr ""
...@@ -6256,6 +6274,9 @@ msgstr "" ...@@ -6256,6 +6274,9 @@ msgstr ""
msgid "Prioritized label" msgid "Prioritized label"
msgstr "" msgstr ""
msgid "Private"
msgstr ""
msgid "Private - Project access must be granted explicitly to each user." msgid "Private - Project access must be granted explicitly to each user."
msgstr "" msgstr ""
...@@ -6823,6 +6844,9 @@ msgstr "" ...@@ -6823,6 +6844,9 @@ msgstr ""
msgid "Pseudonymizer data collection" msgid "Pseudonymizer data collection"
msgstr "" msgstr ""
msgid "Public"
msgstr ""
msgid "Public - The group and any public projects can be viewed without any authentication." msgid "Public - The group and any public projects can be viewed without any authentication."
msgstr "" msgstr ""
......
...@@ -8,7 +8,7 @@ module RuboCop ...@@ -8,7 +8,7 @@ module RuboCop
class AddReference < RuboCop::Cop::Cop class AddReference < RuboCop::Cop::Cop
include MigrationHelpers include MigrationHelpers
MSG = '`add_reference` requires `index: true`' MSG = '`add_reference` requires `index: true` or `index: { options... }`'
def on_send(node) def on_send(node)
return unless in_migration?(node) return unless in_migration?(node)
...@@ -33,7 +33,12 @@ module RuboCop ...@@ -33,7 +33,12 @@ module RuboCop
private private
def index_enabled?(pair) def index_enabled?(pair)
hash_key_type(pair) == :sym && hash_key_name(pair) == :index && pair.children[1].true_type? return unless hash_key_type(pair) == :sym
return unless hash_key_name(pair) == :index
index = pair.children[1]
index.true_type? || index.hash_type?
end end
def hash_key_type(pair) def hash_key_type(pair)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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