RPM build fix (reverted CI changes which will need to be un-reverted or made conditional) and vendor Rust dependencies to make builds much faster in any CI system.

This commit is contained in:
Adam Ierymenko
2022-06-08 07:32:16 -04:00
parent 373ca30269
commit d5ca4e5f52
12611 changed files with 2898014 additions and 284 deletions

View File

@@ -0,0 +1 @@
{"files":{"Cargo-1.45.lock":"95b0618b70bb9fe15491377f444a6821f36cfd736687e6a3d14b1cf4183ced81","Cargo.lock":"9890c50420c46895bdace0deffdf5284b293db439082aa7a770ef11ace6530f6","Cargo.toml":"843dc7d68837dcba0b18a83d36b82a3c9d209142fd070f0b7896a1756331217d","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","README.md":"2e64068e32477e151edab93d0be1f2f1a613d3ba2b55eb6faa3a8ad021076b27","examples/github.rs":"d22f3ad64f6e9612f0da2f549a4abf64c89c14d53141e99599c93624e7ca9c78","examples/github_async.rs":"90b95f863dff20d5bad21e3e31ebfbd8444c99e6fd4faa1c93a14932ae3a23d0","examples/google.rs":"a15bcea659efbf4d9bb0b49f459bfd2a2befdf9694843d8cfbd179edde9ec297","examples/google_devicecode.rs":"f7203544caa226211fc2fd8d4f90daadde6e866733a26129afc5f22ccebcfdf9","examples/letterboxd.rs":"82acde7189ee8248f46bbd189b45651ee761d3e10e37185b452fe48d8a35651a","examples/msgraph.rs":"913262bd5e3bd31e278b8868009d61bcbb4165df352b75088e85c3f511bf1e6d","examples/wunderlist.rs":"77310a5da4a0cb4017322aea81002293fb078c2b48409d6a2f495266da342b29","src/basic.rs":"1e80b1cacbbe31b78ee3e51c9680be0def1e802099ae4f96e4c07b3adcc43fd0","src/curl.rs":"6288722584b8b8c65ce493b79b976adf426e115eb1467fbc9dddecbfe5e2c5de","src/devicecode.rs":"70d3eeadd6dd54bd84d835222e988cde226cf4b6b08b041bfc0c4b7abcc05cc4","src/helpers.rs":"2f2c652d440ec115f7ce5c92dbf19469ddfef1483c0786c0b712d343bd82f9b9","src/lib.rs":"b27bb95f321c246d63699f755f0d683f5553fbdb27ba7bfffe339a65ad46d264","src/reqwest.rs":"70028d76159bd6c13a721ade9686665d18c0e479fccead4b56b810e132592dc6","src/revocation.rs":"af0406fce91ee2379d10812e8350b719af4a0e2f158552691f14398b3db657ce","src/tests.rs":"5e3b0392a8bb4b35d3a32cb2923998c6448ecd2e7a456b99af4c03a53a0b09be","src/types.rs":"ca2bb08960cb6a63ba9a3811349e416af5bc560440d9dd4a035aa149366cdb98","src/ureq.rs":"c426d2c24aabcde4827dba6b0834769f8cc86e95d05067b889bc30240d0306da"},"package":"c3bd7d544f02ae0fa9e06137962703d043870d7ad6e6d44786d6a5f20679b2c9"}

1843
zeroidc/vendor/oauth2/Cargo-1.45.lock vendored Normal file

File diff suppressed because it is too large Load Diff

1730
zeroidc/vendor/oauth2/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

115
zeroidc/vendor/oauth2/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,115 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
name = "oauth2"
version = "4.2.0"
authors = [
"Alex Crichton <alex@alexcrichton.com>",
"Florin Lipan <florinlipan@gmail.com>",
"David A. Ramos <ramos@cs.stanford.edu>",
]
description = "An extensible, strongly-typed implementation of OAuth2"
readme = "README.md"
license = "MIT/Apache-2.0"
repository = "https://github.com/ramosbugs/oauth2-rs"
[package.metadata.docs.rs]
all-features = true
[dependencies.base64]
version = "0.13"
[dependencies.chrono]
version = "0.4"
features = [
"clock",
"serde",
"std",
]
default-features = false
[dependencies.http]
version = "0.2"
[dependencies.rand]
version = "0.8"
[dependencies.reqwest]
version = "0.11"
features = ["blocking"]
optional = true
default-features = false
[dependencies.serde]
version = "1.0"
features = ["derive"]
[dependencies.serde_json]
version = "1.0"
[dependencies.serde_path_to_error]
version = "0.1"
[dependencies.sha2]
version = "0.10"
[dependencies.thiserror]
version = "1.0"
[dependencies.ureq]
version = "2"
optional = true
[dependencies.url]
version = "2.1"
features = ["serde"]
[dev-dependencies.anyhow]
version = "1.0"
[dev-dependencies.async-std]
version = "1.6.3"
[dev-dependencies.hex]
version = "0.4"
[dev-dependencies.hmac]
version = "0.12"
[dev-dependencies.tokio]
version = "1.0"
features = ["full"]
[dev-dependencies.uuid]
version = "0.8"
features = ["v4"]
[features]
default = [
"reqwest",
"rustls-tls",
]
native-tls = ["reqwest/native-tls"]
pkce-plain = []
rustls-tls = ["reqwest/rustls-tls"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.curl]
version = "0.4.0"
optional = true
[target."cfg(target_arch = \"wasm32\")".dependencies.getrandom]
version = "0.2"
features = ["js"]
[badges.maintenance]
status = "actively-developed"

201
zeroidc/vendor/oauth2/LICENSE-APACHE vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

25
zeroidc/vendor/oauth2/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,25 @@
Copyright (c) 2014 Alex Crichton
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

9
zeroidc/vendor/oauth2/README.md vendored Normal file
View File

