test/integration/oauth_authorization_switch_test.rb
Rama main · réplica de lectura del repositorio original.
require "test_helper"
class OauthAuthorizationSwitchTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :david
@client = oauth_clients(:mcp_client)
end
test "the consent screen offers to switch who is signed in" do
untenanted { get new_oauth_authorization_path, params: authorization_params }
assert_select "form[action=?] button", oauth_authorization_switch_path(script_name: nil), text: "Use a different account?"
end
test "switching signs out and saves the authorization request, rebuilt from its checked parameters" do
assert_difference -> { identities(:david).sessions.count }, -1 do
post_switch authorization_params.merge(return_to: "https://evil.example.com/", account_id: "123")
end
assert_redirected_to new_session_path(script_name: nil)
assert_not cookies[:session_token].present?
assert_equal expected_return_url, session[:return_to_after_authenticating]
end
test "switching an audienced request signs out even when the signed-in identity has no account left" do
resource = "https://mcp.example.com/mcp"
saved, Rails.application.config.x.oauth.mcp_resources = Rails.application.config.x.oauth.mcp_resources, [ resource ]
identities(:david).users.each { |user| user.account.create_cancellation!(initiated_by: user) unless user.account.cancellation }
assert_difference -> { identities(:david).sessions.count }, -1 do
post_switch authorization_params.merge(resource: resource)
end
assert_redirected_to new_session_path(script_name: nil)
ensure
Rails.application.config.x.oauth.mcp_resources = saved
end
test "switching with a bearer token and no browser session is refused, not half-handled" do
token = identities(:david).access_tokens.create!(permission: :write)
reset!
assert_no_difference -> { identities(:david).sessions.count } do
post_switch authorization_params, as: :json, headers: { "Authorization" => "Bearer #{token.raw_token}" }
end
assert_response :forbidden
end
test "switching requires a same-origin request" do
with_forgery_protection do
assert_no_difference -> { identities(:david).sessions.count } do
post_switch authorization_params, headers: { "Sec-Fetch-Site" => "cross-site" }
end
end
assert_response :unprocessable_entity
end
test "a redirect the client didn't register is refused, leaving the person signed in and nothing saved" do
assert_no_difference -> { identities(:david).sessions.count } do
post_switch authorization_params(redirect_uri: "https://evil.example.com/callback")
end
assert_response :bad_request
assert cookies[:session_token].present?
assert_nil session[:return_to_after_authenticating]
end
test "an unknown client is refused, leaving the person signed in" do
assert_no_difference -> { identities(:david).sessions.count } do
post_switch authorization_params(client_id: "forged")
end
assert_response :bad_request
assert_select "[data-oauth-error=?]", "invalid_request"
assert_nil session[:return_to_after_authenticating]
end
test "a request too long to resume is refused before signing out" do
assert_no_difference -> { identities(:david).sessions.count } do
post_switch authorization_params(state: "x" * 2048)
end
assert_response :bad_request
assert_select "p", text: /too long to pick up again/
assert cookies[:session_token].present?
assert_nil session[:return_to_after_authenticating]
end
test "whoever signs in next lands back on consent for the same request, and the saved request is used once" do
post_switch authorization_params
sign_in_as :kevin
assert_equal expected_return_url, response.location
assert_nil session[:return_to_after_authenticating]
get response.location
assert_response :success
assert_select ".oauth-consent__identity strong", text: identities(:kevin).email_address
sign_in_as :kevin
assert_not_equal expected_return_url, response.location
end
private
def authorization_params(**overrides)
{
client_id: @client.client_id,
redirect_uri: @client.redirect_uris.first,
response_type: "code",
code_challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
code_challenge_method: "S256",
scope: "read",
state: "xyz123"
}.merge(overrides)
end
def expected_return_url
new_oauth_authorization_url(script_name: nil, **authorization_params)
end
def post_switch(params, **options)
untenanted { post oauth_authorization_switch_path, params: params, **options }
end
def with_forgery_protection
original = ActionController::Base.allow_forgery_protection
ActionController::Base.allow_forgery_protection = true
yield
ensure
ActionController::Base.allow_forgery_protection = original
end
end