diff --git a/app/models/communication/website.rb b/app/models/communication/website.rb index d224b0bf145724d93796d34e820c7e9e2725687c..2022004ccb846dcab17e96f812181e45fec206e3 100644 --- a/app/models/communication/website.rb +++ b/app/models/communication/website.rb @@ -83,16 +83,16 @@ class Communication::Website < ApplicationRecord def git_dependencies(website) dependencies = [ self, - config_default_languages, - config_default_permalinks, - config_development_config, + config_default_languages, + config_default_permalinks, + config_development_config, config_production_config ] + menus - dependencies += pages + pages.includes(parent: { featured_image_attachment: :blob }, featured_image_attachment: :blob).map(&:active_storage_blobs).flatten - dependencies += posts + posts.includes(featured_image_attachment: :blob).map(&:active_storage_blobs).flatten - dependencies += people_with_facets + people.map(&:active_storage_blobs).flatten - dependencies += organizations_in_blocks + organizations_in_blocks.map(&:active_storage_blobs).flatten - dependencies += categories + dependencies += pages + pages.includes(parent: { featured_image_attachment: :blob }, featured_image_attachment: :blob).map(&:active_storage_blobs).flatten + pages.map(&:git_block_dependencies).flatten + dependencies += posts + posts.includes(featured_image_attachment: :blob).map(&:active_storage_blobs).flatten + posts.map(&:git_block_dependencies).flatten + dependencies += people_with_facets + people.map(&:active_storage_blobs).flatten + people.map(&:git_block_dependencies).flatten + dependencies += organizations_in_blocks + organizations_in_blocks.map(&:active_storage_blobs).flatten + organizations_in_blocks.map(&:git_block_dependencies).flatten + dependencies += categories + categories.map(&:git_block_dependencies).flatten dependencies += about.git_dependencies(website) if about.present? dependencies end diff --git a/app/models/education/school.rb b/app/models/education/school.rb index 18c4dcc12f3e5604a8bc9a76a505468fbc4f7076..14dbac9262e182359b73054c93cb1fca756bf071 100644 --- a/app/models/education/school.rb +++ b/app/models/education/school.rb @@ -68,8 +68,10 @@ class Education::School < ApplicationRecord def git_dependencies(website) dependencies = [self] dependencies += programs + - programs.map(&:active_storage_blobs).flatten - dependencies += diplomas + programs.map(&:active_storage_blobs).flatten + + programs.map(&:git_block_dependencies).flatten + dependencies += diplomas + + diplomas.map(&:git_block_dependencies).flatten dependencies += teachers + teachers.map(&:teacher) + teachers.map(&:active_storage_blobs).flatten diff --git a/config/application.rb b/config/application.rb index 88f89b56b81bfdbb1f1bf3b1b1f1c9fa7e68e401..03280c1194e0a00bbcdb9e3dee46db8cfa04474e 100644 --- a/config/application.rb +++ b/config/application.rb @@ -53,8 +53,6 @@ module Osuny # Need for +repage, because of https://github.com/rails/rails/commit/b2ab8dd3a4a184f3115e72b55c237c7b66405bd9 config.active_storage.supported_image_processing_methods = ["+"] - config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::SHA1 - config.action_view.sanitized_allowed_tags = [ "a", "abbr", "acronym", "address", "b", "big", "blockquote", "br", "cite", "code", "dd", "del", "dfn", "div", "dl", "dt", "em", diff --git a/db/migrate/20230106132654_migrate_to_rails7_sha256_signature.rb b/db/migrate/20230106132654_migrate_to_rails7_sha256_signature.rb new file mode 100644 index 0000000000000000000000000000000000000000..be9ae9c9b6d70956b09b540cfead5c080f099f08 --- /dev/null +++ b/db/migrate/20230106132654_migrate_to_rails7_sha256_signature.rb @@ -0,0 +1,53 @@ +class MigrateToRails7Sha256Signature < ActiveRecord::Migration[7.0] + def up + Communication::Block.all.find_each { |block| + crawl(block.data) + block.save + } + end + + protected + + def crawl(enumerable) + case enumerable + when Array + enumerable.each do |item| + crawl(item) if [Array, Hash].include?(item.class) + end + when Hash + enumerable.keys.each do |key| + if key == "signed_id" + # Convert value + enumerable[key] = convert(enumerable[key]) if key == "signed_id" + elsif [Array, Hash].include?(enumerable[key].class) + crawl(enumerable[key]) + end + end + end + end + + def convert(legacy_signed_id) + begin + # Try to find blob with the un-modified legacy_signed_id + blob = ActiveStorage::Blob.find_signed!(legacy_signed_id) + legacy_signed_id + rescue ActiveSupport::MessageVerifier::InvalidSignature + begin + # Try to find blob with ID from SHA1-signed_id + key_generator = ActiveSupport::KeyGenerator.new( + Rails.application.secrets.secret_key_base, + iterations: 1000, + hash_digest_class: OpenSSL::Digest::SHA1 + ) + key_generator = ActiveSupport::CachingKeyGenerator.new(key_generator) + secret = key_generator.generate_key("ActiveStorage") + verifier = ActiveSupport::MessageVerifier.new(secret) + + ActiveStorage::Blob.find_by_id(verifier.verify(legacy_signed_id, purpose: :blob_id)).try(:signed_id) + rescue ActiveSupport::MessageVerifier::InvalidSignature + # Blob not found (SHA1 and SHA256), corrupted blob ID, ignore + legacy_signed_id + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 5e200f19fba5693b35c3a915b70055fc04e56322..0afdf7c295968f61853ebb0f5141d292a431fee6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_01_06_094946) do +ActiveRecord::Schema[7.0].define(version: 2023_01_06_132654) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" diff --git a/docs/_rd/digest_upgrade.md b/docs/_rd/digest_upgrade.md new file mode 100644 index 0000000000000000000000000000000000000000..d4bbad530fcdf8219f566cf048c3a76f2f4e0dc0 --- /dev/null +++ b/docs/_rd/digest_upgrade.md @@ -0,0 +1,84 @@ +# Mise à jour de l'algorithme de génération de clés + +## Préambule + +Avant Rails 7, le générateur de clés `ActiveSupport::KeyGenerator` utilisait l'algorithme SHA1 pour chiffrer des messages. Ce générateur est utilisé dans de multiples cas : +- Les cookies chiffrés (*encrypted cookies*) +- Les attributs chiffrés (*encrypted attributes*) (*Non utilisé par Osuny*) +- Les IDs signés des objets, notamment les blobs d'ActiveStorage +- Les ETags et les clés de cache + +A partir de Rails 7, le générateur de clés utilise SHA256 comme algorithme par défaut, ce qui casse les messages listés précédemment. Pas de vrai problème pour les cookies, les Etags et les clés de cache. Cependant, chaque ID signé de blobs créé avec SHA1 est désormais invalide. Ce qui pose problème dans les blocs qui stockent cet ID signé dans le data JSON ou encore dans l'attribut `direct_url` des fichiers médias des sites web créés avec Osuny. + +Pour les fichiers médias, une mise à jour des sites web va actualiser sans problème. Cependant, il faut un script pour mettre à jour les IDs signés dans les data JSON des blocs. + +## Classe du convertisseur + +### Définition + +```ruby +class ActiveStorageKeyConverter + def self.convert(legacy_signed_id) + begin + # Try to find blob with the un-modified legacy_signed_id + blob = ActiveStorage::Blob.find_signed!(legacy_signed_id) + legacy_signed_id + rescue ActiveSupport::MessageVerifier::InvalidSignature + begin + # Try to find blob with ID from SHA1-signed_id + key_generator = ActiveSupport::KeyGenerator.new( + Rails.application.secrets.secret_key_base, + iterations: 1000, + hash_digest_class: OpenSSL::Digest::SHA1 + ) + key_generator = ActiveSupport::CachingKeyGenerator.new(key_generator) + secret = key_generator.generate_key("ActiveStorage") + verifier = ActiveSupport::MessageVerifier.new(secret) + + ActiveStorage::Blob.find_by_id(verifier.verify(legacy_signed_id, purpose: :blob_id)).try(:signed_id) + rescue ActiveSupport::MessageVerifier::InvalidSignature + # Blob not found (SHA1 and SHA256), corrupted blob ID, ignore + legacy_signed_id + end + end + end +end +``` + +### Utilisation + +```ruby +ActiveStorageKeyConverter.convert legacy_signed_id +``` + +## Script pour les Communication::Blocks + +```ruby + +def crawl(enumerable) + case enumerable + when Array + enumerable.each do |item| + crawl(item) if [Array, Hash].include?(item.class) + end + when Hash + enumerable.keys.each do |key| + if key == "signed_id" + # Convert value + enumerable[key] = ActiveStorageKeyConverter.convert(enumerable[key]) if key == "signed_id" + elsif [Array, Hash].include?(enumerable[key].class) + crawl(enumerable[key]) + end + end + end +end + +Communication::Block.all.find_each { |block| + crawl(block.data) + block.save +} +``` + +## Sources + +https://www.bigbinary.com/blog/how-we-upgraded-from-rails-6-to-rails-7