diff --git a/examples/service_account/src/main.rs b/examples/service_account/src/main.rs index 314372e..6e4c219 100644 --- a/examples/service_account/src/main.rs +++ b/examples/service_account/src/main.rs @@ -178,15 +178,13 @@ fn publish_stuff(methods: &PubsubMethods, message: &str) { fn main() { let client_secret = oauth::service_account_key_from_file(&"pubsub-auth.json".to_string()).unwrap(); - let client = - hyper::Client::with_connector(HttpsConnector::new(NativeTlsClient::new().unwrap())); - let mut access = oauth::ServiceAccountAccess::new(client_secret, client); + let mut access = oauth::ServiceAccountAccess::new(client_secret).build(); use yup_oauth2::GetToken; println!( "{:?}", access - .token(&vec!["https://www.googleapis.com/auth/pubsub"]) + .token(vec!["https://www.googleapis.com/auth/pubsub"]) .unwrap() ); diff --git a/examples/test-device/src/main.rs b/examples/test-device/src/main.rs index 7edb91c..278fd78 100644 --- a/examples/test-device/src/main.rs +++ b/examples/test-device/src/main.rs @@ -1,36 +1,20 @@ use futures::prelude::*; -use yup_oauth2::{self, Authenticator, GetToken}; +use yup_oauth2::{self, Authenticator, DeviceFlow, GetToken}; -use hyper::client::Client; -use hyper_rustls::HttpsConnector; use std::path; -use std::time::Duration; use tokio; fn main() { let creds = yup_oauth2::read_application_secret(path::Path::new("clientsecret.json")) .expect("clientsecret"); - let https = HttpsConnector::new(1); - let client = Client::builder() - .keep_alive(false) - .build::<_, hyper::Body>(https); - let scopes = &["https://www.googleapis.com/auth/youtube.readonly".to_string()]; - - let ad = yup_oauth2::DefaultFlowDelegate; - let mut df = yup_oauth2::DeviceFlow::new::(client.clone(), creds, ad, None); - df.set_wait_duration(Duration::from_secs(120)); - let mut auth = Authenticator::new_disk( - client, - df, - yup_oauth2::DefaultAuthenticatorDelegate, - "tokenstorage.json", - ) - .expect("authenticator"); + let mut auth = Authenticator::new(DeviceFlow::new(creds)) + .persist_tokens_to_disk("tokenstorage.json") + .build() + .expect("authenticator"); + let scopes = vec!["https://www.googleapis.com/auth/youtube.readonly"]; let mut rt = tokio::runtime::Runtime::new().unwrap(); - let fut = auth - .token(scopes.iter()) - .and_then(|tok| Ok(println!("{:?}", tok))); + let fut = auth.token(scopes).and_then(|tok| Ok(println!("{:?}", tok))); println!("{:?}", rt.block_on(fut)); } diff --git a/examples/test-installed/src/main.rs b/examples/test-installed/src/main.rs index b57ec82..3aa29ca 100644 --- a/examples/test-installed/src/main.rs +++ b/examples/test-installed/src/main.rs @@ -15,23 +15,18 @@ fn main() { let ad = yup_oauth2::DefaultFlowDelegate; let secret = yup_oauth2::read_application_secret(Path::new("clientsecret.json")) .expect("clientsecret.json"); - let inf = InstalledFlow::new( - client.clone(), - ad, + + let mut auth = Authenticator::new(InstalledFlow::new( secret, - yup_oauth2::InstalledFlowReturnMethod::HTTPRedirectEphemeral, - ); - let mut auth = Authenticator::new_disk( - client, - inf, - yup_oauth2::DefaultAuthenticatorDelegate, - "tokencache.json", - ) + yup_oauth2::InstalledFlowReturnMethod::HTTPRedirect(8081), + )) + .persist_tokens_to_disk("tokencache.json") + .build() .unwrap(); let s = "https://www.googleapis.com/auth/drive.file".to_string(); let scopes = vec![s]; - let tok = auth.token(scopes.iter()); + let tok = auth.token(scopes); let fut = tok.map_err(|e| println!("error: {:?}", e)).and_then(|t| { println!("The token is {:?}", t); Ok(()) diff --git a/examples/test-svc-acct/src/main.rs b/examples/test-svc-acct/src/main.rs index cf44e3d..ebaaac1 100644 --- a/examples/test-svc-acct/src/main.rs +++ b/examples/test-svc-acct/src/main.rs @@ -3,8 +3,6 @@ use yup_oauth2; use futures::prelude::*; use yup_oauth2::GetToken; -use hyper::client::Client; -use hyper_rustls::HttpsConnector; use tokio; use std::path; @@ -12,21 +10,16 @@ use std::path; fn main() { let creds = yup_oauth2::service_account_key_from_file(path::Path::new("serviceaccount.json")).unwrap(); - let https = HttpsConnector::new(1); - let client = Client::builder() - .keep_alive(false) - .build::<_, hyper::Body>(https); - - let mut sa = yup_oauth2::ServiceAccountAccess::new(creds, client); + let mut sa = yup_oauth2::ServiceAccountAccess::new(creds).build(); let fut = sa - .token(["https://www.googleapis.com/auth/pubsub"].iter()) + .token(vec!["https://www.googleapis.com/auth/pubsub"]) .and_then(|tok| { println!("token is: {:?}", tok); Ok(()) }); let fut2 = sa - .token(["https://www.googleapis.com/auth/pubsub"].iter()) + .token(vec!["https://www.googleapis.com/auth/pubsub"]) .and_then(|tok| { println!("cached token is {:?} and should be identical", tok); Ok(()) diff --git a/src/authenticator.rs b/src/authenticator.rs index 6953a06..cf866ec 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -1,4 +1,4 @@ -use crate::authenticator_delegate::{AuthenticatorDelegate, Retry}; +use crate::authenticator_delegate::{AuthenticatorDelegate, DefaultAuthenticatorDelegate, Retry}; use crate::refresh::RefreshFlow; use crate::storage::{hash_scopes, DiskTokenStorage, MemoryStorage, TokenStorage}; use crate::types::{ApplicationSecret, GetToken, RefreshResult, RequestError, Token}; @@ -8,6 +8,7 @@ use tokio_timer; use std::error::Error; use std::io; +use std::path::Path; use std::sync::{Arc, Mutex}; /// Authenticator abstracts different `GetToken` implementations behind one type and handles @@ -20,7 +21,7 @@ use std::sync::{Arc, Mutex}; /// 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. -pub struct Authenticator< +struct AuthenticatorImpl< T: GetToken, S: TokenStorage, AD: AuthenticatorDelegate, @@ -32,39 +33,156 @@ pub struct Authenticator< delegate: AD, } -impl - Authenticator +/// A trait implemented for any hyper::Client as well as teh DefaultHyperClient. +pub trait HyperClientBuilder { + type Connector: hyper::client::connect::Connect; + + fn build_hyper_client(self) -> hyper::Client; +} + +/// The builder value used when the default hyper client should be used. +pub struct DefaultHyperClient; +impl HyperClientBuilder for DefaultHyperClient { + type Connector = hyper_rustls::HttpsConnector; + + fn build_hyper_client(self) -> hyper::Client { + hyper::Client::builder() + .keep_alive(false) + .build::<_, hyper::Body>(hyper_rustls::HttpsConnector::new(1)) + } +} + +impl HyperClientBuilder for hyper::Client +where + C: hyper::client::connect::Connect, { - /// Create an Authenticator caching tokens for the duration of this authenticator. + type Connector = C; + + fn build_hyper_client(self) -> hyper::Client { + self + } +} + +/// 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; +} + +/// 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< + T: AuthFlow, + S: TokenStorage, + AD: AuthenticatorDelegate, + C: HyperClientBuilder, +> { + client: C, + token_getter: T, + store: io::Result, + 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 + /// ``` + /// use std::path::Path; + /// use yup_oauth2::{ApplicationSecret, Authenticator, DeviceFlow}; + /// let creds = ApplicationSecret::default(); + /// let auth = Authenticator::new(DeviceFlow::new(creds)).build().unwrap(); + /// ``` pub fn new( - client: hyper::Client, - inner: T, - delegate: AD, - ) -> Authenticator { + flow: T, + ) -> Authenticator { Authenticator { - client: client, - inner: Arc::new(Mutex::new(inner)), - store: Arc::new(Mutex::new(MemoryStorage::new())), - delegate: delegate, + client: DefaultHyperClient, + token_getter: flow, + store: Ok(MemoryStorage::new()), + delegate: DefaultAuthenticatorDelegate, } } } -impl - Authenticator +impl Authenticator +where + T: AuthFlow, + S: TokenStorage, + AD: AuthenticatorDelegate, + C: HyperClientBuilder, { - /// Create an Authenticator using the store at `path`. - pub fn new_disk>( - client: hyper::Client, - inner: T, - delegate: AD, - token_storage_path: P, - ) -> io::Result> { - Ok(Authenticator { - client: client, - inner: Arc::new(Mutex::new(inner)), - store: Arc::new(Mutex::new(DiskTokenStorage::new(token_storage_path)?)), + /// Use the provided hyper client. + pub fn hyper_client( + self, + hyper_client: hyper::Client, + ) -> Authenticator> + where + NewC: hyper::client::connect::Connect, + T: AuthFlow, + { + Authenticator { + client: hyper_client, + token_getter: self.token_getter, + store: self.store, + delegate: self.delegate, + } + } + + /// Persist tokens to disk in the provided filename. + pub fn persist_tokens_to_disk>( + self, + path: P, + ) -> Authenticator { + let disk_storage = DiskTokenStorage::new(path.as_ref().to_str().unwrap()); + Authenticator { + client: self.client, + token_getter: self.token_getter, + store: disk_storage, + delegate: self.delegate, + } + } + + /// Use the provided authenticator delegate. + pub fn delegate( + self, + delegate: NewAD, + ) -> Authenticator { + Authenticator { + client: self.client, + token_getter: self.token_getter, + store: self.store, delegate: delegate, + } + } + + /// Create the authenticator. + pub fn build(self) -> io::Result + where + T::TokenGetter: 'static + GetToken + Send, + S: 'static + Send, + AD: 'static + Send, + C::Connector: 'static + Clone + Send, + { + let client = self.client.build_hyper_client(); + let store = Arc::new(Mutex::new(self.store?)); + let inner = Arc::new(Mutex::new( + self.token_getter.build_token_getter(client.clone()), + )); + + Ok(AuthenticatorImpl { + client, + inner, + store, + delegate: self.delegate, }) } } @@ -74,7 +192,7 @@ impl< S: 'static + TokenStorage + Send, AD: 'static + AuthenticatorDelegate + Send, C: 'static + hyper::client::connect::Connect + Clone + Send, - > GetToken for Authenticator + > GetToken for AuthenticatorImpl { /// Returns the API Key of the inner flow. fn api_key(&mut self) -> Option { diff --git a/src/device.rs b/src/device.rs index 67d7048..78cce65 100644 --- a/src/device.rs +++ b/src/device.rs @@ -13,7 +13,7 @@ use serde_json as json; use tokio_timer; use url::form_urlencoded; -use crate::authenticator_delegate::{FlowDelegate, PollInformation, Retry}; +use crate::authenticator_delegate::{DefaultFlowDelegate, FlowDelegate, PollInformation, Retry}; use crate::types::{ ApplicationSecret, Flow, FlowType, GetToken, JsonError, PollError, RequestError, Token, }; @@ -23,8 +23,76 @@ pub const GOOGLE_DEVICE_CODE_URL: &'static str = "https://accounts.google.com/o/ /// Implements the [Oauth2 Device Flow](https://developers.google.com/youtube/v3/guides/authentication#devices) /// It operates in two steps: /// * obtain a code to show to the user -/// * (repeatedly) poll for the user to authenticate your application -pub struct DeviceFlow { +// * (repeatedly) poll for the user to authenticate your application +#[derive(Clone)] +pub struct DeviceFlow { + application_secret: ApplicationSecret, + device_code_url: String, + flow_delegate: FD, + wait: Duration, +} + +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 { + DeviceFlow { + application_secret: secret, + device_code_url: GOOGLE_DEVICE_CODE_URL.to_string(), + flow_delegate: DefaultFlowDelegate, + wait: Duration::from_secs(120), + } + } +} + +impl DeviceFlow { + /// Use the provided device code url. + pub fn device_code_url(self, url: String) -> Self { + DeviceFlow { + device_code_url: url, + ..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, + } + } + + /// Use the provided wait duration. + pub fn wait_duration(self, duration: Duration) -> Self { + DeviceFlow { + wait: duration, + ..self + } + } +} + +impl crate::authenticator::AuthFlow for DeviceFlow +where + FD: FlowDelegate + Send + 'static, + 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), + } + } +} + +/// The DeviceFlow implementation. +pub struct DeviceFlowImpl { client: hyper::Client, application_secret: ApplicationSecret, /// Usually GOOGLE_DEVICE_CODE_URL @@ -33,7 +101,7 @@ pub struct DeviceFlow { wait: Duration, } -impl Flow for DeviceFlow { +impl Flow for DeviceFlowImpl { fn type_id() -> FlowType { FlowType::Device(String::new()) } @@ -42,7 +110,7 @@ impl Flow for DeviceFlow { impl< FD: FlowDelegate + Clone + Send + 'static, C: hyper::client::connect::Connect + Sync + 'static, - > GetToken for DeviceFlow + > GetToken for DeviceFlowImpl { fn token( &mut self, @@ -62,39 +130,16 @@ impl< } } -impl DeviceFlow +impl DeviceFlowImpl where C: hyper::client::connect::Connect + Sync + 'static, C::Transport: 'static, C::Future: 'static, FD: FlowDelegate + Clone + Send + 'static, { - pub fn new>( - client: hyper::Client, - secret: ApplicationSecret, - fd: FD, - device_code_url: Option, - ) -> DeviceFlow { - DeviceFlow { - client: client, - application_secret: secret, - device_code_url: device_code_url - .as_ref() - .map(|s| s.as_ref().to_string()) - .unwrap_or(GOOGLE_DEVICE_CODE_URL.to_string()), - fd: fd, - wait: Duration::from_secs(1200), - } - } - - /// Set the time to wait for the user to authorize us. The default is 120 seconds. - pub fn set_wait_duration(&mut self, wait: Duration) { - self.wait = wait; - } - /// Essentially what `GetToken::token` does: Retrieve a token for the given scopes without /// caching. - pub fn retrieve_device_token<'a>( + fn retrieve_device_token<'a>( &mut self, scopes: Vec, ) -> Box + Send> { @@ -362,6 +407,7 @@ mod tests { use tokio; use super::*; + use crate::authenticator::AuthFlow; use crate::helper::parse_application_secret; #[test] @@ -385,7 +431,10 @@ mod tests { .keep_alive(false) .build::<_, hyper::Body>(https); - let mut flow = DeviceFlow::new(client.clone(), app_secret, FD, Some(device_code_url)); + let mut flow = DeviceFlow::new(app_secret) + .delegate(FD) + .device_code_url(device_code_url) + .build_token_getter(client); let mut rt = tokio::runtime::Builder::new() .core_threads(1) diff --git a/src/installed.rs b/src/installed.rs index 8372247..0ae8ae0 100644 --- a/src/installed.rs +++ b/src/installed.rs @@ -13,7 +13,7 @@ use hyper::{header, StatusCode, Uri}; use url::form_urlencoded; use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET}; -use crate::authenticator_delegate::FlowDelegate; +use crate::authenticator_delegate::{DefaultFlowDelegate, FlowDelegate}; use crate::types::{ApplicationSecret, GetToken, RequestError, Token}; const OOB_REDIRECT_URI: &'static str = "urn:ietf:wg:oauth:2.0:oob"; @@ -59,7 +59,7 @@ where } impl - GetToken for InstalledFlow + GetToken for InstalledFlowImpl { fn token( &mut self, @@ -79,12 +79,8 @@ impl { +/// The InstalledFlow implementation. +pub struct InstalledFlowImpl { method: InstalledFlowReturnMethod, client: hyper::client::Client, fd: FD, @@ -101,38 +97,74 @@ pub enum InstalledFlowReturnMethod { HTTPRedirectEphemeral, /// Involves spinning up a local HTTP server and Google redirecting the browser to /// the server with a URL containing the code (preferred, but not as reliable). The - /// parameter is the port to listen on. Users should typically prefer - /// HTTPRedirectEphemeral unless they need to specify the port to listen on. + /// parameter is the port to listen on. HTTPRedirect(u16), } -impl<'c, FD: 'static + FlowDelegate + Clone + Send, C: 'c + hyper::client::connect::Connect> - InstalledFlow -{ - /// Starts a new Installed App auth flow. - /// If HTTPRedirect is chosen as method and the server can't be started, the flow falls - /// back to Interactive. +/// 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, +} + +impl InstalledFlow { + /// Create a new InstalledFlow with the provided secret and method. pub fn new( - client: hyper::client::Client, - fd: FD, secret: ApplicationSecret, method: InstalledFlowReturnMethod, - ) -> InstalledFlow { + ) -> InstalledFlow { InstalledFlow { - method: method, - fd: fd, + method, + flow_delegate: DefaultFlowDelegate, appsecret: secret, - client: client, } } +} +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 + Send + 'static, + 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<'c, FD: 'static + FlowDelegate + Clone + Send, C: 'c + hyper::client::connect::Connect> + InstalledFlowImpl +{ /// 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. - pub fn obtain_token<'a>( + fn obtain_token<'a>( &mut self, scopes: Vec, // Note: I haven't found a better way to give a list of strings here, due to ownership issues with futures. ) -> impl 'a + Future + Send { @@ -545,6 +577,7 @@ mod tests { use tokio; use super::*; + use crate::authenticator::AuthFlow; use crate::authenticator_delegate::FlowDelegate; use crate::helper::*; use crate::types::StringError; @@ -612,12 +645,10 @@ mod tests { .build::<_, hyper::Body>(https); let fd = FD("authorizationcode".to_string(), client.clone()); - let mut inf = InstalledFlow::new( - client.clone(), - fd, - app_secret.clone(), - InstalledFlowReturnMethod::Interactive, - ); + let mut inf = + InstalledFlow::new(app_secret.clone(), InstalledFlowReturnMethod::Interactive) + .delegate(fd) + .build_token_getter(client.clone()); let mut rt = tokio::runtime::Builder::new() .core_threads(1) @@ -646,15 +677,13 @@ mod tests { } // Successful path with HTTP redirect. { - let mut inf = InstalledFlow::new( - client.clone(), - FD( - "authorizationcodefromlocalserver".to_string(), - client.clone(), - ), - app_secret, - InstalledFlowReturnMethod::HTTPRedirectEphemeral, - ); + let mut inf = + InstalledFlow::new(app_secret, InstalledFlowReturnMethod::HTTPRedirect(8081)) + .delegate(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}"#) diff --git a/src/lib.rs b/src/lib.rs index 761f663..48e3b28 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,41 +48,22 @@ //! use std::path::Path; //! //! fn main() { -//! // Boilerplate: Set up hyper HTTP client and TLS. -//! let https = HttpsConnector::new(1); -//! let client = Client::builder() -//! .keep_alive(false) -//! .build::<_, hyper::Body>(https); -//! //! // Read application secret from a file. Sometimes it's easier to compile it directly into //! // the binary. The clientsecret file contains JSON like `{"installed":{"client_id": ... }}` //! let secret = yup_oauth2::read_application_secret(Path::new("clientsecret.json")) //! .expect("clientsecret.json"); //! -//! // There are two types of delegates; FlowDelegate and AuthenticatorDelegate. See the -//! // respective documentation; all you need to know here is that they determine how the user -//! // is asked to visit the OAuth flow URL or how to read back the provided code. -//! let ad = yup_oauth2::DefaultFlowDelegate; -//! -//! // InstalledFlow handles OAuth flows of that type. They are usually the ones where a user -//! // grants access to their personal account (think Google Drive, Github API, etc.). -//! let inf = InstalledFlow::new( -//! client.clone(), -//! ad, -//! secret, -//! yup_oauth2::InstalledFlowReturnMethod::HTTPRedirectEphemeral, -//! ); -//! // You could already use InstalledFlow by itself, but usually you want to cache tokens and -//! // refresh them, rather than ask the user every time to log in again. Authenticator wraps -//! // other flows and handles these. -//! // This type of authenticator caches tokens in a JSON file on disk. -//! let mut auth = Authenticator::new_disk( -//! client, -//! inf, -//! yup_oauth2::DefaultAuthenticatorDelegate, -//! "tokencache.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(0)) //! ) +//! .persist_tokens_to_disk("tokencache.json") +//! .build() //! .unwrap(); +//! //! let s = "https://www.googleapis.com/auth/drive.file".to_string(); //! let scopes = vec![s]; //! @@ -112,7 +93,7 @@ mod service_account; mod storage; mod types; -pub use crate::authenticator::Authenticator; +pub use crate::authenticator::{AuthFlow, Authenticator}; pub use crate::authenticator_delegate::{ AuthenticatorDelegate, DefaultAuthenticatorDelegate, DefaultFlowDelegate, FlowDelegate, PollInformation, diff --git a/src/service_account.rs b/src/service_account.rs index 598722a..c67fb94 100644 --- a/src/service_account.rs +++ b/src/service_account.rs @@ -14,6 +14,7 @@ use std::default::Default; use std::sync::{Arc, Mutex}; +use crate::authenticator::{DefaultHyperClient, HyperClientBuilder}; use crate::storage::{hash_scopes, MemoryStorage, TokenStorage}; use crate::types::{ApplicationSecret, GetToken, JsonError, RequestError, StringError, Token}; @@ -188,12 +189,75 @@ where /// (and you also should not) use this with `Authenticator`. Just use it directly. #[derive(Clone)] pub struct ServiceAccountAccess { + client: C, + key: ServiceAccountKey, + sub: Option, +} + +impl ServiceAccountAccess { + /// Create a new ServiceAccountAccess with the provided key. + pub fn new(key: ServiceAccountKey) -> Self { + ServiceAccountAccess { + client: DefaultHyperClient, + key, + sub: None, + } + } +} + +impl ServiceAccountAccess +where + C: HyperClientBuilder, + C::Connector: 'static, +{ + /// Use the provided hyper client. + pub fn hyper_client( + self, + hyper_client: NewC, + ) -> ServiceAccountAccess { + ServiceAccountAccess { + client: hyper_client, + key: self.key, + sub: self.sub, + } + } + + /// Use the provided sub. + pub fn sub(self, sub: String) -> Self { + ServiceAccountAccess { + sub: Some(sub), + ..self + } + } + + /// Build the configured ServiceAccountAccess. + pub fn build(self) -> impl GetToken { + ServiceAccountAccessImpl::new(self.client.build_hyper_client(), self.key, self.sub) + } +} + +#[derive(Clone)] +struct ServiceAccountAccessImpl { client: hyper::Client, key: ServiceAccountKey, cache: Arc>, sub: Option, } +impl ServiceAccountAccessImpl +where + C: hyper::client::connect::Connect, +{ + fn new(client: hyper::Client, key: ServiceAccountKey, sub: Option) -> Self { + ServiceAccountAccessImpl { + client, + key, + cache: Arc::new(Mutex::new(MemoryStorage::default())), + sub, + } + } +} + /// This is the schema of the server's response. #[derive(Deserialize, Debug)] struct TokenResponse { @@ -216,36 +280,7 @@ impl TokenResponse { } } -impl<'a, C: 'static + hyper::client::connect::Connect> ServiceAccountAccess { - /// Returns a new `ServiceAccountAccess` token source. - #[allow(dead_code)] - pub fn new( - key: ServiceAccountKey, - client: hyper::Client, - ) -> ServiceAccountAccess { - ServiceAccountAccess { - client: client, - key: key, - cache: Arc::new(Mutex::new(MemoryStorage::default())), - sub: None, - } - } - - /// Set `sub` claim in new `ServiceAccountKey` (see - /// https://developers.google.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests). - pub fn with_sub( - key: ServiceAccountKey, - client: hyper::Client, - sub: String, - ) -> ServiceAccountAccess { - ServiceAccountAccess { - client: client, - key: key, - cache: Arc::new(Mutex::new(MemoryStorage::default())), - sub: Some(sub), - } - } - +impl<'a, C: 'static + hyper::client::connect::Connect> ServiceAccountAccessImpl { /// Send a request for a new Bearer token to the OAuth provider. fn request_token( client: hyper::client::Client, @@ -311,7 +346,7 @@ impl<'a, C: 'static + hyper::client::connect::Connect> ServiceAccountAccess { } } -impl GetToken for ServiceAccountAccess +impl GetToken for ServiceAccountAccessImpl where C: hyper::client::connect::Connect, { @@ -441,7 +476,7 @@ mod tests { .with_body(json_response) .expect(1) .create(); - let mut acc = ServiceAccountAccess::new(key.clone(), client.clone()); + let mut acc = ServiceAccountAccessImpl::new(client.clone(), key.clone(), None); let fut = acc .token(vec!["https://www.googleapis.com/auth/pubsub"]) .and_then(|tok| { @@ -480,7 +515,9 @@ mod tests { .with_header("content-type", "text/json") .with_body(bad_json_response) .create(); - let mut acc = ServiceAccountAccess::new(key.clone(), client.clone()); + let mut acc = ServiceAccountAccess::new(key.clone()) + .hyper_client(client.clone()) + .build(); let fut = acc .token(vec!["https://www.googleapis.com/auth/pubsub"]) .then(|result| { @@ -506,7 +543,7 @@ mod tests { let client = hyper::Client::builder() .executor(runtime.executor()) .build(https); - let mut acc = ServiceAccountAccess::new(key, client); + let mut acc = ServiceAccountAccess::new(key).hyper_client(client).build(); println!( "{:?}", acc.token(vec!["https://www.googleapis.com/auth/pubsub"])