oxide_auth/code_grant/refresh.rs
1//! Retrieve a refreshed access token.
2use std::borrow::Cow;
3use std::collections::HashMap;
4
5use chrono::{Duration, Utc};
6
7use crate::code_grant::{
8 accesstoken::TokenResponse,
9 error::{AccessTokenError, AccessTokenErrorType},
10};
11use crate::primitives::grant::Grant;
12use crate::primitives::issuer::{RefreshedToken, Issuer};
13use crate::primitives::registrar::{Registrar, RegistrarError};
14
15/// Required content of a refresh request.
16///
17/// See [Refreshing an Access Token] in the rfc.
18///
19/// [Refreshing an Access Token]: https://tools.ietf.org/html/rfc6749#section-6
20pub trait Request {
21 /// Received request might not be encoded correctly. This method gives implementors the chance
22 /// to signal that a request was received but its encoding was generally malformed. If this is
23 /// the case, then no other attribute will be queried. This method exists mainly to make
24 /// frontends straightforward by not having them handle special cases for malformed requests.
25 fn valid(&self) -> bool;
26
27 /// The refresh token with which to refresh.
28 fn refresh_token(&self) -> Option<Cow<str>>;
29
30 /// Optionally specifies the requested scope
31 fn scope(&self) -> Option<Cow<str>>;
32
33 /// Valid requests have this set to "refresh_token"
34 fn grant_type(&self) -> Option<Cow<str>>;
35
36 /// User:password of a basic authorization header.
37 fn authorization(&self) -> Option<(Cow<str>, Cow<[u8]>)>;
38
39 /// Retrieve an additional parameter used in an extension
40 fn extension(&self, key: &str) -> Option<Cow<str>>;
41}
42
43/// The specific endpoint trait for refreshing.
44///
45/// Each method will only be invoked exactly once when processing a correct and authorized request,
46/// and potentially less than once when the request is faulty. These methods should be implemented
47/// by internally using `primitives`, as it is implemented in the `frontend` module.
48///
49/// This is the utility trait used by [`refresh`] to provide a simple loop around the [`Refresh`]
50/// state machine, the trait objects returned are used to fulfill the input requests.
51///
52/// [`refresh`]: method.refresh.html
53/// [`Refresh`]: struct.Refresh.html
54pub trait Endpoint {
55 /// Authenticate the requesting confidential client.
56 fn registrar(&self) -> &dyn Registrar;
57
58 /// Recover and test the provided refresh token then issue new tokens.
59 fn issuer(&mut self) -> &mut dyn Issuer;
60}
61
62/// Represents a bearer token, optional refresh token and the associated scope for serialization.
63#[derive(Debug)]
64pub struct BearerToken(RefreshedToken, String);
65
66/// An ongoing refresh request.
67///
68/// This is a rather linear Mealy machine with four basic phases. It will pose some requests in the
69/// form of [`Output`] which should be satisfied with the next [`Input`] data. This will eventually
70/// produce a refreshed [`BearerToken`] or an [`Error`]. Note that the executing environment will
71/// need to use a [`Registrar`] and an [`Issuer`] to which some requests should be forwarded.
72///
73/// [`Input`]: struct.Input.html
74/// [`Output`]: struct.Output.html
75/// [`BearerToken`]: struct.BearerToken.html
76/// [`Error`]: struct.Error.html
77/// [`Issuer`] ../primitives/issuer/trait.Issuer.html
78/// [`Registrar`] ../primitives/registrar/trait.Registrar.html
79///
80/// A rough sketch of the operational phases:
81///
82/// 1. Ensure the request is valid based on the basic requirements (includes required parameters)
83/// 2. Check any included authentication
84/// 3. Try to recover the refresh token
85/// 3.1. Check that it belongs to the authenticated client
86/// 3.2. If there was no authentication, assert token does not require authentication
87/// 3.3. Check the intrinsic validity (timestamp, scope)
88/// 4. Query the backend for a renewed (bearer) token
89#[derive(Debug)]
90pub struct Refresh {
91 state: RefreshState,
92}
93
94/// Inner state machine for refreshing.
95#[derive(Debug)]
96enum RefreshState {
97 /// State we reach after the request has been validated.
98 ///
99 /// Next, the registrar must verify the authentication (authorization header).
100 Authenticating {
101 client: String,
102 passdata: Option<Vec<u8>>,
103 token: String,
104 },
105 /// State after authorization has passed, waiting on recovering the refresh token.
106 Recovering {
107 /// The user the registrar verified.
108 authenticated: Option<String>,
109 token: String,
110 },
111 /// State after the token has been determined but no authenticated client was used. Need to
112 /// potentially wait on grant-to-authorized-user-correspondence matching.
113 CoAuthenticating {
114 /// The restored grant.
115 grant: Box<Grant>,
116 /// The refresh token of the grant.
117 token: String,
118 },
119 /// State when we await the issuing of a refreshed token.
120 Issuing {
121 /// The grant with the parameter set.
122 grant: Box<Grant>,
123 /// The refresh token of the grant.
124 token: String,
125 },
126 /// State after an error occurred.
127 Err(Error),
128}
129
130/// An input injected by the executor into the state machine.
131#[derive(Clone)]
132pub enum Input<'req> {
133 /// Positively answer an authentication query.
134 Authenticated {
135 /// The required scope to access the resource.
136 scope: Option<Cow<'req, str>>,
137 },
138 /// Provide the queried refresh token.
139 Recovered {
140 /// The required scope to access the resource.
141 scope: Option<Cow<'req, str>>,
142 /// The grant
143 grant: Option<Box<Grant>>,
144 },
145 /// The refreshed token.
146 Refreshed(RefreshedToken),
147 /// Advance without input as far as possible, or just retrieve the output again.
148 None,
149}
150
151/// A request by the statemachine to the executor.
152///
153/// Each variant is fulfilled by certain variants of the next inputs as an argument to
154/// `Refresh::next`. The output of most states is simply repeated if `Input::None` is provided
155/// instead but note that the successful bearer token response is **not** repeated.
156///
157/// This borrows data from the underlying state machine, so you need to drop it before advancing it
158/// with newly provided input.
159#[derive(Debug)]
160pub enum Output<'a> {
161 /// The registrar should authenticate a client.
162 ///
163 /// Fulfilled by `Input::Authenticated`. In an unsuccessful case, the executor should not
164 /// continue and discard the flow.
165 Unauthenticated {
166 /// The to-be-authenticated client.
167 client: &'a str,
168 /// The supplied passdata/password.
169 pass: Option<&'a [u8]>,
170 },
171 /// The issuer should try to recover the grant of a refresh token.
172 ///
173 /// Fulfilled by `Input::Recovered`.
174 RecoverRefresh {
175 /// The token supplied by the client.
176 token: &'a str,
177 },
178 /// The issuer should issue a refreshed code grant token.
179 ///
180 /// Fulfilled by `Input::Refreshed`.
181 Refresh {
182 /// The refresh token that has been used.
183 token: &'a str,
184 /// The grant that should be issued as determined.
185 grant: Box<Grant>,
186 },
187 /// The state machine finished and a new bearer token was generated.
188 ///
189 /// This output **can not** be requested repeatedly, any future `Input` will yield a primitive
190 /// error instead.
191 Ok(BearerToken),
192 /// The state machine finished in an error.
193 ///
194 /// The error will be repeated on *any* following input.
195 Err(Error),
196}
197
198/// Defines actions for the response to an access token request.
199#[derive(Clone, Debug)]
200pub enum Error {
201 /// The token did not represent a valid token.
202 Invalid(ErrorDescription),
203
204 /// The client did not properly authorize itself.
205 Unauthorized(ErrorDescription, String),
206
207 /// An underlying primitive operation did not complete successfully.
208 ///
209 /// This is expected to occur with some endpoints. See `PrimitiveError` for
210 /// more details on when this is returned.
211 Primitive,
212}
213
214/// Simple wrapper around RefreshError.
215///
216/// Enables additional json functionality to generate a properly formatted response in the user of
217/// this module.
218#[derive(Clone, Debug)]
219pub struct ErrorDescription {
220 pub(crate) error: AccessTokenError,
221}
222
223type Result<T> = std::result::Result<T, Error>;
224
225impl Refresh {
226 /// Construct a new refresh state machine.
227 ///
228 /// This borrows the request for the duration of the request execution to ensure consistency of
229 /// all client input.
230 pub fn new(request: &dyn Request) -> Self {
231 Refresh {
232 state: initialize(request).unwrap_or_else(RefreshState::Err),
233 }
234 }
235
236 /// Advance the state machine.
237 ///
238 /// The provided `Input` needs to fulfill the *previous* `Output` request. See their
239 /// documentation for more information.
240 pub fn advance<'req>(&mut self, input: Input<'req>) -> Output<'_> {
241 // Run the next state transition if we got the right input. Errors that happen will be
242 // stored as a inescapable error state.
243 match (self.take(), input) {
244 (RefreshState::Err(error), _) => {
245 self.state = RefreshState::Err(error.clone());
246 Output::Err(error)
247 }
248 (
249 RefreshState::Authenticating {
250 client,
251 passdata: _,
252 token,
253 },
254 Input::Authenticated { .. },
255 ) => {
256 self.state = authenticated(client, token);
257 self.output()
258 }
259 (RefreshState::Recovering { authenticated, token }, Input::Recovered { scope, grant }) => {
260 self.state = recovered_refresh(scope, authenticated, grant, token)
261 .unwrap_or_else(RefreshState::Err);
262 self.output()
263 }
264 (RefreshState::CoAuthenticating { grant, token }, Input::Authenticated { scope }) => {
265 self.state = co_authenticated(scope, grant, token).unwrap_or_else(RefreshState::Err);
266 self.output()
267 }
268 (RefreshState::Issuing { grant, token: _ }, Input::Refreshed(token)) => {
269 // Ensure that this result is not duplicated.
270 self.state = RefreshState::Err(Error::Primitive);
271 Output::Ok(issued(grant, token))
272 }
273 (current, Input::None) => {
274 match current {
275 RefreshState::Authenticating { .. } => self.state = current,
276 RefreshState::Recovering { .. } => self.state = current,
277 RefreshState::CoAuthenticating { .. } => (),
278 RefreshState::Issuing { .. } => (),
279 RefreshState::Err(_) => (),
280 }
281 self.output()
282 }
283 (_, _) => {
284 self.state = RefreshState::Err(Error::Primitive);
285 self.output()
286 }
287 }
288 }
289
290 fn take(&mut self) -> RefreshState {
291 core::mem::replace(&mut self.state, RefreshState::Err(Error::Primitive))
292 }
293
294 fn output(&self) -> Output<'_> {
295 match &self.state {
296 RefreshState::Authenticating { client, passdata, .. } => Output::Unauthenticated {
297 client,
298 pass: passdata.as_ref().map(|vec| vec.as_slice()),
299 },
300 RefreshState::CoAuthenticating { grant, .. } => Output::Unauthenticated {
301 client: &grant.client_id,
302 pass: None,
303 },
304 RefreshState::Recovering { token, .. } => Output::RecoverRefresh { token: &token },
305 RefreshState::Issuing { token, grant, .. } => Output::Refresh {
306 token,
307 grant: grant.clone(),
308 },
309 RefreshState::Err(error) => Output::Err(error.clone()),
310 }
311 }
312}
313
314impl<'req> Input<'req> {
315 /// Take the current value of Input and replace it with `Input::None`
316 pub fn take(&mut self) -> Self {
317 core::mem::replace(self, Input::None)
318 }
319}
320
321/// Try to get a refreshed access token.
322///
323/// This has four basic phases:
324/// 1. Ensure the request is valid based on the basic requirements (includes required parameters)
325/// 2. Check any included authentication
326/// 3. Try to recover the refresh token
327/// 3.1. Check that it belongs to the authenticated client
328/// 3.2. If there was no authentication, assert token does not require authentication
329/// 3.3. Check the intrinsic validity (timestamp, scope)
330/// 4. Query the backend for a renewed (bearer) token
331pub fn refresh(handler: &mut dyn Endpoint, request: &dyn Request) -> Result<BearerToken> {
332 enum Requested {
333 None,
334 Refresh { token: String, grant: Box<Grant> },
335 RecoverRefresh { token: String },
336 Authenticate { client: String, pass: Option<Vec<u8>> },
337 }
338 let mut refresh = Refresh::new(request);
339 let mut requested = Requested::None;
340 loop {
341 let input = match requested {
342 Requested::None => Input::None,
343 Requested::Refresh { token, grant } => {
344 let refreshed = handler
345 .issuer()
346 .refresh(&token, *grant)
347 .map_err(|()| Error::Primitive)?;
348 Input::Refreshed(refreshed)
349 }
350 Requested::RecoverRefresh { token } => {
351 let recovered = handler
352 .issuer()
353 .recover_refresh(&token)
354 .map_err(|()| Error::Primitive)?;
355 Input::Recovered {
356 scope: request.scope(),
357 grant: recovered.map(Box::new),
358 }
359 }
360 Requested::Authenticate { client, pass } => {
361 let _: () =
362 handler
363 .registrar()
364 .check(&client, pass.as_deref())
365 .map_err(|err| match err {
366 RegistrarError::PrimitiveError => Error::Primitive,
367 RegistrarError::Unspecified => Error::unauthorized("basic"),
368 })?;
369 Input::Authenticated {
370 scope: request.scope(),
371 }
372 }
373 };
374
375 requested = match refresh.advance(input) {
376 Output::Err(error) => return Err(error),
377 Output::Ok(token) => return Ok(token),
378 Output::Refresh { token, grant } => Requested::Refresh {
379 token: token.to_string(),
380 grant,
381 },
382 Output::RecoverRefresh { token } => Requested::RecoverRefresh {
383 token: token.to_string(),
384 },
385 Output::Unauthenticated { client, pass } => Requested::Authenticate {
386 client: client.to_string(),
387 pass: pass.map(|p| p.to_vec()),
388 },
389 };
390 }
391}
392
393fn initialize(request: &dyn Request) -> Result<RefreshState> {
394 if !request.valid() {
395 return Err(Error::invalid(AccessTokenErrorType::InvalidRequest));
396 }
397
398 // REQUIRED, so not having it makes it an invalid request.
399 let token = request.refresh_token();
400 let token = token.ok_or_else(|| Error::invalid(AccessTokenErrorType::InvalidRequest))?;
401
402 // REQUIRED, otherwise invalid request.
403 match request.grant_type() {
404 Some(ref cow) if cow == "refresh_token" => (),
405 None => return Err(Error::invalid(AccessTokenErrorType::InvalidRequest)),
406 Some(_) => return Err(Error::invalid(AccessTokenErrorType::UnsupportedGrantType)),
407 };
408
409 match request.authorization() {
410 Some((client, passdata)) => Ok(RefreshState::Authenticating {
411 client: client.into_owned(),
412 passdata: Some(passdata.to_vec()),
413 token: token.into_owned(),
414 }),
415 None => Ok(RefreshState::Recovering {
416 token: token.into_owned(),
417 authenticated: None,
418 }),
419 }
420}
421
422fn authenticated(client: String, token: String) -> RefreshState {
423 // Trivial, simply advance to recovering the token.
424 RefreshState::Recovering {
425 token,
426 authenticated: Some(client),
427 }
428}
429
430fn recovered_refresh(
431 scope: Option<Cow<str>>, authenticated: Option<String>, grant: Option<Box<Grant>>, token: String,
432) -> Result<RefreshState> {
433 let grant = grant
434 // ... is invalid, ... (Section 5.2)
435 .ok_or_else(|| Error::invalid(AccessTokenErrorType::InvalidGrant))?;
436
437 // ... MUST ensure that the refresh token was issued to the authenticated client.
438 match authenticated {
439 Some(client) => {
440 if grant.client_id.as_str() != client {
441 // ... or was issued to another client (Section 5.2)
442 // importantly, the client authentication itself was okay, so we don't respond with
443 // Unauthorized but with BadRequest.
444 Err(Error::invalid(AccessTokenErrorType::InvalidGrant))
445 } else {
446 validate(scope, grant, token)
447 }
448 }
449
450 // ... MUST require client authentication for confidential clients.
451 //
452 // We'll see if this was confidential by trying to auth with no passdata. If that fails,
453 // then the client should have authenticated with header information.
454 None => Ok(RefreshState::CoAuthenticating { grant, token }),
455 }
456}
457
458fn co_authenticated(scope: Option<Cow<str>>, grant: Box<Grant>, token: String) -> Result<RefreshState> {
459 validate(scope, grant, token)
460}
461
462fn validate(scope: Option<Cow<str>>, grant: Box<Grant>, token: String) -> Result<RefreshState> {
463 // .. is expired, revoked, ... (Section 5.2)
464 if grant.until <= Utc::now() {
465 return Err(Error::invalid(AccessTokenErrorType::InvalidGrant));
466 }
467
468 let scope = match scope {
469 // ... is invalid, unknown, malformed (Section 5.2)
470 Some(scope) => Some(
471 scope
472 .parse()
473 .map_err(|_| Error::invalid(AccessTokenErrorType::InvalidScope))?,
474 ),
475 None => None,
476 };
477
478 let scope = match scope {
479 Some(scope) => {
480 // ... MUST NOT include any scope not originally granted.
481 if !grant.scope.priviledged_to(&scope) {
482 // ... or exceeds the scope grant (Section 5.2)
483 return Err(Error::invalid(AccessTokenErrorType::InvalidScope));
484 }
485 scope
486 }
487 // ... if omitted is treated as equal to the scope originally granted
488 None => grant.scope.clone(),
489 };
490
491 // Update the grant with the derived data.
492 let mut grant = grant;
493 grant.scope = scope;
494 grant.until = Utc::now() + Duration::hours(1);
495
496 Ok(RefreshState::Issuing { grant, token })
497}
498
499fn issued(grant: Box<Grant>, token: RefreshedToken) -> BearerToken {
500 BearerToken(token, grant.scope.to_string())
501}
502
503impl Error {
504 fn invalid(kind: AccessTokenErrorType) -> Self {
505 Error::Invalid(ErrorDescription {
506 error: AccessTokenError::new(kind),
507 })
508 }
509
510 /// Create unauthorized error type
511 pub fn unauthorized(authtype: &str) -> Self {
512 Error::Unauthorized(
513 ErrorDescription {
514 // ... authentication failed (Section 5.2)
515 error: AccessTokenError::new(AccessTokenErrorType::InvalidClient),
516 },
517 authtype.to_string(),
518 )
519 }
520
521 /// Get a handle to the description the client will receive.
522 ///
523 /// Some types of this error don't return any description which is represented by a `None`
524 /// result.
525 pub fn description(&mut self) -> Option<&mut AccessTokenError> {
526 match self {
527 Error::Invalid(description) => Some(description.description()),
528 Error::Unauthorized(description, _) => Some(description.description()),
529 Error::Primitive => None,
530 }
531 }
532}
533
534impl ErrorDescription {
535 /// Get a handle to the description the client will receive.
536 pub fn description(&mut self) -> &mut AccessTokenError {
537 &mut self.error
538 }
539
540 /// Convert the error into a json string.
541 ///
542 /// The string may be the content of an `application/json` body for example.
543 pub fn to_json(&self) -> String {
544 let asmap = self
545 .error
546 .iter()
547 .map(|(k, v)| (k.to_string(), v.into_owned()))
548 .collect::<HashMap<String, String>>();
549 serde_json::to_string(&asmap).unwrap()
550 }
551}
552
553impl BearerToken {
554 /// Convert the token into a json string, viable for being sent over a network with
555 /// `application/json` encoding.
556 pub fn to_json(&self) -> String {
557 let remaining = self.0.until.signed_duration_since(Utc::now());
558 let token_response = TokenResponse {
559 access_token: Some(self.0.token.clone()),
560 refresh_token: self.0.refresh.clone(),
561 token_type: Some("bearer".to_owned()),
562 expires_in: Some(remaining.num_seconds()),
563 scope: Some(self.1.clone()),
564 error: None,
565 };
566
567 serde_json::to_string(&token_response).unwrap()
568 }
569}