oauth2/lib.rs
1#![warn(missing_docs)]
2//!
3//! An extensible, strongly-typed implementation of OAuth2
4//! ([RFC 6749](https://tools.ietf.org/html/rfc6749)) including token introspection ([RFC 7662](https://tools.ietf.org/html/rfc7662))
5//! and token revocation ([RFC 7009](https://tools.ietf.org/html/rfc7009)).
6//!
7//! # Contents
8//! * [Importing `oauth2`: selecting an HTTP client interface](#importing-oauth2-selecting-an-http-client-interface)
9//! * [Getting started: Authorization Code Grant w/ PKCE](#getting-started-authorization-code-grant-w-pkce)
10//! * [Example: Synchronous (blocking) API](#example-synchronous-blocking-api)
11//! * [Example: Asynchronous API](#example-asynchronous-api)
12//! * [Implicit Grant](#implicit-grant)
13//! * [Resource Owner Password Credentials Grant](#resource-owner-password-credentials-grant)
14//! * [Client Credentials Grant](#client-credentials-grant)
15//! * [Device Code Flow](#device-code-flow)
16//! * [Other examples](#other-examples)
17//! * [Contributed Examples](#contributed-examples)
18//!
19//! # Importing `oauth2`: selecting an HTTP client interface
20//!
21//! This library offers a flexible HTTP client interface with two modes:
22//! * **Synchronous (blocking)**
23//! * **Asynchronous**
24//!
25//! For the HTTP client modes described above, the following HTTP client implementations can be
26//! used:
27//! * **[`reqwest`]**
28//!
29//! The `reqwest` HTTP client supports both the synchronous and asynchronous modes and is enabled
30//! by default.
31//!
32//! Synchronous client: [`reqwest::http_client`]
33//!
34//! Asynchronous client: [`reqwest::async_http_client`]
35//!
36//! * **[`curl`]**
37//!
38//! The `curl` HTTP client only supports the synchronous HTTP client mode and can be enabled in
39//! `Cargo.toml` via the `curl` feature flag.
40//!
41//! Synchronous client: [`curl::http_client`]
42//!
43//! * **[`ureq`]**
44//!
45//! The `ureq` HTTP client is a simple HTTP client with minimal dependencies. It only supports
46//! the synchronous HTTP client mode and can be enabled in `Cargo.toml` via the `ureq` feature
47//! flag.
48//!
49//! * **Custom**
50//!
51//! In addition to the clients above, users may define their own HTTP clients, which must accept
52//! an [`HttpRequest`] and return an [`HttpResponse`] or error. Users writing their own clients
53//! may wish to disable the default `reqwest` dependency by specifying
54//! `default-features = false` in `Cargo.toml` (replacing `...` with the desired version of this
55//! crate):
56//! ```toml
57//! oauth2 = { version = "...", default-features = false }
58//! ```
59//!
60//! Synchronous HTTP clients should implement the following trait:
61//! ```rust,ignore
62//! FnOnce(HttpRequest) -> Result<HttpResponse, RE>
63//! where RE: std::error::Error + 'static
64//! ```
65//!
66//! Asynchronous HTTP clients should implement the following trait:
67//! ```rust,ignore
68//! FnOnce(HttpRequest) -> F
69//! where
70//! F: Future<Output = Result<HttpResponse, RE>>,
71//! RE: std::error::Error + 'static
72//! ```
73//!
74//! # Getting started: Authorization Code Grant w/ PKCE
75//!
76//! This is the most common OAuth2 flow. PKCE is recommended whenever the OAuth2 client has no
77//! client secret or has a client secret that cannot remain confidential (e.g., native, mobile, or
78//! client-side web applications).
79//!
80//! ## Example: Synchronous (blocking) API
81//!
82//! This example works with `oauth2`'s default feature flags, which include `reqwest`.
83//!
84//! ```rust,no_run
85//! use anyhow;
86//! use oauth2::{
87//! AuthorizationCode,
88//! AuthUrl,
89//! ClientId,
90//! ClientSecret,
91//! CsrfToken,
92//! PkceCodeChallenge,
93//! RedirectUrl,
94//! Scope,
95//! TokenResponse,
96//! TokenUrl
97//! };
98//! use oauth2::basic::BasicClient;
99//! use oauth2::reqwest::http_client;
100//! use url::Url;
101//!
102//! # fn err_wrapper() -> Result<(), anyhow::Error> {
103//! // Create an OAuth2 client by specifying the client ID, client secret, authorization URL and
104//! // token URL.
105//! let client =
106//! BasicClient::new(
107//! ClientId::new("client_id".to_string()),
108//! Some(ClientSecret::new("client_secret".to_string())),
109//! AuthUrl::new("http://authorize".to_string())?,
110//! Some(TokenUrl::new("http://token".to_string())?)
111//! )
112//! // Set the URL the user will be redirected to after the authorization process.
113//! .set_redirect_uri(RedirectUrl::new("http://redirect".to_string())?);
114//!
115//! // Generate a PKCE challenge.
116//! let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
117//!
118//! // Generate the full authorization URL.
119//! let (auth_url, csrf_token) = client
120//! .authorize_url(CsrfToken::new_random)
121//! // Set the desired scopes.
122//! .add_scope(Scope::new("read".to_string()))
123//! .add_scope(Scope::new("write".to_string()))
124//! // Set the PKCE code challenge.
125//! .set_pkce_challenge(pkce_challenge)
126//! .url();
127//!
128//! // This is the URL you should redirect the user to, in order to trigger the authorization
129//! // process.
130//! println!("Browse to: {}", auth_url);
131//!
132//! // Once the user has been redirected to the redirect URL, you'll have access to the
133//! // authorization code. For security reasons, your code should verify that the `state`
134//! // parameter returned by the server matches `csrf_state`.
135//!
136//! // Now you can trade it for an access token.
137//! let token_result =
138//! client
139//! .exchange_code(AuthorizationCode::new("some authorization code".to_string()))
140//! // Set the PKCE code verifier.
141//! .set_pkce_verifier(pkce_verifier)
142//! .request(http_client)?;
143//!
144//! // Unwrapping token_result will either produce a Token or a RequestTokenError.
145//! # Ok(())
146//! # }
147//! ```
148//!
149//! ## Example: Asynchronous API
150//!
151//! The example below uses async/await:
152//!
153//! ```rust,no_run
154//! use anyhow;
155//! use oauth2::{
156//! AuthorizationCode,
157//! AuthUrl,
158//! ClientId,
159//! ClientSecret,
160//! CsrfToken,
161//! PkceCodeChallenge,
162//! RedirectUrl,
163//! Scope,
164//! TokenResponse,
165//! TokenUrl
166//! };
167//! use oauth2::basic::BasicClient;
168//! # #[cfg(feature = "reqwest")]
169//! use oauth2::reqwest::async_http_client;
170//! use url::Url;
171//!
172//! # #[cfg(feature = "reqwest")]
173//! # async fn err_wrapper() -> Result<(), anyhow::Error> {
174//! // Create an OAuth2 client by specifying the client ID, client secret, authorization URL and
175//! // token URL.
176//! let client =
177//! BasicClient::new(
178//! ClientId::new("client_id".to_string()),
179//! Some(ClientSecret::new("client_secret".to_string())),
180//! AuthUrl::new("http://authorize".to_string())?,
181//! Some(TokenUrl::new("http://token".to_string())?)
182//! )
183//! // Set the URL the user will be redirected to after the authorization process.
184//! .set_redirect_uri(RedirectUrl::new("http://redirect".to_string())?);
185//!
186//! // Generate a PKCE challenge.
187//! let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
188//!
189//! // Generate the full authorization URL.
190//! let (auth_url, csrf_token) = client
191//! .authorize_url(CsrfToken::new_random)
192//! // Set the desired scopes.
193//! .add_scope(Scope::new("read".to_string()))
194//! .add_scope(Scope::new("write".to_string()))
195//! // Set the PKCE code challenge.
196//! .set_pkce_challenge(pkce_challenge)
197//! .url();
198//!
199//! // This is the URL you should redirect the user to, in order to trigger the authorization
200//! // process.
201//! println!("Browse to: {}", auth_url);
202//!
203//! // Once the user has been redirected to the redirect URL, you'll have access to the
204//! // authorization code. For security reasons, your code should verify that the `state`
205//! // parameter returned by the server matches `csrf_state`.
206//!
207//! // Now you can trade it for an access token.
208//! let token_result = client
209//! .exchange_code(AuthorizationCode::new("some authorization code".to_string()))
210//! // Set the PKCE code verifier.
211//! .set_pkce_verifier(pkce_verifier)
212//! .request_async(async_http_client)
213//! .await?;
214//!
215//! // Unwrapping token_result will either produce a Token or a RequestTokenError.
216//! # Ok(())
217//! # }
218//! ```
219//!
220//! # Implicit Grant
221//!
222//! This flow fetches an access token directly from the authorization endpoint. Be sure to
223//! understand the security implications of this flow before using it. In most cases, the
224//! Authorization Code Grant flow is preferable to the Implicit Grant flow.
225//!
226//! ## Example
227//!
228//! ```rust,no_run
229//! use anyhow;
230//! use oauth2::{
231//! AuthUrl,
232//! ClientId,
233//! ClientSecret,
234//! CsrfToken,
235//! RedirectUrl,
236//! Scope
237//! };
238//! use oauth2::basic::BasicClient;
239//! use url::Url;
240//!
241//! # fn err_wrapper() -> Result<(), anyhow::Error> {
242//! let client =
243//! BasicClient::new(
244//! ClientId::new("client_id".to_string()),
245//! Some(ClientSecret::new("client_secret".to_string())),
246//! AuthUrl::new("http://authorize".to_string())?,
247//! None
248//! );
249//!
250//! // Generate the full authorization URL.
251//! let (auth_url, csrf_token) = client
252//! .authorize_url(CsrfToken::new_random)
253//! .use_implicit_flow()
254//! .url();
255//!
256//! // This is the URL you should redirect the user to, in order to trigger the authorization
257//! // process.
258//! println!("Browse to: {}", auth_url);
259//!
260//! // Once the user has been redirected to the redirect URL, you'll have the access code.
261//! // For security reasons, your code should verify that the `state` parameter returned by the
262//! // server matches `csrf_state`.
263//!
264//! # Ok(())
265//! # }
266//! ```
267//!
268//! # Resource Owner Password Credentials Grant
269//!
270//! You can ask for a *password* access token by calling the `Client::exchange_password` method,
271//! while including the username and password.
272//!
273//! ## Example
274//!
275//! ```rust,no_run
276//! use anyhow;
277//! use oauth2::{
278//! AuthUrl,
279//! ClientId,
280//! ClientSecret,
281//! ResourceOwnerPassword,
282//! ResourceOwnerUsername,
283//! Scope,
284//! TokenResponse,
285//! TokenUrl
286//! };
287//! use oauth2::basic::BasicClient;
288//! use oauth2::reqwest::http_client;
289//! use url::Url;
290//!
291//! # fn err_wrapper() -> Result<(), anyhow::Error> {
292//! let client =
293//! BasicClient::new(
294//! ClientId::new("client_id".to_string()),
295//! Some(ClientSecret::new("client_secret".to_string())),
296//! AuthUrl::new("http://authorize".to_string())?,
297//! Some(TokenUrl::new("http://token".to_string())?)
298//! );
299//!
300//! let token_result =
301//! client
302//! .exchange_password(
303//! &ResourceOwnerUsername::new("user".to_string()),
304//! &ResourceOwnerPassword::new("pass".to_string())
305//! )
306//! .add_scope(Scope::new("read".to_string()))
307//! .request(http_client)?;
308//! # Ok(())
309//! # }
310//! ```
311//!
312//! # Client Credentials Grant
313//!
314//! You can ask for a *client credentials* access token by calling the
315//! `Client::exchange_client_credentials` method.
316//!
317//! ## Example
318//!
319//! ```rust,no_run
320//! use anyhow;
321//! use oauth2::{
322//! AuthUrl,
323//! ClientId,
324//! ClientSecret,
325//! Scope,
326//! TokenResponse,
327//! TokenUrl
328//! };
329//! use oauth2::basic::BasicClient;
330//! use oauth2::reqwest::http_client;
331//! use url::Url;
332//!
333//! # fn err_wrapper() -> Result<(), anyhow::Error> {
334//! let client =
335//! BasicClient::new(
336//! ClientId::new("client_id".to_string()),
337//! Some(ClientSecret::new("client_secret".to_string())),
338//! AuthUrl::new("http://authorize".to_string())?,
339//! Some(TokenUrl::new("http://token".to_string())?),
340//! );
341//!
342//! let token_result = client
343//! .exchange_client_credentials()
344//! .add_scope(Scope::new("read".to_string()))
345//! .request(http_client)?;
346//! # Ok(())
347//! # }
348//! ```
349//!
350//! # Device Code Flow
351//!
352//! Device Code Flow allows users to sign in on browserless or input-constrained
353//! devices. This is a two-stage process; first a user-code and verification
354//! URL are obtained by using the `Client::exchange_client_credentials`
355//! method. Those are displayed to the user, then are used in a second client
356//! to poll the token endpoint for a token.
357//!
358//! ## Example
359//!
360//! ```rust,no_run
361//! use anyhow;
362//! use oauth2::{
363//! AuthUrl,
364//! ClientId,
365//! ClientSecret,
366//! DeviceAuthorizationUrl,
367//! Scope,
368//! TokenResponse,
369//! TokenUrl
370//! };
371//! use oauth2::basic::BasicClient;
372//! use oauth2::devicecode::StandardDeviceAuthorizationResponse;
373//! use oauth2::reqwest::http_client;
374//! use url::Url;
375//!
376//! # fn err_wrapper() -> Result<(), anyhow::Error> {
377//! let device_auth_url = DeviceAuthorizationUrl::new("http://deviceauth".to_string())?;
378//! let client =
379//! BasicClient::new(
380//! ClientId::new("client_id".to_string()),
381//! Some(ClientSecret::new("client_secret".to_string())),
382//! AuthUrl::new("http://authorize".to_string())?,
383//! Some(TokenUrl::new("http://token".to_string())?),
384//! )
385//! .set_device_authorization_url(device_auth_url);
386//!
387//! let details: StandardDeviceAuthorizationResponse = client
388//! .exchange_device_code()?
389//! .add_scope(Scope::new("read".to_string()))
390//! .request(http_client)?;
391//!
392//! println!(
393//! "Open this URL in your browser:\n{}\nand enter the code: {}",
394//! details.verification_uri().to_string(),
395//! details.user_code().secret().to_string()
396//! );
397//!
398//! let token_result =
399//! client
400//! .exchange_device_access_token(&details)
401//! .request(http_client, std::thread::sleep, None)?;
402//!
403//! # Ok(())
404//! # }
405//! ```
406//!
407//! # Other examples
408//!
409//! More specific implementations are available as part of the examples:
410//!
411//! - [Google](https://github.com/ramosbugs/oauth2-rs/blob/main/examples/google.rs) (includes token revocation)
412//! - [Github](https://github.com/ramosbugs/oauth2-rs/blob/main/examples/github.rs)
413//! - [Microsoft Device Code Flow (async)](https://github.com/ramosbugs/oauth2-rs/blob/main/examples/microsoft_devicecode.rs)
414//! - [Microsoft Graph](https://github.com/ramosbugs/oauth2-rs/blob/main/examples/msgraph.rs)
415//! - [Wunderlist](https://github.com/ramosbugs/oauth2-rs/blob/main/examples/wunderlist.rs)
416//!
417//! ## Contributed Examples
418//!
419//! - [`actix-web-oauth2`](https://github.com/pka/actix-web-oauth2) (version 2.x of this crate)
420//!
421use std::borrow::Cow;
422use std::error::Error;
423use std::fmt::Error as FormatterError;
424use std::fmt::{Debug, Display, Formatter};
425use std::future::Future;
426use std::marker::PhantomData;
427use std::sync::Arc;
428use std::time::Duration;
429
430use chrono::serde::ts_seconds_option;
431use chrono::{DateTime, Utc};
432use http::header::{HeaderMap, HeaderValue, ACCEPT, AUTHORIZATION, CONTENT_TYPE};
433use http::status::StatusCode;
434use serde::de::DeserializeOwned;
435use serde::{Deserialize, Serialize};
436use url::{form_urlencoded, Url};
437
438use crate::devicecode::DeviceAccessTokenPollResult;
439
440///
441/// Basic OAuth2 implementation with no extensions
442/// ([RFC 6749](https://tools.ietf.org/html/rfc6749)).
443///
444pub mod basic;
445
446///
447/// HTTP client backed by the [curl](https://crates.io/crates/curl) crate.
448/// Requires "curl" feature.
449///
450#[cfg(all(feature = "curl", not(target_arch = "wasm32")))]
451pub mod curl;
452
453#[cfg(all(feature = "curl", target_arch = "wasm32"))]
454compile_error!("wasm32 is not supported with the `curl` feature. Use the `reqwest` backend or a custom backend for wasm32 support");
455
456///
457/// Device Code Flow OAuth2 implementation
458/// ([RFC 8628](https://tools.ietf.org/html/rfc8628)).
459///
460pub mod devicecode;
461
462///
463/// OAuth 2.0 Token Revocation implementation
464/// ([RFC 7009](https://tools.ietf.org/html/rfc7009)).
465///
466pub mod revocation;
467
468///
469/// Helper methods used by OAuth2 implementations/extensions.
470///
471pub mod helpers;
472
473///
474/// HTTP client backed by the [reqwest](https://crates.io/crates/reqwest) crate.
475/// Requires "reqwest" feature.
476///
477#[cfg(feature = "reqwest")]
478pub mod reqwest;
479
480#[cfg(test)]
481mod tests;
482
483mod types;
484
485///
486/// HTTP client backed by the [ureq](https://crates.io/crates/ureq) crate.
487/// Requires "ureq" feature.
488///
489#[cfg(feature = "ureq")]
490pub mod ureq;
491
492///
493/// Public re-exports of types used for HTTP client interfaces.
494///
495pub use http;
496pub use url;
497
498pub use devicecode::{
499 DeviceAuthorizationResponse, DeviceCodeErrorResponse, DeviceCodeErrorResponseType,
500 EmptyExtraDeviceAuthorizationFields, ExtraDeviceAuthorizationFields,
501 StandardDeviceAuthorizationResponse,
502};
503
504pub use types::{
505 AccessToken, AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken,
506 DeviceAuthorizationUrl, DeviceCode, EndUserVerificationUrl, IntrospectionUrl,
507 PkceCodeChallenge, PkceCodeChallengeMethod, PkceCodeVerifier, RedirectUrl, RefreshToken,
508 ResourceOwnerPassword, ResourceOwnerUsername, ResponseType, RevocationUrl, Scope, TokenUrl,
509 UserCode, VerificationUriComplete,
510};
511
512pub use revocation::{RevocableToken, RevocationErrorResponseType, StandardRevocableToken};
513
514const CONTENT_TYPE_JSON: &str = "application/json";
515const CONTENT_TYPE_FORMENCODED: &str = "application/x-www-form-urlencoded";
516
517///
518/// There was a problem configuring the request.
519///
520#[non_exhaustive]
521#[derive(Debug, thiserror::Error)]
522pub enum ConfigurationError {
523 ///
524 /// The endpoint URL tp be contacted is missing.
525 ///
526 #[error("No {0} endpoint URL specified")]
527 MissingUrl(&'static str),
528 ///
529 /// The endpoint URL to be contacted MUST be HTTPS.
530 ///
531 #[error("Scheme for {0} endpoint URL must be HTTPS")]
532 InsecureUrl(&'static str),
533}
534
535///
536/// Indicates whether requests to the authorization server should use basic authentication or
537/// include the parameters in the request body for requests in which either is valid.
538///
539/// The default AuthType is *BasicAuth*, following the recommendation of
540/// [Section 2.3.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-2.3.1).
541///
542#[derive(Clone, Debug)]
543#[non_exhaustive]
544pub enum AuthType {
545 /// The client_id and client_secret (if set) will be included as part of the request body.
546 RequestBody,
547 /// The client_id and client_secret will be included using the basic auth authentication scheme.
548 BasicAuth,
549}
550
551///
552/// Stores the configuration for an OAuth2 client.
553///
554/// # Error Types
555///
556/// To enable compile time verification that only the correct and complete set of errors for the `Client` function being
557/// invoked are exposed to the caller, the `Client` type is specialized on multiple implementations of the
558/// [`ErrorResponse`] trait. The exact [`ErrorResponse`] implementation returned varies by the RFC that the invoked
559/// `Client` function implements:
560///
561/// - Generic type `TE` (aka Token Error) for errors defined by [RFC 6749 OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749).
562/// - Generic type `TRE` (aka Token Revocation Error) for errors defined by [RFC 7009 OAuth 2.0 Token Revocation](https://tools.ietf.org/html/rfc7009).
563///
564/// For example when revoking a token, error code `unsupported_token_type` (from RFC 7009) may be returned:
565/// ```rust
566/// # use thiserror::Error;
567/// # use http::status::StatusCode;
568/// # use http::header::{HeaderValue, CONTENT_TYPE};
569/// # use oauth2::{*, basic::*};
570/// # let client = BasicClient::new(
571/// # ClientId::new("aaa".to_string()),
572/// # Some(ClientSecret::new("bbb".to_string())),
573/// # AuthUrl::new("https://example.com/auth".to_string()).unwrap(),
574/// # Some(TokenUrl::new("https://example.com/token".to_string()).unwrap()),
575/// # )
576/// # .set_revocation_uri(RevocationUrl::new("https://revocation/url".to_string()).unwrap());
577/// #
578/// # #[derive(Debug, Error)]
579/// # enum FakeError {
580/// # #[error("error")]
581/// # Err,
582/// # }
583/// #
584/// # let http_client = |_| -> Result<HttpResponse, FakeError> {
585/// # Ok(HttpResponse {
586/// # status_code: StatusCode::BAD_REQUEST,
587/// # headers: vec![(
588/// # CONTENT_TYPE,
589/// # HeaderValue::from_str("application/json").unwrap(),
590/// # )]
591/// # .into_iter()
592/// # .collect(),
593/// # body: "{\"error\": \"unsupported_token_type\", \"error_description\": \"stuff happened\", \
594/// # \"error_uri\": \"https://errors\"}"
595/// # .to_string()
596/// # .into_bytes(),
597/// # })
598/// # };
599/// #
600/// let res = client
601/// .revoke_token(AccessToken::new("some token".to_string()).into())
602/// .unwrap()
603/// .request(http_client);
604///
605/// assert!(matches!(res, Err(
606/// RequestTokenError::ServerResponse(err)) if matches!(err.error(),
607/// RevocationErrorResponseType::UnsupportedTokenType)));
608/// ```
609///
610#[derive(Clone, Debug)]
611pub struct Client<TE, TR, TT, TIR, RT, TRE>
612where
613 TE: ErrorResponse,
614 TR: TokenResponse<TT>,
615 TT: TokenType,
616 TIR: TokenIntrospectionResponse<TT>,
617 RT: RevocableToken,
618 TRE: ErrorResponse,
619{
620 client_id: ClientId,
621 client_secret: Option<ClientSecret>,
622 auth_url: AuthUrl,
623 auth_type: AuthType,
624 token_url: Option<TokenUrl>,
625 redirect_url: Option<RedirectUrl>,
626 introspection_url: Option<IntrospectionUrl>,
627 revocation_url: Option<RevocationUrl>,
628 device_authorization_url: Option<DeviceAuthorizationUrl>,
629 phantom: PhantomData<(TE, TR, TT, TIR, RT, TRE)>,
630}
631
632impl<TE, TR, TT, TIR, RT, TRE> Client<TE, TR, TT, TIR, RT, TRE>
633where
634 TE: ErrorResponse + 'static,
635 TR: TokenResponse<TT>,
636 TT: TokenType,
637 TIR: TokenIntrospectionResponse<TT>,
638 RT: RevocableToken,
639 TRE: ErrorResponse + 'static,
640{
641 ///
642 /// Initializes an OAuth2 client with the fields common to most OAuth2 flows.
643 ///
644 /// # Arguments
645 ///
646 /// * `client_id` - Client ID
647 /// * `client_secret` - Optional client secret. A client secret is generally used for private
648 /// (server-side) OAuth2 clients and omitted from public (client-side or native app) OAuth2
649 /// clients (see [RFC 8252](https://tools.ietf.org/html/rfc8252)).
650 /// * `auth_url` - Authorization endpoint: used by the client to obtain authorization from
651 /// the resource owner via user-agent redirection. This URL is used in all standard OAuth2
652 /// flows except the [Resource Owner Password Credentials
653 /// Grant](https://tools.ietf.org/html/rfc6749#section-4.3) and the
654 /// [Client Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.4).
655 /// * `token_url` - Token endpoint: used by the client to exchange an authorization grant
656 /// (code) for an access token, typically with client authentication. This URL is used in
657 /// all standard OAuth2 flows except the
658 /// [Implicit Grant](https://tools.ietf.org/html/rfc6749#section-4.2). If this value is set
659 /// to `None`, the `exchange_*` methods will return `Err(RequestTokenError::Other(_))`.
660 ///
661 pub fn new(
662 client_id: ClientId,
663 client_secret: Option<ClientSecret>,
664 auth_url: AuthUrl,
665 token_url: Option<TokenUrl>,
666 ) -> Self {
667 Client {
668 client_id,
669 client_secret,
670 auth_url,
671 auth_type: AuthType::BasicAuth,
672 token_url,
673 redirect_url: None,
674 introspection_url: None,
675 revocation_url: None,
676 device_authorization_url: None,
677 phantom: PhantomData,
678 }
679 }
680
681 ///
682 /// Configures the type of client authentication used for communicating with the authorization
683 /// server.
684 ///
685 /// The default is to use HTTP Basic authentication, as recommended in
686 /// [Section 2.3.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-2.3.1). Note that
687 /// if a client secret is omitted (i.e., `client_secret` is set to `None` when calling
688 /// [`Client::new`]), [`AuthType::RequestBody`] is used regardless of the `auth_type` passed to
689 /// this function.
690 ///
691 pub fn set_auth_type(mut self, auth_type: AuthType) -> Self {
692 self.auth_type = auth_type;
693
694 self
695 }
696
697 ///
698 /// Sets the redirect URL used by the authorization endpoint.
699 ///
700 pub fn set_redirect_uri(mut self, redirect_url: RedirectUrl) -> Self {
701 self.redirect_url = Some(redirect_url);
702
703 self
704 }
705
706 ///
707 /// Sets the introspection URL for contacting the ([RFC 7662](https://tools.ietf.org/html/rfc7662))
708 /// introspection endpoint.
709 ///
710 pub fn set_introspection_uri(mut self, introspection_url: IntrospectionUrl) -> Self {
711 self.introspection_url = Some(introspection_url);
712
713 self
714 }
715
716 ///
717 /// Sets the revocation URL for contacting the revocation endpoint ([RFC 7009](https://tools.ietf.org/html/rfc7009)).
718 ///
719 /// See: [`revoke_token()`](Self::revoke_token())
720 ///
721 pub fn set_revocation_uri(mut self, revocation_url: RevocationUrl) -> Self {
722 self.revocation_url = Some(revocation_url);
723
724 self
725 }
726
727 ///
728 /// Sets the the device authorization URL used by the device authorization endpoint.
729 /// Used for Device Code Flow, as per [RFC 8628](https://tools.ietf.org/html/rfc8628).
730 ///
731 pub fn set_device_authorization_url(
732 mut self,
733 device_authorization_url: DeviceAuthorizationUrl,
734 ) -> Self {
735 self.device_authorization_url = Some(device_authorization_url);
736
737 self
738 }
739
740 ///
741 /// Generates an authorization URL for a new authorization request.
742 ///
743 /// # Arguments
744 ///
745 /// * `state_fn` - A function that returns an opaque value used by the client to maintain state
746 /// between the request and callback. The authorization server includes this value when
747 /// redirecting the user-agent back to the client.
748 ///
749 /// # Security Warning
750 ///
751 /// Callers should use a fresh, unpredictable `state` for each authorization request and verify
752 /// that this value matches the `state` parameter passed by the authorization server to the
753 /// redirect URI. Doing so mitigates
754 /// [Cross-Site Request Forgery](https://tools.ietf.org/html/rfc6749#section-10.12)
755 /// attacks. To disable CSRF protections (NOT recommended), use `insecure::authorize_url`
756 /// instead.
757 ///
758 pub fn authorize_url<S>(&self, state_fn: S) -> AuthorizationRequest
759 where
760 S: FnOnce() -> CsrfToken,
761 {
762 AuthorizationRequest {
763 auth_url: &self.auth_url,
764 client_id: &self.client_id,
765 extra_params: Vec::new(),
766 pkce_challenge: None,
767 redirect_url: self.redirect_url.as_ref().map(Cow::Borrowed),
768 response_type: "code".into(),
769 scopes: Vec::new(),
770 state: state_fn(),
771 }
772 }
773
774 ///
775 /// Exchanges a code produced by a successful authorization process with an access token.
776 ///
777 /// Acquires ownership of the `code` because authorization codes may only be used once to
778 /// retrieve an access token from the authorization server.
779 ///
780 /// See <https://tools.ietf.org/html/rfc6749#section-4.1.3>.
781 ///
782 pub fn exchange_code(&self, code: AuthorizationCode) -> CodeTokenRequest<TE, TR, TT> {
783 CodeTokenRequest {
784 auth_type: &self.auth_type,
785 client_id: &self.client_id,
786 client_secret: self.client_secret.as_ref(),
787 code,
788 extra_params: Vec::new(),
789 pkce_verifier: None,
790 token_url: self.token_url.as_ref(),
791 redirect_url: self.redirect_url.as_ref().map(Cow::Borrowed),
792 _phantom: PhantomData,
793 }
794 }
795
796 ///
797 /// Requests an access token for the *password* grant type.
798 ///
799 /// See <https://tools.ietf.org/html/rfc6749#section-4.3.2>.
800 ///
801 pub fn exchange_password<'a, 'b>(
802 &'a self,
803 username: &'b ResourceOwnerUsername,
804 password: &'b ResourceOwnerPassword,
805 ) -> PasswordTokenRequest<'b, TE, TR, TT>
806 where
807 'a: 'b,
808 {
809 PasswordTokenRequest::<'b> {
810 auth_type: &self.auth_type,
811 client_id: &self.client_id,
812 client_secret: self.client_secret.as_ref(),
813 username,
814 password,
815 extra_params: Vec::new(),
816 scopes: Vec::new(),
817 token_url: self.token_url.as_ref(),
818 _phantom: PhantomData,
819 }
820 }
821
822 ///
823 /// Requests an access token for the *client credentials* grant type.
824 ///
825 /// See <https://tools.ietf.org/html/rfc6749#section-4.4.2>.
826 ///
827 pub fn exchange_client_credentials(&self) -> ClientCredentialsTokenRequest<TE, TR, TT> {
828 ClientCredentialsTokenRequest {
829 auth_type: &self.auth_type,
830 client_id: &self.client_id,
831 client_secret: self.client_secret.as_ref(),
832 extra_params: Vec::new(),
833 scopes: Vec::new(),
834 token_url: self.token_url.as_ref(),
835 _phantom: PhantomData,
836 }
837 }
838
839 ///
840 /// Exchanges a refresh token for an access token
841 ///
842 /// See <https://tools.ietf.org/html/rfc6749#section-6>.
843 ///
844 pub fn exchange_refresh_token<'a, 'b>(
845 &'a self,
846 refresh_token: &'b RefreshToken,
847 ) -> RefreshTokenRequest<'b, TE, TR, TT>
848 where
849 'a: 'b,
850 {
851 RefreshTokenRequest {
852 auth_type: &self.auth_type,
853 client_id: &self.client_id,
854 client_secret: self.client_secret.as_ref(),
855 extra_params: Vec::new(),
856 refresh_token,
857 scopes: Vec::new(),
858 token_url: self.token_url.as_ref(),
859 _phantom: PhantomData,
860 }
861 }
862
863 ///
864 /// Perform a device authorization request as per
865 /// <https://tools.ietf.org/html/rfc8628#section-3.1>.
866 ///
867 pub fn exchange_device_code(
868 &self,
869 ) -> Result<DeviceAuthorizationRequest<TE>, ConfigurationError> {
870 Ok(DeviceAuthorizationRequest {
871 auth_type: &self.auth_type,
872 client_id: &self.client_id,
873 client_secret: self.client_secret.as_ref(),
874 extra_params: Vec::new(),
875 scopes: Vec::new(),
876 device_authorization_url: self
877 .device_authorization_url
878 .as_ref()
879 .ok_or(ConfigurationError::MissingUrl("device authorization_url"))?,
880 _phantom: PhantomData,
881 })
882 }
883
884 ///
885 /// Perform a device access token request as per
886 /// <https://tools.ietf.org/html/rfc8628#section-3.4>.
887 ///
888 pub fn exchange_device_access_token<'a, 'b, 'c, EF>(
889 &'a self,
890 auth_response: &'b DeviceAuthorizationResponse<EF>,
891 ) -> DeviceAccessTokenRequest<'b, 'c, TR, TT, EF>
892 where
893 'a: 'b,
894 EF: ExtraDeviceAuthorizationFields,
895 {
896 DeviceAccessTokenRequest {
897 auth_type: &self.auth_type,
898 client_id: &self.client_id,
899 client_secret: self.client_secret.as_ref(),
900 extra_params: Vec::new(),
901 token_url: self.token_url.as_ref(),
902 dev_auth_resp: auth_response,
903 time_fn: Arc::new(Utc::now),
904 max_backoff_interval: None,
905 _phantom: PhantomData,
906 }
907 }
908
909 ///
910 /// Query the authorization server [`RFC 7662 compatible`](https://tools.ietf.org/html/rfc7662) introspection
911 /// endpoint to determine the set of metadata for a previously received token.
912 ///
913 /// Requires that [`set_introspection_uri()`](Self::set_introspection_uri()) have already been called to set the
914 /// introspection endpoint URL.
915 ///
916 /// Attempting to submit the generated request without calling [`set_introspection_uri()`](Self::set_introspection_uri())
917 /// first will result in an error.
918 ///
919 pub fn introspect<'a>(
920 &'a self,
921 token: &'a AccessToken,
922 ) -> Result<IntrospectionRequest<'a, TE, TIR, TT>, ConfigurationError> {
923 Ok(IntrospectionRequest {
924 auth_type: &self.auth_type,
925 client_id: &self.client_id,
926 client_secret: self.client_secret.as_ref(),
927 extra_params: Vec::new(),
928 introspection_url: self
929 .introspection_url
930 .as_ref()
931 .ok_or(ConfigurationError::MissingUrl("introspection"))?,
932 token,
933 token_type_hint: None,
934 _phantom: PhantomData,
935 })
936 }
937
938 ///
939 /// Attempts to revoke the given previously received token using an [RFC 7009 OAuth 2.0 Token Revocation](https://tools.ietf.org/html/rfc7009)
940 /// compatible endpoint.
941 ///
942 /// Requires that [`set_revocation_uri()`](Self::set_revocation_uri()) have already been called to set the
943 /// revocation endpoint URL.
944 ///
945 /// Attempting to submit the generated request without calling [`set_revocation_uri()`](Self::set_revocation_uri())
946 /// first will result in an error.
947 ///
948 pub fn revoke_token(
949 &self,
950 token: RT,
951 ) -> Result<RevocationRequest<RT, TRE>, ConfigurationError> {
952 // https://tools.ietf.org/html/rfc7009#section-2 states:
953 // "The client requests the revocation of a particular token by making an
954 // HTTP POST request to the token revocation endpoint URL. This URL
955 // MUST conform to the rules given in [RFC6749], Section 3.1. Clients
956 // MUST verify that the URL is an HTTPS URL."
957 let revocation_url = match self.revocation_url.as_ref() {
958 Some(url) if url.url().scheme() == "https" => Ok(url),
959 Some(_) => Err(ConfigurationError::InsecureUrl("revocation")),
960 None => Err(ConfigurationError::MissingUrl("revocation")),
961 }?;
962
963 Ok(RevocationRequest {
964 auth_type: &self.auth_type,
965 client_id: &self.client_id,
966 client_secret: self.client_secret.as_ref(),
967 extra_params: Vec::new(),
968 revocation_url,
969 token,
970 _phantom: PhantomData,
971 })
972 }
973
974 ///
975 /// Returns the Client ID.
976 ///
977 pub fn client_id(&self) -> &ClientId {
978 &self.client_id
979 }
980
981 ///
982 /// Returns the authorization endpoint.
983 ///
984 pub fn auth_url(&self) -> &AuthUrl {
985 &self.auth_url
986 }
987
988 ///
989 /// Returns the type of client authentication used for communicating with the authorization
990 /// server.
991 ///
992 pub fn auth_type(&self) -> &AuthType {
993 &self.auth_type
994 }
995
996 ///
997 /// Returns the token endpoint.
998 ///
999 pub fn token_url(&self) -> Option<&TokenUrl> {
1000 self.token_url.as_ref()
1001 }
1002
1003 ///
1004 /// Returns the redirect URL used by the authorization endpoint.
1005 ///
1006 pub fn redirect_url(&self) -> Option<&RedirectUrl> {
1007 self.redirect_url.as_ref()
1008 }
1009
1010 ///
1011 /// Returns the introspection URL for contacting the ([RFC 7662](https://tools.ietf.org/html/rfc7662))
1012 /// introspection endpoint.
1013 ///
1014 pub fn introspection_url(&self) -> Option<&IntrospectionUrl> {
1015 self.introspection_url.as_ref()
1016 }
1017
1018 ///
1019 /// Returns the revocation URL for contacting the revocation endpoint ([RFC 7009](https://tools.ietf.org/html/rfc7009)).
1020 ///
1021 /// See: [`revoke_token()`](Self::revoke_token())
1022 ///
1023 pub fn revocation_url(&self) -> Option<&RevocationUrl> {
1024 self.revocation_url.as_ref()
1025 }
1026
1027 ///
1028 /// Returns the the device authorization URL used by the device authorization endpoint.
1029 ///
1030 pub fn device_authorization_url(&self) -> Option<&DeviceAuthorizationUrl> {
1031 self.device_authorization_url.as_ref()
1032 }
1033}
1034
1035///
1036/// A request to the authorization endpoint
1037///
1038#[derive(Debug)]
1039pub struct AuthorizationRequest<'a> {
1040 auth_url: &'a AuthUrl,
1041 client_id: &'a ClientId,
1042 extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
1043 pkce_challenge: Option<PkceCodeChallenge>,
1044 redirect_url: Option<Cow<'a, RedirectUrl>>,
1045 response_type: Cow<'a, str>,
1046 scopes: Vec<Cow<'a, Scope>>,
1047 state: CsrfToken,
1048}
1049impl<'a> AuthorizationRequest<'a> {
1050 ///
1051 /// Appends a new scope to the authorization URL.
1052 ///
1053 pub fn add_scope(mut self, scope: Scope) -> Self {
1054 self.scopes.push(Cow::Owned(scope));
1055 self
1056 }
1057
1058 ///
1059 /// Appends a collection of scopes to the token request.
1060 ///
1061 pub fn add_scopes<I>(mut self, scopes: I) -> Self
1062 where
1063 I: IntoIterator<Item = Scope>,
1064 {
1065 self.scopes.extend(scopes.into_iter().map(Cow::Owned));
1066 self
1067 }
1068
1069 ///
1070 /// Appends an extra param to the authorization URL.
1071 ///
1072 /// This method allows extensions to be used without direct support from
1073 /// this crate. If `name` conflicts with a parameter managed by this crate, the
1074 /// behavior is undefined. In particular, do not set parameters defined by
1075 /// [RFC 6749](https://tools.ietf.org/html/rfc6749) or
1076 /// [RFC 7636](https://tools.ietf.org/html/rfc7636).
1077 ///
1078 /// # Security Warning
1079 ///
1080 /// Callers should follow the security recommendations for any OAuth2 extensions used with
1081 /// this function, which are beyond the scope of
1082 /// [RFC 6749](https://tools.ietf.org/html/rfc6749).
1083 ///
1084 pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
1085 where
1086 N: Into<Cow<'a, str>>,
1087 V: Into<Cow<'a, str>>,
1088 {
1089 self.extra_params.push((name.into(), value.into()));
1090 self
1091 }
1092
1093 ///
1094 /// Enables the [Implicit Grant](https://tools.ietf.org/html/rfc6749#section-4.2) flow.
1095 ///
1096 pub fn use_implicit_flow(mut self) -> Self {
1097 self.response_type = "token".into();
1098 self
1099 }
1100
1101 ///
1102 /// Enables custom flows other than the `code` and `token` (implicit flow) grant.
1103 ///
1104 pub fn set_response_type(mut self, response_type: &ResponseType) -> Self {
1105 self.response_type = (&**response_type).to_owned().into();
1106 self
1107 }
1108
1109 ///
1110 /// Enables the use of [Proof Key for Code Exchange](https://tools.ietf.org/html/rfc7636)
1111 /// (PKCE).
1112 ///
1113 /// PKCE is *highly recommended* for all public clients (i.e., those for which there
1114 /// is no client secret or for which the client secret is distributed with the client,
1115 /// such as in a native, mobile app, or browser app).
1116 ///
1117 pub fn set_pkce_challenge(mut self, pkce_code_challenge: PkceCodeChallenge) -> Self {
1118 self.pkce_challenge = Some(pkce_code_challenge);
1119 self
1120 }
1121
1122 ///
1123 /// Overrides the `redirect_url` to the one specified.
1124 ///
1125 pub fn set_redirect_uri(mut self, redirect_url: Cow<'a, RedirectUrl>) -> Self {
1126 self.redirect_url = Some(redirect_url);
1127 self
1128 }
1129
1130 ///
1131 /// Returns the full authorization URL and CSRF state for this authorization
1132 /// request.
1133 ///
1134 pub fn url(self) -> (Url, CsrfToken) {
1135 let scopes = self
1136 .scopes
1137 .iter()
1138 .map(|s| s.to_string())
1139 .collect::<Vec<_>>()
1140 .join(" ");
1141
1142 let url = {
1143 let mut pairs: Vec<(&str, &str)> = vec![
1144 ("response_type", self.response_type.as_ref()),
1145 ("client_id", &self.client_id),
1146 ("state", self.state.secret()),
1147 ];
1148
1149 if let Some(ref pkce_challenge) = self.pkce_challenge {
1150 pairs.push(("code_challenge", &pkce_challenge.as_str()));
1151 pairs.push(("code_challenge_method", &pkce_challenge.method().as_str()));
1152 }
1153
1154 if let Some(ref redirect_url) = self.redirect_url {
1155 pairs.push(("redirect_uri", redirect_url.as_str()));
1156 }
1157
1158 if !scopes.is_empty() {
1159 pairs.push(("scope", &scopes));
1160 }
1161
1162 let mut url: Url = self.auth_url.url().to_owned();
1163
1164 url.query_pairs_mut()
1165 .extend_pairs(pairs.iter().map(|&(k, v)| (k, &v[..])));
1166
1167 url.query_pairs_mut()
1168 .extend_pairs(self.extra_params.iter().cloned());
1169 url
1170 };
1171
1172 (url, self.state)
1173 }
1174}
1175
1176///
1177/// An HTTP request.
1178///
1179#[derive(Clone, Debug)]
1180pub struct HttpRequest {
1181 // These are all owned values so that the request can safely be passed between
1182 // threads.
1183 /// URL to which the HTTP request is being made.
1184 pub url: Url,
1185 /// HTTP request method for this request.
1186 pub method: http::method::Method,
1187 /// HTTP request headers to send.
1188 pub headers: HeaderMap,
1189 /// HTTP request body (typically for POST requests only).
1190 pub body: Vec<u8>,
1191}
1192
1193///
1194/// An HTTP response.
1195///
1196#[derive(Clone, Debug)]
1197pub struct HttpResponse {
1198 /// HTTP status code returned by the server.
1199 pub status_code: http::status::StatusCode,
1200 /// HTTP response headers returned by the server.
1201 pub headers: HeaderMap,
1202 /// HTTP response body returned by the server.
1203 pub body: Vec<u8>,
1204}
1205
1206///
1207/// A request to exchange an authorization code for an access token.
1208///
1209/// See <https://tools.ietf.org/html/rfc6749#section-4.1.3>.
1210///
1211#[derive(Debug)]
1212pub struct CodeTokenRequest<'a, TE, TR, TT>
1213where
1214 TE: ErrorResponse,
1215 TR: TokenResponse<TT>,
1216 TT: TokenType,
1217{
1218 auth_type: &'a AuthType,
1219 client_id: &'a ClientId,
1220 client_secret: Option<&'a ClientSecret>,
1221 code: AuthorizationCode,
1222 extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
1223 pkce_verifier: Option<PkceCodeVerifier>,
1224 token_url: Option<&'a TokenUrl>,
1225 redirect_url: Option<Cow<'a, RedirectUrl>>,
1226 _phantom: PhantomData<(TE, TR, TT)>,
1227}
1228impl<'a, TE, TR, TT> CodeTokenRequest<'a, TE, TR, TT>
1229where
1230 TE: ErrorResponse + 'static,
1231 TR: TokenResponse<TT>,
1232 TT: TokenType,
1233{
1234 ///
1235 /// Appends an extra param to the token request.
1236 ///
1237 /// This method allows extensions to be used without direct support from
1238 /// this crate. If `name` conflicts with a parameter managed by this crate, the
1239 /// behavior is undefined. In particular, do not set parameters defined by
1240 /// [RFC 6749](https://tools.ietf.org/html/rfc6749) or
1241 /// [RFC 7636](https://tools.ietf.org/html/rfc7636).
1242 ///
1243 /// # Security Warning
1244 ///
1245 /// Callers should follow the security recommendations for any OAuth2 extensions used with
1246 /// this function, which are beyond the scope of
1247 /// [RFC 6749](https://tools.ietf.org/html/rfc6749).
1248 ///
1249 pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
1250 where
1251 N: Into<Cow<'a, str>>,
1252 V: Into<Cow<'a, str>>,
1253 {
1254 self.extra_params.push((name.into(), value.into()));
1255 self
1256 }
1257
1258 ///
1259 /// Completes the [Proof Key for Code Exchange](https://tools.ietf.org/html/rfc7636)
1260 /// (PKCE) protocol flow.
1261 ///
1262 /// This method must be called if `set_pkce_challenge` was used during the authorization
1263 /// request.
1264 ///
1265 pub fn set_pkce_verifier(mut self, pkce_verifier: PkceCodeVerifier) -> Self {
1266 self.pkce_verifier = Some(pkce_verifier);
1267 self
1268 }
1269
1270 ///
1271 /// Overrides the `redirect_url` to the one specified.
1272 ///
1273 pub fn set_redirect_uri(mut self, redirect_url: Cow<'a, RedirectUrl>) -> Self {
1274 self.redirect_url = Some(redirect_url);
1275 self
1276 }
1277
1278 fn prepare_request<RE>(self) -> Result<HttpRequest, RequestTokenError<RE, TE>>
1279 where
1280 RE: Error + 'static,
1281 {
1282 let mut params = vec![
1283 ("grant_type", "authorization_code"),
1284 ("code", self.code.secret()),
1285 ];
1286 if let Some(ref pkce_verifier) = self.pkce_verifier {
1287 params.push(("code_verifier", pkce_verifier.secret()));
1288 }
1289
1290 Ok(endpoint_request(
1291 self.auth_type,
1292 self.client_id,
1293 self.client_secret,
1294 &self.extra_params,
1295 self.redirect_url,
1296 None,
1297 self.token_url
1298 .ok_or_else(|| RequestTokenError::Other("no token_url provided".to_string()))?
1299 .url(),
1300 params,
1301 ))
1302 }
1303
1304 ///
1305 /// Synchronously sends the request to the authorization server and awaits a response.
1306 ///
1307 pub fn request<F, RE>(self, http_client: F) -> Result<TR, RequestTokenError<RE, TE>>
1308 where
1309 F: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
1310 RE: Error + 'static,
1311 {
1312 http_client(self.prepare_request()?)
1313 .map_err(RequestTokenError::Request)
1314 .and_then(endpoint_response)
1315 }
1316
1317 ///
1318 /// Asynchronously sends the request to the authorization server and returns a Future.
1319 ///
1320 pub async fn request_async<C, F, RE>(
1321 self,
1322 http_client: C,
1323 ) -> Result<TR, RequestTokenError<RE, TE>>
1324 where
1325 C: FnOnce(HttpRequest) -> F,
1326 F: Future<Output = Result<HttpResponse, RE>>,
1327 RE: Error + 'static,
1328 {
1329 let http_request = self.prepare_request()?;
1330 let http_response = http_client(http_request)
1331 .await
1332 .map_err(RequestTokenError::Request)?;
1333 endpoint_response(http_response)
1334 }
1335}
1336
1337///
1338/// A request to exchange a refresh token for an access token.
1339///
1340/// See <https://tools.ietf.org/html/rfc6749#section-6>.
1341///
1342#[derive(Debug)]
1343pub struct RefreshTokenRequest<'a, TE, TR, TT>
1344where
1345 TE: ErrorResponse,
1346 TR: TokenResponse<TT>,
1347 TT: TokenType,
1348{
1349 auth_type: &'a AuthType,
1350 client_id: &'a ClientId,
1351 client_secret: Option<&'a ClientSecret>,
1352 extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
1353 refresh_token: &'a RefreshToken,
1354 scopes: Vec<Cow<'a, Scope>>,
1355 token_url: Option<&'a TokenUrl>,
1356 _phantom: PhantomData<(TE, TR, TT)>,
1357}
1358impl<'a, TE, TR, TT> RefreshTokenRequest<'a, TE, TR, TT>
1359where
1360 TE: ErrorResponse + 'static,
1361 TR: TokenResponse<TT>,
1362 TT: TokenType,
1363{
1364 ///
1365 /// Appends an extra param to the token request.
1366 ///
1367 /// This method allows extensions to be used without direct support from
1368 /// this crate. If `name` conflicts with a parameter managed by this crate, the
1369 /// behavior is undefined. In particular, do not set parameters defined by
1370 /// [RFC 6749](https://tools.ietf.org/html/rfc6749) or
1371 /// [RFC 7636](https://tools.ietf.org/html/rfc7636).
1372 ///
1373 /// # Security Warning
1374 ///
1375 /// Callers should follow the security recommendations for any OAuth2 extensions used with
1376 /// this function, which are beyond the scope of
1377 /// [RFC 6749](https://tools.ietf.org/html/rfc6749).
1378 ///
1379 pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
1380 where
1381 N: Into<Cow<'a, str>>,
1382 V: Into<Cow<'a, str>>,
1383 {
1384 self.extra_params.push((name.into(), value.into()));
1385 self
1386 }
1387
1388 ///
1389 /// Appends a new scope to the token request.
1390 ///
1391 pub fn add_scope(mut self, scope: Scope) -> Self {
1392 self.scopes.push(Cow::Owned(scope));
1393 self
1394 }
1395
1396 ///
1397 /// Appends a collection of scopes to the token request.
1398 ///
1399 pub fn add_scopes<I>(mut self, scopes: I) -> Self
1400 where
1401 I: IntoIterator<Item = Scope>,
1402 {
1403 self.scopes.extend(scopes.into_iter().map(Cow::Owned));
1404 self
1405 }
1406
1407 ///
1408 /// Synchronously sends the request to the authorization server and awaits a response.
1409 ///
1410 pub fn request<F, RE>(self, http_client: F) -> Result<TR, RequestTokenError<RE, TE>>
1411 where
1412 F: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
1413 RE: Error + 'static,
1414 {
1415 http_client(self.prepare_request()?)
1416 .map_err(RequestTokenError::Request)
1417 .and_then(endpoint_response)
1418 }
1419 ///
1420 /// Asynchronously sends the request to the authorization server and awaits a response.
1421 ///
1422 pub async fn request_async<C, F, RE>(
1423 self,
1424 http_client: C,
1425 ) -> Result<TR, RequestTokenError<RE, TE>>
1426 where
1427 C: FnOnce(HttpRequest) -> F,
1428 F: Future<Output = Result<HttpResponse, RE>>,
1429 RE: Error + 'static,
1430 {
1431 let http_request = self.prepare_request()?;
1432 let http_response = http_client(http_request)
1433 .await
1434 .map_err(RequestTokenError::Request)?;
1435 endpoint_response(http_response)
1436 }
1437
1438 fn prepare_request<RE>(&self) -> Result<HttpRequest, RequestTokenError<RE, TE>>
1439 where
1440 RE: Error + 'static,
1441 {
1442 Ok(endpoint_request(
1443 self.auth_type,
1444 self.client_id,
1445 self.client_secret,
1446 &self.extra_params,
1447 None,
1448 Some(&self.scopes),
1449 self.token_url
1450 .ok_or_else(|| RequestTokenError::Other("no token_url provided".to_string()))?
1451 .url(),
1452 vec![
1453 ("grant_type", "refresh_token"),
1454 ("refresh_token", self.refresh_token.secret()),
1455 ],
1456 ))
1457 }
1458}
1459
1460///
1461/// A request to exchange resource owner credentials for an access token.
1462///
1463/// See <https://tools.ietf.org/html/rfc6749#section-4.3>.
1464///
1465#[derive(Debug)]
1466pub struct PasswordTokenRequest<'a, TE, TR, TT>
1467where
1468 TE: ErrorResponse,
1469 TR: TokenResponse<TT>,
1470 TT: TokenType,
1471{
1472 auth_type: &'a AuthType,
1473 client_id: &'a ClientId,
1474 client_secret: Option<&'a ClientSecret>,
1475 extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
1476 username: &'a ResourceOwnerUsername,
1477 password: &'a ResourceOwnerPassword,
1478 scopes: Vec<Cow<'a, Scope>>,
1479 token_url: Option<&'a TokenUrl>,
1480 _phantom: PhantomData<(TE, TR, TT)>,
1481}
1482impl<'a, TE, TR, TT> PasswordTokenRequest<'a, TE, TR, TT>
1483where
1484 TE: ErrorResponse + 'static,
1485 TR: TokenResponse<TT>,
1486 TT: TokenType,
1487{
1488 ///
1489 /// Appends an extra param to the token request.
1490 ///
1491 /// This method allows extensions to be used without direct support from
1492 /// this crate. If `name` conflicts with a parameter managed by this crate, the
1493 /// behavior is undefined. In particular, do not set parameters defined by
1494 /// [RFC 6749](https://tools.ietf.org/html/rfc6749) or
1495 /// [RFC 7636](https://tools.ietf.org/html/rfc7636).
1496 ///
1497 /// # Security Warning
1498 ///
1499 /// Callers should follow the security recommendations for any OAuth2 extensions used with
1500 /// this function, which are beyond the scope of
1501 /// [RFC 6749](https://tools.ietf.org/html/rfc6749).
1502 ///
1503 pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
1504 where
1505 N: Into<Cow<'a, str>>,
1506 V: Into<Cow<'a, str>>,
1507 {
1508 self.extra_params.push((name.into(), value.into()));
1509 self
1510 }
1511
1512 ///
1513 /// Appends a new scope to the token request.
1514 ///
1515 pub fn add_scope(mut self, scope: Scope) -> Self {
1516 self.scopes.push(Cow::Owned(scope));
1517 self
1518 }
1519
1520 ///
1521 /// Appends a collection of scopes to the token request.
1522 ///
1523 pub fn add_scopes<I>(mut self, scopes: I) -> Self
1524 where
1525 I: IntoIterator<Item = Scope>,
1526 {
1527 self.scopes.extend(scopes.into_iter().map(Cow::Owned));
1528 self
1529 }
1530
1531 ///
1532 /// Synchronously sends the request to the authorization server and awaits a response.
1533 ///
1534 pub fn request<F, RE>(self, http_client: F) -> Result<TR, RequestTokenError<RE, TE>>
1535 where
1536 F: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
1537 RE: Error + 'static,
1538 {
1539 http_client(self.prepare_request()?)
1540 .map_err(RequestTokenError::Request)
1541 .and_then(endpoint_response)
1542 }
1543
1544 ///
1545 /// Asynchronously sends the request to the authorization server and awaits a response.
1546 ///
1547 pub async fn request_async<C, F, RE>(
1548 self,
1549 http_client: C,
1550 ) -> Result<TR, RequestTokenError<RE, TE>>
1551 where
1552 C: FnOnce(HttpRequest) -> F,
1553 F: Future<Output = Result<HttpResponse, RE>>,
1554 RE: Error + 'static,
1555 {
1556 let http_request = self.prepare_request()?;
1557 let http_response = http_client(http_request)
1558 .await
1559 .map_err(RequestTokenError::Request)?;
1560 endpoint_response(http_response)
1561 }
1562
1563 fn prepare_request<RE>(&self) -> Result<HttpRequest, RequestTokenError<RE, TE>>
1564 where
1565 RE: Error + 'static,
1566 {
1567 Ok(endpoint_request(
1568 self.auth_type,
1569 self.client_id,
1570 self.client_secret,
1571 &self.extra_params,
1572 None,
1573 Some(&self.scopes),
1574 self.token_url
1575 .ok_or_else(|| RequestTokenError::Other("no token_url provided".to_string()))?
1576 .url(),
1577 vec![
1578 ("grant_type", "password"),
1579 ("username", self.username),
1580 ("password", self.password.secret()),
1581 ],
1582 ))
1583 }
1584}
1585
1586///
1587/// A request to exchange client credentials for an access token.
1588///
1589/// See <https://tools.ietf.org/html/rfc6749#section-4.4>.
1590///
1591#[derive(Debug)]
1592pub struct ClientCredentialsTokenRequest<'a, TE, TR, TT>
1593where
1594 TE: ErrorResponse,
1595 TR: TokenResponse<TT>,
1596 TT: TokenType,
1597{
1598 auth_type: &'a AuthType,
1599 client_id: &'a ClientId,
1600 client_secret: Option<&'a ClientSecret>,
1601 extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
1602 scopes: Vec<Cow<'a, Scope>>,
1603 token_url: Option<&'a TokenUrl>,
1604 _phantom: PhantomData<(TE, TR, TT)>,
1605}
1606impl<'a, TE, TR, TT> ClientCredentialsTokenRequest<'a, TE, TR, TT>
1607where
1608 TE: ErrorResponse + 'static,
1609 TR: TokenResponse<TT>,
1610 TT: TokenType,
1611{
1612 ///
1613 /// Appends an extra param to the token request.
1614 ///
1615 /// This method allows extensions to be used without direct support from
1616 /// this crate. If `name` conflicts with a parameter managed by this crate, the
1617 /// behavior is undefined. In particular, do not set parameters defined by
1618 /// [RFC 6749](https://tools.ietf.org/html/rfc6749) or
1619 /// [RFC 7636](https://tools.ietf.org/html/rfc7636).
1620 ///
1621 /// # Security Warning
1622 ///
1623 /// Callers should follow the security recommendations for any OAuth2 extensions used with
1624 /// this function, which are beyond the scope of
1625 /// [RFC 6749](https://tools.ietf.org/html/rfc6749).
1626 ///
1627 pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
1628 where
1629 N: Into<Cow<'a, str>>,
1630 V: Into<Cow<'a, str>>,
1631 {
1632 self.extra_params.push((name.into(), value.into()));
1633 self
1634 }
1635
1636 ///
1637 /// Appends a new scope to the token request.
1638 ///
1639 pub fn add_scope(mut self, scope: Scope) -> Self {
1640 self.scopes.push(Cow::Owned(scope));
1641 self
1642 }
1643
1644 ///
1645 /// Appends a collection of scopes to the token request.
1646 ///
1647 pub fn add_scopes<I>(mut self, scopes: I) -> Self
1648 where
1649 I: IntoIterator<Item = Scope>,
1650 {
1651 self.scopes.extend(scopes.into_iter().map(Cow::Owned));
1652 self
1653 }
1654
1655 ///
1656 /// Synchronously sends the request to the authorization server and awaits a response.
1657 ///
1658 pub fn request<F, RE>(self, http_client: F) -> Result<TR, RequestTokenError<RE, TE>>
1659 where
1660 F: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
1661 RE: Error + 'static,
1662 {
1663 http_client(self.prepare_request()?)
1664 .map_err(RequestTokenError::Request)
1665 .and_then(endpoint_response)
1666 }
1667
1668 ///
1669 /// Asynchronously sends the request to the authorization server and awaits a response.
1670 ///
1671 pub async fn request_async<C, F, RE>(
1672 self,
1673 http_client: C,
1674 ) -> Result<TR, RequestTokenError<RE, TE>>
1675 where
1676 C: FnOnce(HttpRequest) -> F,
1677 F: Future<Output = Result<HttpResponse, RE>>,
1678 RE: Error + 'static,
1679 {
1680 let http_request = self.prepare_request()?;
1681 let http_response = http_client(http_request)
1682 .await
1683 .map_err(RequestTokenError::Request)?;
1684 endpoint_response(http_response)
1685 }
1686
1687 fn prepare_request<RE>(&self) -> Result<HttpRequest, RequestTokenError<RE, TE>>
1688 where
1689 RE: Error + 'static,
1690 {
1691 Ok(endpoint_request(
1692 self.auth_type,
1693 self.client_id,
1694 self.client_secret,
1695 &self.extra_params,
1696 None,
1697 Some(&self.scopes),
1698 self.token_url
1699 .ok_or_else(|| RequestTokenError::Other("no token_url provided".to_string()))?
1700 .url(),
1701 vec![("grant_type", "client_credentials")],
1702 ))
1703 }
1704}
1705
1706///
1707/// A request to introspect an access token.
1708///
1709/// See <https://tools.ietf.org/html/rfc7662#section-2.1>.
1710///
1711#[derive(Debug)]
1712pub struct IntrospectionRequest<'a, TE, TIR, TT>
1713where
1714 TE: ErrorResponse,
1715 TIR: TokenIntrospectionResponse<TT>,
1716 TT: TokenType,
1717{
1718 token: &'a AccessToken,
1719 token_type_hint: Option<Cow<'a, str>>,
1720
1721 auth_type: &'a AuthType,
1722 client_id: &'a ClientId,
1723 client_secret: Option<&'a ClientSecret>,
1724 extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
1725 introspection_url: &'a IntrospectionUrl,
1726
1727 _phantom: PhantomData<(TE, TIR, TT)>,
1728}
1729
1730impl<'a, TE, TIR, TT> IntrospectionRequest<'a, TE, TIR, TT>
1731where
1732 TE: ErrorResponse + 'static,
1733 TIR: TokenIntrospectionResponse<TT>,
1734 TT: TokenType,
1735{
1736 ///
1737 /// Sets the optional token_type_hint parameter.
1738 ///
1739 /// See <https://tools.ietf.org/html/rfc7662#section-2.1>.
1740 ///
1741 /// OPTIONAL. A hint about the type of the token submitted for
1742 /// introspection. The protected resource MAY pass this parameter to
1743 /// help the authorization server optimize the token lookup. If the
1744 /// server is unable to locate the token using the given hint, it MUST
1745 /// extend its search across all of its supported token types. An
1746 /// authorization server MAY ignore this parameter, particularly if it
1747 /// is able to detect the token type automatically. Values for this
1748 /// field are defined in the "OAuth Token Type Hints" registry defined
1749 /// in OAuth Token Revocation [RFC7009](https://tools.ietf.org/html/rfc7009).
1750 ///
1751 pub fn set_token_type_hint<V>(mut self, value: V) -> Self
1752 where
1753 V: Into<Cow<'a, str>>,
1754 {
1755 self.token_type_hint = Some(value.into());
1756
1757 self
1758 }
1759
1760 ///
1761 /// Appends an extra param to the token introspection request.
1762 ///
1763 /// This method allows extensions to be used without direct support from
1764 /// this crate. If `name` conflicts with a parameter managed by this crate, the
1765 /// behavior is undefined. In particular, do not set parameters defined by
1766 /// [RFC 6749](https://tools.ietf.org/html/rfc6749) or
1767 /// [RFC 7662](https://tools.ietf.org/html/rfc7662).
1768 ///
1769 /// # Security Warning
1770 ///
1771 /// Callers should follow the security recommendations for any OAuth2 extensions used with
1772 /// this function, which are beyond the scope of
1773 /// [RFC 6749](https://tools.ietf.org/html/rfc6749).
1774 ///
1775 pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
1776 where
1777 N: Into<Cow<'a, str>>,
1778 V: Into<Cow<'a, str>>,
1779 {
1780 self.extra_params.push((name.into(), value.into()));
1781 self
1782 }
1783
1784 fn prepare_request<RE>(self) -> Result<HttpRequest, RequestTokenError<RE, TE>>
1785 where
1786 RE: Error + 'static,
1787 {
1788 let mut params: Vec<(&str, &str)> = vec![("token", self.token.secret())];
1789 if let Some(ref token_type_hint) = self.token_type_hint {
1790 params.push(("token_type_hint", token_type_hint));
1791 }
1792
1793 Ok(endpoint_request(
1794 self.auth_type,
1795 self.client_id,
1796 self.client_secret,
1797 &self.extra_params,
1798 None,
1799 None,
1800 self.introspection_url.url(),
1801 params,
1802 ))
1803 }
1804
1805 ///
1806 /// Synchronously sends the request to the authorization server and awaits a response.
1807 ///
1808 pub fn request<F, RE>(self, http_client: F) -> Result<TIR, RequestTokenError<RE, TE>>
1809 where
1810 F: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
1811 RE: Error + 'static,
1812 {
1813 http_client(self.prepare_request()?)
1814 .map_err(RequestTokenError::Request)
1815 .and_then(endpoint_response)
1816 }
1817
1818 ///
1819 /// Asynchronously sends the request to the authorization server and returns a Future.
1820 ///
1821 pub async fn request_async<C, F, RE>(
1822 self,
1823 http_client: C,
1824 ) -> Result<TIR, RequestTokenError<RE, TE>>
1825 where
1826 C: FnOnce(HttpRequest) -> F,
1827 F: Future<Output = Result<HttpResponse, RE>>,
1828 RE: Error + 'static,
1829 {
1830 let http_request = self.prepare_request()?;
1831 let http_response = http_client(http_request)
1832 .await
1833 .map_err(RequestTokenError::Request)?;
1834 endpoint_response(http_response)
1835 }
1836}
1837
1838///
1839/// A request to revoke a token via an [`RFC 7009`](https://tools.ietf.org/html/rfc7009#section-2.1) compatible
1840/// endpoint.
1841///
1842#[derive(Debug)]
1843pub struct RevocationRequest<'a, RT, TE>
1844where
1845 RT: RevocableToken,
1846 TE: ErrorResponse,
1847{
1848 token: RT,
1849
1850 auth_type: &'a AuthType,
1851 client_id: &'a ClientId,
1852 client_secret: Option<&'a ClientSecret>,
1853 extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
1854 revocation_url: &'a RevocationUrl,
1855
1856 _phantom: PhantomData<(RT, TE)>,
1857}
1858
1859impl<'a, RT, TE> RevocationRequest<'a, RT, TE>
1860where
1861 RT: RevocableToken,
1862 TE: ErrorResponse + 'static,
1863{
1864 ///
1865 /// Appends an extra param to the token revocation request.
1866 ///
1867 /// This method allows extensions to be used without direct support from
1868 /// this crate. If `name` conflicts with a parameter managed by this crate, the
1869 /// behavior is undefined. In particular, do not set parameters defined by
1870 /// [RFC 6749](https://tools.ietf.org/html/rfc6749) or
1871 /// [RFC 7662](https://tools.ietf.org/html/rfc7662).
1872 ///
1873 /// # Security Warning
1874 ///
1875 /// Callers should follow the security recommendations for any OAuth2 extensions used with
1876 /// this function, which are beyond the scope of
1877 /// [RFC 6749](https://tools.ietf.org/html/rfc6749).
1878 ///
1879 pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
1880 where
1881 N: Into<Cow<'a, str>>,
1882 V: Into<Cow<'a, str>>,
1883 {
1884 self.extra_params.push((name.into(), value.into()));
1885 self
1886 }
1887
1888 fn prepare_request<RE>(self) -> Result<HttpRequest, RequestTokenError<RE, TE>>
1889 where
1890 RE: Error + 'static,
1891 {
1892 let mut params: Vec<(&str, &str)> = vec![("token", self.token.secret())];
1893 if let Some(type_hint) = self.token.type_hint() {
1894 params.push(("token_type_hint", type_hint));
1895 }
1896
1897 Ok(endpoint_request(
1898 self.auth_type,
1899 self.client_id,
1900 self.client_secret,
1901 &self.extra_params,
1902 None,
1903 None,
1904 self.revocation_url.url(),
1905 params,
1906 ))
1907 }
1908
1909 ///
1910 /// Synchronously sends the request to the authorization server and awaits a response.
1911 ///
1912 /// A successful response indicates that the server either revoked the token or the token was not known to the
1913 /// server.
1914 ///
1915 /// Error [`UnsupportedTokenType`](crate::revocation::RevocationErrorResponseType::UnsupportedTokenType) will be returned if the
1916 /// type of token type given is not supported by the server.
1917 ///
1918 pub fn request<F, RE>(self, http_client: F) -> Result<(), RequestTokenError<RE, TE>>
1919 where
1920 F: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
1921 RE: Error + 'static,
1922 {
1923 // From https://tools.ietf.org/html/rfc7009#section-2.2:
1924 // "The content of the response body is ignored by the client as all
1925 // necessary information is conveyed in the response code."
1926 http_client(self.prepare_request()?)
1927 .map_err(RequestTokenError::Request)
1928 .and_then(endpoint_response_status_only)
1929 }
1930
1931 ///
1932 /// Asynchronously sends the request to the authorization server and returns a Future.
1933 ///
1934 pub async fn request_async<C, F, RE>(
1935 self,
1936 http_client: C,
1937 ) -> Result<(), RequestTokenError<RE, TE>>
1938 where
1939 C: FnOnce(HttpRequest) -> F,
1940 F: Future<Output = Result<HttpResponse, RE>>,
1941 RE: Error + 'static,
1942 {
1943 let http_request = self.prepare_request()?;
1944 let http_response = http_client(http_request)
1945 .await
1946 .map_err(RequestTokenError::Request)?;
1947 endpoint_response_status_only(http_response)
1948 }
1949}
1950
1951#[allow(clippy::too_many_arguments)]
1952fn endpoint_request<'a>(
1953 auth_type: &'a AuthType,
1954 client_id: &'a ClientId,
1955 client_secret: Option<&'a ClientSecret>,
1956 extra_params: &'a [(Cow<'a, str>, Cow<'a, str>)],
1957 redirect_url: Option<Cow<'a, RedirectUrl>>,
1958 scopes: Option<&'a Vec<Cow<'a, Scope>>>,
1959 url: &'a Url,
1960 params: Vec<(&'a str, &'a str)>,
1961) -> HttpRequest {
1962 let mut headers = HeaderMap::new();
1963 headers.append(ACCEPT, HeaderValue::from_static(CONTENT_TYPE_JSON));
1964 headers.append(
1965 CONTENT_TYPE,
1966 HeaderValue::from_static(CONTENT_TYPE_FORMENCODED),
1967 );
1968
1969 let scopes_opt = scopes.and_then(|scopes| {
1970 if !scopes.is_empty() {
1971 Some(
1972 scopes
1973 .iter()
1974 .map(|s| s.to_string())
1975 .collect::<Vec<_>>()
1976 .join(" "),
1977 )
1978 } else {
1979 None
1980 }
1981 });
1982
1983 let mut params: Vec<(&str, &str)> = params;
1984 if let Some(ref scopes) = scopes_opt {
1985 params.push(("scope", scopes));
1986 }
1987
1988 // FIXME: add support for auth extensions? e.g., client_secret_jwt and private_key_jwt
1989 match (auth_type, client_secret) {
1990 // Basic auth only makes sense when a client secret is provided. Otherwise, always pass the
1991 // client ID in the request body.
1992 (AuthType::BasicAuth, Some(secret)) => {
1993 // Section 2.3.1 of RFC 6749 requires separately url-encoding the id and secret
1994 // before using them as HTTP Basic auth username and password. Note that this is
1995 // not standard for ordinary Basic auth, so curl won't do it for us.
1996 let urlencoded_id: String =
1997 form_urlencoded::byte_serialize(&client_id.as_bytes()).collect();
1998 let urlencoded_secret: String =
1999 form_urlencoded::byte_serialize(secret.secret().as_bytes()).collect();
2000 let b64_credential =
2001 base64::encode(&format!("{}:{}", &urlencoded_id, urlencoded_secret));
2002 headers.append(
2003 AUTHORIZATION,
2004 HeaderValue::from_str(&format!("Basic {}", &b64_credential)).unwrap(),
2005 );
2006 }
2007 (AuthType::RequestBody, _) | (AuthType::BasicAuth, None) => {
2008 params.push(("client_id", client_id));
2009 if let Some(ref client_secret) = client_secret {
2010 params.push(("client_secret", client_secret.secret()));
2011 }
2012 }
2013 }
2014
2015 if let Some(ref redirect_url) = redirect_url {
2016 params.push(("redirect_uri", redirect_url.as_str()));
2017 }
2018
2019 params.extend_from_slice(
2020 extra_params
2021 .iter()
2022 .map(|&(ref k, ref v)| (k.as_ref(), v.as_ref()))
2023 .collect::<Vec<_>>()
2024 .as_slice(),
2025 );
2026
2027 let body = url::form_urlencoded::Serializer::new(String::new())
2028 .extend_pairs(params)
2029 .finish()
2030 .into_bytes();
2031
2032 HttpRequest {
2033 url: url.to_owned(),
2034 method: http::method::Method::POST,
2035 headers,
2036 body,
2037 }
2038}
2039
2040fn endpoint_response<RE, TE, DO>(
2041 http_response: HttpResponse,
2042) -> Result<DO, RequestTokenError<RE, TE>>
2043where
2044 RE: Error + 'static,
2045 TE: ErrorResponse,
2046 DO: DeserializeOwned,
2047{
2048 check_response_status(&http_response)?;
2049
2050 check_response_body(&http_response)?;
2051
2052 let response_body = http_response.body.as_slice();
2053 serde_path_to_error::deserialize(&mut serde_json::Deserializer::from_slice(response_body))
2054 .map_err(|e| RequestTokenError::Parse(e, response_body.to_vec()))
2055}
2056
2057fn endpoint_response_status_only<RE, TE>(
2058 http_response: HttpResponse,
2059) -> Result<(), RequestTokenError<RE, TE>>
2060where
2061 RE: Error + 'static,
2062 TE: ErrorResponse,
2063{
2064 check_response_status(&http_response)
2065}
2066
2067fn check_response_status<RE, TE>(
2068 http_response: &HttpResponse,
2069) -> Result<(), RequestTokenError<RE, TE>>
2070where
2071 RE: Error + 'static,
2072 TE: ErrorResponse,
2073{
2074 if http_response.status_code != StatusCode::OK {
2075 let reason = http_response.body.as_slice();
2076 if reason.is_empty() {
2077 return Err(RequestTokenError::Other(
2078 "Server returned empty error response".to_string(),
2079 ));
2080 } else {
2081 let error = match serde_path_to_error::deserialize::<_, TE>(
2082 &mut serde_json::Deserializer::from_slice(reason),
2083 ) {
2084 Ok(error) => RequestTokenError::ServerResponse(error),
2085 Err(error) => RequestTokenError::Parse(error, reason.to_vec()),
2086 };
2087 return Err(error);
2088 }
2089 }
2090
2091 Ok(())
2092}
2093
2094fn check_response_body<RE, TE>(
2095 http_response: &HttpResponse,
2096) -> Result<(), RequestTokenError<RE, TE>>
2097where
2098 RE: Error + 'static,
2099 TE: ErrorResponse,
2100{
2101 // Validate that the response Content-Type is JSON.
2102 http_response
2103 .headers
2104 .get(CONTENT_TYPE)
2105 .map_or(Ok(()), |content_type|
2106 // Section 3.1.1.1 of RFC 7231 indicates that media types are case insensitive and
2107 // may be followed by optional whitespace and/or a parameter (e.g., charset).
2108 // See https://tools.ietf.org/html/rfc7231#section-3.1.1.1.
2109 if content_type.to_str().ok().filter(|ct| ct.to_lowercase().starts_with(CONTENT_TYPE_JSON)).is_none() {
2110 Err(
2111 RequestTokenError::Other(
2112 format!(
2113 "Unexpected response Content-Type: {:?}, should be `{}`",
2114 content_type,
2115 CONTENT_TYPE_JSON
2116 )
2117 )
2118 )
2119 } else {
2120 Ok(())
2121 }
2122 )?;
2123
2124 if http_response.body.is_empty() {
2125 return Err(RequestTokenError::Other(
2126 "Server returned empty response body".to_string(),
2127 ));
2128 }
2129
2130 Ok(())
2131}
2132
2133///
2134/// The request for a set of verification codes from the authorization server.
2135///
2136/// See <https://tools.ietf.org/html/rfc8628#section-3.1>.
2137///
2138#[derive(Debug)]
2139pub struct DeviceAuthorizationRequest<'a, TE>
2140where
2141 TE: ErrorResponse,
2142{
2143 auth_type: &'a AuthType,
2144 client_id: &'a ClientId,
2145 client_secret: Option<&'a ClientSecret>,
2146 extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
2147 scopes: Vec<Cow<'a, Scope>>,
2148 device_authorization_url: &'a DeviceAuthorizationUrl,
2149 _phantom: PhantomData<TE>,
2150}
2151
2152impl<'a, TE> DeviceAuthorizationRequest<'a, TE>
2153where
2154 TE: ErrorResponse + 'static,
2155{
2156 ///
2157 /// Appends an extra param to the token request.
2158 ///
2159 /// This method allows extensions to be used without direct support from
2160 /// this crate. If `name` conflicts with a parameter managed by this crate, the
2161 /// behavior is undefined. In particular, do not set parameters defined by
2162 /// [RFC 6749](https://tools.ietf.org/html/rfc6749) or
2163 /// [RFC 7636](https://tools.ietf.org/html/rfc7636).
2164 ///
2165 /// # Security Warning
2166 ///
2167 /// Callers should follow the security recommendations for any OAuth2 extensions used with
2168 /// this function, which are beyond the scope of
2169 /// [RFC 6749](https://tools.ietf.org/html/rfc6749).
2170 ///
2171 pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
2172 where
2173 N: Into<Cow<'a, str>>,
2174 V: Into<Cow<'a, str>>,
2175 {
2176 self.extra_params.push((name.into(), value.into()));
2177 self
2178 }
2179
2180 ///
2181 /// Appends a new scope to the token request.
2182 ///
2183 pub fn add_scope(mut self, scope: Scope) -> Self {
2184 self.scopes.push(Cow::Owned(scope));
2185 self
2186 }
2187
2188 ///
2189 /// Appends a collection of scopes to the token request.
2190 ///
2191 pub fn add_scopes<I>(mut self, scopes: I) -> Self
2192 where
2193 I: IntoIterator<Item = Scope>,
2194 {
2195 self.scopes.extend(scopes.into_iter().map(Cow::Owned));
2196 self
2197 }
2198
2199 fn prepare_request<RE>(self) -> Result<HttpRequest, RequestTokenError<RE, TE>>
2200 where
2201 RE: Error + 'static,
2202 {
2203 Ok(endpoint_request(
2204 self.auth_type,
2205 self.client_id,
2206 self.client_secret,
2207 &self.extra_params,
2208 None,
2209 Some(&self.scopes),
2210 self.device_authorization_url.url(),
2211 vec![],
2212 ))
2213 }
2214
2215 ///
2216 /// Synchronously sends the request to the authorization server and awaits a response.
2217 ///
2218 pub fn request<F, RE, EF>(
2219 self,
2220 http_client: F,
2221 ) -> Result<DeviceAuthorizationResponse<EF>, RequestTokenError<RE, TE>>
2222 where
2223 F: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
2224 RE: Error + 'static,
2225 EF: ExtraDeviceAuthorizationFields,
2226 {
2227 http_client(self.prepare_request()?)
2228 .map_err(RequestTokenError::Request)
2229 .and_then(endpoint_response)
2230 }
2231
2232 ///
2233 /// Asynchronously sends the request to the authorization server and returns a Future.
2234 ///
2235 pub async fn request_async<C, F, RE, EF>(
2236 self,
2237 http_client: C,
2238 ) -> Result<DeviceAuthorizationResponse<EF>, RequestTokenError<RE, TE>>
2239 where
2240 C: FnOnce(HttpRequest) -> F,
2241 F: Future<Output = Result<HttpResponse, RE>>,
2242 RE: Error + 'static,
2243 EF: ExtraDeviceAuthorizationFields,
2244 {
2245 let http_request = self.prepare_request()?;
2246 let http_response = http_client(http_request)
2247 .await
2248 .map_err(RequestTokenError::Request)?;
2249 endpoint_response(http_response)
2250 }
2251}
2252
2253///
2254/// The request for an device access token from the authorization server.
2255///
2256/// See <https://tools.ietf.org/html/rfc8628#section-3.4>.
2257///
2258#[derive(Clone)]
2259pub struct DeviceAccessTokenRequest<'a, 'b, TR, TT, EF>
2260where
2261 TR: TokenResponse<TT>,
2262 TT: TokenType,
2263 EF: ExtraDeviceAuthorizationFields,
2264{
2265 auth_type: &'a AuthType,
2266 client_id: &'a ClientId,
2267 client_secret: Option<&'a ClientSecret>,
2268 extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
2269 token_url: Option<&'a TokenUrl>,
2270 dev_auth_resp: &'a DeviceAuthorizationResponse<EF>,
2271 time_fn: Arc<dyn Fn() -> DateTime<Utc> + 'b + Send + Sync>,
2272 max_backoff_interval: Option<Duration>,
2273 _phantom: PhantomData<(TR, TT, EF)>,
2274}
2275
2276impl<'a, 'b, TR, TT, EF> DeviceAccessTokenRequest<'a, 'b, TR, TT, EF>
2277where
2278 TR: TokenResponse<TT>,
2279 TT: TokenType,
2280 EF: ExtraDeviceAuthorizationFields,
2281{
2282 ///
2283 /// Appends an extra param to the token request.
2284 ///
2285 /// This method allows extensions to be used without direct support from
2286 /// this crate. If `name` conflicts with a parameter managed by this crate, the
2287 /// behavior is undefined. In particular, do not set parameters defined by
2288 /// [RFC 6749](https://tools.ietf.org/html/rfc6749) or
2289 /// [RFC 7636](https://tools.ietf.org/html/rfc7636).
2290 ///
2291 /// # Security Warning
2292 ///
2293 /// Callers should follow the security recommendations for any OAuth2 extensions used with
2294 /// this function, which are beyond the scope of
2295 /// [RFC 6749](https://tools.ietf.org/html/rfc6749).
2296 ///
2297 pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
2298 where
2299 N: Into<Cow<'a, str>>,
2300 V: Into<Cow<'a, str>>,
2301 {
2302 self.extra_params.push((name.into(), value.into()));
2303 self
2304 }
2305
2306 ///
2307 /// Specifies a function for returning the current time.
2308 ///
2309 /// This function is used while polling the authorization server.
2310 ///
2311 pub fn set_time_fn<T>(mut self, time_fn: T) -> Self
2312 where
2313 T: Fn() -> DateTime<Utc> + 'b + Send + Sync,
2314 {
2315 self.time_fn = Arc::new(time_fn);
2316 self
2317 }
2318
2319 ///
2320 /// Sets the upper limit of the sleep interval to use for polling the token endpoint when the
2321 /// HTTP client returns an error (e.g., in case of connection timeout).
2322 ///
2323 pub fn set_max_backoff_interval(mut self, interval: Duration) -> Self {
2324 self.max_backoff_interval = Some(interval);
2325 self
2326 }
2327
2328 ///
2329 /// Synchronously polls the authorization server for a response, waiting
2330 /// using a user defined sleep function.
2331 ///
2332 pub fn request<F, S, RE>(
2333 self,
2334 http_client: F,
2335 sleep_fn: S,
2336 timeout: Option<Duration>,
2337 ) -> Result<TR, RequestTokenError<RE, DeviceCodeErrorResponse>>
2338 where
2339 F: Fn(HttpRequest) -> Result<HttpResponse, RE>,
2340 S: Fn(Duration),
2341 RE: Error + 'static,
2342 {
2343 // Get the request timeout and starting interval
2344 let timeout_dt = self.compute_timeout(timeout)?;
2345 let mut interval = self.dev_auth_resp.interval();
2346
2347 // Loop while requesting a token.
2348 loop {
2349 let now = (*self.time_fn)();
2350 if now > timeout_dt {
2351 break Err(RequestTokenError::ServerResponse(
2352 DeviceCodeErrorResponse::new(
2353 DeviceCodeErrorResponseType::ExpiredToken,
2354 Some(String::from("This device code has expired.")),
2355 None,
2356 ),
2357 ));
2358 }
2359
2360 match self.process_response(http_client(self.prepare_request()?), interval) {
2361 DeviceAccessTokenPollResult::ContinueWithNewPollInterval(new_interval) => {
2362 interval = new_interval
2363 }
2364 DeviceAccessTokenPollResult::Done(res, _) => break res,
2365 }
2366
2367 // Sleep here using the provided sleep function.
2368 sleep_fn(interval);
2369 }
2370 }
2371
2372 ///
2373 /// Asynchronously sends the request to the authorization server and awaits a response.
2374 ///
2375 pub async fn request_async<C, F, S, SF, RE>(
2376 self,
2377 http_client: C,
2378 sleep_fn: S,
2379 timeout: Option<Duration>,
2380 ) -> Result<TR, RequestTokenError<RE, DeviceCodeErrorResponse>>
2381 where
2382 C: Fn(HttpRequest) -> F,
2383 F: Future<Output = Result<HttpResponse, RE>>,
2384 S: Fn(Duration) -> SF,
2385 SF: Future<Output = ()>,
2386 RE: Error + 'static,
2387 {
2388 // Get the request timeout and starting interval
2389 let timeout_dt = self.compute_timeout(timeout)?;
2390 let mut interval = self.dev_auth_resp.interval();
2391
2392 // Loop while requesting a token.
2393 loop {
2394 let now = (*self.time_fn)();
2395 if now > timeout_dt {
2396 break Err(RequestTokenError::ServerResponse(
2397 DeviceCodeErrorResponse::new(
2398 DeviceCodeErrorResponseType::ExpiredToken,
2399 Some(String::from("This device code has expired.")),
2400 None,
2401 ),
2402 ));
2403 }
2404
2405 match self.process_response(http_client(self.prepare_request()?).await, interval) {
2406 DeviceAccessTokenPollResult::ContinueWithNewPollInterval(new_interval) => {
2407 interval = new_interval
2408 }
2409 DeviceAccessTokenPollResult::Done(res, _) => break res,
2410 }
2411
2412 // Sleep here using the provided sleep function.
2413 sleep_fn(interval).await;
2414 }
2415 }
2416
2417 fn prepare_request<RE>(
2418 &self,
2419 ) -> Result<HttpRequest, RequestTokenError<RE, DeviceCodeErrorResponse>>
2420 where
2421 RE: Error + 'static,
2422 {
2423 Ok(endpoint_request(
2424 self.auth_type,
2425 self.client_id,
2426 self.client_secret,
2427 &self.extra_params,
2428 None,
2429 None,
2430 self.token_url
2431 .ok_or_else(|| RequestTokenError::Other("no token_url provided".to_string()))?
2432 .url(),
2433 vec![
2434 ("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
2435 ("device_code", self.dev_auth_resp.device_code().secret()),
2436 ],
2437 ))
2438 }
2439
2440 fn process_response<RE>(
2441 &self,
2442 res: Result<HttpResponse, RE>,
2443 current_interval: Duration,
2444 ) -> DeviceAccessTokenPollResult<TR, RE, DeviceCodeErrorResponse, TT>
2445 where
2446 RE: Error + 'static,
2447 {
2448 let http_response = match res {
2449 Ok(inner) => inner,
2450 Err(_) => {
2451 // RFC 8628 requires a backoff in cases of connection timeout, but we can't
2452 // distinguish between connection timeouts and other HTTP client request errors
2453 // here. Set a maximum backoff so that the client doesn't effectively backoff
2454 // infinitely when there are network issues unrelated to server load.
2455 const DEFAULT_MAX_BACKOFF_INTERVAL: Duration = Duration::from_secs(10);
2456 let new_interval = std::cmp::min(
2457 current_interval.checked_mul(2).unwrap_or(current_interval),
2458 self.max_backoff_interval
2459 .unwrap_or(DEFAULT_MAX_BACKOFF_INTERVAL),
2460 );
2461 return DeviceAccessTokenPollResult::ContinueWithNewPollInterval(new_interval);
2462 }
2463 };
2464
2465 // Explicitly process the response with a DeviceCodeErrorResponse
2466 let res = endpoint_response::<RE, DeviceCodeErrorResponse, TR>(http_response);
2467 match res {
2468 // On a ServerResponse error, the error needs inspecting as a DeviceCodeErrorResponse
2469 // to work out whether a retry needs to happen.
2470 Err(RequestTokenError::ServerResponse(dcer)) => {
2471 match dcer.error() {
2472 // On AuthorizationPending, a retry needs to happen with the same poll interval.
2473 DeviceCodeErrorResponseType::AuthorizationPending => {
2474 DeviceAccessTokenPollResult::ContinueWithNewPollInterval(current_interval)
2475 }
2476 // On SlowDown, a retry needs to happen with a larger poll interval.
2477 DeviceCodeErrorResponseType::SlowDown => {
2478 DeviceAccessTokenPollResult::ContinueWithNewPollInterval(
2479 current_interval + Duration::from_secs(5),
2480 )
2481 }
2482
2483 // On any other error, just return the error.
2484 _ => DeviceAccessTokenPollResult::Done(
2485 Err(RequestTokenError::ServerResponse(dcer)),
2486 PhantomData,
2487 ),
2488 }
2489 }
2490
2491 // On any other success or failure, return the failure.
2492 res => DeviceAccessTokenPollResult::Done(res, PhantomData),
2493 }
2494 }
2495
2496 fn compute_timeout<RE>(
2497 &self,
2498 timeout: Option<Duration>,
2499 ) -> Result<DateTime<Utc>, RequestTokenError<RE, DeviceCodeErrorResponse>>
2500 where
2501 RE: Error + 'static,
2502 {
2503 // Calculate the request timeout - if the user specified a timeout,
2504 // use that, otherwise use the value given by the device authorization
2505 // response.
2506 let timeout_dur = timeout.unwrap_or_else(|| self.dev_auth_resp.expires_in());
2507 let chrono_timeout = chrono::Duration::from_std(timeout_dur)
2508 .map_err(|_| RequestTokenError::Other("Failed to convert duration".to_string()))?;
2509
2510 // Calculate the DateTime at which the request times out.
2511 let timeout_dt = (*self.time_fn)()
2512 .checked_add_signed(chrono_timeout)
2513 .ok_or_else(|| RequestTokenError::Other("Failed to calculate timeout".to_string()))?;
2514
2515 Ok(timeout_dt)
2516 }
2517}
2518
2519///
2520/// Trait for OAuth2 access tokens.
2521///
2522pub trait TokenType: Clone + DeserializeOwned + Debug + PartialEq + Serialize {}
2523
2524///
2525/// Trait for adding extra fields to the `TokenResponse`.
2526///
2527pub trait ExtraTokenFields: DeserializeOwned + Debug + Serialize {}
2528
2529///
2530/// Empty (default) extra token fields.
2531///
2532#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
2533pub struct EmptyExtraTokenFields {}
2534impl ExtraTokenFields for EmptyExtraTokenFields {}
2535
2536///
2537/// Common methods shared by all OAuth2 token implementations.
2538///
2539/// The methods in this trait are defined in
2540/// [Section 5.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-5.1). This trait exists
2541/// separately from the `StandardTokenResponse` struct to support customization by clients,
2542/// such as supporting interoperability with non-standards-complaint OAuth2 providers.
2543///
2544pub trait TokenResponse<TT>: Debug + DeserializeOwned + Serialize
2545where
2546 TT: TokenType,
2547{
2548 ///
2549 /// REQUIRED. The access token issued by the authorization server.
2550 ///
2551 fn access_token(&self) -> &AccessToken;
2552 ///
2553 /// REQUIRED. The type of the token issued as described in
2554 /// [Section 7.1](https://tools.ietf.org/html/rfc6749#section-7.1).
2555 /// Value is case insensitive and deserialized to the generic `TokenType` parameter.
2556 ///
2557 fn token_type(&self) -> &TT;
2558 ///
2559 /// RECOMMENDED. The lifetime in seconds of the access token. For example, the value 3600
2560 /// denotes that the access token will expire in one hour from the time the response was
2561 /// generated. If omitted, the authorization server SHOULD provide the expiration time via
2562 /// other means or document the default value.
2563 ///
2564 fn expires_in(&self) -> Option<Duration>;
2565 ///
2566 /// OPTIONAL. The refresh token, which can be used to obtain new access tokens using the same
2567 /// authorization grant as described in
2568 /// [Section 6](https://tools.ietf.org/html/rfc6749#section-6).
2569 ///
2570 fn refresh_token(&self) -> Option<&RefreshToken>;
2571 ///
2572 /// OPTIONAL, if identical to the scope requested by the client; otherwise, REQUIRED. The
2573 /// scope of the access token as described by
2574 /// [Section 3.3](https://tools.ietf.org/html/rfc6749#section-3.3). If included in the response,
2575 /// this space-delimited field is parsed into a `Vec` of individual scopes. If omitted from
2576 /// the response, this field is `None`.
2577 ///
2578 fn scopes(&self) -> Option<&Vec<Scope>>;
2579}
2580
2581///
2582/// Standard OAuth2 token response.
2583///
2584/// This struct includes the fields defined in
2585/// [Section 5.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-5.1), as well as
2586/// extensions defined by the `EF` type parameter.
2587///
2588#[derive(Clone, Debug, Deserialize, Serialize)]
2589pub struct StandardTokenResponse<EF, TT>
2590where
2591 EF: ExtraTokenFields,
2592 TT: TokenType,
2593{
2594 access_token: AccessToken,
2595 #[serde(bound = "TT: TokenType")]
2596 #[serde(deserialize_with = "helpers::deserialize_untagged_enum_case_insensitive")]
2597 token_type: TT,
2598 #[serde(skip_serializing_if = "Option::is_none")]
2599 expires_in: Option<u64>,
2600 #[serde(skip_serializing_if = "Option::is_none")]
2601 refresh_token: Option<RefreshToken>,
2602 #[serde(rename = "scope")]
2603 #[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")]
2604 #[serde(serialize_with = "helpers::serialize_space_delimited_vec")]
2605 #[serde(skip_serializing_if = "Option::is_none")]
2606 #[serde(default)]
2607 scopes: Option<Vec<Scope>>,
2608
2609 #[serde(bound = "EF: ExtraTokenFields")]
2610 #[serde(flatten)]
2611 extra_fields: EF,
2612}
2613impl<EF, TT> StandardTokenResponse<EF, TT>
2614where
2615 EF: ExtraTokenFields,
2616 TT: TokenType,
2617{
2618 ///
2619 /// Instantiate a new OAuth2 token response.
2620 ///
2621 pub fn new(access_token: AccessToken, token_type: TT, extra_fields: EF) -> Self {
2622 Self {
2623 access_token,
2624 token_type,
2625 expires_in: None,
2626 refresh_token: None,
2627 scopes: None,
2628 extra_fields,
2629 }
2630 }
2631
2632 ///
2633 /// Set the `access_token` field.
2634 ///
2635 pub fn set_access_token(&mut self, access_token: AccessToken) {
2636 self.access_token = access_token;
2637 }
2638
2639 ///
2640 /// Set the `token_type` field.
2641 ///
2642 pub fn set_token_type(&mut self, token_type: TT) {
2643 self.token_type = token_type;
2644 }
2645
2646 ///
2647 /// Set the `expires_in` field.
2648 ///
2649 pub fn set_expires_in(&mut self, expires_in: Option<&Duration>) {
2650 self.expires_in = expires_in.map(Duration::as_secs);
2651 }
2652
2653 ///
2654 /// Set the `refresh_token` field.
2655 ///
2656 pub fn set_refresh_token(&mut self, refresh_token: Option<RefreshToken>) {
2657 self.refresh_token = refresh_token;
2658 }
2659
2660 ///
2661 /// Set the `scopes` field.
2662 ///
2663 pub fn set_scopes(&mut self, scopes: Option<Vec<Scope>>) {
2664 self.scopes = scopes;
2665 }
2666
2667 ///
2668 /// Extra fields defined by the client application.
2669 ///
2670 pub fn extra_fields(&self) -> &EF {
2671 &self.extra_fields
2672 }
2673
2674 ///
2675 /// Set the extra fields defined by the client application.
2676 ///
2677 pub fn set_extra_fields(&mut self, extra_fields: EF) {
2678 self.extra_fields = extra_fields;
2679 }
2680}
2681impl<EF, TT> TokenResponse<TT> for StandardTokenResponse<EF, TT>
2682where
2683 EF: ExtraTokenFields,
2684 TT: TokenType,
2685{
2686 ///
2687 /// REQUIRED. The access token issued by the authorization server.
2688 ///
2689 fn access_token(&self) -> &AccessToken {
2690 &self.access_token
2691 }
2692 ///
2693 /// REQUIRED. The type of the token issued as described in
2694 /// [Section 7.1](https://tools.ietf.org/html/rfc6749#section-7.1).
2695 /// Value is case insensitive and deserialized to the generic `TokenType` parameter.
2696 ///
2697 fn token_type(&self) -> &TT {
2698 &self.token_type
2699 }
2700 ///
2701 /// RECOMMENDED. The lifetime in seconds of the access token. For example, the value 3600
2702 /// denotes that the access token will expire in one hour from the time the response was
2703 /// generated. If omitted, the authorization server SHOULD provide the expiration time via
2704 /// other means or document the default value.
2705 ///
2706 fn expires_in(&self) -> Option<Duration> {
2707 self.expires_in.map(Duration::from_secs)
2708 }
2709 ///
2710 /// OPTIONAL. The refresh token, which can be used to obtain new access tokens using the same
2711 /// authorization grant as described in
2712 /// [Section 6](https://tools.ietf.org/html/rfc6749#section-6).
2713 ///
2714 fn refresh_token(&self) -> Option<&RefreshToken> {
2715 self.refresh_token.as_ref()
2716 }
2717 ///
2718 /// OPTIONAL, if identical to the scope requested by the client; otherwise, REQUIRED. The
2719 /// scope of the access token as described by
2720 /// [Section 3.3](https://tools.ietf.org/html/rfc6749#section-3.3). If included in the response,
2721 /// this space-delimited field is parsed into a `Vec` of individual scopes. If omitted from
2722 /// the response, this field is `None`.
2723 ///
2724 fn scopes(&self) -> Option<&Vec<Scope>> {
2725 self.scopes.as_ref()
2726 }
2727}
2728
2729///
2730/// Common methods shared by all OAuth2 token introspection implementations.
2731///
2732/// The methods in this trait are defined in
2733/// [Section 2.2 of RFC 7662](https://tools.ietf.org/html/rfc7662#section-2.2). This trait exists
2734/// separately from the `StandardTokenIntrospectionResponse` struct to support customization by
2735/// clients, such as supporting interoperability with non-standards-complaint OAuth2 providers.
2736///
2737pub trait TokenIntrospectionResponse<TT>: Debug + DeserializeOwned + Serialize
2738where
2739 TT: TokenType,
2740{
2741 ///
2742 /// REQUIRED. Boolean indicator of whether or not the presented token
2743 /// is currently active. The specifics of a token's "active" state
2744 /// will vary depending on the implementation of the authorization
2745 /// server and the information it keeps about its tokens, but a "true"
2746 /// value return for the "active" property will generally indicate
2747 /// that a given token has been issued by this authorization server,
2748 /// has not been revoked by the resource owner, and is within its
2749 /// given time window of validity (e.g., after its issuance time and
2750 /// before its expiration time).
2751 ///
2752 fn active(&self) -> bool;
2753 ///
2754 ///
2755 /// OPTIONAL. A JSON string containing a space-separated list of
2756 /// scopes associated with this token, in the format described in
2757 /// [Section 3.3 of RFC 7662](https://tools.ietf.org/html/rfc7662#section-3.3).
2758 /// If included in the response,
2759 /// this space-delimited field is parsed into a `Vec` of individual scopes. If omitted from
2760 /// the response, this field is `None`.
2761 ///
2762 fn scopes(&self) -> Option<&Vec<Scope>>;
2763 ///
2764 /// OPTIONAL. Client identifier for the OAuth 2.0 client that
2765 /// requested this token.
2766 ///
2767 fn client_id(&self) -> Option<&ClientId>;
2768 ///
2769 /// OPTIONAL. Human-readable identifier for the resource owner who
2770 /// authorized this token.
2771 ///
2772 fn username(&self) -> Option<&str>;
2773 ///
2774 /// OPTIONAL. Type of the token as defined in
2775 /// [Section 5.1 of RFC 7662](https://tools.ietf.org/html/rfc7662#section-5.1).
2776 /// Value is case insensitive and deserialized to the generic `TokenType` parameter.
2777 ///
2778 fn token_type(&self) -> Option<&TT>;
2779 ///
2780 /// OPTIONAL. Integer timestamp, measured in the number of seconds
2781 /// since January 1 1970 UTC, indicating when this token will expire,
2782 /// as defined in JWT [RFC7519](https://tools.ietf.org/html/rfc7519).
2783 ///
2784 fn exp(&self) -> Option<DateTime<Utc>>;
2785 ///
2786 /// OPTIONAL. Integer timestamp, measured in the number of seconds
2787 /// since January 1 1970 UTC, indicating when this token was
2788 /// originally issued, as defined in JWT [RFC7519](https://tools.ietf.org/html/rfc7519).
2789 ///
2790 fn iat(&self) -> Option<DateTime<Utc>>;
2791 ///
2792 /// OPTIONAL. Integer timestamp, measured in the number of seconds
2793 /// since January 1 1970 UTC, indicating when this token is not to be
2794 /// used before, as defined in JWT [RFC7519](https://tools.ietf.org/html/rfc7519).
2795 ///
2796 fn nbf(&self) -> Option<DateTime<Utc>>;
2797 ///
2798 /// OPTIONAL. Subject of the token, as defined in JWT [RFC7519](https://tools.ietf.org/html/rfc7519).
2799 /// Usually a machine-readable identifier of the resource owner who
2800 /// authorized this token.
2801 ///
2802 fn sub(&self) -> Option<&str>;
2803 ///
2804 /// OPTIONAL. Service-specific string identifier or list of string
2805 /// identifiers representing the intended audience for this token, as
2806 /// defined in JWT [RFC7519](https://tools.ietf.org/html/rfc7519).
2807 ///
2808 fn aud(&self) -> Option<&Vec<String>>;
2809 ///
2810 /// OPTIONAL. String representing the issuer of this token, as
2811 /// defined in JWT [RFC7519](https://tools.ietf.org/html/rfc7519).
2812 ///
2813 fn iss(&self) -> Option<&str>;
2814 ///
2815 /// OPTIONAL. String identifier for the token, as defined in JWT
2816 /// [RFC7519](https://tools.ietf.org/html/rfc7519).
2817 ///
2818 fn jti(&self) -> Option<&str>;
2819}
2820
2821///
2822/// Standard OAuth2 token introspection response.
2823///
2824/// This struct includes the fields defined in
2825/// [Section 2.2 of RFC 7662](https://tools.ietf.org/html/rfc7662#section-2.2), as well as
2826/// extensions defined by the `EF` type parameter.
2827///
2828#[derive(Clone, Debug, Deserialize, Serialize)]
2829pub struct StandardTokenIntrospectionResponse<EF, TT>
2830where
2831 EF: ExtraTokenFields,
2832 TT: TokenType + 'static,
2833{
2834 active: bool,
2835 #[serde(rename = "scope")]
2836 #[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")]
2837 #[serde(serialize_with = "helpers::serialize_space_delimited_vec")]
2838 #[serde(skip_serializing_if = "Option::is_none")]
2839 #[serde(default)]
2840 scopes: Option<Vec<Scope>>,
2841 #[serde(skip_serializing_if = "Option::is_none")]
2842 client_id: Option<ClientId>,
2843 #[serde(skip_serializing_if = "Option::is_none")]
2844 username: Option<String>,
2845 #[serde(
2846 bound = "TT: TokenType",
2847 skip_serializing_if = "Option::is_none",
2848 deserialize_with = "helpers::deserialize_untagged_enum_case_insensitive",
2849 default = "none_field"
2850 )]
2851 token_type: Option<TT>,
2852 #[serde(skip_serializing_if = "Option::is_none")]
2853 #[serde(with = "ts_seconds_option")]
2854 #[serde(default)]
2855 exp: Option<DateTime<Utc>>,
2856 #[serde(skip_serializing_if = "Option::is_none")]
2857 #[serde(with = "ts_seconds_option")]
2858 #[serde(default)]
2859 iat: Option<DateTime<Utc>>,
2860 #[serde(skip_serializing_if = "Option::is_none")]
2861 #[serde(with = "ts_seconds_option")]
2862 #[serde(default)]
2863 nbf: Option<DateTime<Utc>>,
2864 #[serde(skip_serializing_if = "Option::is_none")]
2865 sub: Option<String>,
2866 #[serde(skip_serializing_if = "Option::is_none")]
2867 #[serde(default)]
2868 #[serde(deserialize_with = "helpers::deserialize_optional_string_or_vec_string")]
2869 aud: Option<Vec<String>>,
2870 #[serde(skip_serializing_if = "Option::is_none")]
2871 iss: Option<String>,
2872 #[serde(skip_serializing_if = "Option::is_none")]
2873 jti: Option<String>,
2874
2875 #[serde(bound = "EF: ExtraTokenFields")]
2876 #[serde(flatten)]
2877 extra_fields: EF,
2878}
2879
2880fn none_field<T>() -> Option<T> {
2881 None
2882}
2883
2884impl<EF, TT> StandardTokenIntrospectionResponse<EF, TT>
2885where
2886 EF: ExtraTokenFields,
2887 TT: TokenType,
2888{
2889 ///
2890 /// Instantiate a new OAuth2 token introspection response.
2891 ///
2892 pub fn new(active: bool, extra_fields: EF) -> Self {
2893 Self {
2894 active,
2895
2896 scopes: None,
2897 client_id: None,
2898 username: None,
2899 token_type: None,
2900 exp: None,
2901 iat: None,
2902 nbf: None,
2903 sub: None,
2904 aud: None,
2905 iss: None,
2906 jti: None,
2907 extra_fields,
2908 }
2909 }
2910
2911 ///
2912 /// Sets the `set_active` field.
2913 ///
2914 pub fn set_active(&mut self, active: bool) {
2915 self.active = active;
2916 }
2917 ///
2918 /// Sets the `set_scopes` field.
2919 ///
2920 pub fn set_scopes(&mut self, scopes: Option<Vec<Scope>>) {
2921 self.scopes = scopes;
2922 }
2923 ///
2924 /// Sets the `set_client_id` field.
2925 ///
2926 pub fn set_client_id(&mut self, client_id: Option<ClientId>) {
2927 self.client_id = client_id;
2928 }
2929 ///
2930 /// Sets the `set_username` field.
2931 ///
2932 pub fn set_username(&mut self, username: Option<String>) {
2933 self.username = username;
2934 }
2935 ///
2936 /// Sets the `set_token_type` field.
2937 ///
2938 pub fn set_token_type(&mut self, token_type: Option<TT>) {
2939 self.token_type = token_type;
2940 }
2941 ///
2942 /// Sets the `set_exp` field.
2943 ///
2944 pub fn set_exp(&mut self, exp: Option<DateTime<Utc>>) {
2945 self.exp = exp;
2946 }
2947 ///
2948 /// Sets the `set_iat` field.
2949 ///
2950 pub fn set_iat(&mut self, iat: Option<DateTime<Utc>>) {
2951 self.iat = iat;
2952 }
2953 ///
2954 /// Sets the `set_nbf` field.
2955 ///
2956 pub fn set_nbf(&mut self, nbf: Option<DateTime<Utc>>) {
2957 self.nbf = nbf;
2958 }
2959 ///
2960 /// Sets the `set_sub` field.
2961 ///
2962 pub fn set_sub(&mut self, sub: Option<String>) {
2963 self.sub = sub;
2964 }
2965 ///
2966 /// Sets the `set_aud` field.
2967 ///
2968 pub fn set_aud(&mut self, aud: Option<Vec<String>>) {
2969 self.aud = aud;
2970 }
2971 ///
2972 /// Sets the `set_iss` field.
2973 ///
2974 pub fn set_iss(&mut self, iss: Option<String>) {
2975 self.iss = iss;
2976 }
2977 ///
2978 /// Sets the `set_jti` field.
2979 ///
2980 pub fn set_jti(&mut self, jti: Option<String>) {
2981 self.jti = jti;
2982 }
2983 ///
2984 /// Extra fields defined by the client application.
2985 ///
2986 pub fn extra_fields(&self) -> &EF {
2987 &self.extra_fields
2988 }
2989 ///
2990 /// Sets the `set_extra_fields` field.
2991 ///
2992 pub fn set_extra_fields(&mut self, extra_fields: EF) {
2993 self.extra_fields = extra_fields;
2994 }
2995}
2996impl<EF, TT> TokenIntrospectionResponse<TT> for StandardTokenIntrospectionResponse<EF, TT>
2997where
2998 EF: ExtraTokenFields,
2999 TT: TokenType,
3000{
3001 fn active(&self) -> bool {
3002 self.active
3003 }
3004
3005 fn scopes(&self) -> Option<&Vec<Scope>> {
3006 self.scopes.as_ref()
3007 }
3008
3009 fn client_id(&self) -> Option<&ClientId> {
3010 self.client_id.as_ref()
3011 }
3012
3013 fn username(&self) -> Option<&str> {
3014 self.username.as_deref()
3015 }
3016
3017 fn token_type(&self) -> Option<&TT> {
3018 self.token_type.as_ref()
3019 }
3020
3021 fn exp(&self) -> Option<DateTime<Utc>> {
3022 self.exp
3023 }
3024
3025 fn iat(&self) -> Option<DateTime<Utc>> {
3026 self.iat
3027 }
3028
3029 fn nbf(&self) -> Option<DateTime<Utc>> {
3030 self.nbf
3031 }
3032
3033 fn sub(&self) -> Option<&str> {
3034 self.sub.as_deref()
3035 }
3036
3037 fn aud(&self) -> Option<&Vec<String>> {
3038 self.aud.as_ref()
3039 }
3040
3041 fn iss(&self) -> Option<&str> {
3042 self.iss.as_deref()
3043 }
3044
3045 fn jti(&self) -> Option<&str> {
3046 self.jti.as_deref()
3047 }
3048}
3049
3050///
3051/// Server Error Response
3052///
3053/// This trait exists separately from the `StandardErrorResponse` struct
3054/// to support customization by clients, such as supporting interoperability with
3055/// non-standards-complaint OAuth2 providers
3056///
3057pub trait ErrorResponse: Debug + DeserializeOwned + Serialize {}
3058
3059///
3060/// Error types enum.
3061///
3062/// NOTE: The serialization must return the `snake_case` representation of
3063/// this error type. This value must match the error type from the relevant OAuth 2.0 standards
3064/// (RFC 6749 or an extension).
3065///
3066pub trait ErrorResponseType: Debug + DeserializeOwned + Serialize {}
3067
3068///
3069/// Error response returned by server after requesting an access token.
3070///
3071/// The fields in this structure are defined in
3072/// [Section 5.2 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-5.2). This
3073/// trait is parameterized by a `ErrorResponseType` to support error types specific to future OAuth2
3074/// authentication schemes and extensions.
3075///
3076#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
3077pub struct StandardErrorResponse<T: ErrorResponseType> {
3078 #[serde(bound = "T: ErrorResponseType")]
3079 error: T,
3080 #[serde(default)]
3081 #[serde(skip_serializing_if = "Option::is_none")]
3082 error_description: Option<String>,
3083 #[serde(default)]
3084 #[serde(skip_serializing_if = "Option::is_none")]
3085 error_uri: Option<String>,
3086}
3087
3088impl<T: ErrorResponseType> StandardErrorResponse<T> {
3089 ///
3090 /// Instantiate a new `ErrorResponse`.
3091 ///
3092 /// # Arguments
3093 ///
3094 /// * `error` - REQUIRED. A single ASCII error code deserialized to the generic parameter.
3095 /// `ErrorResponseType`.
3096 /// * `error_description` - OPTIONAL. Human-readable ASCII text providing additional
3097 /// information, used to assist the client developer in understanding the error that
3098 /// occurred. Values for this parameter MUST NOT include characters outside the set
3099 /// `%x20-21 / %x23-5B / %x5D-7E`.
3100 /// * `error_uri` - OPTIONAL. A URI identifying a human-readable web page with information
3101 /// about the error used to provide the client developer with additional information about
3102 /// the error. Values for the "error_uri" parameter MUST conform to the URI-reference
3103 /// syntax and thus MUST NOT include characters outside the set `%x21 / %x23-5B / %x5D-7E`.
3104 ///
3105 pub fn new(error: T, error_description: Option<String>, error_uri: Option<String>) -> Self {
3106 Self {
3107 error,
3108 error_description,
3109 error_uri,
3110 }
3111 }
3112
3113 ///
3114 /// REQUIRED. A single ASCII error code deserialized to the generic parameter
3115 /// `ErrorResponseType`.
3116 ///
3117 pub fn error(&self) -> &T {
3118 &self.error
3119 }
3120 ///
3121 /// OPTIONAL. Human-readable ASCII text providing additional information, used to assist
3122 /// the client developer in understanding the error that occurred. Values for this
3123 /// parameter MUST NOT include characters outside the set `%x20-21 / %x23-5B / %x5D-7E`.
3124 ///
3125 pub fn error_description(&self) -> Option<&String> {
3126 self.error_description.as_ref()
3127 }
3128 ///
3129 /// OPTIONAL. URI identifying a human-readable web page with information about the error,
3130 /// used to provide the client developer with additional information about the error.
3131 /// Values for the "error_uri" parameter MUST conform to the URI-reference syntax and
3132 /// thus MUST NOT include characters outside the set `%x21 / %x23-5B / %x5D-7E`.
3133 ///
3134 pub fn error_uri(&self) -> Option<&String> {
3135 self.error_uri.as_ref()
3136 }
3137}
3138
3139impl<T> ErrorResponse for StandardErrorResponse<T> where T: ErrorResponseType + 'static {}
3140
3141impl<TE> Display for StandardErrorResponse<TE>
3142where
3143 TE: ErrorResponseType + Display,
3144{
3145 fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
3146 let mut formatted = self.error().to_string();
3147
3148 if let Some(error_description) = self.error_description() {
3149 formatted.push_str(": ");
3150 formatted.push_str(error_description);
3151 }
3152
3153 if let Some(error_uri) = self.error_uri() {
3154 formatted.push_str(" / See ");
3155 formatted.push_str(error_uri);
3156 }
3157
3158 write!(f, "{}", formatted)
3159 }
3160}
3161
3162///
3163/// Error encountered while requesting access token.
3164///
3165#[derive(Debug, thiserror::Error)]
3166pub enum RequestTokenError<RE, T>
3167where
3168 RE: Error + 'static,
3169 T: ErrorResponse + 'static,
3170{
3171 ///
3172 /// Error response returned by authorization server. Contains the parsed `ErrorResponse`
3173 /// returned by the server.
3174 ///
3175 #[error("Server returned error response")]
3176 ServerResponse(T),
3177 ///
3178 /// An error occurred while sending the request or receiving the response (e.g., network
3179 /// connectivity failed).
3180 ///
3181 #[error("Request failed")]
3182 Request(#[source] RE),
3183 ///
3184 /// Failed to parse server response. Parse errors may occur while parsing either successful
3185 /// or error responses.
3186 ///
3187 #[error("Failed to parse server response")]
3188 Parse(
3189 #[source] serde_path_to_error::Error<serde_json::error::Error>,
3190 Vec<u8>,
3191 ),
3192 ///
3193 /// Some other type of error occurred (e.g., an unexpected server response).
3194 ///
3195 #[error("Other error: {}", _0)]
3196 Other(String),
3197}