Merge pull request #105 from ggriffiniii/builder-pattern

Use the builder pattern to create authenticators.
This commit is contained in:
Lewin Bormann
2019-08-30 08:37:55 +02:00
committed by GitHub
9 changed files with 392 additions and 208 deletions

View File

@@ -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()
);

View File

@@ -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::<String>(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));
}

View File

@@ -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(())

View File

@@ -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(())

View File

@@ -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<T: GetToken, AD: AuthenticatorDelegate, C: hyper::client::connect::Connect>
Authenticator<T, MemoryStorage, AD, C>
/// 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<Self::Connector>;
}
/// The builder value used when the default hyper client should be used.
pub struct DefaultHyperClient;
impl HyperClientBuilder for DefaultHyperClient {
type Connector = hyper_rustls::HttpsConnector<hyper::client::connect::HttpConnector>;
fn build_hyper_client(self) -> hyper::Client<Self::Connector> {
hyper::Client::builder()
.keep_alive(false)
.build::<_, hyper::Body>(hyper_rustls::HttpsConnector::new(1))
}
}
impl<C> HyperClientBuilder for hyper::Client<C>
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<C> {
self
}
}
/// An internal trait implemented by flows to be used by an authenticator.
pub trait AuthFlow<C> {
type TokenGetter: GetToken;
fn build_token_getter(self, client: hyper::Client<C>) -> 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<C::Connector>,
S: TokenStorage,
AD: AuthenticatorDelegate,
C: HyperClientBuilder,
> {
client: C,
token_getter: T,
store: io::Result<S>,
delegate: AD,
}
impl<T> Authenticator<T, MemoryStorage, DefaultAuthenticatorDelegate, DefaultHyperClient>
where
T: AuthFlow<<DefaultHyperClient as HyperClientBuilder>::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<C>,
inner: T,
delegate: AD,
) -> Authenticator<T, MemoryStorage, AD, C> {
flow: T,
) -> Authenticator<T, MemoryStorage, DefaultAuthenticatorDelegate, DefaultHyperClient> {
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<T: GetToken, AD: AuthenticatorDelegate, C: hyper::client::connect::Connect>
Authenticator<T, DiskTokenStorage, AD, C>
impl<T, S, AD, C> Authenticator<T, S, AD, C>
where
T: AuthFlow<C::Connector>,
S: TokenStorage,
AD: AuthenticatorDelegate,
C: HyperClientBuilder,
{
/// Create an Authenticator using the store at `path`.
pub fn new_disk<P: AsRef<str>>(
client: hyper::Client<C>,
inner: T,
delegate: AD,
token_storage_path: P,
) -> io::Result<Authenticator<T, DiskTokenStorage, AD, C>> {
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<NewC>(
self,
hyper_client: hyper::Client<NewC>,
) -> Authenticator<T, S, AD, hyper::Client<NewC>>
where
NewC: hyper::client::connect::Connect,
T: AuthFlow<NewC>,
{
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<P: AsRef<Path>>(
self,
path: P,
) -> Authenticator<T, DiskTokenStorage, AD, C> {
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<NewAD: AuthenticatorDelegate>(
self,
delegate: NewAD,
) -> Authenticator<T, S, NewAD, C> {
Authenticator {
client: self.client,
token_getter: self.token_getter,
store: self.store,
delegate: delegate,
}
}
/// Create the authenticator.
pub fn build(self) -> io::Result<impl GetToken>
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<GT, S, AD, C>
> GetToken for AuthenticatorImpl<GT, S, AD, C>
{
/// Returns the API Key of the inner flow.
fn api_key(&mut self) -> Option<String> {

View File

@@ -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<FD, C> {
// * (repeatedly) poll for the user to authenticate your application
#[derive(Clone)]
pub struct DeviceFlow<FD> {
application_secret: ApplicationSecret,
device_code_url: String,
flow_delegate: FD,
wait: Duration,
}
impl DeviceFlow<DefaultFlowDelegate> {
/// Create a new DeviceFlow. The default FlowDelegate will be used and the
/// default wait time is 120 seconds.
pub fn new(secret: ApplicationSecret) -> DeviceFlow<DefaultFlowDelegate> {
DeviceFlow {
application_secret: secret,
device_code_url: GOOGLE_DEVICE_CODE_URL.to_string(),
flow_delegate: DefaultFlowDelegate,
wait: Duration::from_secs(120),
}
}
}
impl<FD> DeviceFlow<FD> {
/// 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<NewFD>(self, delegate: NewFD) -> DeviceFlow<NewFD> {
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<FD, C> crate::authenticator::AuthFlow<C> for DeviceFlow<FD>
where
FD: FlowDelegate + Send + 'static,
C: hyper::client::connect::Connect + 'static,
{
type TokenGetter = DeviceFlowImpl<FD, C>;
fn build_token_getter(self, client: hyper::Client<C>) -> 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<FD, C> {
client: hyper::Client<C, hyper::Body>,
application_secret: ApplicationSecret,
/// Usually GOOGLE_DEVICE_CODE_URL
@@ -33,7 +101,7 @@ pub struct DeviceFlow<FD, C> {
wait: Duration,
}
impl<FD, C> Flow for DeviceFlow<FD, C> {
impl<FD, C> Flow for DeviceFlowImpl<FD, C> {
fn type_id() -> FlowType {
FlowType::Device(String::new())
}
@@ -42,7 +110,7 @@ impl<FD, C> Flow for DeviceFlow<FD, C> {
impl<
FD: FlowDelegate + Clone + Send + 'static,
C: hyper::client::connect::Connect + Sync + 'static,
> GetToken for DeviceFlow<FD, C>
> GetToken for DeviceFlowImpl<FD, C>
{
fn token<I, T>(
&mut self,
@@ -62,39 +130,16 @@ impl<
}
}
impl<FD, C> DeviceFlow<FD, C>
impl<FD, C> DeviceFlowImpl<FD, C>
where
C: hyper::client::connect::Connect + Sync + 'static,
C::Transport: 'static,
C::Future: 'static,
FD: FlowDelegate + Clone + Send + 'static,
{
pub fn new<S: 'static + AsRef<str>>(
client: hyper::Client<C, hyper::Body>,
secret: ApplicationSecret,
fd: FD,
device_code_url: Option<S>,
) -> DeviceFlow<FD, C> {
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<String>,
) -> Box<dyn Future<Item = Token, Error = RequestError> + 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)

View File

@@ -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<FD: FlowDelegate + 'static + Send + Clone, C: hyper::client::connect::Connect + 'static>
GetToken for InstalledFlow<FD, C>
GetToken for InstalledFlowImpl<FD, C>
{
fn token<I, T>(
&mut self,
@@ -79,12 +79,8 @@ impl<FD: FlowDelegate + 'static + Send + Clone, C: hyper::client::connect::Conne
}
}
/// InstalledFlow 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). You should use it wrapped
/// inside an `Authenticator` to benefit from refreshing tokens and caching previously obtained
/// authorization.
pub struct InstalledFlow<FD: FlowDelegate, C: hyper::client::connect::Connect + 'static> {
/// The InstalledFlow implementation.
pub struct InstalledFlowImpl<FD: FlowDelegate, C: hyper::client::connect::Connect + 'static> {
method: InstalledFlowReturnMethod,
client: hyper::client::Client<C, hyper::Body>,
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<FD, C>
{
/// 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<FD: FlowDelegate> {
method: InstalledFlowReturnMethod,
flow_delegate: FD,
appsecret: ApplicationSecret,
}
impl InstalledFlow<DefaultFlowDelegate> {
/// Create a new InstalledFlow with the provided secret and method.
pub fn new(
client: hyper::client::Client<C, hyper::Body>,
fd: FD,
secret: ApplicationSecret,
method: InstalledFlowReturnMethod,
) -> InstalledFlow<FD, C> {
) -> InstalledFlow<DefaultFlowDelegate> {
InstalledFlow {
method: method,
fd: fd,
method,
flow_delegate: DefaultFlowDelegate,
appsecret: secret,
client: client,
}
}
}
impl<FD> InstalledFlow<FD>
where
FD: FlowDelegate,
{
/// Use the provided FlowDelegate.
pub fn delegate<NewFD: FlowDelegate>(self, delegate: NewFD) -> InstalledFlow<NewFD> {
InstalledFlow {
method: self.method,
flow_delegate: delegate,
appsecret: self.appsecret,
}
}
}
impl<FD, C> crate::authenticator::AuthFlow<C> for InstalledFlow<FD>
where
FD: FlowDelegate + Send + 'static,
C: hyper::client::connect::Connect + 'static,
{
type TokenGetter = InstalledFlowImpl<FD, C>;
fn build_token_getter(self, client: hyper::Client<C>) -> 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<FD, C>
{
/// 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<String>, // Note: I haven't found a better way to give a list of strings here, due to ownership issues with futures.
) -> impl 'a + Future<Item = Token, Error = RequestError> + 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}"#)

View File

@@ -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,

View File

@@ -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<C> {
client: C,
key: ServiceAccountKey,
sub: Option<String>,
}
impl ServiceAccountAccess<DefaultHyperClient> {
/// Create a new ServiceAccountAccess with the provided key.
pub fn new(key: ServiceAccountKey) -> Self {
ServiceAccountAccess {
client: DefaultHyperClient,
key,
sub: None,
}
}
}
impl<C> ServiceAccountAccess<C>
where
C: HyperClientBuilder,
C::Connector: 'static,
{
/// Use the provided hyper client.
pub fn hyper_client<NewC: HyperClientBuilder>(
self,
hyper_client: NewC,
) -> ServiceAccountAccess<NewC> {
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<C> {
client: hyper::Client<C, hyper::Body>,
key: ServiceAccountKey,
cache: Arc<Mutex<MemoryStorage>>,
sub: Option<String>,
}
impl<C> ServiceAccountAccessImpl<C>
where
C: hyper::client::connect::Connect,
{
fn new(client: hyper::Client<C>, key: ServiceAccountKey, sub: Option<String>) -> 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<C> {
/// Returns a new `ServiceAccountAccess` token source.
#[allow(dead_code)]
pub fn new(
key: ServiceAccountKey,
client: hyper::Client<C, hyper::Body>,
) -> ServiceAccountAccess<C> {
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<C, hyper::Body>,
sub: String,
) -> ServiceAccountAccess<C> {
ServiceAccountAccess {
client: client,
key: key,
cache: Arc::new(Mutex::new(MemoryStorage::default())),
sub: Some(sub),
}
}
impl<'a, C: 'static + hyper::client::connect::Connect> ServiceAccountAccessImpl<C> {
/// Send a request for a new Bearer token to the OAuth provider.
fn request_token(
client: hyper::client::Client<C>,
@@ -311,7 +346,7 @@ impl<'a, C: 'static + hyper::client::connect::Connect> ServiceAccountAccess<C> {
}
}
impl<C: 'static> GetToken for ServiceAccountAccess<C>
impl<C: 'static> GetToken for ServiceAccountAccessImpl<C>
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"])