Recently, we upgraded all neeto products to Rails 7 using Rails upgrade guide.
Here are the issues we faced during the upgrade.
Migrating to Active Record Encryption
This was the biggest challenge we faced during the upgrade. For encrypting columns, we had used the attr_encrypted gem. However Rails 7 came with Active Record Encryption. So we needed to decrypt the records in the production database and encrypt them using Active Record Encryption. We found that "attr_encrypted" gem was incompatible with Rails 7. So the only option was to remove the "attr_encrypted" gem and decrypt the records using a script. We used the following method to decrypt the records.
1def decrypted_attribute(attribute_name, record) 2 value = record.send(attribute_name) 3 return if value.blank? 4 5 value = Base64.decode64(value) 6 7 cipher = OpenSSL::Cipher.new("aes-256-gcm") 8 cipher.decrypt 9 10 cipher.key = Rails.application.secrets.attr_encrypted[:encryption_key] 11 cipher.iv = Base64.decode64(record.send(:"#{attribute_name}_iv")) 12 13 cipher.auth_tag = value[-16..] 14 cipher.auth_data = "" 15 16 cipher.update(value[0..-17]) + cipher.final 17end
Broken images in Active Storage
After the upgrade, we started getting broken images in some places. This happened for Active Storage links embedded in Rich Text. After some debugging we found that we were getting incorrect Active Storage links because of a change in the key generation algorithm. The following configuration was loading images using the old algorithm.
1config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::SHA1
Since the new algorithm provides more security, we decided to migrate the links instead of using the old algorithm. We used the following code to migrate the old link to the new valid link.
1# Usage: 2# text_with_new_links = ActiveStorageKeyConverter.new(text_with_old_links).process 3# If no links are there to replace, the original text will be returned as it is. 4 5class ActiveStorageKeyConverter 6 def initialize(text) 7 @text = text 8 end 9 10 def process 11 replace(@text) 12 end 13 14 private 15 def convert_key(id) 16 verifier_name = "ActiveStorage" 17 key_generator = ActiveSupport::KeyGenerator.new(Rails.application.secrets. 18 secret_key_base, iterations: 1000, hash_digest_class: OpenSSL::Digest::SHA1) 19 key_generator = ActiveSupport::CachingKeyGenerator.new(key_generator) 20 secret = key_generator.generate_key(verifier_name.to_s) 21 verifier = ActiveSupport::MessageVerifier.new(secret) 22 23 ActiveStorage::Blob.find_by_id(verifier.verify(id, purpose: :blob_id)) 24 .try(:signed_id) rescue nil 25 end 26 27 def replace(text) 28 keys = text.scan(URI.regexp).flatten.select{|x| 29 x.to_s.include? ("rails/active_storage")}.map{|x| x.split("/")[-2]} 30 keys.each do |key| 31 new_key = convert_key(key) 32 text = text.gsub(key, new_key) if new_key 33 end 34 text 35 end 36end
Following one time rake task was used to update the Active Storage links in the content column of Task model:
1desc "Update active storage links embedded in rich text to support in rails 7" 2task migrate_old_activestorage_links: :environment do 3 4 table_colum_map = { 5 "Task" => "content", 6 } 7 match_term = "%rails/active_storage%" 8 9 table_colum_map.each do |model_name, column_name| 10 model_name.to_s.constantize.where("#{column_name} ILIKE ?", match_term).find_each do|row| 11 row.update_column(column_name, ActiveStorageKeyConverter.new(row[column_name]).process) 12 end 13 end 14end
Test failures with the mailer jobs
After upgrading to Rails 7, tests related to mailers started to fail. This was because the mailer jobs were enqueued in the default queue instead of mailers. We fixed this by adding the following configuration.
1config.action_mailer.deliver_later_queue_name = :mailers
Autoloading during initialization failed
After the upgrade if we start Rails sever then we were getting the following error.
1$ rails s 2=> Booting Puma 3=> Rails 7.0.3.1 application starting in development 4=> Run `bin/rails server --help` for more startup options 5Exiting 6/Users/BB/Neeto/neeto_commons/lib/neeto_commons/initializers/session_store.rb:13:in 7`session_store': uninitialized constant #<Class:NeetoCommons::Initializers>::ServerSideSession (NameError) 8 9 ActionDispatch::Session::ActiveRecordStore.session_class = ServerSideSession 10 ^^^^^^^^^^^^^^^^^ 11 from /Users/BB/Neeto/neeto-planner-web/config/initializers/common.rb:10:in `<main>'
That error was coming from our internal neeto-commons initializer called session_store.rb. The code looked like this.
1#session_store.rb 2 3module NeetoCommons 4 module Initializers 5 class << self 6 def session_store 7 Rails.application.config.session_store :active_record_store, 8 key: Rails.application.secrets.session_cookie_name, expire_after: 10.years.to_i 9 10 ActiveRecord::SessionStore::Session.table_name = "server_side_sessions" 11 ActiveRecord::SessionStore::Session.primary_key = "session_id" 12 ActiveRecord::SessionStore::Session.serializer = :json 13 ActionDispatch::Session::ActiveRecordStore.session_class = ServerSideSession 14 end 15 end 16 end 17end
In order to fix the issue we had to put the last statement in a block like shown below.
1Rails.application.config.after_initialize do 2 ActionDispatch::Session::ActiveRecordStore.session_class = ServerSideSession 3end
Missing template error with pdf render
After the Rails 7 upgrade the following test started failing.
1def test_get_task_pdf_download_success 2 get api_v1_project_section_tasks_download_path(@project.id, @section, @task, format: :pdf) 3 4 assert_response :ok 5 6 assert response.body.starts_with? "%PDF-1.4" 7 assert response.body.ends_with? "%EOF\n" 8end
The actual error is Missing template api/v1/projects/tasks/show.html.erb.
In order to fix it we renamed the file name from /tasks/show.html.erb to /tasks/show.pdf.erb. Similarly we changed the layout from /layouts/pdf.html.erb to /layouts/pdf.pdf.erb.
Initially the controller code looked like this.
1format.pdf do 2 render \ 3 template: "api/v1/projects/tasks/show.html.erb" 4 pdf: pdf_file_name, 5 layout: "pdf.html.erb" 6end
After the change the code looked like this.
1format.pdf do 2 render \ 3 template: "api/v1/projects/tasks/show", 4 pdf: pdf_file_name, 5 layout: "pdf" 6end
Open Redirect protection
After the Rails 7 upgrade the following test started failing.
1def test_that_users_are_redirected_to_error_url_when_invalid_subdomain_is_entered 2 invalid_subdomain = "invalid-subdomain" 3 4 auth_subdomain_url = URI(app_secrets.auth_app[:url].gsub( 5 app_secrets.app_subdomain, invalid_subdomain)) 6 7 auth_app_url = app_secrets.auth_app[:url] 8 9 host! test_domain(invalid_subdomain) 10 get "/" 11 12 assert_redirected_to auth_app_url 13end
In the test we are expecting the application to redirect to auth_app_url but we are getting UnsafeRedirectError error for open redirections. In Rails 7 the new Rails defaults protects applications against the Open Redirect Vulnerability.
To allow any external redirects we can pass allow_other_host: true.
1redirect_to <External URL>, allow_other_host: true
Since we use open redirection in many places we disabled this protection globally.
1config.action_controller.raise_on_open_redirects = false
Argument Error for Mailgun signing key
After the upgrade, we started getting the following error in production.
1>> ArgumentError: Missing required Mailgun Signing key. Set action_mailbox.mailgun_signing_key 2in your application's encrypted credentials or provide the MAILGUN_INGRESS_SIGNING_KEY 3environment variable. 4
Before Rails 7, we used the MAILGUN_INGRESS_API_KEY environment variable to set up the Mailgun signing key . In Rails 7, that is changed to MAILGUN_INGRESS_SIGNING_KEY. So we renamed the environment variable to fix the problem.