--all-features and --no-default-features

This adjusts the code and documentation for `--all-features` and
`--no-default-features` to work correctly. With `--no-default-features`
no `DefaultAuthenticator` is made available. Users are in control of
picking the `Connector` they want to use, and are not forced to stomach
a dependency on `rustls` or `hyper-tls` if their TLS implementation of
choice doesn't happen to match one of the two.

To indicate this, the unstable `doc_cfg` feature is used to build
documentation on docs.rs. That way the generated documentation has
notices on these types that look as such:

> This is supported on crate features hyper-rustls or hyper-tls only.

Additionally this functionality is tested via additional coverage in the
Actions' CI.
This commit is contained in:
Simonas Kazlauskas
2021-03-15 17:51:08 +02:00
parent e63aa4b843
commit d385601433
8 changed files with 132 additions and 49 deletions

View File

@@ -4,17 +4,44 @@ name: Actions CI
jobs:
build_and_test:
name: yup-oauth2
name: yup-oauth2
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
default: true
- uses: actions-rs/cargo@v1
with:
command: test
- uses: actions-rs/cargo@v1
with:
command: test
args: --all-features
- uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features --tests --examples
- uses: actions-rs/cargo@v1
with:
command: build
args: --examples
doc:
name: yup-oauth2
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
default: true
- uses: actions-rs/cargo@v1
with:
command: doc
args: --all-features
env:
RUSTDOCFLAGS: --cfg yup_oauth2_docsrs

View File

@@ -10,6 +10,15 @@ keywords = ["google", "oauth", "v2"]
license = "MIT OR Apache-2.0"
edition = "2018"
[[example]]
name = "custom_flow"
required-features = ["hyper-rustls"]
[[test]]
name = "tests"
required-features = ["hyper-rustls"]
[features]
default = ["hyper-rustls"]
@@ -41,3 +50,7 @@ webbrowser = "0.5"
[workspace]
members = ["examples/test-installed/", "examples/test-svc-acct/", "examples/test-device/", "examples/service_account", "examples/drive_example"]
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "yup_oauth2_docsrs"]

View File

