Ctrl K
ComunidadGuías…

test/integration/oauth_availability_test.rb

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

Archivo original ↗Preguntar a KiBot
require "test_helper"

class OauthAvailabilityTest < ActionDispatch::IntegrationTest
  include OauthAvailabilityTestHelper
  include OauthClientCredentialsTestHelper

  CODE_VERIFIER = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"

  # Discovery follows acceptance

  test "a dark server publishes no discovery documents" do
    with_oauth_availability acceptance: false do
      untenanted { get "/.well-known/oauth-authorization-server" }
      assert_response :not_found

      untenanted { get "/.well-known/oauth-protected-resource" }
      assert_response :not_found
    end
  end

  test "a pilot client does not make a dark server discoverable" do
    with_oauth_availability acceptance: false, pilot_client_ids: [ oauth_clients(:mcp_client).client_id ] do
      untenanted { get "/.well-known/oauth-authorization-server", params: { client_id: oauth_clients(:mcp_client).client_id } }
      assert_response :not_found
    end
  end

  test "pausing issuance keeps discovery up" do
    with_oauth_availability acceptance: true, issuance: false do
      untenanted { get "/.well-known/oauth-authorization-server" }
      assert_response :success

      untenanted { get "/.well-known/oauth-protected-resource" }
      assert_response :success
    end
  end

  # Issuance

  test "a dark server registers no clients" do
    with_oauth_availability acceptance: false do
      assert_no_difference "Oauth::Client.count" do
        untenanted { post oauth_clients_path, params: { redirect_uris: [ "http://127.0.0.1:9999/callback" ] }, as: :json }
      end
    end

    assert_response :not_found
  end

  test "a dark server answers 404 even past the rate limit" do
    ActiveSupport::Cache::NullStore.any_instance.stubs(:increment).returns(1_000)

    with_oauth_availability acceptance: false do
      untenanted { post oauth_clients_path, params: { redirect_uris: [ "http://127.0.0.1:9999/callback" ] }, as: :json }
      assert_response :not_found

      untenanted { post oauth_token_path, params: { grant_type: "authorization_code", code: "x" } }
      assert_response :not_found
    end
  end

  test "naming a pilot client does not open registration on a dark server" do
    with_oauth_availability acceptance: false, issuance: false, pilot_client_ids: [ oauth_clients(:mcp_client).client_id ] do
      assert_no_difference "Oauth::Client.count" do
        untenanted { post oauth_clients_path, params: { client_id: oauth_clients(:mcp_client).client_id, redirect_uris: [ "http://127.0.0.1:9999/callback" ] }, as: :json }
      end
    end

    assert_response :not_found
  end

  test "pausing issuance refuses registration with 503" do
    with_oauth_availability acceptance: true, issuance: false do
      assert_no_difference "Oauth::Client.count" do
        untenanted { post oauth_clients_path, params: { redirect_uris: [ "http://127.0.0.1:9999/callback" ] }, as: :json }
      end
    end

    assert_response :service_unavailable
  end

  test "a dark server does not ask a signed-out user to sign in to authorize" do
    with_oauth_availability acceptance: false do
      untenanted { get new_oauth_authorization_path, params: authorization_params }
    end

    assert_response :not_found
  end

  test "a dark server shows no consent screen" do
    sign_in_as :david

    with_oauth_availability acceptance: false do
      untenanted { get new_oauth_authorization_path, params: authorization_params }
    end

    assert_response :not_found
  end

  test "a dark server issues no code on approval" do
    sign_in_as :david

    with_oauth_availability acceptance: false do
      untenanted { post oauth_authorization_path, params: authorization_params }
    end

    assert_response :not_found
  end

  test "pausing issuance refuses authorization with 503" do
    sign_in_as :david

    with_oauth_availability acceptance: true, issuance: false do
      untenanted { get new_oauth_authorization_path, params: authorization_params }
      assert_response :service_unavailable

      untenanted { post oauth_authorization_path, params: authorization_params }
      assert_response :service_unavailable
    end
  end

  test "a denial still reaches the client while dark" do
    sign_in_as :david

    with_oauth_availability acceptance: false, issuance: false do
      untenanted { post oauth_authorization_path, params: authorization_params.merge(error: "access_denied") }
    end

    assert_response :redirect
    assert_match %r{\Ahttp://127\.0\.0\.1:8888/callback\?error=access_denied}, response.location
  end

  test "a dark server mints no tokens" do
    with_oauth_availability acceptance: false do
      assert_no_difference "Identity::AccessToken.count" do
        untenanted { post oauth_token_path, params: token_params, as: :json }
      end
    end

    assert_response :not_found
  end

  test "pausing issuance refuses the token endpoint with 503" do
    with_oauth_availability acceptance: true, issuance: false do
      assert_no_difference "Identity::AccessToken.count" do
        untenanted { post oauth_token_path, params: token_params, as: :json }
      end
    end

    assert_response :service_unavailable
  end

  test "a pilot client completes the flow against a dark server" do
    sign_in_as :david
    client = oauth_clients(:mcp_client)

    with_oauth_availability acceptance: false, issuance: false, pilot_client_ids: [ client.client_id ] do
      untenanted { get new_oauth_authorization_path, params: authorization_params }
      assert_response :success

      untenanted { post oauth_authorization_path, params: authorization_params }
      assert_response :redirect

      untenanted { post oauth_token_path, params: token_params, as: :json }
      assert_response :success

      get user_path(users(:david)), env: bearer(response.parsed_body["access_token"]), as: :json
      assert_response :success
    end
  end

  test "another client is still refused while a pilot runs" do
    with_oauth_availability acceptance: false, pilot_client_ids: [ oauth_clients(:trusted_client).client_id ] do
      untenanted { post oauth_token_path, params: token_params, as: :json }
    end

    assert_response :not_found
  end

  test "a dark server refreshes no grants" do
    token = identities(:david).access_tokens.create!(oauth_client: oauth_clients(:mcp_client), permission: :read)

    with_oauth_availability acceptance: false do
      untenanted { post oauth_token_path, params: refresh_params(token), as: :json }
    end

    assert_response :not_found
    assert_equal token.refresh_token_digest, token.reload.refresh_token_digest
  end

  test "pausing issuance refuses refresh with 503 and leaves the grant untouched" do
    token = identities(:david).access_tokens.create!(oauth_client: oauth_clients(:mcp_client), permission: :read)
    raw_token = token.raw_token

    with_oauth_availability acceptance: true, issuance: false do
      untenanted { post oauth_token_path, params: refresh_params(token), as: :json }
      assert_response :service_unavailable
      assert_equal token.refresh_token_digest, token.reload.refresh_token_digest

      get user_path(users(:david)), env: bearer(raw_token), as: :json
      assert_response :success
    end
  end

  test "a pilot client refreshes against a dark server" do
    client = oauth_clients(:mcp_client)
    token = identities(:david).access_tokens.create!(oauth_client: client, permission: :read)

    with_oauth_availability acceptance: false, issuance: false, pilot_client_ids: [ client.client_id ] do
      untenanted { post oauth_token_path, params: refresh_params(token), as: :json }
    end

    assert_response :success
    assert_not_equal token.refresh_token_digest, token.reload.refresh_token_digest
  end

  test "a dark server refreshes no confidential grants, even with the right secret" do
    client = oauth_clients(:confidential_client)
    token = identities(:david).access_tokens.create!(oauth_client: client, permission: :read)

    with_oauth_availability acceptance: false do
      untenanted { post oauth_token_path, params: refresh_params(token).merge(client_secret: "confidential_secret_789") }
    end

    assert_response :not_found
    assert_equal token.refresh_token_digest, token.reload.refresh_token_digest
  end

  test "a confidential pilot client still authenticates against a dark server" do
    client = oauth_clients(:confidential_client)
    token = identities(:david).access_tokens.create!(oauth_client: client, permission: :read)

    with_oauth_availability acceptance: false, issuance: false, pilot_client_ids: [ client.client_id ] do
      untenanted { post oauth_token_path, params: refresh_params(token).merge(client_secret: "wrong") }
    end

    assert_client_authentication_failed basic: false
    assert_equal token.refresh_token_digest, token.reload.refresh_token_digest
  end

  # Acceptance

  test "OAuth bearer tokens are refused while acceptance is off" do
    token = identities(:david).access_tokens.create!(oauth_client: oauth_clients(:mcp_client), permission: :read)

    with_oauth_availability acceptance: false do
      get user_path(users(:david)), env: bearer(token.raw_token), as: :json
    end

    assert_response :unauthorized
  end

  test "personal access tokens still work while OAuth is dark" do
    with_oauth_availability acceptance: false do
      get user_path(users(:jason)), env: bearer(identity_access_tokens(:jasons_api_token).token), as: :json
    end

    assert_response :success
  end

  test "pausing issuance keeps existing OAuth tokens working" do
    token = identities(:david).access_tokens.create!(oauth_client: oauth_clients(:mcp_client), permission: :read)

    with_oauth_availability acceptance: true, issuance: false do
      get user_path(users(:david)), env: bearer(token.raw_token), as: :json
    end

    assert_response :success
  end

  # Management is never gated

  test "revocation works while dark" do
    token = identities(:david).access_tokens.create!(oauth_client: oauth_clients(:mcp_client), permission: :read)

    with_oauth_availability acceptance: false, issuance: false do
      assert_difference "Identity::AccessToken.count", -1 do
        untenanted { post oauth_revocation_path, params: { token: token.raw_token, client_id: token.oauth_client.client_id } }
      end
    end

    assert_response :success
  end

  test "Connected Apps lists and disconnects while dark" do
    sign_in_as :david
    client = oauth_clients(:mcp_client)
    identities(:david).access_tokens.create!(oauth_client: client, permission: :read)

    with_oauth_availability acceptance: false, issuance: false do
      get my_connected_apps_path
      assert_response :success
      assert_match client.name, response.body

      assert_difference "Identity::AccessToken.count", -1 do
        delete my_connected_app_path(client)
      end
      assert_redirected_to my_connected_apps_path
    end
  end

  private
    def authorization_params
      {
        client_id: oauth_clients(:mcp_client).client_id,
        redirect_uri: "http://127.0.0.1:8888/callback",
        response_type: "code",
        code_challenge: Base64.urlsafe_encode64(Digest::SHA256.digest(CODE_VERIFIER), padding: false),
        code_challenge_method: "S256",
        scope: "read",
        state: "xyz123"
      }
    end

    def token_params
      {
        grant_type: "authorization_code",
        client_id: oauth_clients(:mcp_client).client_id,
        code: Oauth::AuthorizationCode.generate(
          client_id: oauth_clients(:mcp_client).client_id,
          identity_id: identities(:david).id,
          code_challenge: authorization_params[:code_challenge],
          redirect_uri: "http://127.0.0.1:8888/callback",
          scope: "read"),
        redirect_uri: "http://127.0.0.1:8888/callback",
        code_verifier: CODE_VERIFIER
      }
    end

    def refresh_params(token)
      { grant_type: "refresh_token", refresh_token: token.raw_refresh_token, client_id: token.oauth_client.client_id }
    end

    def bearer(token)
      { "HTTP_AUTHORIZATION" => "Bearer #{token}" }
    end
end