Follow up - ca56de3648 - Maintenance: Updated to Rails 6.0.4 and the new Zeitwerk autoloader: Fixed deprecation of autoloading in initializers causing namespace lookup errors.

This commit is contained in:
Thorsten Eckel 2021-07-21 12:00:13 +00:00
parent 3d946cf362
commit 887b779b40
17 changed files with 194 additions and 68 deletions

View file

@ -2,4 +2,17 @@
class Session < ActiveRecord::SessionStore::Session
include Session::SetsPersistentFlag
def self.secure_flag?
# enable runtime change support in test/develop environments
return https? if !Rails.env.production?
@secure_flag ||= https?
rescue ActiveRecord::NoDatabaseError, ActiveRecord::StatementInvalid
false
end
def self.https?
Setting.get('http_type') == 'https'
end
end

View file

@ -1,6 +1,6 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
# Rails' constant auto-loading resolves to 'rails/initializable' instead
require_dependency 'zammad/application/initializer/db_preflight_check'
require 'zammad/application/initializer/db_preflight_check'
Zammad::Application::Initializer::DbPreflightCheck.perform

View file

@ -1,9 +1,12 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
return if !ActiveRecord::Base.connected?
Rails.application.reloader.to_prepare do
next if !ActiveRecord::Base.connected?
# sync logo to fs / only if settings already exists
return if ActiveRecord::Base.connection.tables.exclude?('settings')
return if Setting.column_names.exclude?('state_current')
next if ActiveRecord::Base.connection.tables.exclude?('settings')
next if Setting.column_names.exclude?('state_current')
StaticAssets.sync
end

View file

@ -2,10 +2,12 @@
# update settings for searchable models
Rails.application.reloader.to_prepare do
begin
return if !Setting.exists?(name: 'models_searchable')
next if !Setting.exists?(name: 'models_searchable')
Setting.set('models_searchable', Models.searchable.map(&:to_s))
rescue ActiveRecord::StatementInvalid
nil
end
end

View file

@ -1,6 +1,6 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
# Rails' constant auto-loading resolves to 'rails/initializable' instead
require_dependency 'zammad/application/initializer/session_store'
require 'zammad/application/initializer/session_store'
Zammad::Application::Initializer::SessionStore.perform

View file

@ -0,0 +1,17 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
require 'action_dispatch/middleware/cookies'
module ActionDispatch
class Cookies
class CookieJar
alias original_write_cookie? write_cookie?
# https://github.com/rails/rails/blob/v6.0.4/actionpack/lib/action_dispatch/middleware/cookies.rb#L447-L449
def write_cookie?(cookie)
original_write_cookie?(cookie.merge(secure: ::Session.secure_flag?))
end
end
end
end

View file

@ -0,0 +1,20 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
require 'rack/session/abstract/id'
module Rack
module Session
module Abstract
class Persisted
alias original_security_matches? security_matches?
# https://github.com/rack/rack/blob/2.2.3/lib/rack/session/abstract/id.rb#L363-L366
def security_matches?(request, options)
options[:secure] = ::Session.secure_flag?
original_security_matches?(request, options)
end
end
end
end
end

View file

@ -0,0 +1,22 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
require 'rack/utils'
module Rack
module Utils
module_function
singleton_class.alias_method :original_add_cookie_to_header, :add_cookie_to_header
# https://github.com/rack/rack/blob/2.2.3/lib/rack/session/utils.rb#L223-L262
def add_cookie_to_header(header, key, value)
if value.is_a?(Hash)
value[:secure] = ::Session.secure_flag?
end
original_add_cookie_to_header(header, key, value)
end
end
end

View file

@ -7,7 +7,7 @@ require 'zammad/application/initializer/db_preflight_check/nulldb'
module Zammad
class Application
class Initializer
module Initializer
module DbPreflightCheck
def self.perform
adapter.perform

View file

@ -13,7 +13,7 @@
module Zammad
class Application
class Initializer
module Initializer
module DbPreflightCheck
module Base
def check_version_compatibility

