Exemple #1
0
    def test_repr(self):
        s = stanza.IQ(from_=TEST_FROM,
                      to=TEST_TO,
                      id_="someid",
                      type_=structs.IQType.ERROR)
        s.error = stanza.Error()
        self.assertEqual(
            "<iq from='*****@*****.**' to='*****@*****.**'"
            " id='someid' type=<IQType.ERROR: 'error'>"
            " error=<undefined-condition type=<ErrorType.CANCEL: 'cancel'>>>",
            repr(s))

        s = stanza.IQ(from_=TEST_FROM,
                      to=TEST_TO,
                      id_="someid",
                      type_=structs.IQType.RESULT)
        s.payload = TestPayload()
        self.assertEqual(
            "<iq from='*****@*****.**' to='*****@*****.**'"
            " id='someid' type=<IQType.RESULT: 'result'>"
            " data=foobar>", repr(s))

        s = stanza.IQ(from_=TEST_FROM,
                      to=TEST_TO,
                      id_="someid",
                      type_=structs.IQType.RESULT)
        self.assertEqual(
            "<iq from='*****@*****.**' to='*****@*****.**'"
            " id='someid' type=<IQType.RESULT: 'result'>>", repr(s))
Exemple #2
0
    def test_groups_update_fires_events(self):
        request = roster_xso.Query(items=[
            roster_xso.Item(
                jid=self.user1,
                groups=[roster_xso.Group(name="group4")],
            )
        ])

        added_cb = unittest.mock.Mock()
        added_cb.return_value = False
        removed_cb = unittest.mock.Mock()
        removed_cb.return_value = False

        with contextlib.ExitStack() as stack:
            stack.enter_context(
                self.s.on_entry_added_to_group.context_connect(added_cb))
            stack.enter_context(
                self.s.on_entry_removed_from_group.context_connect(removed_cb))
            run_coroutine(
                self.s.handle_roster_push(
                    stanza.IQ(structs.IQType.SET, payload=request)))

        self.assertSequenceEqual([
            unittest.mock.call(self.s.items[self.user1], "group4"),
        ], added_cb.mock_calls)

        self.assertIn(unittest.mock.call(self.s.items[self.user1], "group1"),
                      removed_cb.mock_calls)

        self.assertIn(unittest.mock.call(self.s.items[self.user1], "group3"),
                      removed_cb.mock_calls)
Exemple #3
0
    def send_and_decode_info_query(self, jid, node):
        request_iq = stanza.IQ(to=jid, type_=structs.IQType.GET)
        request_iq.payload = disco_xso.InfoQuery(node=node)

        response = yield from self.client.send(request_iq)

        return response
Exemple #4
0
    def test_item_removal_fixes_groups(self):
        request = roster_xso.Query(
            items=[roster_xso.Item(
                jid=self.user1,
                subscription="remove",
            )])

        added_cb = unittest.mock.Mock()
        added_cb.return_value = False
        removed_cb = unittest.mock.Mock()
        removed_cb.return_value = False

        with contextlib.ExitStack() as stack:
            stack.enter_context(
                self.s.on_entry_added_to_group.context_connect(added_cb))
            stack.enter_context(
                self.s.on_entry_removed_from_group.context_connect(removed_cb))
            run_coroutine(
                self.s.handle_roster_push(
                    stanza.IQ(structs.IQType.SET, payload=request)))

        self.assertSequenceEqual([], added_cb.mock_calls)
        self.assertSequenceEqual([], removed_cb.mock_calls)

        self.assertSetEqual({"group1", "group2"}, set(self.s.groups.keys()))

        self.assertSetEqual({self.s.items[self.user2]},
                            self.s.groups["group1"])

        self.assertSetEqual({self.s.items[self.user2]},
                            self.s.groups["group2"])
Exemple #5
0
    def remove_entry(self, jid, *, timeout=None):
        """
        Request removal of the roster entry identified by the given bare
        `jid`. If the entry currently has any subscription state, the server
        will send the corresponding unsubscribing presence stanzas.

        `timeout` is the maximum time in seconds to wait for a reply from the
        server.

        This may raise arbitrary :class:`.errors.XMPPError` exceptions if the
        server replies with an error and also any kind of connection error if
        the connection gets fatally terminated while waiting for a response.
        """
        yield from self.client.stream.send(
            stanza.IQ(
                structs.IQType.SET,
                payload=roster_xso.Query(items=[
                    roster_xso.Item(
                        jid=jid,
                        subscription="remove"
                    )
                ])
            ),
            timeout=timeout
        )
