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.
This commit is contained in:
Glenn Griffin
2019-11-13 11:51:28 -08:00
parent 911fec82f1
commit 3aadc6b0ef
9 changed files with 519 additions and 653 deletions

View File

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

View File

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

View File

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

View File

@@ -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<T: GetToken, AD: AuthenticatorDelegate, C: hyper::client::connect::Connect>
pub struct Authenticator<C> {
hyper_client: hyper::Client<C>,
app_secret: ApplicationSecret,
auth_delegate: Box<dyn AuthenticatorDelegate>,
storage: Storage,
auth_flow: AuthFlow,
}
impl<C> Authenticator<C>
where
C: hyper::client::connect::Connect + 'static,
{
client: hyper::Client<C>,
inner: T,
store: Storage,
delegate: AD,
pub async fn token<'a, T>(&'a self, scopes: &'a [T]) -> Result<Token, RequestError>
where
T: AsRef<str>,
{
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<C, F> {
hyper_client_builder: C,
app_secret: ApplicationSecret,
auth_delegate: Box<dyn AuthenticatorDelegate>,
storage_type: StorageType,
auth_flow: F,
}
pub struct InstalledFlowAuthenticator;
impl InstalledFlowAuthenticator {
pub fn builder(
app_secret: ApplicationSecret,
method: InstalledFlowReturnMethod,
) -> AuthenticatorBuilder<DefaultHyperClient, InstalledFlow> {
AuthenticatorBuilder::<DefaultHyperClient, _>::with_auth_flow(
app_secret,
InstalledFlow::new(method),
)
}
}
pub struct DeviceFlowAuthenticator;
impl DeviceFlowAuthenticator {
pub fn builder(
app_secret: ApplicationSecret,
) -> AuthenticatorBuilder<DefaultHyperClient, DeviceFlow> {
AuthenticatorBuilder::<DefaultHyperClient, _>::with_auth_flow(app_secret, DeviceFlow::new())
}
}
impl<C, F> AuthenticatorBuilder<C, F> {
fn with_auth_flow(
app_secret: ApplicationSecret,
auth_flow: F,
) -> AuthenticatorBuilder<DefaultHyperClient, F> {
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<NewC>(
self,
hyper_client: hyper::Client<NewC>,
) -> AuthenticatorBuilder<hyper::Client<NewC>, 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<P: Into<PathBuf>>(self, path: P) -> AuthenticatorBuilder<C, F> {
AuthenticatorBuilder {
storage_type: StorageType::Disk(path.into()),
..self
}
}
/// Use the provided authenticator delegate.
pub fn auth_delegate(
self,
auth_delegate: Box<dyn AuthenticatorDelegate>,
) -> AuthenticatorBuilder<C, F> {
AuthenticatorBuilder {
auth_delegate,
..self
}
}
/// Create the authenticator.
pub async fn build(self) -> io::Result<Authenticator<C::Connector>>
where
C: HyperClientBuilder,
F: Into<AuthFlow>,
{
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<C> AuthenticatorBuilder<C, DeviceFlow> {
/// Use the provided device code url.
pub fn device_code_url(self, url: impl Into<Cow<'static, str>>) -> 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<dyn FlowDelegate>) -> 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<Cow<'static, str>>) -> Self {
AuthenticatorBuilder {
auth_flow: DeviceFlow {
grant_type: grant_type.into(),
..self.auth_flow
},
..self
}
}
}
impl<C> AuthenticatorBuilder<C, InstalledFlow> {
/// Use the provided FlowDelegate.
pub fn flow_delegate(self, flow_delegate: Box<dyn FlowDelegate>) -> 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<DeviceFlow> for AuthFlow {
fn from(device_flow: DeviceFlow) -> AuthFlow {
AuthFlow::DeviceFlow(device_flow)
}
}
impl From<InstalledFlow> 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<C>,
app_secret: &'a ApplicationSecret,
scopes: &'a [T],
) -> Result<Token, RequestError>
where
T: AsRef<str>,
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<C> {
type TokenGetter: GetToken;
fn build_token_getter(self, client: hyper::Client<C>) -> 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<T, AD, C> {
client: C,
token_getter: T,
storage_type: StorageType,
delegate: AD,
}
impl<T> Authenticator<T, 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
/// ```
/// # #[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<T, DefaultAuthenticatorDelegate, DefaultHyperClient> {
Authenticator {
client: DefaultHyperClient,
token_getter: flow,
storage_type: StorageType::Memory,
delegate: DefaultAuthenticatorDelegate,
}
}
}
impl<T, AD, C> Authenticator<T, AD, C>
where
T: AuthFlow<C::Connector>,
AD: AuthenticatorDelegate,
C: HyperClientBuilder,
{
/// Use the provided hyper client.
pub fn hyper_client<NewC>(
self,
hyper_client: hyper::Client<NewC>,
) -> Authenticator<T, AD, hyper::Client<NewC>>
where
NewC: hyper::client::connect::Connect + 'static,
T: AuthFlow<NewC>,
{
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<P: Into<PathBuf>>(self, path: P) -> Authenticator<T, AD, C> {
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<NewAD: AuthenticatorDelegate>(
self,
delegate: NewAD,
) -> Authenticator<T, NewAD, C> {
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<impl GetToken>
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<GT, AD, C> AuthenticatorImpl<GT, AD, C>
where
GT: GetToken,
AD: AuthenticatorDelegate,
C: hyper::client::connect::Connect + 'static,
{
async fn get_token<T>(&self, scopes: &[T]) -> Result<Token, RequestError>
where
T: AsRef<str> + 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<GT, AD, C> GetToken for AuthenticatorImpl<GT, AD, C>
where
GT: GetToken,
AD: AuthenticatorDelegate,
C: hyper::client::connect::Connect + 'static,
{
/// Returns the API Key of the inner flow.
fn api_key(&self) -> Option<String> {
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<Box<dyn Future<Output = Result<Token, RequestError>> + Send + 'a>>
where
T: AsRef<str> + Sync,
{
Box::pin(self.get_token(scopes))
}
}

View File

@@ -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<FD> {
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<dyn FlowDelegate>,
pub(crate) wait_duration: Duration,
pub(crate) grant_type: Cow<'static, str>,
}
impl DeviceFlow<DefaultFlowDelegate> {
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<DefaultFlowDelegate> {
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<FD> DeviceFlow<FD> {
/// 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<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,
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<FD, C> crate::authenticator::AuthFlow<C> for DeviceFlow<FD>
where
FD: FlowDelegate,
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),
grant_type: self.grant_type,
}
}
}
/// The DeviceFlow implementation.
pub struct DeviceFlowImpl<FD, C> {
client: hyper::Client<C, hyper::Body>,
application_secret: ApplicationSecret,
/// Usually GOOGLE_DEVICE_CODE_URL
device_code_url: Cow<'static, str>,
fd: FD,
wait: Duration,
grant_type: Cow<'static, str>,
}
impl<FD, C> GetToken for DeviceFlowImpl<FD, C>
where
FD: FlowDelegate,
C: hyper::client::connect::Connect + 'static,
{
fn token<'a, T>(
&'a self,
scopes: &'a [T],
) -> Pin<Box<dyn Future<Output = Result<Token, RequestError>> + Send + 'a>>
where
T: AsRef<str> + Sync,
{
Box::pin(self.retrieve_device_token(scopes))
}
fn api_key(&self) -> Option<String> {
None
}
fn application_secret(&self) -> &ApplicationSecret {
&self.application_secret
}
}
impl<FD, C> DeviceFlowImpl<FD, C>
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<T>(&self, scopes: &[T]) -> Result<Token, RequestError>
pub(crate) async fn token<C, T>(
&self,
hyper_client: &hyper::Client<C>,
app_secret: &ApplicationSecret,
scopes: &[T],
) -> Result<Token, RequestError>
where
T: AsRef<str>,
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<C>(
&self,
hyper_client: &hyper::Client<C>,
app_secret: &ApplicationSecret,
pollinf: &PollInformation,
device_code: &str,
grant_type: &str,
) -> Result<Token, RequestError> {
) -> Result<Token, RequestError>
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<T>(
async fn request_code<C, T>(
application_secret: &ApplicationSecret,
client: &hyper::Client<C>,
device_code_url: &str,
@@ -221,6 +132,7 @@ where
) -> Result<(PollInformation, String), RequestError>
where
T: AsRef<str>,
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<C>,
device_code: &str,
grant_type: &str,
expires_at: DateTime<Utc>,
fd: &FD,
) -> Result<Option<Token>, PollError> {
flow_delegate: &dyn FlowDelegate,
) -> Result<Option<Token>, 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<(), ()>

View File

@@ -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<FD, C> GetToken for InstalledFlowImpl<FD, C>
where
FD: FlowDelegate,
C: hyper::client::connect::Connect + 'static,
{
fn token<'a, T>(
&'a self,
scopes: &'a [T],
) -> Pin<Box<dyn Future<Output = Result<Token, RequestError>> + Send + 'a>>
where
T: AsRef<str> + Sync,
{
Box::pin(self.obtain_token(scopes))
}
fn api_key(&self) -> Option<String> {
None
}
fn application_secret(&self) -> &ApplicationSecret {
&self.appsecret
}
}
/// The InstalledFlow implementation.
pub struct InstalledFlowImpl<FD, C>
where
FD: FlowDelegate,
C: hyper::client::connect::Connect,
{
method: InstalledFlowReturnMethod,
client: hyper::client::Client<C, hyper::Body>,
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<FD: FlowDelegate> {
method: InstalledFlowReturnMethod,
flow_delegate: FD,
appsecret: ApplicationSecret,
pub struct InstalledFlow {
pub(crate) method: InstalledFlowReturnMethod,
pub(crate) flow_delegate: Box<dyn FlowDelegate>,
}
impl InstalledFlow<DefaultFlowDelegate> {
impl InstalledFlow {
/// Create a new InstalledFlow with the provided secret and method.
pub fn new(
secret: ApplicationSecret,
method: InstalledFlowReturnMethod,
) -> InstalledFlow<DefaultFlowDelegate> {
pub(crate) fn new(method: InstalledFlowReturnMethod) -> InstalledFlow {
InstalledFlow {
method,
flow_delegate: DefaultFlowDelegate,
appsecret: secret,
flow_delegate: Box::new(DefaultFlowDelegate),
}
}
}
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,
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<FD, C> InstalledFlowImpl<FD, C>
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<T>(&self, scopes: &[T]) -> Result<Token, RequestError>
pub(crate) async fn token<C, T>(
&self,
hyper_client: &hyper::Client<C>,
app_secret: &ApplicationSecret,
scopes: &[T],
) -> Result<Token, RequestError>
where
T: AsRef<str>,
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<T>(&self, scopes: &[T]) -> Result<Token, RequestError>
async fn ask_auth_code_interactively<C, T>(
&self,
hyper_client: &hyper::Client<C>,
app_secret: &ApplicationSecret,
scopes: &[T],
) -> Result<Token, RequestError>
where
T: AsRef<str>,
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<T>(&self, scopes: &[T]) -> Result<Token, RequestError>
async fn ask_auth_code_via_http<C, T>(
&self,
hyper_client: &hyper::Client<C>,
app_secret: &ApplicationSecret,
scopes: &[T],
) -> Result<Token, RequestError>
where
T: AsRef<str>,
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<str> = match auth_delegate.redirect_uri() {
let redirect_uri: Cow<str> = 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<C>(
&self,
authcode: &str,
hyper_client: &hyper::Client<C>,
app_secret: &ApplicationSecret,
server_addr: Option<SocketAddr>,
) -> Result<Token, RequestError> {
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<Token, RequestError>
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<SocketAddr>,
@@ -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<(), ()>

View File

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

View File

@@ -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<C> {
client: C,
key: ServiceAccountKey,
subject: Option<String>,
}
impl ServiceAccountAccess<DefaultHyperClient> {
/// 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<DefaultHyperClient> {
Builder {
client: DefaultHyperClient,
key,
subject: None,
@@ -176,16 +165,16 @@ impl ServiceAccountAccess<DefaultHyperClient> {
}
}
impl<C> ServiceAccountAccess<C>
where
C: HyperClientBuilder,
{
pub struct Builder<C> {
client: C,
key: ServiceAccountKey,
subject: Option<String>,
}
impl<C> Builder<C> {
/// Use the provided hyper client.
pub fn hyper_client<NewC: HyperClientBuilder>(
self,
hyper_client: NewC,
) -> ServiceAccountAccess<NewC> {
ServiceAccountAccess {
pub fn hyper_client<NewC: HyperClientBuilder>(self, hyper_client: NewC) -> Builder<NewC> {
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<impl GetToken, io::Error> {
ServiceAccountAccessImpl::new(self.client.build_hyper_client(), self.key, self.subject)
pub fn build(self) -> Result<ServiceAccountAccess<C::Connector>, io::Error>
where
C: HyperClientBuilder,
{
ServiceAccountAccess::new(self.client.build_hyper_client(), self.key, self.subject)
}
}
struct ServiceAccountAccessImpl<C> {
client: hyper::Client<C, hyper::Body>,
pub struct ServiceAccountAccess<C> {
client: hyper::Client<C>,
key: ServiceAccountKey,
cache: Storage,
subject: Option<String>,
signer: JWTSigner,
}
impl<C> ServiceAccountAccessImpl<C>
impl<C> ServiceAccountAccess<C>
where
C: hyper::client::connect::Connect,
C: hyper::client::connect::Connect + 'static,
{
fn new(
client: hyper::Client<C>,
@@ -224,7 +216,7 @@ where
subject: Option<String>,
) -> Result<Self, io::Error> {
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<String>,
token_type: Option<String>,
expires_in: Option<i64>,
}
impl<C> ServiceAccountAccessImpl<C>
where
C: hyper::client::connect::Connect + 'static,
{
pub async fn token<T>(&self, scopes: &[T]) -> Result<Token, RequestError>
where
T: AsRef<str>,
{
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<T>(
client: &hyper::client::Client<C>,
@@ -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<String>,
token_type: Option<String>,
expires_in: Option<i64>,
}
match serde_json::from_slice::<JsonErrorOr<TokenResponse>>(&body)? {
JsonErrorOr::Err(err) => Err(err.into()),
JsonErrorOr::Data(TokenResponse {
@@ -305,61 +314,12 @@ where
))),
}
}
async fn get_token<T>(&self, scopes: &[T]) -> Result<Token, RequestError>
where
T: AsRef<str>,
{
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<C> GetToken for ServiceAccountAccessImpl<C>
where
C: hyper::client::connect::Connect + 'static,
{
fn token<'a, T>(
&'a self,
scopes: &'a [T],
) -> Pin<Box<dyn Future<Output = Result<Token, RequestError>> + Send + 'a>>
where
T: AsRef<str> + 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<String> {
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();

View File

@@ -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<Box<dyn Future<Output = Result<Token, RequestError>> + Send + 'a>>
where
T: AsRef<str> + Sync;
fn api_key(&self) -> Option<String>;
/// 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.