user.rs

  1// Copyright (c) 2017 Maxime “pep” Buquet <pep@bouah.net>
  2// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  3//
  4// This Source Code Form is subject to the terms of the Mozilla Public
  5// License, v. 2.0. If a copy of the MPL was not distributed with this
  6// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  7
  8use crate::ns;
  9use crate::presence::PresencePayload;
 10use crate::util::error::Error;
 11use crate::Element;
 12
 13use jid::FullJid;
 14use std::convert::TryFrom;
 15
 16generate_attribute_enum!(
 17/// Lists all of the possible status codes used in MUC presences.
 18Status, "status", MUC_USER, "code", {
 19    /// 100: Inform user that any occupant is allowed to see the user's full JID
 20    NonAnonymousRoom => 100,
 21
 22    /// 101: Inform user that his or her affiliation changed while not in the room
 23    AffiliationChange => 101,
 24
 25    /// 102: Inform occupants that room now shows unavailable members
 26    ConfigShowsUnavailableMembers => 102,
 27
 28    /// 103: Inform occupants that room now does not show unavailable members
 29    ConfigHidesUnavailableMembers => 103,
 30
 31    /// 104: Inform occupants that a non-privacy-related room configuration change has occurred
 32    ConfigNonPrivacyRelated => 104,
 33
 34    /// 110: Inform user that presence refers to itself
 35    SelfPresence => 110,
 36
 37    /// 170: Inform occupants that room logging is now enabled
 38    ConfigRoomLoggingEnabled => 170,
 39
 40    /// 171: Inform occupants that room logging is now disabled
 41    ConfigRoomLoggingDisabled => 171,
 42
 43    /// 172: Inform occupants that the room is now non-anonymous
 44    ConfigRoomNonAnonymous => 172,
 45
 46    /// 173: Inform occupants that the room is now semi-anonymous
 47    ConfigRoomSemiAnonymous => 173,
 48
 49    /// 201: Inform user that a new room has been created
 50    RoomHasBeenCreated => 201,
 51
 52    /// 210: Inform user that service has assigned or modified occupant's roomnick
 53    AssignedNick => 210,
 54
 55    /// 301: Inform user that they have been banned from the room
 56    Banned => 301,
 57
 58    /// 303: Inform all occupants of new room nickname
 59    NewNick => 303,
 60
 61    /// 307: Inform user that they have been kicked from the room
 62    Kicked => 307,
 63
 64    /// 321: Inform user that they are being removed from the room
 65    /// because of an affiliation change
 66    RemovalFromRoom => 321,
 67
 68    /// 322: Inform user that they are being removed from the room
 69    /// because the room has been changed to members-only and the
 70    /// user is not a member
 71    ConfigMembersOnly => 322,
 72
 73    /// 332: Inform user that they are being removed from the room
 74    /// because the MUC service is being shut down
 75    ServiceShutdown => 332,
 76
 77    /// 333: Inform user that they are being removed from the room for technical reasons
 78    ServiceErrorKick => 333,
 79});
 80
 81/// Optional \<actor/\> element used in \<item/\> elements inside presence stanzas of type
 82/// "unavailable" that are sent to users who are kick or banned, as well as within IQs for tracking
 83/// purposes. -- CHANGELOG  0.17 (2002-10-23)
 84///
 85/// Possesses a 'jid' and a 'nick' attribute, so that an action can be attributed either to a real
 86/// JID or to a roomnick. -- CHANGELOG  1.25 (2012-02-08)
 87#[derive(Debug, Clone, PartialEq)]
 88pub enum Actor {
 89    /// The full JID associated with this user.
 90    Jid(FullJid),
 91
 92    /// The nickname of this user.
 93    Nick(String),
 94}
 95
 96impl TryFrom<Element> for Actor {
 97    type Error = Error;
 98
 99    fn try_from(elem: Element) -> Result<Actor, Error> {
100        check_self!(elem, "actor", MUC_USER);
101        check_no_unknown_attributes!(elem, "actor", ["jid", "nick"]);
102        check_no_children!(elem, "actor");
103        let jid: Option<FullJid> = get_attr!(elem, "jid", Option);
104        let nick = get_attr!(elem, "nick", Option);
105
106        match (jid, nick) {
107            (Some(_), Some(_)) | (None, None) => Err(Error::ParseError(
108                "Either 'jid' or 'nick' attribute is required.",
109            )),
110            (Some(jid), _) => Ok(Actor::Jid(jid)),
111            (_, Some(nick)) => Ok(Actor::Nick(nick)),
112        }
113    }
114}
115
116impl From<Actor> for Element {
117    fn from(actor: Actor) -> Element {
118        let elem = Element::builder("actor", ns::MUC_USER);
119
120        (match actor {
121            Actor::Jid(jid) => elem.attr("jid", jid),
122            Actor::Nick(nick) => elem.attr("nick", nick),
123        })
124        .build()
125    }
126}
127
128generate_element!(
129    /// Used to continue a one-to-one discussion in a room, with more than one
130    /// participant.
131    Continue, "continue", MUC_USER,
132    attributes: [
133        /// The thread to continue in this room.
134        thread: Option<String> = "thread",
135    ]
136);
137
138generate_elem_id!(
139    /// A reason for inviting, declining, etc. a request.
140    Reason,
141    "reason",
142    MUC_USER
143);
144
145generate_attribute!(
146    /// The affiliation of an entity with a room, which isn’t tied to its
147    /// presence in it.
148    Affiliation, "affiliation", {
149        /// The user who created the room, or who got appointed by its creator
150        /// to be their equal.
151        Owner => "owner",
152
153        /// A user who has been empowered by an owner to do administrative
154        /// operations.
155        Admin => "admin",
156
157        /// A user who is whitelisted to speak in moderated rooms, or to join a
158        /// member-only room.
159        Member => "member",
160
161        /// A user who has been banned from this room.
162        Outcast => "outcast",
163
164        /// A normal participant.
165        None => "none",
166    }, Default = None
167);
168
169generate_attribute!(
170    /// The current role of an entity in a room, it can be changed by an owner
171    /// or an administrator but will be lost once they leave the room.
172    Role, "role", {
173        /// This user can kick other participants, as well as grant and revoke
174        /// them voice.
175        Moderator => "moderator",
176
177        /// A user who can speak in this room.
178        Participant => "participant",
179
180        /// A user who cannot speak in this room, and must request voice before
181        /// doing so.
182        Visitor => "visitor",
183
184        /// A user who is absent from the room.
185        None => "none",
186    }, Default = None
187);
188
189generate_element!(
190    /// An item representing a user in a room.
191    Item, "item", MUC_USER, attributes: [
192        /// The affiliation of this user with the room.
193        affiliation: Required<Affiliation> = "affiliation",
194
195        /// The real JID of this user, if you are allowed to see it.
196        jid: Option<FullJid> = "jid",
197
198        /// The current nickname of this user.
199        nick: Option<String> = "nick",
200
201        /// The current role of this user.
202        role: Required<Role> = "role",
203    ], children: [
204        /// The actor affected by this item.
205        actor: Option<Actor> = ("actor", MUC_USER) => Actor,
206
207        /// Whether this continues a one-to-one discussion.
208        continue_: Option<Continue> = ("continue", MUC_USER) => Continue,
209
210        /// A reason for this item.
211        reason: Option<Reason> = ("reason", MUC_USER) => Reason
212    ]
213);
214
215impl Item {
216    /// Creates a new item with the given affiliation and role.
217    pub fn new(affiliation: Affiliation, role: Role) -> Item {
218        Item {
219            affiliation,
220            role,
221            jid: None,
222            nick: None,
223            actor: None,
224            continue_: None,
225            reason: None,
226        }
227    }
228
229    /// Set a jid for this Item
230    pub fn with_jid(mut self, jid: FullJid) -> Item {
231        self.jid = Some(jid);
232        self
233    }
234
235    /// Set a nick for this Item
236    pub fn with_nick<S: Into<String>>(mut self, nick: S) -> Item {
237        self.nick = Some(nick.into());
238        self
239    }
240
241    /// Set an actor for this Item
242    pub fn with_actor(mut self, actor: Actor) -> Item {
243        self.actor = Some(actor);
244        self
245    }
246
247    /// Set a continue value for this Item
248    pub fn with_continue<S: Into<String>>(mut self, continue_: S) -> Item {
249        self.continue_ = Some(Continue {
250            thread: Some(continue_.into()),
251        });
252        self
253    }
254
255    /// Set a reason for this Item
256    pub fn with_reason<S: Into<String>>(mut self, reason: S) -> Item {
257        self.reason = Some(Reason(reason.into()));
258        self
259    }
260}
261
262generate_element!(
263    /// The main muc#user element.
264    MucUser, "x", MUC_USER, children: [
265        /// List of statuses applying to this item.
266        status: Vec<Status> = ("status", MUC_USER) => Status,
267
268        /// List of items.
269        items: Vec<Item> = ("item", MUC_USER) => Item
270    ]
271);
272
273impl PresencePayload for MucUser {}
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278    use crate::presence::{Presence, Type as PresenceType};
279
280    #[test]
281    fn test_simple() {
282        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
283            .parse()
284            .unwrap();
285        MucUser::try_from(elem).unwrap();
286    }
287
288    #[test]
289    fn statuses_and_items() {
290        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'>
291                <status code='101'/>
292                <status code='102'/>
293                <item affiliation='member' role='moderator'/>
294            </x>"
295            .parse()
296            .unwrap();
297        let muc_user = MucUser::try_from(elem).unwrap();
298        assert_eq!(muc_user.status.len(), 2);
299        assert_eq!(muc_user.status[0], Status::AffiliationChange);
300        assert_eq!(muc_user.status[1], Status::ConfigShowsUnavailableMembers);
301        assert_eq!(muc_user.items.len(), 1);
302        assert_eq!(muc_user.items[0].affiliation, Affiliation::Member);
303        assert_eq!(muc_user.items[0].role, Role::Moderator);
304    }
305
306    #[test]
307    fn test_invalid_child() {
308        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'>
309                <coucou/>
310            </x>"
311            .parse()
312            .unwrap();
313        let error = MucUser::try_from(elem).unwrap_err();
314        let message = match error {
315            Error::ParseError(string) => string,
316            _ => panic!(),
317        };
318        assert_eq!(message, "Unknown child in x element.");
319    }
320
321    #[test]
322    fn test_serialise() {
323        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
324            .parse()
325            .unwrap();
326        let muc = MucUser {
327            status: vec![],
328            items: vec![],
329        };
330        let elem2 = muc.into();
331        assert_eq!(elem, elem2);
332    }
333
334    #[cfg(not(feature = "disable-validation"))]
335    #[test]
336    fn test_invalid_attribute() {
337        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>"
338            .parse()
339            .unwrap();
340        let error = MucUser::try_from(elem).unwrap_err();
341        let message = match error {
342            Error::ParseError(string) => string,
343            _ => panic!(),
344        };
345        assert_eq!(message, "Unknown attribute in x element.");
346    }
347
348    #[test]
349    fn test_status_simple() {
350        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'/>"
351            .parse()
352            .unwrap();
353        Status::try_from(elem).unwrap();
354    }
355
356    #[test]
357    fn test_status_invalid() {
358        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user'/>"
359            .parse()
360            .unwrap();
361        let error = Status::try_from(elem).unwrap_err();
362        let message = match error {
363            Error::ParseError(string) => string,
364            _ => panic!(),
365        };
366        assert_eq!(message, "Required attribute 'code' missing.");
367    }
368
369    #[cfg(not(feature = "disable-validation"))]
370    #[test]
371    fn test_status_invalid_child() {
372        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'>
373                <foo/>
374            </status>"
375            .parse()
376            .unwrap();
377        let error = Status::try_from(elem).unwrap_err();
378        let message = match error {
379            Error::ParseError(string) => string,
380            _ => panic!(),
381        };
382        assert_eq!(message, "Unknown child in status element.");
383    }
384
385    #[test]
386    fn test_status_simple_code() {
387        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='307'/>"
388            .parse()
389            .unwrap();
390        let status = Status::try_from(elem).unwrap();
391        assert_eq!(status, Status::Kicked);
392    }
393
394    #[test]
395    fn test_status_invalid_code() {
396        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='666'/>"
397            .parse()
398            .unwrap();
399        let error = Status::try_from(elem).unwrap_err();
400        let message = match error {
401            Error::ParseError(string) => string,
402            _ => panic!(),
403        };
404        assert_eq!(message, "Invalid status code value.");
405    }
406
407    #[test]
408    fn test_status_invalid_code2() {
409        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>"
410            .parse()
411            .unwrap();
412        let error = Status::try_from(elem).unwrap_err();
413        let error = match error {
414            Error::ParseIntError(error) => error,
415            _ => panic!(),
416        };
417        assert_eq!(error.to_string(), "invalid digit found in string");
418    }
419
420    #[test]
421    fn test_actor_required_attributes() {
422        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user'/>"
423            .parse()
424            .unwrap();
425        let error = Actor::try_from(elem).unwrap_err();
426        let message = match error {
427            Error::ParseError(string) => string,
428            _ => panic!(),
429        };
430        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
431    }
432
433    #[test]
434    fn test_actor_required_attributes2() {
435        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user'
436                   jid='foo@bar/baz'
437                   nick='baz'/>"
438            .parse()
439            .unwrap();
440        let error = Actor::try_from(elem).unwrap_err();
441        let message = match error {
442            Error::ParseError(string) => string,
443            _ => panic!(),
444        };
445        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
446    }
447
448    #[test]
449    fn test_actor_jid() {
450        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user'
451                   jid='foo@bar/baz'/>"
452            .parse()
453            .unwrap();
454        let actor = Actor::try_from(elem).unwrap();
455        let jid = match actor {
456            Actor::Jid(jid) => jid,
457            _ => panic!(),
458        };
459        assert_eq!(jid, "foo@bar/baz".parse::<FullJid>().unwrap());
460    }
461
462    #[test]
463    fn test_actor_nick() {
464        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>"
465            .parse()
466            .unwrap();
467        let actor = Actor::try_from(elem).unwrap();
468        let nick = match actor {
469            Actor::Nick(nick) => nick,
470            _ => panic!(),
471        };
472        assert_eq!(nick, "baz".to_owned());
473    }
474
475    #[test]
476    fn test_continue_simple() {
477        let elem: Element = "<continue xmlns='http://jabber.org/protocol/muc#user'/>"
478            .parse()
479            .unwrap();
480        Continue::try_from(elem).unwrap();
481    }
482
483    #[test]
484    fn test_continue_thread_attribute() {
485        let elem: Element = "<continue xmlns='http://jabber.org/protocol/muc#user'
486                      thread='foo'/>"
487            .parse()
488            .unwrap();
489        let continue_ = Continue::try_from(elem).unwrap();
490        assert_eq!(continue_.thread, Some("foo".to_owned()));
491    }
492
493    #[test]
494    fn test_continue_invalid() {
495        let elem: Element = "<continue xmlns='http://jabber.org/protocol/muc#user'>
496                <foobar/>
497            </continue>"
498            .parse()
499            .unwrap();
500        let continue_ = Continue::try_from(elem).unwrap_err();
501        let message = match continue_ {
502            Error::ParseError(string) => string,
503            _ => panic!(),
504        };
505        assert_eq!(message, "Unknown child in continue element.".to_owned());
506    }
507
508    #[test]
509    fn test_reason_simple() {
510        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
511            .parse()
512            .unwrap();
513        let elem2 = elem.clone();
514        let reason = Reason::try_from(elem).unwrap();
515        assert_eq!(reason.0, "Reason".to_owned());
516
517        let elem3 = reason.into();
518        assert_eq!(elem2, elem3);
519    }
520
521    #[cfg(not(feature = "disable-validation"))]
522    #[test]
523    fn test_reason_invalid_attribute() {
524        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>"
525            .parse()
526            .unwrap();
527        let error = Reason::try_from(elem).unwrap_err();
528        let message = match error {
529            Error::ParseError(string) => string,
530            _ => panic!(),
531        };
532        assert_eq!(message, "Unknown attribute in reason element.".to_owned());
533    }
534
535    #[cfg(not(feature = "disable-validation"))]
536    #[test]
537    fn test_reason_invalid() {
538        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>
539                <foobar/>
540            </reason>"
541            .parse()
542            .unwrap();
543        let error = Reason::try_from(elem).unwrap_err();
544        let message = match error {
545            Error::ParseError(string) => string,
546            _ => panic!(),
547        };
548        assert_eq!(message, "Unknown child in reason element.".to_owned());
549    }
550
551    #[cfg(not(feature = "disable-validation"))]
552    #[test]
553    fn test_item_invalid_attr() {
554        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
555                  foo='bar'/>"
556            .parse()
557            .unwrap();
558        let error = Item::try_from(elem).unwrap_err();
559        let message = match error {
560            Error::ParseError(string) => string,
561            _ => panic!(),
562        };
563        assert_eq!(message, "Unknown attribute in item element.".to_owned());
564    }
565
566    #[test]
567    fn test_item_affiliation_role_attr() {
568        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
569                  affiliation='member'
570                  role='moderator'/>"
571            .parse()
572            .unwrap();
573        Item::try_from(elem).unwrap();
574    }
575
576    #[test]
577    fn test_item_affiliation_role_invalid_attr() {
578        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
579                  affiliation='member'/>"
580            .parse()
581            .unwrap();
582        let error = Item::try_from(elem).unwrap_err();
583        let message = match error {
584            Error::ParseError(string) => string,
585            _ => panic!(),
586        };
587        assert_eq!(message, "Required attribute 'role' missing.".to_owned());
588    }
589
590    #[test]
591    fn test_item_nick_attr() {
592        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
593                  affiliation='member'
594                  role='moderator'
595                  nick='foobar'/>"
596            .parse()
597            .unwrap();
598        let item = Item::try_from(elem).unwrap();
599        match item {
600            Item { nick, .. } => assert_eq!(nick, Some("foobar".to_owned())),
601        }
602    }
603
604    #[test]
605    fn test_item_affiliation_role_invalid_attr2() {
606        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
607                  role='moderator'/>"
608            .parse()
609            .unwrap();
610        let error = Item::try_from(elem).unwrap_err();
611        let message = match error {
612            Error::ParseError(string) => string,
613            _ => panic!(),
614        };
615        assert_eq!(
616            message,
617            "Required attribute 'affiliation' missing.".to_owned()
618        );
619    }
620
621    #[test]
622    fn test_item_role_actor_child() {
623        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
624                  affiliation='member'
625                  role='moderator'>
626                <actor nick='foobar'/>
627            </item>"
628            .parse()
629            .unwrap();
630        let item = Item::try_from(elem).unwrap();
631        match item {
632            Item { actor, .. } => assert_eq!(actor, Some(Actor::Nick("foobar".to_owned()))),
633        }
634    }
635
636    #[test]
637    fn test_item_role_continue_child() {
638        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
639                  affiliation='member'
640                  role='moderator'>
641                <continue thread='foobar'/>
642            </item>"
643            .parse()
644            .unwrap();
645        let item = Item::try_from(elem).unwrap();
646        let continue_1 = Continue {
647            thread: Some("foobar".to_owned()),
648        };
649        match item {
650            Item {
651                continue_: Some(continue_2),
652                ..
653            } => assert_eq!(continue_2.thread, continue_1.thread),
654            _ => panic!(),
655        }
656    }
657
658    #[test]
659    fn test_item_role_reason_child() {
660        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
661                  affiliation='member'
662                  role='moderator'>
663                <reason>foobar</reason>
664            </item>"
665            .parse()
666            .unwrap();
667        let item = Item::try_from(elem).unwrap();
668        match item {
669            Item { reason, .. } => assert_eq!(reason, Some(Reason("foobar".to_owned()))),
670        }
671    }
672
673    #[test]
674    fn test_serialize_item() {
675        let reference: Element = "<item xmlns='http://jabber.org/protocol/muc#user' affiliation='member' role='moderator'><actor nick='foobar'/><continue thread='foobar'/><reason>foobar</reason></item>"
676        .parse()
677        .unwrap();
678
679        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user' nick='foobar'/>"
680            .parse()
681            .unwrap();
682        let actor = Actor::try_from(elem).unwrap();
683
684        let elem: Element =
685            "<continue xmlns='http://jabber.org/protocol/muc#user' thread='foobar'/>"
686                .parse()
687                .unwrap();
688        let continue_ = Continue::try_from(elem).unwrap();
689
690        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>foobar</reason>"
691            .parse()
692            .unwrap();
693        let reason = Reason::try_from(elem).unwrap();
694
695        let item = Item {
696            affiliation: Affiliation::Member,
697            role: Role::Moderator,
698            jid: None,
699            nick: None,
700            actor: Some(actor),
701            reason: Some(reason),
702            continue_: Some(continue_),
703        };
704
705        let serialized: Element = item.into();
706        assert_eq!(serialized, reference);
707    }
708
709    #[test]
710    fn presence_payload() {
711        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
712            .parse()
713            .unwrap();
714        let presence = Presence::new(PresenceType::None).with_payloads(vec![elem]);
715        assert_eq!(presence.payloads.len(), 1);
716    }
717}