Skip to content

Commit

Permalink
Add safe_redirect_path
Browse files Browse the repository at this point in the history
This makes sure we only redirect internally in the /admin routing context
and redirect to a given safe fallback location or the default admin route.
  • Loading branch information
tvdeyen committed Jan 5, 2025
1 parent 640486d commit b4e7ca4
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 0 deletions.
15 changes: 15 additions & 0 deletions app/controllers/alchemy/admin/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ def leave

private

def safe_redirect_path(path = params[:redirect_to], fallback: admin_path)
if is_safe_redirect_path?(path)
path
elsif is_safe_redirect_path?(fallback)
fallback
else
admin_path
end
end

def is_safe_redirect_path?(path)
mount_path = alchemy.root_path
path.to_s.match? %r{^#{mount_path}admin/}
end

# Disable layout rendering for xhr requests.
def set_layout
(request.xhr? || turbo_frame_request?) ? false : "alchemy/admin"
Expand Down
128 changes: 128 additions & 0 deletions spec/controllers/alchemy/admin/base_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,132 @@ def index
end
end
end

describe "#safe_redirect_path" do
subject { controller.send(:safe_redirect_path) }

context "when params[:redirect_to] is present" do
before do
allow(controller).to receive(:params) { {redirect_to: redirect_url} }
end

context "and it is not an external URL" do
let(:redirect_url) { "/admin/pages" }

it "redirects to given path" do
is_expected.to eq("/admin/pages")
end
end

context "and it is an external URL" do
let(:redirect_url) { "https://evil.com" }

context "and no fallback is given" do
it "redirects to default fallback path" do
is_expected.to eq("/admin")
end
end

context "and a fallback is given" do
subject { controller.send(:safe_redirect_path, fallback: "/admin/pages") }

context "which is a safe path" do
it "redirects to given fallback path" do
is_expected.to eq("/admin/pages")
end
end

context "which is not a safe path" do
subject { controller.send(:safe_redirect_path, fallback: "evil.com") }

it "redirects to default fallback path" do
is_expected.to eq("/admin")
end
end
end
end
end

context "when params[:redirect_to] is not present" do
context "and another path is given" do
subject { controller.send(:safe_redirect_path, redirect_path) }

context "which is a safe path" do
let(:redirect_path) { "/admin/pages" }

it "redirects to given path" do
is_expected.to eq("/admin/pages")
end
end

context "which is not a safe path" do
let(:redirect_path) { "evil.com" }

it "redirects to default fallback path" do
is_expected.to eq("/admin")
end
end
end

context "and no fallback is given" do
it "redirects to default fallback path" do
is_expected.to eq("/admin")
end
end

context "and a fallback is given" do
subject { controller.send(:safe_redirect_path, fallback: "/admin/pages") }

context "which is a safe path" do
it "redirects to given fallback path" do
is_expected.to eq("/admin/pages")
end
end

context "which is not a safe path" do
subject { controller.send(:safe_redirect_path, fallback: "evil.com") }

it "redirects to default fallback path" do
is_expected.to eq("/admin")
end
end
end
end
end

describe "#is_safe_redirect_path?" do
subject { controller.send(:is_safe_redirect_path?, path) }

context "path is not an external URL" do
let(:path) { "/admin/pages" }

it { is_expected.to be(true) }
end

context "path is an external URL" do
let(:path) { "https://evil.com" }

it { is_expected.to be(false) }
end

context "alchemy is mounted under a path" do
before do
allow(controller).to receive(:alchemy) do
double(root_path: "/cms/")
end
end

context "path is not an external URL" do
let(:path) { "/cms/admin/pages" }

it { is_expected.to be(true) }
end

context "path is an external URL" do
let(:path) { "https://evil.com" }

it { is_expected.to be(false) }
end
end
end
end

0 comments on commit b4e7ca4

Please sign in to comment.