summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/application_setting.rb34
-rw-r--r--app/models/blob.rb2
-rw-r--r--app/models/ci/pipeline.rb4
-rw-r--r--app/models/concerns/deprecated_assignee.rb86
-rw-r--r--app/models/concerns/issuable.rb41
-rw-r--r--app/models/concerns/noteable.rb8
-rw-r--r--app/models/gpg_signature.rb9
-rw-r--r--app/models/instance_configuration.rb2
-rw-r--r--app/models/issue.rb34
-rw-r--r--app/models/merge_request.rb51
-rw-r--r--app/models/project.rb52
-rw-r--r--app/models/release.rb1
12 files changed, 202 insertions, 122 deletions
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 7ec8505b33a..d28a12413bf 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -213,6 +213,40 @@ class ApplicationSetting < ApplicationRecord
validate :terms_exist, if: :enforce_terms?
+ validates :external_authorization_service_default_label,
+ presence: true,
+ if: :external_authorization_service_enabled
+
+ validates :external_authorization_service_url,
+ url: true, allow_blank: true,
+ 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
+
+ validates :external_auth_client_key,
+ presence: true,
+ if: -> (setting) { setting.external_auth_client_cert.present? }
+
+ 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? }
+
+ attr_encrypted :external_auth_client_key,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm',
+ encode: true
+
+ attr_encrypted :external_auth_client_key_pass,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm',
+ encode: true
+
before_validation :ensure_uuid!
before_validation :strip_sentry_values
diff --git a/app/models/blob.rb b/app/models/blob.rb
index c5766eb0327..d528bef8b19 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -110,7 +110,7 @@ class Blob < SimpleDelegator
end
def load_all_data!
- # Endpoint needed: gitlab-org/gitaly#756
+ # Endpoint needed: https://gitlab.com/gitlab-org/gitaly/issues/756
Gitlab::GitalyClient.allow_n_plus_1_calls do
super(project.repository) if project
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 01d96754518..b81a3cf8362 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -750,6 +750,10 @@ module Ci
self.sha == sha || self.source_sha == sha
end
+ def triggered_by?(current_user)
+ user == current_user
+ end
+
private
def ci_yaml_from_repo
diff --git a/app/models/concerns/deprecated_assignee.rb b/app/models/concerns/deprecated_assignee.rb
new file mode 100644
index 00000000000..7f12ce39c96
--- /dev/null
+++ b/app/models/concerns/deprecated_assignee.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+# This module handles backward compatibility for import/export of Merge Requests after
+# multiple assignees feature was introduced. Also, it handles the scenarios where
+# the #26496 background migration hasn't finished yet.
+# Ideally, most of this code should be removed at #59457.
+module DeprecatedAssignee
+ extend ActiveSupport::Concern
+
+ def assignee_ids=(ids)
+ nullify_deprecated_assignee
+ super
+ end
+
+ def assignees=(users)
+ nullify_deprecated_assignee
+ super
+ end
+
+ def assignee_id=(id)
+ self.assignee_ids = Array(id)
+ end
+
+ def assignee=(user)
+ self.assignees = Array(user)
+ end
+
+ def assignee
+ assignees.first
+ end
+
+ def assignee_id
+ assignee_ids.first
+ end
+
+ def assignee_ids
+ if Gitlab::Database.read_only? && pending_assignees_population?
+ return Array(deprecated_assignee_id)
+ end
+
+ update_assignees_relation
+ super
+ end
+
+ def assignees
+ if Gitlab::Database.read_only? && pending_assignees_population?
+ return User.where(id: deprecated_assignee_id)
+ end
+
+ update_assignees_relation
+ super
+ end
+
+ private
+
+ # This will make the background migration process quicker (#26496) as it'll have less
+ # assignee_id rows to look through.
+ def nullify_deprecated_assignee
+ return unless persisted? && Gitlab::Database.read_only?
+
+ update_column(:assignee_id, nil)
+ end
+
+ # This code should be removed in the clean-up phase of the
+ # background migration (#59457).
+ def pending_assignees_population?
+ persisted? && deprecated_assignee_id && merge_request_assignees.empty?
+ end
+
+ # If there's an assignee_id and no relation, it means the background
+ # migration at #26496 didn't reach this merge request yet.
+ # This code should be removed in the clean-up phase of the
+ # background migration (#59457).
+ def update_assignees_relation
+ if pending_assignees_population?
+ transaction do
+ merge_request_assignees.create!(user_id: deprecated_assignee_id, merge_request_id: id)
+ update_column(:assignee_id, nil)
+ end
+ end
+ end
+
+ def deprecated_assignee_id
+ read_attribute(:assignee_id)
+ end
+end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 17f94b4bd9b..3232c51bfbd 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -67,13 +67,6 @@ module Issuable
allow_nil: true,
prefix: true
- delegate :name,
- :email,
- :public_email,
- to: :assignee,
- allow_nil: true,
- prefix: true
-
validates :author, presence: true
validates :title, presence: true, length: { maximum: 255 }
validate :milestone_is_valid
@@ -88,6 +81,19 @@ module Issuable
scope :only_opened, -> { with_state(:opened) }
scope :closed, -> { with_state(:closed) }
+ # rubocop:disable GitlabSecurity/SqlInjection
+ # The `to_ability_name` method is not an user input.
+ scope :assigned, -> do
+ where("EXISTS (SELECT TRUE FROM #{to_ability_name}_assignees WHERE #{to_ability_name}_id = #{to_ability_name}s.id)")
+ end
+ scope :unassigned, -> do
+ where("NOT EXISTS (SELECT TRUE FROM #{to_ability_name}_assignees WHERE #{to_ability_name}_id = #{to_ability_name}s.id)")
+ end
+ scope :assigned_to, ->(u) do
+ where("EXISTS (SELECT TRUE FROM #{to_ability_name}_assignees WHERE user_id = ? AND #{to_ability_name}_id = #{to_ability_name}s.id)", u.id)
+ end
+ # rubocop:enable GitlabSecurity/SqlInjection
+
scope :left_joins_milestones, -> { joins("LEFT OUTER JOIN milestones ON #{table_name}.milestone_id = milestones.id") }
scope :order_milestone_due_desc, -> { left_joins_milestones.reorder('milestones.due_date IS NULL, milestones.id IS NULL, milestones.due_date DESC') }
scope :order_milestone_due_asc, -> { left_joins_milestones.reorder('milestones.due_date IS NULL, milestones.id IS NULL, milestones.due_date ASC') }
@@ -104,6 +110,7 @@ module Issuable
participant :author
participant :notes_with_associations
+ participant :assignees
strip_attributes :title
@@ -270,6 +277,10 @@ module Issuable
end
end
+ def assignee_or_author?(user)
+ author_id == user.id || assignees.exists?(user.id)
+ end
+
def today?
Date.today == created_at.to_date
end
@@ -314,11 +325,7 @@ module Issuable
end
if old_assignees != assignees
- if self.is_a?(Issue)
- changes[:assignees] = [old_assignees.map(&:hook_attrs), assignees.map(&:hook_attrs)]
- else
- changes[:assignee] = [old_assignees&.first&.hook_attrs, assignee&.hook_attrs]
- end
+ changes[:assignees] = [old_assignees.map(&:hook_attrs), assignees.map(&:hook_attrs)]
end
if self.respond_to?(:total_time_spent)
@@ -355,10 +362,18 @@ module Issuable
def card_attributes
{
'Author' => author.try(:name),
- 'Assignee' => assignee.try(:name)
+ 'Assignee' => assignee_list
}
end
+ def assignee_list
+ assignees.map(&:name).to_sentence
+ end
+
+ def assignee_username_list
+ assignees.map(&:username).to_sentence
+ end
+
def notes_with_associations
# If A has_many Bs, and B has_many Cs, and you do
# `A.includes(b: :c).each { |a| a.b.includes(:c) }`, sadly ActiveRecord
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index 3c74034b527..423ce7e7db1 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -13,6 +13,14 @@ module Noteable
end
end
+ # The timestamp of the note (e.g. the :updated_at attribute if provided via
+ # API call)
+ def system_note_timestamp
+ @system_note_timestamp || Time.now # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+
+ attr_writer :system_note_timestamp
+
def base_class_name
self.class.base_class.name
end
diff --git a/app/models/gpg_signature.rb b/app/models/gpg_signature.rb
index 7f9ff7bbda6..46cac1d41bb 100644
--- a/app/models/gpg_signature.rb
+++ b/app/models/gpg_signature.rb
@@ -38,6 +38,15 @@ class GpgSignature < ApplicationRecord
.safe_find_or_create_by!(commit_sha: attributes[:commit_sha])
end
+ # Find commits that are lacking a signature in the database at present
+ def self.unsigned_commit_shas(commit_shas)
+ return [] if commit_shas.empty?
+
+ signed = GpgSignature.where(commit_sha: commit_shas).pluck(:commit_sha)
+
+ commit_shas - signed
+ end
+
def gpg_key=(model)
case model
when GpgKey
diff --git a/app/models/instance_configuration.rb b/app/models/instance_configuration.rb
index 11289887e00..a9b1962f24c 100644
--- a/app/models/instance_configuration.rb
+++ b/app/models/instance_configuration.rb
@@ -39,7 +39,7 @@ class InstanceConfiguration
def gitlab_ci
Settings.gitlab_ci
.to_h
- .merge(artifacts_max_size: { value: Settings.artifacts.max_size&.megabytes,
+ .merge(artifacts_max_size: { value: Gitlab::CurrentSettings.max_artifacts_size.megabytes,
default: 100.megabytes })
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 97c6dcc4745..eb5544f2a12 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -49,10 +49,6 @@ class Issue < ApplicationRecord
scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
- scope :assigned, -> { where('EXISTS (SELECT TRUE FROM issue_assignees WHERE issue_id = issues.id)') }
- scope :unassigned, -> { where('NOT EXISTS (SELECT TRUE FROM issue_assignees WHERE issue_id = issues.id)') }
- scope :assigned_to, ->(u) { where('EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = ? AND issue_id = issues.id)', u.id)}
-
scope :with_due_date, -> { where.not(due_date: nil) }
scope :without_due_date, -> { where(due_date: nil) }
scope :due_before, ->(date) { where('issues.due_date < ?', date) }
@@ -75,8 +71,6 @@ class Issue < ApplicationRecord
attr_spammable :title, spam_title: true
attr_spammable :description, spam_description: true
- participant :assignees
-
state_machine :state, initial: :opened do
event :close do
transition [:opened] => :closed
@@ -90,7 +84,7 @@ class Issue < ApplicationRecord
state :closed
before_transition any => :closed do |issue|
- issue.closed_at = Time.zone.now
+ issue.closed_at = issue.system_note_timestamp
end
before_transition closed: :opened do |issue|
@@ -155,22 +149,6 @@ class Issue < ApplicationRecord
Gitlab::HookData::IssueBuilder.new(self).build
end
- # Returns a Hash of attributes to be used for Twitter card metadata
- def card_attributes
- {
- 'Author' => author.try(:name),
- 'Assignee' => assignee_list
- }
- end
-
- def assignee_or_author?(user)
- author_id == user.id || assignees.exists?(user.id)
- end
-
- def assignee_list
- assignees.map(&:name).to_sentence
- end
-
# `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}"
@@ -230,7 +208,13 @@ class Issue < ApplicationRecord
def visible_to_user?(user = nil)
return false unless project && project.feature_available?(:issues, user)
- user ? readable_by?(user) : publicly_visible?
+ return publicly_visible? unless user
+
+ return false unless readable_by?(user)
+
+ user.full_private_access? ||
+ ::Gitlab::ExternalAuthorization.access_allowed?(
+ user, project.external_authorization_classification_label)
end
def check_for_spam?
@@ -298,7 +282,7 @@ class Issue < ApplicationRecord
# Returns `true` if this Issue is visible to everybody.
def publicly_visible?
- project.public? && !confidential?
+ project.public? && !confidential? && !::Gitlab::ExternalAuthorization.enabled?
end
def expire_etag_cache
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 458c57c1dc6..0a39a720766 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -16,6 +16,7 @@ class MergeRequest < ApplicationRecord
include LabelEventable
include ReactiveCaching
include FromUnion
+ include DeprecatedAssignee
self.reactive_cache_key = ->(model) { [model.project.id, model.iid] }
self.reactive_cache_refresh_interval = 10.minutes
@@ -69,8 +70,7 @@ class MergeRequest < ApplicationRecord
has_many :suggestions, through: :notes
has_many :merge_request_assignees
- # Will be deprecated at https://gitlab.com/gitlab-org/gitlab-ce/issues/59457
- belongs_to :assignee, class_name: "User"
+ has_many :assignees, class_name: "User", through: :merge_request_assignees
serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize
@@ -79,10 +79,6 @@ class MergeRequest < ApplicationRecord
after_update :reload_diff_if_branch_changed
after_save :ensure_metrics
- # Required until the codebase starts using this relation for single or multiple assignees.
- # TODO: Remove at gitlab-ee#2004 implementation.
- after_save :refresh_merge_request_assignees, if: :assignee_id_changed?
-
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
attr_accessor :allow_broken
@@ -188,19 +184,14 @@ class MergeRequest < ApplicationRecord
end
scope :join_project, -> { joins(:target_project) }
scope :references_project, -> { references(:target_project) }
- scope :assigned, -> { where("assignee_id IS NOT NULL") }
- scope :unassigned, -> { where("assignee_id IS NULL") }
- scope :assigned_to, ->(u) { where(assignee_id: u.id)}
scope :with_api_entity_associations, -> {
- preload(:author, :assignee, :notes, :labels, :milestone, :timelogs,
+ preload(:assignees, :author, :notes, :labels, :milestone, :timelogs,
latest_merge_request_diff: [:merge_request_diff_commits],
metrics: [:latest_closed_by, :merged_by],
target_project: [:route, { namespace: :route }],
source_project: [:route, { namespace: :route }])
}
- participant :assignee
-
after_save :keep_around_commit
alias_attribute :project, :target_project
@@ -337,31 +328,6 @@ class MergeRequest < ApplicationRecord
Gitlab::HookData::MergeRequestBuilder.new(self).build
end
- # Returns a Hash of attributes to be used for Twitter card metadata
- def card_attributes
- {
- 'Author' => author.try(:name),
- 'Assignee' => assignee.try(:name)
- }
- end
-
- # These method are needed for compatibility with issues to not mess view and other code
- def assignees
- Array(assignee)
- end
-
- def assignee_ids
- Array(assignee_id)
- end
-
- def assignee_ids=(ids)
- write_attribute(:assignee_id, ids.last)
- end
-
- def assignee_or_author?(user)
- author_id == user.id || assignee_id == user.id
- end
-
# `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}"
@@ -682,15 +648,6 @@ class MergeRequest < ApplicationRecord
merge_request_diff || create_merge_request_diff
end
- def refresh_merge_request_assignees
- transaction do
- # Using it instead relation.delete_all in order to avoid adding a
- # dependent: :delete_all (we already have foreign key cascade deletion).
- MergeRequestAssignee.where(merge_request_id: self).delete_all
- merge_request_assignees.create(user_id: assignee_id) if assignee_id
- end
- end
-
def create_merge_request_diff
fetch_ref!
@@ -1208,7 +1165,7 @@ class MergeRequest < ApplicationRecord
variables.append(key: 'CI_MERGE_REQUEST_PROJECT_URL', value: project.web_url)
variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME', value: target_branch.to_s)
variables.append(key: 'CI_MERGE_REQUEST_TITLE', value: title)
- variables.append(key: 'CI_MERGE_REQUEST_ASSIGNEES', value: assignee.username) if assignee
+ variables.append(key: 'CI_MERGE_REQUEST_ASSIGNEES', value: assignee_username_list) if assignees.any?
variables.append(key: 'CI_MERGE_REQUEST_MILESTONE', value: milestone.title) if milestone
variables.append(key: 'CI_MERGE_REQUEST_LABELS', value: label_names.join(',')) if labels.present?
variables.concat(source_project_variables)
diff --git a/app/models/project.rb b/app/models/project.rb
index e2869fc2ad5..66fc83113ea 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -459,41 +459,14 @@ class Project < ApplicationRecord
# Returns a collection of projects that is either public or visible to the
# logged in user.
- #
- # requested_visiblity_levels: Normally all projects that are visible
- # to the user (e.g. internal and public) are queried, but this
- # parameter allows the caller to narrow the search space to optimize
- # database queries. For instance, a caller may only want to see
- # internal projects. Instead of querying for internal and public
- # projects and throwing away public projects, this parameter allows
- # the query to be targeted for only internal projects.
- def self.public_or_visible_to_user(user = nil, requested_visibility_levels = [])
- return public_to_user unless user
-
- visible_levels = Gitlab::VisibilityLevel.levels_for_user(user)
- include_private = true
- requested_visibility_levels = Array(requested_visibility_levels)
-
- if requested_visibility_levels.present?
- visible_levels &= requested_visibility_levels
- include_private = requested_visibility_levels.include?(Gitlab::VisibilityLevel::PRIVATE)
- end
-
- public_or_internal_rel =
- if visible_levels.present?
- where('projects.visibility_level IN (?)', visible_levels)
- else
- Project.none
- end
-
- private_rel =
- if include_private
- where('EXISTS (?)', user.authorizations_for_projects)
- else
- Project.none
- end
-
- public_or_internal_rel.or(private_rel)
+ def self.public_or_visible_to_user(user = nil)
+ if user
+ where('EXISTS (?) OR projects.visibility_level IN (?)',
+ user.authorizations_for_projects,
+ Gitlab::VisibilityLevel.levels_for_user(user))
+ else
+ public_to_user
+ end
end
# project features may be "disabled", "internal", "enabled" or "public". If "internal",
@@ -674,6 +647,10 @@ class Project < ApplicationRecord
{ scope: :project, status: auto_devops&.enabled || Feature.enabled?(:force_autodevops_on_by_default, self) }
end
+ def multiple_mr_assignees_enabled?
+ Feature.enabled?(:multiple_merge_request_assignees, self)
+ end
+
def daily_statistics_enabled?
Feature.enabled?(:project_daily_statistics, self, default_enabled: true)
end
@@ -2062,6 +2039,11 @@ class Project < ApplicationRecord
fetch_branch_allows_collaboration(user, branch_name)
end
+ def external_authorization_classification_label
+ super || ::Gitlab::CurrentSettings.current_application_settings
+ .external_authorization_service_default_label
+ end
+
def licensed_features
[]
end
diff --git a/app/models/release.rb b/app/models/release.rb
index 746fc31a038..0f9e94373c7 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -15,6 +15,7 @@ class Release < ApplicationRecord
accepts_nested_attributes_for :links, allow_destroy: true
validates :description, :project, :tag, presence: true
+ validates :name, presence: true, on: :create
scope :sorted, -> { order(created_at: :desc) }