user.rs

  1// Copyright (c) 2017 Maxime “pep” Buquet <pep+code@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::error::Error;
  9use crate::ns;
 10use jid::Jid;
 11use minidom::Element;
 12use try_from::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(Jid),
 86
 87    /// The nickname of this user.
 88    Nick(String),
 89}
 90
 91impl TryFrom<Element> for Actor {
 92    type Err = 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<Jid> = get_attr!(elem, "jid", optional);
 99        let nick = get_attr!(elem, "nick", optional);
100
101        match (jid, nick) {
102            (Some(_), Some(_)) | (None, None) => {
103                return 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" => optional,
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: Affiliation = "affiliation" => required,
191
192        /// The real JID of this user, if you are allowed to see it.
193        jid: Option<Jid> = "jid" => optional,
194
195        /// The current nickname of this user.
196        nick: Option<String> = "nick" => optional,
197
198        /// The current role of this user.
199        role: Role = "role" => required
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::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    #[test]
307    fn test_invalid_attribute() {
308        let elem: Element = "
309            <x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>
310        "
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 attribute in x element.");
319    }
320
321    #[test]
322    fn test_status_simple() {
323        let elem: Element = "
324            <status xmlns='http://jabber.org/protocol/muc#user' code='110'/>
325        "
326        .parse()
327        .unwrap();
328        Status::try_from(elem).unwrap();
329    }
330
331    #[test]
332    fn test_status_invalid() {
333        let elem: Element = "
334            <status xmlns='http://jabber.org/protocol/muc#user'/>
335        "
336        .parse()
337        .unwrap();
338        let error = Status::try_from(elem).unwrap_err();
339        let message = match error {
340            Error::ParseError(string) => string,
341            _ => panic!(),
342        };
343        assert_eq!(message, "Required attribute 'code' missing.");
344    }
345
346    #[test]
347    fn test_status_invalid_child() {
348        let elem: Element = "
349            <status xmlns='http://jabber.org/protocol/muc#user' code='110'>
350                <foo/>
351            </status>
352        "
353        .parse()
354        .unwrap();
355        let error = Status::try_from(elem).unwrap_err();
356        let message = match error {
357            Error::ParseError(string) => string,
358            _ => panic!(),
359        };
360        assert_eq!(message, "Unknown child in status element.");
361    }
362
363    #[test]
364    fn test_status_simple_code() {
365        let elem: Element = "
366            <status xmlns='http://jabber.org/protocol/muc#user' code='307'/>
367        "
368        .parse()
369        .unwrap();
370        let status = Status::try_from(elem).unwrap();
371        assert_eq!(status, Status::Kicked);
372    }
373
374    #[test]
375    fn test_status_invalid_code() {
376        let elem: Element = "
377            <status xmlns='http://jabber.org/protocol/muc#user' code='666'/>
378        "
379        .parse()
380        .unwrap();
381        let error = Status::try_from(elem).unwrap_err();
382        let message = match error {
383            Error::ParseError(string) => string,
384            _ => panic!(),
385        };
386        assert_eq!(message, "Invalid status code value.");
387    }
388
389    #[test]
390    fn test_status_invalid_code2() {
391        let elem: Element = "
392            <status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>
393        "
394        .parse()
395        .unwrap();
396        let error = Status::try_from(elem).unwrap_err();
397        let error = match error {
398            Error::ParseIntError(error) => error,
399            _ => panic!(),
400        };
401        assert_eq!(error.description(), "invalid digit found in string");
402    }
403
404    #[test]
405    fn test_actor_required_attributes() {
406        let elem: Element = "
407            <actor xmlns='http://jabber.org/protocol/muc#user'/>
408        "
409        .parse()
410        .unwrap();
411        let error = Actor::try_from(elem).unwrap_err();
412        let message = match error {
413            Error::ParseError(string) => string,
414            _ => panic!(),
415        };
416        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
417    }
418
419    #[test]
420    fn test_actor_required_attributes2() {
421        let elem: Element = "
422            <actor xmlns='http://jabber.org/protocol/muc#user'
423                   jid='foo@bar/baz'
424                   nick='baz'/>
425        "
426        .parse()
427        .unwrap();
428        let error = Actor::try_from(elem).unwrap_err();
429        let message = match error {
430            Error::ParseError(string) => string,
431            _ => panic!(),
432        };
433        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
434    }
435
436    #[test]
437    fn test_actor_jid() {
438        let elem: Element = "
439            <actor xmlns='http://jabber.org/protocol/muc#user'
440                   jid='foo@bar/baz'/>
441        "
442        .parse()
443        .unwrap();
444        let actor = Actor::try_from(elem).unwrap();
445        let jid = match actor {
446            Actor::Jid(jid) => jid,
447            _ => panic!(),
448        };
449        assert_eq!(jid, "foo@bar/baz".parse::<Jid>().unwrap());
450    }
451
452    #[test]
453    fn test_actor_nick() {
454        let elem: Element = "
455            <actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>
456        "
457        .parse()
458        .unwrap();
459        let actor = Actor::try_from(elem).unwrap();
460        let nick = match actor {
461            Actor::Nick(nick) => nick,
462            _ => panic!(),
463        };
464        assert_eq!(nick, "baz".to_owned());
465    }
466
467    #[test]
468    fn test_continue_simple() {
469        let elem: Element = "
470            <continue xmlns='http://jabber.org/protocol/muc#user'/>
471        "
472        .parse()
473        .unwrap();
474        Continue::try_from(elem).unwrap();
475    }
476
477    #[test]
478    fn test_continue_thread_attribute() {
479        let elem: Element = "
480            <continue xmlns='http://jabber.org/protocol/muc#user'
481                      thread='foo'/>
482        "
483        .parse()
484        .unwrap();
485        let continue_ = Continue::try_from(elem).unwrap();
486        assert_eq!(continue_.thread, Some("foo".to_owned()));
487    }
488
489    #[test]
490    fn test_continue_invalid() {
491        let elem: Element = "
492            <continue xmlns='http://jabber.org/protocol/muc#user'>
493                <foobar/>
494            </continue>
495        "
496        .parse()
497        .unwrap();
498        let continue_ = Continue::try_from(elem).unwrap_err();
499        let message = match continue_ {
500            Error::ParseError(string) => string,
501            _ => panic!(),
502        };
503        assert_eq!(message, "Unknown child in continue element.".to_owned());
504    }
505
506    #[test]
507    fn test_reason_simple() {
508        let elem: Element = "
509            <reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
510            .parse()
511            .unwrap();
512        let reason = Reason::try_from(elem).unwrap();
513        assert_eq!(reason.0, "Reason".to_owned());
514    }
515
516    #[test]
517    fn test_reason_invalid_attribute() {
518        let elem: Element = "
519            <reason xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>
520        "
521        .parse()
522        .unwrap();
523        let error = Reason::try_from(elem).unwrap_err();
524        let message = match error {
525            Error::ParseError(string) => string,
526            _ => panic!(),
527        };
528        assert_eq!(message, "Unknown attribute in reason element.".to_owned());
529    }
530
531    #[test]
532    fn test_reason_invalid() {
533        let elem: Element = "
534            <reason xmlns='http://jabber.org/protocol/muc#user'>
535                <foobar/>
536            </reason>
537        "
538        .parse()
539        .unwrap();
540        let error = Reason::try_from(elem).unwrap_err();
541        let message = match error {
542            Error::ParseError(string) => string,
543            _ => panic!(),
544        };
545        assert_eq!(message, "Unknown child in reason element.".to_owned());
546    }
547
548    #[test]
549    fn test_item_invalid_attr() {
550        let elem: Element = "
551            <item xmlns='http://jabber.org/protocol/muc#user'
552                  foo='bar'/>
553        "
554        .parse()
555        .unwrap();
556        let error = Item::try_from(elem).unwrap_err();
557        let message = match error {
558            Error::ParseError(string) => string,
559            _ => panic!(),
560        };
561        assert_eq!(message, "Unknown attribute in item element.".to_owned());
562    }
563
564    #[test]
565    fn test_item_affiliation_role_attr() {
566        let elem: Element = "
567            <item xmlns='http://jabber.org/protocol/muc#user'
568                  affiliation='member'
569                  role='moderator'/>
570        "
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 = "
579            <item xmlns='http://jabber.org/protocol/muc#user'
580                  affiliation='member'/>
581        "
582        .parse()
583        .unwrap();
584        let error = Item::try_from(elem).unwrap_err();
585        let message = match error {
586            Error::ParseError(string) => string,
587            _ => panic!(),
588        };
589        assert_eq!(message, "Required attribute 'role' missing.".to_owned());
590    }
591
592    #[test]
593    fn test_item_nick_attr() {
594        let elem: Element = "
595            <item xmlns='http://jabber.org/protocol/muc#user'
596                  affiliation='member'
597                  role='moderator'
598                  nick='foobar'/>
599        "
600        .parse()
601        .unwrap();
602        let item = Item::try_from(elem).unwrap();
603        match item {
604            Item { nick, .. } => assert_eq!(nick, Some("foobar".to_owned())),
605        }
606    }
607
608    #[test]
609    fn test_item_affiliation_role_invalid_attr2() {
610        let elem: Element = "
611            <item xmlns='http://jabber.org/protocol/muc#user'
612                  role='moderator'/>
613        "
614        .parse()
615        .unwrap();
616        let error = Item::try_from(elem).unwrap_err();
617        let message = match error {
618            Error::ParseError(string) => string,
619            _ => panic!(),
620        };
621        assert_eq!(
622            message,
623            "Required attribute 'affiliation' missing.".to_owned()
624        );
625    }
626
627    #[test]
628    fn test_item_role_actor_child() {
629        let elem: Element = "
630            <item xmlns='http://jabber.org/protocol/muc#user'
631                  affiliation='member'
632                  role='moderator'>
633                <actor nick='foobar'/>
634            </item>
635        "
636        .parse()
637        .unwrap();
638        let item = Item::try_from(elem).unwrap();
639        match item {
640            Item { actor, .. } => assert_eq!(actor, Some(Actor::Nick("foobar".to_owned()))),
641        }
642    }
643
644    #[test]
645    fn test_item_role_continue_child() {
646        let elem: Element = "
647            <item xmlns='http://jabber.org/protocol/muc#user'
648                  affiliation='member'
649                  role='moderator'>
650                <continue thread='foobar'/>
651            </item>
652        "
653        .parse()
654        .unwrap();
655        let item = Item::try_from(elem).unwrap();
656        let continue_1 = Continue {
657            thread: Some("foobar".to_owned()),
658        };
659        match item {
660            Item {
661                continue_: Some(continue_2),
662                ..
663            } => assert_eq!(continue_2.thread, continue_1.thread),
664            _ => panic!(),
665        }
666    }
667
668    #[test]
669    fn test_item_role_reason_child() {
670        let elem: Element = "
671            <item xmlns='http://jabber.org/protocol/muc#user'
672                  affiliation='member'
673                  role='moderator'>
674                <reason>foobar</reason>
675            </item>
676        "
677        .parse()
678        .unwrap();
679        let item = Item::try_from(elem).unwrap();
680        match item {
681            Item { reason, .. } => assert_eq!(reason, Some(Reason("foobar".to_owned()))),
682        }
683    }
684}