Merge branch 'dermesser:master' into master

This commit is contained in:
OMGeeky
2023-06-04 13:00:46 +02:00
committed by GitHub
9 changed files with 70 additions and 136 deletions

View File

@@ -1,7 +1,7 @@
[package]
name = "yup-oauth2"
version = "8.1.0"
version = "8.3.0"
authors = ["Sebastian Thiel <byronimo@gmail.com>", "Lewin Bormann <lbo@spheniscida.de>"]
repository = "https://github.com/dermesser/yup-oauth2"
description = "An oauth2 implementation, providing the 'device', 'service account' and 'installed' authorization flows"
@@ -37,12 +37,12 @@ base64 = "0.13.0"
futures = "0.3"
http = "0.2"
hyper = { version = "0.14", features = ["client", "server", "tcp", "http2"] }
hyper-rustls = { version = "0.23", optional = true, features = ["http2"] }
hyper-rustls = { version = "0.24", optional = true, features = ["http2"] }
hyper-tls = { version = "0.5.0", optional = true }
itertools = "0.10.0"
log = "0.4"
percent-encoding = "2"
rustls = { version = "0.20.4", optional = true }
rustls = { version = "0.21.0", optional = true }
rustls-pemfile = { version = "1.0.1", optional = true }
seahash = "4"
serde = {version = "1.0", features = ["derive"]}
@@ -57,7 +57,7 @@ httptest = "0.15"
env_logger = "0.10"
tempfile = "3.1"
webbrowser = "0.8"
hyper-rustls = "0.23"
hyper-rustls = "0.24"
[workspace]
members = ["examples/test-installed/", "examples/test-svc-acct/", "examples/test-device/", "examples/test-adc"]

View File

