tests_delete.rs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. //! `test(dcr_manage)` delete suite: cross-client denial, DELETE, revocation,
  2. //! and Redis-loss survival.
  3. use uuid::Uuid;
  4. use super::delete::delete_client;
  5. use super::read_client;
  6. use super::support::{
  7. TestError, TestResult, expect_forbidden, expect_unauthorized, harness, manage_parts, register,
  8. };
  9. use crate::db::client_repo;
  10. use crate::protocol::token::{ClientKind, MintRequest, SenderProof, TokenService};
  11. #[tokio::test]
  12. async fn dcr_manage_cross_client_forbidden() -> TestResult {
  13. let (_guard, mut harness) = harness("catalog").await?;
  14. let first = register(&mut harness, "https://app.example.test/a", None).await?;
  15. let second = register(&mut harness, "https://app.example.test/b", None).await?;
  16. let request = crate::protocol::dcr::RegisterRequest {
  17. redirect_uris: Some(vec!["https://app.example.test/b".to_owned()]),
  18. client_name: Some("attacker rename".to_owned()),
  19. token_endpoint_auth_method: Some("client_secret_basic".to_owned()),
  20. grant_types: Some(vec!["authorization_code".to_owned()]),
  21. response_types: Some(vec!["code".to_owned()]),
  22. scope: None,
  23. jwks: None,
  24. jwks_uri: None,
  25. tls_client_auth_subject_dn: None,
  26. dpop_bound_access_tokens: Some(true),
  27. contacts: None,
  28. request_uris: None,
  29. software_id: None,
  30. };
  31. let (mut stores, ctx) = manage_parts(&mut harness);
  32. match read_client(&mut stores, &ctx, &second.client_id, Some(&first.reg_token)).await {
  33. Ok(_) => {
  34. return Err(TestError::Unexpected(
  35. "cross-client read succeeded".to_owned(),
  36. ));
  37. }
  38. Err(error) => expect_forbidden(error)?,
  39. }
  40. let (mut stores, ctx) = manage_parts(&mut harness);
  41. match super::update::update_client(
  42. &mut stores,
  43. &ctx,
  44. &second.client_id,
  45. Some(&first.reg_token),
  46. &request,
  47. )
  48. .await
  49. {
  50. Ok(_) => {
  51. return Err(TestError::Unexpected(
  52. "cross-client update succeeded".to_owned(),
  53. ));
  54. }
  55. Err(error) => expect_forbidden(error)?,
  56. }
  57. let (mut stores, ctx) = manage_parts(&mut harness);
  58. match delete_client(&mut stores, &ctx, &second.client_id, Some(&first.reg_token)).await {
  59. Ok(()) => {
  60. return Err(TestError::Unexpected(
  61. "cross-client delete succeeded".to_owned(),
  62. ));
  63. }
  64. Err(error) => expect_forbidden(error)?,
  65. }
  66. let row =
  67. client_repo::find_active_by_client_id(&harness.pool, &harness.config.keys, &second.client_id)
  68. .await?;
  69. if row.is_none() {
  70. return Err(TestError::Unexpected(
  71. "victim client changed by cross-client calls".to_owned(),
  72. ));
  73. }
  74. Ok(())
  75. }
  76. #[tokio::test]
  77. async fn dcr_manage_delete_deactivates() -> TestResult {
  78. let (_guard, mut harness) = harness("catalog").await?;
  79. let registered = register(&mut harness, "https://app.example.test/cb", None).await?;
  80. {
  81. let (mut stores, ctx) = manage_parts(&mut harness);
  82. delete_client(
  83. &mut stores,
  84. &ctx,
  85. &registered.client_id,
  86. Some(&registered.reg_token),
  87. )
  88. .await
  89. .map_err(|error| TestError::Unexpected(format!("delete failed: {error:?}")))?;
  90. }
  91. if client_repo::find_active_by_client_id(
  92. &harness.pool,
  93. &harness.config.keys,
  94. &registered.client_id,
  95. )
  96. .await?
  97. .is_some()
  98. {
  99. return Err(TestError::Unexpected(
  100. "deleted client still active".to_owned(),
  101. ));
  102. }
  103. let listed = client_repo::list(&harness.pool, &harness.config.keys, true).await?;
  104. let kept = listed
  105. .iter()
  106. .find(|row| row.client_id == registered.client_id)
  107. .ok_or_else(|| TestError::Unexpected("deleted row missing from list".to_owned()))?;
  108. if kept.active {
  109. return Err(TestError::Unexpected(
  110. "deleted row still flagged active".to_owned(),
  111. ));
  112. }
  113. let (mut stores, ctx) = manage_parts(&mut harness);
  114. match read_client(
  115. &mut stores,
  116. &ctx,
  117. &registered.client_id,
  118. Some(&registered.reg_token),
  119. )
  120. .await
  121. {
  122. Ok(_) => Err(TestError::Unexpected(
  123. "read after delete succeeded".to_owned(),
  124. )),
  125. Err(error) => expect_unauthorized(error),
  126. }
  127. }
  128. #[tokio::test]
  129. async fn dcr_manage_delete_revokes_tokens() -> TestResult {
  130. let (_guard, mut harness) = harness("catalog").await?;
  131. let registered = register(&mut harness, "https://app.example.test/cb", None).await?;
  132. let other = register(&mut harness, "https://app.example.test/other", None).await?;
  133. let service = TokenService::new(&harness.config.ttl, &harness.config.keys)
  134. .map_err(|error| TestError::Unexpected(format!("token service: {error}")))?;
  135. let now = time::OffsetDateTime::now_utc().unix_timestamp();
  136. let mut victim_hashes: Vec<String> = Vec::new();
  137. for _ in 0..2 {
  138. let minted = service
  139. .mint(
  140. &mut harness.conn,
  141. &harness.store,
  142. &MintRequest {
  143. client_id: registered.client_id.clone(),
  144. client_kind: ClientKind::Confidential,
  145. subject: String::new(),
  146. scope: String::new(),
  147. grant_id: Uuid::new_v4().to_string(),
  148. proof: SenderProof::none(),
  149. },
  150. now,
  151. )
  152. .await
  153. .map_err(|error| TestError::Unexpected(format!("mint failed: {error}")))?;
  154. victim_hashes.push(minted.access_hash);
  155. victim_hashes.push(minted.refresh_hash);
  156. }
  157. let survivor = service
  158. .mint(
  159. &mut harness.conn,
  160. &harness.store,
  161. &MintRequest {
  162. client_id: other.client_id.clone(),
  163. client_kind: ClientKind::Confidential,
  164. subject: String::new(),
  165. scope: String::new(),
  166. grant_id: Uuid::new_v4().to_string(),
  167. proof: SenderProof::none(),
  168. },
  169. now,
  170. )
  171. .await
  172. .map_err(|error| TestError::Unexpected(format!("mint failed: {error}")))?;
  173. {
  174. let (mut stores, ctx) = manage_parts(&mut harness);
  175. delete_client(
  176. &mut stores,
  177. &ctx,
  178. &registered.client_id,
  179. Some(&registered.reg_token),
  180. )
  181. .await
  182. .map_err(|error| TestError::Unexpected(format!("delete failed: {error:?}")))?;
  183. }
  184. for hash in &victim_hashes {
  185. let access = crate::store::tokens::get_access(&mut harness.conn, &harness.store, hash)
  186. .await
  187. .map_err(|error| TestError::Unexpected(format!("lookup failed: {error}")))?;
  188. let refresh = crate::store::tokens::get_refresh(&mut harness.conn, &harness.store, hash)
  189. .await
  190. .map_err(|error| TestError::Unexpected(format!("lookup failed: {error}")))?;
  191. if access.is_some() || refresh.is_some() {
  192. return Err(TestError::Unexpected(
  193. "deleted client's token survived".to_owned(),
  194. ));
  195. }
  196. }
  197. let live =
  198. crate::store::tokens::get_access(&mut harness.conn, &harness.store, &survivor.access_hash)
  199. .await
  200. .map_err(|error| TestError::Unexpected(format!("lookup failed: {error}")))?;
  201. if live.is_none() {
  202. return Err(TestError::Unexpected(
  203. "unrelated client's token was revoked".to_owned(),
  204. ));
  205. }
  206. Ok(())
  207. }
  208. #[tokio::test]
  209. async fn dcr_manage_token_survives_redis_flush() -> TestResult {
  210. let (_guard, mut harness) = harness("catalog").await?;
  211. let registered = register(&mut harness, "https://app.example.test/cb", None).await?;
  212. {
  213. let (mut stores, ctx) = manage_parts(&mut harness);
  214. read_client(
  215. &mut stores,
  216. &ctx,
  217. &registered.client_id,
  218. Some(&registered.reg_token),
  219. )
  220. .await
  221. .map_err(|error| TestError::Unexpected(format!("pre-flush read failed: {error:?}")))?;
  222. }
  223. let _: () = redis::cmd("FLUSHALL")
  224. .query_async(&mut harness.conn)
  225. .await?;
  226. {
  227. let (mut stores, ctx) = manage_parts(&mut harness);
  228. let reread = read_client(
  229. &mut stores,
  230. &ctx,
  231. &registered.client_id,
  232. Some(&registered.reg_token),
  233. )
  234. .await
  235. .map_err(|error| TestError::Unexpected(format!("post-flush read failed: {error:?}")))?;
  236. if reread.client_id != registered.client_id {
  237. return Err(TestError::Unexpected(format!(
  238. "wrong client after flush: {}",
  239. reread.client_id
  240. )));
  241. }
  242. }
  243. Ok(())
  244. }