application_setting.rb 17.6 KB
Newer Older
1 2
# frozen_string_literal: true

3
class ApplicationSetting < ApplicationRecord
4
  include CacheableAttributes
5
  include CacheMarkdownField
6
  include TokenAuthenticatable
7
  include ChronicDurationAttribute
8 9 10
  include IgnorableColumns

  ignore_column :namespace_storage_size_limit, remove_with: '13.5', remove_after: '2020-09-22'
11
  ignore_column :instance_statistics_visibility_private, remove_with: '13.6', remove_after: '2020-10-22'
12

13 14 15
  GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \
    'Admin Area > Settings > Metrics and profiling > Metrics - Grafana'

16
  add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption) ? :optional : :required }
17
  add_authentication_token_field :health_check_access_token
18
  add_authentication_token_field :static_objects_external_storage_auth_token
19

20
  belongs_to :self_monitoring_project, class_name: "Project", foreign_key: 'instance_administration_project_id'
21
  belongs_to :push_rule
22 23
  alias_attribute :self_monitoring_project_id, :instance_administration_project_id

24
  belongs_to :instance_administrators_group, class_name: "Group"
25

26 27 28 29 30 31
  def self.repository_storages_weighted_attributes
    @repository_storages_weighted_atributes ||= Gitlab.config.repositories.storages.keys.map { |k| "repository_storages_weighted_#{k}".to_sym }.freeze
  end

  store_accessor :repository_storages_weighted, *Gitlab.config.repositories.storages.keys, prefix: true

32 33 34 35 36 37
  # Include here so it can override methods from
  # `add_authentication_token_field`
  # We don't prepend for now because otherwise we'll need to
  # fix a lot of tests using allow_any_instance_of
  include ApplicationSettingImplementation

38 39 40 41 42 43
  serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize
  serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize
  serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiveRecordSerialize
  serialize :domain_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
  serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize
  serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize
44
  serialize :asset_proxy_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
45

46 47 48 49 50
  cache_markdown_field :sign_in_text
  cache_markdown_field :help_page_text
  cache_markdown_field :shared_runners_text, pipeline: :plain_markdown
  cache_markdown_field :after_sign_up_text

51
  default_value_for :id, 1
52
  default_value_for :repository_storages_weighted, {}
53

54 55
  chronic_duration_attr_writer :archive_builds_in_human_readable, :archive_builds_in_seconds

56 57 58 59 60 61 62 63
  validates :grafana_url,
            system_hook_url: {
              blocked_message: "is blocked: %{exception_message}. " + GRAFANA_URL_ERROR_MESSAGE
            },
            if: :grafana_url_absolute?

  validate :validate_grafana_url

64 65
  validates :uuid, presence: true

66
  validates :outbound_local_requests_whitelist,
67 68 69
            length: { maximum: 1_000, message: N_('is too long (maximum is 1000 entries)') },
            allow_nil: false,
            qualified_domain_array: true
70

71
  validates :session_expire_delay,
72 73
            presence: true,
            numericality: { only_integer: true, greater_than_or_equal_to: 0 }
74

75 76 77 78 79 80
  validates :minimum_password_length,
            presence: true,
            numericality: { only_integer: true,
                            greater_than_or_equal_to: DEFAULT_MINIMUM_PASSWORD_LENGTH,
                            less_than_or_equal_to: Devise.password_length.max }

81
  validates :home_page_url,
82
            allow_blank: true,
83
            addressable_url: true,
R
Robin Bobbitt 已提交
84 85 86 87
            if: :home_page_url_column_exists?

  validates :help_page_support_url,
            allow_blank: true,
88
            addressable_url: true,
R
Robin Bobbitt 已提交
89
            if: :help_page_support_url_column_exists?
90

91
  validates :after_sign_out_path,
92
            allow_blank: true,
93
            addressable_url: true
94

D
Douwe Maan 已提交
95
  validates :admin_notification_email,
