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::util::error::Error;
 10use crate::Element;
 11use jid::FullJid;
 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) => Err(Error::ParseError(
103                "Either 'jid' or 'nick' attribute is required.",
104            )),
105            (Some(jid), _) => Ok(Actor::Jid(jid)),
106            (_, Some(nick)) => Ok(Actor::Nick(nick)),
107        }
108    }
109}
110
111impl From<Actor> for Element {
112    fn from(actor: Actor) -> Element {
113        let elem = Element::builder("actor", ns::MUC_USER);
114
115        (match actor {
116            Actor::Jid(jid) => elem.attr("jid", jid),
117            Actor::Nick(nick) => elem.attr("nick", nick),
118        })
119        .build()
120    }
121}
122
123generate_element!(
124    /// Used to continue a one-to-one discussion in a room, with more than one
125    /// participant.
126    Continue, "continue", MUC_USER,
127    attributes: [
128        /// The thread to continue in this room.
129        thread: Option<String> = "thread",
130    ]
131);
132
133generate_elem_id!(
134    /// A reason for inviting, declining, etc. a request.
135    Reason,
136    "reason",
137    MUC_USER
138);
139
140generate_attribute!(
141    /// The affiliation of an entity with a room, which isn’t tied to its
142    /// presence in it.
143    Affiliation, "affiliation", {
144        /// The user who created the room, or who got appointed by its creator
145        /// to be their equal.
146        Owner => "owner",
147
148        /// A user who has been empowered by an owner to do administrative
149        /// operations.
150        Admin => "admin",
151
152        /// A user who is whitelisted to speak in moderated rooms, or to join a
153        /// member-only room.
154        Member => "member",
155
156        /// A user who has been banned from this room.
157        Outcast => "outcast",
158
159        /// A normal participant.
160        None => "none",
161    }, Default = None
162);
163
164generate_attribute!(
165    /// The current role of an entity in a room, it can be changed by an owner
166    /// or an administrator but will be lost once they leave the room.
167    Role, "role", {
168        /// This user can kick other participants, as well as grant and revoke
169        /// them voice.
170        Moderator => "moderator",
171
172        /// A user who can speak in this room.
173        Participant => "participant",
174
175        /// A user who cannot speak in this room, and must request voice before
176        /// doing so.
177        Visitor => "visitor",
178
179        /// A user who is absent from the room.
180        None => "none",
181    }, Default = None
182);
183
184generate_element!(
185    /// An item representing a user in a room.
186    Item, "item", MUC_USER, attributes: [
187        /// The affiliation of this user with the room.
188        affiliation: Required<Affiliation> = "affiliation",
189
190        /// The real JID of this user, if you are allowed to see it.
191        jid: Option<FullJid> = "jid",
192
193        /// The current nickname of this user.
194        nick: Option<String> = "nick",
195
196        /// The current role of this user.
197        role: Required<Role> = "role",
198    ], children: [
199        /// The actor affected by this item.
200        actor: Option<Actor> = ("actor", MUC_USER) => Actor,
201
202        /// Whether this continues a one-to-one discussion.
203        continue_: Option<Continue> = ("continue", MUC_USER) => Continue,
204
205        /// A reason for this item.
206        reason: Option<Reason> = ("reason", MUC_USER) => Reason
207    ]
208);
209
210impl Item {
211    /// Creates a new item with the given affiliation and role.
212    pub fn new(affiliation: Affiliation, role: Role) -> Item {
213        Item {
214            affiliation,
215            role,
216            jid: None,
217            nick: None,
218            actor: None,
219            continue_: None,
220            reason: None,
221        }
222    }
223}
224
225generate_element!(
226    /// The main muc#user element.
227    MucUser, "x", MUC_USER, children: [
228        /// List of statuses applying to this item.
229        status: Vec<Status> = ("status", MUC_USER) => Status,
230
231        /// List of items.
232        items: Vec<Item> = ("item", MUC_USER) => Item
233    ]
234);
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239
240    #[test]
241    fn test_simple() {
242        let elem: Element = "
243            <x xmlns='http://jabber.org/protocol/muc#user'/>
244        "
245        .parse()
246        .unwrap();
247        MucUser::try_from(elem).unwrap();
248    }
249
250    #[test]
251    fn statuses_and_items() {
252        let elem: Element = "
253            <x xmlns='http://jabber.org/protocol/muc#user'>
254                <status code='101'/>
255                <status code='102'/>
256                <item affiliation='member' role='moderator'/>
257            </x>
258        "
259        .parse()
260        .unwrap();
261        let muc_user = MucUser::try_from(elem).unwrap();
262        assert_eq!(muc_user.status.len(), 2);
263        assert_eq!(muc_user.status[0], Status::AffiliationChange);
264        assert_eq!(muc_user.status[1], Status::ConfigShowsUnavailableMembers);
265        assert_eq!(muc_user.items.len(), 1);
266        assert_eq!(muc_user.items[0].affiliation, Affiliation::Member);
267        assert_eq!(muc_user.items[0].role, Role::Moderator);
268    }
269
270    #[test]
271    fn test_invalid_child() {
272        let elem: Element = "
273            <x xmlns='http://jabber.org/protocol/muc#user'>
274                <coucou/>
275            </x>
276        "
277        .parse()
278        .unwrap();
279        let error = MucUser::try_from(elem).unwrap_err();
280        let message = match error {
281            Error::ParseError(string) => string,
282            _ => panic!(),
283        };
284        assert_eq!(message, "Unknown child in x element.");
285    }
286
287    #[test]
288    fn test_serialise() {
289        let elem: Element = "
290            <x xmlns='http://jabber.org/protocol/muc#user'/>
291        "
292        .parse()
293        .unwrap();
294        let muc = MucUser {
295            status: vec![],
296            items: vec![],
297        };
298        let elem2 = muc.into();
299        assert_eq!(elem, elem2);
300    }
301
302    #[cfg(not(feature = "disable-validation"))]
303    #[test]
304    fn test_invalid_attribute() {
305        let elem: Element = "
306            <x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>
307        "
308        .parse()
309        .unwrap();
310        let error = MucUser::try_from(elem).unwrap_err();
311        let message = match error {
312            Error::ParseError(string) => string,
313            _ => panic!(),
314        };
315        assert_eq!(message, "Unknown attribute in x element.");
316    }
317
318    #[test]
319    fn test_status_simple() {
320        let elem: Element = "
321            <status xmlns='http://jabber.org/protocol/muc#user' code='110'/>
322        "
323        .parse()
324        .unwrap();
325        Status::try_from(elem).unwrap();
326    }
327
328    #[test]
329    fn test_status_invalid() {
330        let elem: Element = "
331            <status xmlns='http://jabber.org/protocol/muc#user'/>
332        "
333        .parse()
334        .unwrap();
335        let error = Status::try_from(elem).unwrap_err();
336        let message = match error {
337            Error::ParseError(string) => string,
338            _ => panic!(),
339        };
340        assert_eq!(message, "Required attribute 'code' missing.");
341    }
342
343    #[cfg(not(feature = "disable-validation"))]
344    #[test]
345    fn test_status_invalid_child() {
346        let elem: Element = "
347            <status xmlns='http://jabber.org/protocol/muc#user' code='110'>
348                <foo/>
349            </status>
350        "
351        .parse()
352        .unwrap();
353        let error = Status::try_from(elem).unwrap_err();
354        let message = match error {
355            Error::ParseError(string) => string,
356            _ => panic!(),
357        };
358        assert_eq!(message, "Unknown child in status element.");
359    }
360
361    #[test]
362    fn test_status_simple_code() {
363        let elem: Element = "
364            <status xmlns='http://jabber.org/protocol/muc#user' code='307'/>
365        "
366        .parse()
367        .unwrap();
368        let status = Status::try_from(elem).unwrap();
369        assert_eq!(status, Status::Kicked);
370    }
371
372    #[test]
373    fn test_status_invalid_code() {
374        let elem: Element = "
375            <status xmlns='http://jabber.org/protocol/muc#user' code='666'/>
376        "
377        .parse()
378        .unwrap();
379        let error = Status::try_from(elem).unwrap_err();
380        let message = match error {
381            Error::ParseError(string) => string,
382            _ => panic!(),
383        };
384        assert_eq!(message, "Invalid status code value.");
385    }
386
387    #[test]
388    fn test_status_invalid_code2() {
389        let elem: Element = "
390            <status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>
391        "
392        .parse()
393        .unwrap();
394        let error = Status::try_from(elem).unwrap_err();
395        let error = match error {
396            Error::ParseIntError(error) => error,
397            _ => panic!(),
398        };
399        assert_eq!(error.to_string(), "invalid digit found in string");
400    }
401
402    #[test]
403    fn test_actor_required_attributes() {
404        let elem: Element = "
405            <actor xmlns='http://jabber.org/protocol/muc#user'/>
406        "
407        .parse()
408        .unwrap();
409        let error = Actor::try_from(elem).unwrap_err();
410        let message = match error {
411            Error::ParseError(string) => string,
412            _ => panic!(),
413        };
414        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
415    }
416
417    #[test]
418    fn test_actor_required_attributes2() {
419        let elem: Element = "
420            <actor xmlns='http://jabber.org/protocol/muc#user'
421                   jid='foo@bar/baz'
422                   nick='baz'/>
423        "
424        .parse()
425        .unwrap();
426        let error = Actor::try_from(elem).unwrap_err();
427        let message = match error {
428            Error::ParseError(string) => string,
429            _ => panic!(),
430        };
431        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
432    }
433
434    #[test]
435    fn test_actor_jid() {
436        let elem: Element = "
437            <actor xmlns='http://jabber.org/protocol/muc#user'
438                   jid='foo@bar/baz'/>
439        "
440        .parse()
441        .unwrap();
442        let actor = Actor::try_from(elem).unwrap();
443        let jid = match actor {
444            Actor::Jid(jid) => jid,
445            _ => panic!(),
446        };
447        assert_eq!(jid, "foo@bar/baz".parse::<FullJid>().unwrap());
448    }
449
450    #[test]
451    fn test_actor_nick() {
452        let elem: Element = "
453            <actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>
454        "
455        .parse()
456        .unwrap();
457        let actor = Actor::try_from(elem).unwrap();
458        let nick = match actor {
459            Actor::Nick(nick) => nick,
460            _ => panic!(),
461        };
462        assert_eq!(nick, "baz".to_owned());
463    }
464
465    #[test]
466    fn test_continue_simple() {
467        let elem: Element = "
468            <continue xmlns='http://jabber.org/protocol/muc#user'/>
469        "
470        .parse()
471        .unwrap();
472        Continue::try_from(elem).unwrap();
473    }
474
475    #[test]
476    fn test_continue_thread_attribute() {
477        let elem: Element = "
478            <continue xmlns='http://jabber.org/protocol/muc#user'
479                      thread='foo'/>
480        "
481        .parse()
482        .unwrap();
483        let continue_ = Continue::try_from(elem).unwrap();
484        assert_eq!(continue_.thread, Some("foo".to_owned()));
485    }
486
487    #[test]
488    fn test_continue_invalid() {
489        let elem: Element = "
490            <continue xmlns='http://jabber.org/protocol/muc#user'>
491                <foobar/>
492            </continue>
493        "
494        .parse()
495        .unwrap();
496        let continue_ = Continue::try_from(elem).unwrap_err();
497        let message = match continue_ {
498            Error::ParseError(string) => string,
499            _ => panic!(),
500        };
501        assert_eq!(message, "Unknown child in continue element.".to_owned());
502    }
503
504    #[test]
505    fn test_reason_simple() {
506        let elem: Element = "
507            <reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
508            .parse()
509            .unwrap();
510        let elem2 = elem.clone();
511        let reason = Reason::try_from(elem).unwrap();
512        assert_eq!(reason.0, "Reason".to_owned());
513
514        let elem3 = reason.into();
515        assert_eq!(elem2, elem3);
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
690    #[test]
691    fn test_serialize_item() {
692        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>"
693        .parse()
694        .unwrap();
695
696        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user' nick='foobar'/>"
697            .parse()
698            .unwrap();
699        let actor = Actor::try_from(elem).unwrap();
700
701        let elem: Element =
702            "<continue xmlns='http://jabber.org/protocol/muc#user' thread='foobar'/>"
703                .parse()
704                .unwrap();
705        let continue_ = Continue::try_from(elem).unwrap();
706
707        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>foobar</reason>"
708            .parse()
709            .unwrap();
710        let reason = Reason::try_from(elem).unwrap();
711
712        let item = Item {
713            affiliation: Affiliation::Member,
714            role: Role::Moderator,
715            jid: None,
716            nick: None,
717            actor: Some(actor),
718            reason: Some(reason),
719            continue_: Some(continue_),
720        };
721
722        let serialized: Element = item.into();
723        assert_eq!(serialized, reference);
724    }
725}