Move providers to types

This commit is contained in:
Curtis McEnroe 2015-12-01 21:36:51 -05:00
parent 7ca874de6b
commit fcd1945dba
6 changed files with 104 additions and 143 deletions

View File

@ -1,10 +1,10 @@
extern crate inth_oauth2; extern crate inth_oauth2;
use std::io; use std::io;
use inth_oauth2::Client; use inth_oauth2::{Client, GitHub};
fn main() { fn main() {
let client = Client::github( let client = Client::<GitHub>::new(
Default::default(), Default::default(),
"01774654cd9a6051e478", "01774654cd9a6051e478",
"9f14d16d95d605e715ec1a9aecec220d2565fd5c", "9f14d16d95d605e715ec1a9aecec220d2565fd5c",

View File

@ -1,10 +1,10 @@
extern crate inth_oauth2; extern crate inth_oauth2;
use std::io; use std::io;
use inth_oauth2::Client; use inth_oauth2::{Client, Google};
fn main() { fn main() {
let client = Client::google( let client = Client::<Google>::new(
Default::default(), Default::default(),
"143225766783-ip2d9qv6sdr37276t77luk6f7bhd6bj5.apps.googleusercontent.com", "143225766783-ip2d9qv6sdr37276t77luk6f7bhd6bj5.apps.googleusercontent.com",
"3kZ5WomzHFlN2f_XbhkyPd3o", "3kZ5WomzHFlN2f_XbhkyPd3o",

View File

@ -1,10 +1,10 @@
extern crate inth_oauth2; extern crate inth_oauth2;
use std::io; use std::io;
use inth_oauth2::Client; use inth_oauth2::{Client, Imgur};
fn main() { fn main() {
let client = Client::imgur( let client = Client::<Imgur>::new(
Default::default(), Default::default(),
"505c8ca804230e0", "505c8ca804230e0",
"c898d8cf28404102752b2119a3a1c6aab49899c8", "c898d8cf28404102752b2119a3a1c6aab49899c8",

View File

@ -1,10 +1,12 @@
use std::io::Read; use std::io::Read;
use std::marker::PhantomData;
use chrono::{UTC, Duration}; use chrono::{UTC, Duration};
use hyper::{self, header, mime}; use hyper::{self, header, mime};
use rustc_serialize::json; use rustc_serialize::json;
use url::{Url, form_urlencoded}; use url::{Url, form_urlencoded};
use super::Provider;
use super::{TokenPair, AccessTokenType, AccessToken, RefreshToken}; use super::{TokenPair, AccessTokenType, AccessToken, RefreshToken};
use super::error::{Error, Result, OAuth2Error, OAuth2ErrorCode}; use super::error::{Error, Result, OAuth2Error, OAuth2ErrorCode};
@ -13,15 +15,32 @@ use super::error::{Error, Result, OAuth2Error, OAuth2ErrorCode};
/// Performs HTTP requests using the provided `hyper::Client`. /// Performs HTTP requests using the provided `hyper::Client`.
/// ///
/// See [RFC6749 section 4.1](http://tools.ietf.org/html/rfc6749#section-4.1). /// See [RFC6749 section 4.1](http://tools.ietf.org/html/rfc6749#section-4.1).
pub struct Client { pub struct Client<P: Provider> {
http_client: hyper::Client, http_client: hyper::Client,
auth_uri: String,
token_uri: String,
client_id: String, client_id: String,
client_secret: String, client_secret: String,
redirect_uri: Option<String>, redirect_uri: Option<String>,
provider: PhantomData<P>,
}
impl<P: Provider> Client<P> {
/// Creates an OAuth 2.0 client.
pub fn new<S>(
http_client: hyper::Client,
client_id: S,
client_secret: S,
redirect_uri: Option<S>
) -> Self where S: Into<String> {
Client {
http_client: http_client,
client_id: client_id.into(),
client_secret: client_secret.into(),
redirect_uri: redirect_uri.map(Into::into),
provider: PhantomData,
}
}
} }
#[derive(RustcDecodable)] #[derive(RustcDecodable)]
@ -76,81 +95,12 @@ impl Into<OAuth2Error> for ErrorResponse {
} }
} }
macro_rules! site_constructors { impl<P: Provider> Client<P> {
(
$(
#[$attr:meta]
$ident:ident => ($auth_uri:expr, $token_uri:expr)
),*
) => {
$(
#[$attr]
pub fn $ident<S>(
http_client: hyper::Client,
client_id: S,
client_secret: S,
redirect_uri: Option<S>
) -> Self where S: Into<String> {
Client {
http_client: http_client,
auth_uri: String::from($auth_uri),
token_uri: String::from($token_uri),
client_id: client_id.into(),
client_secret: client_secret.into(),
redirect_uri: redirect_uri.map(Into::into),
}
}
)*
}
}
impl Client {
/// Creates an OAuth 2.0 client.
pub fn new<S>(
http_client: hyper::Client,
auth_uri: S,
token_uri: S,
client_id: S,
client_secret: S,
redirect_uri: Option<S>
) -> Self where S: Into<String> {
Client {
http_client: http_client,
auth_uri: auth_uri.into(),
token_uri: token_uri.into(),
client_id: client_id.into(),
client_secret: client_secret.into(),
redirect_uri: redirect_uri.map(Into::into),
}
}
site_constructors!{
#[doc = "Creates a Google OAuth 2.0 client.\n\nSee [Using OAuth 2.0 to Access Google APIs](https://developers.google.com/identity/protocols/OAuth2)."]
google => (
"https://accounts.google.com/o/oauth2/auth",
"https://accounts.google.com/o/oauth2/token"
),
#[doc = "Creates a GitHub OAuth 2.0 client.\n\nSee [OAuth, GitHub API](https://developer.github.com/v3/oauth/)."]
github => (
"https://github.com/login/oauth/authorize",
"https://github.com/login/oauth/access_token"
),
#[doc = "Creates an Imgur OAuth 2.0 client.\n\n See [OAuth 2.0, Imgur](https://api.imgur.com/oauth2)."]
imgur => (
"https://api.imgur.com/oauth2/authorize",
"https://api.imgur.com/oauth2/token"
)
}
}
impl Client {
/// Constructs an authorization request URI. /// Constructs an authorization request URI.
/// ///
/// See [RFC6749 section 4.1.1](http://tools.ietf.org/html/rfc6749#section-4.1.1). /// See [RFC6749 section 4.1.1](http://tools.ietf.org/html/rfc6749#section-4.1.1).
pub fn auth_uri(&self, scope: Option<&str>, state: Option<&str>) -> Result<String> { pub fn auth_uri(&self, scope: Option<&str>, state: Option<&str>) -> Result<String> {
let mut uri = try!(Url::parse(&self.auth_uri)); let mut uri = try!(Url::parse(P::auth_uri()));
let mut query_pairs = vec![ let mut query_pairs = vec![
("response_type", "code"), ("response_type", "code"),
@ -166,7 +116,7 @@ impl Client {
query_pairs.push(("state", state)); query_pairs.push(("state", state));
} }
uri.set_query_from_pairs(query_pairs.iter()); uri.set_query_from_pairs(query_pairs);
Ok(uri.serialize()) Ok(uri.serialize())
} }
@ -194,7 +144,7 @@ impl Client {
fn token_post(&self, body_pairs: Vec<(&str, &str)>) -> Result<TokenPair> { fn token_post(&self, body_pairs: Vec<(&str, &str)>) -> Result<TokenPair> {
let post_body = form_urlencoded::serialize(body_pairs); let post_body = form_urlencoded::serialize(body_pairs);
let request = self.http_client.post(&self.token_uri) let request = self.http_client.post(P::token_uri())
.header(self.auth_header()) .header(self.auth_header())
.header(self.accept_header()) .header(self.accept_header())
.header(header::ContentType::form_url_encoded()) .header(header::ContentType::form_url_encoded())

View File

@ -1,6 +1,6 @@
//! # "It's not that hard" OAuth2 Client //! # "It's not that hard" OAuth2 Client
//! //!
//! OAuth2 really isn't that hard, you know? //! OAuth 2.0 really isn't that hard, you know?
//! //!
//! Implementation of [RFC6749](http://tools.ietf.org/html/rfc6749). //! Implementation of [RFC6749](http://tools.ietf.org/html/rfc6749).
//! //!
@ -11,15 +11,22 @@
//! //!
//! ## Providers //! ## Providers
//! //!
//! `inth_oauth2` can be used with any OAuth 2.0 provider, but provides defaults for a few common //! `inth_oauth2` supports the following OAuth 2.0 providers:
//! ones.
//! //!
//! ### Google //! - `Google`
//! - `GitHub`
//! - `Imgur`
//!
//! Support for others can be added by implementing the `Provider` trait.
//!
//! ## Examples
//!
//! ### Creating a client
//! //!
//! ``` //! ```
//! use inth_oauth2::Client as OAuth2; //! use inth_oauth2::{Client, Google};
//! //!
//! let auth = OAuth2::google( //! let client = Client::<Google>::new(
//! Default::default(), //! Default::default(),
//! "CLIENT_ID", //! "CLIENT_ID",
//! "CLIENT_SECRET", //! "CLIENT_SECRET",
@ -27,77 +34,42 @@
//! ); //! );
//! ``` //! ```
//! //!
//! ### GitHub //! ### Constructing an authorization URI
//! //!
//! ``` //! ```
//! use inth_oauth2::Client as OAuth2; //! # use inth_oauth2::{Client, Google};
//! //! # let client = Client::<Google>::new(Default::default(), "", "", None);
//! let auth = OAuth2::github(Default::default(), "CLIENT_ID", "CLIENT_SECRET", None); //! let auth_uri = client.auth_uri(Some("scope"), Some("state")).unwrap();
//! ``` //! ```
//! //!
//! ### Imgur
//!
//! ```
//! use inth_oauth2::Client as OAuth2;
//!
//! let auth = OAuth2::imgur(Default::default(), "CLIENT_ID", "CLIENT_SECRET", None);
//! ```
//!
//! ### Other
//!
//! An authorization URI and a token URI are required.
//!
//! ```
//! use inth_oauth2::Client as OAuth2;
//!
//! let auth = OAuth2::new(
//! Default::default(),
//! "https://example.com/oauth2/auth",
//! "https://example.com/oauth2/token",
//! "CLIENT_ID",
//! "CLIENT_SECRET",
//! None
//! );
//! ```
//!
//! ## Constructing an authorization URI
//!
//! Direct the user to an authorization URI to have them authorize your application. //! Direct the user to an authorization URI to have them authorize your application.
//! //!
//! ``` //! ### Requesting an access token
//! # use inth_oauth2::Client as OAuth2;
//! # let auth = OAuth2::google(Default::default(), "", "", None);
//! let auth_uri = auth.auth_uri(Some("scope"), Some("state")).unwrap();
//! ```
//! //!
//! ## Requesting an access token //! Request an access token using a code obtained from the redirect of the authorization URI.
//!
//! Using a code obtained from the redirect of the authorization URI, request an access token.
//! //!
//! ```no_run //! ```no_run
//! # use inth_oauth2::Client as OAuth2; //! # use inth_oauth2::{Client, Google};
//! # let auth = OAuth2::google(Default::default(), "", "", None); //! # let client = Client::<Google>::new(Default::default(), "", "", None);
//! # let code = String::new(); //! # let code = String::new();
//! let token_pair = auth.request_token(&code).unwrap(); //! let token_pair = client.request_token(&code).unwrap();
//! println!("{}", token_pair.access.token); //! println!("{}", token_pair.access.token);
//! ``` //! ```
//! //!
//! ## Refreshing an access token //! ### Refreshing an access token
//!
//! Refresh the access token when it has expired.
//! //!
//! ```no_run //! ```no_run
//! # use inth_oauth2::Client as OAuth2; //! # use inth_oauth2::{Client, Google};
//! # let auth = OAuth2::google(Default::default(), "", "", None); //! # let client = Client::<Google>::new(Default::default(), "", "", None);
//! # let mut token_pair = auth.request_token("").unwrap(); //! # let mut token_pair = client.request_token("").unwrap();
//! if token_pair.expired() { //! if token_pair.expired() {
//! if let Some(refresh) = token_pair.refresh { //! if let Some(refresh) = token_pair.refresh {
//! token_pair = auth.refresh_token(refresh, None).unwrap(); //! token_pair = client.refresh_token(refresh, None).unwrap();
//! } //! }
//! } //! }
//! ``` //! ```
//! //!
//! ## Using bearer access tokens //! ### Using bearer access tokens
//! //!
//! If the obtained token is of the `Bearer` type, a Hyper `Authorization` header can be created //! If the obtained token is of the `Bearer` type, a Hyper `Authorization` header can be created
//! from it. //! from it.
@ -106,9 +78,9 @@
//! # extern crate hyper; //! # extern crate hyper;
//! # extern crate inth_oauth2; //! # extern crate inth_oauth2;
//! # fn main() { //! # fn main() {
//! # use inth_oauth2::Client as OAuth2; //! # use inth_oauth2::{Client, Google};
//! # let auth = OAuth2::google(Default::default(), "", "", None); //! # let client = Client::<Google>::new(Default::default(), "", "", None);
//! # let token_pair = auth.request_token("").unwrap(); //! # let mut token_pair = client.request_token("").unwrap();
//! let client = hyper::Client::new(); //! let client = hyper::Client::new();
//! let res = client.get("https://example.com/resource") //! let res = client.get("https://example.com/resource")
//! .header(token_pair.to_bearer_header().unwrap()) //! .header(token_pair.to_bearer_header().unwrap())
@ -117,7 +89,7 @@
//! # } //! # }
//! ``` //! ```
//! //!
//! ## Persisting tokens //! ### Persisting tokens
//! //!
//! `TokenPair` implements `Encodable` and `Decodable` from `rustc_serialize`, so can be persisted //! `TokenPair` implements `Encodable` and `Decodable` from `rustc_serialize`, so can be persisted
//! as JSON. //! as JSON.
@ -154,6 +126,9 @@ extern crate url;
pub use client::Client; pub use client::Client;
pub mod client; pub mod client;
pub use provider::{Provider, Google, GitHub, Imgur};
pub mod provider;
pub use token::{TokenPair, AccessTokenType, AccessToken, RefreshToken}; pub use token::{TokenPair, AccessTokenType, AccessToken, RefreshToken};
pub mod token; pub mod token;

36
src/provider.rs Normal file
View File

@ -0,0 +1,36 @@
/// An OAuth 2.0 provider.
pub trait Provider {
/// The authorization endpoint URI.
fn auth_uri() -> &'static str;
/// The token endpoint URI.
fn token_uri() -> &'static str;
}
/// Google OAuth 2.0 provider.
///
/// See [Using OAuth 2.0 to Access Google
/// APIs](https://developers.google.com/identity/protocols/OAuth2).
pub struct Google;
impl Provider for Google {
fn auth_uri() -> &'static str { "https://accounts.google.com/o/oauth2/auth" }
fn token_uri() -> &'static str { "https://accounts.google.com/o/oauth2/token" }
}
/// GitHub OAuth 2.0 provider.
///
/// See [OAuth, GitHub API](https://developer.github.com/v3/oauth/).
pub struct GitHub;
impl Provider for GitHub {
fn auth_uri() -> &'static str { "https://github.com/login/oauth/authorize" }
fn token_uri() -> &'static str { "https://github.com/login/oauth/access_token" }
}
/// Imgur OAuth 2.0 provider.
///
/// See [OAuth 2.0, Imgur](https://api.imgur.com/oauth2).
pub struct Imgur;
impl Provider for Imgur {
fn auth_uri() -> &'static str { "https://api.imgur.com/oauth2/authorize" }
fn token_uri() -> &'static str { "https://api.imgur.com/oauth2/token" }
}