Exemple #6
0
    def test__validate_rejects_error_without_error(self):
        iq = stanza.IQ(structs.IQType.ERROR)
        iq.autoset_id()

        with self.assertRaisesRegex(
                ValueError, r"IQ with type='error' requires error payload"):
            iq._validate()
Exemple #7
0
    def test_handle_roster_push_accepts_push_from_bare_local_jid(self):
        self.cc.local_jid = structs.JID.fromstr("[email protected]/fnord")

        iq = stanza.IQ(type_=structs.IQType.SET)
        iq.from_ = structs.JID.fromstr("*****@*****.**")
        iq.payload = roster_xso.Query()

        run_coroutine(self.s.handle_roster_push(iq))
Exemple #8
0
    def test_validate_wraps_exceptions_from__validate(self):
        class FooException(Exception):
            pass

        iq = stanza.IQ(structs.IQType.GET)

        with self.assertRaisesRegex(stanza.StanzaError, r"invalid IQ stanza"):
            iq.validate()
Exemple #9
0
    def test_init(self):
        payload = TestPayload()

        s = stanza.IQ(from_=TEST_FROM,
                      type_=structs.IQType.RESULT,
                      payload=payload)
        self.assertEqual(TEST_FROM, s.from_)
        self.assertEqual(structs.IQType.RESULT, s.type_)
        self.assertIs(payload, s.payload)
Exemple #10
0
    def setUp(self):
        self.cc = make_connected_client()
        self.s = disco_service.Service(self.cc)
        self.cc.reset_mock()

        self.request_iq = stanza.IQ(
            "get",
            from_=structs.JID.fromstr("[email protected]/res1"),
            to=structs.JID.fromstr("[email protected]/res2"))
        self.request_iq.autoset_id()
        self.request_iq.payload = disco_xso.InfoQuery()

        self.request_items_iq = stanza.IQ(
            "get",
            from_=structs.JID.fromstr("[email protected]/res1"),
            to=structs.JID.fromstr("[email protected]/res2"))
        self.request_items_iq.autoset_id()
        self.request_items_iq.payload = disco_xso.ItemsQuery()
Exemple #11
0
    def send_and_decode_info_query(self, jid, node):
        request_iq = stanza.IQ(to=jid, type_="get")
        request_iq.payload = disco_xso.InfoQuery(node=node)

        response = yield from self.client.stream.send_iq_and_wait_for_reply(
            request_iq
        )

        return response
Exemple #12
0
    def test_handle_roster_push_rejects_push_with_nonempty_from(self):
        iq = stanza.IQ(type_=structs.IQType.SET)
        iq.from_ = structs.JID.fromstr("*****@*****.**")

        with self.assertRaises(errors.XMPPAuthError) as ctx:
            run_coroutine(self.s.handle_roster_push(iq))

        self.assertEqual((namespaces.stanzas, "forbidden"),
                         ctx.exception.condition)
Exemple #13
0
    def set_entry(self, jid, *,
                  name=_Sentinel,
                  add_to_groups=frozenset(),
                  remove_from_groups=frozenset(),
                  timeout=None):
        """
        Set properties of a roster entry or add a new roster entry. The roster
        entry is identified by its bare `jid`.

        If an entry already exists, all values default to those stored in the
        existing entry. For example, if no `name` is given, the current name of
        the entry is re-used, if any.

        If the entry does not exist, it will be created on the server side.

        The `remove_from_groups` and `add_to_groups` arguments have to be based
        on the locally cached state, as XMPP does not support sending
        diffs. `remove_from_groups` takes precedence over `add_to_groups`.

        `timeout` is the time in seconds to wait for a confirmation by the
        server.

        Note that the changes may not be visible immediately after his
        coroutine returns in the :attr:`items` and :attr:`groups`
        attributes. The :class:`Service` waits for the "official" roster push
        from the server for updating the data structures and firing events, to
        ensure that consistent state with other clients is achieved.

        This may raise arbitrary :class:`.errors.XMPPError` exceptions if the
        server replies with an error and also any kind of connection error if
        the connection gets fatally terminated while waiting for a response.
        """

        existing = self.items.get(jid, Item(jid))

        post_groups = (existing.groups | add_to_groups) - remove_from_groups
        post_name = existing.name
        if name is not _Sentinel:
            post_name = name

        item = roster_xso.Item(
            jid=jid,
            name=post_name,
            groups=[
                roster_xso.Group(name=group_name)
                for group_name in post_groups
            ])

        yield from self.client.stream.send(
            stanza.IQ(
                structs.IQType.SET,
                payload=roster_xso.Query(items=[
                    item
                ])
            ),
            timeout=timeout
        )
