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