DEV: Migrate existing cookies to Rails 7 format

This patch introduces a cookies rotator as indicated in the Rails
upgrade guide. This allows to migrate from the old SHA1 digest to the
new SHA256 digest.
This commit is contained in:
Loïc Guitaut 2022-05-19 16:58:31 +02:00 committed by Loïc Guitaut
parent 1a759fd75f
commit 66e8fe9cc6
4 changed files with 64 additions and 4 deletions

View File

@ -171,6 +171,9 @@ module Discourse
require "middleware/discourse_public_exceptions"
config.exceptions_app = Middleware::DiscoursePublicExceptions.new(Rails.public_path)
require "middleware/cookies_rotator"
config.middleware.insert_before ActionDispatch::Cookies, Middleware::CookiesRotator
require "discourse_js_processor"
require "discourse_sourcemapping_url_processor"

View File

@ -25,7 +25,7 @@ Rails.application.config.action_view.apply_stylesheet_media_default = false
#
# See upgrading guide for more information on how to build a rotator.
# https://guides.rubyonrails.org/v7.0/upgrading_ruby_on_rails.html
# Rails.application.config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::SHA256
Rails.application.config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::SHA256
# Change the digest class for ActiveSupport::Digest.
# Changing this default means that for example Etags change and

View File

@ -0,0 +1,34 @@
# frozen_string_literal: true
# Implementing cookies rotator for Rails 7+ as a middleware because this will
# work in single site mode AND in multisite mode without leaking anything in
# `Rails.application.config.action_dispatch.cookies_rotations`.
module Middleware
class CookiesRotator
def initialize(app)
@app = app
end
def call(env)
request = ActionDispatch::Request.new(env)
env[
ActionDispatch::Cookies::COOKIES_ROTATIONS
] = ActiveSupport::Messages::RotationConfiguration.new.tap do |cookies|
key_generator =
ActiveSupport::KeyGenerator.new(
request.secret_key_base,
iterations: 1000,
hash_digest_class: OpenSSL::Digest::SHA1,
)
key_len = ActiveSupport::MessageEncryptor.key_len
cookies.rotate(
:encrypted,
key_generator.generate_key(request.authenticated_encrypted_cookie_salt, key_len),
)
cookies.rotate(:signed, key_generator.generate_key(request.signed_cookie_salt))
end
@app.call(env)
end
end
end

View File

@ -1,9 +1,11 @@
# frozen_string_literal: true
RSpec.describe "multisite", type: %i[multisite request] do
let!(:first_host) { get "http://test.localhost/session/csrf.json" }
it "works" do
get "http://test.localhost/session/csrf.json"
expect(response.status).to eq(200)
expect(response).to have_http_status :ok
cookie = CGI.escape(response.cookies["_forum_session"])
id1 = session["session_id"]
@ -11,7 +13,7 @@ RSpec.describe "multisite", type: %i[multisite request] do
headers: {
"Cookie" => "_forum_session=#{cookie};",
}
expect(response.status).to eq(200)
expect(response).to have_http_status :ok
id2 = session["session_id"]
expect(id1).to eq(id2)
@ -20,10 +22,31 @@ RSpec.describe "multisite", type: %i[multisite request] do
headers: {
"Cookie" => "_forum_session=#{cookie};",
}
expect(response.status).to eq(200)
expect(response).to have_http_status :ok
id3 = session["session_id"]
# Session cookie was rejected and rotated
expect(id2).not_to eq(id3)
end
describe "Cookies rotator" do
let!(:rotations) { request.cookies_rotations }
let(:second_host) { get "http://test2.localhost/session/csrf.json" }
let(:global_rotations) { Rails.application.config.action_dispatch.cookies_rotations }
it "adds different rotations for different hosts" do
first_host
expect(request.cookies_rotations).to have_attributes signed: rotations.signed,
encrypted: rotations.encrypted
second_host
expect(request.cookies_rotations).not_to have_attributes signed: rotations.signed,
encrypted: rotations.encrypted
end
it "doesn't change global rotations" do
second_host
expect(global_rotations).to have_attributes signed: [], encrypted: []
end
end
end