236 lines
6.6 KiB
Ruby
236 lines
6.6 KiB
Ruby
require "test_helper"
|
|
|
|
class OidcRefreshTokenControllerTest < ActionDispatch::IntegrationTest
|
|
setup do
|
|
@user = users(:alice)
|
|
@application = applications(:kavita_app)
|
|
# Store a known client secret for testing
|
|
@client_secret = SecureRandom.urlsafe_base64(48)
|
|
@application.client_secret = @client_secret
|
|
@application.save!
|
|
end
|
|
|
|
test "token endpoint returns refresh_token with authorization_code grant" do
|
|
# Create an authorization code
|
|
auth_code = OidcAuthorizationCode.create!(
|
|
application: @application,
|
|
user: @user,
|
|
code: SecureRandom.urlsafe_base64(32),
|
|
redirect_uri: @application.parsed_redirect_uris.first,
|
|
scope: "openid profile email",
|
|
expires_at: 10.minutes.from_now
|
|
)
|
|
|
|
# Exchange authorization code for tokens
|
|
post "/oauth/token", params: {
|
|
grant_type: "authorization_code",
|
|
code: auth_code.code,
|
|
redirect_uri: @application.parsed_redirect_uris.first,
|
|
client_id: @application.client_id,
|
|
client_secret: @client_secret
|
|
}
|
|
|
|
assert_response :success
|
|
json = JSON.parse(response.body)
|
|
|
|
assert json["access_token"].present?
|
|
assert json["id_token"].present?
|
|
assert json["refresh_token"].present?
|
|
assert_equal "Bearer", json["token_type"]
|
|
assert_equal 3600, json["expires_in"]
|
|
end
|
|
|
|
test "refresh_token grant exchanges refresh token for new tokens" do
|
|
# Create access and refresh tokens
|
|
access_token = OidcAccessToken.create!(
|
|
application: @application,
|
|
user: @user,
|
|
scope: "openid profile email"
|
|
)
|
|
|
|
refresh_token = OidcRefreshToken.create!(
|
|
application: @application,
|
|
user: @user,
|
|
oidc_access_token: access_token,
|
|
scope: "openid profile email"
|
|
)
|
|
|
|
# Store the plaintext refresh token (available only during creation)
|
|
plaintext_refresh_token = refresh_token.token
|
|
|
|
# Use refresh token to get new tokens
|
|
post "/oauth/token", params: {
|
|
grant_type: "refresh_token",
|
|
refresh_token: plaintext_refresh_token,
|
|
client_id: @application.client_id,
|
|
client_secret: @client_secret
|
|
}
|
|
|
|
assert_response :success
|
|
json = JSON.parse(response.body)
|
|
|
|
assert json["access_token"].present?
|
|
assert json["id_token"].present?
|
|
assert json["refresh_token"].present?
|
|
assert_equal "Bearer", json["token_type"]
|
|
|
|
# Old refresh token should be revoked
|
|
assert refresh_token.reload.revoked?
|
|
end
|
|
|
|
test "refresh_token grant fails with expired refresh token" do
|
|
access_token = OidcAccessToken.create!(
|
|
application: @application,
|
|
user: @user,
|
|
scope: "openid profile email"
|
|
)
|
|
|
|
refresh_token = OidcRefreshToken.create!(
|
|
application: @application,
|
|
user: @user,
|
|
oidc_access_token: access_token,
|
|
scope: "openid profile email",
|
|
expires_at: 1.hour.ago # Expired
|
|
)
|
|
|
|
plaintext_refresh_token = refresh_token.token
|
|
|
|
post "/oauth/token", params: {
|
|
grant_type: "refresh_token",
|
|
refresh_token: plaintext_refresh_token,
|
|
client_id: @application.client_id,
|
|
client_secret: @client_secret
|
|
}
|
|
|
|
assert_response :bad_request
|
|
json = JSON.parse(response.body)
|
|
assert_equal "invalid_grant", json["error"]
|
|
end
|
|
|
|
test "refresh_token grant fails with revoked refresh token" do
|
|
access_token = OidcAccessToken.create!(
|
|
application: @application,
|
|
user: @user,
|
|
scope: "openid profile email"
|
|
)
|
|
|
|
refresh_token = OidcRefreshToken.create!(
|
|
application: @application,
|
|
user: @user,
|
|
oidc_access_token: access_token,
|
|
scope: "openid profile email"
|
|
)
|
|
|
|
plaintext_refresh_token = refresh_token.token
|
|
refresh_token.revoke!
|
|
|
|
post "/oauth/token", params: {
|
|
grant_type: "refresh_token",
|
|
refresh_token: plaintext_refresh_token,
|
|
client_id: @application.client_id,
|
|
client_secret: @client_secret
|
|
}
|
|
|
|
assert_response :bad_request
|
|
json = JSON.parse(response.body)
|
|
assert_equal "invalid_grant", json["error"]
|
|
end
|
|
|
|
test "token revocation endpoint revokes access tokens" do
|
|
access_token = OidcAccessToken.create!(
|
|
application: @application,
|
|
user: @user,
|
|
scope: "openid profile email"
|
|
)
|
|
|
|
plaintext_access_token = access_token.plaintext_token
|
|
|
|
post "/oauth/revoke", params: {
|
|
token: plaintext_access_token,
|
|
token_type_hint: "access_token",
|
|
client_id: @application.client_id,
|
|
client_secret: @client_secret
|
|
}
|
|
|
|
assert_response :success
|
|
assert access_token.reload.revoked?
|
|
end
|
|
|
|
test "token revocation endpoint revokes refresh tokens" do
|
|
access_token = OidcAccessToken.create!(
|
|
application: @application,
|
|
user: @user,
|
|
scope: "openid profile email"
|
|
)
|
|
|
|
refresh_token = OidcRefreshToken.create!(
|
|
application: @application,
|
|
user: @user,
|
|
oidc_access_token: access_token,
|
|
scope: "openid profile email"
|
|
)
|
|
|
|
plaintext_refresh_token = refresh_token.token
|
|
|
|
post "/oauth/revoke", params: {
|
|
token: plaintext_refresh_token,
|
|
token_type_hint: "refresh_token",
|
|
client_id: @application.client_id,
|
|
client_secret: @client_secret
|
|
}
|
|
|
|
assert_response :success
|
|
assert refresh_token.reload.revoked?
|
|
end
|
|
|
|
test "token rotation: new refresh token has same family id" do
|
|
access_token = OidcAccessToken.create!(
|
|
application: @application,
|
|
user: @user,
|
|
scope: "openid profile email"
|
|
)
|
|
|
|
old_refresh_token = OidcRefreshToken.create!(
|
|
application: @application,
|
|
user: @user,
|
|
oidc_access_token: access_token,
|
|
scope: "openid profile email"
|
|
)
|
|
|
|
family_id = old_refresh_token.token_family_id
|
|
plaintext_refresh_token = old_refresh_token.token
|
|
|
|
post "/oauth/token", params: {
|
|
grant_type: "refresh_token",
|
|
refresh_token: plaintext_refresh_token,
|
|
client_id: @application.client_id,
|
|
client_secret: @client_secret
|
|
}
|
|
|
|
assert_response :success
|
|
|
|
# Find the new refresh token
|
|
new_refresh_token = OidcRefreshToken.active.where(user: @user, application: @application).last
|
|
assert_equal family_id, new_refresh_token.token_family_id
|
|
end
|
|
|
|
test "userinfo endpoint works with hashed access token" do
|
|
access_token = OidcAccessToken.create!(
|
|
application: @application,
|
|
user: @user,
|
|
scope: "openid profile email"
|
|
)
|
|
|
|
plaintext_token = access_token.plaintext_token
|
|
|
|
get "/oauth/userinfo", headers: {
|
|
"Authorization" => "Bearer #{plaintext_token}"
|
|
}
|
|
|
|
assert_response :success
|
|
json = JSON.parse(response.body)
|
|
assert_equal @user.id.to_s, json["sub"]
|
|
assert_equal @user.email_address, json["email"]
|
|
end
|
|
end
|