96
            devise_email: true,
97
            allow_blank: true
D
Douwe Maan 已提交
98

99
  validates :two_factor_grace_period,
100 101 102 103
            numericality: { greater_than_or_equal_to: 0 }

  validates :recaptcha_site_key,
            presence: true,
104
            if: :recaptcha_or_login_protection_enabled
105 106 107

  validates :recaptcha_private_key,
            presence: true,
108
            if: :recaptcha_or_login_protection_enabled
109

110 111 112 113
  validates :akismet_api_key,
            presence: true,
            if: :akismet_enabled

P
Pawel Chojnacki 已提交
114 115 116 117 118 119 120 121 122 123
  validates :unique_ips_limit_per_user,
            numericality: { greater_than_or_equal_to: 1 },
            presence: true,
            if: :unique_ips_limit_enabled

  validates :unique_ips_limit_time_window,
            numericality: { greater_than_or_equal_to: 0 },
            presence: true,
            if: :unique_ips_limit_enabled

124 125 126 127
  validates :plantuml_url,
            presence: true,
            if: :plantuml_enabled

128 129 130 131
  validates :sourcegraph_url,
            presence: true,
            if: :sourcegraph_enabled

132 133 134 135 136
  validates :snowplow_collector_hostname,
            presence: true,
            hostname: true,
            if: :snowplow_enabled

137 138 139 140 141
  validates :snowplow_iglu_registry_url,
            addressable_url: true,
            allow_blank: true,
            if: :snowplow_enabled

142 143 144 145
  validates :max_attachment_size,
            presence: true,
            numericality: { only_integer: true, greater_than: 0 }

146 147 148 149
  validates :max_artifacts_size,
            presence: true,
            numericality: { only_integer: true, greater_than: 0 }

150 151 152 153
  validates :max_import_size,
            presence: true,
            numericality: { only_integer: true, greater_than_or_equal_to: 0 }

154 155
  validates :max_pages_size,
            presence: true,
156
            numericality: { only_integer: true, greater_than_or_equal_to: 0,
157 158
                            less_than: ::Gitlab::Pages::MAX_SIZE / 1.megabyte }

159
  validates :default_artifacts_expire_in, presence: true, duration: true
160

161
  validates :container_expiration_policies_enable_historic_entries,
162
            inclusion: { in: [true, false], message: 'must be a boolean value' }
163

164 165 166 167
  validates :container_registry_token_expire_delay,
            presence: true,
            numericality: { only_integer: true, greater_than: 0 }

168 169
  validates :repository_storages, presence: true
  validate :check_repository_storages
170
  validate :check_repository_storages_weighted
171

172 173 174 175 176
  validates :auto_devops_domain,
            allow_blank: true,
            hostname: { allow_numeric_hostname: true, require_valid_tld: true },
            if: :auto_devops_enabled?

177
  validates :enabled_git_access_protocol,
178
            inclusion: { in: %w(ssh http), allow_blank: true }
179

180
  validates :domain_blacklist,
181
            presence: { message: 'Domain blacklist cannot be empty if Blacklist is enabled.' },
182 183
            if: :domain_blacklist_enabled?

J
Jacob Vosmaer 已提交
184 185 186 187 188 189
  validates :housekeeping_incremental_repack_period,
            presence: true,
            numericality: { only_integer: true, greater_than: 0 }

  validates :housekeeping_full_repack_period,
            presence: true,
190
            numericality: { only_integer: true, greater_than_or_equal_to: :housekeeping_incremental_repack_period }
J
Jacob Vosmaer 已提交
191 192 193

  validates :housekeeping_gc_period,
            presence: true,
194
            numericality: { only_integer: true, greater_than_or_equal_to: :housekeeping_full_repack_period }
J
Jacob Vosmaer 已提交
195

196 197 198 199
  validates :terminal_max_session_time,
            presence: true,
            numericality: { only_integer: true, greater_than_or_equal_to: 0 }

