Ctrl K
ComunidadGuías…

test/controllers/api_test.rb

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

Archivo original ↗Preguntar a KiBot
require "test_helper"

class ApiTest < ActionDispatch::IntegrationTest
  include OauthAvailabilityTestHelper

  setup do
    @davids_bearer_token = bearer_token_env(identity_access_tokens(:davids_api_token).token)
    @jasons_bearer_token = bearer_token_env(identity_access_tokens(:jasons_api_token).token)
  end

  test "authenticate with user credentials" do
    identity = identities(:david)

    untenanted do
      post session_path(format: :json), params: { email_address: identity.email_address }
      assert_response :created
      pending_token = @response.parsed_body["pending_authentication_token"]
      assert pending_token.present?

      magic_link = MagicLink.last
      post session_magic_link_path(format: :json), params: { code: magic_link.code, pending_authentication_token: pending_token }
      assert_response :success
      assert @response.parsed_body["session_token"].present?
    end
  end

  test "logout with user credentials" do
    identity = identities(:david)

    untenanted do
      post session_path(format: :json), params: { email_address: identity.email_address }
      magic_link = MagicLink.last

      assert_difference -> { identity.sessions.count }, +1 do
        post session_magic_link_path(format: :json), params: { code: magic_link.code, pending_authentication_token: @response.parsed_body["pending_authentication_token"] }
      end
      assert cookies[:session_token].present?

      assert_difference -> { identity.sessions.count }, -1 do
        delete session_path(format: :json)
      end
      assert_response :no_content
      assert_not cookies[:session_token].present?
    end
  end

  test "authenticate with valid access token" do
    get boards_path(format: :json), env: @davids_bearer_token
    assert_response :success
  end

  test "fail to authenticate with invalid access token" do
    get boards_path(format: :json), env: bearer_token_env("nonsense")

    assert_response :unauthorized
    assert_bearer_challenge error: "invalid_token"
  end

  test "an expired access token is challenged as invalid_token so the client refreshes" do
    token = identities(:david).access_tokens.create!(oauth_client: oauth_clients(:mcp_client), permission: :write)

    travel Identity::AccessToken::EXPIRES_IN + 1.second do
      get boards_path(format: :json), env: bearer_token_env(token.raw_token)
    end

    assert_response :unauthorized
    assert_bearer_challenge error: "invalid_token"
  end

  test "an OAuth token is challenged as invalid_token while OAuth is dark" do
    token = identities(:david).access_tokens.create!(oauth_client: oauth_clients(:mcp_client), permission: :write)

    with_oauth_availability acceptance: false do
      get boards_path(format: :json), env: bearer_token_env(token.raw_token)
    end

    assert_response :unauthorized
    assert_match 'error="invalid_token"', response.headers["WWW-Authenticate"]
    assert_no_match "resource_metadata", response.headers["WWW-Authenticate"]
  end

  test "changing data requires a write-endowed access token" do
    post boards_path(format: :json), params: { board: { name: "My new board" } }, env: @jasons_bearer_token
    assert_response :unauthorized
    assert_bearer_challenge error: "insufficient_scope"
    assert_match 'scope="write"', response.headers["WWW-Authenticate"]

    post boards_path(format: :json), params: { board: { name: "My new board" } }, env: @davids_bearer_token
    assert_response :success
  end

  test "a read-only grant changing data is forbidden, so the app asks for write" do
    token = identities(:jason).access_tokens.create!(oauth_client: oauth_clients(:mcp_client), permission: :read)

    post boards_path(format: :json), params: { board: { name: "My new board" } }, env: bearer_token_env(token.raw_token)

    assert_response :forbidden
    assert_bearer_challenge error: "insufficient_scope"
    assert_match 'scope="write"', response.headers["WWW-Authenticate"]
  end

  test "a read-only token still reads" do
    get boards_path(format: :json), env: @jasons_bearer_token
    assert_response :success
  end

  test "an expired read-only token on a write is invalid_token, not insufficient_scope" do
    token = identities(:jason).access_tokens.create!(oauth_client: oauth_clients(:mcp_client), permission: :read)

    travel Identity::AccessToken::EXPIRES_IN + 1.second do
      post boards_path(format: :json), params: { board: { name: "My new board" } }, env: bearer_token_env(token.raw_token)
    end

    assert_response :unauthorized
    assert_bearer_challenge error: "invalid_token"
  end

  test "a JSON request without credentials goes to sign in, as shipped clients expect" do
    get boards_path(format: :json)

    assert_response :redirect
    assert_nil response.headers["WWW-Authenticate"]
  end

  test "the challenge names protected resource metadata that resolves" do
    get boards_path(format: :json), env: bearer_token_env("nonsense")
    metadata_url = response.headers["WWW-Authenticate"][/resource_metadata="([^"]+)"/, 1]

    get URI(metadata_url).request_uri
    assert_response :success
    assert_equal "http://www.example.com/", response.parsed_body["resource"]
  end

  test "the challenge leaves out resource metadata while OAuth is dark" do
    with_oauth_availability acceptance: false do
      get boards_path(format: :json), env: bearer_token_env("nonsense")
    end

    assert_response :unauthorized
    assert_no_match "resource_metadata", response.headers["WWW-Authenticate"]
  end

  test "a JSON request from our own pages without a session goes to sign in" do
    get boards_path(format: :json), xhr: true
    assert_response :redirect

    cookies[:session_token] = "stale"
    get boards_path(format: :json)
    assert_response :redirect
  end

  test "an HTML request without credentials still goes to sign in" do
    get boards_path
    assert_response :redirect
  end

  test "the bearer scheme is matched case-insensitively" do
    get boards_path(format: :json), env: { "HTTP_AUTHORIZATION" => "bearer #{identity_access_tokens(:davids_api_token).token}" }
    assert_response :success

    get boards_path(format: :json), env: { "HTTP_AUTHORIZATION" => "BEARER nonsense" }
    assert_response :unauthorized
    assert_bearer_challenge error: "invalid_token"
  end

  test "Bearer elsewhere in another scheme's credentials is not bearer authentication" do
    get boards_path(format: :json), env: { "HTTP_AUTHORIZATION" => "Basic Bearer #{identity_access_tokens(:davids_api_token).token}" }

    assert_response :redirect
  end

  test "a bearer token on an HTML request is refused with a Bearer challenge" do
    get boards_path, env: @davids_bearer_token

    assert_response :unauthorized
    assert_bearer_challenge error: "invalid_request"
  end

  test "a session cookie still authenticates JSON" do
    sign_in_as :david

    get boards_path(format: :json)
    assert_response :success
  end

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

    def assert_bearer_challenge(error: nil)
      challenge = response.headers["WWW-Authenticate"]

      assert_match(/\ABearer realm="Application"/, challenge)
      assert_match 'resource_metadata="http://www.example.com/.well-known/oauth-protected-resource"', challenge
      assert_match %(error="#{error}"), challenge if error
    end
end