Fix an open redirect exploit where if you went to <https://danbooru.donmai.us/login?url=//fakebooru.com>, then after you logged in you would be redirected to https://fakebooru.com. This was actually fixed by the upgrade to Rails 7.0. `redirect_to` now raises an `UnsafeRedirectError` on redirect to an offsite URL. Before we tried to prevent offsite redirects by checking that the URL started with a slash, but this was insufficient - it allowed protocol-relative URLs like `//fakebooru.com`. Add a test case for protocol-relative URLs and return a 403 error on an offsite redirect.
138 lines
4.7 KiB
Ruby
138 lines
4.7 KiB
Ruby
require 'test_helper'
|
|
|
|
class SessionsControllerTest < ActionDispatch::IntegrationTest
|
|
context "the sessions controller" do
|
|
setup do
|
|
@user = create(:user, password: "password")
|
|
end
|
|
|
|
context "new action" do
|
|
should "render" do
|
|
get new_session_path
|
|
assert_response :success
|
|
end
|
|
end
|
|
|
|
context "create action" do
|
|
should "log the user in when given the correct password" do
|
|
post session_path, params: { name: @user.name, password: "password" }
|
|
|
|
assert_redirected_to posts_path
|
|
assert_equal(@user.id, session[:user_id])
|
|
assert_not_nil(@user.reload.last_ip_addr)
|
|
assert_equal(true, @user.user_events.login.exists?)
|
|
end
|
|
|
|
should "not log the user in when given an incorrect password" do
|
|
post session_path, params: { name: @user.name, password: "wrong"}
|
|
|
|
assert_response 401
|
|
assert_nil(nil, session[:user_id])
|
|
assert_equal(true, @user.user_events.failed_login.exists?)
|
|
end
|
|
|
|
should "not log the user in when given an incorrect username" do
|
|
post session_path, params: { name: "dne", password: "password" }
|
|
|
|
assert_response 401
|
|
assert_nil(nil, session[:user_id])
|
|
end
|
|
|
|
should "redirect the user when given an url param" do
|
|
post session_path, params: { name: @user.name, password: "password", url: tags_path }
|
|
assert_redirected_to tags_path
|
|
end
|
|
|
|
should "not allow redirects to protocol-relative URLs" do
|
|
post session_path, params: { name: @user.name, password: "password", url: "//example.com" }
|
|
assert_response 403
|
|
end
|
|
|
|
should "not allow IP banned users to login" do
|
|
@ip_ban = create(:ip_ban, category: :full, ip_addr: "1.2.3.4")
|
|
post session_path, params: { name: @user.name, password: "password" }, headers: { REMOTE_ADDR: "1.2.3.4" }
|
|
|
|
assert_response 403
|
|
assert_not_equal(@user.id, session[:user_id])
|
|
assert_equal(1, @ip_ban.reload.hit_count)
|
|
assert(@ip_ban.last_hit_at > 1.minute.ago)
|
|
end
|
|
|
|
should "allow partial IP banned users to login" do
|
|
@ip_ban = create(:ip_ban, category: :partial, ip_addr: "1.2.3.4")
|
|
post session_path, params: { name: @user.name, password: "password" }, headers: { REMOTE_ADDR: "1.2.3.4" }
|
|
|
|
assert_redirected_to posts_path
|
|
assert_equal(@user.id, session[:user_id])
|
|
assert_equal(0, @ip_ban.reload.hit_count)
|
|
assert_nil(@ip_ban.last_hit_at)
|
|
end
|
|
|
|
should "ignore deleted IP bans when logging in" do
|
|
@ip_ban = create(:ip_ban, is_deleted: true, category: :full, ip_addr: "1.2.3.4")
|
|
post session_path, params: { name: @user.name, password: "password" }, headers: { REMOTE_ADDR: "1.2.3.4" }
|
|
|
|
assert_redirected_to posts_path
|
|
assert_equal(@user.id, session[:user_id])
|
|
assert_equal(0, @ip_ban.reload.hit_count)
|
|
assert_nil(@ip_ban.last_hit_at)
|
|
end
|
|
|
|
should "rate limit logins to 10 per minute per IP" do
|
|
freeze_time
|
|
|
|
11.times do
|
|
post session_path, params: { name: @user.name, password: "password" }, headers: { REMOTE_ADDR: "1.2.3.4" }
|
|
assert_redirected_to posts_path
|
|
assert_equal(@user.id, session[:user_id])
|
|
delete_auth session_path, @user
|
|
end
|
|
|
|
post session_path, params: { name: @user.name, password: "password" }, headers: { REMOTE_ADDR: "1.2.3.4" }
|
|
assert_response 429
|
|
assert_not_equal(@user.id, session[:user_id])
|
|
|
|
travel 59.seconds
|
|
post session_path, params: { name: @user.name, password: "password" }, headers: { REMOTE_ADDR: "1.2.3.4" }
|
|
assert_response 429
|
|
assert_not_equal(@user.id, session[:user_id])
|
|
|
|
travel 10.seconds
|
|
post session_path, params: { name: @user.name, password: "password" }, headers: { REMOTE_ADDR: "1.2.3.4" }
|
|
assert_redirected_to posts_path
|
|
assert_equal(@user.id, session[:user_id])
|
|
end
|
|
end
|
|
|
|
context "destroy action" do
|
|
should "clear the session" do
|
|
delete_auth session_path, @user
|
|
|
|
assert_redirected_to posts_path
|
|
assert_nil(session[:user_id])
|
|
end
|
|
|
|
should "generate a logout event" do
|
|
delete_auth session_path, @user
|
|
|
|
assert_equal(true, @user.user_events.logout.exists?)
|
|
end
|
|
|
|
should "not fail if the user is already logged out" do
|
|
delete session_path
|
|
|
|
assert_redirected_to posts_path
|
|
assert_nil(session[:user_id])
|
|
end
|
|
end
|
|
|
|
context "sign_out action" do
|
|
should "clear the session" do
|
|
get_auth sign_out_session_path, @user
|
|
assert_redirected_to posts_path
|
|
assert_nil(session[:user_id])
|
|
end
|
|
end
|
|
end
|
|
end
|