Merge branch 'nagisa/id_token' of github.com:nagisa/yup-oauth2 into nagisa-nagisa/id_token

This commit is contained in:
Brandon Ogle
2022-09-21 18:51:59 -07:00
8 changed files with 79 additions and 64 deletions

View File

@@ -26,7 +26,7 @@ where
let request = http::Request::get("https://example.com")
.header(
http::header::AUTHORIZATION,
format!("Bearer {}", token.as_str()),
format!("Bearer {}", token.access_token().ok_or("no access token")?),
)
.body(hyper::body::Body::empty())?;
let response = client.request(request).await?;

View File

@@ -33,7 +33,7 @@ impl AccessTokenFlow {
S::Error: Into<Box<dyn StdError + Send + Sync>>,
{
Ok(TokenInfo {
access_token: self.access_token.clone(),
access_token: Some(self.access_token.clone()),
refresh_token: None,
expires_at: None,
id_token: None,

View File

@@ -12,7 +12,7 @@ use crate::refresh::RefreshFlow;
#[cfg(feature = "service_account")]
use crate::service_account::{self, ServiceAccountFlow, ServiceAccountFlowOpts, ServiceAccountKey};
use crate::storage::{self, Storage, TokenStorage};
use crate::types::{AccessToken, ApplicationSecret, TokenInfo};
use crate::types::{ApplicationSecret, Token, TokenInfo};
use private::AuthFlow;
use crate::access_token::{AccessTokenFlow};
@@ -69,7 +69,7 @@ where
S::Error: Into<Box<dyn StdError + Send + Sync>>,
{
/// Return the current token for the provided scopes.
pub async fn token<'a, T>(&'a self, scopes: &'a [T]) -> Result<AccessToken, Error>
pub async fn token<'a, T>(&'a self, scopes: &'a [T]) -> Result<Token, Error>
where
T: AsRef<str>,
{
@@ -80,10 +80,7 @@ where
/// Return a token for the provided scopes, but don't reuse cached tokens. Instead,
/// always fetch a new token from the OAuth server.
pub async fn force_refreshed_token<'a, T>(
&'a self,
scopes: &'a [T],
) -> Result<AccessToken, Error>
pub async fn force_refreshed_token<'a, T>(&'a self, scopes: &'a [T]) -> Result<Token, Error>
where
T: AsRef<str>,
{

View File

@@ -111,4 +111,4 @@ pub use crate::service_account::ServiceAccountKey;
#[doc(inline)]
pub use crate::error::Error;
pub use crate::types::{AccessToken, ApplicationSecret, ConsoleApplicationSecret};
pub use crate::types::{ApplicationSecret, ConsoleApplicationSecret, Token};

View File

@@ -240,7 +240,7 @@ mod tests {
const TEST_PRIVATE_KEY_PATH: &'static str = "examples/Sanguine-69411a0c0eea.json";
// Uncomment this test to verify that we can successfully obtain tokens.
//#[tokio::test]
// #[tokio::test]
#[allow(dead_code)]
async fn test_service_account_e2e() {
let acc = ServiceAccountFlow::new(ServiceAccountFlowOpts {
@@ -262,6 +262,14 @@ mod tests {
acc.token(&client, &["https://www.googleapis.com/auth/pubsub"])
.await
);
println!(
"{:?}",
acc.token(
&client,
&["https://some.scope/likely-to-hand-out-id-tokens"]
)
.await
);
}
#[tokio::test]

View File

@@ -449,7 +449,8 @@ mod tests {
#[tokio::test]
async fn test_disk_storage() {
let new_token = |access_token: &str| TokenInfo {
access_token: access_token.to_owned(),
id_token: None,
access_token: Some(access_token.to_owned()),
refresh_token: None,
expires_at: None,
id_token: None,

View File

@@ -3,49 +3,56 @@ use crate::error::{AuthErrorOr, Error};
use time::OffsetDateTime;
use serde::{Deserialize, Serialize};
/// Represents an access token returned by oauth2 servers. All access tokens are
/// Bearer tokens. Other types of tokens are not supported.
/// Represents a token returned by oauth2 servers. All tokens are Bearer tokens. Other types of
/// tokens are not supported.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
pub struct AccessToken {
value: String,
pub struct Token {
id_token: Option<String>,
access_token: Option<String>,
expires_at: Option<OffsetDateTime>,
}
impl AccessToken {
/// A string representation of the access token.
pub fn as_str(&self) -> &str {
&self.value
impl Token {
/// A string representation of the ID token.
pub fn id_token(&self) -> Option<&str> {
self.id_token.as_deref()
}
/// The time the access token will expire, if any.
/// A string representation of the access token.
pub fn access_token(&self) -> Option<&str> {
self.access_token.as_deref()
}
/// The time at which the tokens will expire, if any.
pub fn expiration_time(&self) -> Option<OffsetDateTime> {
self.expires_at
}
/// Determine if the access token is expired.
/// This will report that the token is expired 1 minute prior to the
/// expiration time to ensure that when the token is actually sent to the
/// server it's still valid.
///
/// This will report that the token is expired 1 minute prior to the expiration time to ensure
/// that when the token is actually sent to the server it's still valid.
pub fn is_expired(&self) -> bool {
// Consider the token expired if it's within 1 minute of it's expiration
// time.
// Consider the token expired if it's within 1 minute of it's expiration time.
self.expires_at
.map(|expiration_time| expiration_time - time::Duration::minutes(1) <= OffsetDateTime::now_utc())
.unwrap_or(false)
}
}
impl AsRef<str> for AccessToken {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl From<TokenInfo> for AccessToken {
fn from(value: TokenInfo) -> Self {
AccessToken {
value: value.access_token,
expires_at: value.expires_at,
impl From<TokenInfo> for Token {
fn from(
TokenInfo {
access_token,
id_token,
expires_at,
..
}: TokenInfo,
) -> Self {
Token {
access_token,
id_token,
expires_at,
}
}
}
@@ -53,12 +60,11 @@ impl From<TokenInfo> for AccessToken {
/// Represents a token as returned by OAuth2 servers.
///
/// It is produced by all authentication flows.
/// It authenticates certain operations, and must be refreshed once
/// it reached it's expiry date.
/// It authenticates certain operations, and must be refreshed once it reached it's expiry date.
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct TokenInfo {
/// used when authenticating calls to oauth2 enabled services.
pub access_token: String,
/// used when authorizing calls to oauth2 enabled services.
pub access_token: Option<String>,
/// used to refresh an expired access_token.
pub refresh_token: Option<String>,
/// The time when the token expires.
@@ -74,9 +80,9 @@ impl TokenInfo {
pub(crate) fn from_json(json_data: &[u8]) -> Result<TokenInfo, Error> {
#[derive(Deserialize)]
struct RawToken {
access_token: String,
access_token: Option<String>,
refresh_token: Option<String>,
token_type: String,
token_type: Option<String>,
expires_in: Option<i64>,
id_token: Option<String>,
}
@@ -92,26 +98,29 @@ impl TokenInfo {
id_token,
} = <AuthErrorOr<RawToken>>::deserialize(raw_token)?.into_result()?;
if token_type.to_lowercase().as_str() != "bearer" {
use std::io;
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
r#"unknown token type returned; expected "bearer" found {}"#,
token_type
),
)
.into());
match token_type {
Some(token_ty) if !token_ty.eq_ignore_ascii_case("bearer") => {
use std::io;
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
r#"unknown token type returned; expected "bearer" found {}"#,
token_ty
),
)
.into());
}
_ => (),
}
let expires_at = expires_in
.map(|seconds_from_now| OffsetDateTime::now_utc() + time::Duration::seconds(seconds_from_now));
Ok(TokenInfo {
id_token,
access_token,
refresh_token,
expires_at,
id_token,
})
}

View File

@@ -94,7 +94,7 @@ async fn test_device_success() {
.token(&["https://www.googleapis.com/scope/1"])
.await
.expect("token failed");
assert_eq!("accesstoken", token.as_str());
assert_eq!("accesstoken", token.access_token().expect("should have access token"));
}
#[tokio::test]
@@ -255,7 +255,7 @@ async fn test_installed_interactive_success() {
.token(&["https://googleapis.com/some/scope"])
.await
.expect("failed to get token");
assert_eq!("accesstoken", tok.as_str());
assert_eq!("accesstoken", tok.access_token().expect("should have access token"));
}
#[tokio::test]
@@ -284,7 +284,7 @@ async fn test_installed_redirect_success() {
.token(&["https://googleapis.com/some/scope"])
.await
.expect("failed to get token");
assert_eq!("accesstoken", tok.as_str());
assert_eq!("accesstoken", tok.access_token().expect("should have access token"));
}
#[tokio::test]
@@ -353,7 +353,7 @@ async fn test_service_account_success() {
.token(&["https://www.googleapis.com/auth/pubsub"])
.await
.expect("token failed");
assert!(tok.as_str().contains("ya29.c.ElouBywiys0Ly"));
assert!(tok.access_token().expect("should have access token").contains("ya29.c.ElouBywiys0Ly"));
assert!(OffsetDateTime::now_utc() + time::Duration::seconds(3600) >= tok.expiration_time().unwrap());
}
@@ -404,7 +404,7 @@ async fn test_refresh() {
.token(&["https://googleapis.com/some/scope"])
.await
.expect("failed to get token");
assert_eq!("accesstoken", tok.as_str());
assert_eq!("accesstoken", tok.access_token().expect("should have access token"));
server.expect(
Expectation::matching(all_of![
@@ -425,7 +425,7 @@ async fn test_refresh() {
.token(&["https://googleapis.com/some/scope"])
.await
.expect("failed to get token");
assert_eq!("accesstoken2", tok.as_str());
assert_eq!("accesstoken2", tok.access_token().expect("should have access token"));
server.expect(
Expectation::matching(all_of![
@@ -446,7 +446,7 @@ async fn test_refresh() {
.token(&["https://googleapis.com/some/scope"])
.await
.expect("failed to get token");
assert_eq!("accesstoken3", tok.as_str());
assert_eq!("accesstoken3", tok.access_token().expect("should have access token"));
// Refresh fails, but renewing the token succeeds.
// PR #165
@@ -516,7 +516,7 @@ async fn test_memory_storage() {
.token(&["https://googleapis.com/some/scope"])
.await
.expect("failed to get token");
assert_eq!(token1.as_str(), "accesstoken");
assert_eq!(token1.access_token().expect("should have access token"), "accesstoken");
assert_eq!(token1, token2);
// Create a new authenticator. This authenticator does not share a cache
@@ -542,7 +542,7 @@ async fn test_memory_storage() {
.token(&["https://googleapis.com/some/scope"])
.await
.expect("failed to get token");
assert_eq!(token3.as_str(), "accesstoken2");
assert_eq!(token3.access_token().expect("should have access token"), "accesstoken2");
}
#[tokio::test]
@@ -584,7 +584,7 @@ async fn test_disk_storage() {
.token(&["https://googleapis.com/some/scope"])
.await
.expect("failed to get token");
assert_eq!(token1.as_str(), "accesstoken");
assert_eq!(token1.access_token().expect("should have access token"), "accesstoken");
assert_eq!(token1, token2);
}
@@ -606,7 +606,7 @@ async fn test_disk_storage() {
.token(&["https://googleapis.com/some/scope"])
.await
.expect("failed to get token");
assert_eq!(token1.as_str(), "accesstoken");
assert_eq!(token1.access_token().expect("should have access token"), "accesstoken");
assert_eq!(token1, token2);
}