@@ -0,0 +1,9 @@
# OAuth2
<a href="https://crates.io/crates/oauth2"><img src="https://img.shields.io/crates/v/oauth2.svg"></a>
[![Build Status](https://github.com/ramosbugs/oauth2-rs/actions/workflows/main.yml/badge.svg)](https://github.com/ramosbugs/oauth2-rs/actions/workflows/main.yml)
An extensible, strongly-typed implementation of OAuth2
([RFC 6749](https://tools.ietf.org/html/rfc6749)).
Documentation is available on [docs.rs](https://docs.rs/oauth2). Release notes are available on [GitHub](https://github.com/ramosbugs/oauth2-rs/releases).

147
zeroidc/vendor/oauth2/examples/github.rs vendored Normal file
View File

@@ -0,0 +1,147 @@
//!
//! This example showcases the Github OAuth2 process for requesting access to the user's public repos and
//! email address.
//!
//! Before running it, you'll need to generate your own Github OAuth2 credentials.
//!
//! In order to run the example call:
//!
//! ```sh
//! GITHUB_CLIENT_ID=xxx GITHUB_CLIENT_SECRET=yyy cargo run --example github
//! ```
//!
//! ...and follow the instructions.
//!
use oauth2::basic::BasicClient;
// Alternatively, this can be `oauth2::curl::http_client` or a custom client.
use oauth2::reqwest::http_client;
use oauth2::{
AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, RedirectUrl, Scope,
TokenResponse, TokenUrl,
};
use std::env;
use std::io::{BufRead, BufReader, Write};
use std::net::TcpListener;
use url::Url;
fn main() {
let github_client_id = ClientId::new(
env::var("GITHUB_CLIENT_ID").expect("Missing the GITHUB_CLIENT_ID environment variable."),
);
let github_client_secret = ClientSecret::new(
env::var("GITHUB_CLIENT_SECRET")
.expect("Missing the GITHUB_CLIENT_SECRET environment variable."),
);
let auth_url = AuthUrl::new("https://github.com/login/oauth/authorize".to_string())
.expect("Invalid authorization endpoint URL");
let token_url = TokenUrl::new("https://github.com/login/oauth/access_token".to_string())
.expect("Invalid token endpoint URL");
// Set up the config for the Github OAuth2 process.
let client = BasicClient::new(
github_client_id,
Some(github_client_secret),
auth_url,
Some(token_url),
)
// This example will be running its own server at localhost:8080.
// See below for the server implementation.
.set_redirect_uri(
RedirectUrl::new("http://localhost:8080".to_string()).expect("Invalid redirect URL"),
);
// Generate the authorization URL to which we'll redirect the user.
let (authorize_url, csrf_state) = client
.authorize_url(CsrfToken::new_random)
// This example is requesting access to the user's public repos and email.
.add_scope(Scope::new("public_repo".to_string()))
.add_scope(Scope::new("user:email".to_string()))
.url();
println!(
"Open this URL in your browser:\n{}\n",
authorize_url.to_string()
);
// A very naive implementation of the redirect server.
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
for stream in listener.incoming() {
if let Ok(mut stream) = stream {
let code;
let state;
{
let mut reader = BufReader::new(&stream);
let mut request_line = String::new();
reader.read_line(&mut request_line).unwrap();
let redirect_url = request_line.split_whitespace().nth(1).unwrap();
let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();
let code_pair = url
.query_pairs()
.find(|pair| {
let &(ref key, _) = pair;
key == "code"
})
.unwrap();
let (_, value) = code_pair;
code = AuthorizationCode::new(value.into_owned());
let state_pair = url
.query_pairs()
.find(|pair| {
let &(ref key, _) = pair;
key == "state"
})
.unwrap();
let (_, value) = state_pair;
state = CsrfToken::new(value.into_owned());
}
let message = "Go back to your terminal :)";
let response = format!(
"HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}",
message.len(),
message
);
stream.write_all(response.as_bytes()).unwrap();
println!("Github returned the following code:\n{}\n", code.secret());
println!(
"Github returned the following state:\n{} (expected `{}`)\n",
state.secret(),
csrf_state.secret()
);
// Exchange the code with a token.
let token_res = client.exchange_code(code).request(http_client);
println!("Github returned the following token:\n{:?}\n", token_res);
if let Ok(token) = token_res {
// NB: Github returns a single comma-separated "scope" parameter instead of multiple
// space-separated scopes. Github-specific clients can parse this scope into
// multiple scopes by splitting at the commas. Note that it's not safe for the
// library to do this by default because RFC 6749 allows scopes to contain commas.
let scopes = if let Some(scopes_vec) = token.scopes() {
scopes_vec
.iter()
.map(|comma_separated| comma_separated.split(','))
.flatten()
.collect::<Vec<_>>()
} else {
Vec::new()
};
println!("Github returned the following scopes:\n{:?}\n", scopes);
}
// The server will terminate itself after collecting the first code.
break;
}
}
}

View File

@@ -0,0 +1,151 @@
//!
//! This example showcases the Github OAuth2 process for requesting access to the user's public repos and
//! email address.
//!
//! Before running it, you'll need to generate your own Github OAuth2 credentials.
//!
//! In order to run the example call:
//!
//! ```sh
//! GITHUB_CLIENT_ID=xxx GITHUB_CLIENT_SECRET=yyy cargo run --example github
//! ```
//!
//! ...and follow the instructions.
//!
use oauth2::basic::BasicClient;
// Alternatively, this can be `oauth2::curl::http_client` or a custom client.
use oauth2::reqwest::async_http_client;
use oauth2::{
AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, RedirectUrl, Scope,
TokenResponse, TokenUrl,
};
use std::env;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::TcpListener;
use url::Url;
#[tokio::main]
async fn main() {
let github_client_id = ClientId::new(
env::var("GITHUB_CLIENT_ID").expect("Missing the GITHUB_CLIENT_ID environment variable."),
);
let github_client_secret = ClientSecret::new(
env::var("GITHUB_CLIENT_SECRET")
.expect("Missing the GITHUB_CLIENT_SECRET environment variable."),
);
let auth_url = AuthUrl::new("https://github.com/login/oauth/authorize".to_string())
.expect("Invalid authorization endpoint URL");
let token_url = TokenUrl::new("https://github.com/login/oauth/access_token".to_string())
.expect("Invalid token endpoint URL");
// Set up the config for the Github OAuth2 process.
let client = BasicClient::new(
github_client_id,
Some(github_client_secret),
auth_url,
Some(token_url),
)
// This example will be running its own server at localhost:8080.
// See below for the server implementation.
.set_redirect_uri(
RedirectUrl::new("http://localhost:8080".to_string()).expect("Invalid redirect URL"),
);
// Generate the authorization URL to which we'll redirect the user.
let (authorize_url, csrf_state) = client
.authorize_url(CsrfToken::new_random)
// This example is requesting access to the user's public repos and email.
.add_scope(Scope::new("public_repo".to_string()))
.add_scope(Scope::new("user:email".to_string()))
.url();
println!(
"Open this URL in your browser:\n{}\n",
authorize_url.to_string()
);
// A very naive implementation of the redirect server.
let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap();
loop {
if let Ok((mut stream, _)) = listener.accept().await {
let code;
let state;
{
let mut reader = BufReader::new(&mut stream);
let mut request_line = String::new();
reader.read_line(&mut request_line).await.unwrap();
let redirect_url = request_line.split_whitespace().nth(1).unwrap();
let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();
let code_pair = url
.query_pairs()
.find(|pair| {
let &(ref key, _) = pair;
key == "code"
})
.unwrap();
let (_, value) = code_pair;
code = AuthorizationCode::new(value.into_owned());
let state_pair = url
.query_pairs()
.find(|pair| {
let &(ref key, _) = pair;
key == "state"
})
.unwrap();
let (_, value) = state_pair;
state = CsrfToken::new(value.into_owned());
}
let message = "Go back to your terminal :)";
let response = format!(
"HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}",
message.len(),
message
);
stream.write_all(response.as_bytes()).await.unwrap();
println!("Github returned the following code:\n{}\n", code.secret());
println!(
"Github returned the following state:\n{} (expected `{}`)\n",
state.secret(),
csrf_state.secret()
);
// Exchange the code with a token.
let token_res = client
.exchange_code(code)
.request_async(async_http_client)
.await;
println!("Github returned the following token:\n{:?}\n", token_res);
if let Ok(token) = token_res {
// NB: Github returns a single comma-separated "scope" parameter instead of multiple
// space-separated scopes. Github-specific clients can parse this scope into
// multiple scopes by splitting at the commas. Note that it's not safe for the
// library to do this by default because RFC 6749 allows scopes to contain commas.
let scopes = if let Some(scopes_vec) = token.scopes() {
scopes_vec
.iter()
.map(|comma_separated| comma_separated.split(','))
.flatten()
.collect::<Vec<_>>()
} else {
Vec::new()
};
println!("Github returned the following scopes:\n{:?}\n", scopes);
}
// The server will terminate itself after collecting the first code.
break;
}
}
}

162
zeroidc/vendor/oauth2/examples/google.rs vendored Normal file
View File

@@ -0,0 +1,162 @@
//!
//! This example showcases the Google OAuth2 process for requesting access to the Google Calendar features
//! and the user's profile.
//!
//! Before running it, you'll need to generate your own Google OAuth2 credentials.
//!
//! In order to run the example call:
//!
//! ```sh
//! GOOGLE_CLIENT_ID=xxx GOOGLE_CLIENT_SECRET=yyy cargo run --example google
//! ```
//!
//! ...and follow the instructions.
//!
use oauth2::{basic::BasicClient, revocation::StandardRevocableToken, TokenResponse};
// Alternatively, this can be oauth2::curl::http_client or a custom.
use oauth2::reqwest::http_client;
use oauth2::{
AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, RedirectUrl,
RevocationUrl, Scope, TokenUrl,
};
use std::env;
use std::io::{BufRead, BufReader, Write};
use std::net::TcpListener;
use url::Url;
fn main() {
let google_client_id = ClientId::new(
env::var("GOOGLE_CLIENT_ID").expect("Missing the GOOGLE_CLIENT_ID environment variable."),
);
let google_client_secret = ClientSecret::new(
env::var("GOOGLE_CLIENT_SECRET")
.expect("Missing the GOOGLE_CLIENT_SECRET environment variable."),
);
let auth_url = AuthUrl::new("https://accounts.google.com/o/oauth2/v2/auth".to_string())
.expect("Invalid authorization endpoint URL");
let token_url = TokenUrl::new("https://www.googleapis.com/oauth2/v3/token".to_string())
.expect("Invalid token endpoint URL");
// Set up the config for the Google OAuth2 process.
let client = BasicClient::new(
google_client_id,
Some(google_client_secret),
auth_url,
Some(token_url),
)
// This example will be running its own server at localhost:8080.
// See below for the server implementation.
.set_redirect_uri(
RedirectUrl::new("http://localhost:8080".to_string()).expect("Invalid redirect URL"),
)
// Google supports OAuth 2.0 Token Revocation (RFC-7009)
.set_revocation_uri(
RevocationUrl::new("https://oauth2.googleapis.com/revoke".to_string())
.expect("Invalid revocation endpoint URL"),
);
// Google supports Proof Key for Code Exchange (PKCE - https://oauth.net/2/pkce/).
// Create a PKCE code verifier and SHA-256 encode it as a code challenge.
let (pkce_code_challenge, pkce_code_verifier) = PkceCodeChallenge::new_random_sha256();
// Generate the authorization URL to which we'll redirect the user.
let (authorize_url, csrf_state) = client
.authorize_url(CsrfToken::new_random)
// This example is requesting access to the "calendar" features and the user's profile.
.add_scope(Scope::new(
"https://www.googleapis.com/auth/calendar".to_string(),
))
.add_scope(Scope::new(
"https://www.googleapis.com/auth/plus.me".to_string(),
))
.set_pkce_challenge(pkce_code_challenge)
.url();
println!(
"Open this URL in your browser:\n{}\n",
authorize_url.to_string()
);
// A very naive implementation of the redirect server.
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
for stream in listener.incoming() {
if let Ok(mut stream) = stream {
let code;
let state;
{
let mut reader = BufReader::new(&stream);
let mut request_line = String::new();
reader.read_line(&mut request_line).unwrap();
let redirect_url = request_line.split_whitespace().nth(1).unwrap();
let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();
let code_pair = url
.query_pairs()
.find(|pair| {
let &(ref key, _) = pair;
key == "code"
})
.unwrap();
let (_, value) = code_pair;
code = AuthorizationCode::new(value.into_owned());
let state_pair = url
.query_pairs()
.find(|pair| {
let &(ref key, _) = pair;
key == "state"
})
.unwrap();
let (_, value) = state_pair;
state = CsrfToken::new(value.into_owned());
}
let message = "Go back to your terminal :)";
let response = format!(
"HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}",
message.len(),
message
);
stream.write_all(response.as_bytes()).unwrap();
println!("Google returned the following code:\n{}\n", code.secret());
println!(
"Google returned the following state:\n{} (expected `{}`)\n",
state.secret(),
csrf_state.secret()
);
// Exchange the code with a token.
let token_response = client
.exchange_code(code)
.set_pkce_verifier(pkce_code_verifier)
.request(http_client);
println!(
"Google returned the following token:\n{:?}\n",
token_response
);
// Revoke the obtained token
let token_response = token_response.unwrap();
let token_to_revoke: StandardRevocableToken = match token_response.refresh_token() {
Some(token) => token.into(),
None => token_response.access_token().into(),
};
client
.revoke_token(token_to_revoke)
.unwrap()
.request(http_client)
.expect("Failed to revoke token");
// The server will terminate itself after revoking the token.
break;
}
}
}

View File

@@ -0,0 +1,82 @@
//!
//! This example showcases the Google OAuth2 process for requesting access to the Google Calendar features
//! and the user's profile.
//!
//! Before running it, you'll need to generate your own Google OAuth2 credentials.
//!
//! In order to run the example call:
//!
//! ```sh
//! GOOGLE_CLIENT_ID=xxx GOOGLE_CLIENT_SECRET=yyy cargo run --example google
//! ```
//!
//! ...and follow the instructions.
//!
use oauth2::basic::BasicClient;
// Alternatively, this can be oauth2::curl::http_client or a custom.
use oauth2::devicecode::{DeviceAuthorizationResponse, ExtraDeviceAuthorizationFields};
use oauth2::reqwest::http_client;
use oauth2::{AuthType, AuthUrl, ClientId, ClientSecret, DeviceAuthorizationUrl, Scope, TokenUrl};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::env;
#[derive(Debug, Serialize, Deserialize)]
struct StoringFields(HashMap<String, serde_json::Value>);
impl ExtraDeviceAuthorizationFields for StoringFields {}
type StoringDeviceAuthorizationResponse = DeviceAuthorizationResponse<StoringFields>;
fn main() {
let google_client_id = ClientId::new(
env::var("GOOGLE_CLIENT_ID").expect("Missing the GOOGLE_CLIENT_ID environment variable."),
);
let google_client_secret = ClientSecret::new(
env::var("GOOGLE_CLIENT_SECRET")
.expect("Missing the GOOGLE_CLIENT_SECRET environment variable."),
);
let auth_url = AuthUrl::new("https://accounts.google.com/o/oauth2/v2/auth".to_string())
.expect("Invalid authorization endpoint URL");
let token_url = TokenUrl::new("https://www.googleapis.com/oauth2/v3/token".to_string())
.expect("Invalid token endpoint URL");
let device_auth_url =
DeviceAuthorizationUrl::new("https://oauth2.googleapis.com/device/code".to_string())
.expect("Invalid device authorization endpoint URL");
// Set up the config for the Google OAuth2 process.
//
// Google's OAuth endpoint expects the client_id to be in the request body,
// so ensure that option is set.
let device_client = BasicClient::new(
google_client_id,
Some(google_client_secret),
auth_url,
Some(token_url),
)
.set_device_authorization_url(device_auth_url)
.set_auth_type(AuthType::RequestBody);
// Request the set of codes from the Device Authorization endpoint.
let details: StoringDeviceAuthorizationResponse = device_client
.exchange_device_code()
.unwrap()
.add_scope(Scope::new("profile".to_string()))
.request(http_client)
.expect("Failed to request codes from device auth endpoint");
// Display the URL and user-code.
println!(
"Open this URL in your browser:\n{}\nand enter the code: {}",
details.verification_uri().to_string(),
details.user_code().secret().to_string()
);
// Now poll for the token
let token = device_client
.exchange_device_access_token(&details)
.request(http_client, std::thread::sleep, None)
.expect("Failed to get token");
println!("Google returned the following token:\n{:?}\n", token);
}

View File

@@ -0,0 +1,133 @@
//!
//! This example showcases the Letterboxd OAuth2 process for requesting access
//! to the API features restricted by authentication. Letterboxd requires all
//! requests being signed as described in http://api-docs.letterboxd.com/#signing.
//! So this serves as an example how to implement a custom client, which signs
//! requests and appends the signature to the url query.
//!
//! Before running it, you'll need to get access to the API.
//!
//! In order to run the example call:
//!
//! ```sh
//! LETTERBOXD_CLIENT_ID=xxx LETTERBOXD_CLIENT_SECRET=yyy LETTERBOXD_USERNAME=www LETTERBOXD_PASSWORD=zzz cargo run --example letterboxd
//! ```
use hex::ToHex;
use hmac::{Hmac, Mac};
use oauth2::{
basic::BasicClient, AuthType, AuthUrl, ClientId, ClientSecret, HttpRequest, HttpResponse,
ResourceOwnerPassword, ResourceOwnerUsername, TokenUrl,
};
use sha2::Sha256;
use url::Url;
use std::env;
use std::time;
fn main() -> Result<(), anyhow::Error> {
// a.k.a api key in Letterboxd API documentation
let letterboxd_client_id = ClientId::new(
env::var("LETTERBOXD_CLIENT_ID")
.expect("Missing the LETTERBOXD_CLIENT_ID environment variable."),
);
// a.k.a api secret in Letterboxd API documentation
let letterboxd_client_secret = ClientSecret::new(
env::var("LETTERBOXD_CLIENT_SECRET")
.expect("Missing the LETTERBOXD_CLIENT_SECRET environment variable."),
);
// Letterboxd uses the Resource Owner flow and does not have an auth url
let auth_url = AuthUrl::new("https://api.letterboxd.com/api/v0/auth/404".to_string())?;
let token_url = TokenUrl::new("https://api.letterboxd.com/api/v0/auth/token".to_string())?;
// Set up the config for the Letterboxd OAuth2 process.
let client = BasicClient::new(
letterboxd_client_id.clone(),
Some(letterboxd_client_secret.clone()),
auth_url,
Some(token_url),
);
// Resource Owner flow uses username and password for authentication
let letterboxd_username = ResourceOwnerUsername::new(
env::var("LETTERBOXD_USERNAME")
.expect("Missing the LETTERBOXD_USERNAME environment variable."),
);
let letterboxd_password = ResourceOwnerPassword::new(
env::var("LETTERBOXD_PASSWORD")
.expect("Missing the LETTERBOXD_PASSWORD environment variable."),
);
// All API requests must be signed as described at http://api-docs.letterboxd.com/#signing;
// for that, we use a custom http client.
let http_client = SigningHttpClient::new(letterboxd_client_id, letterboxd_client_secret);
let token_result = client
.set_auth_type(AuthType::RequestBody)
.exchange_password(&letterboxd_username, &letterboxd_password)
.request(|request| http_client.execute(request))?;
println!("{:?}", token_result);
Ok(())
}
/// Custom HTTP client which signs requests.
///
/// See http://api-docs.letterboxd.com/#signing.
#[derive(Debug, Clone)]
struct SigningHttpClient {
client_id: ClientId,
client_secret: ClientSecret,
}
impl SigningHttpClient {
fn new(client_id: ClientId, client_secret: ClientSecret) -> Self {
Self {
client_id,
client_secret,
}
}
/// Signs the request before calling `oauth2::reqwest::http_client`.
fn execute(&self, mut request: HttpRequest) -> Result<HttpResponse, impl std::error::Error> {
let signed_url = self.sign_url(request.url, &request.method, &request.body);
request.url = signed_url;
oauth2::reqwest::http_client(request)
}
/// Signs the request based on a random and unique nonce, timestamp, and
/// client id and secret.
///
/// The client id, nonce, timestamp and signature are added to the url's
/// query.
///
/// See http://api-docs.letterboxd.com/#signing.
fn sign_url(&self, mut url: Url, method: &http::method::Method, body: &[u8]) -> Url {
let nonce = uuid::Uuid::new_v4(); // use UUID as random and unique nonce
let timestamp = time::SystemTime::now()
.duration_since(time::UNIX_EPOCH)
.expect("SystemTime::duration_since failed")
.as_secs();
url.query_pairs_mut()
.append_pair("apikey", &self.client_id)
.append_pair("nonce", &format!("{}", nonce))
.append_pair("timestamp", &format!("{}", timestamp));
// create signature
let mut hmac = Hmac::<Sha256>::new_from_slice(&self.client_secret.secret().as_bytes())
.expect("HMAC can take key of any size");
hmac.update(method.as_str().as_bytes());
hmac.update(&[b'\0']);
hmac.update(url.as_str().as_bytes());
hmac.update(&[b'\0']);
hmac.update(body);
let signature: String = hmac.finalize().into_bytes().encode_hex();
url.query_pairs_mut().append_pair("signature", &signature);
url
}
}

View File

@@ -0,0 +1,152 @@
//!
//! This example showcases the Microsoft Graph OAuth2 process for requesting access to Microsoft
//! services using PKCE.
//!
//! Before running it, you'll need to generate your own Microsoft OAuth2 credentials. See
//! https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app
//! * Register a `Web` application with a `Redirect URI` of `http://localhost:3003/redirect`.
//! * In the left menu select `Overview`. Copy the `Application (client) ID` as the MSGRAPH_CLIENT_ID.
//! * In the left menu select `Certificates & secrets` and add a new client secret. Copy the secret value
//! as MSGRAPH_CLIENT_SECRET.
//! * In the left menu select `API permissions` and add a permission. Select Microsoft Graph and
//! `Delegated permissions`. Add the `Files.Read` permission.
//!
//! In order to run the example call:
//!
//! ```sh
//! MSGRAPH_CLIENT_ID=xxx MSGRAPH_CLIENT_SECRET=yyy cargo run --example msgraph
//! ```
//!
//! ...and follow the instructions.
//!
use oauth2::basic::BasicClient;
// Alternatively, this can be `oauth2::curl::http_client` or a custom client.
use oauth2::reqwest::http_client;
use oauth2::{
AuthType, AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge,
RedirectUrl, Scope, TokenUrl,
};
use std::env;
use std::io::{BufRead, BufReader, Write};
use std::net::TcpListener;
use url::Url;
fn main() {
let graph_client_id = ClientId::new(
env::var("MSGRAPH_CLIENT_ID").expect("Missing the MSGRAPH_CLIENT_ID environment variable."),
);
let graph_client_secret = ClientSecret::new(
env::var("MSGRAPH_CLIENT_SECRET")
.expect("Missing the MSGRAPH_CLIENT_SECRET environment variable."),
);
let auth_url =
AuthUrl::new("https://login.microsoftonline.com/common/oauth2/v2.0/authorize".to_string())
.expect("Invalid authorization endpoint URL");
let token_url =
TokenUrl::new("https://login.microsoftonline.com/common/oauth2/v2.0/token".to_string())
.expect("Invalid token endpoint URL");
// Set up the config for the Microsoft Graph OAuth2 process.
let client = BasicClient::new(
graph_client_id,
Some(graph_client_secret),
auth_url,
Some(token_url),
)
// Microsoft Graph requires client_id and client_secret in URL rather than
// using Basic authentication.
.set_auth_type(AuthType::RequestBody)
// This example will be running its own server at localhost:3003.
// See below for the server implementation.
.set_redirect_uri(
RedirectUrl::new("http://localhost:3003/redirect".to_string())
.expect("Invalid redirect URL"),
);
// Microsoft Graph supports Proof Key for Code Exchange (PKCE - https://oauth.net/2/pkce/).
// Create a PKCE code verifier and SHA-256 encode it as a code challenge.
let (pkce_code_challenge, pkce_code_verifier) = PkceCodeChallenge::new_random_sha256();
// Generate the authorization URL to which we'll redirect the user.
let (authorize_url, csrf_state) = client
.authorize_url(CsrfToken::new_random)
// This example requests read access to OneDrive.
.add_scope(Scope::new(
"https://graph.microsoft.com/Files.Read".to_string(),
))
.set_pkce_challenge(pkce_code_challenge)
.url();
println!(
"Open this URL in your browser:\n{}\n",
authorize_url.to_string()
);
// A very naive implementation of the redirect server.
let listener = TcpListener::bind("127.0.0.1:3003").unwrap();
for stream in listener.incoming() {
if let Ok(mut stream) = stream {
let code;
let state;
{
let mut reader = BufReader::new(&stream);
let mut request_line = String::new();
reader.read_line(&mut request_line).unwrap();
let redirect_url = request_line.split_whitespace().nth(1).unwrap();
let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();
let code_pair = url
.query_pairs()
.find(|pair| {
let &(ref key, _) = pair;
key == "code"
})
.unwrap();
let (_, value) = code_pair;
code = AuthorizationCode::new(value.into_owned());
let state_pair = url
.query_pairs()
.find(|pair| {
let &(ref key, _) = pair;
key == "state"
})
.unwrap();
let (_, value) = state_pair;
state = CsrfToken::new(value.into_owned());
}
let message = "Go back to your terminal :)";
let response = format!(
"HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}",
message.len(),
message
);
stream.write_all(response.as_bytes()).unwrap();
println!("MS Graph returned the following code:\n{}\n", code.secret());
println!(
"MS Graph returned the following state:\n{} (expected `{}`)\n",
state.secret(),
csrf_state.secret()
);
// Exchange the code with a token.
let token = client
.exchange_code(code)
// Send the PKCE code verifier in the token request
.set_pkce_verifier(pkce_code_verifier)
.request(http_client);
println!("MS Graph returned the following token:\n{:?}\n", token);
// The server will terminate itself after collecting the first code.
break;
}
}
}

View File

@@ -0,0 +1,246 @@
//!
//! This example showcases the Wunderlist OAuth2 process for requesting access to the user's todo lists.
//! Wunderlist does not implement the correct token response, so this serves as an example of how to
//! implement a custom client.
//!
//! Before running it, you'll need to create your own wunderlist app.
//!
//! In order to run the example call:
//!
//! ```sh
//! WUNDER_CLIENT_ID=xxx WUNDER_CLIENT_SECRET=yyy cargo run --example wunderlist
//! ```
//!
//! ...and follow the instructions.
//!
use oauth2::TokenType;
use oauth2::{
basic::{
BasicErrorResponse, BasicRevocationErrorResponse, BasicTokenIntrospectionResponse,
BasicTokenType,
},
revocation::StandardRevocableToken,
};
// Alternatively, this can be `oauth2::curl::http_client` or a custom client.
use oauth2::helpers;
use oauth2::reqwest::http_client;
use oauth2::{
AccessToken, AuthUrl, AuthorizationCode, Client, ClientId, ClientSecret, CsrfToken,
EmptyExtraTokenFields, ExtraTokenFields, RedirectUrl, RefreshToken, Scope, TokenResponse,
TokenUrl,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use std::env;
use std::io::{BufRead, BufReader, Write};
use std::net::TcpListener;
use url::Url;
type SpecialTokenResponse = NonStandardTokenResponse<EmptyExtraTokenFields>;
type SpecialClient = Client<
BasicErrorResponse,
SpecialTokenResponse,
BasicTokenType,
BasicTokenIntrospectionResponse,
StandardRevocableToken,
BasicRevocationErrorResponse,
>;
fn default_token_type() -> Option<BasicTokenType> {
Some(BasicTokenType::Bearer)
}
///
/// Non Standard OAuth2 token response.
///
/// This struct includes the fields defined in
/// [Section 5.1 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-5.1), as well as
/// extensions defined by the `EF` type parameter.
/// In this particular example token_type is optional to showcase how to deal with a non
/// compliant provider.
///
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct NonStandardTokenResponse<EF: ExtraTokenFields> {
access_token: AccessToken,
// In this example wunderlist does not follow the RFC specs and don't return the
// token_type. `NonStandardTokenResponse` makes the `token_type` optional.
#[serde(default = "default_token_type")]
token_type: Option<BasicTokenType>,
#[serde(skip_serializing_if = "Option::is_none")]
expires_in: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
refresh_token: Option<RefreshToken>,
#[serde(rename = "scope")]
#[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")]
#[serde(serialize_with = "helpers::serialize_space_delimited_vec")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
scopes: Option<Vec<Scope>>,
#[serde(bound = "EF: ExtraTokenFields")]
#[serde(flatten)]
extra_fields: EF,
}
impl<EF> TokenResponse<BasicTokenType> for NonStandardTokenResponse<EF>
where
EF: ExtraTokenFields,
BasicTokenType: TokenType,
{
///
/// REQUIRED. The access token issued by the authorization server.
///
fn access_token(&self) -> &AccessToken {
&self.access_token
}
///
/// REQUIRED. The type of the token issued as described in
/// [Section 7.1](https://tools.ietf.org/html/rfc6749#section-7.1).
/// Value is case insensitive and deserialized to the generic `TokenType` parameter.
/// But in this particular case as the service is non compliant, it has a default value
///
fn token_type(&self) -> &BasicTokenType {
match &self.token_type {
Some(t) => t,
None => &BasicTokenType::Bearer,
}
}
///
/// RECOMMENDED. The lifetime in seconds of the access token. For example, the value 3600
/// denotes that the access token will expire in one hour from the time the response was
/// generated. If omitted, the authorization server SHOULD provide the expiration time via
/// other means or document the default value.
///
fn expires_in(&self) -> Option<Duration> {
self.expires_in.map(Duration::from_secs)
}
///
/// OPTIONAL. The refresh token, which can be used to obtain new access tokens using the same
/// authorization grant as described in
/// [Section 6](https://tools.ietf.org/html/rfc6749#section-6).
///
fn refresh_token(&self) -> Option<&RefreshToken> {
self.refresh_token.as_ref()
}
///
/// OPTIONAL, if identical to the scope requested by the client; otherwise, REQUIRED. The
/// scipe of the access token as described by
/// [Section 3.3](https://tools.ietf.org/html/rfc6749#section-3.3). If included in the response,
/// this space-delimited field is parsed into a `Vec` of individual scopes. If omitted from
/// the response, this field is `None`.
///
fn scopes(&self) -> Option<&Vec<Scope>> {
self.scopes.as_ref()
}
}
fn main() {
let client_id_str = env::var("WUNDERLIST_CLIENT_ID")
.expect("Missing the WUNDERLIST_CLIENT_ID environment variable.");
let client_secret_str = env::var("WUNDERLIST_CLIENT_SECRET")
.expect("Missing the WUNDERLIST_CLIENT_SECRET environment variable.");
let wunder_client_id = ClientId::new(client_id_str.clone());
let wunderlist_client_secret = ClientSecret::new(client_secret_str.clone());
let auth_url = AuthUrl::new("https://www.wunderlist.com/oauth/authorize".to_string())
.expect("Invalid authorization endpoint URL");
let token_url = TokenUrl::new("https://www.wunderlist.com/oauth/access_token".to_string())
.expect("Invalid token endpoint URL");
// Set up the config for the Wunderlist OAuth2 process.
let client = SpecialClient::new(
wunder_client_id,
Some(wunderlist_client_secret),
auth_url,
Some(token_url),
)
// This example will be running its own server at localhost:8080.
// See below for the server implementation.
.set_redirect_uri(
RedirectUrl::new("http://localhost:8080".to_string()).expect("Invalid redirect URL"),
);
// Generate the authorization URL to which we'll redirect the user.
let (authorize_url, csrf_state) = client.authorize_url(CsrfToken::new_random).url();
println!(
"Open this URL in your browser:\n{}\n",
authorize_url.to_string()
);
// A very naive implementation of the redirect server.
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
for stream in listener.incoming() {
if let Ok(mut stream) = stream {
let code;
let state;
{
let mut reader = BufReader::new(&stream);
let mut request_line = String::new();
reader.read_line(&mut request_line).unwrap();
let redirect_url = request_line.split_whitespace().nth(1).unwrap();
let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();
let code_pair = url
.query_pairs()
.find(|pair| {
let &(ref key, _) = pair;
key == "code"
})
.unwrap();
let (_, value) = code_pair;
code = AuthorizationCode::new(value.into_owned());
let state_pair = url
.query_pairs()
.find(|pair| {
let &(ref key, _) = pair;
key == "state"
})
.unwrap();
let (_, value) = state_pair;
state = CsrfToken::new(value.into_owned());
}
let message = "Go back to your terminal :)";
let response = format!(
"HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}",
message.len(),
message
);
stream.write_all(response.as_bytes()).unwrap();
println!(
"Wunderlist returned the following code:\n{}\n",
code.secret()
);
println!(
"Wunderlist returned the following state:\n{} (expected `{}`)\n",
state.secret(),
csrf_state.secret()
);
// Exchange the code with a token.
let token_res = client
.exchange_code(code)
.add_extra_param("client_id", client_id_str)
.add_extra_param("client_secret", client_secret_str)
.request(http_client);
println!(
"Wunderlist returned the following token:\n{:?}\n",
token_res
);
break;
}
}
}

205
zeroidc/vendor/oauth2/src/basic.rs vendored Normal file
View File

@@ -0,0 +1,205 @@
use std::fmt::Error as FormatterError;
use std::fmt::{Debug, Display, Formatter};
use super::{
Client, EmptyExtraTokenFields, ErrorResponseType, RequestTokenError, StandardErrorResponse,
StandardTokenResponse, TokenType,
};
use crate::{
revocation::{RevocationErrorResponseType, StandardRevocableToken},
StandardTokenIntrospectionResponse,
};
///
/// Basic OAuth2 client specialization, suitable for most applications.
///
pub type BasicClient = Client<
BasicErrorResponse,
BasicTokenResponse,
BasicTokenType,
BasicTokenIntrospectionResponse,
StandardRevocableToken,
BasicRevocationErrorResponse,
>;
///
/// Basic OAuth2 authorization token types.
///
#[derive(Clone, Debug, PartialEq)]
pub enum BasicTokenType {
///
/// Bearer token
/// ([OAuth 2.0 Bearer Tokens - RFC 6750](https://tools.ietf.org/html/rfc6750)).
///
Bearer,
///
/// MAC ([OAuth 2.0 Message Authentication Code (MAC)
/// Tokens](https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-05)).
///
Mac,
///
/// An extension not defined by RFC 6749.
///
Extension(String),
}
impl BasicTokenType {
fn from_str(s: &str) -> Self {
match s {
"bearer" => BasicTokenType::Bearer,
"mac" => BasicTokenType::Mac,
ext => BasicTokenType::Extension(ext.to_string()),
}
}
}
impl AsRef<str> for BasicTokenType {
fn as_ref(&self) -> &str {
match *self {
BasicTokenType::Bearer => "bearer",
BasicTokenType::Mac => "mac",
BasicTokenType::Extension(ref ext) => ext.as_str(),
}
}
}
impl<'de> serde::Deserialize<'de> for BasicTokenType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let variant_str = String::deserialize(deserializer)?;
Ok(Self::from_str(&variant_str))
}
}
impl serde::ser::Serialize for BasicTokenType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(self.as_ref())
}
}
impl TokenType for BasicTokenType {}
///
/// Basic OAuth2 token response.
///
pub type BasicTokenResponse = StandardTokenResponse<EmptyExtraTokenFields, BasicTokenType>;
///
/// Basic OAuth2 token introspection response.
///
pub type BasicTokenIntrospectionResponse =
StandardTokenIntrospectionResponse<EmptyExtraTokenFields, BasicTokenType>;
///
/// Basic access token error types.
///
/// These error types are defined in
/// [Section 5.2 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-5.2).
///
#[derive(Clone, PartialEq)]
pub enum BasicErrorResponseType {
///
/// Client authentication failed (e.g., unknown client, no client authentication included,
/// or unsupported authentication method).
///
InvalidClient,
///
/// The provided authorization grant (e.g., authorization code, resource owner credentials)
/// or refresh token is invalid, expired, revoked, does not match the redirection URI used
/// in the authorization request, or was issued to another client.
///
InvalidGrant,
///
/// The request is missing a required parameter, includes an unsupported parameter value
/// (other than grant type), repeats a parameter, includes multiple credentials, utilizes
/// more than one mechanism for authenticating the client, or is otherwise malformed.
///
InvalidRequest,
///
/// The requested scope is invalid, unknown, malformed, or exceeds the scope granted by the
/// resource owner.
///
InvalidScope,
///
/// The authenticated client is not authorized to use this authorization grant type.
///
UnauthorizedClient,
///
/// The authorization grant type is not supported by the authorization server.
///
UnsupportedGrantType,
///
/// An extension not defined by RFC 6749.
///
Extension(String),
}
impl BasicErrorResponseType {
pub(crate) fn from_str(s: &str) -> Self {
match s {
"invalid_client" => BasicErrorResponseType::InvalidClient,
"invalid_grant" => BasicErrorResponseType::InvalidGrant,
"invalid_request" => BasicErrorResponseType::InvalidRequest,
"invalid_scope" => BasicErrorResponseType::InvalidScope,
"unauthorized_client" => BasicErrorResponseType::UnauthorizedClient,
"unsupported_grant_type" => BasicErrorResponseType::UnsupportedGrantType,
ext => BasicErrorResponseType::Extension(ext.to_string()),
}
}
}
impl AsRef<str> for BasicErrorResponseType {
fn as_ref(&self) -> &str {
match *self {
BasicErrorResponseType::InvalidClient => "invalid_client",
BasicErrorResponseType::InvalidGrant => "invalid_grant",
BasicErrorResponseType::InvalidRequest => "invalid_request",
BasicErrorResponseType::InvalidScope => "invalid_scope",
BasicErrorResponseType::UnauthorizedClient => "unauthorized_client",
BasicErrorResponseType::UnsupportedGrantType => "unsupported_grant_type",
BasicErrorResponseType::Extension(ref ext) => ext.as_str(),
}
}
}
impl<'de> serde::Deserialize<'de> for BasicErrorResponseType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let variant_str = String::deserialize(deserializer)?;
Ok(Self::from_str(&variant_str))
}
}
impl serde::ser::Serialize for BasicErrorResponseType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(self.as_ref())
}
}
impl ErrorResponseType for BasicErrorResponseType {}
impl Debug for BasicErrorResponseType {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
Display::fmt(self, f)
}
}
impl Display for BasicErrorResponseType {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
write!(f, "{}", self.as_ref())
}
}
///
/// Error response specialization for basic OAuth2 implementation.
///
pub type BasicErrorResponse = StandardErrorResponse<BasicErrorResponseType>;
///
/// Token error specialization for basic OAuth2 implementation.
///
pub type BasicRequestTokenError<RE> = RequestTokenError<RE, BasicErrorResponse>;
///
/// Revocation error response specialization for basic OAuth2 implementation.
///
pub type BasicRevocationErrorResponse = StandardErrorResponse<RevocationErrorResponseType>;