@@ -132,6 +132,11 @@ where
}
}
enum StorageType {
Memory,
Disk(PathBuf),
}
/// Configure an Authenticator using the builder pattern.
pub struct AuthenticatorBuilder<C, F> {
hyper_client_builder: C,
@@ -157,6 +162,11 @@ pub struct AuthenticatorBuilder<C, F> {
pub struct InstalledFlowAuthenticator;
impl InstalledFlowAuthenticator {
/// Use the builder pattern to create an Authenticator that uses the installed flow.
#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
#[cfg_attr(
yup_oauth2_docsrs,
doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls")))
)]
pub fn builder(
app_secret: ApplicationSecret,
method: InstalledFlowReturnMethod,
@@ -180,6 +190,11 @@ impl InstalledFlowAuthenticator {
pub struct DeviceFlowAuthenticator;
impl DeviceFlowAuthenticator {
/// Use the builder pattern to create an Authenticator that uses the device flow.
#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
#[cfg_attr(
yup_oauth2_docsrs,
doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls")))
)]
pub fn builder(
app_secret: ApplicationSecret,
) -> AuthenticatorBuilder<DefaultHyperClient, DeviceFlow> {
@@ -200,6 +215,11 @@ impl DeviceFlowAuthenticator {
pub struct ServiceAccountAuthenticator;
impl ServiceAccountAuthenticator {
/// Use the builder pattern to create an Authenticator that uses a service account.
#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
#[cfg_attr(
yup_oauth2_docsrs,
doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls")))
)]
pub fn builder(
service_account_key: ServiceAccountKey,
) -> AuthenticatorBuilder<DefaultHyperClient, ServiceAccountFlowOpts> {
@@ -250,6 +270,11 @@ impl<C, F> AuthenticatorBuilder<C, F> {
})
}
#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
#[cfg_attr(
yup_oauth2_docsrs,
doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls")))
)]
fn with_auth_flow(auth_flow: F) -> AuthenticatorBuilder<DefaultHyperClient, F> {
AuthenticatorBuilder {
hyper_client_builder: DefaultHyperClient,
@@ -484,27 +509,58 @@ pub trait HyperClientBuilder {
fn build_hyper_client(self) -> hyper::Client<Self::Connector>;
}
#[cfg(not(feature = "hyper-tls"))]
impl<C> HyperClientBuilder for hyper::Client<C>
where
C: hyper::client::connect::Connect + Clone + Send + Sync + 'static,
{
type Connector = C;
fn build_hyper_client(self) -> hyper::Client<C> {
self
}
}
#[cfg(feature = "hyper-rustls")]
#[cfg_attr(
yup_oauth2_docsrs,
doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls")))
)]
/// Default authenticator type
pub type DefaultAuthenticator =
Authenticator<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>>;
#[cfg(feature = "hyper-tls")]
#[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))]
#[cfg_attr(
yup_oauth2_docsrs,
doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls")))
)]
/// Default authenticator type
pub type DefaultAuthenticator =
Authenticator<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>;
/// The builder value used when the default hyper client should be used.
#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
#[cfg_attr(
yup_oauth2_docsrs,
doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls")))
)]
pub struct DefaultHyperClient;
#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
#[cfg_attr(
yup_oauth2_docsrs,
doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls")))
)]
impl HyperClientBuilder for DefaultHyperClient {
#[cfg(not(feature = "hyper-tls"))]
#[cfg(feature = "hyper-rustls")]
type Connector = hyper_rustls::HttpsConnector<hyper::client::connect::HttpConnector>;
#[cfg(feature = "hyper-tls")]
#[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))]
type Connector = hyper_tls::HttpsConnector<hyper::client::connect::HttpConnector>;
fn build_hyper_client(self) -> hyper::Client<Self::Connector> {
#[cfg(not(feature = "hyper-tls"))]
#[cfg(feature = "hyper-rustls")]
let connector = hyper_rustls::HttpsConnector::with_native_roots();
#[cfg(feature = "hyper-tls")]
#[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))]
let connector = hyper_tls::HttpsConnector::new();
hyper::Client::builder()
@@ -537,8 +593,8 @@ enum StorageType {
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
fn ensure_send_sync() {
fn is_send_sync<T: Send + Sync>() {}
is_send_sync::<Authenticator<<DefaultHyperClient as HyperClientBuilder>::Connector>>()

View File

@@ -8,8 +8,9 @@ use std::io;
use serde::Deserialize;
/// Error returned by the authorization server.
/// https://tools.ietf.org/html/rfc6749#section-5.2
/// https://tools.ietf.org/html/rfc8628#section-3.5
///
/// <https://tools.ietf.org/html/rfc6749#section-5.2>
/// <https://tools.ietf.org/html/rfc8628#section-3.5>
#[derive(Deserialize, Debug, PartialEq, Eq)]
pub struct AuthError {
/// Error code from the server.

View File

@@ -60,7 +60,9 @@ where
})
}
/// cf. https://developers.google.com/identity/protocols/OAuth2InstalledApp#choosingredirecturi
/// Method by which the user agent return token to this application.
///
/// cf. <https://developers.google.com/identity/protocols/OAuth2InstalledApp#choosingredirecturi>
pub enum InstalledFlowReturnMethod {
/// Involves showing a URL to the user and asking to copy a code from their browser
/// (default)
@@ -71,8 +73,8 @@ pub enum InstalledFlowReturnMethod {
}
/// InstalledFlowImpl provides tokens for services that follow the "Installed" OAuth flow. (See
/// https://www.oauth.com/oauth2-servers/authorization/,
/// https://developers.google.com/identity/protocols/OAuth2InstalledApp).
/// <https://www.oauth.com/oauth2-servers/authorization/>,
/// <https://developers.google.com/identity/protocols/OAuth2InstalledApp>).
pub struct InstalledFlow {
pub(crate) app_secret: ApplicationSecret,
pub(crate) method: InstalledFlowReturnMethod,

View File

@@ -69,6 +69,8 @@
//! ```
//!
#![deny(missing_docs)]
#![cfg_attr(yup_oauth2_docsrs, feature(doc_cfg))]
pub mod authenticator;
pub mod authenticator_delegate;
mod device;

View File

@@ -1,7 +1,8 @@
//! This module provides a token source (`GetToken`) that obtains tokens for service accounts.
//! This module provides a flow that obtains tokens for service accounts.
//!
//! Service accounts are usually used by software (i.e., non-human actors) to get access to
//! resources. Currently, this module only works with RS256 JWTs, which makes it at least suitable for
//! authentication with Google services.
//! resources. Currently, this module only works with RS256 JWTs, which makes it at least suitable
//! for authentication with Google services.
//!
//! Resources:
//! - [Using OAuth 2.0 for Server to Server
@@ -9,7 +10,6 @@
//! - [JSON Web Tokens](https://jwt.io/)
//!
//! Copyright (c) 2016 Google Inc (lewinb@google.com).
//!
use crate::error::Error;
use crate::types::TokenInfo;
@@ -54,8 +54,9 @@ fn decode_rsa_key(pem_pkcs8: &str) -> Result<PrivateKey, io::Error> {
}
}
/// JSON schema of secret service account key. You can obtain the key from
/// the Cloud Console at https://console.cloud.google.com/.
/// JSON schema of secret service account key.
///
/// You can obtain the key from the [Cloud Console](https://console.cloud.google.com/).
///
/// You can use `helpers::read_service_account_key()` as a quick way to read a JSON client
/// secret into a ServiceAccountKey.
@@ -210,12 +211,10 @@ impl ServiceAccountFlow {
#[cfg(test)]
mod tests {
use super::*;
use crate::authenticator::HyperClientBuilder;
use crate::helper::read_service_account_key;
#[cfg(not(feature = "hyper-tls"))]
use hyper_rustls::HttpsConnector;
#[cfg(feature = "hyper-tls")]
use hyper_tls::HttpsConnector;
// Valid but deactivated key.
const TEST_PRIVATE_KEY_PATH: &'static str = "examples/Sanguine-69411a0c0eea.json";
@@ -223,18 +222,13 @@ mod tests {
// Uncomment this test to verify that we can successfully obtain tokens.
//#[tokio::test]
#[allow(dead_code)]
#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
async fn test_service_account_e2e() {
let key = read_service_account_key(TEST_PRIVATE_KEY_PATH)
.await
.unwrap();
let acc = ServiceAccountFlow::new(ServiceAccountFlowOpts { key, subject: None }).unwrap();
#[cfg(not(feature = "hyper-tls"))]
let https = HttpsConnector::with_native_roots();
#[cfg(feature = "hyper-tls")]
let https = HttpsConnector::new();
let client = hyper::Client::builder()
.pool_max_idle_per_host(0)
.build::<_, hyper::Body>(https);
let client = crate::authenticator::DefaultHyperClient.build_hyper_client();
println!(
"{:?}",
acc.token(&client, &["https://www.googleapis.com/auth/pubsub"])

View File

@@ -1,5 +1,5 @@
use yup_oauth2::{
authenticator::Authenticator,
authenticator::{DefaultAuthenticator, DefaultHyperClient, HyperClientBuilder},
authenticator_delegate::{DeviceAuthResponse, DeviceFlowDelegate, InstalledFlowDelegate},
error::{AuthError, AuthErrorCode},
ApplicationSecret, DeviceFlowAuthenticator, Error, InstalledFlowAuthenticator,
@@ -11,12 +11,7 @@ use std::path::PathBuf;
use std::pin::Pin;
use httptest::{matchers::*, responders::json_encoded, Expectation, Server};
use hyper::client::connect::HttpConnector;
use hyper::Uri;
#[cfg(not(feature = "hyper-tls"))]
use hyper_rustls::HttpsConnector;
#[cfg(feature = "hyper-tls")]
use hyper_tls::HttpsConnector;
use url::form_urlencoded;
/// Utility function for parsing json. Useful in unit tests. Simply wrap the
@@ -27,7 +22,7 @@ macro_rules! parse_json {
}
}
async fn create_device_flow_auth(server: &Server) -> Authenticator<HttpsConnector<HttpConnector>> {
async fn create_device_flow_auth(server: &Server) -> DefaultAuthenticator {
let app_secret: ApplicationSecret = parse_json!({
"client_id": "902216714886-k2v9uei3p1dk6h686jbsn9mo96tnbvto.apps.googleusercontent.com",
"project_id": "yup-test-243420",
@@ -166,7 +161,7 @@ async fn create_installed_flow_auth(
server: &Server,
method: InstalledFlowReturnMethod,
filename: Option<PathBuf>,
) -> Authenticator<HttpsConnector<HttpConnector>> {
) -> DefaultAuthenticator {
let app_secret: ApplicationSecret = parse_json!({
"client_id": "902216714886-k2v9uei3p1dk6h686jbsn9mo96tnbvto.apps.googleusercontent.com",
"project_id": "yup-test-243420",
@@ -176,7 +171,7 @@ async fn create_installed_flow_auth(
"client_secret": "iuMPN6Ne1PD7cos29Tk9rlqH",
"redirect_uris": ["urn:ietf:wg:oauth:2.0:oob","http://localhost"],
});
struct FD(hyper::Client<HttpsConnector<HttpConnector>>);
struct FD(hyper::Client<<DefaultHyperClient as HyperClientBuilder>::Connector>);
impl InstalledFlowDelegate for FD {
/// Depending on need_code, return the pre-set code or send the code to the server at
/// the redirect_uri given in the url.
@@ -219,13 +214,8 @@ async fn create_installed_flow_auth(
}
}
let mut builder =
InstalledFlowAuthenticator::builder(app_secret, method).flow_delegate(Box::new(FD(
#[cfg(not(feature = "hyper-tls"))]
hyper::Client::builder().build(HttpsConnector::with_native_roots()),
#[cfg(feature = "hyper-tls")]
hyper::Client::builder().build(HttpsConnector::new()),
)));
let mut builder = InstalledFlowAuthenticator::builder(app_secret, method)
.flow_delegate(Box::new(FD(DefaultHyperClient.build_hyper_client())));
builder = if let Some(filename) = filename {
builder.persist_tokens_to_disk(filename)
@@ -321,9 +311,7 @@ async fn test_installed_error() {
assert!(format!("{}", tokr.unwrap_err()).contains("invalid_code"));
}
async fn create_service_account_auth(
server: &Server,
) -> Authenticator<HttpsConnector<HttpConnector>> {
async fn create_service_account_auth(server: &Server) -> DefaultAuthenticator {
let key: ServiceAccountKey = parse_json!({
"type": "service_account",
"project_id": "yup-test-243420",