mirror of
https://github.com/OMGeeky/yup-oauth2.git
synced 2026-02-15 22:24:31 +01:00
Merge pull request #105 from ggriffiniii/builder-pattern
Use the builder pattern to create authenticators.
This commit is contained in:
@@ -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()
|
||||
);
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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> {
|
||||
|
||||
111
src/device.rs
111
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<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)
|
||||
|
||||
105
src/installed.rs
105
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<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}"#)
|
||||
|
||||
39
src/lib.rs
39
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,
|
||||
|
||||
@@ -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"])
|
||||
|
||||
Reference in New Issue
Block a user