200 201 202 203
  validates :polling_interval_multiplier,
            presence: true,
            numericality: { greater_than_or_equal_to: 0 }

A
Andrew Newdigate 已提交
204 205
  validates :gitaly_timeout_default,
            presence: true,
206
            if: :gitaly_timeout_default_changed?,
207 208 209 210 211
            numericality: {
              only_integer: true,
              greater_than_or_equal_to: 0,
              less_than_or_equal_to: Settings.gitlab.max_request_duration_seconds
            }
A
Andrew Newdigate 已提交
212 213 214

  validates :gitaly_timeout_medium,
            presence: true,
215
            if: :gitaly_timeout_medium_changed?,
A
Andrew Newdigate 已提交
216 217 218 219 220 221 222 223 224 225
            numericality: { only_integer: true, greater_than_or_equal_to: 0 }
  validates :gitaly_timeout_medium,
            numericality: { less_than_or_equal_to: :gitaly_timeout_default },
            if: :gitaly_timeout_default
  validates :gitaly_timeout_medium,
            numericality: { greater_than_or_equal_to: :gitaly_timeout_fast },
            if: :gitaly_timeout_fast

  validates :gitaly_timeout_fast,
            presence: true,
226
            if: :gitaly_timeout_fast_changed?,
A
Andrew Newdigate 已提交
227 228 229 230 231
            numericality: { only_integer: true, greater_than_or_equal_to: 0 }
  validates :gitaly_timeout_fast,
            numericality: { less_than_or_equal_to: :gitaly_timeout_default },
            if: :gitaly_timeout_default

232 233 234 235 236 237
  validates :diff_max_patch_bytes,
            presence: true,
            numericality: { only_integer: true,
                            greater_than_or_equal_to: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
                            less_than_or_equal_to: Gitlab::Git::Diff::MAX_PATCH_BYTES_UPPER_BOUND }

238 239
  validates :user_default_internal_regex, js_regex: true, allow_nil: true

240 241
  validates :commit_email_hostname, format: { with: /\A[^@]+\z/ }

242 243 244 245
  validates :archive_builds_in_seconds,
            allow_nil: true,
            numericality: { only_integer: true, greater_than_or_equal_to: 1.day.seconds }

J
Jan Provaznik 已提交
246 247 248 249
  validates :local_markdown_version,
            allow_nil: true,
            numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than: 65536 }

250 251 252 253 254 255 256 257 258 259 260
  validates :asset_proxy_url,
            presence: true,
            allow_blank: false,
            url: true,
            if: :asset_proxy_enabled?

  validates :asset_proxy_secret_key,
            presence: true,
            allow_blank: false,
            if: :asset_proxy_enabled?

261 262 263 264 265 266 267
  validates :static_objects_external_storage_url,
            addressable_url: true, allow_blank: true

  validates :static_objects_external_storage_auth_token,
            presence: true,
            if: :static_objects_external_storage_url?

268 269 270 271
  validates :protected_paths,
            length: { maximum: 100, message: N_('is too long (maximum is 100 entries)') },
            allow_nil: false

272 273 274
  validates :push_event_hooks_limit,
            numericality: { greater_than_or_equal_to: 0 }

275 276 277
  validates :push_event_activities_limit,
            numericality: { greater_than_or_equal_to: 0 }

278
  validates :snippet_size_limit, numericality: { only_integer: true, greater_than: 0 }
279
  validates :wiki_page_max_content_bytes, numericality: { only_integer: true, greater_than_or_equal_to: 1.kilobytes }
280

281
  validates :email_restrictions, untrusted_regexp: true
282

283 284
  validates :hashed_storage_enabled, inclusion: { in: [true], message: _("Hashed storage can't be disabled anymore for new projects") }

285
  SUPPORTED_KEY_TYPES.each do |type|
286
    validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
287
  end
288

N
Nick Thomas 已提交
289 290
  validates :allowed_key_types, presence: true

