cascade.rs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. //! Revocation semantics: access-only revoke, refresh cascade, no-op cases.
  2. use axum::http::StatusCode;
  3. use http_body_util::BodyExt as _;
  4. use serde_json::Value;
  5. use super::{TestError, TestResult, mint_request, now_unix, service, test_redis, unique};
  6. use crate::{
  7. protocol::revoke::{
  8. HINT_ACCESS, HINT_REFRESH, OwnedToken, RevokeOutcome, outcome_response, revoke_owned,
  9. },
  10. store::grants,
  11. };
  12. /// Revoking an access token removes it but leaves its sibling refresh token.
  13. #[tokio::test]
  14. async fn revoke_access_token_removes_only_it() -> TestResult {
  15. let (mut conn, store, _prefixes) = test_redis().await?;
  16. let service = service()?;
  17. let client = unique("client");
  18. let minted = service
  19. .mint(
  20. &mut conn,
  21. &store,
  22. &mint_request(&client, &unique("grant")),
  23. now_unix(),
  24. )
  25. .await?;
  26. let outcome = revoke_owned(
  27. &mut conn,
  28. &store,
  29. &service,
  30. &OwnedToken {
  31. owner_client_id: &client,
  32. token: &minted.access_token,
  33. token_type_hint: Some(HINT_ACCESS),
  34. },
  35. )
  36. .await?;
  37. if outcome != RevokeOutcome::Revoked {
  38. return Err(TestError::Unexpected(
  39. "owned access token must be revoked".to_owned(),
  40. ));
  41. }
  42. if service
  43. .find_access(&mut conn, &store, &minted.access_token)
  44. .await?
  45. .is_some()
  46. {
  47. return Err(TestError::Unexpected(
  48. "revoked access token must no longer resolve".to_owned(),
  49. ));
  50. }
  51. if service
  52. .find_refresh(&mut conn, &store, &minted.refresh_token)
  53. .await?
  54. .is_none()
  55. {
  56. return Err(TestError::Unexpected(
  57. "access-only revocation must leave the refresh token".to_owned(),
  58. ));
  59. }
  60. Ok(())
  61. }
  62. /// Revoking a refresh token cascades to the whole grant family.
  63. #[tokio::test]
  64. async fn revoke_refresh_token_cascades_to_grant_chain() -> TestResult {
  65. let (mut conn, store, _prefixes) = test_redis().await?;
  66. let service = service()?;
  67. let client = unique("client");
  68. let grant = unique("grant");
  69. let minted = service
  70. .mint(
  71. &mut conn,
  72. &store,
  73. &mint_request(&client, &grant),
  74. now_unix(),
  75. )
  76. .await?;
  77. let outcome = revoke_owned(
  78. &mut conn,
  79. &store,
  80. &service,
  81. &OwnedToken {
  82. owner_client_id: &client,
  83. token: &minted.refresh_token,
  84. token_type_hint: Some(HINT_REFRESH),
  85. },
  86. )
  87. .await?;
  88. if outcome != RevokeOutcome::Revoked {
  89. return Err(TestError::Unexpected(
  90. "owned refresh token must be revoked".to_owned(),
  91. ));
  92. }
  93. if service
  94. .find_access(&mut conn, &store, &minted.access_token)
  95. .await?
  96. .is_some()
  97. || service
  98. .find_refresh(&mut conn, &store, &minted.refresh_token)
  99. .await?
  100. .is_some()
  101. {
  102. return Err(TestError::Unexpected(
  103. "refresh revocation must kill the chain".to_owned(),
  104. ));
  105. }
  106. if !grants::members(&mut conn, &store, &grant).await?.is_empty() {
  107. return Err(TestError::Unexpected(
  108. "refresh revocation must drop the token index".to_owned(),
  109. ));
  110. }
  111. Ok(())
  112. }
  113. /// An unknown token is a silent `200` no-op.
  114. #[tokio::test]
  115. async fn revoke_unknown_token_is_noop_200() -> TestResult {
  116. let (mut conn, store, _prefixes) = test_redis().await?;
  117. let service = service()?;
  118. let outcome = revoke_owned(
  119. &mut conn,
  120. &store,
  121. &service,
  122. &OwnedToken {
  123. owner_client_id: "client-a",
  124. token: "no-such-token",
  125. token_type_hint: None,
  126. },
  127. )
  128. .await?;
  129. if outcome != RevokeOutcome::NoOp {
  130. return Err(TestError::Unexpected(
  131. "unknown token must be a no-op".to_owned(),
  132. ));
  133. }
  134. if outcome_response(outcome).status() != StatusCode::OK {
  135. return Err(TestError::Unexpected("no-op must render 200".to_owned()));
  136. }
  137. Ok(())
  138. }
  139. /// Another client's token is a no-op and is never revoked (no cross-client).
  140. #[tokio::test]
  141. async fn revoke_foreign_token_is_noop_and_untouched() -> TestResult {
  142. let (mut conn, store, _prefixes) = test_redis().await?;
  143. let service = service()?;
  144. let owner = unique("owner");
  145. let attacker = unique("attacker");
  146. let minted = service
  147. .mint(
  148. &mut conn,
  149. &store,
  150. &mint_request(&owner, &unique("grant")),
  151. now_unix(),
  152. )
  153. .await?;
  154. let outcome = revoke_owned(
  155. &mut conn,
  156. &store,
  157. &service,
  158. &OwnedToken {
  159. owner_client_id: &attacker,
  160. token: &minted.access_token,
  161. token_type_hint: Some(HINT_ACCESS),
  162. },
  163. )
  164. .await?;
  165. if outcome != RevokeOutcome::NoOp {
  166. return Err(TestError::Unexpected(
  167. "a foreign token must be a no-op".to_owned(),
  168. ));
  169. }
  170. if service
  171. .find_access(&mut conn, &store, &minted.access_token)
  172. .await?
  173. .is_none()
  174. {
  175. return Err(TestError::Unexpected(
  176. "a foreign client must never revoke another client's token".to_owned(),
  177. ));
  178. }
  179. Ok(())
  180. }
  181. /// An unsupported `token_type_hint` is `400 unsupported_token_type`.
  182. #[tokio::test]
  183. async fn revoke_unsupported_hint_is_rejected() -> TestResult {
  184. let (mut conn, store, _prefixes) = test_redis().await?;
  185. let service = service()?;
  186. let outcome = revoke_owned(
  187. &mut conn,
  188. &store,
  189. &service,
  190. &OwnedToken {
  191. owner_client_id: "client-a",
  192. token: "some-token",
  193. token_type_hint: Some("password"),
  194. },
  195. )
  196. .await?;
  197. if outcome != RevokeOutcome::UnsupportedHint {
  198. return Err(TestError::Unexpected(
  199. "unknown hint must be unsupported".to_owned(),
  200. ));
  201. }
  202. let response = outcome_response(outcome);
  203. if response.status() != StatusCode::BAD_REQUEST {
  204. return Err(TestError::Unexpected(
  205. "unsupported hint must render 400".to_owned(),
  206. ));
  207. }
  208. let bytes = response.into_body().collect().await?.to_bytes();
  209. let body: Value = serde_json::from_slice(&bytes)?;
  210. if body.get("error").and_then(Value::as_str) != Some("unsupported_token_type") {
  211. return Err(TestError::Unexpected(
  212. "error code must be unsupported_token_type".to_owned(),
  213. ));
  214. }
  215. Ok(())
  216. }
  217. /// A `refresh_token` hint on an access token still revokes it: the hint only
  218. /// guides the lookup (RFC 7009 §2.1), and the located token's real type
  219. /// decides the scope of revocation (revoke.rs: an access token revokes only
  220. /// itself, so the sibling refresh token survives).
  221. #[tokio::test]
  222. async fn revoke_refresh_hint_on_access_token_still_revokes_it() -> TestResult {
  223. let (mut conn, store, _prefixes) = test_redis().await?;
  224. let service = service()?;
  225. let client = unique("client");
  226. let minted = service
  227. .mint(
  228. &mut conn,
  229. &store,
  230. &mint_request(&client, &unique("grant")),
  231. now_unix(),
  232. )
  233. .await?;
  234. let outcome = revoke_owned(
  235. &mut conn,
  236. &store,
  237. &service,
  238. &OwnedToken {
  239. owner_client_id: &client,
  240. token: &minted.access_token,
  241. token_type_hint: Some(HINT_REFRESH),
  242. },
  243. )
  244. .await?;
  245. if outcome != RevokeOutcome::Revoked {
  246. return Err(TestError::Unexpected(
  247. "the misplaced hint must not block revocation".to_owned(),
  248. ));
  249. }
  250. if service
  251. .find_access(&mut conn, &store, &minted.access_token)
  252. .await?
  253. .is_some()
  254. {
  255. return Err(TestError::Unexpected(
  256. "the access token must be gone after revocation".to_owned(),
  257. ));
  258. }
  259. if service
  260. .find_refresh(&mut conn, &store, &minted.refresh_token)
  261. .await?
  262. .is_none()
  263. {
  264. return Err(TestError::Unexpected(
  265. "the hint must not extend revocation beyond an access token".to_owned(),
  266. ));
  267. }
  268. Ok(())
  269. }
  270. /// Revoking another client's refresh token is a silent no-op: `NoOp`, the
  271. /// refresh token stays resolvable, and its access sibling is untouched too.
  272. #[tokio::test]
  273. async fn revoke_foreign_refresh_token_is_noop_and_untouched() -> TestResult {
  274. let (mut conn, store, _prefixes) = test_redis().await?;
  275. let service = service()?;
  276. let owner = unique("owner");
  277. let attacker = unique("attacker");
  278. let minted = service
  279. .mint(
  280. &mut conn,
  281. &store,
  282. &mint_request(&owner, &unique("grant")),
  283. now_unix(),
  284. )
  285. .await?;
  286. let outcome = revoke_owned(
  287. &mut conn,
  288. &store,
  289. &service,
  290. &OwnedToken {
  291. owner_client_id: &attacker,
  292. token: &minted.refresh_token,
  293. token_type_hint: Some(HINT_REFRESH),
  294. },
  295. )
  296. .await?;
  297. if outcome != RevokeOutcome::NoOp {
  298. return Err(TestError::Unexpected(
  299. "a foreign refresh token must be a no-op".to_owned(),
  300. ));
  301. }
  302. if service
  303. .find_refresh(&mut conn, &store, &minted.refresh_token)
  304. .await?
  305. .is_none()
  306. {
  307. return Err(TestError::Unexpected(
  308. "a foreign client must never revoke another client's refresh token".to_owned(),
  309. ));
  310. }
  311. if service
  312. .find_access(&mut conn, &store, &minted.access_token)
  313. .await?
  314. .is_none()
  315. {
  316. return Err(TestError::Unexpected(
  317. "the sibling access token must also survive a foreign attempt".to_owned(),
  318. ));
  319. }
  320. Ok(())
  321. }