xmpp_parsers/
sasl2.rs

1// Copyright (c) 2024 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7use xso::{text::Base64, AsXml, FromXml};
8
9use crate::bind2;
10use crate::ns;
11use crate::sm::StreamManagement;
12use jid::Jid;
13use minidom::Element;
14
15/// Server advertisement for supported auth mechanisms
16#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
17#[xml(namespace = ns::SASL2, name = "authentication")]
18pub struct Authentication {
19    /// Plaintext names of supported auth mechanisms
20    #[xml(extract(n = .., name = "mechanism", fields(text(type_ = String))))]
21    pub mechanisms: Vec<String>,
22
23    /// Additional auth information provided by server
24    #[xml(child(default))]
25    pub inline: Option<InlineFeatures>,
26}
27
28/// Additional auth information provided by server
29#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
30#[xml(namespace = ns::SASL2, name = "inline")]
31pub struct InlineFeatures {
32    /// Bind 2 inline feature
33    #[xml(child(default))]
34    pub bind2: Option<bind2::BindFeature>,
35
36    /// Stream management inline feature
37    #[xml(child(default))]
38    pub sm: Option<StreamManagement>,
39
40    /// Additional inline features
41    #[xml(element(n = ..))]
42    pub payloads: Vec<Element>,
43}
44
45/// Client aborts the connection.
46#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
47#[xml(namespace = ns::SASL2, name = "abort")]
48pub struct Abort {
49    /// Plaintext reason for aborting
50    #[xml(extract(default, fields(text(type_ = String))))]
51    pub text: Option<String>,
52
53    /// Extra untyped payloads
54    #[xml(element(n = ..))]
55    pub payloads: Vec<Element>,
56}
57
58/// Optional client software information
59#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
60#[xml(namespace = ns::SASL2, name = "user-agent")]
61pub struct UserAgent {
62    /// Random, unique identifier for the client
63    #[xml(attribute)]
64    pub id: uuid::Uuid,
65
66    /// Name of the client software
67    #[xml(extract(default, fields(text(type_ = String))))]
68    pub software: Option<String>,
69
70    /// Name of the client device (eg. phone/laptop)
71    #[xml(extract(default, fields(text(type_ = String))))]
72    pub device: Option<String>,
73}
74
75/// Client authentication request
76#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
77#[xml(namespace = ns::SASL2, name = "authenticate")]
78pub struct Authenticate {
79    /// Chosen SASL mechanism
80    #[xml(attribute)]
81    pub mechanism: String,
82
83    /// SASL response
84    #[xml(extract(default, name = "initial-response", fields(text = Base64)))]
85    pub initial_response: Option<Vec<u8>>,
86
87    /// Information about client software
88    #[xml(child)]
89    pub user_agent: UserAgent,
90
91    /// Extra untyped payloads
92    #[xml(element(n = ..))]
93    pub payloads: Vec<Element>,
94}
95
96/// SASL challenge
97#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
98#[xml(namespace = ns::SASL2, name = "challenge")]
99pub struct Challenge {
100    /// SASL challenge data
101    #[xml(text = Base64)]
102    pub sasl_data: Vec<u8>,
103}
104
105/// SASL response
106#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
107#[xml(namespace = ns::SASL2, name = "response")]
108pub struct Response {
109    /// SASL challenge data
110    #[xml(text = Base64)]
111    pub sasl_data: Vec<u8>,
112}
113
114/// Authentication was successful
115#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
116#[xml(namespace = ns::SASL2, name = "success")]
117pub struct Success {
118    /// Additional SASL data
119    #[xml(extract(default, name = "additional-data", fields(text = Base64)))]
120    pub additional_data: Option<Vec<u8>>,
121
122    /// Identity assigned by the server
123    #[xml(extract(name = "authorization-identifier", fields(text)))]
124    pub authorization_identifier: Jid,
125
126    /// Extra untyped payloads
127    #[xml(element(n = ..))]
128    pub payloads: Vec<Element>,
129}
130
131/// Authentication failed
132#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
133#[xml(namespace = ns::SASL2, name = "failure")]
134pub struct Failure {
135    /// Plaintext reason for failure
136    #[xml(extract(default, fields(text(type_ = String))))]
137    pub text: Option<String>,
138
139    /// Extra untyped payloads
140    #[xml(element(n = ..))]
141    pub payloads: Vec<Element>,
142}
143
144/// Authentication requires extra steps (eg. 2FA)
145#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
146#[xml(namespace = ns::SASL2, name = "continue")]
147pub struct Continue {
148    /// Additional SASL data
149    #[xml(extract(name = "additional-data", fields(text = Base64)))]
150    pub additional_data: Vec<u8>,
151
152    /// List of extra authentication steps.
153    ///
154    /// The client may choose any, but the server may respond with more Continue steps until all required
155    /// steps are fulfilled.
156    #[xml(extract(fields(extract(n = .., name = "task", fields(text(type_ = String))))))]
157    pub tasks: Vec<String>,
158
159    /// Plaintext reason for extra steps
160    #[xml(extract(default, fields(text(type_ = String))))]
161    pub text: Option<String>,
162}
163
164/// Client answers Continue extra step by selecting task.
165#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
166#[xml(namespace = ns::SASL2, name = "next")]
167pub struct Next {
168    /// Task selected by client
169    #[xml(attribute)]
170    pub task: String,
171
172    /// Extra untyped payloads
173    #[xml(element(n = ..))]
174    pub payloads: Vec<Element>,
175}
176
177/// Client/Server data exchange about selected task.
178#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
179#[xml(namespace = ns::SASL2, name = "task-data")]
180pub struct TaskData {
181    /// Extra untyped payloads
182    #[xml(element(n = ..))]
183    pub payloads: Vec<Element>,
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use base64::prelude::*;
190    use uuid::Uuid;
191
192    #[cfg(target_pointer_width = "32")]
193    #[test]
194    fn test_size() {
195        assert_size!(Authentication, 40);
196        assert_size!(InlineFeatures, 28);
197        assert_size!(Abort, 24);
198        assert_size!(UserAgent, 40);
199        assert_size!(Authenticate, 76);
200        assert_size!(Challenge, 12);
201        assert_size!(Response, 12);
202        assert_size!(Success, 40);
203        assert_size!(Failure, 24);
204        assert_size!(Continue, 36);
205        assert_size!(Next, 24);
206        assert_size!(TaskData, 12);
207    }
208
209    #[cfg(target_pointer_width = "64")]
210    #[test]
211    fn test_size() {
212        assert_size!(Authentication, 80);
213        assert_size!(InlineFeatures, 56);
214        assert_size!(Abort, 48);
215        assert_size!(UserAgent, 64);
216        assert_size!(Authenticate, 136);
217        assert_size!(Challenge, 24);
218        assert_size!(Response, 24);
219        assert_size!(Success, 80);
220        assert_size!(Failure, 48);
221        assert_size!(Continue, 72);
222        assert_size!(Next, 48);
223        assert_size!(TaskData, 24);
224    }
225
226    #[test]
227    fn test_simple() {
228        let elem: Element = "<authentication xmlns='urn:xmpp:sasl:2'><mechanism>SCRAM-SHA-1</mechanism></authentication>"
229            .parse()
230            .unwrap();
231        let auth = Authentication::try_from(elem).unwrap();
232        assert_eq!(auth.mechanisms.len(), 1);
233        assert_eq!(auth.inline, None);
234
235        let elem: Element = "<challenge xmlns='urn:xmpp:sasl:2'>AAAA</challenge>"
236            .parse()
237            .unwrap();
238        let challenge = Challenge::try_from(elem).unwrap();
239        assert_eq!(challenge.sasl_data, b"\0\0\0");
240
241        let elem: Element = "<response xmlns='urn:xmpp:sasl:2'>YWJj</response>"
242            .parse()
243            .unwrap();
244        let response = Response::try_from(elem).unwrap();
245        assert_eq!(response.sasl_data, b"abc");
246    }
247
248    // XEP-0388 Example 2
249    #[test]
250    fn test_auth() {
251        let elem: Element = r#"<authentication xmlns='urn:xmpp:sasl:2'>
252            <mechanism>SCRAM-SHA-1</mechanism>
253            <mechanism>SCRAM-SHA-1-PLUS</mechanism>
254            <inline>
255              <sm xmlns='urn:xmpp:sm:3'/>
256              <bind xmlns='urn:xmpp:bind:0'/>
257            </inline>
258          </authentication>"#
259            .parse()
260            .unwrap();
261
262        let auth = Authentication::try_from(elem).unwrap();
263
264        assert_eq!(auth.mechanisms.len(), 2);
265        let mut mech = auth.mechanisms.iter();
266        assert_eq!(mech.next().unwrap(), "SCRAM-SHA-1");
267        assert_eq!(mech.next().unwrap(), "SCRAM-SHA-1-PLUS");
268        assert_eq!(mech.next(), None);
269
270        let inline = auth.inline.unwrap();
271        assert_eq!(inline.bind2.unwrap().inline_features.len(), 0);
272        assert_eq!(
273            inline.sm.unwrap(),
274            StreamManagement {
275                optional: false,
276                required: false
277            }
278        );
279        assert_eq!(inline.payloads.len(), 0);
280    }
281
282    // XEP-0388 Example 3
283    #[test]
284    fn test_authenticate() {
285        let elem: Element = r#"<authenticate xmlns='urn:xmpp:sasl:2' mechanism='SCRAM-SHA-1-PLUS'>
286              <initial-response>cD10bHMtZXhwb3J0ZXIsLG49dXNlcixyPTEyQzRDRDVDLUUzOEUtNEE5OC04RjZELTE1QzM4RjUxQ0NDNg==</initial-response>
287              <user-agent id='d4565fa7-4d72-4749-b3d3-740edbf87770'>
288                <software>AwesomeXMPP</software>
289                <device>Kiva's Phone</device>
290              </user-agent>
291            </authenticate>"#
292                .parse()
293                .unwrap();
294
295        let auth = Authenticate::try_from(elem).unwrap();
296
297        assert_eq!(auth.mechanism, "SCRAM-SHA-1-PLUS");
298        assert_eq!(
299            auth.initial_response.unwrap(),
300            BASE64_STANDARD.decode("cD10bHMtZXhwb3J0ZXIsLG49dXNlcixyPTEyQzRDRDVDLUUzOEUtNEE5OC04RjZELTE1QzM4RjUxQ0NDNg==").unwrap()
301        );
302
303        assert_eq!(auth.user_agent.software.as_ref().unwrap(), "AwesomeXMPP");
304        assert_eq!(auth.user_agent.device.as_ref().unwrap(), "Kiva's Phone");
305    }
306
307    // XEP-0388 Example 4
308    #[test]
309    fn test_authenticate_2() {
310        let elem: Element = r#"<authenticate xmlns='urn:xmpp:sasl:2' mechanism='BLURDYBLOOP'>
311              <initial-response>SSBzaG91bGQgbWFrZSB0aGlzIGEgY29tcGV0aXRpb24=</initial-response>
312              <user-agent id='d4565fa7-4d72-4749-b3d3-740edbf87770'>
313                <software>AwesomeXMPP</software>
314                <device>Kiva's Phone</device>
315              </user-agent>
316              <bind xmlns='urn:xmpp:bind:example'/>
317            </authenticate>"#
318            .parse()
319            .unwrap();
320
321        let auth = Authenticate::try_from(elem).unwrap();
322
323        assert_eq!(auth.mechanism, "BLURDYBLOOP");
324        assert_eq!(
325            auth.initial_response.unwrap(),
326            BASE64_STANDARD
327                .decode("SSBzaG91bGQgbWFrZSB0aGlzIGEgY29tcGV0aXRpb24=")
328                .unwrap()
329        );
330
331        assert_eq!(auth.user_agent.software.as_ref().unwrap(), "AwesomeXMPP");
332        assert_eq!(auth.user_agent.device.as_ref().unwrap(), "Kiva's Phone");
333
334        assert_eq!(auth.payloads.len(), 1);
335        let bind = auth.payloads.iter().next().unwrap();
336        assert!(bind.is("bind", "urn:xmpp:bind:example"));
337    }
338
339    // XEP-0388 Example 5
340    #[test]
341    fn test_example_5() {
342        let elem: Element = "<challenge xmlns='urn:xmpp:sasl:2'>cj0xMkM0Q0Q1Qy1FMzhFLTRBOTgtOEY2RC0xNUMzOEY1MUNDQzZhMDkxMTdhNi1hYzUwLTRmMmYtOTNmMS05Mzc5OWMyYmRkZjYscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==</challenge>"
343            .parse()
344            .unwrap();
345        let challenge = Challenge::try_from(elem).unwrap();
346        assert_eq!(
347            challenge.sasl_data,
348            b"r=12C4CD5C-E38E-4A98-8F6D-15C38F51CCC6a09117a6-ac50-4f2f-93f1-93799c2bddf6,s=QSXCR+Q6sek8bf92,i=4096"
349        );
350
351        let elem: Element = "<response xmlns='urn:xmpp:sasl:2'>Yz1jRDEwYkhNdFpYaHdiM0owWlhJc0xNY29Rdk9kQkRlUGQ0T3N3bG1BV1YzZGcxYTFXaDF0WVBUQndWaWQxMFZVLHI9MTJDNENENUMtRTM4RS00QTk4LThGNkQtMTVDMzhGNTFDQ0M2YTA5MTE3YTYtYWM1MC00ZjJmLTkzZjEtOTM3OTljMmJkZGY2LHA9VUFwbzd4bzZQYTlKK1ZhZWpmei9kRzdCb21VPQ==</response>"
352            .parse()
353            .unwrap();
354        let response = Response::try_from(elem).unwrap();
355        assert_eq!(
356            response.sasl_data,
357            b"c=cD10bHMtZXhwb3J0ZXIsLMcoQvOdBDePd4OswlmAWV3dg1a1Wh1tYPTBwVid10VU,r=12C4CD5C-E38E-4A98-8F6D-15C38F51CCC6a09117a6-ac50-4f2f-93f1-93799c2bddf6,p=UApo7xo6Pa9J+Vaejfz/dG7BomU="
358        );
359    }
360
361    // XEP-0388 Example 7 and 8
362    #[test]
363    fn test_example_7_8() {
364        let elem: Element = r#"<success xmlns='urn:xmpp:sasl:2'>
365              <additional-data>dj1tc1ZIcy9CeklPSERxWGVWSDdFbW1EdTlpZDg9</additional-data>
366              <authorization-identifier>user@example.org</authorization-identifier>
367           </success>"#
368            .parse()
369            .unwrap();
370
371        let success = Success::try_from(elem).unwrap();
372
373        assert_eq!(
374            success.additional_data.unwrap(),
375            BASE64_STANDARD
376                .decode("dj1tc1ZIcy9CeklPSERxWGVWSDdFbW1EdTlpZDg9")
377                .unwrap()
378        );
379
380        assert_eq!(
381            success.authorization_identifier,
382            Jid::new("user@example.org").unwrap()
383        );
384
385        let elem: Element = r#"<success xmlns='urn:xmpp:sasl:2'>
386              <additional-data>ip/AeIOfZXKBV+fW2smE0GUB3I//nnrrLCYkt0Vj</additional-data>
387              <authorization-identifier>juliet@montague.example/Balcony/a987dsh9a87sdh</authorization-identifier>
388           </success>"#
389            .parse()
390            .unwrap();
391
392        let success = Success::try_from(elem).unwrap();
393
394        assert_eq!(
395            success.additional_data.unwrap(),
396            BASE64_STANDARD
397                .decode("ip/AeIOfZXKBV+fW2smE0GUB3I//nnrrLCYkt0Vj")
398                .unwrap()
399        );
400
401        assert_eq!(
402            success.authorization_identifier,
403            Jid::new("juliet@montague.example/Balcony/a987dsh9a87sdh").unwrap()
404        );
405    }
406
407    // XEP-0388 Example 9
408    #[test]
409    fn example_success_stream_management() {
410        let elem: Element = r#"<success xmlns='urn:xmpp:sasl:2'>
411              <additional-data>SGFkIHlvdSBnb2luZywgdGhlcmUsIGRpZG4ndCBJPw==</additional-data>
412              <authorization-identifier>juliet@montague.example</authorization-identifier>
413              <resumed xmlns='urn:xmpp:sm:3' h='345' previd='124'/>
414           </success>"#
415            .parse()
416            .unwrap();
417
418        let success = Success::try_from(elem).unwrap();
419
420        assert_eq!(
421            success.additional_data.unwrap(),
422            BASE64_STANDARD
423                .decode("SGFkIHlvdSBnb2luZywgdGhlcmUsIGRpZG4ndCBJPw==")
424                .unwrap()
425        );
426
427        assert_eq!(
428            success.authorization_identifier,
429            Jid::new("juliet@montague.example").unwrap()
430        );
431
432        assert_eq!(success.payloads.len(), 1);
433        let resumed =
434            crate::sm::Resumed::try_from(success.payloads.into_iter().next().unwrap()).unwrap();
435        assert_eq!(resumed.h, 345);
436        assert_eq!(resumed.previd, crate::sm::StreamId(String::from("124")));
437    }
438
439    // XEP-0388 Example 10
440    #[test]
441    fn example_failure() {
442        let elem: Element = r#"<failure xmlns='urn:xmpp:sasl:2'>
443  <aborted xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
444  <optional-application-specific xmlns='urn:something:else'/>
445  <text>This is a terrible example.</text>
446</failure>"#
447            .parse()
448            .unwrap();
449
450        let failure = Failure::try_from(elem).unwrap();
451
452        assert_eq!(failure.text.unwrap(), "This is a terrible example.");
453
454        assert_eq!(failure.payloads.len(), 2);
455
456        let mut payloads = failure.payloads.into_iter();
457
458        let condition = crate::sasl::DefinedCondition::try_from(payloads.next().unwrap()).unwrap();
459        assert_eq!(condition, crate::sasl::DefinedCondition::Aborted);
460
461        assert!(payloads
462            .next()
463            .unwrap()
464            .is("optional-application-specific", "urn:something:else"));
465    }
466
467    #[test]
468    fn example_failure_no_text() {
469        let elem: Element = r#"<failure xmlns='urn:xmpp:sasl:2'><aborted xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/></failure>"#
470            .parse()
471            .unwrap();
472
473        let failure = Failure::try_from(elem).unwrap();
474
475        assert_eq!(failure.text, None);
476
477        assert_eq!(failure.payloads.len(), 1);
478
479        let mut payloads = failure.payloads.into_iter();
480
481        let condition = crate::sasl::DefinedCondition::try_from(payloads.next().unwrap()).unwrap();
482        assert_eq!(condition, crate::sasl::DefinedCondition::Aborted);
483    }
484
485    // XEP-0388 Example 11
486    #[test]
487    fn example_11() {
488        let elem: Element = r#"<continue xmlns='urn:xmpp:sasl:2'>
489  <additional-data>SSdtIGJvcmVkIG5vdy4=</additional-data>
490  <tasks>
491    <task>HOTP-EXAMPLE</task>
492    <task>TOTP-EXAMPLE</task>
493  </tasks>
494  <text>This account requires 2FA</text>
495</continue>"#
496            .parse()
497            .unwrap();
498
499        let cont = Continue::try_from(elem).unwrap();
500
501        assert_eq!(
502            cont.additional_data,
503            BASE64_STANDARD.decode("SSdtIGJvcmVkIG5vdy4=").unwrap()
504        );
505
506        assert_eq!(cont.text.as_deref(), Some("This account requires 2FA"));
507
508        assert_eq!(cont.tasks.len(), 2);
509        let mut tasks = cont.tasks.into_iter();
510
511        assert_eq!(tasks.next().unwrap(), "HOTP-EXAMPLE");
512
513        assert_eq!(tasks.next().unwrap(), "TOTP-EXAMPLE");
514    }
515
516    // XEP-0388 Example 12
517    #[test]
518    fn test_fictional_totp() {
519        let elem: Element = r#"<next xmlns='urn:xmpp:sasl:2' task='TOTP-EXAMPLE'>
520  <totp xmlns="urn:totp:example">SSd2ZSBydW4gb3V0IG9mIGlkZWFzIGhlcmUu</totp>
521</next>"#
522            .parse()
523            .unwrap();
524
525        let next = Next::try_from(elem).unwrap();
526        assert_eq!(next.task, "TOTP-EXAMPLE");
527
528        let payload = next.payloads.into_iter().next().unwrap();
529        assert!(payload.is("totp", "urn:totp:example"));
530        assert_eq!(&payload.text(), "SSd2ZSBydW4gb3V0IG9mIGlkZWFzIGhlcmUu");
531
532        let elem: Element = r#"<task-data xmlns='urn:xmpp:sasl:2'>
533  <totp xmlns="urn:totp:example">94d27acffa2e99a42ba7786162a9e73e7ab17b9d</totp>
534</task-data>"#
535            .parse()
536            .unwrap();
537
538        let task_data = TaskData::try_from(elem).unwrap();
539        let payload = task_data.payloads.into_iter().next().unwrap();
540        assert!(payload.is("totp", "urn:totp:example"));
541        assert_eq!(&payload.text(), "94d27acffa2e99a42ba7786162a9e73e7ab17b9d");
542
543        let elem: Element = r#"<task-data xmlns='urn:xmpp:sasl:2'>
544  <totp xmlns="urn:totp:example">OTRkMjdhY2ZmYTJlOTlhNDJiYTc3ODYxNjJhOWU3M2U3YWIxN2I5ZAo=</totp>
545</task-data>"#
546            .parse()
547            .unwrap();
548
549        let task_data = TaskData::try_from(elem).unwrap();
550        let payload = task_data.payloads.into_iter().next().unwrap();
551        assert!(payload.is("totp", "urn:totp:example"));
552        assert_eq!(
553            &payload.text(),
554            "OTRkMjdhY2ZmYTJlOTlhNDJiYTc3ODYxNjJhOWU3M2U3YWIxN2I5ZAo="
555        );
556
557        let elem: Element = r#"<success xmlns='urn:xmpp:sasl:2'>
558  <totp xmlns="urn:totp:example">SGFkIHlvdSBnb2luZywgdGhlcmUsIGRpZG4ndCBJPw==</totp>
559  <authorization-identifier>juliet@montague.example</authorization-identifier>
560</success>"#
561            .parse()
562            .unwrap();
563
564        let success = Success::try_from(elem).unwrap();
565        assert_eq!(success.additional_data, None);
566
567        let payload = success.payloads.into_iter().next().unwrap();
568        assert!(payload.is("totp", "urn:totp:example"));
569        assert_eq!(
570            &payload.text(),
571            "SGFkIHlvdSBnb2luZywgdGhlcmUsIGRpZG4ndCBJPw=="
572        );
573
574        assert_eq!(
575            success.authorization_identifier,
576            Jid::new("juliet@montague.example").unwrap(),
577        )
578    }
579
580    /// XEP-0388 Example 13
581    #[test]
582    fn example_13() {
583        let elem: Element = r#"<authenticate xmlns='urn:xmpp:sasl:2' mechanism='PLAIN'>
584  <initial-response>AGFsaWNlQGV4YW1wbGUub3JnCjM0NQ==</initial-response>
585  <user-agent id='d4565fa7-4d72-4749-b3d3-740edbf87770'>
586    <software>AwesomeXMPP</software>
587    <device>Kiva's Phone</device>
588  </user-agent>
589</authenticate>"#
590            .parse()
591            .unwrap();
592
593        let auth = Authenticate::try_from(elem).unwrap();
594
595        assert_eq!(auth.mechanism, "PLAIN");
596        assert_eq!(
597            auth.initial_response.unwrap(),
598            BASE64_STANDARD
599                .decode("AGFsaWNlQGV4YW1wbGUub3JnCjM0NQ==")
600                .unwrap()
601        );
602
603        assert_eq!(auth.payloads.len(), 0);
604
605        let user_agent = auth.user_agent;
606        assert_eq!(
607            user_agent.id,
608            "d4565fa7-4d72-4749-b3d3-740edbf87770"
609                .parse::<Uuid>()
610                .unwrap()
611        );
612        assert_eq!(user_agent.software.as_deref(), Some("AwesomeXMPP"));
613        assert_eq!(user_agent.device.as_deref(), Some("Kiva's Phone"));
614
615        let elem: Element = r#"<success xmlns='urn:xmpp:sasl:2'>
616  <authorization-identifier>alice@example.org</authorization-identifier>
617</success>"#
618            .parse()
619            .unwrap();
620
621        let success = Success::try_from(elem).unwrap();
622        assert_eq!(
623            success.authorization_identifier,
624            Jid::new("alice@example.org").unwrap()
625        );
626        assert_eq!(success.additional_data, None);
627        assert_eq!(success.payloads.len(), 0);
628    }
629
630    // XEP-0388 Example 14
631    #[test]
632    fn example_14() {
633        let elem: Element = r#"<authenticate xmlns='urn:xmpp:sasl:2' mechanism='CRAM-MD5'>
634  <user-agent id='d4565fa7-4d72-4749-b3d3-740edbf87770'>
635    <software>AwesomeXMPP</software>
636    <device>Kiva's Phone</device>
637  </user-agent>
638</authenticate>"#
639            .parse()
640            .unwrap();
641
642        let auth = Authenticate::try_from(elem).unwrap();
643
644        assert_eq!(auth.mechanism, "CRAM-MD5");
645        assert_eq!(auth.initial_response, None);
646        assert_eq!(auth.payloads.len(), 0);
647
648        let user_agent = auth.user_agent;
649        assert_eq!(
650            user_agent.id,
651            "d4565fa7-4d72-4749-b3d3-740edbf87770"
652                .parse::<Uuid>()
653                .unwrap()
654        );
655        assert_eq!(user_agent.software.as_deref(), Some("AwesomeXMPP"));
656        assert_eq!(user_agent.device.as_deref(), Some("Kiva's Phone"));
657
658        let elem: Element = r#"<challenge xmlns='urn:xmpp:sasl:2'>PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+</challenge>"#
659        .parse()
660        .unwrap();
661
662        let challenge = Challenge::try_from(elem).unwrap();
663        assert_eq!(
664            challenge.sasl_data,
665            BASE64_STANDARD
666                .decode("PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+")
667                .unwrap()
668        );
669
670        let elem: Element = r#"<response xmlns='urn:xmpp:sasl:2'>dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw</response>"#
671        .parse()
672        .unwrap();
673
674        let response = Response::try_from(elem).unwrap();
675        assert_eq!(
676            response.sasl_data,
677            BASE64_STANDARD
678                .decode("dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw")
679                .unwrap()
680        );
681
682        let elem: Element = r#"<success xmlns='urn:xmpp:sasl:2'>
683  <authorization-identifier>tim@example.org</authorization-identifier>
684</success>
685        "#
686        .parse()
687        .unwrap();
688
689        let success = Success::try_from(elem).unwrap();
690        assert_eq!(
691            success.authorization_identifier,
692            Jid::new("tim@example.org").unwrap()
693        );
694        assert_eq!(success.additional_data, None);
695        assert_eq!(success.payloads.len(), 0);
696    }
697
698    // XEP-0388 Example 15
699    #[test]
700    fn example_15() {
701        let elem: Element = r#"<authenticate xmlns='urn:xmpp:sasl:2' mechanism='BLURDYBLOOP'>
702  <initial-response>SW5pdGlhbCBSZXNwb25zZQ==</initial-response>
703  <user-agent id='d4565fa7-4d72-4749-b3d3-740edbf87770'>
704    <software>AwesomeXMPP</software>
705    <device>Kiva's Phone</device>
706  </user-agent>
707  <megabind xmlns='urn:example:megabind'>
708    <resource>this-one-please</resource>
709  </megabind>
710</authenticate>"#
711            .parse()
712            .unwrap();
713
714        let auth = Authenticate::try_from(elem).unwrap();
715        assert_eq!(auth.mechanism, "BLURDYBLOOP");
716        assert_eq!(
717            auth.initial_response,
718            Some(BASE64_STANDARD.decode("SW5pdGlhbCBSZXNwb25zZQ==").unwrap())
719        );
720
721        assert_eq!(
722            auth.user_agent.id,
723            "d4565fa7-4d72-4749-b3d3-740edbf87770"
724                .parse::<Uuid>()
725                .unwrap()
726        );
727        assert_eq!(auth.user_agent.software.as_deref(), Some("AwesomeXMPP"));
728        assert_eq!(auth.user_agent.device.as_deref(), Some("Kiva's Phone"));
729
730        assert_eq!(auth.payloads.len(), 1);
731        let bind = auth.payloads.into_iter().next().unwrap();
732        assert!(bind.is("megabind", "urn:example:megabind"));
733
734        let mut bind_payloads = bind.children();
735        let resource = bind_payloads.next().unwrap();
736        assert_eq!(resource.name(), "resource");
737        assert_eq!(&resource.text(), "this-one-please");
738        assert_eq!(bind_payloads.next(), None);
739
740        let elem: Element = r#"<challenge xmlns='urn:xmpp:sasl:2'>PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+</challenge>"#
741        .parse()
742        .unwrap();
743        let challenge = Challenge::try_from(elem).unwrap();
744        assert_eq!(
745            challenge.sasl_data,
746            BASE64_STANDARD
747                .decode("PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+")
748                .unwrap()
749        );
750
751        let elem: Element = r#"<response xmlns='urn:xmpp:sasl:2'>dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw</response>"#
752        .parse()
753        .unwrap();
754        let response = Response::try_from(elem).unwrap();
755        assert_eq!(response.sasl_data, b"tim b913a602c7eda7a495b4e6e7334d3890");
756
757        let elem: Element = r#"<continue xmlns='urn:xmpp:sasl:2'>
758  <additional-data>QWRkaXRpb25hbCBEYXRh</additional-data>
759  <tasks>
760    <task>UNREALISTIC-2FA</task>
761  </tasks>
762</continue>"#
763            .parse()
764            .unwrap();
765        let cont = Continue::try_from(elem).unwrap();
766        assert_eq!(
767            cont.additional_data,
768            BASE64_STANDARD.decode("QWRkaXRpb25hbCBEYXRh").unwrap()
769        );
770        assert_eq!(cont.tasks.len(), 1);
771        assert_eq!(cont.tasks.into_iter().next().unwrap(), "UNREALISTIC-2FA");
772
773        let elem: Element = r#"<next xmlns='urn:xmpp:sasl:2' task='UNREALISTIC-2FA'>
774  <parameters xmlns='urn:example:unrealistic2fa'>VW5yZWFsaXN0aWMgMkZBIElS</parameters>
775</next>"#
776            .parse()
777            .unwrap();
778        let next = Next::try_from(elem).unwrap();
779        assert_eq!(next.payloads.len(), 1);
780        let params = next.payloads.into_iter().next().unwrap();
781        assert!(params.is("parameters", "urn:example:unrealistic2fa"));
782        assert_eq!(&params.text(), "VW5yZWFsaXN0aWMgMkZBIElS");
783
784        let elem: Element = r#"<task-data xmlns='urn:xmpp:sasl:2'>
785  <question xmlns='urn:example:unrealistic2fa'>PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+</question>
786</task-data>"#
787        .parse()
788        .unwrap();
789        let task_data = TaskData::try_from(elem).unwrap();
790        assert_eq!(task_data.payloads.len(), 1);
791        let question = task_data.payloads.into_iter().next().unwrap();
792        assert!(question.is("question", "urn:example:unrealistic2fa"));
793        assert_eq!(
794            &question.text(),
795            "PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+"
796        );
797
798        let elem: Element = r#"<task-data xmlns='urn:xmpp:sasl:2'>
799  <response xmlns='urn:example:unrealistic2fa'>dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw</response>
800</task-data>"#
801        .parse()
802        .unwrap();
803        let task_data = TaskData::try_from(elem).unwrap();
804        assert_eq!(task_data.payloads.len(), 1);
805        let response = task_data.payloads.into_iter().next().unwrap();
806        assert!(response.is("response", "urn:example:unrealistic2fa"));
807        assert_eq!(
808            &response.text(),
809            "dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw"
810        );
811
812        let elem: Element = r#"<success xmlns='urn:xmpp:sasl:2'>
813  <result xmlns='urn:example:unrealistic2fa'>VW5yZWFsaXN0aWMgMkZBIG11dHVhbCBhdXRoIGRhdGE=</result>
814  <authorization-identifier>alice@example.org/this-one-please</authorization-identifier>
815</success>"#
816            .parse()
817            .unwrap();
818        let success = Success::try_from(elem).unwrap();
819        assert_eq!(
820            success.authorization_identifier,
821            Jid::new("alice@example.org/this-one-please").unwrap()
822        );
823
824        assert_eq!(success.payloads.len(), 1);
825        let res = success.payloads.into_iter().next().unwrap();
826        assert!(res.is("result", "urn:example:unrealistic2fa"));
827        assert_eq!(&res.text(), "VW5yZWFsaXN0aWMgMkZBIG11dHVhbCBhdXRoIGRhdGE=");
828    }
829}