291
  validates_each :restricted_visibility_levels do |record, attr, value|
Z
Z.J. van de Weg 已提交
292
    value&.each do |level|
293
      unless Gitlab::VisibilityLevel.options.value?(level)
294
        record.errors.add(attr, _("'%{level}' is not a valid visibility level") % { level: level })
295 296 297 298
      end
    end
  end

299
  validates_each :import_sources do |record, attr, value|
Z
Z.J. van de Weg 已提交
300
    value&.each do |source|
301
      unless Gitlab::ImportSources.options.value?(source)
302
        record.errors.add(attr, _("'%{source}' is not a import source") % { source: source })
303 304 305 306
      end
    end
  end

B
Bob Van Landuyt 已提交
307 308
  validate :terms_exist, if: :enforce_terms?

309 310 311 312 313
  validates :external_authorization_service_default_label,
            presence: true,
            if: :external_authorization_service_enabled

  validates :external_authorization_service_url,
314
            addressable_url: true, allow_blank: true,
315 316 317 318 319 320
            if: :external_authorization_service_enabled

  validates :external_authorization_service_timeout,
            numericality: { greater_than: 0, less_than_or_equal_to: 10 },
            if: :external_authorization_service_enabled

321 322 323 324 325 326 327
  validates :spam_check_endpoint_url,
            addressable_url: true, allow_blank: true

  validates :spam_check_endpoint_url,
            presence: true,
            if: :spam_check_endpoint_enabled

328 329 330 331
  validates :external_auth_client_key,
            presence: true,
            if: -> (setting) { setting.external_auth_client_cert.present? }

332 333 334 335 336 337 338 339 340 341
  validates :lets_encrypt_notification_email,
            devise_email: true,
            format: { without: /@example\.(com|org|net)\z/,
                      message: N_("Let's Encrypt does not accept emails on example.com") },
            allow_blank: true

  validates :lets_encrypt_notification_email,
            presence: true,
            if: :lets_encrypt_terms_of_service_accepted?

342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
  validates :eks_integration_enabled,
            inclusion: { in: [true, false] }

  validates :eks_account_id,
            format: { with: Gitlab::Regex.aws_account_id_regex,
                      message: Gitlab::Regex.aws_account_id_message },
            if: :eks_integration_enabled?

  validates :eks_access_key_id,
            length: { in: 16..128 },
            if: :eks_integration_enabled?

  validates :eks_secret_access_key,
            presence: true,
            if: :eks_integration_enabled?

358 359 360 361 362 363
  validates_with X509CertificateCredentialsValidator,
                 certificate: :external_auth_client_cert,
                 pkey: :external_auth_client_key,
                 pass: :external_auth_client_key_pass,
                 if: -> (setting) { setting.external_auth_client_cert.present? }

364 365 366 367 368 369
  validates :default_ci_config_path,
    format: { without: %r{(\.{2}|\A/)},
              message: N_('cannot include leading slash or directory traversal.') },
    length: { maximum: 255 },
    allow_blank: true

370
  validates :issues_create_limit,
371 372 373 374
            numericality: { only_integer: true, greater_than_or_equal_to: 0 }

  validates :raw_blob_request_limit,
            numericality: { only_integer: true, greater_than_or_equal_to: 0 }
375

376 377 378 379 380 381
  attr_encrypted :asset_proxy_secret_key,
                 mode: :per_attribute_iv,
                 key: Settings.attr_encrypted_db_key_base_truncated,
                 algorithm: 'aes-256-cbc',
                 insecure_mode: true

382 383 384 385 386 387 388 389
  private_class_method def self.encryption_options_base_truncated_aes_256_gcm
    {
      mode: :per_attribute_iv,
      key: Settings.attr_encrypted_db_key_base_truncated,
      algorithm: 'aes-256-gcm',
      encode: true
    }
  end
390

