//! Client. mod error; pub mod response; pub use self::error::ClientError; use reqwest::{self, header, mime}; use serde_json::{self, Value}; use url::Url; use url::form_urlencoded::Serializer; use client::response::FromResponse; use error::OAuth2Error; use provider::Provider; use token::{Token, Lifetime, Refresh}; /// OAuth 2.0 client. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Client

{ /// OAuth provider. pub provider: P, /// Client ID. pub client_id: String, /// Client secret. pub client_secret: String, /// Redirect URI. pub redirect_uri: Option, } impl Client

{ /// Creates a client. /// /// # Examples /// /// ``` /// use inth_oauth2::Client; /// use inth_oauth2::provider::google::Installed; /// /// let client = Client::new( /// Installed, /// String::from("CLIENT_ID"), /// String::from("CLIENT_SECRET"), /// Some(String::from("urn:ietf:wg:oauth:2.0:oob")), /// ); /// ``` pub fn new( provider: P, client_id: String, client_secret: String, redirect_uri: Option, ) -> Self { Client { provider, client_id, client_secret, redirect_uri, } } /// Returns an authorization endpoint URI to direct the user to. /// /// See [RFC 6749, section 3.1](http://tools.ietf.org/html/rfc6749#section-3.1). /// /// # Examples /// /// ``` /// use inth_oauth2::Client; /// use inth_oauth2::provider::google::Installed; /// /// let client = Client::new( /// Installed, /// String::from("CLIENT_ID"), /// String::from("CLIENT_SECRET"), /// 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, /// ); /// ``` pub fn auth_uri(&self, scope: Option<&str>, state: Option<&str>) -> Url { let mut uri = self.provider.auth_uri().clone(); { let mut query = uri.query_pairs_mut(); query.append_pair("response_type", "code"); query.append_pair("client_id", &self.client_id); if let Some(ref redirect_uri) = self.redirect_uri { query.append_pair("redirect_uri", redirect_uri); } if let Some(scope) = scope { query.append_pair("scope", scope); } if let Some(state) = state { query.append_pair("state", state); } } uri } fn post_token( &self, http_client: &reqwest::Client, mut body: Serializer ) -> Result { if self.provider.credentials_in_body() { body.append_pair("client_id", &self.client_id); body.append_pair("client_secret", &self.client_secret); } let auth_header = header::Authorization( header::Basic { username: self.client_id.clone(), password: Some(self.client_secret.clone()), } ); let accept_header = header::Accept(vec![ header::qitem(mime::APPLICATION_JSON), ]); let body = body.finish(); let mut response = http_client.post(self.provider.token_uri().clone())? .header(auth_header) .header(accept_header) .header(header::ContentType::form_url_encoded()) .body(body) .send()?; let json = serde_json::from_reader(&mut response)?; let error = OAuth2Error::from_response(&json); if let Ok(error) = error { Err(ClientError::from(error)) } else { Ok(json) } } /// Requests an access token using an authorization code. /// /// See [RFC 6749, section 4.1.3](http://tools.ietf.org/html/rfc6749#section-4.1.3). pub fn request_token( &self, http_client: &reqwest::Client, code: &str, ) -> Result { let mut body = Serializer::new(String::new()); body.append_pair("grant_type", "authorization_code"); body.append_pair("code", code); if let Some(ref redirect_uri) = self.redirect_uri { body.append_pair("redirect_uri", redirect_uri); } let json = self.post_token(http_client, body)?; let token = P::Token::from_response(&json)?; Ok(token) } } impl

Client

where P: Provider, P::Token: Token { /// Refreshes an access token. /// /// See [RFC 6749, section 6](http://tools.ietf.org/html/rfc6749#section-6). pub fn refresh_token( &self, http_client: &reqwest::Client, token: P::Token, scope: Option<&str>, ) -> Result { let mut body = Serializer::new(String::new()); body.append_pair("grant_type", "refresh_token"); body.append_pair("refresh_token", token.lifetime().refresh_token()); if let Some(scope) = scope { body.append_pair("scope", scope); } let json = self.post_token(http_client, body)?; let token = P::Token::from_response_inherit(&json, &token)?; Ok(token) } /// Ensures an access token is valid by refreshing it if necessary. pub fn ensure_token( &self, http_client: &reqwest::Client, token: P::Token, ) -> Result { if token.lifetime().expired() { self.refresh_token(http_client, token, None) } else { Ok(token) } } } #[cfg(test)] mod tests { use url::Url; use token::{Bearer, Static}; use provider::Provider; use super::Client; struct Test { auth_uri: Url, token_uri: Url } impl Provider for Test { type Lifetime = Static; type Token = Bearer; fn auth_uri(&self) -> &Url { &self.auth_uri } fn token_uri(&self) -> &Url { &self.token_uri } } impl Test { fn new() -> Self { Test { auth_uri: Url::parse("http://example.com/oauth2/auth").unwrap(), token_uri: Url::parse("http://example.com/oauth2/token").unwrap() } } } #[test] fn auth_uri() { let client = Client::new(Test::new(), 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).as_str() ); } #[test] fn auth_uri_with_redirect_uri() { let client = Client::new( Test::new(), String::from("foo"), String::from("bar"), 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", client.auth_uri(None, None).as_str() ); } #[test] fn auth_uri_with_scope() { let client = Client::new(Test::new(), 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).as_str() ); } #[test] fn auth_uri_with_state() { let client = Client::new(Test::new(), 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")).as_str() ); } }