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]
|
[package]
|
||||||
|
|
||||||
name = "yup-oauth2"
|
name = "yup-oauth2"
|
||||||
version = "8.1.0"
|
version = "8.3.0"
|
||||||
authors = ["Sebastian Thiel <byronimo@gmail.com>", "Lewin Bormann <lbo@spheniscida.de>"]
|
authors = ["Sebastian Thiel <byronimo@gmail.com>", "Lewin Bormann <lbo@spheniscida.de>"]
|
||||||
repository = "https://github.com/dermesser/yup-oauth2"
|
repository = "https://github.com/dermesser/yup-oauth2"
|
||||||
description = "An oauth2 implementation, providing the 'device', 'service account' and 'installed' authorization flows"
|
description = "An oauth2 implementation, providing the 'device', 'service account' and 'installed' authorization flows"
|
||||||
@@ -37,12 +37,12 @@ base64 = "0.13.0"
|
|||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
http = "0.2"
|
http = "0.2"
|
||||||
hyper = { version = "0.14", features = ["client", "server", "tcp", "http2"] }
|
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 }
|
hyper-tls = { version = "0.5.0", optional = true }
|
||||||
itertools = "0.10.0"
|
itertools = "0.10.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
percent-encoding = "2"
|
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 }
|
rustls-pemfile = { version = "1.0.1", optional = true }
|
||||||
seahash = "4"
|
seahash = "4"
|
||||||
serde = {version = "1.0", features = ["derive"]}
|
serde = {version = "1.0", features = ["derive"]}
|
||||||
@@ -57,7 +57,7 @@ httptest = "0.15"
|
|||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
tempfile = "3.1"
|
tempfile = "3.1"
|
||||||
webbrowser = "0.8"
|
webbrowser = "0.8"
|
||||||
hyper-rustls = "0.23"
|
hyper-rustls = "0.24"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["examples/test-installed/", "examples/test-svc-acct/", "examples/test-device/", "examples/test-adc"]
|
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)
|
Status](https://github.com/dermesser/yup-oauth2/actions/workflows/test.yml/badge.svg)](https://github.com/dermesser/yup-oauth2/actions)
|
||||||
[](https://codecov.io/gh/dermesser/yup-oauth2)
|
|
||||||
[](https://crates.io/crates/yup-oauth2)
|
[](https://crates.io/crates/yup-oauth2)
|
||||||
|
|
||||||
**yup-oauth2** is a utility library which implements several OAuth 2.0 flows. It's mainly used by
|
**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
|
* Service account flow: Non-interactive authorization of server-to-server communication based on
|
||||||
public key cryptography. Used for services like Cloud Pubsub, Cloud Storage, ...
|
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
|
## Versions
|
||||||
|
|
||||||
* Version 1.x for Hyper versions below 12
|
* 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)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct DefaultInstalledFlowDelegate;
|
pub struct DefaultInstalledFlowDelegate;
|
||||||
impl InstalledFlowDelegate for 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 {
|
impl InstalledFlow {
|
||||||
/// Create a new InstalledFlow with the provided secret and method.
|
/// 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(
|
pub(crate) fn new(
|
||||||
app_secret: ApplicationSecret,
|
app_secret: ApplicationSecret,
|
||||||
method: InstalledFlowReturnMethod,
|
method: InstalledFlowReturnMethod,
|
||||||
|
|||||||
@@ -93,6 +93,11 @@ pub mod storage;
|
|||||||
|
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
|
pub use hyper;
|
||||||
|
|
||||||
|
#[cfg(feature = "hyper-rustls")]
|
||||||
|
pub use hyper_rustls;
|
||||||
|
|
||||||
#[cfg(feature = "service_account")]
|
#[cfg(feature = "service_account")]
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use crate::authenticator::ServiceAccountAuthenticator;
|
pub use crate::authenticator::ServiceAccountAuthenticator;
|
||||||
|
|||||||
49
src/types.rs
49
src/types.rs
@@ -1,7 +1,7 @@
|
|||||||
use crate::error::{AuthErrorOr, Error};
|
use crate::error::{AuthErrorOr, Error};
|
||||||
|
|
||||||
use time::OffsetDateTime;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
/// Represents a token returned by oauth2 servers. All tokens are Bearer tokens. Other types of
|
/// Represents a token returned by oauth2 servers. All tokens are Bearer tokens. Other types of
|
||||||
/// tokens are not supported.
|
/// tokens are not supported.
|
||||||
@@ -12,7 +12,6 @@ pub struct AccessToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AccessToken {
|
impl AccessToken {
|
||||||
|
|
||||||
/// A string representation of the access token.
|
/// A string representation of the access token.
|
||||||
pub fn token(&self) -> Option<&str> {
|
pub fn token(&self) -> Option<&str> {
|
||||||
self.access_token.as_deref()
|
self.access_token.as_deref()
|
||||||
@@ -30,7 +29,9 @@ impl AccessToken {
|
|||||||
pub fn is_expired(&self) -> bool {
|
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
|
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)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,8 +107,19 @@ impl TokenInfo {
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let expires_at = expires_in
|
let expires_at = match expires_in {
|
||||||
.map(|seconds_from_now| OffsetDateTime::now_utc() + time::Duration::seconds(seconds_from_now));
|
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 {
|
Ok(TokenInfo {
|
||||||
id_token,
|
id_token,
|
||||||
@@ -120,7 +132,9 @@ impl TokenInfo {
|
|||||||
/// Returns true if we are expired.
|
/// Returns true if we are expired.
|
||||||
pub fn is_expired(&self) -> bool {
|
pub fn is_expired(&self) -> bool {
|
||||||
self.expires_at
|
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)
|
.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