Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions lib/activeadmin/oidc/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,24 @@ def controllers
# isolated engines don't try to resolve the controller as
# `<Engine>::ActiveAdmin::Devise::SessionsController` from
# the relative string form.
get login_path, to: ::ActiveAdmin::Devise::SessionsController.action(:new), as: :"new_#{scope_name}_session"
delete logout_path, to: ::ActiveAdmin::Devise::SessionsController.action(:destroy), as: :"destroy_#{scope_name}_session"
get login_path, to: ::ActiveAdmin::Devise::SessionsController.action(:new), as: :"new_#{scope_name}_session"

# Mirror what AA's own Devise integration does in
# lib/active_admin/devise.rb: accept whichever HTTP
# methods Devise.sign_out_via and
# ActiveAdmin.application.logout_link_method combine to.
# Read at route-draw time (not in the enclosing
# after_initialize) so `Rails.application.reload_routes!`
# picks up host changes to either value — useful for
# specs that stub the setting and re-evaluate routes.
# AA 4 dropped `logout_link_method` (its layout uses
# `button_to` + Turbo), so only consult the setting when
# the version still exposes it.
aa_app = ::ActiveAdmin.application
aa_method = aa_app.logout_link_method if aa_app.respond_to?(:logout_link_method)
logout_via = [*::Devise.sign_out_via, aa_method].compact.uniq

match logout_path, to: ::ActiveAdmin::Devise::SessionsController.action(:destroy), as: :"destroy_#{scope_name}_session", via: logout_via
end
end
end
Expand Down
89 changes: 89 additions & 0 deletions spec/requests/logout_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,93 @@
expect(response).to redirect_to("/admin/login")
end
end

# The mounted route should accept whichever HTTP method
# `ActiveAdmin.application.logout_link_method` is set to, mirroring
# what AA's own Devise integration does in
# `lib/active_admin/devise.rb`:
#
# sign_out_via: [*::Devise.sign_out_via, ActiveAdmin.application.logout_link_method].uniq
#
# Hardcoding DELETE-only meant AA hosts that kept AA's default
# `logout_link_method = :get` (the vast majority) 404'd on every
# Sign Out click: the rendered `<a data-method="get">` link goes
# through rails-ujs and lands on a route the gem never mounted for
# GET.
context "with AA's default logout_link_method (:get)" do
before { sign_in admin_user }

it "accepts GET /admin/logout", skip: (ActiveAdmin::Oidc.aa_v4? && "AA 4 dropped logout_link_method; layout uses button_to + Turbo") do
get "/admin/logout"

expect(response).to be_redirect

get "/admin"
expect(response).to redirect_to("/admin/login")
end
end

# Route-table introspection so a future regression in the mount-time
# method-resolution logic is caught even if no request spec happens
# to exercise the affected verb. Reads the verbs actually advertised
# by the destroy route and compares them against what AA and Devise
# configured.
describe "destroy_admin_user_session route verbs" do
subject(:route) do
Rails.application.routes.routes.find { |r| r.name == "destroy_admin_user_session" }
end

it "exists" do
expect(route).not_to be_nil
end

it "includes Devise.sign_out_via" do
Array(::Devise.sign_out_via).each do |method|
expect(route.verb).to match(/#{method.to_s.upcase}/),
"destroy_admin_user_session does not accept #{method.to_s.upcase} (verb: #{route.verb.inspect})"
end
end

it "includes ActiveAdmin.application.logout_link_method when AA exposes it" do
aa_app = ::ActiveAdmin.application
skip "AA 4 dropped logout_link_method" unless aa_app.respond_to?(:logout_link_method)

expected = aa_app.logout_link_method&.to_s&.upcase
skip "logout_link_method not set" if expected.nil?

expect(route.verb).to match(/#{expected}/),
"destroy_admin_user_session does not accept #{expected} (verb: #{route.verb.inspect})"
end

# End-to-end proof that the gem follows host overrides: change
# AA's setting, reload routes, then drive an actual request
# through the freshly-drawn route. Catches regressions where
# logout_link_method gets read once at boot and frozen into the
# closure (route introspection alone wouldn't catch a frozen
# `via:` array if Rails resolved it before the stub took effect).
%i[get post put delete].each do |method|
it "logs the user out when logout_link_method = #{method.inspect}" do
aa_app = ::ActiveAdmin.application
skip "AA 4 dropped logout_link_method" unless aa_app.respond_to?(:logout_link_method)

original = aa_app.logout_link_method
begin
allow(aa_app).to receive(:logout_link_method).and_return(method)
Rails.application.reload_routes!

sign_in admin_user
public_send(method, "/admin/logout")
expect(response).to be_redirect

# Session is really cleared — subsequent admin request is
# bounced back to the login page.
get "/admin"
expect(response).to redirect_to("/admin/login")
ensure
allow(aa_app).to receive(:logout_link_method).and_return(original)
Rails.application.reload_routes!
end
end
end
end
end
Loading