diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-20 13:00:54 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-20 13:00:54 +0300 |
commit | 3cccd102ba543e02725d247893729e5c73b38295 (patch) | |
tree | f36a04ec38517f5deaaacb5acc7d949688d1e187 /app/models/integration.rb | |
parent | 205943281328046ef7b4528031b90fbda70c75ac (diff) |
Add latest changes from gitlab-org/gitlab@14-10-stable-eev14.10.0-rc42
Diffstat (limited to 'app/models/integration.rb')
-rw-r--r-- | app/models/integration.rb | 98 |
1 files changed, 61 insertions, 37 deletions
diff --git a/app/models/integration.rb b/app/models/integration.rb index 274c16507b7..c0e244e38b6 100644 --- a/app/models/integration.rb +++ b/app/models/integration.rb @@ -10,9 +10,11 @@ class Integration < ApplicationRecord include FromUnion include EachBatch include IgnorableColumns + extend ::Gitlab::Utils::Override ignore_column :template, remove_with: '15.0', remove_after: '2022-04-22' ignore_column :type, remove_with: '15.0', remove_after: '2022-04-22' + ignore_column :properties, remove_with: '15.1', remove_after: '2022-05-22' UnknownType = Class.new(StandardError) @@ -47,10 +49,7 @@ class Integration < ApplicationRecord SECTION_TYPE_CONNECTION = 'connection' - serialize :properties, JSON # rubocop:disable Cop/ActiveRecordSerialize - - attr_encrypted :encrypted_properties_tmp, - attribute: :encrypted_properties, + attr_encrypted :properties, mode: :per_attribute_iv, key: Settings.attr_encrypted_db_key_base_32, algorithm: 'aes-256-gcm', @@ -59,6 +58,15 @@ class Integration < ApplicationRecord encode: false, encode_iv: false + # Handle assignment of props with symbol keys. + # To do this correctly, we need to call the method generated by attr_encrypted. + alias_method :attr_encrypted_props=, :properties= + private :attr_encrypted_props= + + def properties=(props) + self.attr_encrypted_props = props&.with_indifferent_access&.freeze + end + alias_attribute :type, :type_new default_value_for :active, false @@ -77,8 +85,6 @@ class Integration < ApplicationRecord default_value_for :wiki_page_events, true after_initialize :initialize_properties - after_initialize :copy_properties_to_encrypted_properties - before_save :copy_properties_to_encrypted_properties after_commit :reset_updated_properties @@ -96,6 +102,9 @@ class Integration < ApplicationRecord validate :validate_belongs_to_project_or_group scope :external_issue_trackers, -> { where(category: 'issue_tracker').active } + # TODO: Will be modified in 15.0 + # Details: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74501#note_744393645 + scope :third_party_wikis, -> { where(type: %w[Integrations::Confluence Integrations::Shimo]).active } scope :by_name, ->(name) { by_type(integration_name_to_type(name)) } scope :external_wikis, -> { by_name(:external_wiki).active } scope :active, -> { where(active: true) } @@ -162,16 +171,14 @@ class Integration < ApplicationRecord class_eval <<~RUBY, __FILE__, __LINE__ + 1 unless method_defined?(arg) def #{arg} - properties['#{arg}'] + properties['#{arg}'] if properties.present? end end def #{arg}=(value) self.properties ||= {} - self.encrypted_properties_tmp = properties updated_properties['#{arg}'] = #{arg} unless #{arg}_changed? - self.properties['#{arg}'] = value - self.encrypted_properties_tmp['#{arg}'] = value + self.properties = self.properties.merge('#{arg}' => value) end def #{arg}_changed? @@ -192,11 +199,13 @@ class Integration < ApplicationRecord # Provide convenient boolean accessor methods for each serialized property. # Also keep track of updated properties in a similar way as ActiveModel::Dirty def self.boolean_accessor(*args) - self.prop_accessor(*args) + prop_accessor(*args) args.each do |arg| class_eval <<~RUBY, __FILE__, __LINE__ + 1 def #{arg} + return if properties.blank? + Gitlab::Utils.to_boolean(properties['#{arg}']) end @@ -315,18 +324,31 @@ class Integration < ApplicationRecord def self.build_from_integration(integration, project_id: nil, group_id: nil) new_integration = integration.dup - if integration.supports_data_fields? - data_fields = integration.data_fields.dup - data_fields.integration = new_integration - end - new_integration.instance = false new_integration.project_id = project_id new_integration.group_id = group_id - new_integration.inherit_from_id = integration.id if integration.instance_level? || integration.group_level? + new_integration.inherit_from_id = integration.id if integration.inheritable? new_integration end + # Duplicating an integration also duplicates the data fields. Duped records have different ciphertexts. + override :dup + def dup + new_integration = super + new_integration.assign_attributes(reencrypt_properties) + + if supports_data_fields? + fields = data_fields.dup + fields.integration = new_integration + end + + new_integration + end + + def inheritable? + instance_level? || group_level? + end + def self.instance_exists_for?(type) exists?(instance: true, type: type) end @@ -350,16 +372,17 @@ class Integration < ApplicationRecord end private_class_method :instance_level_integration - def self.create_from_active_default_integrations(scope, association) - group_ids = sorted_ancestors(scope).select(:id) + # Returns the number of successfully saved integrations + # Duplicate integrations are excluded from this count by their validations. + def self.create_from_active_default_integrations(owner, association) + group_ids = sorted_ancestors(owner).select(:id) array = group_ids.to_sql.present? ? "array(#{group_ids.to_sql})" : 'ARRAY[]' + order = Arel.sql("type_new ASC, array_position(#{array}::bigint[], #{table_name}.group_id), instance DESC") - from_union([ - active.where(instance: true), - active.where(group_id: group_ids, inherit_from_id: nil) - ]).order(Arel.sql("type_new ASC, array_position(#{array}::bigint[], #{table_name}.group_id), instance DESC")).group_by(&:type).each do |type, records| - build_from_integration(records.first, association => scope.id).save - end + from_union([active.where(instance: true), active.where(group_id: group_ids, inherit_from_id: nil)]) + .order(order) + .group_by(&:type) + .count { |type, parents| build_from_integration(parents.first, association => owner.id).save } end def self.inherited_descendants_from_self_or_ancestors_from(integration) @@ -398,13 +421,7 @@ class Integration < ApplicationRecord end def initialize_properties - self.properties = {} if has_attribute?(:properties) && properties.nil? - end - - def copy_properties_to_encrypted_properties - self.encrypted_properties_tmp = properties - rescue ActiveModel::MissingAttributeError - # ignore - in a record built from using a restricted select list + self.properties = {} if has_attribute?(:encrypted_properties) && encrypted_properties.nil? end def title @@ -428,7 +445,9 @@ class Integration < ApplicationRecord [] end - def password_fields + # TODO: Once all integrations use `Integrations::Field` we can + # use `#secret?` here. + def secret_fields fields.select { |f| f[:type] == 'password' }.pluck(:name) end @@ -439,21 +458,26 @@ class Integration < ApplicationRecord %w[active] end + # properties is always nil - ignore it. + override :attributes + def attributes + super.except('properties') + end + # return a hash of columns => values suitable for passing to insert_all def to_integration_hash column = self.class.attribute_aliases.fetch('type', 'type') - copy_properties_to_encrypted_properties - as_json(except: %w[id instance project_id group_id encrypted_properties_tmp]) + as_json(except: %w[id instance project_id group_id]) .merge(column => type) .merge(reencrypt_properties) end def reencrypt_properties unless properties.nil? || properties.empty? - alg = self.class.encrypted_attributes[:encrypted_properties_tmp][:algorithm] + alg = self.class.encrypted_attributes[:properties][:algorithm] iv = generate_iv(alg) - ep = self.class.encrypt(:encrypted_properties_tmp, properties, { iv: iv }) + ep = self.class.encrypt(:properties, properties, { iv: iv }) end { 'encrypted_properties' => ep, 'encrypted_properties_iv' => iv } |