September 20, 2022
Recently, we upgraded all neeto products to Rails 7 using Rails upgrade guide.
Here are the issues we faced during the upgrade.
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.
def decrypted_attribute(attribute_name, record)
value = record.send(attribute_name)
return if value.blank?
value = Base64.decode64(value)
cipher = OpenSSL::Cipher.new("aes-256-gcm")
cipher.decrypt
cipher.key = Rails.application.secrets.attr_encrypted[:encryption_key]
cipher.iv = Base64.decode64(record.send(:"#{attribute_name}_iv"))
cipher.auth_tag = value[-16..]
cipher.auth_data = ""
cipher.update(value[0..-17]) + cipher.final
end
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.
config.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.
# Usage:
# text_with_new_links = ActiveStorageKeyConverter.new(text_with_old_links).process
# If no links are there to replace, the original text will be returned as it is.
class ActiveStorageKeyConverter
def initialize(text)
@text = text
end
def process
replace(@text)
end
private
def convert_key(id)
verifier_name = "ActiveStorage"
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(verifier_name.to_s)
verifier = ActiveSupport::MessageVerifier.new(secret)
ActiveStorage::Blob.find_by_id(verifier.verify(id, purpose: :blob_id))
.try(:signed_id) rescue nil
end
def replace(text)
keys = text.scan(URI.regexp).flatten.select{|x|
x.to_s.include? ("rails/active_storage")}.map{|x| x.split("/")[-2]}
keys.each do |key|
new_key = convert_key(key)
text = text.gsub(key, new_key) if new_key
end
text
end
end
Following one time rake task was used to update the Active Storage links in the
content
column of Task
model:
desc "Update active storage links embedded in rich text to support in rails 7"
task migrate_old_activestorage_links: :environment do
table_colum_map = {
"Task" => "content",
}
match_term = "%rails/active_storage%"
table_colum_map.each do |model_name, column_name|
model_name.to_s.constantize.where("#{column_name} ILIKE ?", match_term).find_each do|row|
row.update_column(column_name, ActiveStorageKeyConverter.new(row[column_name]).process)
end
end
end
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.
config.action_mailer.deliver_later_queue_name = :mailers
After the upgrade if we start Rails sever then we were getting the following error.
$ rails s
=> Booting Puma
=> Rails 7.0.3.1 application starting in development
=> Run `bin/rails server --help` for more startup options
Exiting
/Users/BB/Neeto/neeto_commons/lib/neeto_commons/initializers/session_store.rb:13:in
`session_store': uninitialized constant #<Class:NeetoCommons::Initializers>::ServerSideSession (NameError)
ActionDispatch::Session::ActiveRecordStore.session_class = ServerSideSession
^^^^^^^^^^^^^^^^^
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.
#session_store.rb
module NeetoCommons
module Initializers
class << self
def session_store
Rails.application.config.session_store :active_record_store,
key: Rails.application.secrets.session_cookie_name, expire_after: 10.years.to_i
ActiveRecord::SessionStore::Session.table_name = "server_side_sessions"
ActiveRecord::SessionStore::Session.primary_key = "session_id"
ActiveRecord::SessionStore::Session.serializer = :json
ActionDispatch::Session::ActiveRecordStore.session_class = ServerSideSession
end
end
end
end
In order to fix the issue we had to put the last statement in a block like shown below.
Rails.application.config.after_initialize do
ActionDispatch::Session::ActiveRecordStore.session_class = ServerSideSession
end
After the Rails 7 upgrade the following test started failing.
def test_get_task_pdf_download_success
get api_v1_project_section_tasks_download_path(@project.id, @section, @task, format: :pdf)
assert_response :ok
assert response.body.starts_with? "%PDF-1.4"
assert response.body.ends_with? "%EOF\n"
end
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.
format.pdf do
render \
template: "api/v1/projects/tasks/show.html.erb"
pdf: pdf_file_name,
layout: "pdf.html.erb"
end
After the change the code looked like this.
format.pdf do
render \
template: "api/v1/projects/tasks/show",
pdf: pdf_file_name,
layout: "pdf"
end
After the Rails 7 upgrade the following test started failing.
def test_that_users_are_redirected_to_error_url_when_invalid_subdomain_is_entered
invalid_subdomain = "invalid-subdomain"
auth_subdomain_url = URI(app_secrets.auth_app[:url].gsub(
app_secrets.app_subdomain, invalid_subdomain))
auth_app_url = app_secrets.auth_app[:url]
host! test_domain(invalid_subdomain)
get "/"
assert_redirected_to auth_app_url
end
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
.
redirect_to <External URL>, allow_other_host: true
Since we use open redirection in many places we disabled this protection globally.
config.action_controller.raise_on_open_redirects = false
After the upgrade, we started getting the following error in production.
>> ArgumentError: Missing required Mailgun Signing key. Set action_mailbox.mailgun_signing_key
in your application's encrypted credentials or provide the MAILGUN_INGRESS_SIGNING_KEY
environment variable.
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.
If this blog was helpful, check out our full blog archive.