Exemple #14
0
 def test_make_reply_enforces_request(self):
     s = stanza.IQ(
         from_=TEST_FROM,
         to=TEST_TO,
         id_="someid",
         type_="error")
     with self.assertRaises(ValueError):
         s.make_reply("error")
     s.type_ = "result"
     with self.assertRaises(ValueError):
         s.make_reply("error")
Exemple #15
0
    def query_items(self,
                    jid,
                    *,
                    node=None,
                    require_fresh=False,
                    timeout=None):
        """
        Send an items query to the given `jid`, querying for the items at the
        `node`. Return the :class:`~.xso.ItemsQuery` result.

        The arguments have the same semantics as with :meth:`query_info`, as
        does the caching and error handling.
        """
        key = jid, node

        if not require_fresh:
            try:
                request = self._items_pending[key]
            except KeyError:
                pass
            else:
                try:
                    return (yield from request)
                except asyncio.CancelledError:
                    pass

        request_iq = stanza.IQ(to=jid, type_=structs.IQType.GET)
        request_iq.payload = disco_xso.ItemsQuery(node=node)

        request = asyncio. async (
            self.client.stream.send_iq_and_wait_for_reply(request_iq))

        self._items_pending[key] = request
        try:
            if timeout is not None:
                try:
                    result = yield from asyncio.wait_for(request,
                                                         timeout=timeout)
                except asyncio.TimeoutError:
                    raise TimeoutError()
            else:
                result = yield from request
        except:
            if request.done():
                try:
                    pending = self._items_pending[key]
                except KeyError:
                    pass
                else:
                    if pending is request:
                        del self._items_pending[key]
            raise

        return result
Exemple #16
0
    def test_make_reply(self):
        s = stanza.IQ(from_=TEST_FROM,
                      to=TEST_TO,
                      id_="someid",
                      type_=structs.IQType.GET)

        r1 = s.make_reply(structs.IQType.ERROR)
        self.assertEqual(s.from_, r1.to)
        self.assertEqual(s.to, r1.from_)
        self.assertEqual(s.id_, r1.id_)
        self.assertEqual(structs.IQType.ERROR, r1.type_)
Exemple #17
0
    def test_handle_roster_push_rejects_push_with_nonempty_from(self):
        self.cc.local_jid = structs.JID.fromstr("*****@*****.**")

        iq = stanza.IQ(type_=structs.IQType.SET)
        iq.from_ = structs.JID.fromstr("*****@*****.**")

        with self.assertRaises(errors.XMPPAuthError) as ctx:
            run_coroutine(self.s.handle_roster_push(iq))

        self.assertEqual(errors.ErrorCondition.FORBIDDEN,
                         ctx.exception.condition)
Exemple #18
0
 def test_make_reply_enforces_request(self):
     s = stanza.IQ(from_=TEST_FROM,
                   to=TEST_TO,
                   id_="someid",
                   type_=structs.IQType.ERROR)
     with self.assertRaisesRegex(ValueError,
                                 r"make_reply requires request IQ"):
         s.make_reply(unittest.mock.sentinel.type_)
     s.type_ = structs.IQType.RESULT
     with self.assertRaisesRegex(ValueError,
                                 r"make_reply requires request IQ"):
         s.make_reply(unittest.mock.sentinel.type_)