101
zeroidc/vendor/oauth2/src/curl.rs vendored Normal file
View File

@@ -0,0 +1,101 @@
use std::io::Read;
use curl::easy::Easy;
use http::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
use http::method::Method;
use http::status::StatusCode;
use super::{HttpRequest, HttpResponse};
///
/// Error type returned by failed curl HTTP requests.
///
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Error returned by curl crate.
#[error("curl request failed")]
Curl(#[source] curl::Error),
/// Non-curl HTTP error.
#[error("HTTP error")]
Http(#[source] http::Error),
/// Other error.
#[error("Other error: {}", _0)]
Other(String),
}
///
/// Synchronous HTTP client.
///
pub fn http_client(request: HttpRequest) -> Result<HttpResponse, Error> {
let mut easy = Easy::new();
easy.url(&request.url.to_string()[..])
.map_err(Error::Curl)?;
let mut headers = curl::easy::List::new();
request
.headers
.iter()
.map(|(name, value)| {
headers
.append(&format!(
"{}: {}",
name,
value.to_str().map_err(|_| Error::Other(format!(
"invalid {} header value {:?}",
name,
value.as_bytes()
)))?
))
.map_err(Error::Curl)
})
.collect::<Result<_, _>>()?;
easy.http_headers(headers).map_err(Error::Curl)?;
if let Method::POST = request.method {
easy.post(true).map_err(Error::Curl)?;
easy.post_field_size(request.body.len() as u64)
.map_err(Error::Curl)?;
} else {
assert_eq!(request.method, Method::GET);
}
let mut form_slice = &request.body[..];
let mut data = Vec::new();
{
let mut transfer = easy.transfer();
transfer
.read_function(|buf| Ok(form_slice.read(buf).unwrap_or(0)))
.map_err(Error::Curl)?;
transfer
.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
})
.map_err(Error::Curl)?;
transfer.perform().map_err(Error::Curl)?;
}
let status_code = easy.response_code().map_err(Error::Curl)? as u16;
Ok(HttpResponse {
status_code: StatusCode::from_u16(status_code).map_err(|err| Error::Http(err.into()))?,
headers: easy
.content_type()
.map_err(Error::Curl)?
.map(|content_type| {
Ok(vec![(
CONTENT_TYPE,
HeaderValue::from_str(content_type).map_err(|err| Error::Http(err.into()))?,
)]
.into_iter()
.collect::<HeaderMap>())
})
.transpose()?
.unwrap_or_else(HeaderMap::new),
body: data,
})
}

