Merge branch 'generic_oauth2_clean'

This commit is contained in:
Sebastian Thiel
2022-09-25 10:30:00 +08:00
8 changed files with 119 additions and 15 deletions

View File

@@ -19,6 +19,7 @@ path = "src/rust/lib.rs"
[dependencies]
clap = "2"
http = "^0.2"
hyper = "0.14"
mime = "0.2"
rustc-serialize = "*"
@@ -30,4 +31,4 @@ strsim = "*"
tokio = "^ 1.0"
hyper-rustls = "^0.22"
itertools = "^ 0.10"
tower-service = "^0.3"
tower-service = "^0.3"

View File

@@ -1,5 +1,11 @@
<a name="api-v2.0.0:cli-v2.0.0"></a>
## api/cli-vNEXT (YYYY-MM-DD)
- New `GetToken` trait for custom ways of specifying a token. The latter can now be a String or be
`NoToken` as well.
- Upgrade `mkdocs` to a more recent version that doesn't break in more recent python interpreters.
## api/cli-v3.0.0 (2022-3-8)
- Support for yup-oauth 0.6

View File

@@ -1,6 +1,6 @@
mako==1.1.4
mako==1.2.2
pyyaml<6
mkdocs==0.11
mkdocs==1.3.1
pytest
pytest-cov
codecov

View File

@@ -26,12 +26,16 @@ path = "src/main.rs"
% endif
[dependencies]
## TODO: temporary, remove when yup-oauth2 is optional
anyhow = "^ 1.0"
hyper-rustls = "0.23.0"
## Must match the one hyper uses, otherwise there are duplicate similarly named `Mime` structs
mime = "^ 0.2.0"
serde = "^ 1.0"
serde_json = "^ 1.0"
serde_derive = "^ 1.0"
## TODO: Make yup-oauth2 optional
## yup-oauth2 = { version = "^ 7.0", optional = true }
yup-oauth2 = "^ 7.0"
itertools = "^ 0.10"
% for dep in cargo.get('dependencies', list()):
@@ -41,7 +45,7 @@ ${dep}
<%
api_name = util.library_name()
crate_name_we_depend_on = None
if make.depends_on_suffix is not None:
crate_name_we_depend_on = library_to_crate_name(api_name, suffix=make.depends_on_suffix)
%>\
@@ -53,3 +57,7 @@ ${dep}
path = "../${api_name}"
version = "${util.crate_version()}"
% endif
## TODO: Make yup-oauth2 optional
# [features]
# default = ["yup-oauth2"]

View File