Exemple #19
0
    def test_handle_roster_push_rejects_push_from_full_local_jid(self):
        self.cc.local_jid = structs.JID.fromstr("[email protected]/fnord")

        iq = stanza.IQ(type_=structs.IQType.SET)
        iq.from_ = structs.JID.fromstr("[email protected]/fnord")
        iq.payload = roster_xso.Query()

        with self.assertRaises(errors.XMPPAuthError) as ctx:
            run_coroutine(self.s.handle_roster_push(iq))

        self.assertEqual((namespaces.stanzas, "forbidden"),
                         ctx.exception.condition)
Exemple #20
0
    def test_init_error(self):
        error = object()

        s = stanza.IQ(
            from_=TEST_FROM,
            type_="error",
            error=error)
        self.assertEqual(
            "error",
            s.type_)
        self.assertIs(
            error,
            s.error)
Exemple #21
0
    def test_handle_roster_push_removes_from_roster(self):
        request = roster_xso.Query(items=[
            roster_xso.Item(jid=self.user1, subscription="remove"),
        ],
                                   ver="foobarbaz")

        iq = stanza.IQ(type_=structs.IQType.SET)
        iq.payload = request

        self.assertIsNone(run_coroutine(self.s.handle_roster_push(iq)))

        self.assertNotIn(self.user1, self.s.items)
        self.assertIn(self.user2, self.s.items)
        self.assertEqual("foobarbaz", self.s.version)
Exemple #22
0
    def test_make_error(self):
        e = stanza.Error(condition=(namespaces.stanzas, "bad-request"))
        s = stanza.IQ(from_=TEST_FROM,
                      to=TEST_TO,
                      id_="someid",
                      type_=structs.IQType.GET)
        r = s.make_error(e)

        self.assertIsInstance(r, stanza.IQ)

        self.assertEqual(r.type_, structs.IQType.ERROR)
        self.assertEqual(TEST_FROM, r.to)
        self.assertEqual(TEST_TO, r.from_)
        self.assertEqual(s.id_, r.id_)
Exemple #23
0
    def test_make_error(self):
        e = stanza.Error(condition=errors.ErrorCondition.BAD_REQUEST)
        s = stanza.IQ(from_=TEST_FROM,
                      to=TEST_TO,
                      id_="someid",
                      type_=structs.IQType.GET)
        r = s.make_error(e)

        self.assertIsInstance(r, stanza.IQ)

        self.assertEqual(r.type_, structs.IQType.ERROR)
        self.assertEqual(TEST_FROM, r.to)
        self.assertEqual(TEST_TO, r.from_)
        self.assertEqual(s.id_, r.id_)
Exemple #24
0
    def test_repr(self):
        s = stanza.IQ(
            from_=TEST_FROM,
            to=TEST_TO,
            id_="someid",
            type_="error")
        s.error = stanza.Error()
        self.assertEqual(
            "<iq from='*****@*****.**' to='*****@*****.**'"
            " id='someid' type='error'"
            " error=<undefined-condition type='cancel'>>",
            repr(s)
        )

        s = stanza.IQ(
            from_=TEST_FROM,
            to=TEST_TO,
            id_="someid",
            type_="result")
        s.payload = TestPayload()
        self.assertEqual(
            "<iq from='*****@*****.**' to='*****@*****.**'"
            " id='someid' type='result'"
            " data=foobar>",
            repr(s)
        )

        s = stanza.IQ(
            from_=TEST_FROM,
            to=TEST_TO,
            id_="someid",
            type_="result")
        self.assertEqual(
            "<iq from='*****@*****.**' to='*****@*****.**'"
            " id='someid' type='result'>",
            repr(s)
        )
Exemple #25
0
    def test_item_objects_do_not_change_during_push(self):
        old_item = self.s.items[self.user1]

        request = roster_xso.Query(items=[
            roster_xso.Item(jid=self.user1, subscription="both"),
        ],
                                   ver="foobar")

        iq = stanza.IQ(type_=structs.IQType.SET)
        iq.payload = request

        self.assertIsNone(run_coroutine(self.s.handle_roster_push(iq)))

        self.assertIs(old_item, self.s.items[self.user1])
        self.assertEqual("both", old_item.subscription)
