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}