1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use percent_encoding::{AsciiSet, NON_ALPHANUMERIC};
use rand::distributions::{Alphanumeric, Distribution};
use rand::thread_rng;
use ring::hmac::{self, HMAC_SHA1_FOR_LEGACY_USE_ONLY};
use std::borrow::Cow;
use std::collections::HashMap;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Token<'a> {
pub key: Cow<'a, str>,
pub secret: Cow<'a, str>,
}
impl<'a> Token<'a> {
pub fn new<K, S>(key: K, secret: S) -> Self
where
K: Into<Cow<'a, str>>,
S: Into<Cow<'a, str>>,
{
Token {
key: key.into(),
secret: secret.into(),
}
}
}
pub fn authorize(
method: &str,
uri: &str,
consumer: &Token,
token: Option<&Token>,
params: Option<HashMap<&str, Cow<str>>>,
) -> String {
let mut params = params.unwrap_or_else(HashMap::new);
let timestamp = time::OffsetDateTime::now().timestamp().to_string();
let nonce: String = Alphanumeric.sample_iter(thread_rng()).take(32).collect();
params.insert("oauth_consumer_key", consumer.key.clone().into());
params.insert("oauth_nonce", nonce.into());
params.insert("oauth_signature_method", "HMAC-SHA1".into());
params.insert("oauth_timestamp", timestamp.into());
params.insert("oauth_version", "1.0".into());
if let Some(tk) = token {
params.insert("oauth_token", tk.key.as_ref().into());
}
let signature = gen_signature(
method,
uri,
&to_query(¶ms),
&consumer.secret,
token.map(|t| t.secret.as_ref()),
);
params.insert("oauth_signature", signature.into());
let mut pairs = params
.iter()
.filter(|&(k, _)| k.starts_with("oauth_"))
.map(|(k, v)| format!("{}=\"{}\"", k, encode(v)))
.collect::<Vec<_>>();
pairs.sort();
format!("OAuth {}", pairs.join(", "))
}
#[derive(Copy, Clone)]
struct StrictEncodeSet;
static STRICT_ENCODE_SET: AsciiSet = NON_ALPHANUMERIC
.remove(b'-')
.remove(b'.')
.remove(b'_')
.remove(b'~');
fn encode(s: &str) -> String {
percent_encoding::percent_encode(s.as_bytes(), &STRICT_ENCODE_SET).collect()
}
fn to_query(params: &HashMap<&str, Cow<str>>) -> String {
let mut pairs: Vec<_> = params
.iter()
.map(|(k, v)| format!("{}={}", encode(k), encode(v)))
.collect();
pairs.sort();
pairs.join("&")
}
fn gen_signature(
method: &str,
uri: &str,
query: &str,
consumer_secret: &str,
token_secret: Option<&str>,
) -> String {
let base = format!("{}&{}&{}", encode(method), encode(uri), encode(query));
let key = format!(
"{}&{}",
encode(consumer_secret),
encode(token_secret.unwrap_or(""))
);
let s_key = hmac::Key::new(HMAC_SHA1_FOR_LEGACY_USE_ONLY, key.as_ref());
let signature = hmac::sign(&s_key, base.as_bytes());
base64::encode(signature.as_ref())
}