@@ -30,7 +30,7 @@ use http::Uri;
use hyper::client::connect;
use tokio::io::{AsyncRead, AsyncWrite};
use tower_service;
use crate::client;
use crate::{client, client::GetToken};
// ##############
// UTILITIES ###
@@ -55,7 +55,7 @@ ${lib.hub_usage_example(c)}\
#[derive(Clone)]
pub struct ${hub_type}${ht_params} {
pub client: hyper::Client<S, hyper::body::Body>,
pub auth: oauth2::authenticator::Authenticator<S>,
pub auth: Box<dyn client::GetToken>,
_user_agent: String,
_base_url: String,
_root_url: String,
@@ -65,10 +65,10 @@ impl<'a, ${', '.join(HUB_TYPE_PARAMETERS)}> client::Hub for ${hub_type}${ht_para
impl<'a, ${', '.join(HUB_TYPE_PARAMETERS)}> ${hub_type}${ht_params} {
pub fn new(client: hyper::Client<S, hyper::body::Body>, authenticator: oauth2::authenticator::Authenticator<S>) -> ${hub_type}${ht_params} {
pub fn new<A: 'static + client::GetToken>(client: hyper::Client<S, hyper::body::Body>, auth: A) -> ${hub_type}${ht_params} {
${hub_type} {
client,
auth: authenticator,
auth: Box::new(auth),
_user_agent: "${default_user_agent}".to_string(),
_base_url: "${baseUrl}".to_string(),
_root_url: "${rootUrl}".to_string(),

View File

@@ -706,10 +706,21 @@ else {
loop {
% if default_scope:
let token = match ${auth_call}.token(&self.${api.properties.scopes}.keys().collect::<Vec<_>>()[..]).await {
Ok(token) => token.clone(),
let token = match ${auth_call}.get_token(&self.${api.properties.scopes}.keys().map(String::as_str).collect::<Vec<_>>()[..]).await {
// TODO: remove Ok / Err branches
Ok(Some(token)) => token.clone(),
Ok(None) => {
let err = oauth2::Error::OtherError(anyhow::Error::msg("unknown error occurred while generating oauth2 token"));
match dlg.token(&err) {
Some(token) => token,
None => {
${delegate_finish}(false);
return Err(client::Error::MissingToken(err))
}
}
}
Err(err) => {
match dlg.token(&err) {
match dlg.token(&err) {
Some(token) => token,
None => {
${delegate_finish}(false);

View File

@@ -14,11 +14,12 @@ repo_url: ${util.github_source_root_url()}
docs_dir: ${mkdocs.docs_dir}
site_dir: ${mkdocs.site_dir}
pages:
- ['index.md', 'Home']
nav:
- Home: 'index.md'
% for resource in sorted(c.rta_map.keys()):
- '${pretty(resource)}':
% for method in sorted(c.rta_map[resource]):
- ['${subcommand_md_filename(resource, method)}', '${pretty(resource)}', '${pretty(method)}']
- '${pretty(method)}': '${subcommand_md_filename(resource, method)}'
% endfor # each method
% endfor # each resource

View File

@@ -1,7 +1,9 @@
use std::error;
use std::error::Error as StdError;
use std::fmt::{self, Display};
use std::future::Future;
use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};
use std::pin::Pin;
use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;
@@ -105,12 +107,13 @@ pub trait Delegate: Send {
None
}
// TODO: Remove oauth2::Error
/// Called whenever the Authenticator didn't yield a token. The delegate
/// may attempt to provide one, or just take it as a general information about the
/// impending failure.
/// The given Error provides information about why the token couldn't be acquired in the
/// first place
fn token(&mut self, err: &oauth2::Error) -> Option<oauth2::AccessToken> {
fn token(&mut self, err: &oauth2::Error) -> Option<String> {
let _ = err;
None
}
@@ -227,6 +230,7 @@ pub enum Error {
/// Neither through the authenticator, nor through the Delegate.
MissingAPIKey,
// TODO: Remove oauth2::Error
/// We required a Token, but didn't get one from the Authenticator
MissingToken(oauth2::Error),
@@ -272,6 +276,7 @@ impl Display for Error {
writeln!(f, "Bad Request: {}", message)?;
Ok(())
}
// TODO: Remove oauth2::Error
Error::MissingToken(ref err) => {
writeln!(f, "Token retrieval failed with error: {}", err)
}
@@ -785,3 +790,75 @@ pub async fn get_body_as_string(res_body: &mut hyper::Body) -> String {
let res_body_string = String::from_utf8_lossy(&res_body_buf);
res_body_string.to_string()
}
// TODO: Simplify this to Option<String>
type TokenResult = std::result::Result<Option<String>, oauth2::Error>;
pub trait GetToken: GetTokenClone {
/// Called whenever there is the need for an oauth token after
/// the official authenticator implementation didn't provide one, for some reason.
/// If this method returns None as well, the underlying operation will fail
fn get_token<'a>(&'a self, _scopes: &'a [&str]) -> Pin<Box<dyn Future<Output=TokenResult> + 'a>> {
Box::pin(async move { Ok(None) })
}
}
pub trait GetTokenClone {
fn clone_box(&self) -> Box<dyn GetToken>;
}
impl<T> GetTokenClone for T
where
T: 'static + GetToken + Clone,
{
fn clone_box(&self) -> Box<dyn GetToken> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn GetToken> {
fn clone(&self) -> Box<dyn GetToken> {
self.clone_box()
}
}
impl GetToken for String {
fn get_token<'a>(&'a self, _scopes: &'a [&str]) -> Pin<Box<dyn Future<Output=TokenResult> + 'a>> {
Box::pin(async move { Ok(Some(self.clone())) })
}
}
/// In the event that the API endpoint does not require an oauth2 token, `NoToken` should be provided to the hub to avoid specifying an
/// authenticator.
#[derive(Default, Clone)]
pub struct NoToken;
impl GetToken for NoToken {}
// TODO: Make this optional
// #[cfg(feature = "yup-oauth2")]
mod yup_oauth2_impl {
use core::future::Future;
use core::pin::Pin;
use super::{GetToken, TokenResult};
use tower_service::Service;
use yup_oauth2::authenticator::Authenticator;
use tokio::io::{AsyncRead, AsyncWrite};
use http::Uri;
use hyper::client::connect::Connection;
impl<S> GetToken for Authenticator<S> where
S: Service<Uri> + Clone + Send + Sync + 'static,
S::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static,
S::Future: Send + Unpin + 'static,
S::Error: Into<Box<dyn std::error::Error + Send + Sync>> {
fn get_token<'a>(&'a self, scopes: &'a [&str]) -> Pin<Box<dyn Future<Output=TokenResult> + 'a>> {
Box::pin(async move {
self.token(scopes).await.map(|t| Some(t.as_str().to_owned()))
})
}
}
}