391 392 393 394 395 396 397 398 399 400
  attr_encrypted :external_auth_client_key, encryption_options_base_truncated_aes_256_gcm
  attr_encrypted :external_auth_client_key_pass, encryption_options_base_truncated_aes_256_gcm
  attr_encrypted :lets_encrypt_private_key, encryption_options_base_truncated_aes_256_gcm
  attr_encrypted :eks_secret_access_key, encryption_options_base_truncated_aes_256_gcm
  attr_encrypted :akismet_api_key, encryption_options_base_truncated_aes_256_gcm
  attr_encrypted :elasticsearch_aws_secret_access_key, encryption_options_base_truncated_aes_256_gcm
  attr_encrypted :recaptcha_private_key, encryption_options_base_truncated_aes_256_gcm
  attr_encrypted :recaptcha_site_key, encryption_options_base_truncated_aes_256_gcm
  attr_encrypted :slack_app_secret, encryption_options_base_truncated_aes_256_gcm
  attr_encrypted :slack_app_verification_token, encryption_options_base_truncated_aes_256_gcm
401

402
  before_validation :ensure_uuid!
403

404
  before_save :ensure_runners_registration_token
405
  before_save :ensure_health_check_access_token
406

407
  after_commit do
B
Bob Van Landuyt 已提交
408
    reset_memoized_terms
409
  end
410
  after_commit :expire_performance_bar_allowed_user_ids_cache, if: -> { previous_changes.key?('performance_bar_allowed_group_id') }
411

412 413 414 415 416 417 418 419 420 421 422 423 424
  def validate_grafana_url
    unless parsed_grafana_url
      self.errors.add(
        :grafana_url,
        "must be a valid relative or absolute URL. #{GRAFANA_URL_ERROR_MESSAGE}"
      )
    end
  end

  def grafana_url_absolute?
    parsed_grafana_url&.absolute?
  end

425 426 427 428
  def sourcegraph_url_is_com?
    !!(sourcegraph_url =~ /\Ahttps:\/\/(www\.)?sourcegraph\.com/)
  end

429
  def self.create_from_defaults
430 431
    check_schema!

432 433 434
    transaction(requires_new: true) do
      super
    end
435 436 437 438
  rescue ActiveRecord::RecordNotUnique
    # We already have an ApplicationSetting record, so just return it.
    current_without_cache
  end
439

440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
  # Due to the frequency with which settings are accessed, it is
  # likely that during a backup restore a running GitLab process
  # will insert a new `application_settings` row before the
  # constraints have been added to the table. This would add an
  # extra row with ID 1 and prevent the primary key constraint from
  # being added, which made ActiveRecord throw a
  # IrreversibleOrderError anytime the settings were accessed
  # (https://gitlab.com/gitlab-org/gitlab/-/issues/36405).  To
  # prevent this from happening, we do a sanity check that the
  # primary key constraint is present before inserting a new entry.
  def self.check_schema!
    return if ActiveRecord::Base.connection.primary_key(self.table_name).present?

    raise "The `#{self.table_name}` table is missing a primary key constraint in the database schema"
  end

456 457 458 459 460
  # By default, the backend is Rails.cache, which uses
  # ActiveSupport::Cache::RedisStore. Since loading ApplicationSetting
  # can cause a significant amount of load on Redis, let's cache it in
  # memory.
  def self.cache_backend
461
    Gitlab::ProcessMemoryCache.cache_backend
462
  end
463 464 465 466

  def recaptcha_or_login_protection_enabled
    recaptcha_enabled || login_recaptcha_protection_enabled
  end
467

468 469 470 471 472 473
  repository_storages_weighted_attributes.each do |attribute|
    define_method :"#{attribute}=" do |value|
      super(value.to_i)
    end
  end

474 475 476 477 478
  private

  def parsed_grafana_url
    @parsed_grafana_url ||= Gitlab::Utils.parse_url(grafana_url)
  end
479
end
480 481

ApplicationSetting.prepend_if_ee('EE::ApplicationSetting')