Exemple #26
0
    def test_on_group_removed_for_removed_contact(self):
        request = roster_xso.Query(items=[
            roster_xso.Item(
                jid=self.user2,
                subscription="remove",
            ),
        ],
                                   ver="foobar")

        iq = stanza.IQ(type_=structs.IQType.SET)
        iq.payload = request

        run_coroutine(self.s.handle_roster_push(iq))

        self.listener.on_group_removed.assert_called_once_with("group2")
Exemple #27
0
    def test_init(self):
        payload = object()

        s = stanza.IQ(
            from_=TEST_FROM,
            type_="result",
            payload=payload)
        self.assertEqual(
            TEST_FROM,
            s.from_)
        self.assertEqual(
            "result",
            s.type_)
        self.assertIs(
            payload,
            s.payload)
Exemple #28
0
    def test_update_groups_on_update(self):
        request = roster_xso.Query(items=[
            roster_xso.Item(
                jid=self.user1,
                groups=[roster_xso.Group(name="group4")],
            )
        ])

        run_coroutine(
            self.s.handle_roster_push(stanza.IQ("set", payload=request)))

        self.assertNotIn("group3", self.s.groups)
        self.assertSetEqual({self.s.items[self.user2]},
                            self.s.groups["group1"])
        self.assertSetEqual({self.s.items[self.user2]},
                            self.s.groups["group2"])
        self.assertSetEqual({self.s.items[self.user1]},
                            self.s.groups["group4"])
Exemple #29
0
    def test_do_not_lose_update_during_initial_roster(self):
        self.cc.mock_calls.clear()

        initial = roster_xso.Query(items=[
            roster_xso.Item(jid=self.user2,
                            name="some bar user",
                            subscription="both")
        ],
                                   ver="foobar")

        push = stanza.IQ(type_=structs.IQType.SET,
                         payload=roster_xso.Query(items=[
                             roster_xso.Item(
                                 jid=self.user1,
                                 name="some foo user",
                             ),
                             roster_xso.Item(
                                 jid=self.user2,
                                 subscription="remove",
                             )
                         ],
                                                  ver="foobar"))

        @asyncio.coroutine
        def send(iq, timeout=None):
            # this is brutal, but a sure way to provoke the race
            asyncio.ensure_future(self.s.handle_roster_push(push))
            # give the roster push a chance to act
            # (we cannot yield from the handle_roster_push() here: in the fixed
            # version that would be a deadlock)
            yield from asyncio.sleep(0)
            return initial

        self.cc.send = unittest.mock.Mock()
        self.cc.send.side_effect = send

        initial_roster = asyncio.ensure_future(
            self.cc.before_stream_established())

        run_coroutine(initial_roster)

        self.assertNotIn(
            self.user2, self.s.items,
            "initial roster processing lost a race against roster push")
Exemple #30
0
    def _request_initial_roster(self):
        iq = stanza.IQ(type_=structs.IQType.GET)
        iq.payload = roster_xso.Query()

        with (yield from self.__roster_lock):
            logger.debug("requesting initial roster")
            if self.client.stream_features.has_feature(
                    roster_xso.RosterVersioningFeature):
                logger.debug("requesting incremental updates (old ver = %s)",
                             self.version)
                iq.payload.ver = self.version

            response = yield from self.client.stream.send(
                iq,
                timeout=self.client.negotiation_timeout.total_seconds()
            )

            if response is None:
                logger.debug("roster will be updated incrementally")
                self.on_initial_roster_received()
                return True

            self.version = response.ver
            logger.debug("roster update received (new ver = %s)", self.version)

            actual_jids = {item.jid for item in response.items}
            known_jids = set(self.items.keys())

            removed_jids = known_jids - actual_jids
            logger.debug("jids dropped: %r", removed_jids)

            for removed_jid in removed_jids:
                old_item = self.items.pop(removed_jid)
                self._remove_from_groups(old_item, old_item.groups)
                self.on_entry_removed(old_item)

            logger.debug("jids updated: %r", actual_jids - removed_jids)
            for item in response.items:
                self._update_entry(item)

            self.on_initial_roster_received()
            return True