jar_request_uri.rs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. //! `test(jar)` suite, part 2: `request_uri` references.
  2. //!
  3. //! Unknown URIs are never fetched; registered URIs behind the SSRF guard
  4. //! reject private-IP and plaintext targets; PAR URNs consume one-time and
  5. //! bind to the issuing client. No test performs an arbitrary fetch.
  6. #[path = "jar_support.rs"]
  7. mod support;
  8. use std::{
  9. collections::HashMap,
  10. sync::atomic::{AtomicU64, Ordering},
  11. time::Duration,
  12. };
  13. use d_oauth::{
  14. error::OAuthErrorCode,
  15. jose::jar::{JarRequest, PAR_REQUEST_URI_PREFIX, resolve},
  16. store::par::{self, ParRecord},
  17. };
  18. use serde_json::json;
  19. use support::{CLIENT_ID, ISSUER, TestError, TestResult, code_of, store_conn, test_client};
  20. static TEST_NONCE: AtomicU64 = AtomicU64::new(1);
  21. /// Unique PAR id per test so the shared container stays isolated.
  22. fn unique_par_id() -> String {
  23. let n = TEST_NONCE.fetch_add(1, Ordering::Relaxed);
  24. format!("jar-test-{n}")
  25. }
  26. #[tokio::test]
  27. async fn jar_non_registered_request_uri_rejected() -> TestResult {
  28. let (_redis, mut conn, store) = store_conn().await?;
  29. let client = test_client("client_secret_basic", None);
  30. let mut query = HashMap::new();
  31. query.insert("client_id".to_owned(), CLIENT_ID.to_owned());
  32. let uri = "https://127.0.0.1:9/unregistered.jwt";
  33. let input = JarRequest {
  34. client: &client,
  35. issuer: ISSUER,
  36. query: &query,
  37. request: None,
  38. request_uri: Some(uri),
  39. };
  40. let outcome = resolve(&mut conn, &store, &input).await;
  41. if code_of(&outcome) != Some(OAuthErrorCode::InvalidRequestObject) {
  42. return Err(TestError::Unexpected(
  43. "non-registered request_uri accepted".to_owned(),
  44. ));
  45. }
  46. Ok(())
  47. }
  48. #[tokio::test]
  49. async fn jar_registered_request_uri_private_ip_rejected() -> TestResult {
  50. let (_redis, mut conn, store) = store_conn().await?;
  51. let uri = "https://127.0.0.1:9/req.jwt";
  52. let client = test_client("client_secret_basic", Some(json!([uri])));
  53. let query = HashMap::new();
  54. let input = JarRequest {
  55. client: &client,
  56. issuer: ISSUER,
  57. query: &query,
  58. request: None,
  59. request_uri: Some(uri),
  60. };
  61. let outcome = resolve(&mut conn, &store, &input).await;
  62. if code_of(&outcome) != Some(OAuthErrorCode::InvalidRequestObject) {
  63. return Err(TestError::Unexpected(
  64. "private-IP request_uri accepted".to_owned(),
  65. ));
  66. }
  67. Ok(())
  68. }
  69. #[tokio::test]
  70. async fn jar_registered_request_uri_http_rejected() -> TestResult {
  71. let (_redis, mut conn, store) = store_conn().await?;
  72. let uri = "http://127.0.0.1:9/req.jwt";
  73. let client = test_client("client_secret_basic", Some(json!([uri])));
  74. let query = HashMap::new();
  75. let input = JarRequest {
  76. client: &client,
  77. issuer: ISSUER,
  78. query: &query,
  79. request: None,
  80. request_uri: Some(uri),
  81. };
  82. let outcome = resolve(&mut conn, &store, &input).await;
  83. if code_of(&outcome) != Some(OAuthErrorCode::InvalidRequestObject) {
  84. return Err(TestError::Unexpected(
  85. "http request_uri accepted".to_owned(),
  86. ));
  87. }
  88. Ok(())
  89. }
  90. #[tokio::test]
  91. async fn jar_par_request_uri_consumed_once() -> TestResult {
  92. let (_redis, mut conn, store) = store_conn().await?;
  93. let client = test_client("client_secret_basic", None);
  94. let id = unique_par_id();
  95. let record = ParRecord {
  96. client_id: CLIENT_ID.to_owned(),
  97. request: serde_json::to_string(&json!({
  98. "response_type": "code", "client_id": CLIENT_ID,
  99. "redirect_uri": "https://client.example.test/cb", "scope": "read",
  100. }))?,
  101. aud: Vec::new(),
  102. };
  103. par::put(&mut conn, &store, &id, &record, Duration::from_secs(90)).await?;
  104. let uri = format!("{PAR_REQUEST_URI_PREFIX}{id}");
  105. let mut query = HashMap::new();
  106. query.insert("client_id".to_owned(), CLIENT_ID.to_owned());
  107. let input = JarRequest {
  108. client: &client,
  109. issuer: ISSUER,
  110. query: &query,
  111. request: None,
  112. request_uri: Some(&uri),
  113. };
  114. let merged = resolve(&mut conn, &store, &input)
  115. .await
  116. .map_err(|error| TestError::Unexpected(error.to_string()))?;
  117. if merged.params.get("scope").map(String::as_str) != Some("read") {
  118. return Err(TestError::Unexpected("PAR params not merged".to_owned()));
  119. }
  120. let replay = resolve(&mut conn, &store, &input).await;
  121. if code_of(&replay) != Some(OAuthErrorCode::InvalidRequest) {
  122. return Err(TestError::Unexpected("PAR request_uri replayed".to_owned()));
  123. }
  124. Ok(())
  125. }
  126. #[tokio::test]
  127. async fn jar_par_cross_client_rejected() -> TestResult {
  128. let (_redis, mut conn, store) = store_conn().await?;
  129. let mut foreign = test_client("client_secret_basic", None);
  130. foreign.client_id = "jar-foreign-client".to_owned();
  131. let id = unique_par_id();
  132. let record = ParRecord {
  133. client_id: CLIENT_ID.to_owned(),
  134. request: serde_json::to_string(&json!({"response_type": "code"}))?,
  135. aud: Vec::new(),
  136. };
  137. par::put(&mut conn, &store, &id, &record, Duration::from_secs(90)).await?;
  138. let uri = format!("{PAR_REQUEST_URI_PREFIX}{id}");
  139. let query = HashMap::new();
  140. let input = JarRequest {
  141. client: &foreign,
  142. issuer: ISSUER,
  143. query: &query,
  144. request: None,
  145. request_uri: Some(&uri),
  146. };
  147. let outcome = resolve(&mut conn, &store, &input).await;
  148. if code_of(&outcome) != Some(OAuthErrorCode::InvalidRequest) {
  149. return Err(TestError::Unexpected(
  150. "cross-client PAR accepted".to_owned(),
  151. ));
  152. }
  153. Ok(())
  154. }