summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMarcia Ramos <virtua.creative@gmail.com>2019-04-10 17:05:46 +0100
committerMarcia Ramos <virtua.creative@gmail.com>2019-04-10 17:05:46 +0100
commitcbd6841cac8185f181a5dcec33704f6e7c040732 (patch)
tree423bbc4fb873ab51590d0be4ae594769c80b739b /lib
parent3402f8c817e9798eed9d86555f3f85fd10f49abf (diff)
parent490b31f740d23b54a62588cd9fd0e0cf7fdd9370 (diff)
downloadgitlab-ce-docs-pages-intro.tar.gz
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into docs-pages-introdocs-pages-intro
Diffstat (limited to 'lib')
-rw-r--r--lib/api/entities.rb18
-rw-r--r--lib/api/environments.rb15
-rw-r--r--lib/api/helpers/internal_helpers.rb22
-rw-r--r--lib/api/helpers/projects_helpers.rb5
-rw-r--r--lib/api/internal.rb24
-rw-r--r--lib/api/issues.rb10
-rw-r--r--lib/api/merge_requests.rb4
-rw-r--r--lib/api/pipelines.rb13
-rw-r--r--lib/api/settings.rb4
-rw-r--r--lib/banzai/filter/relative_link_filter.rb2
-rw-r--r--lib/banzai/reference_parser/merge_request_parser.rb2
-rw-r--r--lib/gitlab/background_migration.rb28
-rw-r--r--lib/gitlab/background_migration/migrate_stage_index.rb4
-rw-r--r--lib/gitlab/ci/config/entry/environment.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/skip.rb4
-rw-r--r--lib/gitlab/data_builder/push.rb9
-rw-r--r--lib/gitlab/external_authorization.rb40
-rw-r--r--lib/gitlab/external_authorization/access.rb55
-rw-r--r--lib/gitlab/external_authorization/cache.rb62
-rw-r--r--lib/gitlab/external_authorization/client.rb63
-rw-r--r--lib/gitlab/external_authorization/config.rb47
-rw-r--r--lib/gitlab/external_authorization/logger.rb21
-rw-r--r--lib/gitlab/external_authorization/response.rb38
-rw-r--r--lib/gitlab/git_post_receive.rb2
-rw-r--r--lib/gitlab/hook_data/issuable_builder.rb6
-rw-r--r--lib/gitlab/hook_data/merge_request_builder.rb5
-rw-r--r--lib/gitlab/import/merge_request_helpers.rb2
-rw-r--r--lib/gitlab/kubernetes/namespace.rb29
-rw-r--r--lib/gitlab/legacy_github_import/project_creator.rb2
-rw-r--r--lib/gitlab/legacy_github_import/release_formatter.rb1
-rw-r--r--lib/gitlab/push_options.rb70
31 files changed, 562 insertions, 47 deletions
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 2dd3120d3fc..4bdac278add 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -277,6 +277,7 @@ module API
expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) {
options[:statistics] && Ability.allowed?(options[:current_user], :read_statistics, project)
}
+ expose :external_authorization_classification_label
# rubocop: disable CodeReuse/ActiveRecord
def self.preload_relation(projects_relation, options = {})
@@ -663,7 +664,11 @@ module API
expose(:user_notes_count) { |merge_request, options| issuable_metadata(merge_request, options, :user_notes_count) }
expose(:upvotes) { |merge_request, options| issuable_metadata(merge_request, options, :upvotes) }
expose(:downvotes) { |merge_request, options| issuable_metadata(merge_request, options, :downvotes) }
- expose :author, :assignee, using: Entities::UserBasic
+ expose :assignee, using: ::API::Entities::UserBasic do |merge_request|
+ merge_request.assignee
+ end
+ expose :author, :assignees, using: Entities::UserBasic
+
expose :source_project_id, :target_project_id
expose :labels do |merge_request|
# Avoids an N+1 query since labels are preloaded
@@ -1116,6 +1121,8 @@ module API
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(*::ApplicationSettingsHelper.external_authorization_service_attributes)
+
# support legacy names, can be removed in v5
expose :password_authentication_enabled_for_web, as: :password_authentication_enabled
expose :password_authentication_enabled_for_web, as: :signin_enabled
@@ -1294,10 +1301,6 @@ module API
expose :id, :name, :slug, :external_url
end
- class Environment < EnvironmentBasic
- expose :project, using: Entities::BasicProjectDetails
- end
-
class Deployment < Grape::Entity
expose :id, :iid, :ref, :sha, :created_at
expose :user, using: Entities::UserBasic
@@ -1305,6 +1308,11 @@ module API
expose :deployable, using: Entities::Job
end
+ class Environment < EnvironmentBasic
+ expose :project, using: Entities::BasicProjectDetails
+ expose :last_deployment, using: Entities::Deployment, if: { last_deployment: true }
+ end
+
class LicenseBasic < Grape::Entity
expose :key, :name, :nickname
expose :url, as: :html_url
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index 5b0f3b914cb..6cd43923559 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -101,6 +101,21 @@ module API
status 200
present environment, with: Entities::Environment, current_user: current_user
end
+
+ desc 'Get a single environment' do
+ success Entities::Environment
+ end
+ params do
+ requires :environment_id, type: Integer, desc: 'The environment ID'
+ end
+ get ':id/environments/:environment_id' do
+ authorize! :read_environment, user_project
+
+ environment = user_project.environments.find(params[:environment_id])
+ present environment, with: Entities::Environment, current_user: current_user,
+ except: [:project, { last_deployment: [:environment] }],
+ last_deployment: true
+ end
end
end
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 3fd824877ae..71c30ec99a5 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -43,6 +43,28 @@ module API
::MergeRequests::GetUrlsService.new(project).execute(params[:changes])
end
+ def process_mr_push_options(push_options, project, user, changes)
+ output = {}
+
+ service = ::MergeRequests::PushOptionsHandlerService.new(
+ project,
+ user,
+ changes,
+ push_options
+ ).execute
+
+ if service.errors.present?
+ output[:warnings] = push_options_warning(service.errors.join("\n\n"))
+ end
+
+ output
+ end
+
+ def push_options_warning(warning)
+ options = Array.wrap(params[:push_options]).map { |p| "'#{p}'" }.join(' ')
+ "Error encountered with push options #{options}: #{warning}"
+ end
+
def redis_ping
result = Gitlab::Redis::SharedState.with { |redis| redis.ping }
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 7b858dc2e72..aaf32dafca4 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -29,13 +29,13 @@ module API
optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line'
optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests'
optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md"
+ optional :external_authorization_classification_label, type: String, desc: 'The classification label for the project'
end
if Gitlab.ee?
params :optional_project_params_ee do
optional :repository_storage, type: String, desc: 'Which storage shard the repository is on. Available only to admins'
optional :approvals_before_merge, type: Integer, desc: 'How many approvers should approve merge request by default'
- optional :external_authorization_classification_label, type: String, desc: 'The classification label for the project'
optional :mirror, type: Boolean, desc: 'Enables pull mirroring in a project'
optional :mirror_trigger_builds, type: Boolean, desc: 'Pull mirroring triggers builds'
end
@@ -72,7 +72,8 @@ module API
:tag_list,
:visibility,
:wiki_enabled,
- :avatar
+ :avatar,
+ :external_authorization_classification_label
]
end
end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 9c7b9146c8f..00f0bbab231 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -256,19 +256,27 @@ module API
post '/post_receive' do
status 200
+ output = {} # Messages to gitlab-shell
+ user = identify(params[:identifier])
+ project = Gitlab::GlRepository.parse(params[:gl_repository]).first
+ push_options = Gitlab::PushOptions.new(params[:push_options])
+
PostReceive.perform_async(params[:gl_repository], params[:identifier],
- params[:changes], params[:push_options].to_a)
+ params[:changes], push_options.as_json)
+
+ if Feature.enabled?(:mr_push_options, default_enabled: true)
+ mr_options = push_options.get(:merge_request)
+ output.merge!(process_mr_push_options(mr_options, project, user, params[:changes])) if mr_options.present?
+ end
+
broadcast_message = BroadcastMessage.current&.last&.message
reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease
- output = {
- merge_request_urls: merge_request_urls,
+ output.merge!(
broadcast_message: broadcast_message,
- reference_counter_decreased: reference_counter_decreased
- }
-
- project = Gitlab::GlRepository.parse(params[:gl_repository]).first
- user = identify(params[:identifier])
+ reference_counter_decreased: reference_counter_decreased,
+ merge_request_urls: merge_request_urls
+ )
# A user is not guaranteed to be returned; an orphaned write deploy
# key could be used
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 999a9cb5a82..000c00ea9f9 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -230,9 +230,13 @@ module API
issue = user_project.issues.find_by!(iid: params.delete(:issue_iid))
authorize! :update_issue, issue
- # Setting created_at time only allowed for admins and project/group owners
- unless current_user.admin? || user_project.owner == current_user || current_user.owned_groups.include?(user_project.owner)
- params.delete(:updated_at)
+ # Setting updated_at only allowed for admins and owners as well
+ if params[:updated_at].present?
+ if current_user.admin? || user_project.owner == current_user || current_user.owned_groups.include?(user_project.owner)
+ issue.system_note_timestamp = params[:updated_at]
+ else
+ params.delete(:updated_at)
+ end
end
update_params = declared_params(include_missing: false).merge(request: request, api: true)
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index e4b21b7d1c4..1cc0ecc6df8 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -20,6 +20,7 @@ module API
def self.update_params_at_least_one_of
%i[
assignee_id
+ assignee_ids
description
labels
milestone_id
@@ -184,6 +185,7 @@ module API
params :optional_params 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'
+ optional :assignee_ids, type: Array[Integer], desc: 'The array of user IDs to assign issue'
optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request'
optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging'
@@ -231,6 +233,7 @@ module API
mr_params = declared_params(include_missing: false)
mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch)
+ mr_params = convert_parameters_from_legacy_format(mr_params)
merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute
@@ -334,6 +337,7 @@ module API
mr_params = declared_params(include_missing: false)
mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present?
+ mr_params = convert_parameters_from_legacy_format(mr_params)
merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request)
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index ac8fe98e55e..f29a18e94cf 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -81,6 +81,19 @@ module API
present pipeline, with: Entities::Pipeline
end
+ desc 'Gets the variables for a given pipeline' do
+ detail 'This feature was introduced in GitLab 11.11'
+ success Entities::Variable
+ end
+ params do
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ end
+ get ':id/pipelines/:pipeline_id/variables' do
+ authorize! :read_pipeline_variable, pipeline
+
+ present pipeline.variables, with: Entities::Variable
+ end
+
desc 'Deletes a pipeline' do
detail 'This feature was introduced in GitLab 11.6'
http_codes [[204, 'Pipeline was deleted'], [403, 'Forbidden']]
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index d96cdc31212..b064747e5fc 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -168,7 +168,9 @@ module API
optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.'
end
- optional_attributes = ::ApplicationSettingsHelper.visible_attributes << :performance_bar_allowed_group_id
+ optional_attributes = [*::ApplicationSettingsHelper.visible_attributes,
+ *::ApplicationSettingsHelper.external_authorization_service_attributes,
+ :performance_bar_allowed_group_id]
if Gitlab.ee?
optional_attributes += EE::ApplicationSettingsHelper.possible_licensed_attributes
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 2745905c5ff..199b3533cf4 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -150,7 +150,7 @@ module Banzai
end
def uri_type(path)
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/58011
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/58657
Gitlab::GitalyClient.allow_n_plus_1_calls do
@uri_types[path] ||= current_commit.uri_type(path)
end
diff --git a/lib/banzai/reference_parser/merge_request_parser.rb b/lib/banzai/reference_parser/merge_request_parser.rb
index e8147ac591a..d7bf450465e 100644
--- a/lib/banzai/reference_parser/merge_request_parser.rb
+++ b/lib/banzai/reference_parser/merge_request_parser.rb
@@ -10,7 +10,7 @@ module Banzai
nodes,
MergeRequest.includes(
:author,
- :assignee,
+ :assignees,
{
# These associations are primarily used for checking permissions.
# Eager loading these ensures we don't end up running dozens of
diff --git a/lib/gitlab/background_migration.rb b/lib/gitlab/background_migration.rb
index 5251e0fadf9..2e3a4f3b869 100644
--- a/lib/gitlab/background_migration.rb
+++ b/lib/gitlab/background_migration.rb
@@ -58,11 +58,31 @@ module Gitlab
migration_class_for(class_name).new.perform(*arguments)
end
- def self.exists?(migration_class)
+ def self.exists?(migration_class, additional_queues = [])
enqueued = Sidekiq::Queue.new(self.queue)
scheduled = Sidekiq::ScheduledSet.new
- [enqueued, scheduled].each do |queue|
+ enqueued_job?([enqueued, scheduled], migration_class)
+ end
+
+ def self.dead_jobs?(migration_class)
+ dead_set = Sidekiq::DeadSet.new
+
+ enqueued_job?([dead_set], migration_class)
+ end
+
+ def self.retrying_jobs?(migration_class)
+ retry_set = Sidekiq::RetrySet.new
+
+ enqueued_job?([retry_set], migration_class)
+ end
+
+ def self.migration_class_for(class_name)
+ const_get(class_name)
+ end
+
+ def self.enqueued_job?(queues, migration_class)
+ queues.each do |queue|
queue.each do |job|
return true if job.queue == self.queue && job.args.first == migration_class
end
@@ -70,9 +90,5 @@ module Gitlab
false
end
-
- def self.migration_class_for(class_name)
- const_get(class_name)
- end
end
end
diff --git a/lib/gitlab/background_migration/migrate_stage_index.rb b/lib/gitlab/background_migration/migrate_stage_index.rb
index f90f35a913d..f921233460d 100644
--- a/lib/gitlab/background_migration/migrate_stage_index.rb
+++ b/lib/gitlab/background_migration/migrate_stage_index.rb
@@ -21,8 +21,8 @@ module Gitlab
AND stage_idx IS NOT NULL
GROUP BY stage_id, stage_idx
), indexes AS (
- SELECT DISTINCT stage_id, last_value(stage_idx)
- OVER (PARTITION BY stage_id ORDER BY freq ASC) AS index
+ SELECT DISTINCT stage_id, first_value(stage_idx)
+ OVER (PARTITION BY stage_id ORDER BY freq DESC) AS index
FROM freqs
)
diff --git a/lib/gitlab/ci/config/entry/environment.rb b/lib/gitlab/ci/config/entry/environment.rb
index 69a3a1aedef..5a13fd18504 100644
--- a/lib/gitlab/ci/config/entry/environment.rb
+++ b/lib/gitlab/ci/config/entry/environment.rb
@@ -36,10 +36,12 @@ module Gitlab
validates :config, allowed_keys: ALLOWED_KEYS
validates :url,
+ type: String,
length: { maximum: 255 },
allow_nil: true
validates :action,
+ type: String,
inclusion: { in: %w[start stop], message: 'should be start or stop' },
allow_nil: true
diff --git a/lib/gitlab/ci/pipeline/chain/skip.rb b/lib/gitlab/ci/pipeline/chain/skip.rb
index 79bbcc1ed1e..7d6e0704d4a 100644
--- a/lib/gitlab/ci/pipeline/chain/skip.rb
+++ b/lib/gitlab/ci/pipeline/chain/skip.rb
@@ -8,7 +8,6 @@ module Gitlab
include ::Gitlab::Utils::StrongMemoize
SKIP_PATTERN = /\[(ci[ _-]skip|skip[ _-]ci)\]/i
- SKIP_PUSH_OPTION = 'ci.skip'
def perform!
if skipped?
@@ -35,7 +34,8 @@ module Gitlab
end
def push_option_skips_ci?
- !!(@command.push_options&.include?(SKIP_PUSH_OPTION))
+ @command.push_options.present? &&
+ @command.push_options.deep_symbolize_keys.dig(:ci, :skip).present?
end
end
end
diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb
index ea08b5f7eae..af385d7d4ca 100644
--- a/lib/gitlab/data_builder/push.rb
+++ b/lib/gitlab/data_builder/push.rb
@@ -32,10 +32,7 @@ module Gitlab
}
],
total_commits_count: 1,
- push_options: [
- "ci.skip",
- "custom option"
- ]
+ push_options: { ci: { skip: true } }
}.freeze
# Produce a hash of post-receive data
@@ -57,11 +54,11 @@ module Gitlab
# },
# commits: Array,
# total_commits_count: Fixnum,
- # push_options: Array
+ # push_options: Hash
# }
#
# rubocop:disable Metrics/ParameterLists
- def build(project, user, oldrev, newrev, ref, commits = [], message = nil, commits_count: nil, push_options: [])
+ def build(project, user, oldrev, newrev, ref, commits = [], message = nil, commits_count: nil, push_options: {})
commits = Array(commits)
# Total commits count
diff --git a/lib/gitlab/external_authorization.rb b/lib/gitlab/external_authorization.rb
new file mode 100644
index 00000000000..25f8b7b3628
--- /dev/null
+++ b/lib/gitlab/external_authorization.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ extend ExternalAuthorization::Config
+
+ RequestFailed = Class.new(StandardError)
+
+ def self.access_allowed?(user, label, project_path = nil)
+ return true unless perform_check?
+ return false unless user
+
+ access_for_user_to_label(user, label, project_path).has_access?
+ end
+
+ def self.rejection_reason(user, label)
+ return unless enabled?
+ return unless user
+
+ access_for_user_to_label(user, label, nil).reason
+ end
+
+ def self.access_for_user_to_label(user, label, project_path)
+ if RequestStore.active?
+ RequestStore.fetch("external_authorisation:user-#{user.id}:label-#{label}") do
+ load_access(user, label, project_path)
+ end
+ else
+ load_access(user, label, project_path)
+ end
+ end
+
+ def self.load_access(user, label, project_path)
+ access = ::Gitlab::ExternalAuthorization::Access.new(user, label).load!
+ ::Gitlab::ExternalAuthorization::Logger.log_access(access, project_path)
+
+ access
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/access.rb b/lib/gitlab/external_authorization/access.rb
new file mode 100644
index 00000000000..e111c41fcc2
--- /dev/null
+++ b/lib/gitlab/external_authorization/access.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ class Access
+ attr_reader :user,
+ :reason,
+ :loaded_at,
+ :label,
+ :load_type
+
+ def initialize(user, label)
+ @user, @label = user, label
+ end
+
+ def loaded?
+ loaded_at && (loaded_at > ExternalAuthorization::Cache::VALIDITY_TIME.ago)
+ end
+
+ def has_access?
+ @access
+ end
+
+ def load!
+ load_from_cache
+ load_from_service unless loaded?
+ self
+ end
+
+ private
+
+ def load_from_cache
+ @load_type = :cache
+ @access, @reason, @loaded_at = cache.load
+ end
+
+ def load_from_service
+ @load_type = :request
+ response = Client.new(@user, @label).request_access
+ @access = response.successful?
+ @reason = response.reason
+ @loaded_at = Time.now
+ cache.store(@access, @reason, @loaded_at) if response.valid?
+ rescue ::Gitlab::ExternalAuthorization::RequestFailed => e
+ @access = false
+ @reason = e.message
+ @loaded_at = Time.now
+ end
+
+ def cache
+ @cache ||= ExternalAuthorization::Cache.new(@user, @label)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/cache.rb b/lib/gitlab/external_authorization/cache.rb
new file mode 100644
index 00000000000..acdc028b4dc
--- /dev/null
+++ b/lib/gitlab/external_authorization/cache.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ class Cache
+ VALIDITY_TIME = 6.hours
+
+ def initialize(user, label)
+ @user, @label = user, label
+ end
+
+ def load
+ @access, @reason, @refreshed_at = ::Gitlab::Redis::Cache.with do |redis|
+ redis.hmget(cache_key, :access, :reason, :refreshed_at)
+ end
+
+ [access, reason, refreshed_at]
+ end
+
+ def store(new_access, new_reason, new_refreshed_at)
+ ::Gitlab::Redis::Cache.with do |redis|
+ redis.pipelined do
+ redis.mapped_hmset(
+ cache_key,
+ {
+ access: new_access.to_s,
+ reason: new_reason.to_s,
+ refreshed_at: new_refreshed_at.to_s
+ }
+ )
+
+ redis.expire(cache_key, VALIDITY_TIME)
+ end
+ end
+ end
+
+ private
+
+ def access
+ ::Gitlab::Utils.to_boolean(@access)
+ end
+
+ def reason
+ # `nil` if the cached value was an empty string
+ return unless @reason.present?
+
+ @reason
+ end
+
+ def refreshed_at
+ # Don't try to parse a time if there was no cache
+ return unless @refreshed_at.present?
+
+ Time.parse(@refreshed_at)
+ end
+
+ def cache_key
+ "external_authorization:user-#{@user.id}:label-#{@label}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/client.rb b/lib/gitlab/external_authorization/client.rb
new file mode 100644
index 00000000000..60aab2e7044
--- /dev/null
+++ b/lib/gitlab/external_authorization/client.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+Excon.defaults[:ssl_verify_peer] = false
+
+module Gitlab
+ module ExternalAuthorization
+ class Client
+ include ExternalAuthorization::Config
+
+ REQUEST_HEADERS = {
+ 'Content-Type' => 'application/json',
+ 'Accept' => 'application/json'
+ }.freeze
+
+ def initialize(user, label)
+ @user, @label = user, label
+ end
+
+ def request_access
+ response = Excon.post(
+ service_url,
+ post_params
+ )
+ ::Gitlab::ExternalAuthorization::Response.new(response)
+ rescue Excon::Error => e
+ raise ::Gitlab::ExternalAuthorization::RequestFailed.new(e)
+ end
+
+ private
+
+ def post_params
+ params = { headers: REQUEST_HEADERS,
+ body: body.to_json,
+ connect_timeout: timeout,
+ read_timeout: timeout,
+ write_timeout: timeout }
+
+ if has_tls?
+ params[:client_cert_data] = client_cert
+ params[:client_key_data] = client_key
+ params[:client_key_pass] = client_key_pass
+ end
+
+ params
+ end
+
+ def body
+ @body ||= begin
+ body = {
+ user_identifier: @user.email,
+ project_classification_label: @label
+ }
+
+ if @user.ldap_identity
+ body[:user_ldap_dn] = @user.ldap_identity.extern_uid
+ end
+
+ body
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/config.rb b/lib/gitlab/external_authorization/config.rb
new file mode 100644
index 00000000000..8654a8c1e2e
--- /dev/null
+++ b/lib/gitlab/external_authorization/config.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ module Config
+ extend self
+
+ def timeout
+ application_settings.external_authorization_service_timeout
+ end
+
+ def service_url
+ application_settings.external_authorization_service_url
+ end
+
+ def enabled?
+ application_settings.external_authorization_service_enabled
+ end
+
+ def perform_check?
+ enabled? && service_url.present?
+ end
+
+ def client_cert
+ application_settings.external_auth_client_cert
+ end
+
+ def client_key
+ application_settings.external_auth_client_key
+ end
+
+ def client_key_pass
+ application_settings.external_auth_client_key_pass
+ end
+
+ def has_tls?
+ client_cert.present? && client_key.present?
+ end
+
+ private
+
+ def application_settings
+ ::Gitlab::CurrentSettings.current_application_settings
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/logger.rb b/lib/gitlab/external_authorization/logger.rb
new file mode 100644
index 00000000000..61246cd870e
--- /dev/null
+++ b/lib/gitlab/external_authorization/logger.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ class Logger < ::Gitlab::Logger
+ def self.log_access(access, project_path)
+ status = access.has_access? ? "GRANTED" : "DENIED"
+ message = ["#{status} #{access.user.email} access to '#{access.label}'"]
+
+ message << "(#{project_path})" if project_path.present?
+ message << "- #{access.load_type} #{access.loaded_at}" if access.load_type == :cache
+
+ info(message.join(' '))
+ end
+
+ def self.file_name_noext
+ 'external-policy-access-control'
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/response.rb b/lib/gitlab/external_authorization/response.rb
new file mode 100644
index 00000000000..4f3fe5882db
--- /dev/null
+++ b/lib/gitlab/external_authorization/response.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ class Response
+ include ::Gitlab::Utils::StrongMemoize
+
+ def initialize(excon_response)
+ @excon_response = excon_response
+ end
+
+ def valid?
+ @excon_response && [200, 401, 403].include?(@excon_response.status)
+ end
+
+ def successful?
+ valid? && @excon_response.status == 200
+ end
+
+ def reason
+ parsed_response['reason'] if parsed_response
+ end
+
+ private
+
+ def parsed_response
+ strong_memoize(:parsed_response) { parse_response! }
+ end
+
+ def parse_response!
+ JSON.parse(@excon_response.body)
+ rescue JSON::JSONError
+ # The JSON response is optional, so don't fail when it's missing
+ nil
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git_post_receive.rb b/lib/gitlab/git_post_receive.rb
index 426436c2164..d98b85fecc4 100644
--- a/lib/gitlab/git_post_receive.rb
+++ b/lib/gitlab/git_post_receive.rb
@@ -5,7 +5,7 @@ module Gitlab
include Gitlab::Identifier
attr_reader :project, :identifier, :changes, :push_options
- def initialize(project, identifier, changes, push_options)
+ def initialize(project, identifier, changes, push_options = {})
@project = project
@identifier = identifier
@changes = deserialize_changes(changes)
diff --git a/lib/gitlab/hook_data/issuable_builder.rb b/lib/gitlab/hook_data/issuable_builder.rb
index 0803df65632..b8da6731081 100644
--- a/lib/gitlab/hook_data/issuable_builder.rb
+++ b/lib/gitlab/hook_data/issuable_builder.rb
@@ -20,11 +20,7 @@ module Gitlab
repository: issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)
}
- if issuable.is_a?(Issue)
- hook_data[:assignees] = issuable.assignees.map(&:hook_attrs) if issuable.assignees.any?
- else
- hook_data[:assignee] = issuable.assignee.hook_attrs if issuable.assignee
- end
+ hook_data[:assignees] = issuable.assignees.map(&:hook_attrs) if issuable.assignees.any?
hook_data
end
diff --git a/lib/gitlab/hook_data/merge_request_builder.rb b/lib/gitlab/hook_data/merge_request_builder.rb
index d77b1d04644..a8e993e087e 100644
--- a/lib/gitlab/hook_data/merge_request_builder.rb
+++ b/lib/gitlab/hook_data/merge_request_builder.rb
@@ -34,7 +34,6 @@ module Gitlab
end
SAFE_HOOK_RELATIONS = %i[
- assignee
labels
total_time_spent
].freeze
@@ -51,7 +50,9 @@ module Gitlab
work_in_progress: merge_request.work_in_progress?,
total_time_spent: merge_request.total_time_spent,
human_total_time_spent: merge_request.human_total_time_spent,
- human_time_estimate: merge_request.human_time_estimate
+ human_time_estimate: merge_request.human_time_estimate,
+ assignee_ids: merge_request.assignee_ids,
+ assignee_id: merge_request.assignee_ids.first # This key is deprecated
}
merge_request.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes)
diff --git a/lib/gitlab/import/merge_request_helpers.rb b/lib/gitlab/import/merge_request_helpers.rb
index b3fe1fc0685..4bc39868389 100644
--- a/lib/gitlab/import/merge_request_helpers.rb
+++ b/lib/gitlab/import/merge_request_helpers.rb
@@ -22,7 +22,7 @@ module Gitlab
# additional work that is strictly necessary.
merge_request_id = insert_and_return_id(attributes, project.merge_requests)
- merge_request = project.merge_requests.reload.find(merge_request_id)
+ merge_request = project.merge_requests.reset.find(merge_request_id)
[merge_request, false]
end
diff --git a/lib/gitlab/kubernetes/namespace.rb b/lib/gitlab/kubernetes/namespace.rb
index 919f19c86d7..8a3bea95a04 100644
--- a/lib/gitlab/kubernetes/namespace.rb
+++ b/lib/gitlab/kubernetes/namespace.rb
@@ -19,11 +19,40 @@ module Gitlab
def create!
resource = ::Kubeclient::Resource.new(metadata: { name: name })
+ log_event(:begin_create)
@client.create_namespace(resource)
end
def ensure_exists!
exists? || create!
+ rescue ::Kubeclient::HttpError => error
+ log_create_failed(error)
+ raise
+ end
+
+ private
+
+ def log_create_failed(error)
+ logger.error({
+ exception: error.class.name,
+ status_code: error.error_code,
+ namespace: name,
+ class_name: self.class.name,
+ event: :failed_to_create_namespace,
+ message: error.message
+ })
+ end
+
+ def log_event(event)
+ logger.info(
+ namespace: name,
+ class_name: self.class.name,
+ event: event
+ )
+ end
+
+ def logger
+ @logger ||= Gitlab::Kubernetes::Logger.build
end
end
end
diff --git a/lib/gitlab/legacy_github_import/project_creator.rb b/lib/gitlab/legacy_github_import/project_creator.rb
index ca1a1b8e9bd..b484b69c932 100644
--- a/lib/gitlab/legacy_github_import/project_creator.rb
+++ b/lib/gitlab/legacy_github_import/project_creator.rb
@@ -37,7 +37,7 @@ module Gitlab
end
def visibility_level
- visibility_level = repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC
+ visibility_level = repo.private ? Gitlab::VisibilityLevel::PRIVATE : @namespace.visibility_level
visibility_level = Gitlab::CurrentSettings.default_project_visibility if Gitlab::CurrentSettings.restricted_visibility_levels.include?(visibility_level)
visibility_level
diff --git a/lib/gitlab/legacy_github_import/release_formatter.rb b/lib/gitlab/legacy_github_import/release_formatter.rb
index 8c0c17780ca..746786b5a66 100644
--- a/lib/gitlab/legacy_github_import/release_formatter.rb
+++ b/lib/gitlab/legacy_github_import/release_formatter.rb
@@ -7,6 +7,7 @@ module Gitlab
{
project: project,
tag: raw_data.tag_name,
+ name: raw_data.name,
description: raw_data.body,
created_at: raw_data.created_at,
updated_at: raw_data.created_at
diff --git a/lib/gitlab/push_options.rb b/lib/gitlab/push_options.rb
new file mode 100644
index 00000000000..810aba436cc
--- /dev/null
+++ b/lib/gitlab/push_options.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class PushOptions
+ VALID_OPTIONS = HashWithIndifferentAccess.new({
+ merge_request: {
+ keys: [:create, :merge_when_pipeline_succeeds, :target]
+ },
+ ci: {
+ keys: [:skip]
+ }
+ }).freeze
+
+ NAMESPACE_ALIASES = HashWithIndifferentAccess.new({
+ mr: :merge_request
+ }).freeze
+
+ OPTION_MATCHER = /(?<namespace>[^\.]+)\.(?<key>[^=]+)=?(?<value>.*)/
+
+ attr_reader :options
+
+ def initialize(options = [])
+ @options = parse_options(options)
+ end
+
+ def get(*args)
+ options.dig(*args)
+ end
+
+ # Allow #to_json serialization
+ def as_json(*_args)
+ options
+ end
+
+ private
+
+ def parse_options(raw_options)
+ options = HashWithIndifferentAccess.new
+
+ Array.wrap(raw_options).each do |option|
+ namespace, key, value = parse_option(option)
+
+ next if [namespace, key].any?(&:nil?)
+
+ options[namespace] ||= HashWithIndifferentAccess.new
+ options[namespace][key] = value
+ end
+
+ options
+ end
+
+ def parse_option(option)
+ parts = OPTION_MATCHER.match(option)
+ return unless parts
+
+ namespace, key, value = parts.values_at(:namespace, :key, :value).map(&:strip)
+ namespace = NAMESPACE_ALIASES[namespace] if NAMESPACE_ALIASES[namespace]
+ value = value.presence || true
+
+ return unless valid_option?(namespace, key)
+
+ [namespace, key, value]
+ end
+
+ def valid_option?(namespace, key)
+ keys = VALID_OPTIONS.dig(namespace, :keys)
+ keys && keys.include?(key.to_sym)
+ end
+ end
+end