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