Skip to main content

twapi_v2/
oauth10a.rs

1use crate::api::Authentication;
2use base64::{Engine as _, engine::general_purpose};
3use chrono::prelude::*;
4use hmac::{Hmac, Mac};
5use rand::distr::{Alphanumeric, SampleString};
6use reqwest::RequestBuilder;
7use sha1::Sha1;
8
9type HmacSha1 = Hmac<Sha1>;
10
11pub struct OAuthAuthentication {
12    consumer_key: String,
13    consumer_secret: String,
14    access_key: String,
15    access_secret: String,
16}
17
18impl OAuthAuthentication {
19    pub fn new<T: Into<String>>(
20        consumer_key: T,
21        consumer_secret: T,
22        access_key: T,
23        access_secret: T,
24    ) -> Self {
25        Self {
26            consumer_key: consumer_key.into(),
27            consumer_secret: consumer_secret.into(),
28            access_key: access_key.into(),
29            access_secret: access_secret.into(),
30        }
31    }
32}
33
34impl Authentication for OAuthAuthentication {
35    fn execute(
36        &self,
37        builder: RequestBuilder,
38        method: &str,
39        uri: &str,
40        options: &[(&str, &str)],
41    ) -> RequestBuilder {
42        let auth = oauth1_authorization_header(
43            &self.consumer_key,
44            &self.consumer_secret,
45            &self.access_key,
46            &self.access_secret,
47            method,
48            uri,
49            &options.to_vec(),
50        );
51        builder.header(reqwest::header::AUTHORIZATION, auth)
52    }
53}
54
55fn oauth1_authorization_header(
56    consumer_key: &str,
57    consumer_secret: &str,
58    access_token: &str,
59    access_token_secret: &str,
60    method: &str,
61    uri: &str,
62    options: &Vec<(&str, &str)>,
63) -> String {
64    let res = calc_oauth_header(
65        &format!("{}&{}", consumer_secret, access_token_secret),
66        consumer_key,
67        &vec![("oauth_token", access_token)],
68        method,
69        uri,
70        options,
71    );
72    format!("OAuth {}", res)
73}
74
75fn calc_oauth_header(
76    sign_key: &str,
77    consumer_key: &str,
78    header_options: &Vec<(&str, &str)>,
79    method: &str,
80    uri: &str,
81    options: &Vec<(&str, &str)>,
82) -> String {
83    let mut param0: Vec<(&str, String)> = vec![
84        ("oauth_consumer_key", String::from(consumer_key)),
85        ("oauth_nonce", nonce()),
86        ("oauth_signature_method", String::from("HMAC-SHA1")),
87        ("oauth_timestamp", timestamp()),
88        ("oauth_version", String::from("1.0")),
89    ];
90    for header_option in header_options {
91        param0.push((header_option.0, encode(header_option.1)));
92    }
93    let mut param1 = param0.clone();
94    for option in options {
95        param1.push((option.0, encode(option.1)));
96    }
97    param1.sort();
98    let parameter = make_query(&param1, "&");
99    let base = format!("{}&{}&{}", method, encode(uri), encode(&parameter));
100    let mut param2 = param0.clone();
101    param2.push(("oauth_signature", encode(&sign(&base, sign_key))));
102    make_query(&param2, ", ")
103}
104
105fn nonce() -> String {
106    let mut rng = &mut rand::rng();
107    Alphanumeric.sample_string(&mut rng, 32)
108}
109
110fn timestamp() -> String {
111    format!("{}", Utc::now().timestamp())
112}
113
114pub fn encode(s: &str) -> String {
115    // Twitter API URL encode space is %20 not +
116    form_urlencoded::byte_serialize(s.as_bytes())
117        .collect::<String>()
118        .replace('+', "%20")
119        .replace('*', "%2A")
120        .replace("%7E", "~")
121}
122
123fn sign(base: &str, key: &str) -> String {
124    let mut mac = HmacSha1::new_from_slice(key.as_bytes()).expect("HMAC can take key of any size");
125    mac.update(base.as_bytes());
126    let result = mac.finalize();
127    general_purpose::STANDARD.encode(result.into_bytes())
128}
129
130fn make_query(list: &Vec<(&str, String)>, separator: &str) -> String {
131    let mut result = String::from("");
132    for item in list {
133        if !result.is_empty() {
134            result.push_str(separator);
135        }
136        result.push_str(&format!("{}={}", item.0, item.1));
137    }
138    result
139}
140
141#[cfg(test)]
142mod tests {
143    use crate::oauth10a::oauth1_authorization_header;
144    #[test]
145    fn it_oauth2_authorization_header() {
146        println!(
147            "{}",
148            oauth1_authorization_header("a", "b", "c", "d", "GET", "http://localhost", &vec![])
149        );
150    }
151}