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(¶m1, "&");
99 let base = format!("{}&{}&{}", method, encode(uri), encode(¶meter));
100 let mut param2 = param0.clone();
101 param2.push(("oauth_signature", encode(&sign(&base, sign_key))));
102 make_query(¶m2, ", ")
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 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}