View file

@ -12,7 +12,7 @@
module Zammad
class Application
class Initializer
module Initializer
module DbPreflightCheck
module Mysql2
extend Base

View file

@ -2,7 +2,7 @@
module Zammad
class Application
class Initializer
module Initializer
module DbPreflightCheck
module Nulldb
# no-op

View file

@ -12,7 +12,7 @@
module Zammad
class Application
class Initializer
module Initializer
module DbPreflightCheck
module Postgresql
extend Base

View file

@ -6,24 +6,28 @@
module Zammad
class Application
class Initializer
module Initializer
module SessionStore
STORE_TYPE = :active_record_store # default: :cookie_store
SESSION_KEY = "_zammad_session_#{Digest::MD5.hexdigest(Rails.root.to_s)[5..15]}".freeze # default: '_zammad_session'
def self.perform
ActionDispatch::Session::ActiveRecordStore.session_class = Session
# it's important to register the session store at initialization time
# otherwise the store won't be used
# ATTENTION: Rails/Rack Cookie handling was customized to call `Session.secure_flag?`
# instead of accessing the `:secure` key (default Rack/Rails behavior).
# See: lib/core_ext/action_dispatch/middleware/cookies.rb
# See: lib/core_ext/rack/session/abstract/id.rb
# See: lib/core_ext/rack/session/utils.rb
Rails.application.config.session_store STORE_TYPE,
key: SESSION_KEY,
secure: secure?
end
key: SESSION_KEY
def self.secure?
Setting.get('http_type') == 'https'
rescue ActiveRecord::StatementInvalid
false
end
private_class_method :secure?
# once the application is initialized and we can access the models
# we need to update the session_class
Rails.application.reloader.to_prepare do
ActionDispatch::Session::ActiveRecordStore.session_class = Session
end
end
end
end
end

View file

@ -1,31 +0,0 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
require 'rails_helper'
RSpec.describe Zammad::Application::Initializer::SessionStore do
describe '.perform' do
context 'for HTTP deployment' do
before { Setting.set('http_type', 'http') }
# Why not use the "change" matcher in this example?
#
# This initializer is already run when the application is loaded for testing.
# Since the test env always uses http, the :secure option is already set to false.
it 'adds { secure: false } to application session options' do
described_class.perform
expect(Rails.application.config.session_options).to include(secure: false)
end
end
context 'for HTTPS deployment' do
before { Setting.set('http_type', 'https') }
it 'adds { secure: true } to application session options' do
expect { described_class.perform }
.to change(Rails.application.config, :session_options)
.to include(secure: true)
end
end
end
end

View file