@@ -1,6 +1,5 @@
[![Build
Status](https://travis-ci.org/dermesser/yup-oauth2.svg)](https://travis-ci.org/dermesser/yup-oauth2)
[![codecov](https://codecov.io/gh/dermesser/yup-oauth2/branch/master/graph/badge.svg)](https://codecov.io/gh/dermesser/yup-oauth2)
Status](https://github.com/dermesser/yup-oauth2/actions/workflows/test.yml/badge.svg)](https://github.com/dermesser/yup-oauth2/actions)
[![crates.io](https://img.shields.io/crates/v/yup-oauth2.svg)](https://crates.io/crates/yup-oauth2)
**yup-oauth2** is a utility library which implements several OAuth 2.0 flows. It's mainly used by
@@ -27,18 +26,6 @@ doesn't, please let us know and/or contribute a fix!
* Service account flow: Non-interactive authorization of server-to-server communication based on
public key cryptography. Used for services like Cloud Pubsub, Cloud Storage, ...
### Usage
Please have a look at the [API landing page][API-docs] for all the examples you will ever need.
A simple commandline program which authenticates any scope and prints token information can be found
in [the examples directory][examples].
The video below shows the *auth* example in action. It's meant to be used as utility to record all
server communication and improve protocol compliance.
![usage][auth-usage]
## Versions
* Version 1.x for Hyper versions below 12

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

View File

@@ -1,112 +0,0 @@
use chrono::Local;
use getopts::{Fail, HasArg, Occur, Options};
use hyper_tls::HttpsConnector;
use std::default::Default;
use std::env;
use std::thread::sleep;
use std::time::Duration;
use yup_oauth2::{self as oauth2, GetToken};
fn usage(program: &str, opts: &Options, err: Option<Fail>) -> ! {
if err.is_some() {
println!("{}", err.unwrap());
std::process::exit(1);
}
println!("{}", opts.short_usage(program) + " SCOPE [SCOPE ...]");
println!(
"{}",
opts.usage(
"A program to authenticate against oauthv2 services.\n\
See https://developers.google.com/youtube/registering_an_application\n\
and https://developers.google.com/youtube/v3/guides/authentication#devices"
)
);
std::process::exit(0);
}
fn main() {
let args: Vec<String> = env::args().collect();
let prog = args[0].clone();
let mut opts = Options::new();
opts.opt(
"c",
"id",
"oauthv2 ID of your application",
"CLIENT_ID",
HasArg::Yes,
Occur::Req,
)
.opt(
"s",
"secret",
"oauthv2 secret of your application",
"CLIENT_SECRET",
HasArg::Yes,
Occur::Req,
);
let m = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(e) => {
usage(&prog, &opts, Some(e));
}
};
if m.free.len() == 0 {
let msg = Fail::ArgumentMissing(
"you must provide one or more authorization scopes as free options".to_string(),
);
usage(&prog, &opts, Some(msg));
}
let secret = oauth2::ApplicationSecret {
client_id: m.opt_str("c").unwrap(),
client_secret: m.opt_str("s").unwrap(),
token_uri: Default::default(),
auth_uri: Default::default(),
redirect_uris: Default::default(),
..Default::default()
};
println!("THIS PROGRAM PRINTS ALL COMMUNICATION TO STDERR !!!");
struct StdoutHandler;
impl oauth2::AuthenticatorDelegate for StdoutHandler {
fn present_user_code(&mut self, pi: &oauth2::PollInformation) {
println!(
"Please enter '{}' at {} and authenticate the application for the\n\
given scopes. This is not a test !\n\
You have time until {} to do that.
Do not terminate the program until you deny or grant access !",
pi.user_code,
pi.verification_url,
pi.expires_at.with_timezone(&Local)
);
let delay = Duration::from_secs(5);
println!("Browser opens automatically in {:?} seconds", delay);
sleep(delay);
open::that(&pi.verification_url).ok();
println!("DONE - waiting for authorization ...");
}
}
let https = HttpsConnector::new(4).unwrap();
let client = hyper::Client::builder().build(https);
match oauth2::Authenticator::new(&secret, StdoutHandler, client, oauth2::NullStorage, None)
.token(&m.free)
{
Ok(t) => {
println!("Authentication granted !");
println!("You should store the following information for use, or revoke it.");
println!("All dates are given in UTC.");
println!("{:?}", t);
}
Err(err) => {
println!("Access token wasn't obtained: {}", err);
std::process::exit(10);
}
};
}

View File

@@ -156,3 +156,11 @@ impl DeviceFlowDelegate for DefaultDeviceFlowDelegate {}
#[derive(Copy, Clone)]
pub struct DefaultInstalledFlowDelegate;
impl InstalledFlowDelegate for DefaultInstalledFlowDelegate {}
/// The default installed-flow delegate (i.e.: show URL on stdout). Use this to specify
/// a custom redirect URL.
#[derive(Clone)]
pub struct DefaultInstalledFlowDelegateWithRedirectURI(pub String);
impl InstalledFlowDelegate for DefaultInstalledFlowDelegateWithRedirectURI {
fn redirect_uri(&self) -> Option<&str> { Some(self.0.as_str()) }
}

View File

@@ -97,6 +97,15 @@ pub struct InstalledFlow {
impl InstalledFlow {
/// Create a new InstalledFlow with the provided secret and method.
///
/// In order to specify the redirect URL to use (in the case of `HTTPRedirect` or
/// `HTTPPortRedirect` as method), either implement the `InstalledFlowDelegate` trait, or
/// use the `DefaultInstalledFlowDelegateWithRedirectURI`, which presents the URL on stdout.
/// The redirect URL to use is configured with the OAuth provider, and possible options are
/// given in the `ApplicationSecret.redirect_uris` field.
///
/// The `InstalledFlowDelegate` implementation should be assigned to the `flow_delegate` field
/// of the `InstalledFlow` struct.
pub(crate) fn new(
app_secret: ApplicationSecret,
method: InstalledFlowReturnMethod,

View File

@@ -93,6 +93,11 @@ pub mod storage;
mod types;
pub use hyper;
#[cfg(feature = "hyper-rustls")]
pub use hyper_rustls;
#[cfg(feature = "service_account")]
#[doc(inline)]
pub use crate::authenticator::ServiceAccountAuthenticator;

View File

@@ -1,7 +1,7 @@
use crate::error::{AuthErrorOr, Error};
use time::OffsetDateTime;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
/// Represents a token returned by oauth2 servers. All tokens are Bearer tokens. Other types of
/// tokens are not supported.
@@ -12,7 +12,6 @@ pub struct AccessToken {
}
impl AccessToken {
/// A string representation of the access token.
pub fn token(&self) -> Option<&str> {
self.access_token.as_deref()
@@ -30,7 +29,9 @@ impl AccessToken {
pub fn is_expired(&self) -> bool {
// 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())
.map(|expiration_time| {
expiration_time - time::Duration::minutes(1) <= OffsetDateTime::now_utc()
})
.unwrap_or(false)
}
}
@@ -106,8 +107,19 @@ impl TokenInfo {
_ => (),
}
let expires_at = expires_in
.map(|seconds_from_now| OffsetDateTime::now_utc() + time::Duration::seconds(seconds_from_now));
let expires_at = match expires_in {
Some(seconds_from_now) => {
Some(OffsetDateTime::now_utc() + time::Duration::seconds(seconds_from_now))
}
None if id_token.is_some() && access_token.is_none() => {
// If the response contains only an ID token, an expiration date may not be
// returned. According to the docs, the tokens are always valid for 1 hour.
//
// https://cloud.google.com/iam/docs/create-short-lived-credentials-direct#sa-credentials-oidc
Some(OffsetDateTime::now_utc() + time::Duration::HOUR)
}
None => None,
};
Ok(TokenInfo {
id_token,
@@ -120,7 +132,9 @@ impl TokenInfo {
/// Returns true if we are expired.
pub fn is_expired(&self) -> bool {
self.expires_at
.map(|expiration_time| expiration_time - time::Duration::minutes(1) <= OffsetDateTime::now_utc())
.map(|expiration_time| {
expiration_time - time::Duration::minutes(1) <= OffsetDateTime::now_utc()
})
.unwrap_or(false)
}
}
@@ -183,4 +197,27 @@ pub mod tests {
),
}
}
#[test]
fn default_expiry_for_id_token_only() {
// If only an ID token is present, set a default expiration date
let json = r#"{"id_token": "id"}"#;
let token = TokenInfo::from_json(json.as_bytes()).unwrap();
assert_eq!(token.id_token, Some("id".to_owned()));
let expiry = token.expires_at.unwrap();
assert!(expiry <= time::OffsetDateTime::now_utc() + time::Duration::HOUR);
}
#[test]
fn no_default_expiry_for_access_token() {
// Don't set a default expiration date if an access token is returned
let json = r#"{"access_token": "access", "id_token": "id"}"#;
let token = TokenInfo::from_json(json.as_bytes()).unwrap();
assert_eq!(token.access_token, Some("access".to_owned()));
assert_eq!(token.id_token, Some("id".to_owned()));
assert_eq!(token.expires_at, None);
}
}