239
zeroidc/vendor/oauth2/src/devicecode.rs vendored Normal file
View File

@@ -0,0 +1,239 @@
use std::error::Error;
use std::fmt::Error as FormatterError;
use std::fmt::{Debug, Display, Formatter};
use std::marker::PhantomData;
use std::time::Duration;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use super::{
DeviceCode, EndUserVerificationUrl, ErrorResponse, ErrorResponseType, RequestTokenError,
StandardErrorResponse, TokenResponse, TokenType, UserCode,
};
use crate::basic::BasicErrorResponseType;
use crate::types::VerificationUriComplete;
/// The minimum amount of time in seconds that the client SHOULD wait
/// between polling requests to the token endpoint. If no value is
/// provided, clients MUST use 5 as the default.
fn default_devicecode_interval() -> u64 {
5
}
///
/// Trait for adding extra fields to the `DeviceAuthorizationResponse`.
///
pub trait ExtraDeviceAuthorizationFields: DeserializeOwned + Debug + Serialize {}
#[derive(Clone, Debug, Deserialize, Serialize)]
///
/// Empty (default) extra token fields.
///
pub struct EmptyExtraDeviceAuthorizationFields {}
impl ExtraDeviceAuthorizationFields for EmptyExtraDeviceAuthorizationFields {}
///
/// Standard OAuth2 device authorization response.
///
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DeviceAuthorizationResponse<EF>
where
EF: ExtraDeviceAuthorizationFields,
{
/// The device verification code.
device_code: DeviceCode,
/// The end-user verification code.
user_code: UserCode,
/// The end-user verification URI on the authorization The URI should be
/// short and easy to remember as end users will be asked to manually type
/// it into their user agent.
///
/// The `verification_url` alias here is a deviation from the RFC, as
/// implementations of device code flow predate RFC 8628.
#[serde(alias = "verification_url")]
verification_uri: EndUserVerificationUrl,
/// A verification URI that includes the "user_code" (or other information
/// with the same function as the "user_code"), which is designed for
/// non-textual transmission.
#[serde(skip_serializing_if = "Option::is_none")]
verification_uri_complete: Option<VerificationUriComplete>,
/// The lifetime in seconds of the "device_code" and "user_code".
expires_in: u64,
/// The minimum amount of time in seconds that the client SHOULD wait
/// between polling requests to the token endpoint. If no value is
/// provided, clients MUST use 5 as the default.
#[serde(default = "default_devicecode_interval")]
interval: u64,
#[serde(bound = "EF: ExtraDeviceAuthorizationFields", flatten)]
extra_fields: EF,
}
impl<EF> DeviceAuthorizationResponse<EF>
where
EF: ExtraDeviceAuthorizationFields,
{
/// The device verification code.
pub fn device_code(&self) -> &DeviceCode {
&self.device_code
}
/// The end-user verification code.
pub fn user_code(&self) -> &UserCode {
&self.user_code
}
/// The end-user verification URI on the authorization The URI should be
/// short and easy to remember as end users will be asked to manually type
/// it into their user agent.
pub fn verification_uri(&self) -> &EndUserVerificationUrl {
&self.verification_uri
}
/// A verification URI that includes the "user_code" (or other information
/// with the same function as the "user_code"), which is designed for
/// non-textual transmission.
pub fn verification_uri_complete(&self) -> Option<&VerificationUriComplete> {
self.verification_uri_complete.as_ref()
}
/// The lifetime in seconds of the "device_code" and "user_code".
pub fn expires_in(&self) -> Duration {
Duration::from_secs(self.expires_in)
}
/// The minimum amount of time in seconds that the client SHOULD wait
/// between polling requests to the token endpoint. If no value is
/// provided, clients MUST use 5 as the default.
pub fn interval(&self) -> Duration {
Duration::from_secs(self.interval)
}
/// Any extra fields returned on the response.
pub fn extra_fields(&self) -> &EF {
&self.extra_fields
}
}
///
/// Standard implementation of DeviceAuthorizationResponse which throws away
/// extra received response fields.
///
pub type StandardDeviceAuthorizationResponse =
DeviceAuthorizationResponse<EmptyExtraDeviceAuthorizationFields>;
///
/// Basic access token error types.
///
/// These error types are defined in
/// [Section 5.2 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-5.2) and
/// [Section 3.5 of RFC 6749](https://tools.ietf.org/html/rfc8628#section-3.5)
///
#[derive(Clone, PartialEq)]
pub enum DeviceCodeErrorResponseType {
///
/// The authorization request is still pending as the end user hasn't
/// yet completed the user-interaction steps. The client SHOULD repeat the
/// access token request to the token endpoint. Before each new request,
/// the client MUST wait at least the number of seconds specified by the
/// "interval" parameter of the device authorization response, or 5 seconds
/// if none was provided, and respect any increase in the polling interval
/// required by the "slow_down" error.
///
AuthorizationPending,
///
/// A variant of "authorization_pending", the authorization request is
/// still pending and polling should continue, but the interval MUST be
/// increased by 5 seconds for this and all subsequent requests.
SlowDown,
///
/// The authorization request was denied.
///
AccessDenied,
///
/// The "device_code" has expired, and the device authorization session has
/// concluded. The client MAY commence a new device authorization request
/// but SHOULD wait for user interaction before restarting to avoid
/// unnecessary polling.
ExpiredToken,
///
/// A Basic response type
///
Basic(BasicErrorResponseType),
}
impl DeviceCodeErrorResponseType {
fn from_str(s: &str) -> Self {
match BasicErrorResponseType::from_str(s) {
BasicErrorResponseType::Extension(ext) => match ext.as_str() {
"authorization_pending" => DeviceCodeErrorResponseType::AuthorizationPending,
"slow_down" => DeviceCodeErrorResponseType::SlowDown,
"access_denied" => DeviceCodeErrorResponseType::AccessDenied,
"expired_token" => DeviceCodeErrorResponseType::ExpiredToken,
_ => DeviceCodeErrorResponseType::Basic(BasicErrorResponseType::Extension(ext)),
},
basic => DeviceCodeErrorResponseType::Basic(basic),
}
}
}
impl AsRef<str> for DeviceCodeErrorResponseType {
fn as_ref(&self) -> &str {
match self {
DeviceCodeErrorResponseType::AuthorizationPending => "authorization_pending",
DeviceCodeErrorResponseType::SlowDown => "slow_down",
DeviceCodeErrorResponseType::AccessDenied => "access_denied",
DeviceCodeErrorResponseType::ExpiredToken => "expired_token",
DeviceCodeErrorResponseType::Basic(basic) => basic.as_ref(),
}
}
}
impl<'de> serde::Deserialize<'de> for DeviceCodeErrorResponseType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let variant_str = String::deserialize(deserializer)?;
Ok(Self::from_str(&variant_str))
}
}
impl serde::ser::Serialize for DeviceCodeErrorResponseType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(self.as_ref())
}
}
impl ErrorResponseType for DeviceCodeErrorResponseType {}
impl Debug for DeviceCodeErrorResponseType {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
Display::fmt(self, f)
}
}
impl Display for DeviceCodeErrorResponseType {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
write!(f, "{}", self.as_ref())
}
}
///
/// Error response specialization for device code OAuth2 implementation.
///
pub type DeviceCodeErrorResponse = StandardErrorResponse<DeviceCodeErrorResponseType>;
pub(crate) enum DeviceAccessTokenPollResult<TR, RE, TE, TT>
where
TE: ErrorResponse + 'static,
TR: TokenResponse<TT>,
TT: TokenType,
RE: Error + 'static,
{
ContinueWithNewPollInterval(Duration),
Done(Result<TR, RequestTokenError<RE, TE>>, PhantomData<TT>),
}

