oauth1_request/
serializer.rs

1//! Low-level machinery to convert a `Request` to a signature or a URI query/form string.
2
3doc_auto_cfg! {
4    pub mod auth;
5    #[cfg(feature = "test")]
6    pub mod recorder;
7    pub mod urlencode;
8}
9
10doc_auto_cfg! {
11    pub use auth::Authorizer;
12    #[cfg(feature = "test")]
13    pub use recorder::Recorder;
14    pub use urlencode::Urlencoder;
15}
16
17use core::fmt::Display;
18
19/// Helper macro for implementors of `Serializer` which generates blank implementation of
20/// `serialize_oauth_*` methods.
21///
22/// This is useful for implementing a `Serializer` that does not involve OAuth authorization
23/// process (e.g. [`Urlencoder`]).
24#[macro_export]
25macro_rules! skip_serialize_oauth_parameters {
26    () => {
27        fn serialize_oauth_callback(&mut self) {}
28        fn serialize_oauth_consumer_key(&mut self) {}
29        fn serialize_oauth_nonce(&mut self) {}
30        fn serialize_oauth_signature_method(&mut self) {}
31        fn serialize_oauth_timestamp(&mut self) {}
32        fn serialize_oauth_token(&mut self) {}
33        fn serialize_oauth_verifier(&mut self) {}
34        fn serialize_oauth_version(&mut self) {}
35    };
36}
37
38#[doc(inline)]
39pub use skip_serialize_oauth_parameters;
40
41/// A `Serializer` will be fed with the key-value pairs of a request
42/// and produces a single value from them.
43///
44/// A `Request` implementation `serialize`s itself by feeding a `Serializer` with its key-value
45/// pairs through the serializer's `serialize_*` methods. The `serialize_*` method calls correspond
46/// to appending parameters to the signature base string ([RFC 5849 section 3.4.1.][rfc]) of
47/// the OAuth request, and the key-value pairs must be serialized in ascending dictionary order.
48///
49/// [rfc]: https://tools.ietf.org/html/rfc5849#section-3.4.1
50///
51#[cfg_attr(all(feature = "alloc", feature = "hmac-sha1"), doc = " ```")]
52#[cfg_attr(not(all(feature = "alloc", feature = "hmac-sha1")), doc = " ```ignore")]
53/// # extern crate oauth1_request as oauth;
54/// #
55/// use std::num::NonZeroU64;
56///
57/// use oauth::serializer::auth::{self, Authorizer};
58/// use oauth::serializer::{Serializer, SerializerExt};
59///
60/// // Create an OAuth 1.0 `Authorization` header serializer.
61/// let client = oauth::Credentials::new("consumer_key", "consumer_secret");
62/// let token = oauth::Credentials::new("token", "token_secret");
63/// let options = auth::Options::new();
64/// # let mut options = options;
65/// # options.nonce("mo8_whwD5c91").timestamp(NonZeroU64::new(1234567890));
66/// let mut serializer = Authorizer::authorization(
67///     "GET",
68///     "https://example.com/api/v1/get.json",
69///     client,
70///     Some(token),
71///     &options,
72///     oauth::HMAC_SHA1,
73/// );
74///
75/// // The parameters must be serialized in ascending ordering.
76/// serializer.serialize_parameter("abc", "value");
77/// serializer.serialize_parameter("lmn", "something");
78///
79/// // Add `oauth_*` parameters to the signature base string.
80/// serializer.serialize_oauth_parameters();
81///
82/// // Continue serializing parameters greater than `oauth_*=...`.
83/// serializer.serialize_parameter("qrs", "stuff");
84/// serializer.serialize_parameter("xyz", "blah-blah");
85///
86/// let authorization = serializer.end();
87///
88/// assert_eq!(
89///     authorization,
90///     "OAuth \
91///      oauth_consumer_key=\"consumer_key\",\
92///      oauth_nonce=\"mo8_whwD5c91\",\
93///      oauth_signature_method=\"HMAC-SHA1\",\
94///      oauth_timestamp=\"1234567890\",\
95///      oauth_token=\"token\",\
96///      oauth_signature=\"eC5rUmIcYvAaIIWCIvOwhgUDByk%3D\"",
97/// );
98/// ```
99pub trait Serializer {
100    /// The type of the value produced by this serializer.
101    type Output;
102
103    /// Serializes a key-value pair.
104    ///
105    /// The serializer percent encodes the value, but not the key.
106    ///
107    /// # Panics
108    ///
109    /// The parameters must be serialized in byte ascending order
110    /// and implementations may panic otherwise.
111    fn serialize_parameter<V>(&mut self, key: &str, value: V)
112    where
113        V: Display;
114
115    /// Serializes a key-value pair.
116    ///
117    /// This treats the value as already percent encoded and will not encode it again.
118    ///
119    /// # Panics
120    ///
121    /// The parameters must be serialized in byte ascending order
122    /// and implementations may panic otherwise.
123    fn serialize_parameter_encoded<V>(&mut self, key: &str, value: V)
124    where
125        V: Display;
126
127    /// Appends `oauth_callback` parameter to the `Authorization` header.
128    ///
129    /// This must be called exactly once in a serialization process.
130    fn serialize_oauth_callback(&mut self);
131
132    /// Appends `oauth_consumer_key` parameter to the `Authorization` header.
133    ///
134    /// This must be called exactly once in a serialization process.
135    fn serialize_oauth_consumer_key(&mut self);
136
137    /// Appends `oauth_nonce` parameter to the `Authorization` header.
138    ///
139    /// This must be called exactly once in a serialization process.
140    fn serialize_oauth_nonce(&mut self);
141
142    /// Appends `oauth_signature_method` parameter to the `Authorization` header.
143    ///
144    /// This must be called exactly once in a serialization process.
145    fn serialize_oauth_signature_method(&mut self);
146
147    /// Appends `oauth_timestamp` parameter to the `Authorization` header.
148    ///
149    /// This must be called exactly once in a serialization process.
150    fn serialize_oauth_timestamp(&mut self);
151
152    /// Appends `oauth_token` parameter to the `Authorization` header.
153    ///
154    /// This must be called exactly once in a serialization process.
155    fn serialize_oauth_token(&mut self);
156
157    /// Appends `oauth_verifier` parameter to the `Authorization` header.
158    ///
159    /// This must be called exactly once in a serialization process.
160    fn serialize_oauth_verifier(&mut self);
161
162    /// Appends `oauth_version` parameter to the `Authorization` header.
163    ///
164    /// This must be called exactly once in a serialization process.
165    fn serialize_oauth_version(&mut self);
166
167    /// Finalizes the serialization and returns the serialized value.
168    fn end(self) -> Self::Output;
169}
170
171/// An extension trait for `Serializer` that provides convenience methods.
172pub trait SerializerExt: Serializer {
173    /// Appends all `oauth_*` parameter to the `Authorization` header.
174    fn serialize_oauth_parameters(&mut self);
175}
176
177impl<S: Serializer> SerializerExt for S {
178    fn serialize_oauth_parameters(&mut self) {
179        self.serialize_oauth_callback();
180        self.serialize_oauth_consumer_key();
181        self.serialize_oauth_nonce();
182        self.serialize_oauth_signature_method();
183        self.serialize_oauth_timestamp();
184        self.serialize_oauth_token();
185        self.serialize_oauth_verifier();
186        self.serialize_oauth_version();
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    #[cfg(not(feature = "std"))]
193    extern crate std;
194
195    use std::println;
196    use std::string::{String, ToString};
197
198    use super::*;
199
200    #[cfg(feature = "hmac-sha1")]
201    use crate::signature_method::HmacSha1;
202    use crate::signature_method::{Plaintext, Sign, SignatureMethod};
203    #[cfg(any(feature = "alloc", feature = "hmac-sha1"))]
204    use crate::Credentials;
205
206    // These values are taken from Twitter's document:
207    // https://developer.twitter.com/en/docs/basics/authentication/guides/creating-a-signature.html
208    cfg_if::cfg_if! {
209        if #[cfg(any(feature = "alloc", feature = "hmac-sha1"))] {
210            const CK: &str = "xvz1evFS4wEEPTGEFPHBog";
211            const CS: &str = "kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw";
212            const AK: &str = "370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb";
213            const AS: &str = "LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE";
214        }
215    }
216    #[cfg(feature = "hmac-sha1")]
217    const NONCE: &str = "kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg";
218    #[cfg(feature = "hmac-sha1")]
219    const TIMESTAMP: u64 = 1318622958;
220
221    struct Inspect<SM>(SM);
222    struct InspectSign<S>(S);
223
224    impl<SM: SignatureMethod> SignatureMethod for Inspect<SM> {
225        type Sign = InspectSign<SM::Sign>;
226
227        fn sign_with(self, client_secret: &str, token_secret: Option<&str>) -> Self::Sign {
228            println!("client_secret: {:?}", client_secret);
229            println!("token_secret: {:?}", token_secret);
230            InspectSign(self.0.sign_with(client_secret, token_secret))
231        }
232    }
233
234    #[allow(dead_code)]
235    #[derive(Clone, Debug)]
236    struct AssertImpl<'a>(
237        #[cfg(feature = "hmac-sha1")] Authorizer<'a, HmacSha1, String>,
238        Authorizer<'a, Plaintext<String>, String>,
239    );
240
241    impl<S: Sign> Sign for InspectSign<S> {
242        type Signature = S::Signature;
243
244        fn get_signature_method_name(&self) -> &'static str {
245            self.0.get_signature_method_name()
246        }
247        fn request_method(&mut self, method: &str) {
248            println!("method: {:?}", method);
249            self.0.request_method(method);
250        }
251        fn uri<T: Display>(&mut self, uri: T) {
252            println!("uri: {:?}", uri.to_string());
253            self.0.uri(uri);
254        }
255        fn delimiter(&mut self) {
256            println!("delimiter");
257            self.0.delimiter();
258        }
259        fn parameter<V: Display>(&mut self, k: &str, v: V) {
260            println!("parameter: {:?}={:?}", k, v.to_string());
261            self.0.parameter(k, v);
262        }
263        fn end(self) -> S::Signature {
264            println!("end");
265            self.0.end()
266        }
267    }
268
269    #[cfg(feature = "hmac-sha1")]
270    #[test]
271    fn serialize() {
272        use core::num::NonZeroU64;
273        use std::format;
274
275        use crate::serializer::auth;
276
277        macro_rules! test {
278            ($((
279                $method:expr, $ep:expr,
280                $ck:expr, $cs:expr, $t:expr, $ts:expr,
281                $nonce:expr, $timestamp:expr,
282                { $($param1:tt)* }, { $($param2:tt)* } $(,)*
283            ) -> ($expected_sign:expr, $expected_data:expr $(,)*);)*) => {
284                let client = Credentials::new(CK, CS);
285                let token = Credentials::new(AK, AS);
286                let mut options = auth::Options::new();
287                $(
288                    options.nonce($nonce)
289                        .timestamp($timestamp)
290                        .version(true);
291                    let mut auth = Authorizer::authorization_with_buf(
292                        String::new(),
293                        $method,
294                        $ep,
295                        client,
296                        Some(token),
297                        &options,
298                        Inspect(crate::HMAC_SHA1),
299                    );
300
301                    test_inner! { auth; $($param1)* }
302                    auth.serialize_oauth_parameters();
303                    test_inner! { auth; $($param2)* }
304
305                    let authorization = auth.end();
306                    let expected = format!(
307                        "OAuth \
308                        oauth_consumer_key=\"{}\",\
309                        oauth_nonce=\"{}\",\
310                        oauth_signature_method=\"HMAC-SHA1\",\
311                        oauth_timestamp=\"{}\",\
312                        oauth_token=\"{}\",\
313                        oauth_version=\"1.0\",\
314                        oauth_signature=\"{}\"",
315                        $ck,
316                        $nonce,
317                        $timestamp,
318                        token.identifier,
319                        $expected_sign,
320                    );
321                    assert_eq!(authorization, expected);
322
323                    let mut urlencoded = if $method == "POST" {
324                        Urlencoder::form_with_buf(String::new())
325                    } else {
326                        Urlencoder::query($ep.to_string())
327                    };
328
329                    test_inner! { urlencoded; $($param1)* }
330                    urlencoded.serialize_oauth_parameters();
331                    test_inner! { urlencoded; $($param2)* }
332
333                    let data = urlencoded.end();
334                    assert_eq!(data, $expected_data);
335                )*
336            };
337        }
338
339        macro_rules! test_inner {
340            ($ser:ident; encoded $key:ident: $v:expr, $($rest:tt)*) => {
341                $ser.serialize_parameter_encoded(stringify!($key), $v);
342                test_inner! { $ser; $($rest)* }
343            };
344            ($ser:ident; $key:ident: $v:expr, $($rest:tt)*) => {
345                $ser.serialize_parameter(stringify!($key), $v);
346                test_inner! { signerb; $($rest)* }
347            };
348            ($_signer:ident;) => ();
349        }
350
351        let timestamp = NonZeroU64::new(TIMESTAMP).unwrap();
352
353        test! {
354            (
355                "GET", "https://stream.twitter.com/1.1/statuses/sample.json",
356                CK, CS, AK, AS, NONCE, timestamp,
357                {}, { encoded stall_warnings: "true", },
358            ) -> (
359                "OGQqcy4l5xWBFX7t0DrkP5%2FD0rM%3D",
360                "https://stream.twitter.com/1.1/statuses/sample.json?stall_warnings=true",
361            );
362            (
363                "POST", "https://api.twitter.com/1.1/statuses/update.json",
364                CK, CS, AK, AS, NONCE, timestamp,
365                { encoded include_entities: "true", },
366                { status: "Hello Ladies + Gentlemen, a signed OAuth request!", },
367            ) -> (
368                "hCtSmYh%2BiHYCEqBWrE7C7hYmtUk%3D",
369                "include_entities=true&\
370                    status=Hello%20Ladies%20%2B%20Gentlemen%2C%20a%20signed%20OAuth%20request%21",
371            );
372            ("POST", "https://example.com/post.json", CK, CS, AK, AS, NONCE, timestamp, {}, {})
373                -> ("pN52L1gJ6sOyYOyv23cwfWFsIZc%3D", "");
374            (
375                "GET", "https://example.com/get.json",
376                CK, CS, AK, AS, NONCE, timestamp,
377                { encoded bar: "%E9%85%92%E5%A0%B4", foo: "ふー", }, {},
378            ) -> (
379                "Xp35hf3T21yhpEuxez7p6bV62Bw%3D",
380                "https://example.com/get.json?bar=%E9%85%92%E5%A0%B4&foo=%E3%81%B5%E3%83%BC",
381            );
382        }
383    }
384
385    #[cfg(all(feature = "alloc", debug_assertions))]
386    #[test]
387    #[should_panic(
388        expected = "appended key is less than previously appended one in dictionary order\
389                    \n previous: `\"foo\"`,\
390                    \n  current: `\"bar\"`"
391    )]
392    fn panic_on_misordering() {
393        let client = Credentials::new(CK, CS);
394        let token = Credentials::new(AK, AS);
395        let options = auth::Options::default();
396        let mut ser = Authorizer::authorization_with_buf(
397            String::new(),
398            "",
399            "",
400            client,
401            Some(token),
402            &options,
403            Plaintext::<String>::with_buf(),
404        );
405        ser.serialize_parameter_encoded("foo", true);
406        ser.serialize_parameter("bar", "ばー!");
407    }
408}