Skip to content

Commit

Permalink
--all-features and --no-default-features
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nagisa committed Mar 19, 2021
1 parent 376de2a commit be7d072
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 60 deletions.
29 changes: 28 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 13 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down Expand Up @@ -38,3 +47,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"]
62 changes: 39 additions & 23 deletions src/authenticator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -157,6 +162,8 @@ 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,
Expand All @@ -180,6 +187,8 @@ 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> {
Expand All @@ -200,6 +209,8 @@ 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> {
Expand Down Expand Up @@ -249,6 +260,8 @@ 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,
Expand Down Expand Up @@ -475,27 +488,46 @@ 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()
Expand All @@ -504,27 +536,11 @@ impl HyperClientBuilder for DefaultHyperClient {
}
}

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
}
}

enum StorageType {
Memory,
Disk(PathBuf),
}

#[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>>()
Expand Down
5 changes: 3 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 5 additions & 3 deletions src/installed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
//! ```
//!
#![deny(missing_docs)]
#![cfg_attr(yup_oauth2_docsrs, feature(doc_cfg))]

pub mod authenticator;
pub mod authenticator_delegate;
mod device;
Expand Down
28 changes: 11 additions & 17 deletions src/service_account.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
//! 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
//! Applications](https://developers.google.com/identity/protocols/OAuth2ServiceAccount)
//! - [JSON Web Tokens](https://jwt.io/)
//!
//! Copyright (c) 2016 Google Inc ([email protected]).
//!
use crate::error::Error;
use crate::types::TokenInfo;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -210,31 +211,24 @@ impl ServiceAccountFlow {

#[cfg(test)]
mod tests {

use super::*;
use crate::helper::read_service_account_key;
#[cfg(not(feature = "hyper-tls"))]
use hyper_rustls::HttpsConnector;
#[cfg(feature = "hyper-tls")]
use hyper_tls::HttpsConnector;
use crate::authenticator::HyperClientBuilder;

// Valid but deactivated key.
const TEST_PRIVATE_KEY_PATH: &'static str = "examples/Sanguine-69411a0c0eea.json";

// 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"])
Expand Down
20 changes: 6 additions & 14 deletions tests/tests.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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.
Expand Down Expand Up @@ -221,10 +216,7 @@ 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()),
DefaultHyperClient.build_hyper_client()
)));

builder = if let Some(filename) = filename {
Expand Down Expand Up @@ -323,7 +315,7 @@ async fn test_installed_error() {

async fn create_service_account_auth(
server: &Server,
) -> Authenticator<HttpsConnector<HttpConnector>> {
) -> DefaultAuthenticator {
let key: ServiceAccountKey = parse_json!({
"type": "service_account",
"project_id": "yup-test-243420",
Expand Down

0 comments on commit be7d072

Please sign in to comment.