369
zeroidc/vendor/oauth2/src/helpers.rs vendored Normal file
View File

@@ -0,0 +1,369 @@
use serde::ser::{Impossible, SerializeStructVariant, SerializeTupleVariant};
use serde::{de, ser};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::marker::PhantomData;
///
/// Serde case-insensitive deserializer for an untagged `enum`.
///
/// This function converts values to lowercase before deserializing as the `enum`. Requires the
/// `#[serde(rename_all = "lowercase")]` attribute to be set on the `enum`.
///
/// # Example
///
/// In example below, the following JSON values all deserialize to
/// `GroceryBasket { fruit_item: Fruit::Banana }`:
///
/// * `{"fruit_item": "banana"}`
/// * `{"fruit_item": "BANANA"}`
/// * `{"fruit_item": "Banana"}`
///
/// Note: this example does not compile automatically due to
/// [Rust issue #29286](https://github.com/rust-lang/rust/issues/29286).
///
/// ```
/// # /*
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// #[serde(rename_all = "lowercase")]
/// enum Fruit {
/// Apple,
/// Banana,
/// Orange,
/// }
///
/// #[derive(Deserialize)]
/// struct GroceryBasket {
/// #[serde(deserialize_with = "helpers::deserialize_untagged_enum_case_insensitive")]
/// fruit_item: Fruit,
/// }
/// # */
/// ```
///
pub fn deserialize_untagged_enum_case_insensitive<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: Deserialize<'de>,
D: Deserializer<'de>,
{
use serde::de::Error;
use serde_json::Value;
T::deserialize(Value::String(
String::deserialize(deserializer)?.to_lowercase(),
))
.map_err(Error::custom)
}
///
/// Serde space-delimited string deserializer for a `Vec<String>`.
///
/// This function splits a JSON string at each space character into a `Vec<String>` .
///
/// # Example
///
/// In example below, the JSON value `{"items": "foo bar baz"}` would deserialize to:
///
/// ```
/// # struct GroceryBasket {
/// # items: Vec<String>,
/// # }
/// GroceryBasket {
/// items: vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]
/// };
/// ```
///
/// Note: this example does not compile automatically due to
/// [Rust issue #29286](https://github.com/rust-lang/rust/issues/29286).
///
/// ```
/// # /*
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct GroceryBasket {
/// #[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")]
/// items: Vec<String>,
/// }
/// # */
/// ```
///
pub fn deserialize_space_delimited_vec<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: Default + Deserialize<'de>,
D: Deserializer<'de>,
{
use serde::de::Error;
use serde_json::Value;
if let Some(space_delimited) = Option::<String>::deserialize(deserializer)? {
let entries = space_delimited
.split(' ')
.map(|s| Value::String(s.to_string()))
.collect();
T::deserialize(Value::Array(entries)).map_err(Error::custom)
} else {
// If the JSON value is null, use the default value.
Ok(T::default())
}
}
///
/// Deserializes a string or array of strings into an array of strings
///
pub fn deserialize_optional_string_or_vec_string<'de, D>(
deserializer: D,
) -> Result<Option<Vec<String>>, D::Error>
where
D: Deserializer<'de>,
{
struct StringOrVec(PhantomData<Vec<String>>);
impl<'de> de::Visitor<'de> for StringOrVec {
type Value = Option<Vec<String>>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string or list of strings")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Some(vec![value.to_owned()]))
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
fn visit_seq<S>(self, visitor: S) -> Result<Self::Value, S::Error>
where
S: de::SeqAccess<'de>,
{
Deserialize::deserialize(de::value::SeqAccessDeserializer::new(visitor)).map(Some)
}
}
deserializer.deserialize_any(StringOrVec(PhantomData))
}
///
/// Serde space-delimited string serializer for an `Option<Vec<String>>`.
///
/// This function serializes a string vector into a single space-delimited string.
/// If `string_vec_opt` is `None`, the function serializes it as `None` (e.g., `null`
/// in the case of JSON serialization).
///
pub fn serialize_space_delimited_vec<T, S>(
vec_opt: &Option<Vec<T>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
T: AsRef<str>,
S: Serializer,
{
if let Some(ref vec) = *vec_opt {
let space_delimited = vec.iter().map(|s| s.as_ref()).collect::<Vec<_>>().join(" ");
serializer.serialize_str(&space_delimited)
} else {
serializer.serialize_none()
}
}
///
/// Serde string serializer for an enum.
///<
/// Source:
/// [https://github.com/serde-rs/serde/issues/553](https://github.com/serde-rs/serde/issues/553)
///
pub fn variant_name<T: Serialize>(t: &T) -> &'static str {
#[derive(Debug)]
struct NotEnum;
type Result<T> = std::result::Result<T, NotEnum>;
impl std::error::Error for NotEnum {
fn description(&self) -> &str {
"not struct"
}
}
impl std::fmt::Display for NotEnum {
fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
unimplemented!()
}
}
impl ser::Error for NotEnum {
fn custom<T: std::fmt::Display>(_msg: T) -> Self {
NotEnum
}
}
struct VariantName;
impl Serializer for VariantName {
type Ok = &'static str;
type Error = NotEnum;
type SerializeSeq = Impossible<Self::Ok, Self::Error>;
type SerializeTuple = Impossible<Self::Ok, Self::Error>;
type SerializeTupleStruct = Impossible<Self::Ok, Self::Error>;
type SerializeTupleVariant = Enum;
type SerializeMap = Impossible<Self::Ok, Self::Error>;
type SerializeStruct = Impossible<Self::Ok, Self::Error>;
type SerializeStructVariant = Enum;
fn serialize_bool(self, _v: bool) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_i8(self, _v: i8) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_i16(self, _v: i16) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_i32(self, _v: i32) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_i64(self, _v: i64) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_u8(self, _v: u8) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_u16(self, _v: u16) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_u32(self, _v: u32) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_u64(self, _v: u64) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_f32(self, _v: f32) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_f64(self, _v: f64) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_char(self, _v: char) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_str(self, _v: &str) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_bytes(self, _v: &[u8]) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_none(self) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_some<T: ?Sized + Serialize>(self, _value: &T) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_unit(self) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Self::Ok> {
Ok(variant)
}
fn serialize_newtype_struct<T: ?Sized + Serialize>(
self,
_name: &'static str,
_value: &T,
) -> Result<Self::Ok> {
Err(NotEnum)
}
fn serialize_newtype_variant<T: ?Sized + Serialize>(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_value: &T,
) -> Result<Self::Ok> {
Ok(variant)
}
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
Err(NotEnum)
}
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> {
Err(NotEnum)
}
fn serialize_tuple_struct(
self,
_name: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleStruct> {
Err(NotEnum)
}
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant> {
Ok(Enum(variant))
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
Err(NotEnum)
}
fn serialize_struct(
self,
_name: &'static str,
_len: usize,
) -> Result<Self::SerializeStruct> {
Err(NotEnum)
}
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant> {
Ok(Enum(variant))
}
}
struct Enum(&'static str);
impl SerializeStructVariant for Enum {
type Ok = &'static str;
type Error = NotEnum;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
_key: &'static str,
_value: &T,
) -> Result<()> {
Ok(())
}
fn end(self) -> Result<Self::Ok> {
Ok(self.0)
}
}
impl SerializeTupleVariant for Enum {
type Ok = &'static str;
type Error = NotEnum;
fn serialize_field<T: ?Sized + Serialize>(&mut self, _value: &T) -> Result<()> {
Ok(())
}
fn end(self) -> Result<Self::Ok> {
Ok(self.0)
}
}
t.serialize(VariantName).unwrap()
}

