Merge pull request #23 from programble/feature/dyn-struct

Providers as values
This commit is contained in:
Curtis McEnroe 2017-08-23 14:44:01 -04:00 committed by GitHub
commit d3a1524168
9 changed files with 95 additions and 80 deletions

View File

@ -14,10 +14,11 @@ fn main() {
let connector = HttpsConnector::new(tls);
let https = hyper::Client::with_connector(connector);
let client = Client::<GitHub>::new(
let client = Client::new(
GitHub,
String::from("01774654cd9a6051e478"),
String::from("9f14d16d95d605e715ec1a9aecec220d2565fd5c"),
Some(String::from("https://cmcenroe.me/oauth2-paste/"))
Some(String::from("https://cmcenroe.me/oauth2-paste/")),
);
let auth_uri = client.auth_uri(Some("user"), None).unwrap();

View File

@ -14,7 +14,8 @@ fn main() {
let connector = HttpsConnector::new(tls);
let https = hyper::Client::with_connector(connector);
let client = Client::<Installed>::new(
let client = Client::new(
Installed,
String::from("143225766783-ip2d9qv6sdr37276t77luk6f7bhd6bj5.apps.googleusercontent.com"),
String::from("3kZ5WomzHFlN2f_XbhkyPd3o"),
Some(String::from(REDIRECT_URI_OOB)),

View File

@ -14,7 +14,8 @@ fn main() {
let connector = HttpsConnector::new(tls);
let https = hyper::Client::with_connector(connector);
let client = Client::<Web>::new(
let client = Client::new(
Web,
String::from("143225766783-0h4h5ktpvhc7kqp6ohbpd2sssqrap57n.apps.googleusercontent.com"),
String::from("7Xjn-vRN-8qsz3Zh9zZGkHsM"),
Some(String::from("https://cmcenroe.me/oauth2-paste/")),

View File

@ -14,7 +14,8 @@ fn main() {
let connector = HttpsConnector::new(tls);
let https = hyper::Client::with_connector(connector);
let client = Client::<Imgur>::new(
let client = Client::new(
Imgur,
String::from("505c8ca804230e0"),
String::from("c898d8cf28404102752b2119a3a1c6aab49899c8"),
Some(String::from("https://cmcenroe.me/oauth2-paste/"))

View File

@ -5,8 +5,6 @@ mod error;
pub mod response;
pub use self::error::ClientError;
use std::marker::PhantomData;
use hyper::{self, header, mime};
use serde_json::{self, Value};
use url::Url;
@ -19,7 +17,10 @@ use token::{Token, Lifetime, Refresh};
/// OAuth 2.0 client.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Client<P: Provider> {
pub struct Client<P> {
/// OAuth provider.
pub provider: P,
/// Client ID.
pub client_id: String,
@ -28,8 +29,6 @@ pub struct Client<P: Provider> {
/// Redirect URI.
pub redirect_uri: Option<String>,
provider: PhantomData<P>,
}
impl<P: Provider> Client<P> {
@ -41,18 +40,24 @@ impl<P: Provider> Client<P> {
/// use inth_oauth2::Client;
/// use inth_oauth2::provider::google::Installed;
///
/// let client = Client::<Installed>::new(
/// let client = Client::new(
/// Installed,
/// String::from("CLIENT_ID"),
/// String::from("CLIENT_SECRET"),
/// Some(String::from("urn:ietf:wg:oauth:2.0:oob"))
/// Some(String::from("urn:ietf:wg:oauth:2.0:oob")),
/// );
/// ```
pub fn new(client_id: String, client_secret: String, redirect_uri: Option<String>) -> Self {
pub fn new(
provider: P,
client_id: String,
client_secret: String,
redirect_uri: Option<String>,
) -> Self {
Client {
client_id: client_id,
client_secret: client_secret,
redirect_uri: redirect_uri,
provider: PhantomData,
provider,
client_id,
client_secret,
redirect_uri,
}
}
@ -66,20 +71,21 @@ impl<P: Provider> Client<P> {
/// use inth_oauth2::Client;
/// use inth_oauth2::provider::google::Installed;
///
/// let client = Client::<Installed>::new(
/// let client = Client::new(
/// Installed,
/// String::from("CLIENT_ID"),
/// String::from("CLIENT_SECRET"),
/// Some(String::from("urn:ietf:wg:oauth:2.0:oob"))
/// Some(String::from("urn:ietf:wg:oauth:2.0:oob")),
/// );
///
/// let auth_uri = client.auth_uri(
/// Some("https://www.googleapis.com/auth/userinfo.email"),
/// None
/// None,
/// );
/// ```
pub fn auth_uri(&self, scope: Option<&str>, state: Option<&str>) -> Result<Url, ClientError>
{
let mut uri = Url::parse(P::auth_uri())?;
let mut uri = Url::parse(self.provider.auth_uri())?;
{
let mut query = uri.query_pairs_mut();
@ -101,12 +107,12 @@ impl<P: Provider> Client<P> {
Ok(uri)
}
fn post_token<'a>(
&'a self,
fn post_token(
&self,
http_client: &hyper::Client,
mut body: Serializer<String>
mut body: Serializer<String>,
) -> Result<Value, ClientError> {
if P::credentials_in_body() {
if self.provider.credentials_in_body() {
body.append_pair("client_id", &self.client_id);
body.append_pair("client_secret", &self.client_secret);
}
@ -122,7 +128,7 @@ impl<P: Provider> Client<P> {
]);
let body = body.finish();
let request = http_client.post(P::token_uri())
let request = http_client.post(self.provider.token_uri())
.header(auth_header)
.header(accept_header)
.header(header::ContentType::form_url_encoded())
@ -146,7 +152,7 @@ impl<P: Provider> Client<P> {
pub fn request_token(
&self,
http_client: &hyper::Client,
code: &str
code: &str,
) -> Result<P::Token, ClientError> {
let mut body = Serializer::new(String::new());
body.append_pair("grant_type", "authorization_code");
@ -162,7 +168,7 @@ impl<P: Provider> Client<P> {
}
}
impl<P: Provider> Client<P> where P::Token: Token<Refresh> {
impl<P> Client<P> where P: Provider, P::Token: Token<Refresh> {
/// Refreshes an access token.
///
/// See [RFC 6749, section 6](http://tools.ietf.org/html/rfc6749#section-6).
@ -170,7 +176,7 @@ impl<P: Provider> Client<P> where P::Token: Token<Refresh> {
&self,
http_client: &hyper::Client,
token: P::Token,
scope: Option<&str>
scope: Option<&str>,
) -> Result<P::Token, ClientError> {
let mut body = Serializer::new(String::new());
body.append_pair("grant_type", "refresh_token");
@ -186,7 +192,11 @@ impl<P: Provider> Client<P> where P::Token: Token<Refresh> {
}
/// Ensures an access token is valid by refreshing it if necessary.
pub fn ensure_token(&self, http_client: &hyper::Client, token: P::Token) -> Result<P::Token, ClientError> {
pub fn ensure_token(
&self,
http_client: &hyper::Client,
token: P::Token,
) -> Result<P::Token, ClientError> {
if token.lifetime().expired() {
self.refresh_token(http_client, token, None)
} else {
@ -205,13 +215,13 @@ mod tests {
impl Provider for Test {
type Lifetime = Static;
type Token = Bearer<Static>;
fn auth_uri() -> &'static str { "http://example.com/oauth2/auth" }
fn token_uri() -> &'static str { "http://example.com/oauth2/token" }
fn auth_uri(&self) -> &str { "http://example.com/oauth2/auth" }
fn token_uri(&self) -> &str { "http://example.com/oauth2/token" }
}
#[test]
fn auth_uri() {
let client = Client::<Test>::new(String::from("foo"), String::from("bar"), None);
let client = Client::new(Test, String::from("foo"), String::from("bar"), None);
assert_eq!(
"http://example.com/oauth2/auth?response_type=code&client_id=foo",
client.auth_uri(None, None).unwrap().as_str()
@ -220,10 +230,11 @@ mod tests {
#[test]
fn auth_uri_with_redirect_uri() {
let client = Client::<Test>::new(
let client = Client::new(
Test,
String::from("foo"),
String::from("bar"),
Some(String::from("http://example.com/oauth2/callback"))
Some(String::from("http://example.com/oauth2/callback")),
);
assert_eq!(
"http://example.com/oauth2/auth?response_type=code&client_id=foo&redirect_uri=http%3A%2F%2Fexample.com%2Foauth2%2Fcallback",
@ -233,7 +244,7 @@ mod tests {
#[test]
fn auth_uri_with_scope() {
let client = Client::<Test>::new(String::from("foo"), String::from("bar"), None);
let client = Client::new(Test, String::from("foo"), String::from("bar"), None);
assert_eq!(
"http://example.com/oauth2/auth?response_type=code&client_id=foo&scope=baz",
client.auth_uri(Some("baz"), None).unwrap().as_str()
@ -242,7 +253,7 @@ mod tests {
#[test]
fn auth_uri_with_state() {
let client = Client::<Test>::new(String::from("foo"), String::from("bar"), None);
let client = Client::new(Test, String::from("foo"), String::from("bar"), None);
assert_eq!(
"http://example.com/oauth2/auth?response_type=code&client_id=foo&state=baz",
client.auth_uri(None, Some("baz")).unwrap().as_str()

View File

@ -34,10 +34,11 @@
//! use inth_oauth2::Client;
//! use inth_oauth2::provider::google::Installed;
//!
//! let client = Client::<Installed>::new(
//! let client = Client::new(
//! Installed,
//! String::from("client_id"),
//! String::from("client_secret"),
//! Some(String::from("redirect_uri"))
//! Some(String::from("redirect_uri")),
//! );
//! ```
//!
@ -46,7 +47,7 @@
//! ```
//! # use inth_oauth2::Client;
//! # use inth_oauth2::provider::google::Installed;
//! # let client = Client::<Installed>::new(String::new(), String::new(), None);
//! # let client = Client::new(Installed, String::new(), String::new(), None);
//! let auth_uri = client.auth_uri(Some("scope"), Some("state")).unwrap();
//! println!("Authorize the application by clicking on the link: {}", auth_uri);
//! ```
@ -61,7 +62,7 @@
//! use inth_oauth2::{Client, Token};
//! # use inth_oauth2::provider::google::Installed;
//! # fn main() {
//! # let client = Client::<Installed>::new(String::new(), String::new(), None);
//! # let client = Client::new(Installed, String::new(), String::new(), None);
//!
//! let mut code = String::new();
//! io::stdin().read_line(&mut code).unwrap();
@ -80,7 +81,7 @@
//! ```no_run
//! # use inth_oauth2::Client;
//! # use inth_oauth2::provider::google::Installed;
//! # let client = Client::<Installed>::new(String::new(), String::new(), None);
//! # let client = Client::new(Installed, String::new(), String::new(), None);
//! # let https = Default::default();
//! # let token = client.request_token(&https, "").unwrap();
//! let token = client.refresh_token(&https, token, None).unwrap();
@ -91,7 +92,7 @@
//! ```no_run
//! # use inth_oauth2::Client;
//! # use inth_oauth2::provider::google::Installed;
//! # let client = Client::<Installed>::new(String::new(), String::new(), None);
//! # let client = Client::new(Installed, String::new(), String::new(), None);
//! # let https = Default::default();
//! # let mut token = client.request_token(&https, "").unwrap();
//! // Refresh token only if it has expired.
@ -110,7 +111,7 @@
//! use hyper::header::Authorization;
//!
//! # fn main() {
//! # let oauth_client = Client::<Installed>::new(String::new(), String::new(), None);
//! # let oauth_client = Client::new(Installed, String::new(), String::new(), None);
//! # let https = Default::default();
//! # let token = oauth_client.request_token(&https, "").unwrap();
//! let request = https.get("https://example.com/resource")
@ -129,7 +130,7 @@
//! # use inth_oauth2::provider::google::Installed;
//! # fn main() {
//! # let http_client = Default::default();
//! # let client = Client::<Installed>::new(String::new(), String::new(), None);
//! # let client = Client::new(Installed, String::new(), String::new(), None);
//! # let token = client.request_token(&http_client, "").unwrap();
//! let json = serde_json::to_string(&token).unwrap();
//! # }
@ -144,7 +145,7 @@
unused_extern_crates,
unused_import_braces,
unused_qualifications,
variant_size_differences
variant_size_differences,
)]
#[macro_use]

View File

@ -13,16 +13,12 @@ pub trait Provider {
/// The authorization endpoint URI.
///
/// See [RFC 6749, section 3.1](http://tools.ietf.org/html/rfc6749#section-3.1).
///
/// Note: likely to become an associated constant.
fn auth_uri() -> &'static str;
fn auth_uri(&self) -> &str;
/// The token endpoint URI.
///
/// See [RFC 6749, section 3.2](http://tools.ietf.org/html/rfc6749#section-3.2).
///
/// Note: likely to become an associated constant.
fn token_uri() -> &'static str;
fn token_uri(&self) -> &str;
/// Provider requires credentials via request body.
///
@ -30,9 +26,7 @@ pub trait Provider {
/// as part of the request body.
///
/// See [RFC 6749, section 2.3.1](http://tools.ietf.org/html/rfc6749#section-2.3.1).
///
/// Note: likely to become an associated constant.
fn credentials_in_body() -> bool { false }
fn credentials_in_body(&self) -> bool { false }
}
/// Google OAuth 2.0 providers.
@ -67,8 +61,8 @@ pub mod google {
impl Provider for Web {
type Lifetime = Expiring;
type Token = Bearer<Expiring>;
fn auth_uri() -> &'static str { "https://accounts.google.com/o/oauth2/v2/auth" }
fn token_uri() -> &'static str { "https://www.googleapis.com/oauth2/v4/token" }
fn auth_uri(&self) -> &str { "https://accounts.google.com/o/oauth2/v2/auth" }
fn token_uri(&self) -> &str { "https://www.googleapis.com/oauth2/v4/token" }
}
/// Google OAuth 2.0 provider for installed applications.
@ -80,8 +74,8 @@ pub mod google {
impl Provider for Installed {
type Lifetime = Refresh;
type Token = Bearer<Refresh>;
fn auth_uri() -> &'static str { "https://accounts.google.com/o/oauth2/v2/auth" }
fn token_uri() -> &'static str { "https://www.googleapis.com/oauth2/v4/token" }
fn auth_uri(&self) -> &str { "https://accounts.google.com/o/oauth2/v2/auth" }
fn token_uri(&self) -> &str { "https://www.googleapis.com/oauth2/v4/token" }
}
}
@ -93,8 +87,8 @@ pub struct GitHub;
impl Provider for GitHub {
type Lifetime = Static;
type Token = Bearer<Static>;
fn auth_uri() -> &'static str { "https://github.com/login/oauth/authorize" }
fn token_uri() -> &'static str { "https://github.com/login/oauth/access_token" }
fn auth_uri(&self) -> &str { "https://github.com/login/oauth/authorize" }
fn token_uri(&self) -> &str { "https://github.com/login/oauth/access_token" }
}
/// Imgur OAuth 2.0 provider.
@ -105,6 +99,6 @@ pub struct Imgur;
impl Provider for Imgur {
type Lifetime = Refresh;
type Token = Bearer<Refresh>;
fn auth_uri() -> &'static str { "https://api.imgur.com/oauth2/authorize" }
fn token_uri() -> &'static str { "https://api.imgur.com/oauth2/token" }
fn auth_uri(&self) -> &str { "https://api.imgur.com/oauth2/authorize" }
fn token_uri(&self) -> &str { "https://api.imgur.com/oauth2/token" }
}

View File

@ -19,7 +19,8 @@ fn assert_get_uri_ok(uri: Url) {
#[test]
fn google_web_auth_uri_ok() {
let client = Client::<google::Web>::new(
let client = Client::new(
google::Web,
String::from("143225766783-0h4h5ktpvhc7kqp6ohbpd2sssqrap57n.apps.googleusercontent.com"),
String::new(),
Some(String::from("https://cmcenroe.me/oauth2-paste/")),
@ -33,24 +34,26 @@ fn google_web_auth_uri_ok() {
#[test]
fn google_installed_auth_uri_ok() {
let client = Client::<google::Installed>::new(
let client = Client::new(
google::Installed,
String::from("143225766783-ip2d9qv6sdr37276t77luk6f7bhd6bj5.apps.googleusercontent.com"),
String::new(),
Some(String::from("urn:ietf:wg:oauth:2.0:oob"))
Some(String::from("urn:ietf:wg:oauth:2.0:oob")),
);
let auth_uri = client.auth_uri(
Some("https://www.googleapis.com/auth/userinfo.email"),
Some("state")
Some("state"),
).unwrap();
assert_get_uri_ok(auth_uri);
}
#[test]
fn github_auth_uri_ok() {
let client = Client::<GitHub>::new(
let client = Client::new(
GitHub,
String::from("01774654cd9a6051e478"),
String::new(),
Some(String::from("https://cmcenroe.me/oauth2-paste/"))
Some(String::from("https://cmcenroe.me/oauth2-paste/")),
);
let auth_uri = client.auth_uri(Some("user"), Some("state")).unwrap();
assert_get_uri_ok(auth_uri);
@ -58,10 +61,11 @@ fn github_auth_uri_ok() {
#[test]
fn imgur_auth_uri_ok() {
let client = Client::<Imgur>::new(
let client = Client::new(
Imgur,
String::from("505c8ca804230e0"),
String::new(),
Some(String::from("https://cmcenroe.me/oauth2-paste/"))
Some(String::from("https://cmcenroe.me/oauth2-paste/")),
);
let auth_uri = client.auth_uri(None, Some("state")).unwrap();
assert_get_uri_ok(auth_uri);

View File

@ -16,24 +16,24 @@ mod provider {
impl Provider for BearerStatic {
type Lifetime = Static;
type Token = Bearer<Static>;
fn auth_uri() -> &'static str { "https://example.com/oauth/auth" }
fn token_uri() -> &'static str { "https://example.com/oauth/token" }
fn auth_uri(&self) -> &str { "https://example.com/oauth/auth" }
fn token_uri(&self) -> &str { "https://example.com/oauth/token" }
}
pub struct BearerExpiring;
impl Provider for BearerExpiring {
type Lifetime = Expiring;
type Token = Bearer<Expiring>;
fn auth_uri() -> &'static str { "https://example.com/oauth/auth" }
fn token_uri() -> &'static str { "https://example.com/oauth/token" }
fn auth_uri(&self) -> &str { "https://example.com/oauth/auth" }
fn token_uri(&self) -> &str { "https://example.com/oauth/token" }
}
pub struct BearerRefresh;
impl Provider for BearerRefresh {
type Lifetime = Refresh;
type Token = Bearer<Refresh>;
fn auth_uri() -> &'static str { "https://example.com/oauth/auth" }
fn token_uri() -> &'static str { "https://example.com/oauth/token" }
fn auth_uri(&self) -> &str { "https://example.com/oauth/auth" }
fn token_uri(&self) -> &str { "https://example.com/oauth/token" }
}
}
@ -69,11 +69,12 @@ mod connector {
}
macro_rules! mock_client {
($p:ty, $c:ty) => {
(Client::<$p>::new(
($p:path, $c:ty) => {
(Client::new(
$p,
String::from("client_id"),
String::from("client_secret"),
None
None,
),
hyper::Client::with_connector(<$c>::default()))
}