mirror of
https://github.com/OMGeeky/yup-oauth2.git
synced 2026-02-23 15:50:00 +01:00
Merge branch 'dermesser:master' into master
This commit is contained in:
@@ -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"]
|
||||
|
||||
15
README.md
15
README.md
@@ -1,6 +1,5 @@
|
||||
[](https://travis-ci.org/dermesser/yup-oauth2)
|
||||
[](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)
|
||||
[](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.
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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()) }
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
49
src/types.rs
49
src/types.rs
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user