summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorGrzegorz Bizon <grzesiek.bizon@gmail.com>2017-07-31 10:50:10 +0200
committerGrzegorz Bizon <grzesiek.bizon@gmail.com>2017-07-31 10:50:10 +0200
commit8f1274ae0350590a8a0d8f16558d24a9514b78c1 (patch)
tree514a6628a0a45dac10608b408cf3f011c893431b /lib
parent79a7f7b6e59fa1225c440547796331caedabeaab (diff)
parent9a3b283402b8cc1c86802c526f19a459ce09c2e3 (diff)
downloadgitlab-ce-8f1274ae0350590a8a0d8f16558d24a9514b78c1.tar.gz
Merge commit '9a3b283402b8cc1c86802c526f19a459ce09c2e3' into backstage/gb/migrate-stages-statuses
* commit '9a3b283402b8cc1c86802c526f19a459ce09c2e3': (270 commits) Add a note about EFS and GitLab log files Projects logo are not centered vertically on projects page Fix spec/features/projects/branches_spec Fixup POST /v3/:id/hooks and PUT /v3/:id/hooks/:hook_id Fix a spec that was assuming to be on the wrong page Add copy about search terms to ux guide Update documentation of user creation by replacing the 'confirm' param with 'skip_confirmation' Fix replying to commit comments on MRs from forks Fix 500 error when rendering avatar for deleted project creator Load and process at most 100 commits when pushing into default branch Ensure Gitlab::Application.routes.default_url_options are set correctly in Capybara + :js specs Add log messages to clarify log messages about API CSRF token verification failure Update gitlab_flow.md, Teatro seems to be completely dead, see also https://forum.gitlab.com/t/gitlab-flow-documentation-teatro/7774 Fix diff commenting results just after changing view Update CHANGELOG.md for 9.4.2 none is not a CSS Value for sizes ;-) Merge issuable "reopened" state into "opened" Make access level more compatible with EE Add link to JIRA article in docs Expand pipeline_trigger_service_spec by godfat request ...
Diffstat (limited to 'lib')
-rw-r--r--lib/api/api.rb3
-rw-r--r--lib/api/branches.rb18
-rw-r--r--lib/api/entities.rb33
-rw-r--r--lib/api/helpers.rb10
-rw-r--r--lib/api/helpers/related_resources_helpers.rb2
-rw-r--r--lib/api/issues.rb8
-rw-r--r--lib/api/merge_requests.rb97
-rw-r--r--lib/api/settings.rb58
-rw-r--r--lib/api/triggers.rb23
-rw-r--r--lib/api/v3/project_hooks.rb8
-rw-r--r--lib/backup/manager.rb42
-rw-r--r--lib/gitlab/data_builder/push.rb4
-rw-r--r--lib/gitlab/devise_failure.rb23
-rw-r--r--lib/gitlab/git.rb2
-rw-r--r--lib/gitlab/git/commit.rb11
-rw-r--r--lib/gitlab/git/repository.rb214
-rw-r--r--lib/gitlab/gitaly_client/ref_service.rb38
-rw-r--r--lib/gitlab/gpg.rb62
-rw-r--r--lib/gitlab/gpg/commit.rb85
-rw-r--r--lib/gitlab/gpg/invalid_gpg_signature_updater.rb19
-rw-r--r--lib/gitlab/health_checks/base_abstract_check.rb6
-rw-r--r--lib/gitlab/health_checks/fs_shards_check.rb95
-rw-r--r--lib/gitlab/health_checks/simple_abstract_check.rb15
-rw-r--r--lib/gitlab/ldap/adapter.rb2
-rw-r--r--lib/gitlab/ldap/config.rb62
-rw-r--r--lib/gitlab/request_forgery_protection.rb39
-rw-r--r--lib/mattermost/client.rb10
-rw-r--r--lib/mattermost/team.rb7
-rw-r--r--lib/omni_auth/request_forgery_protection.rb21
-rw-r--r--lib/tasks/gitlab/assets.rake1
-rw-r--r--lib/tasks/gitlab/gitaly.rake2
31 files changed, 576 insertions, 444 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 3bdafa3edc1..045a0db1842 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -86,6 +86,9 @@ module API
helpers ::API::Helpers
helpers ::API::Helpers::CommonHelpers
+ NO_SLASH_URL_PART_REGEX = %r{[^/]+}
+ PROJECT_ENDPOINT_REQUIREMENTS = { id: NO_SLASH_URL_PART_REGEX }.freeze
+
# Keep in alphabetical order
mount ::API::AccessRequests
mount ::API::AwardEmoji
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 3d816f8771d..9dd60d1833b 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -4,19 +4,21 @@ module API
class Branches < Grape::API
include PaginationParams
+ BRANCH_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX)
+
before { authorize! :download_code, user_project }
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a project repository branches' do
success Entities::RepoBranch
end
params do
use :pagination
end
- get ":id/repository/branches" do
+ get ':id/repository/branches' do
branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name))
present paginate(branches), with: Entities::RepoBranch, project: user_project
@@ -28,7 +30,7 @@ module API
params do
requires :branch, type: String, desc: 'The name of the branch'
end
- get ':id/repository/branches/:branch', requirements: { branch: /.+/ } do
+ get ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
branch = user_project.repository.find_branch(params[:branch])
not_found!("Branch") unless branch
@@ -46,7 +48,7 @@ module API
optional :developers_can_push, type: Boolean, desc: 'Flag if developers can push to that branch'
optional :developers_can_merge, type: Boolean, desc: 'Flag if developers can merge to that branch'
end
- put ':id/repository/branches/:branch/protect', requirements: { branch: /.+/ } do
+ put ':id/repository/branches/:branch/protect', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
authorize_admin_project
branch = user_project.repository.find_branch(params[:branch])
@@ -81,7 +83,7 @@ module API
params do
requires :branch, type: String, desc: 'The name of the branch'
end
- put ':id/repository/branches/:branch/unprotect', requirements: { branch: /.+/ } do
+ put ':id/repository/branches/:branch/unprotect', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
authorize_admin_project
branch = user_project.repository.find_branch(params[:branch])
@@ -99,7 +101,7 @@ module API
requires :branch, type: String, desc: 'The name of the branch'
requires :ref, type: String, desc: 'Create branch from commit sha or existing branch'
end
- post ":id/repository/branches" do
+ post ':id/repository/branches' do
authorize_push_project
result = CreateBranchService.new(user_project, current_user)
@@ -118,7 +120,7 @@ module API
params do
requires :branch, type: String, desc: 'The name of the branch'
end
- delete ":id/repository/branches/:branch", requirements: { branch: /.+/ } do
+ delete ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
authorize_push_project
result = DeleteBranchService.new(user_project, current_user)
@@ -130,7 +132,7 @@ module API
end
desc 'Delete all merged branches'
- delete ":id/repository/merged_branches" do
+ delete ':id/repository/merged_branches' do
DeleteMergedBranchesService.new(user_project, current_user).async_execute
accepted!
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 5cdc441e8cb..ce25be34ec4 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -671,43 +671,14 @@ module API
class ApplicationSetting < Grape::Entity
expose :id
- expose :default_projects_limit
- expose :signup_enabled
- expose :password_authentication_enabled
- expose :password_authentication_enabled, as: :signin_enabled
- expose :gravatar_enabled
- expose :sign_in_text
- expose :after_sign_up_text
- expose :created_at
- expose :updated_at
- expose :home_page_url
- expose :default_branch_protection
+ expose(*::ApplicationSettingsHelper.visible_attributes)
expose(:restricted_visibility_levels) do |setting, _options|
setting.restricted_visibility_levels.map { |level| Gitlab::VisibilityLevel.string_level(level) }
end
- expose :max_attachment_size
- expose :session_expire_delay
expose(:default_project_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_project_visibility) }
expose(:default_snippet_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_snippet_visibility) }
expose(:default_group_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_group_visibility) }
- expose :default_artifacts_expire_in
- expose :domain_whitelist
- expose :domain_blacklist_enabled
- expose :domain_blacklist
- expose :user_oauth_applications
- expose :after_sign_out_path
- expose :container_registry_token_expire_delay
- expose :repository_storage
- expose :repository_storages
- expose :koding_enabled
- expose :koding_url
- expose :plantuml_enabled
- expose :plantuml_url
- expose :terminal_max_session_time
- expose :polling_interval_multiplier
- expose :help_page_hide_commercial_content
- expose :help_page_text
- expose :help_page_support_url
+ expose :password_authentication_enabled, as: :signin_enabled
end
class Release < Grape::Entity
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 57e3e93500f..234825480f2 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -336,12 +336,14 @@ module API
env['warden']
end
+ # Check if the request is GET/HEAD, or if CSRF token is valid.
+ def verified_request?
+ Gitlab::RequestForgeryProtection.verified?(env)
+ end
+
# Check the Rails session for valid authentication details
- #
- # Until CSRF protection is added to the API, disallow this method for
- # state-changing endpoints
def find_user_from_warden
- warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD'])
+ warden.try(:authenticate) if verified_request?
end
def initial_current_user
diff --git a/lib/api/helpers/related_resources_helpers.rb b/lib/api/helpers/related_resources_helpers.rb
index 769cc1457fc..1f677529b07 100644
--- a/lib/api/helpers/related_resources_helpers.rb
+++ b/lib/api/helpers/related_resources_helpers.rb
@@ -12,7 +12,7 @@ module API
end
def expose_url(path)
- url_options = Rails.application.routes.default_url_options
+ url_options = Gitlab::Application.routes.default_url_options
protocol, host, port = url_options.slice(:protocol, :host, :port).values
URI::HTTP.build(scheme: protocol, host: host, port: port, path: path).to_s
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 93ebe18508d..4cec1145f3a 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -29,6 +29,10 @@ module API
optional :search, type: String, desc: 'Search issues for text present in the title or description'
optional :created_after, type: DateTime, desc: 'Return issues created after the specified time'
optional :created_before, type: DateTime, desc: 'Return issues created before the specified time'
+ optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID'
+ optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID'
+ optional :scope, type: String, values: %w[created-by-me assigned-to-me all],
+ desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`'
use :pagination
end
@@ -55,9 +59,11 @@ module API
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
use :issues_params
+ optional :scope, type: String, values: %w[created-by-me assigned-to-me all], default: 'created-by-me',
+ desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`'
end
get do
- issues = find_issues(scope: 'authored')
+ issues = find_issues
present paginate(issues), with: Entities::IssueBasic, current_user: current_user
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index f64ac659413..8810d4e441d 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -4,14 +4,77 @@ module API
before { authenticate! }
+ helpers ::Gitlab::IssuableMetadata
+
+ helpers do
+ def find_merge_requests(args = {})
+ args = params.merge(args)
+
+ args[:milestone_title] = args.delete(:milestone)
+ args[:label_name] = args.delete(:labels)
+
+ merge_requests = MergeRequestsFinder.new(current_user, args).execute
+ .reorder(args[:order_by] => args[:sort])
+ merge_requests = paginate(merge_requests)
+ .preload(:target_project)
+
+ return merge_requests if args[:view] == 'simple'
+
+ merge_requests
+ .preload(:notes, :author, :assignee, :milestone, :merge_request_diff, :labels)
+ end
+
+ params :merge_requests_params do
+ optional :state, type: String, values: %w[opened closed merged all], default: 'all',
+ desc: 'Return opened, closed, merged, or all merge requests'
+ optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
+ desc: 'Return merge requests ordered by `created_at` or `updated_at` fields.'
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Return merge requests sorted in `asc` or `desc` order.'
+ optional :milestone, type: String, desc: 'Return merge requests for a specific milestone'
+ optional :labels, type: String, desc: 'Comma-separated list of label names'
+ optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time'
+ optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time'
+ optional :view, type: String, values: %w[simple], desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request'
+ optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID'
+ optional :assignee_id, type: Integer, desc: 'Return merge requests which are assigned to the user with the given ID'
+ optional :scope, type: String, values: %w[created-by-me assigned-to-me all],
+ desc: 'Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`'
+ use :pagination
+ end
+ end
+
+ resource :merge_requests do
+ desc 'List merge requests' do
+ success Entities::MergeRequestBasic
+ end
+ params do
+ use :merge_requests_params
+ optional :scope, type: String, values: %w[created-by-me assigned-to-me all], default: 'created-by-me',
+ desc: 'Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`'
+ end
+ get do
+ merge_requests = find_merge_requests
+
+ options = { with: Entities::MergeRequestBasic,
+ current_user: current_user }
+
+ if params[:view] == 'simple'
+ options[:with] = Entities::MergeRequestSimple
+ else
+ options[:issuable_metadata] = issuable_meta_data(merge_requests, 'MergeRequest')
+ end
+
+ present merge_requests, options
+ end
+ end
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: { id: %r{[^/]+} } do
include TimeTrackingEndpoints
- helpers ::Gitlab::IssuableMetadata
-
helpers do
def handle_merge_request_errors!(errors)
if errors[:project_access].any?
@@ -29,23 +92,6 @@ module API
render_api_error!(errors, 400)
end
- def find_merge_requests(args = {})
- args = params.merge(args)
-
- args[:milestone_title] = args.delete(:milestone)
- args[:label_name] = args.delete(:labels)
-
- merge_requests = MergeRequestsFinder.new(current_user, args).execute
- .reorder(args[:order_by] => args[:sort])
- merge_requests = paginate(merge_requests)
- .preload(:target_project)
-
- return merge_requests if args[:view] == 'simple'
-
- merge_requests
- .preload(:notes, :author, :assignee, :milestone, :merge_request_diff, :labels)
- end
-
params :optional_params_ce do
optional :description, type: String, desc: 'The description of the merge request'
optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request'
@@ -63,19 +109,8 @@ module API
success Entities::MergeRequestBasic
end
params do
- optional :state, type: String, values: %w[opened closed merged all], default: 'all',
- desc: 'Return opened, closed, merged, or all merge requests'
- optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
- desc: 'Return merge requests ordered by `created_at` or `updated_at` fields.'
- optional :sort, type: String, values: %w[asc desc], default: 'desc',
- desc: 'Return merge requests sorted in `asc` or `desc` order.'
+ use :merge_requests_params
optional :iids, type: Array[Integer], desc: 'The IID array of merge requests'
- optional :milestone, type: String, desc: 'Return merge requests for a specific milestone'
- optional :labels, type: String, desc: 'Comma-separated list of label names'
- optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time'
- optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time'
- optional :view, type: String, values: %w[simple], desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request'
- use :pagination
end
get ":id/merge_requests" do
authorize! :read_merge_request, user_project
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index b19095d1252..d55a61fa638 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -20,59 +20,6 @@ module API
success Entities::ApplicationSetting
end
params do
- # CE
- at_least_one_of_ce = [
- :admin_notification_email,
- :after_sign_out_path,
- :after_sign_up_text,
- :akismet_enabled,
- :container_registry_token_expire_delay,
- :default_artifacts_expire_in,
- :default_branch_protection,
- :default_group_visibility,
- :default_project_visibility,
- :default_projects_limit,
- :default_snippet_visibility,
- :disabled_oauth_sign_in_sources,
- :domain_blacklist_enabled,
- :domain_whitelist,
- :email_author_in_body,
- :enabled_git_access_protocol,
- :gravatar_enabled,
- :help_page_hide_commercial_content,
- :help_page_text,
- :help_page_support_url,
- :home_page_url,
- :housekeeping_enabled,
- :html_emails_enabled,
- :import_sources,
- :koding_enabled,
- :max_artifacts_size,
- :max_attachment_size,
- :max_pages_size,
- :metrics_enabled,
- :plantuml_enabled,
- :polling_interval_multiplier,
- :recaptcha_enabled,
- :repository_checks_enabled,
- :repository_storage,
- :require_two_factor_authentication,
- :restricted_visibility_levels,
- :send_user_confirmation_email,
- :sentry_enabled,
- :clientside_sentry_enabled,
- :session_expire_delay,
- :shared_runners_enabled,
- :sidekiq_throttling_enabled,
- :sign_in_text,
- :password_authentication_enabled,
- :signin_enabled,
- :signup_enabled,
- :terminal_max_session_time,
- :user_default_external,
- :user_oauth_applications,
- :version_check_enabled
- ]
optional :default_branch_protection, type: Integer, values: [0, 1, 2], desc: 'Determine if developers can push to master'
optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility'
optional :default_snippet_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default snippet visibility'
@@ -151,7 +98,7 @@ module API
given clientside_sentry_enabled: ->(val) { val } do
requires :clientside_sentry_dsn, type: String, desc: 'Clientside Sentry Data Source Name'
end
- optional :repository_storage, type: String, desc: 'Storage paths for new projects'
+ optional :repository_storages, type: Array[String], desc: 'Storage paths for new projects'
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 :koding_enabled, type: Boolean, desc: 'Enable Koding'
given koding_enabled: ->(val) { val } do
@@ -174,7 +121,8 @@ 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 :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.'
- at_least_one_of(*at_least_one_of_ce)
+ optional(*::ApplicationSettingsHelper.visible_attributes)
+ at_least_one_of(*::ApplicationSettingsHelper.visible_attributes)
end
put "application/settings" do
attrs = declared_params(include_missing: false)
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 9375e7eb768..edfdb63d183 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -15,25 +15,22 @@ module API
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do
- project = find_project(params[:id])
- trigger = Ci::Trigger.find_by_token(params[:token].to_s)
- not_found! unless project && trigger
- unauthorized! unless trigger.project == project
-
# validate variables
- variables = params[:variables].to_h
- unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
+ params[:variables] = params[:variables].to_h
+ unless params[:variables].all? { |key, value| key.is_a?(String) && value.is_a?(String) }
render_api_error!('variables needs to be a map of key-valued strings', 400)
end
- # create request and trigger builds
- result = Ci::CreateTriggerRequestService.execute(project, trigger, params[:ref].to_s, variables)
- pipeline = result.pipeline
+ project = find_project(params[:id])
+ not_found! unless project
+
+ result = Ci::PipelineTriggerService.new(project, nil, params).execute
+ not_found! unless result
- if pipeline.persisted?
- present pipeline, with: Entities::Pipeline
+ if result[:http_status]
+ render_api_error!(result[:message], result[:http_status])
else
- render_validation_error!(pipeline)
+ present result[:pipeline], with: Entities::Pipeline
end
end
diff --git a/lib/api/v3/project_hooks.rb b/lib/api/v3/project_hooks.rb
index 94614bfc8b6..51014591a93 100644
--- a/lib/api/v3/project_hooks.rb
+++ b/lib/api/v3/project_hooks.rb
@@ -56,7 +56,9 @@ module API
use :project_hook_properties
end
post ":id/hooks" do
- hook = user_project.hooks.new(declared_params(include_missing: false))
+ attrs = declared_params(include_missing: false)
+ attrs[:job_events] = attrs.delete(:build_events) if attrs.key?(:build_events)
+ hook = user_project.hooks.new(attrs)
if hook.save
present hook, with: ::API::V3::Entities::ProjectHook
@@ -77,7 +79,9 @@ module API
put ":id/hooks/:hook_id" do
hook = user_project.hooks.find(params.delete(:hook_id))
- if hook.update_attributes(declared_params(include_missing: false))
+ attrs = declared_params(include_missing: false)
+ attrs[:job_events] = attrs.delete(:build_events) if attrs.key?(:build_events)
+ if hook.update_attributes(attrs)
present hook, with: ::API::V3::Entities::ProjectHook
else
error!("Invalid url given", 422) if hook.errors[:url].present?
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index f755c99ea4a..ca6d6848d41 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -8,18 +8,9 @@ module Backup
# Make sure there is a connection
ActiveRecord::Base.connection.reconnect!
- # saving additional informations
- s = {}
- s[:db_version] = "#{ActiveRecord::Migrator.current_version}"
- s[:backup_created_at] = Time.now
- s[:gitlab_version] = Gitlab::VERSION
- s[:tar_version] = tar_version
- s[:skipped] = ENV["SKIP"]
- tar_file = "#{s[:backup_created_at].strftime('%s_%Y_%m_%d_')}#{s[:gitlab_version]}#{FILE_NAME_SUFFIX}"
-
Dir.chdir(backup_path) do
File.open("#{backup_path}/backup_information.yml", "w+") do |file|
- file << s.to_yaml.gsub(/^---\n/, '')
+ file << backup_information.to_yaml.gsub(/^---\n/, '')
end
# create archive
@@ -33,11 +24,11 @@ module Backup
abort 'Backup failed'
end
- upload(tar_file)
+ upload
end
end
- def upload(tar_file)
+ def upload
$progress.print "Uploading backup archive to remote storage #{remote_directory} ... "
connection_settings = Gitlab.config.backup.upload.connection
@@ -48,7 +39,7 @@ module Backup
directory = connect_to_remote_directory(connection_settings)
- if directory.files.create(key: tar_file, body: File.open(tar_file), public: false,
+ if directory.files.create(key: remote_target, body: File.open(tar_file), public: false,
multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size,
encryption: Gitlab.config.backup.upload.encryption,
storage_class: Gitlab.config.backup.upload.storage_class)
@@ -177,7 +168,8 @@ module Backup
end
def connect_to_remote_directory(connection_settings)
- connection = ::Fog::Storage.new(connection_settings)
+ # our settings use string keys, but Fog expects symbols
+ connection = ::Fog::Storage.new(connection_settings.symbolize_keys)
# We only attempt to create the directory for local backups. For AWS
# and other cloud providers, we cannot guarantee the user will have
@@ -193,6 +185,14 @@ module Backup
Gitlab.config.backup.upload.remote_directory
end
+ def remote_target
+ if ENV['DIRECTORY']
+ File.join(ENV['DIRECTORY'], tar_file)
+ else
+ tar_file
+ end
+ end
+
def backup_contents
folders_to_backup + archives_to_backup + ["backup_information.yml"]
end
@@ -214,5 +214,19 @@ module Backup
def settings
@settings ||= YAML.load_file("backup_information.yml")
end
+
+ def tar_file
+ @tar_file ||= "#{backup_information[:backup_created_at].strftime('%s_%Y_%m_%d_')}#{backup_information[:gitlab_version]}#{FILE_NAME_SUFFIX}"
+ end
+
+ def backup_information
+ @backup_information ||= {
+ db_version: ActiveRecord::Migrator.current_version.to_s,
+ backup_created_at: Time.now,
+ gitlab_version: Gitlab::VERSION,
+ tar_version: tar_version,
+ skipped: ENV["SKIP"]
+ }
+ end
end
end
diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb
index 8c8729b6557..5c5f507d44d 100644
--- a/lib/gitlab/data_builder/push.rb
+++ b/lib/gitlab/data_builder/push.rb
@@ -24,11 +24,11 @@ module Gitlab
# total_commits_count: Fixnum
# }
#
- def build(project, user, oldrev, newrev, ref, commits = [], message = nil)
+ def build(project, user, oldrev, newrev, ref, commits = [], message = nil, commits_count: nil)
commits = Array(commits)
# Total commits count
- commits_count = commits.size
+ commits_count ||= commits.size
# Get latest 20 commits ASC
commits_limited = commits.last(20)
diff --git a/lib/gitlab/devise_failure.rb b/lib/gitlab/devise_failure.rb
deleted file mode 100644
index a78fde9d782..00000000000
--- a/lib/gitlab/devise_failure.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-module Gitlab
- class DeviseFailure < Devise::FailureApp
- protected
-
- # Override `Devise::FailureApp#request_format` to handle a special case
- #
- # This tells Devise to handle an unauthenticated `.zip` request as an HTML
- # request (i.e., redirect to sign in).
- #
- # Otherwise, Devise would respond with a 401 Unauthorized with
- # `Content-Type: application/zip` and a response body in plaintext, and the
- # browser would freak out.
- #
- # See https://gitlab.com/gitlab-org/gitlab-ce/issues/12944
- def request_format
- if request.format == :zip
- Mime::Type.lookup_by_extension(:html).ref
- else
- super
- end
- end
- end
-end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 4175746be39..b6449f27034 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -10,7 +10,7 @@ module Gitlab
include Gitlab::EncodingHelper
def ref_name(ref)
- encode! ref.sub(/\Arefs\/(tags|heads)\//, '')
+ encode! ref.sub(/\Arefs\/(tags|heads|remotes)\//, '')
end
def branch_name(ref)
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 09511cc6504..ca7e3a7c4be 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -319,6 +319,15 @@ module Gitlab
end
end
+ # Get the gpg signature of this commit.
+ #
+ # Ex.
+ # commit.signature(repo)
+ #
+ def signature(repo)
+ Rugged::Commit.extract_signature(repo.rugged, sha)
+ end
+
def stats
Gitlab::Git::CommitStats.new(self)
end
@@ -327,7 +336,7 @@ module Gitlab
begin
raw_commit.to_mbox(options)
rescue Rugged::InvalidError => ex
- if ex.message =~ /Commit \w+ is a merge commit/
+ if ex.message =~ /commit \w+ is a merge commit/i
'Patch format is not currently supported for merge commits.'
end
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 3e27fd7b682..a3bc79109f8 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -82,10 +82,14 @@ module Gitlab
end
# Returns an Array of Branches
- #
- # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/389
- def branches(sort_by: nil)
- branches_filter(sort_by: sort_by)
+ def branches
+ gitaly_migrate(:branches) do |is_enabled|
+ if is_enabled
+ gitaly_ref_client.branches
+ else
+ branches_filter
+ end
+ end
end
def reload_rugged
@@ -164,20 +168,13 @@ module Gitlab
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/390
def tags
- rugged.references.each("refs/tags/*").map do |ref|
- message = nil
-
- if ref.target.is_a?(Rugged::Tag::Annotation)
- tag_message = ref.target.message
-
- if tag_message.respond_to?(:chomp)
- message = tag_message.chomp
- end
+ gitaly_migrate(:tags) do |is_enabled|
+ if is_enabled
+ tags_from_gitaly
+ else
+ tags_from_rugged
end
-
- target_commit = Gitlab::Git::Commit.find(self, ref.target)
- Gitlab::Git::Tag.new(self, ref.name, ref.target, target_commit, message)
- end.sort_by(&:name)
+ end
end
# Returns true if the given tag exists
@@ -478,20 +475,6 @@ module Gitlab
end
end
- # Sets HEAD to the commit specified by +ref+; +ref+ can be a branch or
- # tag name or a commit SHA. Valid +reset_type+ values are:
- #
- # [:soft]
- # the head will be moved to the commit.
- # [:mixed]
- # will trigger a +:soft+ reset, plus the index will be replaced
- # with the content of the commit tree.
- # [:hard]
- # will trigger a +:mixed+ reset and the working directory will be
- # replaced with the content of the index. (Untracked and ignored files
- # will be left alone)
- delegate :reset, to: :rugged
-
# Mimic the `git clean` command and recursively delete untracked files.
# Valid keys that can be passed in the +options+ hash are:
#
@@ -516,154 +499,6 @@ module Gitlab
# TODO: implement this method
end
- # Check out the specified ref. Valid options are:
- #
- # :b - Create a new branch at +start_point+ and set HEAD to the new
- # branch.
- #
- # * These options are passed to the Rugged::Repository#checkout method:
- #
- # :progress ::
- # A callback that will be executed for checkout progress notifications.
- # Up to 3 parameters are passed on each execution:
- #
- # - The path to the last updated file (or +nil+ on the very first
- # invocation).
- # - The number of completed checkout steps.
- # - The number of total checkout steps to be performed.
- #
- # :notify ::
- # A callback that will be executed for each checkout notification
- # types specified with +:notify_flags+. Up to 5 parameters are passed
- # on each execution:
- #
- # - An array containing the +:notify_flags+ that caused the callback
- # execution.
- # - The path of the current file.
- # - A hash describing the baseline blob (or +nil+ if it does not
- # exist).
- # - A hash describing the target blob (or +nil+ if it does not exist).
- # - A hash describing the workdir blob (or +nil+ if it does not
- # exist).
- #
- # :strategy ::
- # A single symbol or an array of symbols representing the strategies
- # to use when performing the checkout. Possible values are:
- #
- # :none ::
- # Perform a dry run (default).
- #
- # :safe ::
- # Allow safe updates that cannot overwrite uncommitted data.
- #
- # :safe_create ::
- # Allow safe updates plus creation of missing files.
- #
- # :force ::
- # Allow all updates to force working directory to look like index.
- #
- # :allow_conflicts ::
- # Allow checkout to make safe updates even if conflicts are found.
- #
- # :remove_untracked ::
- # Remove untracked files not in index (that are not ignored).
- #
- # :remove_ignored ::
- # Remove ignored files not in index.
- #
- # :update_only ::
- # Only update existing files, don't create new ones.
- #
- # :dont_update_index ::
- # Normally checkout updates index entries as it goes; this stops
- # that.
- #
- # :no_refresh ::
- # Don't refresh index/config/etc before doing checkout.
- #
- # :disable_pathspec_match ::
- # Treat pathspec as simple list of exact match file paths.
- #
- # :skip_locked_directories ::
- # Ignore directories in use, they will be left empty.
- #
- # :skip_unmerged ::
- # Allow checkout to skip unmerged files (NOT IMPLEMENTED).
- #
- # :use_ours ::
- # For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED).
- #
- # :use_theirs ::
- # For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED).
- #
- # :update_submodules ::
- # Recursively checkout submodules with same options (NOT
- # IMPLEMENTED).
- #
- # :update_submodules_if_changed ::
- # Recursively checkout submodules if HEAD moved in super repo (NOT
- # IMPLEMENTED).
- #
- # :disable_filters ::
- # If +true+, filters like CRLF line conversion will be disabled.
- #
- # :dir_mode ::
- # Mode for newly created directories. Default: +0755+.
- #
- # :file_mode ::
- # Mode for newly created files. Default: +0755+ or +0644+.
- #
- # :file_open_flags ::
- # Mode for opening files. Default:
- # <code>IO::CREAT | IO::TRUNC | IO::WRONLY</code>.
- #
- # :notify_flags ::
- # A single symbol or an array of symbols representing the cases in
- # which the +:notify+ callback should be invoked. Possible values are:
- #
- # :none ::
- # Do not invoke the +:notify+ callback (default).
- #
- # :conflict ::
- # Invoke the callback for conflicting paths.
- #
- # :dirty ::
- # Invoke the callback for "dirty" files, i.e. those that do not need
- # an update but no longer match the baseline.
- #
- # :updated ::
- # Invoke the callback for any file that was changed.
- #
- # :untracked ::
- # Invoke the callback for untracked files.
- #
- # :ignored ::
- # Invoke the callback for ignored files.
- #
- # :all ::
- # Invoke the callback for all these cases.
- #
- # :paths ::
- # A glob string or an array of glob strings specifying which paths
- # should be taken into account for the checkout operation. +nil+ will
- # match all files. Default: +nil+.
- #
- # :baseline ::
- # A Rugged::Tree that represents the current, expected contents of the
- # workdir. Default: +HEAD+.
- #
- # :target_directory ::
- # A path to an alternative workdir directory in which the checkout
- # should be performed.
- def checkout(ref, options = {}, start_point = "HEAD")
- if options[:b]
- rugged.branches.create(ref, start_point)
- options.delete(:b)
- end
- default_options = { strategy: [:recreate_missing, :safe] }
- rugged.checkout(ref, default_options.merge(options))
- end
-
# Delete the specified branch from the repository
def delete_branch(branch_name)
rugged.branches.delete(branch_name)
@@ -1115,6 +950,27 @@ module Gitlab
end
end
+ def tags_from_rugged
+ rugged.references.each("refs/tags/*").map do |ref|
+ message = nil
+
+ if ref.target.is_a?(Rugged::Tag::Annotation)
+ tag_message = ref.target.message
+
+ if tag_message.respond_to?(:chomp)
+ message = tag_message.chomp
+ end
+ end
+
+ target_commit = Gitlab::Git::Commit.find(self, ref.target)
+ Gitlab::Git::Tag.new(self, ref.name, ref.target, target_commit, message)
+ end.sort_by(&:name)
+ end
+
+ def tags_from_gitaly
+ gitaly_ref_client.tags
+ end
+
def gitaly_migrate(method, &block)
Gitlab::GitalyClient.migrate(method, &block)
rescue GRPC::NotFound => e
diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb
index 2c3d53410ac..b0f7548b7dc 100644
--- a/lib/gitlab/gitaly_client/ref_service.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -10,6 +10,19 @@ module Gitlab
@storage = repository.storage
end
+ def branches
+ request = Gitaly::FindAllBranchesRequest.new(repository: @gitaly_repo)
+ response = GitalyClient.call(@storage, :ref_service, :find_all_branches, request)
+
+ response.flat_map do |message|
+ message.branches.map do |branch|
+ gitaly_commit = GitalyClient::Commit.new(@repository, branch.target)
+ target_commit = Gitlab::Git::Commit.decorate(gitaly_commit)
+ Gitlab::Git::Branch.new(@repository, branch.name, branch.target.id, target_commit)
+ end
+ end
+ end
+
def default_branch_name
request = Gitaly::FindDefaultBranchNameRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :ref_service, :find_default_branch_name, request)
@@ -52,6 +65,12 @@ module Gitlab
consume_branches_response(response)
end
+ def tags
+ request = Gitaly::FindAllTagsRequest.new(repository: @gitaly_repo)
+ response = GitalyClient.call(@storage, :ref_service, :find_all_tags, request)
+ consume_tags_response(response)
+ end
+
private
def consume_refs_response(response)
@@ -79,6 +98,25 @@ module Gitlab
end
end
+ def consume_tags_response(response)
+ response.flat_map do |message|
+ message.tags.map do |gitaly_tag|
+ if gitaly_tag.target_commit.present?
+ commit = GitalyClient::Commit.new(@repository, gitaly_tag.target_commit)
+ gitaly_commit = Gitlab::Git::Commit.new(commit)
+ end
+
+ Gitlab::Git::Tag.new(
+ @repository,
+ encode!(gitaly_tag.name.dup),
+ gitaly_tag.id,
+ gitaly_commit,
+ encode!(gitaly_tag.message.chomp)
+ )
+ end
+ end
+ end
+
def commit_from_local_branches_response(response)
# Git messages have no encoding enforcements. However, in the UI we only
# handle UTF-8, so basically we cross our fingers that the message force
diff --git a/lib/gitlab/gpg.rb b/lib/gitlab/gpg.rb
new file mode 100644
index 00000000000..e1d1724295a
--- /dev/null
+++ b/lib/gitlab/gpg.rb
@@ -0,0 +1,62 @@
+module Gitlab
+ module Gpg
+ extend self
+
+ module CurrentKeyChain
+ extend self
+
+ def add(key)
+ GPGME::Key.import(key)
+ end
+
+ def fingerprints_from_key(key)
+ import = GPGME::Key.import(key)
+
+ return [] if import.imported == 0
+
+ import.imports.map(&:fingerprint)
+ end
+ end
+
+ def fingerprints_from_key(key)
+ using_tmp_keychain do
+ CurrentKeyChain.fingerprints_from_key(key)
+ end
+ end
+
+ def primary_keyids_from_key(key)
+ using_tmp_keychain do
+ fingerprints = CurrentKeyChain.fingerprints_from_key(key)
+
+ GPGME::Key.find(:public, fingerprints).map { |raw_key| raw_key.primary_subkey.keyid }
+ end
+ end
+
+ def user_infos_from_key(key)
+ using_tmp_keychain do
+ fingerprints = CurrentKeyChain.fingerprints_from_key(key)
+
+ GPGME::Key.find(:public, fingerprints).flat_map do |raw_key|
+ raw_key.uids.map { |uid| { name: uid.name, email: uid.email } }
+ end
+ end
+ end
+
+ def using_tmp_keychain
+ Dir.mktmpdir do |dir|
+ @original_dirs ||= [GPGME::Engine.dirinfo('homedir')]
+ @original_dirs.push(dir)
+
+ GPGME::Engine.home_dir = dir
+
+ return_value = yield
+
+ @original_dirs.pop
+
+ GPGME::Engine.home_dir = @original_dirs[-1]
+
+ return_value
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gpg/commit.rb b/lib/gitlab/gpg/commit.rb
new file mode 100644
index 00000000000..55428b85207
--- /dev/null
+++ b/lib/gitlab/gpg/commit.rb
@@ -0,0 +1,85 @@
+module Gitlab
+ module Gpg
+ class Commit
+ attr_reader :commit
+
+ def initialize(commit)
+ @commit = commit
+
+ @signature_text, @signed_text = commit.raw.signature(commit.project.repository)
+ end
+
+ def has_signature?
+ !!(@signature_text && @signed_text)
+ end
+
+ def signature
+ return unless has_signature?
+
+ cached_signature = GpgSignature.find_by(commit_sha: commit.sha)
+ return cached_signature if cached_signature.present?
+
+ using_keychain do |gpg_key|
+ create_cached_signature!(gpg_key)
+ end
+ end
+
+ def update_signature!(cached_signature)
+ using_keychain do |gpg_key|
+ cached_signature.update_attributes!(attributes(gpg_key))
+ end
+ end
+
+ private
+
+ def using_keychain
+ Gitlab::Gpg.using_tmp_keychain do
+ # first we need to get the keyid from the signature to query the gpg
+ # key belonging to the keyid.
+ # This way we can add the key to the temporary keychain and extract
+ # the proper signature.
+ gpg_key = GpgKey.find_by(primary_keyid: verified_signature.fingerprint)
+
+ if gpg_key
+ Gitlab::Gpg::CurrentKeyChain.add(gpg_key.key)
+ @verified_signature = nil
+ end
+
+ yield gpg_key
+ end
+ end
+
+ def verified_signature
+ @verified_signature ||= GPGME::Crypto.new.verify(@signature_text, signed_text: @signed_text) do |verified_signature|
+ break verified_signature
+ end
+ end
+
+ def create_cached_signature!(gpg_key)
+ GpgSignature.create!(attributes(gpg_key))
+ end
+
+ def attributes(gpg_key)
+ user_infos = user_infos(gpg_key)
+
+ {
+ commit_sha: commit.sha,
+ project: commit.project,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: gpg_key&.primary_keyid || verified_signature.fingerprint,
+ gpg_key_user_name: user_infos[:name],
+ gpg_key_user_email: user_infos[:email],
+ valid_signature: gpg_signature_valid_signature_value(gpg_key)
+ }
+ end
+
+ def gpg_signature_valid_signature_value(gpg_key)
+ !!(gpg_key && gpg_key.verified? && verified_signature.valid?)
+ end
+
+ def user_infos(gpg_key)
+ gpg_key&.verified_user_infos&.first || gpg_key&.user_infos&.first || {}
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gpg/invalid_gpg_signature_updater.rb b/lib/gitlab/gpg/invalid_gpg_signature_updater.rb
new file mode 100644
index 00000000000..3bb491120ba
--- /dev/null
+++ b/lib/gitlab/gpg/invalid_gpg_signature_updater.rb
@@ -0,0 +1,19 @@
+module Gitlab
+ module Gpg
+ class InvalidGpgSignatureUpdater
+ def initialize(gpg_key)
+ @gpg_key = gpg_key
+ end
+
+ def run
+ GpgSignature
+ .select(:id, :commit_sha, :project_id)
+ .where('gpg_key_id IS NULL OR valid_signature = ?', false)
+ .where(gpg_key_primary_keyid: @gpg_key.primary_keyid)
+ .find_each do |gpg_signature|
+ Gitlab::Gpg::Commit.new(gpg_signature.commit).update_signature!(gpg_signature)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/health_checks/base_abstract_check.rb b/lib/gitlab/health_checks/base_abstract_check.rb
index 7de6d4d9367..8b365dab185 100644
--- a/lib/gitlab/health_checks/base_abstract_check.rb
+++ b/lib/gitlab/health_checks/base_abstract_check.rb
@@ -27,10 +27,10 @@ module Gitlab
Metric.new(name, value, labels)
end
- def with_timing(proc)
+ def with_timing
start = Time.now
- result = proc.call
- yield result, Time.now.to_f - start.to_f
+ result = yield
+ [result, Time.now.to_f - start.to_f]
end
def catch_timeout(seconds, &block)
diff --git a/lib/gitlab/health_checks/fs_shards_check.rb b/lib/gitlab/health_checks/fs_shards_check.rb
index bebde857b16..9e91c135956 100644
--- a/lib/gitlab/health_checks/fs_shards_check.rb
+++ b/lib/gitlab/health_checks/fs_shards_check.rb
@@ -10,47 +10,45 @@ module Gitlab
def readiness
repository_storages.map do |storage_name|
begin
- tmp_file_path = tmp_file_path(storage_name)
-
if !storage_stat_test(storage_name)
HealthChecks::Result.new(false, 'cannot stat storage', shard: storage_name)
- elsif !storage_write_test(tmp_file_path)
- HealthChecks::Result.new(false, 'cannot write to storage', shard: storage_name)
- elsif !storage_read_test(tmp_file_path)
- HealthChecks::Result.new(false, 'cannot read from storage', shard: storage_name)
else
- HealthChecks::Result.new(true, nil, shard: storage_name)
+ with_temp_file(storage_name) do |tmp_file_path|
+ if !storage_write_test(tmp_file_path)
+ HealthChecks::Result.new(false, 'cannot write to storage', shard: storage_name)
+ elsif !storage_read_test(tmp_file_path)
+ HealthChecks::Result.new(false, 'cannot read from storage', shard: storage_name)
+ else
+ HealthChecks::Result.new(true, nil, shard: storage_name)
+ end
+ end
end
rescue RuntimeError => ex
message = "unexpected error #{ex} when checking storage #{storage_name}"
Rails.logger.error(message)
HealthChecks::Result.new(false, message, shard: storage_name)
- ensure
- delete_test_file(tmp_file_path)
end
end
end
def metrics
repository_storages.flat_map do |storage_name|
- tmp_file_path = tmp_file_path(storage_name)
[
- operation_metrics(:filesystem_accessible, :filesystem_access_latency_seconds, -> { storage_stat_test(storage_name) }, shard: storage_name),
- operation_metrics(:filesystem_writable, :filesystem_write_latency_seconds, -> { storage_write_test(tmp_file_path) }, shard: storage_name),
- operation_metrics(:filesystem_readable, :filesystem_read_latency_seconds, -> { storage_read_test(tmp_file_path) }, shard: storage_name)
+ storage_stat_metrics(storage_name),
+ storage_write_metrics(storage_name),
+ storage_read_metrics(storage_name)
].flatten
end
end
private
- def operation_metrics(ok_metric, latency_metric, operation, **labels)
- with_timing operation do |result, elapsed|
- [
- metric(latency_metric, elapsed, **labels),
- metric(ok_metric, result ? 1 : 0, **labels)
- ]
- end
+ def operation_metrics(ok_metric, latency_metric, **labels)
+ result, elapsed = yield
+ [
+ metric(latency_metric, elapsed, **labels),
+ metric(ok_metric, result ? 1 : 0, **labels)
+ ]
rescue RuntimeError => ex
Rails.logger.error("unexpected error #{ex} when checking #{ok_metric}")
[metric(ok_metric, 0, **labels)]
@@ -68,19 +66,36 @@ module Gitlab
Gitlab::Popen.popen([TIMEOUT_EXECUTABLE, COMMAND_TIMEOUT].concat(cmd_args), *args, &block)
end
- def tmp_file_path(storage_name)
- Dir::Tmpname.create(%w(fs_shards_check +deleted), path(storage_name)) { |path| path }
+ def with_temp_file(storage_name)
+ temp_file_path = Dir::Tmpname.create(%w(fs_shards_check +deleted), storage_path(storage_name)) { |path| path }
+ yield temp_file_path
+ ensure
+ delete_test_file(temp_file_path)
end
- def path(storage_name)
+ def storage_path(storage_name)
storages_paths&.dig(storage_name, 'path')
end
+ # All below test methods use shell commands to perform actions on storage volumes.
+ # In case a storage volume have connectivity problems causing pure Ruby IO operation to wait indefinitely,
+ # we can rely on shell commands to be terminated once `timeout` kills them.
+ #
+ # However we also fallback to pure Ruby file operations in case a specific shell command is missing
+ # so we are still able to perform healthchecks and gather metrics from such system.
+
+ def delete_test_file(tmp_path)
+ _, status = exec_with_timeout(%W{ rm -f #{tmp_path} })
+ status.zero?
+ rescue Errno::ENOENT
+ File.delete(tmp_path) rescue Errno::ENOENT
+ end
+
def storage_stat_test(storage_name)
- stat_path = File.join(path(storage_name), '.')
+ stat_path = File.join(storage_path(storage_name), '.')
begin
_, status = exec_with_timeout(%W{ stat #{stat_path} })
- status == 0
+ status.zero?
rescue Errno::ENOENT
File.exist?(stat_path) && File::Stat.new(stat_path).readable?
end
@@ -90,7 +105,7 @@ module Gitlab
_, status = exec_with_timeout(%W{ tee #{tmp_path} }) do |stdin|
stdin.write(RANDOM_STRING)
end
- status == 0
+ status.zero?
rescue Errno::ENOENT
written_bytes = File.write(tmp_path, RANDOM_STRING) rescue Errno::ENOENT
written_bytes == RANDOM_STRING.length
@@ -100,17 +115,33 @@ module Gitlab
_, status = exec_with_timeout(%W{ diff #{tmp_path} - }) do |stdin|
stdin.write(RANDOM_STRING)
end
- status == 0
+ status.zero?
rescue Errno::ENOENT
file_contents = File.read(tmp_path) rescue Errno::ENOENT
file_contents == RANDOM_STRING
end
- def delete_test_file(tmp_path)
- _, status = exec_with_timeout(%W{ rm -f #{tmp_path} })
- status == 0
- rescue Errno::ENOENT
- File.delete(tmp_path) rescue Errno::ENOENT
+ def storage_stat_metrics(storage_name)
+ operation_metrics(:filesystem_accessible, :filesystem_access_latency_seconds, shard: storage_name) do
+ with_timing { storage_stat_test(storage_name) }
+ end
+ end
+
+ def storage_write_metrics(storage_name)
+ operation_metrics(:filesystem_writable, :filesystem_write_latency_seconds, shard: storage_name) do
+ with_temp_file(storage_name) do |tmp_file_path|
+ with_timing { storage_write_test(tmp_file_path) }
+ end
+ end
+ end
+
+ def storage_read_metrics(storage_name)
+ operation_metrics(:filesystem_readable, :filesystem_read_latency_seconds, shard: storage_name) do
+ with_temp_file(storage_name) do |tmp_file_path|
+ storage_write_test(tmp_file_path) # writes data used by read test
+ with_timing { storage_read_test(tmp_file_path) }
+ end
+ end
end
end
end
diff --git a/lib/gitlab/health_checks/simple_abstract_check.rb b/lib/gitlab/health_checks/simple_abstract_check.rb
index 3dcb28a193c..f5026171ba4 100644
--- a/lib/gitlab/health_checks/simple_abstract_check.rb
+++ b/lib/gitlab/health_checks/simple_abstract_check.rb
@@ -15,14 +15,13 @@ module Gitlab
end
def metrics
- with_timing method(:check) do |result, elapsed|
- Rails.logger.error("#{human_name} check returned unexpected result #{result}") unless is_successful?(result)
- [
- metric("#{metric_prefix}_timeout", result.is_a?(Timeout::Error) ? 1 : 0),
- metric("#{metric_prefix}_success", is_successful?(result) ? 1 : 0),
- metric("#{metric_prefix}_latency_seconds", elapsed)
- ]
- end
+ result, elapsed = with_timing(&method(:check))
+ Rails.logger.error("#{human_name} check returned unexpected result #{result}") unless is_successful?(result)
+ [
+ metric("#{metric_prefix}_timeout", result.is_a?(Timeout::Error) ? 1 : 0),
+ metric("#{metric_prefix}_success", is_successful?(result) ? 1 : 0),
+ metric("#{metric_prefix}_latency_seconds", elapsed)
+ ]
end
private
diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb
index 7b05290e5cc..8867a91c244 100644
--- a/lib/gitlab/ldap/adapter.rb
+++ b/lib/gitlab/ldap/adapter.rb
@@ -101,7 +101,7 @@ module Gitlab
end
def user_attributes
- %W(#{config.uid} cn mail dn)
+ %W(#{config.uid} cn dn) + config.attributes['username'] + config.attributes['email']
end
end
end
diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb
index 6fdf68641e2..c8f19cd52d5 100644
--- a/lib/gitlab/ldap/config.rb
+++ b/lib/gitlab/ldap/config.rb
@@ -2,6 +2,12 @@
module Gitlab
module LDAP
class Config
+ NET_LDAP_ENCRYPTION_METHOD = {
+ simple_tls: :simple_tls,
+ start_tls: :start_tls,
+ plain: nil
+ }.freeze
+
attr_accessor :provider, :options
def self.enabled?
@@ -12,6 +18,12 @@ module Gitlab
Gitlab.config.ldap.servers.values
end
+ def self.available_servers
+ return [] unless enabled?
+
+ Array.wrap(servers.first)
+ end
+
def self.providers
servers.map { |server| server['provider_name'] }
end
@@ -39,7 +51,7 @@ module Gitlab
def adapter_options
opts = base_options.merge(
- encryption: encryption
+ encryption: encryption_options
)
opts.merge!(auth_options) if has_auth?
@@ -50,9 +62,10 @@ module Gitlab
def omniauth_options
opts = base_options.merge(
base: base,
- method: options['method'],
+ encryption: options['encryption'],
filter: omniauth_user_filter,
- name_proc: name_proc
+ name_proc: name_proc,
+ disable_verify_certificates: !options['verify_certificates']
)
if has_auth?
@@ -62,6 +75,9 @@ module Gitlab
)
end
+ opts[:ca_file] = options['ca_file'] if options['ca_file'].present?
+ opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present?
+
opts
end
@@ -157,15 +173,37 @@ module Gitlab
base_config.servers.values.find { |server| server['provider_name'] == provider }
end
- def encryption
- case options['method'].to_s
- when 'ssl'
- :simple_tls
- when 'tls'
- :start_tls
- else
- nil
- end
+ def encryption_options
+ method = translate_method(options['encryption'])
+ return nil unless method
+
+ {
+ method: method,
+ tls_options: tls_options(method)
+ }
+ end
+
+ def translate_method(method_from_config)
+ NET_LDAP_ENCRYPTION_METHOD[method_from_config.to_sym]
+ end
+
+ def tls_options(method)
+ return { verify_mode: OpenSSL::SSL::VERIFY_NONE } unless method
+
+ opts = if options['verify_certificates']
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
+ else
+ # It is important to explicitly set verify_mode for two reasons:
+ # 1. The behavior of OpenSSL is undefined when verify_mode is not set.
+ # 2. The net-ldap gem implementation verifies the certificate hostname
+ # unless verify_mode is set to VERIFY_NONE.
+ { verify_mode: OpenSSL::SSL::VERIFY_NONE }
+ end
+
+ opts[:ca_file] = options['ca_file'] if options['ca_file'].present?
+ opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present?
+
+ opts
end
def auth_options
diff --git a/lib/gitlab/request_forgery_protection.rb b/lib/gitlab/request_forgery_protection.rb
new file mode 100644
index 00000000000..ccfe0d6bed3
--- /dev/null
+++ b/lib/gitlab/request_forgery_protection.rb
@@ -0,0 +1,39 @@
+# A module to check CSRF tokens in requests.
+# It's used in API helpers and OmniAuth.
+# Usage: GitLab::RequestForgeryProtection.call(env)
+
+module Gitlab
+ module RequestForgeryProtection
+ class Controller < ActionController::Base
+ protect_from_forgery with: :exception
+
+ rescue_from ActionController::InvalidAuthenticityToken do |e|
+ logger.warn "This CSRF token verification failure is handled internally by `GitLab::RequestForgeryProtection`"
+ logger.warn "Unlike the logs may suggest, this does not result in an actual 422 response to the user"
+ logger.warn "For API requests, the only effect is that `current_user` will be `nil` for the duration of the request"
+
+ raise e
+ end
+
+ def index
+ head :ok
+ end
+ end
+
+ def self.app
+ @app ||= Controller.action(:index)
+ end
+
+ def self.call(env)
+ app.call(env)
+ end
+
+ def self.verified?(env)
+ call(env)
+
+ true
+ rescue ActionController::InvalidAuthenticityToken
+ false
+ end
+ end
+end
diff --git a/lib/mattermost/client.rb b/lib/mattermost/client.rb
index 3d60618006c..d80cd7d2a4e 100644
--- a/lib/mattermost/client.rb
+++ b/lib/mattermost/client.rb
@@ -24,6 +24,10 @@ module Mattermost
json_response session.post(path, options)
end
+ def delete(session, path, options)
+ json_response session.delete(path, options)
+ end
+
def session_get(path, options = {})
with_session do |session|
get(session, path, options)
@@ -36,6 +40,12 @@ module Mattermost
end
end
+ def session_delete(path, options = {})
+ with_session do |session|
+ delete(session, path, options)
+ end
+ end
+
def json_response(response)
json_response = JSON.parse(response.body)
diff --git a/lib/mattermost/team.rb b/lib/mattermost/team.rb
index 2cdbbdece16..b2511f3af1d 100644
--- a/lib/mattermost/team.rb
+++ b/lib/mattermost/team.rb
@@ -14,5 +14,12 @@ module Mattermost
type: type
}.to_json)
end
+
+ # The deletion is done async, so the response is fast.
+ # On the mattermost side, this triggers an soft deletion first, after which
+ # the actuall data is removed
+ def destroy(team_id:)
+ session_delete("/api/v4/teams/#{team_id}?permanent=true")
+ end
end
end
diff --git a/lib/omni_auth/request_forgery_protection.rb b/lib/omni_auth/request_forgery_protection.rb
deleted file mode 100644
index 69155131d8d..00000000000
--- a/lib/omni_auth/request_forgery_protection.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# Protects OmniAuth request phase against CSRF.
-
-module OmniAuth
- module RequestForgeryProtection
- class Controller < ActionController::Base
- protect_from_forgery with: :exception
-
- def index
- head :ok
- end
- end
-
- def self.app
- @app ||= Controller.action(:index)
- end
-
- def self.call(env)
- app.call(env)
- end
- end
-end
diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake
index 003d57adbbd..259a755d724 100644
--- a/lib/tasks/gitlab/assets.rake
+++ b/lib/tasks/gitlab/assets.rake
@@ -4,6 +4,7 @@ namespace :gitlab do
task compile: [
'yarn:check',
'rake:assets:precompile',
+ 'gettext:po_to_json',
'webpack:compile',
'fix_urls'
]
diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake
index a8db5701d0b..9df07ea8d83 100644
--- a/lib/tasks/gitlab/gitaly.rake
+++ b/lib/tasks/gitlab/gitaly.rake
@@ -19,7 +19,7 @@ namespace :gitlab do
Dir.chdir(args.dir) do
create_gitaly_configuration
- run_command!([command])
+ Bundler.with_original_env { run_command!([command]) }
end
end