oauth1_request/serializer/
auth.rs

1//! An OAuth 1.0 `Authorization` header serializer.
2
3use core::fmt::{self, Display, Write};
4use core::num::NonZeroU64;
5use core::str;
6
7use base64::{Engine as _};
8use rand::prelude::*;
9
10use crate::signature_method::{Sign, SignatureMethod};
11use crate::util::*;
12use crate::Credentials;
13
14use super::{Serializer, Urlencoder};
15
16cfg_type_param_hack! {
17    /// A `Serializer` that signs a request and produces OAuth 1.0 `oauth_*` parameter values.
18    ///
19    /// The resulting parameter values are either written to an HTTP `Authorization` header value or
20    /// URI query/`x-www-form-urlencoded` string (along with the other request parameters)
21    /// depending on the constructor you use.
22    #[derive(Clone, Debug)]
23    pub struct Authorizer<
24        'a,
25        SM: SignatureMethod,
26        #[cfg(feature = "alloc")] W = alloc::string::String,
27        #[cfg(not(feature = "alloc"))] W,
28    > {
29        consumer_key: &'a str,
30        token: Option<&'a str>,
31        options: &'a Options<'a>,
32        data: Data<W>,
33        sign: SM::Sign,
34        append_delim_to_sign: bool,
35        #[cfg(all(feature = "alloc", debug_assertions))]
36        prev_key: alloc::string::String,
37    }
38}
39
40#[derive(Clone, Debug)]
41enum Data<W> {
42    Authorization(W),
43    Urlencode(Urlencoder<W>),
44}
45
46options! {
47    /// Optional OAuth parameters.
48    #[derive(Clone, Debug, Default)]
49    pub struct Options<'a> {
50        /// Creates a blank `Options` with default values (`None`).
51        new;
52        /// Sets `oauth_callback` parameter.
53        callback: Option<&'a str>,
54        /// Sets `oauth_verifier` parameter.
55        verifier: Option<&'a str>,
56        /// Sets `oauth_nonce` parameter.
57        nonce: Option<&'a str>,
58        /// Sets `oauth_timestamp` parameter.
59        ///
60        /// The OAuth standard ([RFC 5849 section 3.3.][rfc]) says that the timestamp value
61        /// MUST be a positive integer.
62        ///
63        /// [rfc]: https://tools.ietf.org/html/rfc5849#section-3.3
64        timestamp: Option<NonZeroU64>,
65        /// Sets whether to include `oauth_version="1.0"` parameter in the `Authorization` header.
66        version: bool,
67    }
68}
69
70doc_auto_cfg! {
71    #[cfg(feature = "alloc")]
72    impl<'a, SM: SignatureMethod> Authorizer<'a, SM> {
73        /// Creates an `Authorizer` that produces an HTTP `Authorization header value.
74        ///
75        /// `uri` must not contain a query part.
76        /// Otherwise, the serializer will produce a wrong signature.
77        ///
78        /// # Panics
79        ///
80        /// In debug builds, panics if `uri` contains a `'?'` character.
81        pub fn authorization<T: Display>(
82            method: &str,
83            uri: T,
84            client: Credentials<&'a str>,
85            token: Option<Credentials<&'a str>>,
86            options: &'a Options<'a>,
87            signature_method: SM,
88        ) -> Self {
89            let buf = alloc::string::String::with_capacity(512);
90            Authorizer::authorization_with_buf(
91                buf,
92                method,
93                uri,
94                client,
95                token,
96                options,
97                signature_method,
98            )
99        }
100
101        /// Creates an `Authorizer` that produces an `x-www-form-urlencoded` string.
102        ///
103        /// `uri` must not contain a query part.
104        /// Otherwise, the serializer will produce a wrong signature.
105        ///
106        /// # Panics
107        ///
108        /// In debug builds, panics if `uri` contains a `'?'` character.
109        pub fn form<T: Display>(
110            method: &str,
111            uri: T,
112            client: Credentials<&'a str>,
113            token: Option<Credentials<&'a str>>,
114            options: &'a Options<'a>,
115            signature_method: SM,
116        ) -> Self {
117            let buf = alloc::string::String::with_capacity(512);
118            Authorizer::form_with_buf(buf, method, uri, client, token, options, signature_method)
119        }
120    }
121}
122
123impl<'a, SM: SignatureMethod, W: Write> Authorizer<'a, SM, W> {
124    /// Creates an `Authorizer` that appends a query part to `uri`.
125    ///
126    /// `uri` must not contain a query part.
127    /// Otherwise, the serializer will produce a wrong signature.
128    ///
129    /// # Panics
130    ///
131    /// In debug builds, panics if `uri` contains a `'?'` character.
132    pub fn query(
133        method: &str,
134        uri: W,
135        client: Credentials<&'a str>,
136        token: Option<Credentials<&'a str>>,
137        options: &'a Options<'a>,
138        signature_method: SM,
139    ) -> Self
140    where
141        W: Display,
142    {
143        let sign = make_sign(method, &uri, client, token, signature_method);
144        let data = Data::Urlencode(Urlencoder::query(uri));
145        Authorizer::new_(data, sign, client, token, options)
146    }
147
148    /// Same as `authorization` except that this writes the resulting `Authorization` header value
149    /// into `buf`.
150    pub fn authorization_with_buf<T: Display>(
151        mut buf: W,
152        method: &str,
153        uri: T,
154        client: Credentials<&'a str>,
155        token: Option<Credentials<&'a str>>,
156        options: &'a Options<'a>,
157        signature_method: SM,
158    ) -> Self {
159        buf.write_str("OAuth ").unwrap();
160        let data = Data::Authorization(buf);
161        let sign = make_sign(method, uri, client, token, signature_method);
162        Authorizer::new_(data, sign, client, token, options)
163    }
164
165    /// Same with `form` except that this writes the resulting form string into `buf`.
166    pub fn form_with_buf<T: Display>(
167        buf: W,
168        method: &str,
169        uri: T,
170        client: Credentials<&'a str>,
171        token: Option<Credentials<&'a str>>,
172        options: &'a Options<'a>,
173        signature_method: SM,
174    ) -> Self {
175        let data = Data::Urlencode(Urlencoder::form_with_buf(buf));
176        let sign = make_sign(method, uri, client, token, signature_method);
177        Authorizer::new_(data, sign, client, token, options)
178    }
179
180    fn new_(
181        data: Data<W>,
182        sign: SM::Sign,
183        client: Credentials<&'a str>,
184        token: Option<Credentials<&'a str>>,
185        options: &'a Options<'a>,
186    ) -> Self {
187        cfg_if::cfg_if! {
188            if #[cfg(all(feature = "alloc", debug_assertions))] {
189                Authorizer {
190                    consumer_key: client.identifier,
191                    token: token.map(|t| t.identifier),
192                    options,
193                    data,
194                    sign,
195                    append_delim_to_sign: false,
196                    prev_key: alloc::string::String::new(),
197                }
198            } else {
199                Authorizer {
200                    consumer_key: client.identifier,
201                    token: token.map(|t| t.identifier),
202                    options,
203                    data,
204                    sign,
205                    append_delim_to_sign: false,
206                }
207            }
208        }
209    }
210}
211
212fn make_sign<SM: SignatureMethod, T: Display>(
213    method: &str,
214    uri: T,
215    client: Credentials<&str>,
216    token: Option<Credentials<&str>>,
217    signature_method: SM,
218) -> SM::Sign {
219    // This is a no_alloc-equivalent of `assert!(!uri.to_string().contains('?'))`.
220    // We can determine if the URI contains a query part by just checking if it contains a `'?'`
221    // character, because the scheme and authority part of a valid URI does not contain
222    // that character.
223    #[cfg(debug_assertions)]
224    {
225        struct AssertNotContainQuestion;
226        impl Write for AssertNotContainQuestion {
227            #[track_caller]
228            fn write_str(&mut self, uri: &str) -> fmt::Result {
229                assert!(!uri.contains('?'), "`uri` must not contain a query part");
230                Ok(())
231            }
232        }
233        write!(AssertNotContainQuestion, "{}", uri).unwrap();
234    }
235
236    let mut ret = signature_method.sign_with(client.secret, token.map(|t| t.secret));
237    ret.request_method(method);
238    ret.uri(PercentEncode(uri));
239
240    ret
241}
242
243impl<'a, SM: SignatureMethod, W: Write> Authorizer<'a, SM, W> {
244    fn append_to_header_encoded<V: Display>(&mut self, k: &str, v: V) {
245        self.check_dictionary_order(k);
246        match self.data {
247            Data::Authorization(ref mut header) => write!(header, r#"{}="{}","#, k, v).unwrap(),
248            Data::Urlencode(ref mut encoder) => encoder.serialize_parameter_encoded(k, v),
249        }
250        self.sign_delimiter();
251    }
252
253    fn sign_delimiter(&mut self) {
254        if self.append_delim_to_sign {
255            self.sign.delimiter();
256        } else {
257            self.append_delim_to_sign = true;
258        }
259    }
260
261    fn check_dictionary_order(&mut self, _k: &str) {
262        #[cfg(all(feature = "alloc", debug_assertions))]
263        {
264            assert!(
265                *self.prev_key <= *_k,
266                "appended key is less than previously appended one in dictionary order\
267                 \n previous: `{:?}`,\
268                 \n  current: `{:?}`",
269                self.prev_key,
270                _k,
271            );
272            self.prev_key.clear();
273            self.prev_key.push_str(_k);
274        }
275    }
276}
277
278macro_rules! append_to_header {
279    (@inner $self:expr, $k:ident, $v:expr, $w:expr) => {{
280        let this = $self;
281        let k = concat!("oauth_", stringify!($k));
282        this.append_to_header_encoded(k, $v);
283        this.sign.$k($w);
284    }};
285    ($self:expr, encoded $k:ident, $v:expr) => {{
286        let v = $v;
287        append_to_header!(@inner $self, $k, v, v);
288    }};
289    ($self:expr, $k:ident, $v:expr) => {{
290        let v = $v;
291        append_to_header!(@inner $self, $k, percent_encode(v), DoublePercentEncode(v));
292    }};
293}
294
295impl<'a, SM: SignatureMethod, W: Write> Serializer for Authorizer<'a, SM, W> {
296    type Output = W;
297
298    fn serialize_parameter<V: Display>(&mut self, key: &str, value: V) {
299        self.check_dictionary_order(key);
300        self.sign_delimiter();
301        self.sign.parameter(key, DoublePercentEncode(value));
302    }
303
304    fn serialize_parameter_encoded<V: Display>(&mut self, key: &str, value: V) {
305        self.check_dictionary_order(key);
306        self.sign_delimiter();
307        self.sign.parameter(key, PercentEncode(value));
308    }
309
310    fn serialize_oauth_callback(&mut self) {
311        if let Some(c) = self.options.callback {
312            append_to_header!(self, callback, c);
313        }
314    }
315
316    fn serialize_oauth_consumer_key(&mut self) {
317        append_to_header!(self, consumer_key, self.consumer_key);
318    }
319
320    fn serialize_oauth_nonce(&mut self) {
321        if self.sign.use_nonce() {
322            if let Some(n) = self.options.nonce {
323                append_to_header!(self, nonce, n);
324            } else {
325                let mut nonce_buf = Default::default();
326                append_to_header!(self, encoded nonce, gen_nonce(&mut nonce_buf, &mut get_rng()));
327            }
328        }
329    }
330
331    fn serialize_oauth_signature_method(&mut self) {
332        let v = self.sign.get_signature_method_name();
333        self.append_to_header_encoded("oauth_signature_method", v);
334        self.sign.signature_method();
335    }
336
337    fn serialize_oauth_timestamp(&mut self) {
338        if self.sign.use_timestamp() {
339            let t = if let Some(t) = self.options.timestamp {
340                t.get()
341            } else {
342                get_current_timestamp()
343            };
344            append_to_header!(self, encoded timestamp, t);
345        }
346    }
347
348    fn serialize_oauth_token(&mut self) {
349        if let Some(t) = self.token {
350            append_to_header!(self, token, t);
351        }
352    }
353
354    fn serialize_oauth_verifier(&mut self) {
355        if let Some(v) = self.options.verifier {
356            append_to_header!(self, verifier, v);
357        }
358    }
359
360    fn serialize_oauth_version(&mut self) {
361        if self.options.version {
362            self.append_to_header_encoded("oauth_version", "1.0");
363            self.sign.version();
364        }
365    }
366
367    fn end(self) -> W {
368        let Self { data, sign, .. } = self;
369
370        match data {
371            Data::Authorization(mut header) => {
372                header.write_str("oauth_signature=").unwrap();
373                write!(header, r#""{}""#, sign.end()).unwrap();
374                header
375            }
376            Data::Urlencode(mut encoder) => {
377                encoder.serialize_parameter_encoded("oauth_signature", sign.end());
378                encoder.end()
379            }
380        }
381    }
382}
383
384fn get_current_timestamp() -> u64 {
385    cfg_if::cfg_if! {
386        // `std::time::SystemTime::now` is not supported and panics on `wasm32-unknown-unknown` target
387        if #[cfg(all(feature = "js", target_arch = "wasm32", target_os = "unknown"))] {
388            (js_sys::Date::now() / 1000.0) as u64
389        } else if #[cfg(feature = "std")] {
390            use std::time::{SystemTime, UNIX_EPOCH};
391            match SystemTime::now().duration_since(UNIX_EPOCH) {
392                Ok(d) => d.as_secs(),
393                Err(_) => 1,
394            }
395        } else {
396            panic!(
397                "Attempted to get current timestamp in `no_std` mode. You must either use a \
398                signature method that do not use timestamp (i.e. SignatureMethod::Sign::timestamp` \
399                returns `true`) or explicitly set the timestamp via `Builder::timestamp` or \
400                `serializer::auth::Options::timestamp`",
401            );
402        }
403    }
404}
405
406fn get_rng() -> impl RngCore + CryptoRng {
407    cfg_if::cfg_if! {
408        if #[cfg(feature = "std")] {
409            thread_rng()
410        } else {
411            rand::rngs::OsRng
412        }
413    }
414}
415
416// This is worth 72 bits of entropy. The nonce is required to be unique across all requests with
417// the same timestamp, client and token. Even if you generate the nonce one million times a second
418// (which is unlikely unless you are DoS-ing the server or something), the expected time it takes
419// until getting a collision is about 299 years (*), which should be sufficient in practice.
420//
421// (*): the probability that there is at least one nonce collision in a second is:
422//     P = 1 - (2^72 - 1)/(2^72) * (2^72 - 2)/(2^72) * ... * (2^72 - 999999)/(2^72)
423// (birthday problem), and the expected number of seconds it takes until getting a collision with
424// the same timestamp is 1/P.
425const NONCE_LEN: usize = 12;
426
427fn gen_nonce<'a, R: RngCore + CryptoRng>(buf: &'a mut [u8; NONCE_LEN], rng: &mut R) -> &'a str {
428    let mut rand = [0_u8; NONCE_LEN * 3 / 4];
429    rng.fill_bytes(&mut rand);
430
431    // Trim leading zeroes to be stingy.
432    let i = rand.iter().position(|&b| b != 0).unwrap_or(rand.len());
433    let rand = &rand[i..];
434
435    let len = base64::engine::general_purpose::URL_SAFE_NO_PAD
436        .encode_slice(rand, buf)
437        .unwrap();
438    let buf = &buf[..len];
439
440    str::from_utf8(buf).unwrap()
441}