3165
zeroidc/vendor/oauth2/src/lib.rs vendored Normal file

File diff suppressed because it is too large Load Diff

129
zeroidc/vendor/oauth2/src/reqwest.rs vendored Normal file
View File

@@ -0,0 +1,129 @@
use thiserror::Error;
///
/// Error type returned by failed reqwest HTTP requests.
///
#[derive(Debug, Error)]
pub enum Error<T>
where
T: std::error::Error + 'static,
{
/// Error returned by reqwest crate.
#[error("request failed")]
Reqwest(#[source] T),
/// Non-reqwest HTTP error.
#[error("HTTP error")]
Http(#[source] http::Error),
/// I/O error.
#[error("I/O error")]
Io(#[source] std::io::Error),
/// Other error.
#[error("Other error: {}", _0)]
Other(String),
}
#[cfg(not(target_arch = "wasm32"))]
pub use blocking::http_client;
///
/// Error type returned by failed reqwest blocking HTTP requests.
///
#[cfg(not(target_arch = "wasm32"))]
pub type HttpClientError = Error<blocking::reqwest::Error>;
pub use async_client::async_http_client;
///
/// Error type returned by failed reqwest async HTTP requests.
///
pub type AsyncHttpClientError = Error<reqwest::Error>;
#[cfg(not(target_arch = "wasm32"))]
mod blocking {
use super::super::{HttpRequest, HttpResponse};
use super::Error;
pub use reqwest;
use reqwest::blocking;
use reqwest::redirect::Policy as RedirectPolicy;
use std::io::Read;
///
/// Synchronous HTTP client.
///
pub fn http_client(request: HttpRequest) -> Result<HttpResponse, Error<reqwest::Error>> {
let client = blocking::Client::builder()
// Following redirects opens the client up to SSRF vulnerabilities.
.redirect(RedirectPolicy::none())
.build()
.map_err(Error::Reqwest)?;
#[cfg(feature = "reqwest")]
let mut request_builder = client
.request(request.method, request.url.as_str())
.body(request.body);
for (name, value) in &request.headers {
request_builder = request_builder.header(name.as_str(), value.as_bytes());
}
let mut response = client
.execute(request_builder.build().map_err(Error::Reqwest)?)
.map_err(Error::Reqwest)?;
let mut body = Vec::new();
response.read_to_end(&mut body).map_err(Error::Io)?;
#[cfg(feature = "reqwest")]
{
Ok(HttpResponse {
status_code: response.status(),
headers: response.headers().to_owned(),
body,
})
}
}
}
mod async_client {
use super::super::{HttpRequest, HttpResponse};
use super::Error;
pub use reqwest;
///
/// Asynchronous HTTP client.
///
pub async fn async_http_client(
request: HttpRequest,
) -> Result<HttpResponse, Error<reqwest::Error>> {
let client = {
let builder = reqwest::Client::builder();
// Following redirects opens the client up to SSRF vulnerabilities.
// but this is not possible to prevent on wasm targets
#[cfg(not(target_arch = "wasm32"))]
let builder = builder.redirect(reqwest::redirect::Policy::none());
builder.build().map_err(Error::Reqwest)?
};
let mut request_builder = client
.request(request.method, request.url.as_str())
.body(request.body);
for (name, value) in &request.headers {
request_builder = request_builder.header(name.as_str(), value.as_bytes());
}
let request = request_builder.build().map_err(Error::Reqwest)?;
let response = client.execute(request).await.map_err(Error::Reqwest)?;
let status_code = response.status();
let headers = response.headers().to_owned();
let chunks = response.bytes().await.map_err(Error::Reqwest)?;
Ok(HttpResponse {
status_code,
headers,
body: chunks.to_vec(),
})
}
}

179
zeroidc/vendor/oauth2/src/revocation.rs vendored Normal file
View File

@@ -0,0 +1,179 @@
use serde::{Deserialize, Serialize};
use std::fmt::Error as FormatterError;
use std::fmt::{Debug, Display, Formatter};
use crate::{basic::BasicErrorResponseType, ErrorResponseType};
use crate::{AccessToken, RefreshToken};
///
/// A revocable token.
///
/// Implement this trait to indicate support for token revocation per [RFC 7009 OAuth 2.0 Token Revocation](https://tools.ietf.org/html/rfc7009#section-2.2).
///
pub trait RevocableToken {
///
/// The actual token value to be revoked.
///
fn secret(&self) -> &str;
///
/// Indicates the type of the token being revoked, as defined by [RFC 7009, Section 2.1](https://tools.ietf.org/html/rfc7009#section-2.1).
///
/// Implementations should return `Some(...)` values for token types that the target authorization servers are
/// expected to know (e.g. because they are registered in the [OAuth Token Type Hints Registry](https://tools.ietf.org/html/rfc7009#section-4.1.2))
/// so that they can potentially optimize their search for the token to be revoked.
///
fn type_hint(&self) -> Option<&str>;
}
///
/// A token representation usable with authorization servers that support [RFC 7009](https://tools.ietf.org/html/rfc7009) token revocation.
///
/// For use with [`revoke_token()`].
///
/// Automatically reports the correct RFC 7009 [`token_type_hint`](https://tools.ietf.org/html/rfc7009#section-2.1) value corresponding to the token type variant used, i.e.
/// `access_token` for [`AccessToken`] and `secret_token` for [`RefreshToken`].
///
/// # Example
///
/// Per [RFC 7009, Section 2](https://tools.ietf.org/html/rfc7009#section-2) prefer revocation by refresh token which,
/// if issued to the client, must be supported by the server, otherwise fallback to access token (which may or may not
/// be supported by the server).
///
/// ```ignore
/// let token_to_revoke: StandardRevocableToken = match token_response.refresh_token() {
/// Some(token) => token.into(),
/// None => token_response.access_token().into(),
/// };
///
/// client
/// .revoke_token(token_to_revoke)
/// .request(http_client)
/// .unwrap();
/// ```
///
/// [`revoke_token()`]: crate::Client::revoke_token()
///
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub enum StandardRevocableToken {
/// A representation of an [`AccessToken`] suitable for use with [`revoke_token()`](crate::Client::revoke_token()).
AccessToken(AccessToken),
/// A representation of an [`RefreshToken`] suitable for use with [`revoke_token()`](crate::Client::revoke_token()).
RefreshToken(RefreshToken),
}
impl RevocableToken for StandardRevocableToken {
fn secret(&self) -> &str {
match self {
Self::AccessToken(token) => token.secret(),
Self::RefreshToken(token) => token.secret(),
}
}
///
/// Indicates the type of the token to be revoked, as defined by [RFC 7009, Section 2.1](https://tools.ietf.org/html/rfc7009#section-2.1), i.e.:
///
/// * `access_token`: An access token as defined in [RFC 6749,
/// Section 1.4](https://tools.ietf.org/html/rfc6749#section-1.4)
///
/// * `refresh_token`: A refresh token as defined in [RFC 6749,
/// Section 1.5](https://tools.ietf.org/html/rfc6749#section-1.5)
///
fn type_hint(&self) -> Option<&str> {
match self {
StandardRevocableToken::AccessToken(_) => Some("access_token"),
StandardRevocableToken::RefreshToken(_) => Some("refresh_token"),
}
}
}
impl From<AccessToken> for StandardRevocableToken {
fn from(token: AccessToken) -> Self {
Self::AccessToken(token)
}
}
impl From<&AccessToken> for StandardRevocableToken {
fn from(token: &AccessToken) -> Self {
Self::AccessToken(token.clone())
}
}
impl From<RefreshToken> for StandardRevocableToken {
fn from(token: RefreshToken) -> Self {
Self::RefreshToken(token)
}
}
impl From<&RefreshToken> for StandardRevocableToken {
fn from(token: &RefreshToken) -> Self {
Self::RefreshToken(token.clone())
}
}
///
/// OAuth 2.0 Token Revocation error response types.
///
/// These error types are defined in
/// [Section 2.2.1 of RFC 7009](https://tools.ietf.org/html/rfc7009#section-2.2.1) and
/// [Section 5.2 of RFC 6749](https://tools.ietf.org/html/rfc8628#section-5.2)
///
#[derive(Clone, PartialEq)]
pub enum RevocationErrorResponseType {
///
/// The authorization server does not support the revocation of the presented token type.
///
UnsupportedTokenType,
///
/// The authorization server responded with some other error as defined [RFC 6749](https://tools.ietf.org/html/rfc6749) error.
///
Basic(BasicErrorResponseType),
}
impl RevocationErrorResponseType {
fn from_str(s: &str) -> Self {
match BasicErrorResponseType::from_str(s) {
BasicErrorResponseType::Extension(ext) => match ext.as_str() {
"unsupported_token_type" => RevocationErrorResponseType::UnsupportedTokenType,
_ => RevocationErrorResponseType::Basic(BasicErrorResponseType::Extension(ext)),
},
basic => RevocationErrorResponseType::Basic(basic),
}
}
}
impl AsRef<str> for RevocationErrorResponseType {
fn as_ref(&self) -> &str {
match self {
RevocationErrorResponseType::UnsupportedTokenType => "unsupported_token_type",
RevocationErrorResponseType::Basic(basic) => basic.as_ref(),
}
}
}
impl<'de> serde::Deserialize<'de> for RevocationErrorResponseType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let variant_str = String::deserialize(deserializer)?;
Ok(Self::from_str(&variant_str))
}
}
impl serde::ser::Serialize for RevocationErrorResponseType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(self.as_ref())
}
}
impl ErrorResponseType for RevocationErrorResponseType {}
impl Debug for RevocationErrorResponseType {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
Display::fmt(self, f)
}
}
impl Display for RevocationErrorResponseType {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
write!(f, "{}", self.as_ref())
}
}

