Ctrl K
ComunidadGuías…

test/integration/oauth_dcr_redirects_test.rb

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
require "test_helper"

class OauthDcrRedirectsTest < ActionDispatch::IntegrationTest
  # VS Code registers its vscode.dev callbacks beside loopback, all in one set,
  # which a host allowlist refused whole.
  VSCODE_CALLBACKS = %w[ https://vscode.dev/redirect https://insiders.vscode.dev/redirect http://127.0.0.1/ http://127.0.0.1:33418/ ]

  test "registration accepts https on any plain host we don't run" do
    register(*VSCODE_CALLBACKS)
    assert_response :created
    assert_equal VSCODE_CALLBACKS, Oauth::Client.find_by!(client_id: response.parsed_body["client_id"]).redirect_uris

    %w[ https://claude.ai/api/mcp/auth_callback https://acme-be.glean.com/oauth/callback https://connector.example.com:8443/callback ].each do |uri|
      register uri
      assert_response :created, uri
    end
  end

  test "registration refuses plain http, fragments and userinfo, naming what it accepts" do
    %w[ http://connector.example.com/callback https://connector.example.com/callback#frag https://user@connector.example.com/callback
        https://%65vil.example/callback ].each do |uri|
      assert_no_difference "Oauth::Client.count" do
        register uri
      end

      assert_response :bad_request, uri
      assert_equal "invalid_redirect_uri", response.parsed_body["error"], uri
      assert_match "https redirects on a plain host this server doesn't run", response.parsed_body["error_description"], uri
    end
  end

  test "one refused callback refuses the whole registration" do
    assert_no_difference "Oauth::Client.count" do
      register(*VSCODE_CALLBACKS, "http://elsewhere.example/callback")
    end

    assert_response :bad_request
    assert_equal "invalid_redirect_uri", response.parsed_body["error"]
  end

  # Consent names a self-registered app by where its access returns, so a
  # callback on a host we run would read as ours: this install's own host,
  # as configured (example.com in tests) and as served (www.example.com, with
  # or without BASE_URL), and 37signals' domains, in any case or subdomain.
  test "registration refuses https callbacks on hosts we run" do
    %w[
      https://example.com/callback
      https://EXAMPLE.com/callback
      https://app.fizzy.do/callback
      https://FIZZY.do/callback
      https://app.fizzy-staging.com/callback
      https://3.basecamp.com/callback
      https://basecamp.com/callback
      https://launchpad.37signals.com/callback
      https://app.hey.com/callback
      https://www.example.com/callback
    ].each do |uri|
      assert_no_difference "Oauth::Client.count" do
        register uri
      end

      assert_response :bad_request, uri
      assert_equal "invalid_redirect_uri", response.parsed_body["error"], uri
    end
  end

  test "registration accepts the cursor and claude app schemes and no others" do
    register "cursor://anysphere.cursor-mcp/oauth/callback", "claude://claude.ai/mcp-auth-callback/sdk"
    assert_response :created

    %w[ com.example.app://callback vscode://callback cursor:callback ].each do |uri|
      register uri
      assert_response :bad_request, uri
      assert_equal "invalid_redirect_uri", response.parsed_body["error"], uri
    end
  end

  test "an app-scheme client gets its code at its app redirect" do
    sign_in_as :david
    register "cursor://anysphere.cursor-mcp/oauth/callback"
    client = Oauth::Client.find_by!(client_id: response.parsed_body["client_id"])

    untenanted do
      get new_oauth_authorization_path, params: {
        client_id: client.client_id,
        redirect_uri: "cursor://anysphere.cursor-mcp/oauth/callback",
        response_type: "code",
        code_challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
        code_challenge_method: "S256",
        scope: "read"
      }
    end
    assert_response :success
    assert_includes response.headers["Content-Security-Policy"], "cursor:"
    assert_select "h1", text: "Connect this app to Fizzy?"

    untenanted do
      post oauth_authorization_path, params: {
        client_id: client.client_id,
        redirect_uri: "cursor://anysphere.cursor-mcp/oauth/callback",
        response_type: "code",
        code_challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
        code_challenge_method: "S256",
        scope: "read",
        state: "xyz123"
      }
    end

    assert_response :redirect
    location = URI.parse(response.location)
    assert_equal "cursor", location.scheme
    assert_equal "anysphere.cursor-mcp", location.host
    assert Rack::Utils.parse_query(location.query)["code"].present?
  end

  test "an app scheme naming a loopback host gets no pre-consent redirect" do
    sign_in_as :david
    register "cursor://localhost/callback"
    assert_response :created
    client = Oauth::Client.find_by!(client_id: response.parsed_body["client_id"])

    untenanted do
      get new_oauth_authorization_path, params: {
        client_id: client.client_id,
        redirect_uri: "cursor://localhost/callback",
        response_type: "code",
        code_challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
        code_challenge_method: "plain",
        scope: "read"
      }
    end

    assert_response :bad_request
    assert_nil response.location
  end

  test "a self-registered https client gets its code at its callback" do
    sign_in_as :david
    register "https://connector.example.com/callback"
    client = Oauth::Client.find_by!(client_id: response.parsed_body["client_id"])

    untenanted do
      post oauth_authorization_path, params: {
        client_id: client.client_id,
        redirect_uri: "https://connector.example.com/callback",
        response_type: "code",
        code_challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
        code_challenge_method: "S256",
        scope: "read",
        state: "xyz123"
      }
    end

    assert_response :redirect
    assert_match %r{\Ahttps://connector\.example\.com/callback\?}, response.location
    assert Rack::Utils.parse_query(URI.parse(response.location).query)["code"].present?
  end

  # Errors found before consent aren't bounced to a host nobody vetted
  # (RFC 9700 §4.11.2), now that any host can be registered.
  test "a self-registered https client's error is shown here, not redirected" do
    sign_in_as :david
    register "https://connector.example.com/callback"
    client = Oauth::Client.find_by!(client_id: response.parsed_body["client_id"])

    untenanted do
      get new_oauth_authorization_path, params: {
        client_id: client.client_id,
        redirect_uri: "https://connector.example.com/callback",
        response_type: "token",
        code_challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
        code_challenge_method: "S256",
        scope: "read"
      }
    end

    assert_response :bad_request
    assert_nil response.location
  end

  private
    def register(*redirect_uris)
      untenanted do
        post oauth_clients_path, params: { client_name: "Connector", redirect_uris: redirect_uris }, as: :json
      end
    end
end