extern crate reqwest;
extern crate serde;
extern crate serde_json;
use std::fmt;
use std::collections::HashMap;
use std::io::Read;
use std::sync::{Arc, RwLock};
use self::reqwest::{header, Response};
pub struct Client {
config: Config,
state: Arc<RwLock<State>>,
}
pub struct Config {
pub protected_url: String,
pub token_url: String,
pub refresh_url: String,
pub client_id: String,
pub redirect_uri: String,
}
pub enum Error {
AccessFailed,
NoToken,
AuthorizationFailed,
RefreshFailed,
Invalid(serde_json::Error),
MissingToken,
Response(String),
}
#[derive(Debug, Default)]
struct State {
pub token: Option<String>,
pub refresh: Option<String>,
pub until: Option<String>,
}
impl Client {
pub fn new(config: Config) -> Self {
Client {
config,
state: Arc::new(RwLock::new(State::default())),
}
}
pub fn authorize(&self, code: &str) -> Result<(), Error> {
let client = reqwest::Client::new();
let mut state = self.state.write().unwrap();
let mut params = HashMap::new();
params.insert("grant_type", "authorization_code");
params.insert("client_id", &self.config.client_id);
params.insert("code", code);
params.insert("redirect_uri", &self.config.redirect_uri);
let access_token_request = client
.post(&self.config.token_url)
.form(¶ms).build().unwrap();
let token_response = client
.execute(access_token_request)
.map_err(|_| Error::AuthorizationFailed)?;
let mut token_map = parse_token_response(token_response)?;
if let Some(err) = token_map.remove("error") {
return Err(Error::Response(err));
}
if let Some(token) = token_map.remove("access_token") {
state.token = Some(token);
state.refresh = token_map.remove("refresh_token");
state.until = token_map.remove("expires_in");
return Ok(());
}
Err(Error::MissingToken)
}
pub fn retrieve_protected_page(&self) -> Result<String, Error> {
let client = reqwest::Client::new();
let state = self.state.read().unwrap();
let token = match state.token {
Some(ref token) => token,
None => return Err(Error::NoToken),
};
let page_request = client
.get(&self.config.protected_url)
.header(header::AUTHORIZATION, "Bearer ".to_string() + token)
.build()
.unwrap();
let mut page_response = match client.execute(page_request) {
Ok(response) => response,
Err(_) => return Err(Error::AccessFailed),
};
let mut protected_page = String::new();
page_response.read_to_string(&mut protected_page).unwrap();
Ok(protected_page)
}
pub fn refresh(&self) -> Result<(), Error> {
let client = reqwest::Client::new();
let mut state = self.state.write().unwrap();
let refresh = match state.refresh {
Some(ref refresh) => refresh.clone(),
None => return Err(Error::NoToken),
};
let mut params = HashMap::new();
params.insert("grant_type", "refresh_token");
params.insert("client_id", &self.config.client_id);
params.insert("refresh_token", &refresh);
let access_token_request = client
.post(&self.config.refresh_url)
.form(¶ms).build().unwrap();
let token_response = client
.execute(access_token_request)
.map_err(|_| Error::RefreshFailed)?;
let mut token_map = parse_token_response(token_response)?;
if token_map.get("error").is_some() || !token_map.get("access_token").is_some() {
return Err(Error::MissingToken);
}
let token = token_map
.remove("access_token")
.unwrap();
state.token = Some(token);
state.refresh = token_map
.remove("refresh_token")
.or(state.refresh.take());
state.until = token_map
.remove("expires_in");
Ok(())
}
pub fn as_html(&self) -> String {
format!("{}", self.state.read().unwrap())
}
}
fn parse_token_response(mut response: Response) -> Result<HashMap<String, String>, serde_json::Error> {
let mut token = String::new();
response.read_to_string(&mut token).unwrap();
serde_json::from_str(&token)
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error::Invalid(err)
}
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Token {<br>")?;
write!(f, " token: {:?},<br>", self.token)?;
write!(f, " refresh: {:?},<br>", self.refresh)?;
write!(f, " expires_in: {:?},<br>", self.until)?;
f.write_str("}")
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::AuthorizationFailed => f.write_str("Could not fetch bearer token"),
Error::NoToken => f.write_str("No token with which to access protected page"),
Error::AccessFailed => f.write_str("Access token failed to authorize for protected page"),
Error::RefreshFailed => f.write_str("Could not refresh bearer token"),
Error::Invalid(serde) => write!(f, "Bad json response: {}", serde),
Error::MissingToken => write!(f, "No token nor error in server response"),
Error::Response(err) => write!(f, "Server error while fetching token: {}", err),
}
}
}