2569
zeroidc/vendor/oauth2/src/tests.rs vendored Normal file

File diff suppressed because it is too large Load Diff

655
zeroidc/vendor/oauth2/src/types.rs vendored Normal file
View File

@@ -0,0 +1,655 @@
use std::convert::Into;
use std::fmt::Error as FormatterError;
use std::fmt::{Debug, Formatter};
use std::ops::Deref;
use rand::{thread_rng, Rng};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use url::Url;
macro_rules! new_type {
// Convenience pattern without an impl.
(
$(#[$attr:meta])*
$name:ident(
$(#[$type_attr:meta])*
$type:ty
)
) => {
new_type![
@new_type $(#[$attr])*,
$name(
$(#[$type_attr])*
$type
),
concat!(
"Create a new `",
stringify!($name),
"` to wrap the given `",
stringify!($type),
"`."
),
impl {}
];
};
// Main entry point with an impl.
(
$(#[$attr:meta])*
$name:ident(
$(#[$type_attr:meta])*
$type:ty
)
impl {
$($item:tt)*
}
) => {
new_type![
@new_type $(#[$attr])*,
$name(
$(#[$type_attr])*
$type
),
concat!(
"Create a new `",
stringify!($name),
"` to wrap the given `",
stringify!($type),
"`."
),
impl {
$($item)*
}
];
};
// Actual implementation, after stringifying the #[doc] attr.
(
@new_type $(#[$attr:meta])*,
$name:ident(
$(#[$type_attr:meta])*
$type:ty
),
$new_doc:expr,
impl {
$($item:tt)*
}
) => {
$(#[$attr])*
#[derive(Clone, Debug, PartialEq)]
pub struct $name(
$(#[$type_attr])*
$type
);
impl $name {
$($item)*
#[doc = $new_doc]
pub fn new(s: $type) -> Self {
$name(s)
}
}
impl Deref for $name {
type Target = $type;
fn deref(&self) -> &$type {
&self.0
}
}
impl Into<$type> for $name {
fn into(self) -> $type {
self.0
}
}
}
}
macro_rules! new_secret_type {
(
$(#[$attr:meta])*
$name:ident($type:ty)
) => {
new_secret_type![
$(#[$attr])*
$name($type)
impl {}
];
};
(
$(#[$attr:meta])*
$name:ident($type:ty)
impl {
$($item:tt)*
}
) => {
new_secret_type![
$(#[$attr])*,
$name($type),
concat!(
"Create a new `",
stringify!($name),
"` to wrap the given `",
stringify!($type),
"`."
),
concat!("Get the secret contained within this `", stringify!($name), "`."),
impl {
$($item)*
}
];
};
(
$(#[$attr:meta])*,
$name:ident($type:ty),
$new_doc:expr,
$secret_doc:expr,
impl {
$($item:tt)*
}
) => {
$(
#[$attr]
)*
pub struct $name($type);
impl $name {
$($item)*
#[doc = $new_doc]
pub fn new(s: $type) -> Self {
$name(s)
}
///
#[doc = $secret_doc]
///
/// # Security Warning
///
/// Leaking this value may compromise the security of the OAuth2 flow.
///
pub fn secret(&self) -> &$type { &self.0 }
}
impl Debug for $name {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
write!(f, concat!(stringify!($name), "([redacted])"))
}
}
};
}
///
/// Creates a URL-specific new type
///
/// Types created by this macro enforce during construction that the contained value represents a
/// syntactically valid URL. However, comparisons and hashes of these types are based on the string
/// representation given during construction, disregarding any canonicalization performed by the
/// underlying `Url` struct. OpenID Connect requires certain URLs (e.g., ID token issuers) to be
/// compared exactly, without canonicalization.
///
/// In addition to the raw string representation, these types include a `url` method to retrieve a
/// parsed `Url` struct.
///
macro_rules! new_url_type {
// Convenience pattern without an impl.
(
$(#[$attr:meta])*
$name:ident
) => {
new_url_type![
@new_type_pub $(#[$attr])*,
$name,
concat!("Create a new `", stringify!($name), "` from a `String` to wrap a URL."),
concat!("Create a new `", stringify!($name), "` from a `Url` to wrap a URL."),
concat!("Return this `", stringify!($name), "` as a parsed `Url`."),
impl {}
];
};
// Main entry point with an impl.
(
$(#[$attr:meta])*
$name:ident
impl {
$($item:tt)*
}
) => {
new_url_type![
@new_type_pub $(#[$attr])*,
$name,
concat!("Create a new `", stringify!($name), "` from a `String` to wrap a URL."),
concat!("Create a new `", stringify!($name), "` from a `Url` to wrap a URL."),
concat!("Return this `", stringify!($name), "` as a parsed `Url`."),
impl {
$($item)*
}
];
};
// Actual implementation, after stringifying the #[doc] attr.
(
@new_type_pub $(#[$attr:meta])*,
$name:ident,
$new_doc:expr,
$from_url_doc:expr,
$url_doc:expr,
impl {
$($item:tt)*
}
) => {
$(#[$attr])*
#[derive(Clone)]
pub struct $name(Url, String);
impl $name {
#[doc = $new_doc]
pub fn new(url: String) -> Result<Self, ::url::ParseError> {
Ok($name(Url::parse(&url)?, url))
}
#[doc = $from_url_doc]
pub fn from_url(url: Url) -> Self {
let s = url.to_string();
Self(url, s)
}
#[doc = $url_doc]
pub fn url(&self) -> &Url {
return &self.0;
}
$($item)*
}
impl Deref for $name {
type Target = String;
fn deref(&self) -> &String {
&self.1
}
}
impl ::std::fmt::Debug for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {
let mut debug_trait_builder = f.debug_tuple(stringify!($name));
debug_trait_builder.field(&self.1);
debug_trait_builder.finish()
}
}
impl<'de> ::serde::Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::de::Deserializer<'de>,
{
struct UrlVisitor;
impl<'de> ::serde::de::Visitor<'de> for UrlVisitor {
type Value = $name;
fn expecting(
&self,
formatter: &mut ::std::fmt::Formatter
) -> ::std::fmt::Result {
formatter.write_str(stringify!($name))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: ::serde::de::Error,
{
$name::new(v.to_string()).map_err(E::custom)
}
}
deserializer.deserialize_str(UrlVisitor {})
}
}
impl ::serde::Serialize for $name {
fn serialize<SE>(&self, serializer: SE) -> Result<SE::Ok, SE::Error>
where
SE: ::serde::Serializer,
{
serializer.serialize_str(&self.1)
}
}
impl ::std::hash::Hash for $name {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) -> () {
::std::hash::Hash::hash(&(self.1), state);
}
}
impl Ord for $name {
fn cmp(&self, other: &$name) -> ::std::cmp::Ordering {
self.1.cmp(&other.1)
}
}
impl PartialOrd for $name {
fn partial_cmp(&self, other: &$name) -> Option<::std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for $name {
fn eq(&self, other: &$name) -> bool {
self.1 == other.1
}
}
impl Eq for $name {}
};
}
new_type![
///
/// Client identifier issued to the client during the registration process described by
/// [Section 2.2](https://tools.ietf.org/html/rfc6749#section-2.2).
///
#[derive(Deserialize, Serialize, Eq, Hash)]
ClientId(String)
];
new_url_type![
///
/// URL of the authorization server's authorization endpoint.
///
AuthUrl
];
new_url_type![
///
/// URL of the authorization server's token endpoint.
///
TokenUrl
];
new_url_type![
///
/// URL of the client's redirection endpoint.
///
RedirectUrl
];
new_url_type![
///
/// URL of the client's [RFC 7662 OAuth 2.0 Token Introspection](https://tools.ietf.org/html/rfc7662) endpoint.
///
IntrospectionUrl
];
new_url_type![
///
/// URL of the authorization server's RFC 7009 token revocation endpoint.
///
RevocationUrl
];
new_url_type![
///
/// URL of the client's device authorization endpoint.
///
DeviceAuthorizationUrl
];
new_url_type![
///
/// URL of the end-user verification URI on the authorization server.
///
EndUserVerificationUrl
];
new_type![
///
/// Authorization endpoint response (grant) type defined in
/// [Section 3.1.1](https://tools.ietf.org/html/rfc6749#section-3.1.1).
///
#[derive(Deserialize, Serialize, Eq, Hash)]
ResponseType(String)
];
new_type![
///
/// Resource owner's username used directly as an authorization grant to obtain an access
/// token.
///
#[derive(Deserialize, Serialize, Eq, Hash)]
ResourceOwnerUsername(String)
];
new_type![
///
/// Access token scope, as defined by the authorization server.
///
#[derive(Deserialize, Serialize, Eq, Hash)]
Scope(String)
];
impl AsRef<str> for Scope {
fn as_ref(&self) -> &str {
self
}
}
new_type![
///
/// Code Challenge Method used for [PKCE]((https://tools.ietf.org/html/rfc7636)) protection
/// via the `code_challenge_method` parameter.
///
#[derive(Deserialize, Serialize, Eq, Hash)]
PkceCodeChallengeMethod(String)
];
// This type intentionally does not implement Clone in order to make it difficult to reuse PKCE
// challenges across multiple requests.
new_secret_type![
///
/// Code Verifier used for [PKCE]((https://tools.ietf.org/html/rfc7636)) protection via the
/// `code_verifier` parameter. The value must have a minimum length of 43 characters and a
/// maximum length of 128 characters. Each character must be ASCII alphanumeric or one of
/// the characters "-" / "." / "_" / "~".
///
#[derive(Deserialize, Serialize)]
PkceCodeVerifier(String)
];
///
/// Code Challenge used for [PKCE]((https://tools.ietf.org/html/rfc7636)) protection via the
/// `code_challenge` parameter.
///
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct PkceCodeChallenge {
code_challenge: String,
code_challenge_method: PkceCodeChallengeMethod,
}
impl PkceCodeChallenge {
///
/// Generate a new random, base64-encoded SHA-256 PKCE code.
///
pub fn new_random_sha256() -> (Self, PkceCodeVerifier) {
Self::new_random_sha256_len(32)
}
///
/// Generate a new random, base64-encoded SHA-256 PKCE challenge code and verifier.
///
/// # Arguments
///
/// * `num_bytes` - Number of random bytes to generate, prior to base64-encoding.
/// The value must be in the range 32 to 96 inclusive in order to generate a verifier
/// with a suitable length.
///
/// # Panics
///
/// This method panics if the resulting PKCE code verifier is not of a suitable length
/// to comply with [RFC 7636](https://tools.ietf.org/html/rfc7636).
///
pub fn new_random_sha256_len(num_bytes: u32) -> (Self, PkceCodeVerifier) {
let code_verifier = Self::new_random_len(num_bytes);
(
Self::from_code_verifier_sha256(&code_verifier),
code_verifier,
)
}
///
/// Generate a new random, base64-encoded PKCE code verifier.
///
/// # Arguments
///
/// * `num_bytes` - Number of random bytes to generate, prior to base64-encoding.
/// The value must be in the range 32 to 96 inclusive in order to generate a verifier
/// with a suitable length.
///
/// # Panics
///
/// This method panics if the resulting PKCE code verifier is not of a suitable length
/// to comply with [RFC 7636](https://tools.ietf.org/html/rfc7636).
///
fn new_random_len(num_bytes: u32) -> PkceCodeVerifier {
// The RFC specifies that the code verifier must have "a minimum length of 43
// characters and a maximum length of 128 characters".
// This implies 32-96 octets of random data to be base64 encoded.
assert!(num_bytes >= 32 && num_bytes <= 96);
let random_bytes: Vec<u8> = (0..num_bytes).map(|_| thread_rng().gen::<u8>()).collect();
PkceCodeVerifier::new(base64::encode_config(
&random_bytes,
base64::URL_SAFE_NO_PAD,
))
}
///
/// Generate a SHA-256 PKCE code challenge from the supplied PKCE code verifier.
///
/// # Panics
///
/// This method panics if the supplied PKCE code verifier is not of a suitable length
/// to comply with [RFC 7636](https://tools.ietf.org/html/rfc7636).
///
pub fn from_code_verifier_sha256(code_verifier: &PkceCodeVerifier) -> Self {
// The RFC specifies that the code verifier must have "a minimum length of 43
// characters and a maximum length of 128 characters".
assert!(code_verifier.secret().len() >= 43 && code_verifier.secret().len() <= 128);
let digest = Sha256::digest(code_verifier.secret().as_bytes());
let code_challenge = base64::encode_config(&digest, base64::URL_SAFE_NO_PAD);
Self {
code_challenge,
code_challenge_method: PkceCodeChallengeMethod::new("S256".to_string()),
}
}
///
/// Generate a new random, base64-encoded PKCE code.
/// Use is discouraged unless the endpoint does not support SHA-256.
///
/// # Panics
///
/// This method panics if the supplied PKCE code verifier is not of a suitable length
/// to comply with [RFC 7636](https://tools.ietf.org/html/rfc7636).
///
#[cfg(feature = "pkce-plain")]
pub fn new_random_plain() -> (Self, PkceCodeVerifier) {
let code_verifier = Self::new_random_len(32);
(
Self::from_code_verifier_plain(&code_verifier),
code_verifier,
)
}
///
/// Generate a plain PKCE code challenge from the supplied PKCE code verifier.
/// Use is discouraged unless the endpoint does not support SHA-256.
///
/// # Panics
///
/// This method panics if the supplied PKCE code verifier is not of a suitable length
/// to comply with [RFC 7636](https://tools.ietf.org/html/rfc7636).
///
#[cfg(feature = "pkce-plain")]
pub fn from_code_verifier_plain(code_verifier: &PkceCodeVerifier) -> Self {
// The RFC specifies that the code verifier must have "a minimum length of 43
// characters and a maximum length of 128 characters".
assert!(code_verifier.secret().len() >= 43 && code_verifier.secret().len() <= 128);
let code_challenge = code_verifier.secret().clone();
Self {
code_challenge,
code_challenge_method: PkceCodeChallengeMethod::new("plain".to_string()),
}
}
///
/// Returns the PKCE code challenge as a string.
///
pub fn as_str(&self) -> &str {
&self.code_challenge
}
///
/// Returns the PKCE code challenge method as a string.
///
pub fn method(&self) -> &PkceCodeChallengeMethod {
&self.code_challenge_method
}
}
new_secret_type![
///
/// Client password issued to the client during the registration process described by
/// [Section 2.2](https://tools.ietf.org/html/rfc6749#section-2.2).
///
#[derive(Clone, Deserialize, Serialize)]
ClientSecret(String)
];
new_secret_type![
///
/// Value used for [CSRF](https://tools.ietf.org/html/rfc6749#section-10.12) protection
/// via the `state` parameter.
///
#[must_use]
#[derive(Clone, Deserialize, Serialize)]
CsrfToken(String)
impl {
///
/// Generate a new random, base64-encoded 128-bit CSRF token.
///
pub fn new_random() -> Self {
CsrfToken::new_random_len(16)
}
///
/// Generate a new random, base64-encoded CSRF token of the specified length.
///
/// # Arguments
///
/// * `num_bytes` - Number of random bytes to generate, prior to base64-encoding.
///
pub fn new_random_len(num_bytes: u32) -> Self {
let random_bytes: Vec<u8> = (0..num_bytes).map(|_| thread_rng().gen::<u8>()).collect();
CsrfToken::new(base64::encode_config(&random_bytes, base64::URL_SAFE_NO_PAD))
}
}
];
new_secret_type![
///
/// Authorization code returned from the authorization endpoint.
///
#[derive(Clone, Deserialize, Serialize)]
AuthorizationCode(String)
];
new_secret_type![
///
/// Refresh token used to obtain a new access token (if supported by the authorization server).
///
#[derive(Clone, Deserialize, Serialize)]
RefreshToken(String)
];
new_secret_type![
///
/// Access token returned by the token endpoint and used to access protected resources.
///
#[derive(Clone, Deserialize, Serialize)]
AccessToken(String)
];
new_secret_type![
///
/// Resource owner's password used directly as an authorization grant to obtain an access
/// token.
///
#[derive(Clone)]
ResourceOwnerPassword(String)
];
new_secret_type![
///
/// Device code returned by the device authorization endpoint and used to query the token endpoint.
///
#[derive(Clone, Deserialize, Serialize)]
DeviceCode(String)
];
new_secret_type![
///
/// Verification URI returned by the device authorization endpoint and visited by the user
/// to authorize. Contains the user code.
///
#[derive(Clone, Deserialize, Serialize)]
VerificationUriComplete(String)
];
new_secret_type![
///
/// User code returned by the device authorization endpoint and used by the user to authorize at
/// the verification URI.
///
#[derive(Clone, Deserialize, Serialize)]
UserCode(String)
];

73
zeroidc/vendor/oauth2/src/ureq.rs vendored Normal file
View File

@@ -0,0 +1,73 @@
use http::{
header::{HeaderMap, HeaderValue, CONTENT_TYPE},
method::Method,
status::StatusCode,
};
use super::{HttpRequest, HttpResponse};
///
/// Error type returned by failed ureq HTTP requests.
///
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Non-ureq HTTP error.
#[error("HTTP error")]
Http(#[from] http::Error),
/// IO error
#[error("IO error")]
IO(#[from] std::io::Error),
/// Other error.
#[error("Other error: {}", _0)]
Other(String),
/// Error returned by ureq crate.
// boxed due to https://github.com/algesten/ureq/issues/296
#[error("ureq request failed")]
Ureq(#[from] Box<ureq::Error>),
}
///
/// Synchronous HTTP client for ureq.
///
pub fn http_client(request: HttpRequest) -> Result<HttpResponse, Error> {
let mut req = if let Method::POST = request.method {
ureq::post(&request.url.to_string())
} else {
ureq::get(&request.url.to_string())
};
for (name, value) in request.headers {
if let Some(name) = name {
req = req.set(
&name.to_string(),
value.to_str().map_err(|_| {
Error::Other(format!(
"invalid {} header value {:?}",
name,
value.as_bytes()
))
})?,
);
}
}
let response = if let Method::POST = request.method {
req.send(&*request.body)
} else {
req.call()
}
.map_err(Box::new)?;
Ok(HttpResponse {
status_code: StatusCode::from_u16(response.status())
.map_err(|err| Error::Http(err.into()))?,
headers: vec![(
CONTENT_TYPE,
HeaderValue::from_str(response.content_type())
.map_err(|err| Error::Http(err.into()))?,
)]
.into_iter()
.collect::<HeaderMap>(),
body: response.into_string()?.as_bytes().into(),
})
}