From 3aadc6b0efb339ebcd2975e4b90a87b7a6bb743f Mon Sep 17 00:00:00 2001 From: Glenn Griffin Date: Wed, 13 Nov 2019 11:51:28 -0800 Subject: [PATCH] Major refactor of the public API. 1) Remove the GetToken trait. The trait seemed to be organically designed. It appeared to be mostly tailored for simplifying the implementation since there was no way for users to provide their own implementation to Authenticator. It sadly seemed to get in the way of implementations more than it helped. An enum representing the known implementations is a more straightforward way to accomplish the goal and also has the benefit of not requiring Boxing when returning features (which admittedly is a minor concern for this use case). 2) Reduce the number of type parameters by using trait object for delegates. This simplifies the code considerably and the performance impact of virtual dispatch for the delegate calls is a non-factor. 3) With the above two simplifications it became easier to unify the public interface for building an authenticator. See the examples for how InstalledFlow, DeviceFlow, and ServiceAccount authenticators are now created. --- examples/test-device/src/main.rs | 6 +- examples/test-installed/src/main.rs | 19 +- examples/test-svc-acct/src/main.rs | 11 +- src/authenticator.rs | 505 ++++++++++++++++------------ src/device.rs | 222 +++++------- src/installed.rs | 203 +++++------ src/lib.rs | 29 +- src/service_account.rs | 156 ++++----- src/types.rs | 21 -- 9 files changed, 519 insertions(+), 653 deletions(-) diff --git a/examples/test-device/src/main.rs b/examples/test-device/src/main.rs index 62bdf10..82a4741 100644 --- a/examples/test-device/src/main.rs +++ b/examples/test-device/src/main.rs @@ -1,13 +1,13 @@ -use yup_oauth2::{self, Authenticator, DeviceFlow, GetToken}; +use yup_oauth2::DeviceFlowAuthenticator; use std::path; use tokio; #[tokio::main] async fn main() { - let creds = yup_oauth2::read_application_secret(path::Path::new("clientsecret.json")) + let app_secret = yup_oauth2::read_application_secret(path::Path::new("clientsecret.json")) .expect("clientsecret"); - let auth = Authenticator::new(DeviceFlow::new(creds)) + let auth = DeviceFlowAuthenticator::builder(app_secret) .persist_tokens_to_disk("tokenstorage.json") .build() .await diff --git a/examples/test-installed/src/main.rs b/examples/test-installed/src/main.rs index 1bdfee0..3febb75 100644 --- a/examples/test-installed/src/main.rs +++ b/examples/test-installed/src/main.rs @@ -1,21 +1,18 @@ -use yup_oauth2::GetToken; -use yup_oauth2::{Authenticator, InstalledFlow}; +use yup_oauth2::{InstalledFlowAuthenticator, InstalledFlowReturnMethod}; use std::path::Path; #[tokio::main] async fn main() { - let secret = yup_oauth2::read_application_secret(Path::new("clientsecret.json")) + let app_secret = yup_oauth2::read_application_secret(Path::new("clientsecret.json")) .expect("clientsecret.json"); - let auth = Authenticator::new(InstalledFlow::new( - secret, - yup_oauth2::InstalledFlowReturnMethod::HTTPRedirect, - )) - .persist_tokens_to_disk("tokencache.json") - .build() - .await - .unwrap(); + let auth = + InstalledFlowAuthenticator::builder(app_secret, InstalledFlowReturnMethod::HTTPRedirect) + .persist_tokens_to_disk("tokencache.json") + .build() + .await + .unwrap(); let scopes = &["https://www.googleapis.com/auth/drive.file"]; match auth.token(scopes).await { diff --git a/examples/test-svc-acct/src/main.rs b/examples/test-svc-acct/src/main.rs index e5ef33c..8ff8db0 100644 --- a/examples/test-svc-acct/src/main.rs +++ b/examples/test-svc-acct/src/main.rs @@ -1,15 +1,10 @@ -use std::path; use tokio; -use yup_oauth2; -use yup_oauth2::GetToken; +use yup_oauth2::ServiceAccountAuthenticator; #[tokio::main] async fn main() { - let creds = - yup_oauth2::service_account_key_from_file(path::Path::new("serviceaccount.json")).unwrap(); - let sa = yup_oauth2::ServiceAccountAccess::new(creds) - .build() - .unwrap(); + let creds = yup_oauth2::service_account_key_from_file("serviceaccount.json").unwrap(); + let sa = ServiceAccountAuthenticator::builder(creds).build().unwrap(); let scopes = &["https://www.googleapis.com/auth/pubsub"]; let tok = sa.token(scopes).await.unwrap(); diff --git a/src/authenticator.rs b/src/authenticator.rs index b332e5a..1b5a2de 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -1,32 +1,293 @@ -use crate::authenticator_delegate::{AuthenticatorDelegate, DefaultAuthenticatorDelegate}; +use crate::authenticator_delegate::{ + AuthenticatorDelegate, DefaultAuthenticatorDelegate, FlowDelegate, +}; +use crate::device::DeviceFlow; +use crate::installed::{InstalledFlow, InstalledFlowReturnMethod}; use crate::refresh::RefreshFlow; use crate::storage::{self, Storage}; -use crate::types::{ApplicationSecret, GetToken, RefreshResult, RequestError, Token}; - -use futures::prelude::*; +use crate::types::{ApplicationSecret, RefreshResult, RequestError, Token}; +use private::AuthFlow; +use std::borrow::Cow; use std::error::Error; use std::io; use std::path::PathBuf; -use std::pin::Pin; use std::sync::Mutex; +use std::time::Duration; -/// Authenticator abstracts different `GetToken` implementations behind one type and handles -/// caching received tokens. It's important to use it (instead of the flows directly) because -/// otherwise the user needs to be asked for new authorization every time a token is generated. -/// -/// `ServiceAccountAccess` does not need (and does not work) with `Authenticator`, given that it -/// does not require interaction and implements its own caching. Use it directly. -/// -/// NOTE: It is recommended to use a client constructed like this in order to prevent functions -/// like `hyper::run()` from hanging: `let client = hyper::Client::builder().keep_alive(false);`. -/// Due to token requests being rare, this should not result in a too bad performance problem. -struct AuthenticatorImpl +pub struct Authenticator { + hyper_client: hyper::Client, + app_secret: ApplicationSecret, + auth_delegate: Box, + storage: Storage, + auth_flow: AuthFlow, +} + +impl Authenticator +where + C: hyper::client::connect::Connect + 'static, { - client: hyper::Client, - inner: T, - store: Storage, - delegate: AD, + pub async fn token<'a, T>(&'a self, scopes: &'a [T]) -> Result + where + T: AsRef, + { + let scope_key = storage::ScopeHash::new(scopes); + match self.storage.get(scope_key, scopes) { + Some(t) if !t.expired() => { + // unexpired token found + Ok(t) + } + Some(Token { + refresh_token: Some(refresh_token), + .. + }) => { + // token is expired but has a refresh token. + let rr = RefreshFlow::refresh_token( + &self.hyper_client, + &self.app_secret, + &refresh_token, + ) + .await?; + match rr { + RefreshResult::Error(ref e) => { + self.auth_delegate.token_refresh_failed( + e.description(), + Some("the request has likely timed out"), + ); + Err(RequestError::Refresh(rr)) + } + RefreshResult::RefreshError(ref s, ref ss) => { + self.auth_delegate.token_refresh_failed( + &format!("{}{}", s, ss.as_ref().map(|s| format!(" ({})", s)).unwrap_or_else(String::new)), + Some("the refresh token is likely invalid and your authorization has been revoked"), + ); + Err(RequestError::Refresh(rr)) + } + RefreshResult::Success(t) => { + self.storage.set(scope_key, scopes, Some(t.clone())).await; + Ok(t) + } + } + } + None + | Some(Token { + refresh_token: None, + .. + }) => { + // no token in the cache or the token returned does not contain a refresh token. + let t = self + .auth_flow + .token(&self.hyper_client, &self.app_secret, scopes) + .await?; + self.storage.set(scope_key, scopes, Some(t.clone())).await; + Ok(t) + } + } + } +} + +pub struct AuthenticatorBuilder { + hyper_client_builder: C, + app_secret: ApplicationSecret, + auth_delegate: Box, + storage_type: StorageType, + auth_flow: F, +} + +pub struct InstalledFlowAuthenticator; +impl InstalledFlowAuthenticator { + pub fn builder( + app_secret: ApplicationSecret, + method: InstalledFlowReturnMethod, + ) -> AuthenticatorBuilder { + AuthenticatorBuilder::::with_auth_flow( + app_secret, + InstalledFlow::new(method), + ) + } +} + +pub struct DeviceFlowAuthenticator; +impl DeviceFlowAuthenticator { + pub fn builder( + app_secret: ApplicationSecret, + ) -> AuthenticatorBuilder { + AuthenticatorBuilder::::with_auth_flow(app_secret, DeviceFlow::new()) + } +} + +impl AuthenticatorBuilder { + fn with_auth_flow( + app_secret: ApplicationSecret, + auth_flow: F, + ) -> AuthenticatorBuilder { + AuthenticatorBuilder { + hyper_client_builder: DefaultHyperClient, + app_secret, + auth_delegate: Box::new(DefaultAuthenticatorDelegate), + storage_type: StorageType::Memory, + auth_flow, + } + } + + /// Use the provided hyper client. + pub fn hyper_client( + self, + hyper_client: hyper::Client, + ) -> AuthenticatorBuilder, F> { + AuthenticatorBuilder { + hyper_client_builder: hyper_client, + app_secret: self.app_secret, + auth_delegate: self.auth_delegate, + storage_type: self.storage_type, + auth_flow: self.auth_flow, + } + } + + /// Persist tokens to disk in the provided filename. + pub fn persist_tokens_to_disk>(self, path: P) -> AuthenticatorBuilder { + AuthenticatorBuilder { + storage_type: StorageType::Disk(path.into()), + ..self + } + } + + /// Use the provided authenticator delegate. + pub fn auth_delegate( + self, + auth_delegate: Box, + ) -> AuthenticatorBuilder { + AuthenticatorBuilder { + auth_delegate, + ..self + } + } + + /// Create the authenticator. + pub async fn build(self) -> io::Result> + where + C: HyperClientBuilder, + F: Into, + { + let hyper_client = self.hyper_client_builder.build_hyper_client(); + let storage = match self.storage_type { + StorageType::Memory => Storage::Memory { + tokens: Mutex::new(storage::JSONTokens::new()), + }, + StorageType::Disk(path) => Storage::Disk(storage::DiskStorage::new(path).await?), + }; + + Ok(Authenticator { + hyper_client, + app_secret: self.app_secret, + storage, + auth_delegate: self.auth_delegate, + auth_flow: self.auth_flow.into(), + }) + } +} + +impl AuthenticatorBuilder { + /// Use the provided device code url. + pub fn device_code_url(self, url: impl Into>) -> Self { + AuthenticatorBuilder { + auth_flow: DeviceFlow { + device_code_url: url.into(), + ..self.auth_flow + }, + ..self + } + } + + /// Use the provided FlowDelegate. + pub fn flow_delegate(self, flow_delegate: Box) -> Self { + AuthenticatorBuilder { + auth_flow: DeviceFlow { + flow_delegate, + ..self.auth_flow + }, + ..self + } + } + + /// Use the provided wait duration. + pub fn wait_duration(self, wait_duration: Duration) -> Self { + AuthenticatorBuilder { + auth_flow: DeviceFlow { + wait_duration, + ..self.auth_flow + }, + ..self + } + } + + /// Use the provided grant type. + pub fn grant_type(self, grant_type: impl Into>) -> Self { + AuthenticatorBuilder { + auth_flow: DeviceFlow { + grant_type: grant_type.into(), + ..self.auth_flow + }, + ..self + } + } +} + +impl AuthenticatorBuilder { + /// Use the provided FlowDelegate. + pub fn flow_delegate(self, flow_delegate: Box) -> Self { + AuthenticatorBuilder { + auth_flow: InstalledFlow { + flow_delegate, + ..self.auth_flow + }, + ..self + } + } +} + +mod private { + use crate::device::DeviceFlow; + use crate::installed::InstalledFlow; + use crate::types::{ApplicationSecret, RequestError, Token}; + pub enum AuthFlow { + DeviceFlow(DeviceFlow), + InstalledFlow(InstalledFlow), + } + + impl From for AuthFlow { + fn from(device_flow: DeviceFlow) -> AuthFlow { + AuthFlow::DeviceFlow(device_flow) + } + } + + impl From for AuthFlow { + fn from(installed_flow: InstalledFlow) -> AuthFlow { + AuthFlow::InstalledFlow(installed_flow) + } + } + + impl AuthFlow { + pub(crate) async fn token<'a, C, T>( + &'a self, + hyper_client: &'a hyper::Client, + app_secret: &'a ApplicationSecret, + scopes: &'a [T], + ) -> Result + where + T: AsRef, + C: hyper::client::connect::Connect + 'static, + { + match self { + AuthFlow::DeviceFlow(device_flow) => { + device_flow.token(hyper_client, app_secret, scopes).await + } + AuthFlow::InstalledFlow(installed_flow) => { + installed_flow.token(hyper_client, app_secret, scopes).await + } + } + } + } } /// A trait implemented for any hyper::Client as well as teh DefaultHyperClient. @@ -59,211 +320,7 @@ where } } -/// An internal trait implemented by flows to be used by an authenticator. -pub trait AuthFlow { - type TokenGetter: GetToken; - - fn build_token_getter(self, client: hyper::Client) -> Self::TokenGetter; -} - enum StorageType { Memory, Disk(PathBuf), } - -/// An authenticator can be used with `InstalledFlow`'s or `DeviceFlow`'s and -/// will refresh tokens as they expire as well as optionally persist tokens to -/// disk. -pub struct Authenticator { - client: C, - token_getter: T, - storage_type: StorageType, - delegate: AD, -} - -impl Authenticator -where - T: AuthFlow<::Connector>, -{ - /// Create a new authenticator with the provided flow. By default a new - /// hyper::Client will be created the default authenticator delegate will be - /// used, and tokens will not be persisted to disk. - /// Accepted flow types are DeviceFlow and InstalledFlow. - /// - /// Examples - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::path::Path; - /// use yup_oauth2::{ApplicationSecret, Authenticator, DeviceFlow}; - /// let creds = ApplicationSecret::default(); - /// let auth = Authenticator::new(DeviceFlow::new(creds)).build().await.unwrap(); - /// # } - /// ``` - pub fn new(flow: T) -> Authenticator { - Authenticator { - client: DefaultHyperClient, - token_getter: flow, - storage_type: StorageType::Memory, - delegate: DefaultAuthenticatorDelegate, - } - } -} - -impl Authenticator -where - T: AuthFlow, - AD: AuthenticatorDelegate, - C: HyperClientBuilder, -{ - /// Use the provided hyper client. - pub fn hyper_client( - self, - hyper_client: hyper::Client, - ) -> Authenticator> - where - NewC: hyper::client::connect::Connect + 'static, - T: AuthFlow, - { - Authenticator { - client: hyper_client, - token_getter: self.token_getter, - storage_type: self.storage_type, - delegate: self.delegate, - } - } - - /// Persist tokens to disk in the provided filename. - pub fn persist_tokens_to_disk>(self, path: P) -> Authenticator { - Authenticator { - client: self.client, - token_getter: self.token_getter, - storage_type: StorageType::Disk(path.into()), - delegate: self.delegate, - } - } - - /// Use the provided authenticator delegate. - pub fn delegate( - self, - delegate: NewAD, - ) -> Authenticator { - Authenticator { - client: self.client, - token_getter: self.token_getter, - storage_type: self.storage_type, - delegate, - } - } - - /// Create the authenticator. - pub async fn build(self) -> io::Result - where - T::TokenGetter: GetToken, - C::Connector: hyper::client::connect::Connect + 'static, - { - let client = self.client.build_hyper_client(); - let inner = self.token_getter.build_token_getter(client.clone()); - let store = match self.storage_type { - StorageType::Memory => Storage::Memory { - tokens: Mutex::new(storage::JSONTokens::new()), - }, - StorageType::Disk(path) => Storage::Disk(storage::DiskStorage::new(path).await?), - }; - - Ok(AuthenticatorImpl { - client, - inner, - store, - delegate: self.delegate, - }) - } -} - -impl AuthenticatorImpl -where - GT: GetToken, - AD: AuthenticatorDelegate, - C: hyper::client::connect::Connect + 'static, -{ - async fn get_token(&self, scopes: &[T]) -> Result - where - T: AsRef + Sync, - { - let scope_key = storage::ScopeHash::new(scopes); - let store = &self.store; - let delegate = &self.delegate; - let client = &self.client; - let gettoken = &self.inner; - let appsecret = gettoken.application_secret(); - match store.get(scope_key, scopes) { - Some(t) if !t.expired() => { - // unexpired token found - Ok(t) - } - Some(Token { - refresh_token: Some(refresh_token), - .. - }) => { - // token is expired but has a refresh token. - let rr = RefreshFlow::refresh_token(client, appsecret, &refresh_token).await?; - match rr { - RefreshResult::Error(ref e) => { - delegate.token_refresh_failed( - e.description(), - Some("the request has likely timed out"), - ); - Err(RequestError::Refresh(rr)) - } - RefreshResult::RefreshError(ref s, ref ss) => { - delegate.token_refresh_failed( - &format!("{}{}", s, ss.as_ref().map(|s| format!(" ({})", s)).unwrap_or_else(String::new)), - Some("the refresh token is likely invalid and your authorization has been revoked"), - ); - Err(RequestError::Refresh(rr)) - } - RefreshResult::Success(t) => { - store.set(scope_key, scopes, Some(t.clone())).await; - Ok(t) - } - } - } - None - | Some(Token { - refresh_token: None, - .. - }) => { - // no token in the cache or the token returned does not contain a refresh token. - let t = gettoken.token(scopes).await?; - store.set(scope_key, scopes, Some(t.clone())).await; - Ok(t) - } - } - } -} - -impl GetToken for AuthenticatorImpl -where - GT: GetToken, - AD: AuthenticatorDelegate, - C: hyper::client::connect::Connect + 'static, -{ - /// Returns the API Key of the inner flow. - fn api_key(&self) -> Option { - self.inner.api_key() - } - /// Returns the application secret of the inner flow. - fn application_secret(&self) -> &ApplicationSecret { - self.inner.application_secret() - } - - fn token<'a, T>( - &'a self, - scopes: &'a [T], - ) -> Pin> + Send + 'a>> - where - T: AsRef + Sync, - { - Box::pin(self.get_token(scopes)) - } -} diff --git a/src/device.rs b/src/device.rs index 45e117e..373c36a 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,5 +1,4 @@ use std::borrow::Cow; -use std::pin::Pin; use std::time::Duration; use ::log::error; @@ -11,7 +10,7 @@ use serde_json as json; use url::form_urlencoded; use crate::authenticator_delegate::{DefaultFlowDelegate, FlowDelegate, PollInformation, Retry}; -use crate::types::{ApplicationSecret, GetToken, JsonErrorOr, PollError, RequestError, Token}; +use crate::types::{ApplicationSecret, JsonErrorOr, PollError, RequestError, Token}; pub const GOOGLE_DEVICE_CODE_URL: &str = "https://accounts.google.com/o/oauth2/device/code"; @@ -22,165 +21,77 @@ pub const GOOGLE_GRANT_TYPE: &str = "http://oauth.net/grant_type/device/1.0"; /// It operates in two steps: /// * obtain a code to show to the user // * (repeatedly) poll for the user to authenticate your application -#[derive(Clone)] -pub struct DeviceFlow { - application_secret: ApplicationSecret, - device_code_url: Cow<'static, str>, - flow_delegate: FD, - wait: Duration, - grant_type: Cow<'static, str>, +pub struct DeviceFlow { + pub(crate) device_code_url: Cow<'static, str>, + pub(crate) flow_delegate: Box, + pub(crate) wait_duration: Duration, + pub(crate) grant_type: Cow<'static, str>, } -impl DeviceFlow { +impl DeviceFlow { /// Create a new DeviceFlow. The default FlowDelegate will be used and the /// default wait time is 120 seconds. - pub fn new(secret: ApplicationSecret) -> DeviceFlow { + pub(crate) fn new() -> Self { DeviceFlow { - application_secret: secret, device_code_url: GOOGLE_DEVICE_CODE_URL.into(), - flow_delegate: DefaultFlowDelegate, - wait: Duration::from_secs(120), + flow_delegate: Box::new(DefaultFlowDelegate), + wait_duration: Duration::from_secs(120), grant_type: GOOGLE_GRANT_TYPE.into(), } } -} -impl DeviceFlow { - /// Use the provided device code url. - pub fn device_code_url(self, url: String) -> Self { - DeviceFlow { - device_code_url: url.into(), - ..self - } - } - - /// Use the provided FlowDelegate. - pub fn delegate(self, delegate: NewFD) -> DeviceFlow { - DeviceFlow { - application_secret: self.application_secret, - device_code_url: self.device_code_url, - flow_delegate: delegate, - wait: self.wait, - grant_type: self.grant_type, - } - } - - /// Use the provided wait duration. - pub fn wait_duration(self, duration: Duration) -> Self { - DeviceFlow { - wait: duration, - ..self - } - } - - pub fn grant_type(self, grant_type: String) -> Self { - DeviceFlow { - grant_type: grant_type.into(), - ..self - } - } -} - -impl crate::authenticator::AuthFlow for DeviceFlow -where - FD: FlowDelegate, - C: hyper::client::connect::Connect + 'static, -{ - type TokenGetter = DeviceFlowImpl; - - fn build_token_getter(self, client: hyper::Client) -> Self::TokenGetter { - DeviceFlowImpl { - client, - application_secret: self.application_secret, - device_code_url: self.device_code_url, - fd: self.flow_delegate, - wait: Duration::from_secs(1200), - grant_type: self.grant_type, - } - } -} - -/// The DeviceFlow implementation. -pub struct DeviceFlowImpl { - client: hyper::Client, - application_secret: ApplicationSecret, - /// Usually GOOGLE_DEVICE_CODE_URL - device_code_url: Cow<'static, str>, - fd: FD, - wait: Duration, - grant_type: Cow<'static, str>, -} - -impl GetToken for DeviceFlowImpl -where - FD: FlowDelegate, - C: hyper::client::connect::Connect + 'static, -{ - fn token<'a, T>( - &'a self, - scopes: &'a [T], - ) -> Pin> + Send + 'a>> - where - T: AsRef + Sync, - { - Box::pin(self.retrieve_device_token(scopes)) - } - fn api_key(&self) -> Option { - None - } - fn application_secret(&self) -> &ApplicationSecret { - &self.application_secret - } -} - -impl DeviceFlowImpl -where - C: hyper::client::connect::Connect + 'static, - FD: FlowDelegate, -{ - /// Essentially what `GetToken::token` does: Retrieve a token for the given scopes without - /// caching. - pub async fn retrieve_device_token(&self, scopes: &[T]) -> Result + pub(crate) async fn token( + &self, + hyper_client: &hyper::Client, + app_secret: &ApplicationSecret, + scopes: &[T], + ) -> Result where T: AsRef, + C: hyper::client::connect::Connect + 'static, { - let application_secret = &self.application_secret; - let (pollinf, device_code) = Self::request_code( - application_secret, - &self.client, - &self.device_code_url, - scopes, - ) - .await?; - self.fd.present_user_code(&pollinf); + let (pollinf, device_code) = + Self::request_code(app_secret, hyper_client, &self.device_code_url, scopes).await?; + self.flow_delegate.present_user_code(&pollinf); tokio::timer::Timeout::new( - self.wait_for_device_token(&pollinf, &device_code, &self.grant_type), - self.wait, + self.wait_for_device_token( + hyper_client, + app_secret, + &pollinf, + &device_code, + &self.grant_type, + ), + self.wait_duration, ) .await .map_err(|_| RequestError::Poll(PollError::TimedOut))? } - async fn wait_for_device_token( + async fn wait_for_device_token( &self, + hyper_client: &hyper::Client, + app_secret: &ApplicationSecret, pollinf: &PollInformation, device_code: &str, grant_type: &str, - ) -> Result { + ) -> Result + where + C: hyper::client::connect::Connect + 'static, + { let mut interval = pollinf.interval; loop { tokio::timer::delay_for(interval).await; - let r = Self::poll_token( - &self.application_secret, - &self.client, + interval = match Self::poll_token( + &app_secret, + hyper_client, device_code, grant_type, pollinf.expires_at, - &self.fd, + &*self.flow_delegate as &dyn FlowDelegate, ) - .await; - interval = match r { - Ok(None) => match self.fd.pending(&pollinf) { + .await + { + Ok(None) => match self.flow_delegate.pending(&pollinf) { Retry::Abort | Retry::Skip => { return Err(RequestError::Poll(PollError::TimedOut)) } @@ -213,7 +124,7 @@ where /// * If called after a successful result was returned at least once. /// # Examples /// See test-cases in source code for a more complete example. - async fn request_code( + async fn request_code( application_secret: &ApplicationSecret, client: &hyper::Client, device_code_url: &str, @@ -221,6 +132,7 @@ where ) -> Result<(PollInformation, String), RequestError> where T: AsRef, + C: hyper::client::connect::Connect + 'static, { let req = form_urlencoded::Serializer::new(String::new()) .extend_pairs(&[ @@ -288,16 +200,19 @@ where /// /// # Examples /// See test-cases in source code for a more complete example. - async fn poll_token<'a>( + async fn poll_token<'a, C>( application_secret: &ApplicationSecret, client: &hyper::Client, device_code: &str, grant_type: &str, expires_at: DateTime, - fd: &FD, - ) -> Result, PollError> { + flow_delegate: &dyn FlowDelegate, + ) -> Result, PollError> + where + C: hyper::client::connect::Connect + 'static, + { if expires_at <= Utc::now() { - fd.expired(expires_at); + flow_delegate.expired(expires_at); return Err(PollError::Expired(expires_at)); } @@ -334,7 +249,7 @@ where Ok(res) => { match res.error.as_ref() { "access_denied" => { - fd.denied(); + flow_delegate.denied(); return Err(PollError::AccessDenied); } "authorization_pending" => return Ok(None), @@ -364,7 +279,6 @@ mod tests { use tokio; use super::*; - use crate::authenticator::AuthFlow; use crate::helper::parse_application_secret; #[test] @@ -388,10 +302,12 @@ mod tests { .keep_alive(false) .build::<_, hyper::Body>(https); - let flow = DeviceFlow::new(app_secret) - .delegate(FD) - .device_code_url(device_code_url) - .build_token_getter(client); + let flow = DeviceFlow { + device_code_url: device_code_url.into(), + flow_delegate: Box::new(FD), + wait_duration: Duration::from_secs(5), + grant_type: GOOGLE_GRANT_TYPE.into(), + }; let rt = tokio::runtime::Builder::new() .core_threads(1) @@ -420,7 +336,11 @@ mod tests { let fut = async { let token = flow - .token(&["https://www.googleapis.com/scope/1"]) + .token( + &client, + &app_secret, + &["https://www.googleapis.com/scope/1"], + ) .await .unwrap(); assert_eq!("accesstoken", token.access_token); @@ -452,7 +372,13 @@ mod tests { .create(); let fut = async { - let res = flow.token(&["https://www.googleapis.com/scope/1"]).await; + let res = flow + .token( + &client, + &app_secret, + &["https://www.googleapis.com/scope/1"], + ) + .await; assert!(res.is_err()); assert!(format!("{}", res.unwrap_err()).contains("invalid_client_id")); Ok(()) as Result<(), ()> @@ -482,7 +408,13 @@ mod tests { .create(); let fut = async { - let res = flow.token(&["https://www.googleapis.com/scope/1"]).await; + let res = flow + .token( + &client, + &app_secret, + &["https://www.googleapis.com/scope/1"], + ) + .await; assert!(res.is_err()); assert!(format!("{}", res.unwrap_err()).contains("Access denied by user")); Ok(()) as Result<(), ()> diff --git a/src/installed.rs b/src/installed.rs index fa72489..8e1bf3d 100644 --- a/src/installed.rs +++ b/src/installed.rs @@ -17,7 +17,7 @@ use url::form_urlencoded; use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET}; use crate::authenticator_delegate::{DefaultFlowDelegate, FlowDelegate}; -use crate::types::{ApplicationSecret, GetToken, JsonErrorOr, RequestError, Token}; +use crate::types::{ApplicationSecret, JsonErrorOr, RequestError, Token}; const OOB_REDIRECT_URI: &str = "urn:ietf:wg:oauth:2.0:oob"; @@ -51,40 +51,6 @@ where }) } -impl GetToken for InstalledFlowImpl -where - FD: FlowDelegate, - C: hyper::client::connect::Connect + 'static, -{ - fn token<'a, T>( - &'a self, - scopes: &'a [T], - ) -> Pin> + Send + 'a>> - where - T: AsRef + Sync, - { - Box::pin(self.obtain_token(scopes)) - } - fn api_key(&self) -> Option { - None - } - fn application_secret(&self) -> &ApplicationSecret { - &self.appsecret - } -} - -/// The InstalledFlow implementation. -pub struct InstalledFlowImpl -where - FD: FlowDelegate, - C: hyper::client::connect::Connect, -{ - method: InstalledFlowReturnMethod, - client: hyper::client::Client, - fd: FD, - appsecret: ApplicationSecret, -} - /// cf. https://developers.google.com/identity/protocols/OAuth2InstalledApp#choosingredirecturi pub enum InstalledFlowReturnMethod { /// Involves showing a URL to the user and asking to copy a code from their browser @@ -98,151 +64,133 @@ pub enum InstalledFlowReturnMethod { /// InstalledFlowImpl provides tokens for services that follow the "Installed" OAuth flow. (See /// https://www.oauth.com/oauth2-servers/authorization/, /// https://developers.google.com/identity/protocols/OAuth2InstalledApp). -pub struct InstalledFlow { - method: InstalledFlowReturnMethod, - flow_delegate: FD, - appsecret: ApplicationSecret, +pub struct InstalledFlow { + pub(crate) method: InstalledFlowReturnMethod, + pub(crate) flow_delegate: Box, } -impl InstalledFlow { +impl InstalledFlow { /// Create a new InstalledFlow with the provided secret and method. - pub fn new( - secret: ApplicationSecret, - method: InstalledFlowReturnMethod, - ) -> InstalledFlow { + pub(crate) fn new(method: InstalledFlowReturnMethod) -> InstalledFlow { InstalledFlow { method, - flow_delegate: DefaultFlowDelegate, - appsecret: secret, + flow_delegate: Box::new(DefaultFlowDelegate), } } -} -impl InstalledFlow -where - FD: FlowDelegate, -{ - /// Use the provided FlowDelegate. - pub fn delegate(self, delegate: NewFD) -> InstalledFlow { - InstalledFlow { - method: self.method, - flow_delegate: delegate, - appsecret: self.appsecret, - } - } -} - -impl crate::authenticator::AuthFlow for InstalledFlow -where - FD: FlowDelegate, - C: hyper::client::connect::Connect + 'static, -{ - type TokenGetter = InstalledFlowImpl; - - fn build_token_getter(self, client: hyper::Client) -> Self::TokenGetter { - InstalledFlowImpl { - method: self.method, - fd: self.flow_delegate, - appsecret: self.appsecret, - client, - } - } -} - -impl InstalledFlowImpl -where - FD: FlowDelegate, - C: hyper::client::connect::Connect + 'static, -{ /// Handles the token request flow; it consists of the following steps: /// . Obtain a authorization code with user cooperation or internal redirect. /// . Obtain a token and refresh token using that code. /// . Return that token /// /// It's recommended not to use the DefaultFlowDelegate, but a specialized one. - async fn obtain_token(&self, scopes: &[T]) -> Result + pub(crate) async fn token( + &self, + hyper_client: &hyper::Client, + app_secret: &ApplicationSecret, + scopes: &[T], + ) -> Result where T: AsRef, + C: hyper::client::connect::Connect + 'static, { match self.method { - InstalledFlowReturnMethod::HTTPRedirect => self.ask_auth_code_via_http(scopes).await, + InstalledFlowReturnMethod::HTTPRedirect => { + self.ask_auth_code_via_http(hyper_client, app_secret, scopes) + .await + } InstalledFlowReturnMethod::Interactive => { - self.ask_auth_code_interactively(scopes).await + self.ask_auth_code_interactively(hyper_client, app_secret, scopes) + .await } } } - async fn ask_auth_code_interactively(&self, scopes: &[T]) -> Result + async fn ask_auth_code_interactively( + &self, + hyper_client: &hyper::Client, + app_secret: &ApplicationSecret, + scopes: &[T], + ) -> Result where T: AsRef, + C: hyper::client::connect::Connect + 'static, { - let auth_delegate = &self.fd; - let appsecret = &self.appsecret; let url = build_authentication_request_url( - &appsecret.auth_uri, - &appsecret.client_id, + &app_secret.auth_uri, + &app_secret.client_id, scopes, - auth_delegate.redirect_uri(), + self.flow_delegate.redirect_uri(), ); - let authcode = match auth_delegate + let authcode = match self + .flow_delegate .present_user_url(&url, true /* need code */) .await { Ok(mut code) => { // Partial backwards compatibility in case an implementation adds a new line // due to previous behaviour. - let ends_with_newline = code.chars().last().map(|c| c == '\n').unwrap_or(false); - if ends_with_newline { + if code.ends_with('\n') { code.pop(); } code } _ => return Err(RequestError::UserError("couldn't read code".to_string())), }; - self.exchange_auth_code(&authcode, None).await + self.exchange_auth_code(&authcode, hyper_client, app_secret, None) + .await } - async fn ask_auth_code_via_http(&self, scopes: &[T]) -> Result + async fn ask_auth_code_via_http( + &self, + hyper_client: &hyper::Client, + app_secret: &ApplicationSecret, + scopes: &[T], + ) -> Result where T: AsRef, + C: hyper::client::connect::Connect + 'static, { use std::borrow::Cow; - let auth_delegate = &self.fd; - let appsecret = &self.appsecret; let server = InstalledFlowServer::run()?; let server_addr = server.local_addr(); // Present url to user. // The redirect URI must be this very localhost URL, otherwise authorization is refused // by certain providers. - let redirect_uri: Cow = match auth_delegate.redirect_uri() { + let redirect_uri: Cow = match self.flow_delegate.redirect_uri() { Some(uri) => uri.into(), None => format!("http://{}", server_addr).into(), }; let url = build_authentication_request_url( - &appsecret.auth_uri, - &appsecret.client_id, + &app_secret.auth_uri, + &app_secret.client_id, scopes, Some(redirect_uri.as_ref()), ); - let _ = auth_delegate + let _ = self + .flow_delegate .present_user_url(&url, false /* need code */) .await; let auth_code = server.wait_for_auth_code().await; - self.exchange_auth_code(&auth_code, Some(server_addr)).await + self.exchange_auth_code(&auth_code, hyper_client, app_secret, Some(server_addr)) + .await } - async fn exchange_auth_code( + async fn exchange_auth_code( &self, authcode: &str, + hyper_client: &hyper::Client, + app_secret: &ApplicationSecret, server_addr: Option, - ) -> Result { - let appsec = &self.appsecret; - let redirect_uri = self.fd.redirect_uri(); - let request = Self::request_token(appsec, authcode, redirect_uri, server_addr); - let resp = self - .client + ) -> Result + where + C: hyper::client::connect::Connect + 'static, + { + let redirect_uri = self.flow_delegate.redirect_uri(); + let request = Self::request_token(app_secret, authcode, redirect_uri, server_addr); + let resp = hyper_client .request(request) .await .map_err(RequestError::ClientError)?; @@ -283,7 +231,7 @@ where /// Sends the authorization code to the provider in order to obtain access and refresh tokens. fn request_token( - appsecret: &ApplicationSecret, + app_secret: &ApplicationSecret, authcode: &str, custom_redirect_uri: Option<&str>, server_addr: Option, @@ -298,14 +246,14 @@ where let body = form_urlencoded::Serializer::new(String::new()) .extend_pairs(vec![ ("code", authcode), - ("client_id", appsecret.client_id.as_str()), - ("client_secret", appsecret.client_secret.as_str()), + ("client_id", app_secret.client_id.as_str()), + ("client_secret", app_secret.client_secret.as_str()), ("redirect_uri", redirect_uri.as_ref()), ("grant_type", "authorization_code"), ]) .finish(); - hyper::Request::post(&appsecret.token_uri) + hyper::Request::post(&app_secret.token_uri) .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded") .body(hyper::Body::from(body)) .unwrap() // TODO: error check @@ -456,7 +404,6 @@ mod tests { use tokio; use super::*; - use crate::authenticator::AuthFlow; use crate::authenticator_delegate::FlowDelegate; use crate::helper::*; use crate::types::StringError; @@ -523,9 +470,10 @@ mod tests { .build::<_, hyper::Body>(https); let fd = FD("authorizationcode".to_string(), client.clone()); - let inf = InstalledFlow::new(app_secret.clone(), InstalledFlowReturnMethod::Interactive) - .delegate(fd) - .build_token_getter(client.clone()); + let inf = InstalledFlow { + method: InstalledFlowReturnMethod::Interactive, + flow_delegate: Box::new(fd), + }; let rt = tokio::runtime::Builder::new() .core_threads(1) @@ -544,7 +492,7 @@ mod tests { let fut = || { async { let tok = inf - .token(&["https://googleapis.com/some/scope"]) + .token(&client, &app_secret, &["https://googleapis.com/some/scope"]) .await .map_err(|_| ())?; assert_eq!("accesstoken", tok.access_token); @@ -558,12 +506,13 @@ mod tests { } // Successful path with HTTP redirect. { - let inf = InstalledFlow::new(app_secret, InstalledFlowReturnMethod::HTTPRedirect) - .delegate(FD( + let inf = InstalledFlow { + method: InstalledFlowReturnMethod::HTTPRedirect, + flow_delegate: Box::new(FD( "authorizationcodefromlocalserver".to_string(), client.clone(), - )) - .build_token_getter(client.clone()); + )), + }; let _m = mock("POST", "/token") .match_body(mockito::Matcher::Regex(".*code=authorizationcodefromlocalserver.*client_id=9022167.*".to_string())) .with_body(r#"{"access_token": "accesstoken", "refresh_token": "refreshtoken", "token_type": "Bearer", "expires_in": 12345678}"#) @@ -572,7 +521,7 @@ mod tests { let fut = async { let tok = inf - .token(&["https://googleapis.com/some/scope"]) + .token(&client, &app_secret, &["https://googleapis.com/some/scope"]) .await .map_err(|_| ())?; assert_eq!("accesstoken", tok.access_token); @@ -595,7 +544,9 @@ mod tests { .create(); let fut = async { - let tokr = inf.token(&["https://googleapis.com/some/scope"]).await; + let tokr = inf + .token(&client, &app_secret, &["https://googleapis.com/some/scope"]) + .await; assert!(tokr.is_err()); assert!(format!("{}", tokr.unwrap_err()).contains("invalid_code")); Ok(()) as Result<(), ()> diff --git a/src/lib.rs b/src/lib.rs index 549ea28..152f5b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,12 +38,7 @@ //! `examples/test-installed/`, shows the basics of using this crate: //! //! ```test_harness,no_run -//! use futures::prelude::*; -//! use yup_oauth2::GetToken; -//! use yup_oauth2::{Authenticator, InstalledFlow}; -//! -//! use hyper::client::Client; -//! use hyper_rustls::HttpsConnector; +//! use yup_oauth2::{InstalledFlowAuthenticator, InstalledFlowReturnMethod}; //! //! #[tokio::main] //! async fn main() { @@ -53,12 +48,10 @@ //! .expect("clientsecret.json"); //! //! // Create an authenticator that uses an InstalledFlow to authenticate. The -//! // authentication tokens are persisted to a file named tokencache.json. The -//! // authenticator takes care of caching tokens to disk and refreshing tokens once -//! // they've expired. -//! let mut auth = Authenticator::new( -//! InstalledFlow::new(secret, yup_oauth2::InstalledFlowReturnMethod::HTTPRedirect) -//! ) +//! // authentication tokens are persisted to a file named tokencache.json. The +//! // authenticator takes care of caching tokens to disk and refreshing tokens once +//! // they've expired. +//! let mut auth = InstalledFlowAuthenticator::builder(secret, InstalledFlowReturnMethod::HTTPRedirect) //! .persist_tokens_to_disk("tokencache.json") //! .build() //! .await @@ -88,16 +81,18 @@ mod service_account; mod storage; mod types; -pub use crate::authenticator::{AuthFlow, Authenticator}; +pub use crate::authenticator::{ + Authenticator, AuthenticatorBuilder, DeviceFlowAuthenticator, InstalledFlowAuthenticator, +}; pub use crate::authenticator_delegate::{ AuthenticatorDelegate, DefaultAuthenticatorDelegate, DefaultFlowDelegate, FlowDelegate, PollInformation, }; -pub use crate::device::{DeviceFlow, GOOGLE_DEVICE_CODE_URL}; +pub use crate::device::GOOGLE_DEVICE_CODE_URL; pub use crate::helper::*; -pub use crate::installed::{InstalledFlow, InstalledFlowReturnMethod}; +pub use crate::installed::InstalledFlowReturnMethod; pub use crate::service_account::*; pub use crate::types::{ - ApplicationSecret, ConsoleApplicationSecret, GetToken, PollError, RefreshResult, RequestError, - Scheme, Token, TokenType, + ApplicationSecret, ConsoleApplicationSecret, PollError, RefreshResult, RequestError, Scheme, + Token, TokenType, }; diff --git a/src/service_account.rs b/src/service_account.rs index 1b10de8..eb013e7 100644 --- a/src/service_account.rs +++ b/src/service_account.rs @@ -11,12 +11,11 @@ //! Copyright (c) 2016 Google Inc (lewinb@google.com). //! -use std::pin::Pin; use std::sync::Mutex; use crate::authenticator::{DefaultHyperClient, HyperClientBuilder}; use crate::storage::{self, Storage}; -use crate::types::{ApplicationSecret, GetToken, JsonErrorOr, RequestError, Token}; +use crate::types::{JsonErrorOr, RequestError, Token}; use futures::prelude::*; use hyper::header; @@ -155,20 +154,10 @@ impl JWTSigner { } } -/// A token source (`GetToken`) yielding OAuth tokens for services that use ServiceAccount authorization. -/// This token source caches token and automatically renews expired ones, meaning you do not need -/// (and you also should not) use this with `Authenticator`. Just use it directly. -#[derive(Clone)] -pub struct ServiceAccountAccess { - client: C, - key: ServiceAccountKey, - subject: Option, -} - -impl ServiceAccountAccess { - /// Create a new ServiceAccountAccess with the provided key. - pub fn new(key: ServiceAccountKey) -> Self { - ServiceAccountAccess { +pub struct ServiceAccountAuthenticator; +impl ServiceAccountAuthenticator { + pub fn builder(key: ServiceAccountKey) -> Builder { + Builder { client: DefaultHyperClient, key, subject: None, @@ -176,16 +165,16 @@ impl ServiceAccountAccess { } } -impl ServiceAccountAccess -where - C: HyperClientBuilder, -{ +pub struct Builder { + client: C, + key: ServiceAccountKey, + subject: Option, +} + +impl Builder { /// Use the provided hyper client. - pub fn hyper_client( - self, - hyper_client: NewC, - ) -> ServiceAccountAccess { - ServiceAccountAccess { + pub fn hyper_client(self, hyper_client: NewC) -> Builder { + Builder { client: hyper_client, key: self.key, subject: self.subject, @@ -194,29 +183,32 @@ where /// Use the provided subject. pub fn subject(self, subject: String) -> Self { - ServiceAccountAccess { + Builder { subject: Some(subject), ..self } } /// Build the configured ServiceAccountAccess. - pub fn build(self) -> Result { - ServiceAccountAccessImpl::new(self.client.build_hyper_client(), self.key, self.subject) + pub fn build(self) -> Result, io::Error> + where + C: HyperClientBuilder, + { + ServiceAccountAccess::new(self.client.build_hyper_client(), self.key, self.subject) } } -struct ServiceAccountAccessImpl { - client: hyper::Client, +pub struct ServiceAccountAccess { + client: hyper::Client, key: ServiceAccountKey, cache: Storage, subject: Option, signer: JWTSigner, } -impl ServiceAccountAccessImpl +impl ServiceAccountAccess where - C: hyper::client::connect::Connect, + C: hyper::client::connect::Connect + 'static, { fn new( client: hyper::Client, @@ -224,7 +216,7 @@ where subject: Option, ) -> Result { let signer = JWTSigner::new(&key.private_key)?; - Ok(ServiceAccountAccessImpl { + Ok(ServiceAccountAccess { client, key, cache: Storage::Memory { @@ -234,20 +226,28 @@ where signer, }) } -} -/// This is the schema of the server's response. -#[derive(Deserialize, Debug)] -struct TokenResponse { - access_token: Option, - token_type: Option, - expires_in: Option, -} - -impl ServiceAccountAccessImpl -where - C: hyper::client::connect::Connect + 'static, -{ + pub async fn token(&self, scopes: &[T]) -> Result + where + T: AsRef, + { + let hash = storage::ScopeHash::new(scopes); + let cache = &self.cache; + match cache.get(hash, scopes) { + Some(token) if !token.expired() => return Ok(token), + _ => {} + } + let token = Self::request_token( + &self.client, + &self.signer, + self.subject.as_ref().map(|x| x.as_str()), + &self.key, + scopes, + ) + .await?; + cache.set(hash, scopes, Some(token.clone())).await; + Ok(token) + } /// Send a request for a new Bearer token to the OAuth provider. async fn request_token( client: &hyper::client::Client, @@ -282,6 +282,15 @@ where .try_concat() .await .map_err(RequestError::ClientError)?; + + /// This is the schema of the server's response. + #[derive(Deserialize, Debug)] + struct TokenResponse { + access_token: Option, + token_type: Option, + expires_in: Option, + } + match serde_json::from_slice::>(&body)? { JsonErrorOr::Err(err) => Err(err.into()), JsonErrorOr::Data(TokenResponse { @@ -305,61 +314,12 @@ where ))), } } - - async fn get_token(&self, scopes: &[T]) -> Result - where - T: AsRef, - { - let hash = storage::ScopeHash::new(scopes); - let cache = &self.cache; - match cache.get(hash, scopes) { - Some(token) if !token.expired() => return Ok(token), - _ => {} - } - let token = Self::request_token( - &self.client, - &self.signer, - self.subject.as_ref().map(|x| x.as_str()), - &self.key, - scopes, - ) - .await?; - cache.set(hash, scopes, Some(token.clone())).await; - Ok(token) - } -} - -impl GetToken for ServiceAccountAccessImpl -where - C: hyper::client::connect::Connect + 'static, -{ - fn token<'a, T>( - &'a self, - scopes: &'a [T], - ) -> Pin> + Send + 'a>> - where - T: AsRef + Sync, - { - Box::pin(self.get_token(scopes)) - } - - /// Returns an empty ApplicationSecret as tokens for service accounts don't need to be - /// refreshed (they are simply reissued). - fn application_secret(&self) -> &ApplicationSecret { - static APP_SECRET: ApplicationSecret = ApplicationSecret::empty(); - &APP_SECRET - } - - fn api_key(&self) -> Option { - None - } } #[cfg(test)] mod tests { use super::*; use crate::helper::service_account_key_from_file; - use crate::types::GetToken; use hyper; use hyper_rustls::HttpsConnector; @@ -413,7 +373,7 @@ mod tests { .with_body(json_response) .expect(1) .create(); - let acc = ServiceAccountAccessImpl::new(client.clone(), key.clone(), None).unwrap(); + let acc = ServiceAccountAccess::new(client.clone(), key.clone(), None).unwrap(); let fut = async { let tok = acc .token(&["https://www.googleapis.com/auth/pubsub"]) @@ -453,7 +413,7 @@ mod tests { .with_header("content-type", "text/json") .with_body(bad_json_response) .create(); - let acc = ServiceAccountAccess::new(key.clone()) + let acc = ServiceAccountAuthenticator::builder(key.clone()) .hyper_client(client.clone()) .build() .unwrap(); @@ -478,7 +438,7 @@ mod tests { let key = service_account_key_from_file(&TEST_PRIVATE_KEY_PATH.to_string()).unwrap(); let https = HttpsConnector::new(); let client = hyper::Client::builder().build(https); - let acc = ServiceAccountAccess::new(key) + let acc = ServiceAccountAuthenticator::builder(key) .hyper_client(client) .build() .unwrap(); diff --git a/src/types.rs b/src/types.rs index a9d371c..796cb90 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,11 +3,8 @@ use hyper; use std::error::Error; use std::fmt; use std::io; -use std::pin::Pin; use std::str::FromStr; -use futures::prelude::*; - #[derive(Deserialize, Debug)] pub struct JsonError { pub error: String, @@ -246,24 +243,6 @@ impl FromStr for Scheme { } } -/// A provider for authorization tokens, yielding tokens valid for a given scope. -/// The `api_key()` method is an alternative in case there are no scopes or -/// if no user is involved. -pub trait GetToken: Send + Sync { - fn token<'a, T>( - &'a self, - scopes: &'a [T], - ) -> Pin> + Send + 'a>> - where - T: AsRef + Sync; - - fn api_key(&self) -> Option; - - /// Return an application secret with at least token_uri, client_secret, and client_id filled - /// in. This is used for refreshing tokens without interaction from the flow. - fn application_secret(&self) -> &ApplicationSecret; -} - /// Represents a token as returned by OAuth2 servers. /// /// It is produced by all authentication flows.