twapi_oauth/
lib.rs

1use chrono::prelude::*;
2use crypto::hmac::Hmac;
3use crypto::mac::Mac;
4use crypto::sha1::Sha1;
5use rand::seq::SliceRandom;
6
7pub fn oauth2_authorization_header(bearer_token: &str) -> String {
8    format!("Bearer {}", bearer_token)
9}
10
11pub fn oauth1_authorization_header(
12    consumer_key: &str,
13    consumer_secret: &str,
14    access_token: &str,
15    access_token_secret: &str,
16    method: &str,
17    uri: &str,
18    options: &Vec<(&str, &str)>,
19) -> String {
20    let res = calc_oauth_header(
21        &format!("{}&{}", consumer_secret, access_token_secret),
22        consumer_key,
23        &vec![("oauth_token", access_token)],
24        method,
25        uri,
26        options,
27    );
28    format!("OAuth {}", res)
29}
30
31pub fn calc_oauth_header(
32    sign_key: &str,
33    consumer_key: &str,
34    header_options: &Vec<(&str, &str)>,
35    method: &str,
36    uri: &str,
37    options: &Vec<(&str, &str)>,
38) -> String {
39    let mut param0: Vec<(&str, String)> = vec![
40        ("oauth_consumer_key", String::from(consumer_key)),
41        ("oauth_nonce", nonce()),
42        ("oauth_signature_method", String::from("HMAC-SHA1")),
43        ("oauth_timestamp", timestamp()),
44        ("oauth_version", String::from("1.0")),
45    ];
46    for header_option in header_options {
47        param0.push((header_option.0, encode(header_option.1)));
48    }
49    let mut param1 = param0.clone();
50    for option in options {
51        param1.push((option.0, encode(option.1)));
52    }
53    param1.sort();
54    let parameter = make_query(&param1, "&");
55    let base = format!("{}&{}&{}", method, encode(uri), encode(&parameter));
56    let mut param2 = param0.clone();
57    param2.push(("oauth_signature", encode(&sign(&base, sign_key))));
58    make_query(&param2, ", ")
59}
60
61const BASE_STR: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
62
63fn nonce() -> String {
64    let mut rng = &mut rand::thread_rng();
65    String::from_utf8(
66        BASE_STR
67            .as_bytes()
68            .choose_multiple(&mut rng, 32)
69            .cloned()
70            .collect(),
71    )
72    .unwrap()
73}
74
75fn timestamp() -> String {
76    format!("{}", Utc::now().timestamp())
77}
78
79pub fn encode(s: &str) -> String {
80    // Twitter API URL encode space is %20 not +
81    url::form_urlencoded::byte_serialize(s.as_bytes()).collect::<String>().replace('+', "%20").replace('*', "%2A").replace("%7E", "~")
82}
83
84fn sign(base: &str, key: &str) -> String {
85    let mut hmac = Hmac::new(Sha1::new(), key.as_bytes());
86    hmac.input(base.as_bytes());
87    base64::encode(hmac.result().code())
88}
89
90fn make_query(list: &Vec<(&str, String)>, separator: &str) -> String {
91    let mut result = String::from("");
92    for item in list {
93        if "" != result {
94            result.push_str(separator);
95        }
96        result.push_str(&format!("{}={}", item.0, item.1));
97    }
98    result
99}
100
101#[cfg(test)]
102mod tests {
103    #[test]
104    fn it_oauth2_authorization_header() {
105        assert_eq!("Bearer abc", crate::oauth2_authorization_header("abc"));
106        println!(
107            "{}",
108            crate::oauth1_authorization_header(
109                "a",
110                "b",
111                "c",
112                "d",
113                "GET",
114                "http://localhost",
115                &vec![]
116            )
117        );
118    }
119}