From 4cc9faef4f335239e1c03f5217bbd9d4b38b1a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Wed, 27 May 2026 09:17:57 +0200 Subject: [PATCH] Consistently use OpenProject::SqlSanitization instead of forwarding to sanitize_sql_array --- app/models/actions/scopes/default.rb | 19 +++---- app/models/custom_field/order_statements.rb | 5 +- app/models/groups/hierarchy.rb | 4 +- .../queries/operators/boolean_equals.rb | 2 +- .../operators/boolean_equals_strict.rb | 2 +- .../operators/custom_fields/equals_all.rb | 19 +++---- .../operators/custom_fields/not_equals_all.rb | 19 +++---- app/models/queries/operators/equals.rb | 2 +- app/models/queries/operators/not_equals.rb | 2 +- .../filter/shared_with_user_filter.rb | 15 +++--- app/models/sessions/sql_bypass.rb | 50 ++++++++----------- app/models/version.rb | 6 +-- .../v3/projects/project_sql_representer.rb | 5 +- lib/open_project/full_text_search.rb | 6 +-- lib/open_project/nested_set/rebuild_patch.rb | 5 +- .../meetings/filters/attended_user_filter.rb | 19 +++---- .../meetings/filters/invited_user_filter.rb | 19 +++---- ...in_origin_status_to_file_links_relation.rb | 4 +- .../wikis/page_link_metadata_service.rb | 2 +- 19 files changed, 88 insertions(+), 117 deletions(-) diff --git a/app/models/actions/scopes/default.rb b/app/models/actions/scopes/default.rb index 46a7e9b6c21..aca84cb6659 100644 --- a/app/models/actions/scopes/default.rb +++ b/app/models/actions/scopes/default.rb @@ -59,17 +59,14 @@ module Actions::Scopes def map_actions(permission, actions:, global:, module_name:, grant_to_admin:, public:) actions.map do |namespace, actions| actions.map do |action| - ActiveRecord::Base.send( - :sanitize_sql_array, - [ - "(?, ?, ?, ?, ?, ?)", - "#{action_v3_name(namespace)}/#{action}", - permission, - global, - module_name, - grant_to_admin, - public - ] + OpenProject::SqlSanitization.sanitize( + "(?, ?, ?, ?, ?, ?)", + "#{action_v3_name(namespace)}/#{action}", + permission, + global, + module_name, + grant_to_admin, + public ) end end diff --git a/app/models/custom_field/order_statements.rb b/app/models/custom_field/order_statements.rb index b5260cdaab3..83cf79efb43 100644 --- a/app/models/custom_field/order_statements.rb +++ b/app/models/custom_field/order_statements.rb @@ -113,9 +113,8 @@ module CustomField::OrderStatements # ) cf_order_NNN ON cf_order_NNN.customized_id = … # def join_for_order_sql(value:, add_select: nil, join: nil, multi_value: false) - customized_type_condition = ActiveRecord::Base.send( - :sanitize_sql_array, - ["cv.customized_type = ?", self.class.customized_class.base_class.name] + customized_type_condition = OpenProject::SqlSanitization.sanitize( + "cv.customized_type = ?", self.class.customized_class.base_class.name ) <<~SQL.squish diff --git a/app/models/groups/hierarchy.rb b/app/models/groups/hierarchy.rb index bebe514c554..0e842102a0a 100644 --- a/app/models/groups/hierarchy.rb +++ b/app/models/groups/hierarchy.rb @@ -56,8 +56,8 @@ module Groups::Hierarchy # ancestor_ids are returned child-first by the CTE. # Use array_position to preserve that order, then apply asc/desc. ordered_ids = order == :asc ? ids.reverse : ids - order_sql = self.class.sanitize_sql_array( - ["array_position(ARRAY[?]::bigint[], #{Group.table_name}.id)", ordered_ids] + order_sql = OpenProject::SqlSanitization.sanitize( + "array_position(ARRAY[?]::bigint[], #{Group.table_name}.id)", ordered_ids ) scope.order(Arel.sql(order_sql)) else diff --git a/app/models/queries/operators/boolean_equals.rb b/app/models/queries/operators/boolean_equals.rb index 944c47852c4..cfeebb7df2b 100644 --- a/app/models/queries/operators/boolean_equals.rb +++ b/app/models/queries/operators/boolean_equals.rb @@ -40,7 +40,7 @@ module Queries::Operators sql = "#{db_table}.#{db_field} IS NULL OR " end - sql += ActiveRecord::Base.send(:sanitize_sql_array, ["#{db_table}.#{db_field} IN (?)", values]) + sql += OpenProject::SqlSanitization.sanitize("#{db_table}.#{db_field} IN (?)", values) sql end diff --git a/app/models/queries/operators/boolean_equals_strict.rb b/app/models/queries/operators/boolean_equals_strict.rb index 335a8f73805..bad3c2fb4f2 100644 --- a/app/models/queries/operators/boolean_equals_strict.rb +++ b/app/models/queries/operators/boolean_equals_strict.rb @@ -34,7 +34,7 @@ module Queries::Operators set_symbol "=" def self.sql_for_field(values, db_table, db_field) - ActiveRecord::Base.send(:sanitize_sql_array, ["#{db_table}.#{db_field} IN (?)", values]) + OpenProject::SqlSanitization.sanitize("#{db_table}.#{db_field} IN (?)", values) end end end diff --git a/app/models/queries/operators/custom_fields/equals_all.rb b/app/models/queries/operators/custom_fields/equals_all.rb index 28caf2bc8c1..eca9a9d9d86 100644 --- a/app/models/queries/operators/custom_fields/equals_all.rb +++ b/app/models/queries/operators/custom_fields/equals_all.rb @@ -41,17 +41,14 @@ module Queries::Operators if values.present? sql = values.map do |val| - ActiveRecord::Base.send( - :sanitize_sql_array, - [ - "EXISTS (SELECT 1 FROM #{cv_table} WHERE customized_type = ? " \ - "AND custom_field_id = ? " \ - "AND customized_id = #{customized_id_join_field} " \ - "AND value = ?)", - customized_type, - custom_field_id, - val - ] + OpenProject::SqlSanitization.sanitize( + "EXISTS (SELECT 1 FROM #{cv_table} WHERE customized_type = ? " \ + "AND custom_field_id = ? " \ + "AND customized_id = #{customized_id_join_field} " \ + "AND value = ?)", + customized_type, + custom_field_id, + val ) end diff --git a/app/models/queries/operators/custom_fields/not_equals_all.rb b/app/models/queries/operators/custom_fields/not_equals_all.rb index 77226a2b571..2215498f79a 100644 --- a/app/models/queries/operators/custom_fields/not_equals_all.rb +++ b/app/models/queries/operators/custom_fields/not_equals_all.rb @@ -38,17 +38,14 @@ module Queries::Operators if values.present? sql = values.map do |val| - ActiveRecord::Base.send( - :sanitize_sql_array, - [ - "NOT EXISTS (SELECT 1 FROM #{cv_table} WHERE customized_type = ? " \ - "AND custom_field_id = ? " \ - "AND customized_id = #{customized_id_join_field} " \ - "AND value = ?)", - customized_type, - custom_field_id, - val - ] + OpenProject::SqlSanitization.sanitize( + "NOT EXISTS (SELECT 1 FROM #{cv_table} WHERE customized_type = ? " \ + "AND custom_field_id = ? " \ + "AND customized_id = #{customized_id_join_field} " \ + "AND value = ?)", + customized_type, + custom_field_id, + val ) end diff --git a/app/models/queries/operators/equals.rb b/app/models/queries/operators/equals.rb index 904f395bbd4..d88ca68328e 100644 --- a/app/models/queries/operators/equals.rb +++ b/app/models/queries/operators/equals.rb @@ -44,7 +44,7 @@ module Queries::Operators sql = "#{db_table}.#{db_field} IS NULL OR " end - sql += ActiveRecord::Base.send(:sanitize_sql_array, ["#{db_table}.#{db_field} IN (?)", values]) + sql += OpenProject::SqlSanitization.sanitize("#{db_table}.#{db_field} IN (?)", values) else # empty set of allowed values produces no result sql = "0=1" diff --git a/app/models/queries/operators/not_equals.rb b/app/models/queries/operators/not_equals.rb index 73b758554d0..e33b8a658db 100644 --- a/app/models/queries/operators/not_equals.rb +++ b/app/models/queries/operators/not_equals.rb @@ -38,7 +38,7 @@ module Queries::Operators values = values.map(&:to_s) if values.present? - not_in = ActiveRecord::Base.send(:sanitize_sql_array, ["#{db_table}.#{db_field} NOT IN (?)", values]) + not_in = OpenProject::SqlSanitization.sanitize("#{db_table}.#{db_field} NOT IN (?)", values) "(#{db_table}.#{db_field} IS NULL OR #{not_in})" else # empty set of forbidden values allows all results diff --git a/app/models/queries/work_packages/filter/shared_with_user_filter.rb b/app/models/queries/work_packages/filter/shared_with_user_filter.rb index c9596dcbedd..3b2b026e859 100644 --- a/app/models/queries/work_packages/filter/shared_with_user_filter.rb +++ b/app/models/queries/work_packages/filter/shared_with_user_filter.rb @@ -98,15 +98,12 @@ class Queries::WorkPackages::Filter::SharedWithUserFilter < if normalized_user_id.nil? "1=0" else - ActiveRecord::Base.send( - :sanitize_sql_array, - [<<~SQL.squish, normalized_user_id] - EXISTS (SELECT 1 - FROM #{members_table} - WHERE #{members_table}.entity_id = #{work_packages_table}.id - AND #{members_table}.user_id = ?) - SQL - ) + OpenProject::SqlSanitization.sanitize(<<~SQL.squish, normalized_user_id) + EXISTS (SELECT 1 + FROM #{members_table} + WHERE #{members_table}.entity_id = #{work_packages_table}.id + AND #{members_table}.user_id = ?) + SQL end end diff --git a/app/models/sessions/sql_bypass.rb b/app/models/sessions/sql_bypass.rb index 2d929a0f3d3..77fd521ce75 100644 --- a/app/models/sessions/sql_bypass.rb +++ b/app/models/sessions/sql_bypass.rb @@ -82,40 +82,34 @@ module Sessions def insert! @new_record = false - sql = ActiveRecord::Base.send( - :sanitize_sql_array, - [ - <<~SQL.squish, - INSERT INTO sessions (session_id, data, user_id, updated_at) - VALUES (?, ?, ?, (now() at time zone 'utc')) - SQL - session_id, - self.class.serialize(data), - user_id - ] + sql = OpenProject::SqlSanitization.sanitize( + <<~SQL.squish, + INSERT INTO sessions (session_id, data, user_id, updated_at) + VALUES (?, ?, ?, (now() at time zone 'utc')) + SQL + session_id, + self.class.serialize(data), + user_id ) connection.update sql, "Create session" end def update! - sql = ActiveRecord::Base.send( - :sanitize_sql_array, - [ - <<~SQL.squish, - UPDATE sessions - SET - data = ?, - session_id = ?, - user_id = ?, - updated_at = (now() at time zone 'utc') - WHERE session_id = ? - SQL - self.class.serialize(data), - session_id, - user_id, - @retrieved_by - ] + sql = OpenProject::SqlSanitization.sanitize( + <<~SQL.squish, + UPDATE sessions + SET + data = ?, + session_id = ?, + user_id = ?, + updated_at = (now() at time zone 'utc') + WHERE session_id = ? + SQL + self.class.serialize(data), + session_id, + user_id, + @retrieved_by ) connection.update sql, "Update session" diff --git a/app/models/version.rb b/app/models/version.rb index 801f5a37ef8..32349f211f8 100644 --- a/app/models/version.rb +++ b/app/models/version.rb @@ -222,15 +222,15 @@ class Version < ApplicationRecord # Examples: # issues_progress(true) => returns the progress percentage for open issues. # issues_progress(false) => returns the progress percentage for closed issues. - def issues_progress(open) + def issues_progress(open) # rubocop:disable Metrics/AbcSize @issues_progress ||= {} @issues_progress[open] ||= begin progress = 0 if issues_count > 0 ratio = open ? "done_ratio" : 100 - sum_sql = self.class.sanitize_sql_array( - ["COALESCE(#{WorkPackage.table_name}.estimated_hours, ?) * #{ratio}", estimated_average] + sum_sql = OpenProject::SqlSanitization.sanitize( + "COALESCE(#{WorkPackage.table_name}.estimated_hours, ?) * #{ratio}", estimated_average ) done = work_packages diff --git a/lib/api/v3/projects/project_sql_representer.rb b/lib/api/v3/projects/project_sql_representer.rb index 16e4da9a1f7..60cd2956e00 100644 --- a/lib/api/v3/projects/project_sql_representer.rb +++ b/lib/api/v3/projects/project_sql_representer.rb @@ -73,9 +73,8 @@ module API end def ancestor_projection - undisclosed_ancestor_title = ActiveRecord::Base.send( - :sanitize_sql_array, - ["?", I18n.t(:"api_v3.undisclosed.ancestor")] + undisclosed_ancestor_title = OpenProject::SqlSanitization.sanitize( + "?", I18n.t(:"api_v3.undisclosed.ancestor") ) if User.current.admin? diff --git a/lib/open_project/full_text_search.rb b/lib/open_project/full_text_search.rb index ad78f27579e..6d56134f111 100644 --- a/lib/open_project/full_text_search.rb +++ b/lib/open_project/full_text_search.rb @@ -39,10 +39,8 @@ module OpenProject language = OpenProject::Configuration.main_content_language - ActiveRecord::Base.send( - :sanitize_sql_array, ["#{column} @@ to_tsquery(?, ?)", - language, - query] + OpenProject::SqlSanitization.sanitize( + "#{column} @@ to_tsquery(?, ?)", language, query ) end end diff --git a/lib/open_project/nested_set/rebuild_patch.rb b/lib/open_project/nested_set/rebuild_patch.rb index e8e4ce4ab22..19a3f168fbb 100644 --- a/lib/open_project/nested_set/rebuild_patch.rb +++ b/lib/open_project/nested_set/rebuild_patch.rb @@ -55,9 +55,8 @@ module OpenProject::NestedSet::RebuildPatch if acts_as_nested_set_options[:scope] scope = lambda { |node| scope_column_names.inject("") do |str, column_name| - str << ActiveRecord::Base.send( - :sanitize_sql_array, - ["AND #{connection.quote_column_name(column_name)} = ? ", node.send(column_name.to_sym)] + str << OpenProject::SqlSanitization.sanitize( + "AND #{connection.quote_column_name(column_name)} = ? ", node.send(column_name.to_sym) ) end } diff --git a/modules/meeting/app/models/queries/meetings/filters/attended_user_filter.rb b/modules/meeting/app/models/queries/meetings/filters/attended_user_filter.rb index be31061a9c4..43ed65f7079 100644 --- a/modules/meeting/app/models/queries/meetings/filters/attended_user_filter.rb +++ b/modules/meeting/app/models/queries/meetings/filters/attended_user_filter.rb @@ -48,17 +48,14 @@ class Queries::Meetings::Filters::AttendedUserFilter < Queries::Meetings::Filter user_id = normalized_user_id(values.first) return "1=1" if user_id.nil? - ActiveRecord::Base.send( - :sanitize_sql_array, - [<<~SQL.squish, user_id] - NOT EXISTS ( - SELECT 1 FROM #{MeetingParticipant.table_name} - WHERE #{MeetingParticipant.table_name}.meeting_id = meetings.id - AND #{MeetingParticipant.table_name}.user_id = ? - AND #{condition} - ) - SQL - ) + OpenProject::SqlSanitization.sanitize(<<~SQL.squish, user_id) + NOT EXISTS ( + SELECT 1 FROM #{MeetingParticipant.table_name} + WHERE #{MeetingParticipant.table_name}.meeting_id = meetings.id + AND #{MeetingParticipant.table_name}.user_id = ? + AND #{condition} + ) + SQL when "*" ["#{MeetingParticipant.table_name}.user_id IS NOT NULL", condition].join(" AND ") when "!*" diff --git a/modules/meeting/app/models/queries/meetings/filters/invited_user_filter.rb b/modules/meeting/app/models/queries/meetings/filters/invited_user_filter.rb index 53cb584bf35..b0fe1fdc457 100644 --- a/modules/meeting/app/models/queries/meetings/filters/invited_user_filter.rb +++ b/modules/meeting/app/models/queries/meetings/filters/invited_user_filter.rb @@ -48,17 +48,14 @@ class Queries::Meetings::Filters::InvitedUserFilter < Queries::Meetings::Filters user_id = normalized_user_id(values.first) return "1=1" if user_id.nil? - ActiveRecord::Base.send( - :sanitize_sql_array, - [<<~SQL.squish, user_id] - NOT EXISTS ( - SELECT 1 FROM #{MeetingParticipant.table_name} - WHERE #{MeetingParticipant.table_name}.meeting_id = meetings.id - AND #{MeetingParticipant.table_name}.user_id = ? - AND #{condition} - ) - SQL - ) + OpenProject::SqlSanitization.sanitize(<<~SQL.squish, user_id) + NOT EXISTS ( + SELECT 1 FROM #{MeetingParticipant.table_name} + WHERE #{MeetingParticipant.table_name}.meeting_id = meetings.id + AND #{MeetingParticipant.table_name}.user_id = ? + AND #{condition} + ) + SQL when "*" ["#{MeetingParticipant.table_name}.user_id IS NOT NULL", condition].join(" AND ") when "!*" diff --git a/modules/storages/lib/api/v3/file_links/join_origin_status_to_file_links_relation.rb b/modules/storages/lib/api/v3/file_links/join_origin_status_to_file_links_relation.rb index 926c9fa9746..334c1cfe87b 100644 --- a/modules/storages/lib/api/v3/file_links/join_origin_status_to_file_links_relation.rb +++ b/modules/storages/lib/api/v3/file_links/join_origin_status_to_file_links_relation.rb @@ -35,8 +35,8 @@ module API # @param [Hash] id_status_map A hash mapping file link IDs to their origin status # in the format { 137: "view_allowed", 142: "error" } def self.create(id_status_map) - sanitized_sql = ActiveRecord::Base.sanitize_sql_array( - [origin_status_join(id_status_map.size), *id_status_map.flatten] + sanitized_sql = OpenProject::SqlSanitization.sanitize( + origin_status_join(id_status_map.size), *id_status_map.flatten ) ::Storages::FileLink.where(id: id_status_map.keys) diff --git a/modules/wikis/app/services/wikis/page_link_metadata_service.rb b/modules/wikis/app/services/wikis/page_link_metadata_service.rb index 7aa6d8c993a..659b99dfeec 100644 --- a/modules/wikis/app/services/wikis/page_link_metadata_service.rb +++ b/modules/wikis/app/services/wikis/page_link_metadata_service.rb @@ -68,7 +68,7 @@ module Wikis ON metadata.identifier = wiki_page_links.identifier AND metadata.provider_id = wiki_page_links.provider_id SQL - join_expression = ActiveRecord::Base.sanitize_sql_array([join_string, *identifier_title_map.flatten]) + join_expression = OpenProject::SqlSanitization.sanitize(join_string, *identifier_title_map.flatten) relation.joins(join_expression).select("wiki_page_links.*, metadata.title as title") end