reqwest_oauth1/
signer.rs

1use std::{borrow::Cow, collections::HashMap};
2
3use crate::{SecretsProvider, SignResult, SignerError};
4use crate::{
5    OAUTH_CALLBACK_KEY, OAUTH_CONSUMER_KEY, OAUTH_KEY_PREFIX, OAUTH_NONCE_KEY,
6    OAUTH_SIGNATURE_METHOD_KEY, OAUTH_TIMESTAMP_KEY, OAUTH_TOKEN_KEY, OAUTH_VERIFIER_KEY,
7    OAUTH_VERSION_KEY, REALM_KEY,
8};
9use http::Method;
10use oauth1_request::signature_method::SignatureMethod;
11use oauth1_request::signer::Signer as OAuthSigner;
12use oauth1_request::{HmacSha1, Options};
13use url::Url;
14
15/**
16Provides OAuth signature with [oauth1-request](https://crates.io/crates/oauth1-request).
17
18# Note
19
20This struct is intended for internal use.
21
22You may consider use the struct provided from oauth1-request crate directly
23instead of this struct.
24
25*/
26#[derive(Debug, Clone)]
27pub struct Signer<'a, TSecrets, TSM>
28where
29    TSecrets: SecretsProvider + Clone,
30    TSM: SignatureMethod + Clone,
31{
32    secrets: TSecrets,
33    parameters: Result<OAuthParameters<'a, TSM>, SignerError>,
34}
35
36impl<'a, TSecretsProvider, TSM> Signer<'a, TSecretsProvider, TSM>
37where
38    TSecretsProvider: SecretsProvider + Clone,
39    TSM: SignatureMethod + Clone,
40{
41    pub fn new(secrets: TSecretsProvider, parameters: OAuthParameters<'a, TSM>) -> Self {
42        Signer {
43            secrets,
44            parameters: Ok(parameters),
45        }
46    }
47
48    pub fn override_oauth_parameter(mut self, parameters: HashMap<String, String>) -> Self {
49        for (key, value) in parameters {
50            self.parameters = match self.parameters {
51                Ok(p) => match key.as_str() {
52                    // always success
53                    OAUTH_CALLBACK_KEY => Ok(p.callback(value)),
54                    OAUTH_NONCE_KEY => Ok(p.nonce(value)),
55                    OAUTH_VERIFIER_KEY => Ok(p.verifier(value)),
56                    REALM_KEY => Ok(p.realm(value)),
57                    // potential to fail
58                    OAUTH_TIMESTAMP_KEY => match value.parse::<u64>() {
59                        Ok(v) => Ok(p.timestamp(v)),
60                        Err(_) => Err(SignerError::InvalidTimestamp(value)),
61                    },
62                    OAUTH_VERSION_KEY => match value.as_str() {
63                        "1.0" => Ok(p.version(true)),
64                        "" => Ok(p.version(false)),
65                        _ => Err(SignerError::InvalidVersion(value)),
66                    },
67                    // always fail
68                    OAUTH_SIGNATURE_METHOD_KEY | OAUTH_CONSUMER_KEY | OAUTH_TOKEN_KEY => {
69                        Err(SignerError::UnconfigurableParameter(key))
70                    }
71                    _ => Err(SignerError::UnknownParameter(key)),
72                },
73                Err(e) => Err(e),
74            };
75        }
76
77        self
78    }
79
80    /// Generate OAuth signature with specified parameters.
81    pub(crate) fn generate_signature(
82        self,
83        method: Method,
84        url: Url,
85        payload: &str,
86        is_url_query: bool,
87    ) -> SignResult<String> {
88        let (consumer_key, consumer_secret) = self.secrets.get_consumer_key_pair();
89        let (token, token_secret) = self.secrets.get_token_option_pair();
90        // build oauth option
91        let params = self.parameters?;
92        let options = params.build_options(token);
93
94        // destructure query and sort by alphabetical order
95        let parsed_payload: Vec<(Cow<str>, Cow<str>)> =
96            url::form_urlencoded::parse(payload.as_bytes())
97                .into_iter()
98                .collect();
99        // add `oauth_` key to identify where to divide
100        let oauth_identifier = vec![(Cow::from(OAUTH_KEY_PREFIX), Cow::from(""))];
101        let mut sorted_query = [parsed_payload, oauth_identifier].concat();
102
103        // then, sort by alphabetical order (that is required by OAuth specification)
104        sorted_query.sort();
105
106        // divide key-value items by the element has "oauth_" key
107        let mut divided = sorted_query
108            .splitn(2, |(k, _)| k == &OAUTH_KEY_PREFIX)
109            .into_iter();
110        let query_before_oauth = divided.next().unwrap();
111        let query_after_oauth = divided.next().unwrap_or_default();
112
113        // generate signature
114        // Step 0. instantiate sign generator
115        let sig_method = params.signature_method.clone();
116        // println!("signing url: {:#?}", url);
117        let mut signer = generate_signer(
118            sig_method,
119            method.as_str(),
120            url,
121            consumer_secret,
122            token_secret,
123            is_url_query,
124        );
125
126        // Step 1. key [a ~ oauth_)
127        for (key, value) in query_before_oauth {
128            if !key.starts_with(OAUTH_KEY_PREFIX) {
129                // not an oauth_* parameter
130                signer.parameter(key, value);
131            }
132        }
133        // Step 2. add oauth_* parameters
134        let mut signer = signer.oauth_parameters(consumer_key, &options);
135        // Step 3. key (oauth_ ~ z]
136        for (key, value) in query_after_oauth {
137            if !key.starts_with(OAUTH_KEY_PREFIX) {
138                // not an oauth_* parameter
139                signer.parameter(key, value);
140            }
141        }
142
143        // signature is generated.
144        let sign = signer.finish().authorization;
145
146        if let Some(realm) = params.realm {
147            // OAuth oauth_...,realm="realm"
148            Ok(format!("{},{}=\"{}\"", sign, REALM_KEY, realm.as_ref()))
149        } else {
150            // OAuth oauth_...
151            Ok(sign)
152        }
153    }
154}
155
156fn generate_signer<TSM>(
157    signature_method: TSM,
158    method: &str,
159    url: Url,
160    consumer_secret: &str,
161    token_secret: Option<&str>,
162    is_url_query: bool,
163) -> OAuthSigner<TSM>
164where
165    TSM: SignatureMethod,
166{
167    if is_url_query {
168        OAuthSigner::with_signature_method(
169            signature_method,
170            method,
171            url,
172            consumer_secret,
173            token_secret,
174        )
175    } else {
176        OAuthSigner::form_with_signature_method(
177            signature_method,
178            method,
179            url,
180            consumer_secret,
181            token_secret,
182        )
183    }
184}
185
186/**
187Represents OAuth parameters including oauth_nonce, oauth_timestamp, realm, and others.
188
189# Basic usage
190
191```rust
192use reqwest_oauth1::*;
193
194#[cfg(feature = "blocking")]
195use reqwest::blocking::Client as Client;
196
197#[cfg(not(feature = "blocking"))]
198use reqwest::Client;
199
200let consumer_key = "[CONSUMER_KEY]";
201let consumer_secret = "[CONSUMER_SECRET]";
202let secrets = reqwest_oauth1::Secrets::new(consumer_key, consumer_secret);
203
204let nonce = "[NONCE]";
205let timestamp = 100_000_001u64;
206let callback = "http://example.com/ready";
207
208let params = reqwest_oauth1::OAuthParameters::new()
209    .nonce(nonce)
210    .timestamp(timestamp)
211    .callback(callback);
212
213let req = Client::new()
214    .oauth1_with_params(secrets, params)
215    .post("http://example.com/")
216    // and so on...
217    ;
218```
219
220# Note
221
222You can specify same parameters as get/post queries and they will superseded
223with the specified one in the OAuthParameters.
224
225```rust
226use reqwest_oauth1::*;
227
228#[cfg(feature = "blocking")]
229use reqwest::blocking::Client as Client;
230
231#[cfg(not(feature = "blocking"))]
232use reqwest::Client;
233
234let consumer_key = "[CONSUMER_KEY]";
235let consumer_secret = "[CONSUMER_SECRET]";
236let secrets = reqwest_oauth1::Secrets::new(consumer_key, consumer_secret);
237
238let params = reqwest_oauth1::OAuthParameters::new()
239    .nonce("ThisNonceWillBeSuperseded");
240let req = Client::new()
241    .oauth1_with_params(secrets, params)
242    .get("http://example.com/")
243    .query(&[("nonce", "ThisNonceWillSupersedeTheOldOne")])
244    // and so on...
245    ;
246```
247
248*/
249#[derive(Debug, Clone)]
250pub struct OAuthParameters<'a, TSM>
251where
252    TSM: SignatureMethod + Clone,
253{
254    callback: Option<Cow<'a, str>>,
255    nonce: Option<Cow<'a, str>>,
256    realm: Option<Cow<'a, str>>,
257    signature_method: TSM,
258    timestamp: Option<u64>,
259    verifier: Option<Cow<'a, str>>,
260    version: bool,
261}
262
263impl Default for OAuthParameters<'static, HmacSha1> {
264    fn default() -> Self {
265        OAuthParameters {
266            callback: None,
267            nonce: None,
268            realm: None,
269            signature_method: HmacSha1,
270            timestamp: None,
271            verifier: None,
272            version: false,
273        }
274    }
275}
276
277impl OAuthParameters<'_, HmacSha1> {
278    pub fn new() -> Self {
279        Default::default()
280    }
281}
282
283impl<'a, TSM> OAuthParameters<'a, TSM>
284where
285    TSM: SignatureMethod + Clone,
286{
287    /// set the oauth_callback value
288    pub fn callback<T>(self, callback: T) -> Self
289    where
290        T: Into<Cow<'a, str>>,
291    {
292        OAuthParameters {
293            callback: Some(callback.into()),
294            ..self
295        }
296    }
297
298    /// set the oauth_nonce value
299    pub fn nonce<T>(self, nonce: T) -> Self
300    where
301        T: Into<Cow<'a, str>>,
302    {
303        OAuthParameters {
304            nonce: Some(nonce.into()),
305            ..self
306        }
307    }
308
309    /// set the realm value
310    ///
311    /// # Note
312    /// this parameter will not be included in the signature-base string.
313    /// cf. https://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
314    pub fn realm<T>(self, realm: T) -> Self
315    where
316        T: Into<Cow<'a, str>>,
317    {
318        OAuthParameters {
319            realm: Some(realm.into()),
320            ..self
321        }
322    }
323
324    /// set the oauth_timestamp value
325    pub fn timestamp<T>(self, timestamp: T) -> Self
326    where
327        T: Into<u64>,
328    {
329        OAuthParameters {
330            timestamp: Some(timestamp.into()),
331            ..self
332        }
333    }
334
335    /// set the oauth_verifier value
336    pub fn verifier<T>(self, verifier: T) -> Self
337    where
338        T: Into<Cow<'a, str>>,
339    {
340        OAuthParameters {
341            verifier: Some(verifier.into()),
342            ..self
343        }
344    }
345
346    /// set the oauth_version value (boolean)
347    ///
348    /// # Note
349    /// When the version has value `true`, oauth_version will be set with "1.0".
350    /// Otherwise, oauth_version will not be included in your request.
351    /// In oauth1, oauth_version value must be "1.0" or not specified.
352    pub fn version<T>(self, version: T) -> Self
353    where
354        T: Into<bool>,
355    {
356        OAuthParameters {
357            version: version.into(),
358            ..self
359        }
360    }
361    pub fn signature_method<T>(self, signature_method: T) -> OAuthParameters<'a, T>
362    where
363        T: SignatureMethod + Clone,
364    {
365        OAuthParameters {
366            signature_method,
367            callback: None,
368            nonce: None,
369            realm: None,
370            timestamp: None,
371            verifier: None,
372            version: false,
373        }
374    }
375}
376
377impl<T> OAuthParameters<'_, T>
378where
379    T: SignatureMethod + Clone,
380{
381    fn build_options<'a>(&'a self, token: Option<&'a str>) -> Options<'a> {
382        let mut opt = Options::new();
383
384        // NOTE: items must be added by alphabetical order
385
386        if let Some(ref callback) = self.callback {
387            opt.callback(callback.as_ref());
388        }
389        if let Some(ref nonce) = self.nonce {
390            opt.nonce(nonce.as_ref());
391        }
392        if let Some(timestamp) = self.timestamp {
393            opt.timestamp(timestamp);
394        }
395        if let Some(token) = token {
396            opt.token(token);
397        }
398        if let Some(ref verifier) = self.verifier {
399            opt.verifier(verifier.as_ref());
400        }
401        opt.version(self.version);
402        if self.version {}
403
404        opt
405    }
406}