diff --git a/examples/github.rs b/examples/github.rs index 12b83a0..4461482 100644 --- a/examples/github.rs +++ b/examples/github.rs @@ -1,10 +1,10 @@ extern crate inth_oauth2; use std::io; -use inth_oauth2::Client; +use inth_oauth2::{Client, GitHub}; fn main() { - let client = Client::github( + let client = Client::::new( Default::default(), "01774654cd9a6051e478", "9f14d16d95d605e715ec1a9aecec220d2565fd5c", diff --git a/examples/google.rs b/examples/google.rs index 98b6dfd..a64ebaf 100644 --- a/examples/google.rs +++ b/examples/google.rs @@ -1,10 +1,10 @@ extern crate inth_oauth2; use std::io; -use inth_oauth2::Client; +use inth_oauth2::{Client, Google}; fn main() { - let client = Client::google( + let client = Client::::new( Default::default(), "143225766783-ip2d9qv6sdr37276t77luk6f7bhd6bj5.apps.googleusercontent.com", "3kZ5WomzHFlN2f_XbhkyPd3o", diff --git a/examples/imgur.rs b/examples/imgur.rs index 82093db..08b0dea 100644 --- a/examples/imgur.rs +++ b/examples/imgur.rs @@ -1,10 +1,10 @@ extern crate inth_oauth2; use std::io; -use inth_oauth2::Client; +use inth_oauth2::{Client, Imgur}; fn main() { - let client = Client::imgur( + let client = Client::::new( Default::default(), "505c8ca804230e0", "c898d8cf28404102752b2119a3a1c6aab49899c8", diff --git a/src/client.rs b/src/client.rs index d991b22..13264a7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,10 +1,12 @@ use std::io::Read; +use std::marker::PhantomData; use chrono::{UTC, Duration}; use hyper::{self, header, mime}; use rustc_serialize::json; use url::{Url, form_urlencoded}; +use super::Provider; use super::{TokenPair, AccessTokenType, AccessToken, RefreshToken}; 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`. /// /// See [RFC6749 section 4.1](http://tools.ietf.org/html/rfc6749#section-4.1). -pub struct Client { +pub struct Client { http_client: hyper::Client, - auth_uri: String, - token_uri: String, - client_id: String, client_secret: String, redirect_uri: Option, + + provider: PhantomData

, +} + +impl Client

{ + /// Creates an OAuth 2.0 client. + pub fn new( + http_client: hyper::Client, + client_id: S, + client_secret: S, + redirect_uri: Option + ) -> Self where S: Into { + 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)] @@ -76,81 +95,12 @@ impl Into for ErrorResponse { } } -macro_rules! site_constructors { - ( - $( - #[$attr:meta] - $ident:ident => ($auth_uri:expr, $token_uri:expr) - ),* - ) => { - $( - #[$attr] - pub fn $ident( - http_client: hyper::Client, - client_id: S, - client_secret: S, - redirect_uri: Option - ) -> Self where S: Into { - 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( - http_client: hyper::Client, - auth_uri: S, - token_uri: S, - client_id: S, - client_secret: S, - redirect_uri: Option - ) -> Self where S: Into { - 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 { +impl Client

{ /// Constructs an authorization request URI. /// /// 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 { - let mut uri = try!(Url::parse(&self.auth_uri)); + let mut uri = try!(Url::parse(P::auth_uri())); let mut query_pairs = vec![ ("response_type", "code"), @@ -166,7 +116,7 @@ impl Client { query_pairs.push(("state", state)); } - uri.set_query_from_pairs(query_pairs.iter()); + uri.set_query_from_pairs(query_pairs); Ok(uri.serialize()) } @@ -194,7 +144,7 @@ impl Client { fn token_post(&self, body_pairs: Vec<(&str, &str)>) -> Result { 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.accept_header()) .header(header::ContentType::form_url_encoded()) diff --git a/src/lib.rs b/src/lib.rs index 1048c8f..b91533b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ //! # "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). //! @@ -11,15 +11,22 @@ //! //! ## Providers //! -//! `inth_oauth2` can be used with any OAuth 2.0 provider, but provides defaults for a few common -//! ones. +//! `inth_oauth2` supports the following OAuth 2.0 providers: //! -//! ### 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::::new( //! Default::default(), //! "CLIENT_ID", //! "CLIENT_SECRET", @@ -27,77 +34,42 @@ //! ); //! ``` //! -//! ### GitHub +//! ### Constructing an authorization URI //! //! ``` -//! use inth_oauth2::Client as OAuth2; -//! -//! let auth = OAuth2::github(Default::default(), "CLIENT_ID", "CLIENT_SECRET", None); +//! # use inth_oauth2::{Client, Google}; +//! # let client = Client::::new(Default::default(), "", "", 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. //! -//! ``` -//! # 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 //! -//! ## Requesting an access token -//! -//! Using a code obtained from the redirect of the authorization URI, request an access token. +//! Request an access token using a code obtained from the redirect of the authorization URI. //! //! ```no_run -//! # use inth_oauth2::Client as OAuth2; -//! # let auth = OAuth2::google(Default::default(), "", "", None); +//! # use inth_oauth2::{Client, Google}; +//! # let client = Client::::new(Default::default(), "", "", None); //! # 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); //! ``` //! -//! ## Refreshing an access token -//! -//! Refresh the access token when it has expired. +//! ### Refreshing an access token //! //! ```no_run -//! # use inth_oauth2::Client as OAuth2; -//! # let auth = OAuth2::google(Default::default(), "", "", None); -//! # let mut token_pair = auth.request_token("").unwrap(); +//! # use inth_oauth2::{Client, Google}; +//! # let client = Client::::new(Default::default(), "", "", None); +//! # let mut token_pair = client.request_token("").unwrap(); //! if token_pair.expired() { //! 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 //! from it. @@ -106,9 +78,9 @@ //! # extern crate hyper; //! # extern crate inth_oauth2; //! # fn main() { -//! # use inth_oauth2::Client as OAuth2; -//! # let auth = OAuth2::google(Default::default(), "", "", None); -//! # let token_pair = auth.request_token("").unwrap(); +//! # use inth_oauth2::{Client, Google}; +//! # let client = Client::::new(Default::default(), "", "", None); +//! # let mut token_pair = client.request_token("").unwrap(); //! let client = hyper::Client::new(); //! let res = client.get("https://example.com/resource") //! .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 //! as JSON. @@ -154,6 +126,9 @@ extern crate url; pub use client::Client; pub mod client; +pub use provider::{Provider, Google, GitHub, Imgur}; +pub mod provider; + pub use token::{TokenPair, AccessTokenType, AccessToken, RefreshToken}; pub mod token; diff --git a/src/provider.rs b/src/provider.rs new file mode 100644 index 0000000..f4e6141 --- /dev/null +++ b/src/provider.rs @@ -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" } +}