grants.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. //! Per-grant `POST /token` branches: form inputs to grant handler, response
  2. //! shaping, and the `grant_type` dispatch table.
  3. use std::time::Duration;
  4. use secrecy::SecretString;
  5. use super::{
  6. Dispatch, TokenError, TokenFormat, TokenResponseBody, basic_header, client_kind, dpop_proof,
  7. enabled_scopes, jwt_signer, require_dpop_binding, require_grant, token_service,
  8. };
  9. use crate::{
  10. db::{user_repo, user_scope_repo},
  11. error::AppError,
  12. protocol::{
  13. client_auth,
  14. device::{DEVICE_GRANT, DEVICE_INTERVAL_SECS},
  15. scope::ScopeSet,
  16. token::{
  17. TokenService,
  18. grants::{
  19. authorization_code::{self, CodeGrantInputs, CodeGrantState},
  20. client_credentials::{self, ClientCredentialsInput},
  21. device_code::{self, DevicePollInputs, PollState},
  22. refresh_token::{self, RefreshInput},
  23. token_exchange::{self, ExchangeInput},
  24. },
  25. },
  26. },
  27. };
  28. /// Dispatch on the parsed `grant_type`.
  29. ///
  30. /// A missing or empty member is the RFC 6749 §5.2 `invalid_request` (the
  31. /// request is missing a required parameter); an unknown value fails closed
  32. /// with `unsupported_grant_type`.
  33. pub(super) async fn dispatch_grant(d: &mut Dispatch<'_>) -> Result<TokenResponseBody, TokenError> {
  34. let Some(grant_type) = d
  35. .form
  36. .grant_type
  37. .as_deref()
  38. .filter(|value| !value.is_empty())
  39. else {
  40. return Err(AppError::invalid_request("grant_type is required").into());
  41. };
  42. match grant_type {
  43. authorization_code::GRANT_TYPE_AUTHORIZATION_CODE => authorization_code(d).await,
  44. refresh_token::GRANT_TYPE_REFRESH_TOKEN => refresh(d).await,
  45. client_credentials::GRANT_TYPE_CLIENT_CREDENTIALS => client_credentials(d).await,
  46. DEVICE_GRANT => device(d).await,
  47. token_exchange::GRANT_TYPE_TOKEN_EXCHANGE => exchange(d).await,
  48. _ => Err(AppError::unsupported_grant_type("the grant type is not supported").into()),
  49. }
  50. }
  51. /// The candidate presenter id for `authorization_code`, mirroring the grant's
  52. /// own resolution so the format gate and the grant agree: Basic wins, and a
  53. /// disagreeing body id is left to the grant's `invalid_client` path.
  54. fn authorization_code_candidate_id(form_id: Option<&str>, basic: Option<&str>) -> Option<String> {
  55. match basic {
  56. Some(header) => {
  57. let creds = client_auth::parse_basic_header(header)?;
  58. match form_id {
  59. Some(body_id) if body_id != creds.client_id => None,
  60. _ => Some(creds.client_id),
  61. }
  62. }
  63. None => form_id.map(str::to_owned),
  64. }
  65. }
  66. /// `grant_type=authorization_code`: the grant authenticates the client itself.
  67. async fn authorization_code(d: &mut Dispatch<'_>) -> Result<TokenResponseBody, TokenError> {
  68. let basic = basic_header(d.headers);
  69. let proof = dpop_proof(d.headers);
  70. let format = d
  71. .gate_candidate_client_format(authorization_code_candidate_id(
  72. d.form.client_id.as_deref(),
  73. basic,
  74. ))
  75. .await?
  76. .unwrap_or(TokenFormat::Opaque);
  77. let jwt = if format == TokenFormat::Jwt {
  78. Some(jwt_signer(d.state)?)
  79. } else {
  80. None
  81. };
  82. let body_secret = d.form.client_secret.clone().map(SecretString::from);
  83. let mut grant = CodeGrantState {
  84. pool: &d.state.pool,
  85. conn: &mut *d.conn,
  86. store: d.store,
  87. keys: &d.state.keys,
  88. issuer: &d.state.config.issuer,
  89. ttl: &d.state.config.ttl,
  90. token_format: format,
  91. jwt_signer: jwt,
  92. cimd: d.cimd,
  93. now_unix: d.now_unix,
  94. };
  95. let inputs = CodeGrantInputs {
  96. code: d.form.code.as_deref().unwrap_or(""),
  97. redirect_uri: d.form.redirect_uri.as_deref().unwrap_or(""),
  98. code_verifier: d.form.code_verifier.as_deref(),
  99. client_id: d.form.client_id.as_deref(),
  100. basic_header: basic,
  101. body_secret,
  102. assertion_type: d.form.client_assertion_type.as_deref(),
  103. assertion: d.form.client_assertion.as_deref(),
  104. dpop_proof: proof,
  105. peer_certs: d.peer_certs,
  106. aud: d.resources,
  107. };
  108. let tokens = authorization_code::redeem_code(&mut grant, &inputs).await?;
  109. Ok(TokenResponseBody {
  110. access_token: tokens.access_token,
  111. token_type: tokens.token_type,
  112. expires_in: tokens.expires_in,
  113. refresh_token: tokens.refresh_token,
  114. scope: tokens.scope,
  115. issued_token_type: None,
  116. })
  117. }
  118. /// `grant_type=refresh_token`: authenticate, then rotate with fresh proofs.
  119. async fn refresh(d: &mut Dispatch<'_>) -> Result<TokenResponseBody, TokenError> {
  120. let authenticated = d.authenticate().await?;
  121. let client = authenticated.client;
  122. require_grant(&client, refresh_token::GRANT_TYPE_REFRESH_TOKEN)?;
  123. let format = d.require_token_format(&client)?;
  124. let kind = client_kind(&client)?;
  125. let proof = d.sender_proof(&client).await?;
  126. require_dpop_binding(&client, &proof)?;
  127. let enabled = enabled_scopes(&d.state.pool).await?;
  128. let mut service = token_service(d.state)?;
  129. if format == TokenFormat::Jwt {
  130. service = service.with_jwt_signer(jwt_signer(d.state)?);
  131. }
  132. let users = refresh_token::RefreshUserStores {
  133. pool: &d.state.pool,
  134. keys: &d.state.keys,
  135. };
  136. let registered = ScopeSet::parse(&client.scope).map_err(|_| AppError::server_error())?;
  137. // The user term is resolved fresh inside `refresh_with_subject_check` from
  138. // the presented token's subject; this placeholder only flows through on the
  139. // unknown-token path, where scope is never minted.
  140. let no_user_term = ScopeSet::empty();
  141. let refreshed = refresh_token::refresh_with_subject_check(
  142. &mut *d.conn,
  143. d.store,
  144. &service,
  145. &users,
  146. &RefreshInput {
  147. client_id: &client.client_id,
  148. client_kind: kind,
  149. dpop_required: client.dpop_bound_access_tokens,
  150. refresh_token: d.form.refresh_token.as_deref().unwrap_or(""),
  151. requested_scope: d.form.scope.as_deref(),
  152. requested_resources: d.resources,
  153. proof: &proof,
  154. enabled: &enabled,
  155. client_registered: &registered,
  156. user_entitled: &no_user_term,
  157. token_format: format,
  158. now_unix: d.now_unix,
  159. },
  160. )
  161. .await?;
  162. Ok(TokenResponseBody {
  163. access_token: refreshed.access_token,
  164. token_type: refreshed.token_type,
  165. expires_in: refreshed.expires_in,
  166. refresh_token: Some(refreshed.refresh_token),
  167. scope: refreshed.scope,
  168. issued_token_type: None,
  169. })
  170. }
  171. /// `grant_type=client_credentials`: confidential clients only, access-only.
  172. async fn client_credentials(d: &mut Dispatch<'_>) -> Result<TokenResponseBody, TokenError> {
  173. let authenticated = d.authenticate().await?;
  174. let client = authenticated.client;
  175. require_grant(&client, client_credentials::GRANT_TYPE_CLIENT_CREDENTIALS)?;
  176. let format = d.require_token_format(&client)?;
  177. let kind = client_kind(&client)?;
  178. let proof = d.sender_proof(&client).await?;
  179. require_dpop_binding(&client, &proof)?;
  180. let registered = ScopeSet::parse(&client.scope).map_err(|_| AppError::server_error())?;
  181. let enabled = enabled_scopes(&d.state.pool).await?;
  182. let mut service = token_service(d.state)?;
  183. if format == TokenFormat::Jwt {
  184. service = service.with_jwt_signer(jwt_signer(d.state)?);
  185. }
  186. let issued = client_credentials::issue_client_credentials(
  187. &mut *d.conn,
  188. d.store,
  189. &service,
  190. &ClientCredentialsInput {
  191. client_id: &client.client_id,
  192. client_kind: kind,
  193. client_registered: &registered,
  194. enabled: &enabled,
  195. requested_scope: d.form.scope.as_deref(),
  196. dpop_bound_jkt: None,
  197. mtls_bound_x5t_s256: client.tls_client_auth_cert_thumbprint.as_deref(),
  198. proof: &proof,
  199. aud: d.resources,
  200. token_format: format,
  201. now_unix: d.now_unix,
  202. },
  203. )
  204. .await?;
  205. Ok(TokenResponseBody {
  206. access_token: issued.access_token,
  207. token_type: issued.token_type,
  208. expires_in: issued.expires_in,
  209. refresh_token: None,
  210. scope: issued.scope,
  211. issued_token_type: None,
  212. })
  213. }
  214. /// `grant_type=urn:ietf:params:oauth:grant-type:device_code`: poll.
  215. async fn device(d: &mut Dispatch<'_>) -> Result<TokenResponseBody, TokenError> {
  216. let basic = basic_header(d.headers);
  217. let proof = dpop_proof(d.headers);
  218. let body_secret = d.form.client_secret.clone().map(SecretString::from);
  219. let client_id = d
  220. .form
  221. .client_id
  222. .clone()
  223. .or_else(|| {
  224. basic.and_then(|header| client_auth::parse_basic_header(header).map(|creds| creds.client_id))
  225. })
  226. .unwrap_or_default();
  227. let candidate = (!client_id.is_empty()).then_some(client_id.clone());
  228. let format = d
  229. .gate_candidate_client_format(candidate)
  230. .await?
  231. .unwrap_or(TokenFormat::Opaque);
  232. let jwt = if format == TokenFormat::Jwt {
  233. Some(jwt_signer(d.state)?)
  234. } else {
  235. None
  236. };
  237. let mut poll = PollState {
  238. pool: &d.state.pool,
  239. conn: &mut *d.conn,
  240. store: d.store,
  241. keys: &d.state.keys,
  242. issuer: &d.state.config.issuer,
  243. device_ttl: d.state.config.ttl.device,
  244. interval: Duration::from_secs(DEVICE_INTERVAL_SECS),
  245. ttl: &d.state.config.ttl,
  246. token_format: format,
  247. jwt_signer: jwt,
  248. cimd: d.cimd,
  249. now_unix: d.now_unix,
  250. };
  251. let inputs = DevicePollInputs {
  252. client_id: &client_id,
  253. device_code: d.form.device_code.as_deref().unwrap_or(""),
  254. basic_header: basic,
  255. body_secret,
  256. assertion_type: d.form.client_assertion_type.as_deref(),
  257. assertion: d.form.client_assertion.as_deref(),
  258. dpop_proof: proof,
  259. peer_certs: d.peer_certs,
  260. aud: d.resources,
  261. };
  262. let tokens = device_code::poll_device(&mut poll, &inputs).await?;
  263. Ok(TokenResponseBody {
  264. access_token: tokens.access_token,
  265. token_type: tokens.token_type,
  266. expires_in: tokens.expires_in,
  267. refresh_token: Some(tokens.refresh_token),
  268. scope: tokens.scope,
  269. issued_token_type: None,
  270. })
  271. }
  272. /// `grant_type=urn:ietf:params:oauth:grant-type:token-exchange`: policy gate.
  273. ///
  274. /// Token exchange mints an access token, so it is subject to the same
  275. /// per-client `access_token_format` gate as every other minting path: a `jwt`
  276. /// caller while the global switch is off fails closed with
  277. /// `temporarily_unavailable` (503) rather than exchanging. A `jwt` caller
  278. /// with the switch on is rejected the same way (Todo 26): exchange never
  279. /// mints an `act` JWT, so the format gate is a rejection, not a mint branch.
  280. async fn exchange(d: &mut Dispatch<'_>) -> Result<TokenResponseBody, TokenError> {
  281. let authenticated = d.authenticate().await?;
  282. let client = authenticated.client;
  283. require_grant(&client, token_exchange::GRANT_TYPE_TOKEN_EXCHANGE)?;
  284. match d.require_token_format(&client)? {
  285. TokenFormat::Opaque => {}
  286. TokenFormat::Jwt => return Err(AppError::temporarily_unavailable().into()),
  287. }
  288. let kind = client_kind(&client)?;
  289. let proof = d.sender_proof(&client).await?;
  290. require_dpop_binding(&client, &proof)?;
  291. let registered = ScopeSet::parse(&client.scope).map_err(|_| AppError::server_error())?;
  292. let enabled = enabled_scopes(&d.state.pool).await?;
  293. let service = token_service(d.state)?;
  294. let subject_token = d.form.subject_token.as_deref().unwrap_or("");
  295. let entitled = subject_entitlements(d, &service, subject_token).await?;
  296. let exchanged = token_exchange::exchange(
  297. &mut *d.conn,
  298. d.store,
  299. &service,
  300. &ExchangeInput {
  301. caller_client_id: &client.client_id,
  302. caller_kind: kind,
  303. caller_dpop_required: client.dpop_bound_access_tokens,
  304. caller_registered: &registered,
  305. subject_token,
  306. subject_token_type: d.form.subject_token_type.as_deref().unwrap_or(""),
  307. actor_token: d.form.actor_token.as_deref(),
  308. actor_token_type: d.form.actor_token_type.as_deref(),
  309. requested_token_type: d.form.requested_token_type.as_deref(),
  310. requested_scope: d.form.scope.as_deref(),
  311. subject_user_entitled: &entitled,
  312. enabled: &enabled,
  313. allow_exchange: d.state.config.allow_token_exchange,
  314. allowlist: &d.state.config.token_exchange_allowlist,
  315. proof: &proof,
  316. now_unix: d.now_unix,
  317. },
  318. )
  319. .await?;
  320. Ok(TokenResponseBody {
  321. access_token: exchanged.access_token,
  322. token_type: exchanged.token_type,
  323. expires_in: token_exchange::exchange_ttl(&service).as_secs(),
  324. refresh_token: None,
  325. scope: exchanged.scope,
  326. issued_token_type: Some(token_exchange::TOKEN_TYPE_ACCESS_TOKEN.to_owned()),
  327. })
  328. }
  329. /// The subject token's user entitlements; empty (fail closed) when the token
  330. /// is unknown, the user is gone, or the subject is a client-credentials
  331. /// principal (bounded by the subject token's own scope).
  332. async fn subject_entitlements(
  333. d: &mut Dispatch<'_>,
  334. service: &TokenService,
  335. subject_token: &str,
  336. ) -> Result<ScopeSet, AppError> {
  337. let Some(found) = service
  338. .find_access(&mut *d.conn, d.store, subject_token)
  339. .await
  340. .map_err(AppError::from)?
  341. else {
  342. return Ok(ScopeSet::empty());
  343. };
  344. let subject_scope = ScopeSet::parse(&found.record.scope).unwrap_or_else(|_| ScopeSet::empty());
  345. if found.record.subject.trim().is_empty() {
  346. return Ok(subject_scope);
  347. }
  348. let Some(user) =
  349. user_repo::find_by_email(&d.state.pool, &d.state.keys, &found.record.subject).await?
  350. else {
  351. return Ok(ScopeSet::empty());
  352. };
  353. if user.disabled {
  354. return Ok(ScopeSet::empty());
  355. }
  356. let names = user_scope_repo::list_for_user(&d.state.pool, user.id).await?;
  357. ScopeSet::from_names(&names)
  358. }