@ -192,7 +192,7 @@ RSpec.describe 'External Credentials', type: :request do
shared_examples 'for failure cases' do
it 'responds with the appropriate status and error message' do
send(*endpoint, as: :json, params: try(:params) || {})
send(*endpoint, as: :json, params: try(:params) || {}, headers: headers)
expect(response).to have_http_status(status)
expect(json_response).to include('error' => error_message)
@ -415,7 +415,14 @@ RSpec.describe 'External Credentials', type: :request do
let!(:twitter_credential) { create(:twitter_credential) }
before { get '/api/v1/external_credentials/twitter/link_account', as: :json }
# Rails / Rack needs to detect that the request comes via HTTPS as well
let(:headers) do
{
'X-Forwarded-Proto' => 'https'
}
end
before { get '/api/v1/external_credentials/twitter/link_account', as: :json, headers: headers }
include_examples 'for failure cases' do
let(:status) { :unprocessable_entity }
@ -458,7 +465,14 @@ RSpec.describe 'External Credentials', type: :request do
let(:oauth_token) { 'DyhnyQAAAAAA9CNXAAABcSxAexs' }
let(:oauth_verifier) { '15DOeRkjP4JkOSVqULkTKA1SCuIPP105' }
before { get '/api/v1/external_credentials/twitter/link_account', as: :json }
# Rails / Rack needs to detect that the request comes via HTTPS as well
let(:headers) do
{
'X-Forwarded-Proto' => 'https'
}
end
before { get '/api/v1/external_credentials/twitter/link_account', as: :json, headers: headers }
context 'if Twitter account has already been added' do
let!(:channel) { create(:twitter_channel, custom_options: channel_options) }
@ -472,12 +486,12 @@ RSpec.describe 'External Credentials', type: :request do
end
it 'uses the existing channel' do
expect { send(*endpoint, as: :json, params: params) }
expect { send(*endpoint, as: :json, params: params, headers: headers) }
.not_to change(Channel, :count)
end
it 'updates channel properties' do
expect { send(*endpoint, as: :json, params: params) }
expect { send(*endpoint, as: :json, params: params, headers: headers) }
.to change { channel.reload.options[:user][:name] }
.and change { channel.reload.options[:auth][:external_credential_id] }
.and change { channel.reload.options[:auth][:oauth_token] }
@ -485,7 +499,7 @@ RSpec.describe 'External Credentials', type: :request do
end
it 'subscribes to webhooks' do
send(*endpoint, as: :json, params: params)
send(*endpoint, as: :json, params: params, headers: headers)
expect(WebMock)
.to have_requested(:post, "https://api.twitter.com/1.1/account_activity/all/#{twitter_credential.credentials[:env]}/subscriptions.json")
@ -496,7 +510,7 @@ RSpec.describe 'External Credentials', type: :request do
end
it 'creates a new channel' do
expect { send(*endpoint, as: :json, params: params) }
expect { send(*endpoint, as: :json, params: params, headers: headers) }
.to change(Channel, :count).by(1)
expect(Channel.last.options)
@ -506,19 +520,19 @@ RSpec.describe 'External Credentials', type: :request do
end
it 'redirects to the newly created channel' do
send(*endpoint, as: :json, params: params)
send(*endpoint, as: :json, params: params, headers: headers)
expect(response).to redirect_to(%r{/#channels/twitter/#{Channel.last.id}$})
end
it 'clears the :request_token session variable' do
send(*endpoint, as: :json, params: params)
send(*endpoint, as: :json, params: params, headers: headers)
expect(session[:request_token]).to be(nil)
end
it 'subscribes to webhooks' do
send(*endpoint, as: :json, params: params)
send(*endpoint, as: :json, params: params, headers: headers)
expect(WebMock)
.to have_requested(:post, "https://api.twitter.com/1.1/account_activity/all/#{twitter_credential.credentials[:env]}/subscriptions.json")

View file

@ -4,6 +4,68 @@ require 'rails_helper'
RSpec.describe 'Sessions endpoints', type: :request do
describe 'GET /' do
let(:headers) { {} }
let(:session_key) { Zammad::Application::Initializer::SessionStore::SESSION_KEY }
before do
Setting.set('http_type', http_type)
get '/', headers: headers
end
context "when Setting 'http_type' is set to 'https'" do
let(:http_type) { 'https' }
context "when it's not an HTTPS request" do
it 'sets no Cookie' do
expect(response.header['Set-Cookie']).to be_nil
end
end
context "when it's an HTTPS request" do
let(:headers) do
{
'X-Forwarded-Proto' => 'https'
}
end
it "sets Cookie with 'secure' flag" do
expect(response.header['Set-Cookie']).to include(session_key).and include('; secure;')
end
end
end
context "when Setting 'http_type' is set to 'http'" do
let(:http_type) { 'http' }
context "when it's not an HTTPS request" do
it 'sets Cookie' do
expect(response.header['Set-Cookie']).to include(session_key).and not_include('; secure;')
end
end
context "when it's an HTTPS request" do
let(:headers) do
{
'X-Forwarded-Proto' => 'https'
}
end
it "sets Cookie without 'secure' flag" do
expect(response.header['Set-Cookie']).to include(session_key).and not_include('; secure;')
end
end
end
end
describe 'GET /signshow' do
context 'user logged in' do