auth.rs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. //! Revocation request validation: unauthenticated `401`, missing token `400`.
  2. use axum::{
  3. Router,
  4. body::Body,
  5. http::{Request, StatusCode, header},
  6. response::IntoResponse,
  7. routing::post,
  8. };
  9. use http_body_util::BodyExt as _;
  10. use serde_json::Value;
  11. use tower::ServiceExt as _;
  12. use super::{
  13. TestError, TestResult, handler_state, lazy_pool, lazy_redis, no_credentials, service, store,
  14. test_keys,
  15. };
  16. use crate::{
  17. error::OAuthErrorCode,
  18. protocol::revoke::{RevokeContext, RevokeRequest, RevokeStores, revoke_handler, revoke_tokens},
  19. };
  20. /// An unauthenticated request fails closed with `401 invalid_client`.
  21. #[tokio::test]
  22. async fn revoke_unauthenticated_request_is_401() -> TestResult {
  23. let mut conn = lazy_redis()?;
  24. let pool = lazy_pool()?;
  25. let store = store();
  26. let service = service()?;
  27. let auth = no_credentials();
  28. let ring = test_keys()?;
  29. let mut stores = RevokeStores {
  30. pool: &pool,
  31. keys: &ring,
  32. conn: &mut conn,
  33. store: &store,
  34. service: &service,
  35. };
  36. let ctx = RevokeContext {
  37. token_endpoint: "https://auth.example.test/token",
  38. now: time::OffsetDateTime::now_utc(),
  39. request_id: Some("req-revoke-test"),
  40. };
  41. let request = RevokeRequest {
  42. token: "some-token",
  43. token_type_hint: None,
  44. auth: &auth,
  45. };
  46. match revoke_tokens(&mut stores, &ctx, &request).await {
  47. Err(error) => {
  48. if error.code() != OAuthErrorCode::InvalidClient {
  49. return Err(TestError::Unexpected(
  50. "unauthenticated request must be invalid_client".to_owned(),
  51. ));
  52. }
  53. if error.status() != StatusCode::UNAUTHORIZED {
  54. return Err(TestError::Unexpected(
  55. "unauthenticated request must be 401".to_owned(),
  56. ));
  57. }
  58. let response = error.into_response();
  59. if response.status() != StatusCode::UNAUTHORIZED
  60. || response.headers().get(header::WWW_AUTHENTICATE).is_none()
  61. {
  62. return Err(TestError::Unexpected(
  63. "401 must carry a WWW-Authenticate challenge".to_owned(),
  64. ));
  65. }
  66. }
  67. Ok(_) => {
  68. return Err(TestError::Unexpected(
  69. "unauthenticated request must not succeed".to_owned(),
  70. ));
  71. }
  72. }
  73. Ok(())
  74. }
  75. /// A missing `token` is `invalid_request`, not a silent success.
  76. #[tokio::test]
  77. async fn revoke_missing_token_is_invalid_request() -> TestResult {
  78. let mut conn = lazy_redis()?;
  79. let pool = lazy_pool()?;
  80. let store = store();
  81. let service = service()?;
  82. let auth = no_credentials();
  83. let ring = test_keys()?;
  84. let mut stores = RevokeStores {
  85. pool: &pool,
  86. keys: &ring,
  87. conn: &mut conn,
  88. store: &store,
  89. service: &service,
  90. };
  91. let ctx = RevokeContext {
  92. token_endpoint: "https://auth.example.test/token",
  93. now: time::OffsetDateTime::now_utc(),
  94. request_id: None,
  95. };
  96. let request = RevokeRequest {
  97. token: "",
  98. token_type_hint: None,
  99. auth: &auth,
  100. };
  101. match revoke_tokens(&mut stores, &ctx, &request).await {
  102. Err(error) if error.code() == OAuthErrorCode::InvalidRequest => Ok(()),
  103. Err(_) | Ok(_) => Err(TestError::Unexpected(
  104. "missing token must be invalid_request".to_owned(),
  105. )),
  106. }
  107. }
  108. /// A request missing the `token` form field is `400 invalid_request`
  109. /// (RFC 7009 §2.1: `token` is REQUIRED; §2.2.1 defines the error response).
  110. #[tokio::test]
  111. async fn revoke_missing_token_form_is_400_invalid_request() -> TestResult {
  112. let app = Router::new()
  113. .route("/revoke", post(revoke_handler))
  114. .with_state(handler_state()?);
  115. let request = Request::post("/revoke")
  116. .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
  117. .header(header::AUTHORIZATION, "Basic cmV2b2tlLXRlc3Q6c2VjcmV0")
  118. .body(Body::from("token_type_hint=access_token"))
  119. .map_err(|error| TestError::Unexpected(format!("request build failed: {error}")))?;
  120. let response = match app.oneshot(request).await {
  121. Ok(response) => response,
  122. Err(never) => match never {},
  123. };
  124. if response.status() != StatusCode::BAD_REQUEST {
  125. return Err(TestError::Unexpected(format!(
  126. "a missing token parameter must be 400, got {}",
  127. response.status()
  128. )));
  129. }
  130. let bytes = response.into_body().collect().await?.to_bytes();
  131. let body: Value = serde_json::from_slice(&bytes)?;
  132. if body.get("error").and_then(Value::as_str) != Some("invalid_request") {
  133. return Err(TestError::Unexpected(
  134. "a missing token parameter must render invalid_request".to_owned(),
  135. ));
  136. }
  137. Ok(())
  138. }