1#![cfg_attr(test, deny(warnings))]
2extern crate url;
45extern crate curl;
46#[macro_use] extern crate log;
47
48use url::Url;
49use std::sync::{Arc,Mutex};
50use std::io::Read;
51
52use curl::easy::{Easy, List};
53
54pub struct Config {
56 pub client_id: String,
57 pub client_secret: String,
58 pub scopes: Vec<String>,
59 pub auth_url: Url,
60 pub token_url: Url,
61 pub redirect_url: String,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
66pub struct Token {
67 pub access_token: String,
69 pub scopes: Vec<String>,
71 pub token_type: String,
73}
74
75struct ErrorContainer {
76 error : String,
77 error_desc : String,
78 error_uri : String
79}
80
81impl ErrorContainer {
82 fn new() -> ErrorContainer {
83 ErrorContainer {
84 error: String::new(),
85 error_desc: String::new(),
86 error_uri: String::new()
87 }
88 }
89}
90
91macro_rules! try_error_to_string {
92 ($e:expr) => (match $e {
93 Ok(val) => val,
94 Err(err) => return Err(::std::convert::From::from(error_to_string(err))),
95 });
96}
97
98
99pub trait Authorization {
104 fn auth_with(&mut self, token: &Token) -> Result<(), curl::Error>;
105}
106
107impl Config {
108
109 pub fn new(id: &str, secret: &str, auth_url: &str,
111 token_url: &str) -> Config {
112 Config {
113 client_id: id.to_string(),
114 client_secret: secret.to_string(),
115 scopes: Vec::new(),
116 auth_url: Url::parse(auth_url).unwrap(),
117 token_url: Url::parse(token_url).unwrap(),
118 redirect_url: String::new(),
119 }
120 }
121
122 #[allow(deprecated)] pub fn authorize_url(&self, state: String) -> Url {
125 let scopes = self.scopes.connect(",");
126 let mut pairs = vec![
127 ("client_id", &self.client_id),
128 ("state", &state),
129 ("scope", &scopes),
130 ];
131 if self.redirect_url.len() > 0 {
132 pairs.push(("redirect_uri", &self.redirect_url));
133 }
134 let mut url = self.auth_url.clone();
135
136 for (k,v) in pairs {
137 url.query_pairs_mut().append_pair(k,v);
138 }
139 return url;
140 }
141
142 pub fn exchange(&self, code: String) -> Result<Token, String> {
147 let mut form = url::form_urlencoded::Serializer::new(String::new());
148 form.append_pair("client_id", &self.client_id.clone());
149 form.append_pair("client_secret", &self.client_secret.clone());
150 form.append_pair("code", &code);
151 if self.redirect_url.len() > 0 {
152 form.append_pair("redirect_uri", &self.redirect_url.clone());
153 }
154
155 let form_str : String = form.finish();
156 let post_len = form_str.as_bytes().len();
157
158 let mut easy = Easy::new();
159 try_error_to_string!(easy.url(&self.token_url.to_string()));
160 let mut list = List::new();
161 try_error_to_string!(list.append("Content-Type: application/x-www-form-urlencoded"));
162 try_error_to_string!(easy.http_headers(list));
163 try_error_to_string!(easy.show_header(true));
164 try_error_to_string!(easy.read_function(move |buf| {
165 Ok(form_str.as_bytes().read(buf).unwrap_or(0))
166 }));
167 try_error_to_string!(easy.post(true));
168 try_error_to_string!(easy.post_field_size(post_len as u64));
169
170 let token = Token {
171 access_token: String::new(),
172 scopes: Vec::new(),
173 token_type: String::new(),
174 };
175
176 let protector = Arc::new(Mutex::new(token));
177 let result_ref = protector.clone();
178 let error_strings = Arc::new(Mutex::new(ErrorContainer::new()));
179 let error_strings_copy = error_strings.clone();
180
181 try_error_to_string!(easy.write_function(move |data| {
182 let mut result_token = result_ref.lock().unwrap();
183 let mut err_cont = error_strings_copy.lock().unwrap();
184
185 let result_form = url::form_urlencoded::parse(data);
186 for(k, v) in result_form.into_iter() {
187 match &k[..] {
188 "access_token" => result_token.access_token = (*v).to_owned(),
189 "token_type" => result_token.token_type = (*v).to_owned(),
190 "scope" => {
191 result_token.scopes = v.split(',')
192 .map(|s| s.to_string()).collect();
193 },
194 "error" => err_cont.error = (*v).to_owned(),
195 "error_description" => err_cont.error_desc = (*v).to_owned(),
196 "error_uri" => err_cont.error_uri = (*v).to_owned(),
197 _ => {}
198 }
199 }
200 return Ok(data.len());
201 }));
202
203 try_error_to_string!(easy.perform());
204
205 let resp_code = try_error_to_string!(easy.response_code());
206 if resp_code != 200 {
207 return Err(format!("expected `200`, found `{}`", resp_code))
208 }
209
210 let new_token = protector.lock().unwrap();
211 let new_errors = error_strings.lock().unwrap();
212
213 if new_token.access_token.len() != 0 {
214 Ok(new_token.clone())
215 } else if new_errors.error.len() > 0 {
216 Err(format!("error `{}`: {}, see {}", new_errors.error, new_errors.error_desc, new_errors.error_uri))
217 } else {
218 Err(format!("couldn't find access_token in the response"))
219 }
220 }
221}
222
223fn error_to_string(e : curl::Error) -> String {
224 let err_str : &str;
225 err_str = if e.is_unsupported_protocol() {
226 "Unsupported Protocol!"
227 } else if e.is_failed_init() {
228 "Failed to initialize"
229 } else if e.is_url_malformed() {
230 "Url is malformed!"
231 } else if e.is_couldnt_resolve_proxy() {
232 "Couldn't resolve proxy"
233 } else if e.is_couldnt_resolve_host() {
234 "Couldn't Resolve host"
235 } else if e.is_couldnt_connect() {
236 "Couldn't Connect"
237 } else if e.is_remote_access_denied() {
238 "Remote access is denied"
239 } else if e.is_partial_file() {
240 "Partial file given"
241 } else if e.is_quote_error() {
242 "Quote error"
243 } else if e.is_http_returned_error() {
244 "Http returned error"
245 } else if e.is_read_error() {
246 "Read error"
247 } else if e.is_write_error() {
248 "Write Error"
249 } else if e.is_upload_failed() {
250 "Upload failed"
251 } else if e.is_out_of_memory() {
252 "Out of memory"
253 } else if e.is_operation_timedout() {
254 "Timed out"
255 } else if e.is_range_error() {
256 "Range error"
257 } else if e.is_http_post_error() {
258 "Http post error"
259 } else if e.is_ssl_connect_error() {
260 "SSL connect error"
261 } else if e.is_bad_download_resume() {
262 "Bad download resume error"
263 } else if e.is_file_couldnt_read_file() {
264 "Cannot read given file"
265 } else if e.is_function_not_found() {
266 "Cannot find given function error"
267 } else if e.is_aborted_by_callback() {
268 "Callback aborted error"
269 } else if e.is_bad_function_argument() {
270 "Bad function argument error"
271 } else if e.is_interface_failed() {
272 "Interface failed error"
273 } else if e.is_too_many_redirects() {
274 "Too many redirects error"
275 } else if e.is_unknown_option() {
276 "Unknown option error"
277 } else if e.is_peer_failed_verification() {
278 "Peer failed to validate error"
279 } else if e.is_got_nothing() {
280 "Received nothing error"
281 } else if e.is_ssl_engine_notfound() {
282 "SSL engine not found error"
283 } else if e.is_ssl_engine_setfailed() {
284 "SSL engine set failed error"
285 } else if e.is_send_error() {
286 "Send failed error"
287 } else if e.is_recv_error() {
288 "Recieve failed error"
289 } else if e.is_ssl_certproblem() {
290 "SSL certificate problem error"
291 } else if e.is_ssl_cipher() {
292 "SSL cipher error"
293 } else if e.is_ssl_cacert() {
294 "SSL CA Cert error"
295 } else if e.is_bad_content_encoding() {
296 "Bad content encoding error"
297 } else if e.is_filesize_exceeded() {
298 "Filesize exceeded error"
299 } else if e.is_use_ssl_failed() {
300 "Use SSL failed error"
301 } else if e.is_send_fail_rewind() {
302 "Send rewind fail error"
303 } else if e.is_ssl_engine_initfailed() {
304 "SSL engine init fail error"
305 } else if e.is_login_denied() {
306 "Login denied error"
307 } else if e.is_conv_failed() {
308 "Conv failed error"
309 } else if e.is_conv_required() {
310 "Conv required error"
311 } else if e.is_ssl_cacert_badfile() {
312 "CA cert bad file error"
313 } else if e.is_ssl_crl_badfile() {
314 "SSL crl bad file error"
315 } else if e.is_ssl_shutdown_failed() {
316 "SSL Shutdown failed error"
317 } else if e.is_again() {
318 "Again error"
319 } else if e.is_ssl_issuer_error() {
320 "SSL Issuer error"
321 } else if e.is_chunk_failed() {
322 "Chunk failed error"
323 } else {
324 "general error"
325 };
326 return err_str.to_string();
327}
328
329impl Authorization for curl::easy::Easy{
332 fn auth_with(&mut self, token: &Token) -> Result<(), curl::Error> {
333 let mut auth_header = List::new();
334 let auth_header_text = format!("Authorization: {}", token.access_token);
335 let res = auth_header.append(&auth_header_text);
336 if res.is_ok() {
337 self.http_headers(auth_header)
338 } else {
339 res
340 }
341 }
342}