def test(q, bus, conn, stream): # Gabble asks for the roster; the server sends back an empty roster. event = q.expect('stream-iq', query_ns=ns.ROSTER) acknowledge_iq(stream, event.stanza) pairs = expect_contact_list_signals(q, bus, conn, ['stored']) stored = check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'stored', []) # The server sends us a roster push without an id=''. WTF! iq = make_roster_push(stream, jid, 'both') del iq['id'] stream.send(iq) h = conn.RequestHandles(cs.HT_CONTACT, [jid])[0] q.expect_many( EventPattern('dbus-signal', signal='MembersChanged', args=['', [h], [], [], [], 0, 0], path=stored.object_path), EventPattern('dbus-signal', signal='ContactsChanged', args=[{ h: (cs.SUBSCRIPTION_STATE_YES, cs.SUBSCRIPTION_STATE_YES, ''), }, []], ), ) # Verify that Gabble didn't crash while trying to ack the push. sync_stream(q, stream) # Just for completeness, let's repeat this test with a malicious roster # push from a contact (rather than from our server). Our server's *really* # broken if it allows this. Nonetheless... iq = make_roster_push(stream, '*****@*****.**', 'both') del iq['id'] iq['from'] = '*****@*****.**' stream.send(iq) q.forbid_events( [ EventPattern('dbus-signal', signal='MembersChanged', path=stored.object_path), EventPattern('dbus-signal', signal='ContactsChanged'), ]) # Make sure Gabble's got the evil push... sync_stream(q, stream) # ...and make sure it's not emitted anything. sync_dbus(bus, q, conn)
def test(q, bus, conn, stream): # Gabble asks for the roster; the server sends back an empty roster. event = q.expect('stream-iq', query_ns=ns.ROSTER) acknowledge_iq(stream, event.stanza) pairs = expect_contact_list_signals(q, bus, conn, ['stored']) stored = check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'stored', []) # Some malicious peer sends us a roster push to try to trick us into # showing them on our roster. Gabble should know better than to trust it. iq = make_roster_push(stream, jid, 'both') iq['from'] = jid stream.send(iq) q.forbid_events( [ EventPattern('dbus-signal', signal='MembersChanged', path=stored.object_path), EventPattern('dbus-signal', signal='ContactsChanged'), ]) e = q.expect('stream-iq', iq_type='error')
def test(q, bus, conn, stream): call_async(q, conn.ContactList, 'GetContactListAttributes', [], False) q.expect('dbus-error', method='GetContactListAttributes', name=cs.NOT_YET) event = q.expect('stream-iq', query_ns=ns.ROSTER) event.stanza['type'] = 'result' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'both' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'from' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'to' stream.send(event.stanza) # slight implementation detail: TpBaseContactList emits ContactsChanged # before it announces its channels s = q.expect('dbus-signal', signal='ContactsChanged', interface=cs.CONN_IFACE_CONTACT_LIST, path=conn.object_path) amy, bob, che = conn.RequestHandles(cs.HT_CONTACT, ['*****@*****.**', '*****@*****.**', '*****@*****.**']) assertEquals([{ amy: (cs.SUBSCRIPTION_STATE_YES, cs.SUBSCRIPTION_STATE_YES, ''), bob: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_YES, ''), che: (cs.SUBSCRIPTION_STATE_YES, cs.SUBSCRIPTION_STATE_NO, ''), }, []], s.args) pairs = expect_contact_list_signals(q, bus, conn, ['publish', 'subscribe', 'stored']) # this is emitted last, so clients can tell when the initial state dump # has finished q.expect('dbus-signal', signal='ContactListStateChanged', args=[cs.CONTACT_LIST_STATE_SUCCESS]) call_async(q, conn.ContactList, 'GetContactListAttributes', [], False) r = q.expect('dbus-return', method='GetContactListAttributes') assertEquals(({ amy: { cs.CONN_IFACE_CONTACT_LIST + '/subscribe': cs.SUBSCRIPTION_STATE_YES, cs.CONN_IFACE_CONTACT_LIST + '/publish': cs.SUBSCRIPTION_STATE_YES, cs.CONN + '/contact-id': '*****@*****.**', }, bob: { cs.CONN_IFACE_CONTACT_LIST + '/subscribe': cs.SUBSCRIPTION_STATE_NO, cs.CONN_IFACE_CONTACT_LIST + '/publish': cs.SUBSCRIPTION_STATE_YES, cs.CONN + '/contact-id': '*****@*****.**', }, che: { cs.CONN_IFACE_CONTACT_LIST + '/subscribe': cs.SUBSCRIPTION_STATE_YES, cs.CONN_IFACE_CONTACT_LIST + '/publish': cs.SUBSCRIPTION_STATE_NO, cs.CONN + '/contact-id': '*****@*****.**', }, },), r.value) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'publish', ['*****@*****.**', '*****@*****.**']) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'subscribe', ['*****@*****.**', '*****@*****.**']) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'stored', ['*****@*****.**', '*****@*****.**', '*****@*****.**']) assertLength(0, pairs) # i.e. we've checked all of them
def test(q, bus, conns, streams): conn1, conn2 = conns stream1, stream2 = streams # Connection 1 conn1.Connect() q.expect('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTING, cs.CSR_REQUESTED], path=conn1.object.object_path) q.expect('stream-authenticated') q.expect('dbus-signal', signal='PresencesChanged', args=[{1L: (cs.PRESENCE_AVAILABLE, 'available', '')}], path=conn1.object.object_path) q.expect('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED], path=conn1.object.object_path) pairs = expect_contact_list_signals(q, bus, conn1, ['publish', 'subscribe', 'stored']) check_contact_list_signals(q, bus, conn1, pairs.pop(0), cs.HT_LIST, 'publish', []) check_contact_list_signals(q, bus, conn1, pairs.pop(0), cs.HT_LIST, 'subscribe', []) check_contact_list_signals(q, bus, conn1, pairs.pop(0), cs.HT_LIST, 'stored', []) assertLength(0, pairs) # i.e. we popped and checked all of them # Connection 2 conn2.Connect() q.expect('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTING, cs.CSR_REQUESTED], path=conn2.object.object_path) q.expect('stream-authenticated') q.expect('dbus-signal', signal='PresencesChanged', args=[{1L: (cs.PRESENCE_AVAILABLE, 'available', '')}], path=conn2.object.object_path) q.expect('dbus-signal', signal='StatusChanged', args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED], path=conn2.object.object_path) pairs = expect_contact_list_signals(q, bus, conn2, ['publish', 'subscribe', 'stored']) check_contact_list_signals(q, bus, conn2, pairs.pop(0), cs.HT_LIST, 'publish', []) check_contact_list_signals(q, bus, conn2, pairs.pop(0), cs.HT_LIST, 'subscribe', []) check_contact_list_signals(q, bus, conn2, pairs.pop(0), cs.HT_LIST, 'stored', []) assertLength(0, pairs) # i.e. we popped and checked all of them
def test(q, bus, conn, stream): event = q.expect('stream-iq', query_ns=ns.ROSTER) event.stanza['type'] = 'result' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'both' item.addElement('group', content='women') item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'from' item.addElement('group', content='men') item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'to' item.addElement('group', content='men') stream.send(event.stanza) # slight implementation detail: TpBaseContactList emits ContactsChanged # etc. before it announces its channels, and it emits one CGC per group. s1, s2 = q.expect_many( EventPattern('dbus-signal', signal='GroupsChanged', interface=cs.CONN_IFACE_CONTACT_GROUPS, path=conn.object_path, predicate=lambda e: 'women' in e.args[1]), EventPattern('dbus-signal', signal='GroupsChanged', interface=cs.CONN_IFACE_CONTACT_GROUPS, path=conn.object_path, predicate=lambda e: 'men' in e.args[1]), ) amy, bob, che = conn.RequestHandles(cs.HT_CONTACT, ['*****@*****.**', '*****@*****.**', '*****@*****.**']) assertEquals([[amy], ['women'], []], s1.args) assertEquals([[bob, che], ['men'], []], s2.args) pairs = expect_contact_list_signals(q, bus, conn, [], ['men', 'women']) q.expect('dbus-signal', signal='ContactListStateChanged', args=[cs.CONTACT_LIST_STATE_SUCCESS]) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_GROUP, 'men', ['*****@*****.**', '*****@*****.**']) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_GROUP, 'women', ['*****@*****.**']) assertLength(0, pairs) # i.e. we've checked all of them # change Amy's groups call_async(q, conn.ContactGroups, 'SetContactGroups', amy, ['ladies', 'people starting with A']) s, iq = q.expect_many( EventPattern('dbus-signal', signal='GroupsCreated'), EventPattern('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER), ) assertEquals(set(('ladies', 'people starting with A')), set(s.args[0])) jid, groups = parse_roster_change_request(iq.query, iq.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(('ladies', 'people starting with A')), groups) acknowledge_iq(stream, iq.stanza) q.expect('dbus-return', method='SetContactGroups') # Now the server sends us a roster push. send_roster_push(stream, '*****@*****.**', ['people starting with A', 'ladies']) # We get a single signal corresponding to that roster push e = q.expect('dbus-signal', signal='GroupsChanged', predicate=lambda e: e.args[0] == [amy]) assertEquals(set(['ladies', 'people starting with A']), set(e.args[1])) assertEquals(['women'], e.args[2]) # check that Amy's state is what we expected attrs = conn.Contacts.GetContactAttributes([amy], [cs.CONN_IFACE_CONTACT_GROUPS], False)[amy] # make the group list order-independent attrs[cs.CONN_IFACE_CONTACT_GROUPS + '/groups'] = \ set(attrs[cs.CONN_IFACE_CONTACT_GROUPS + '/groups']) assertEquals({ cs.CONN_IFACE_CONTACT_GROUPS + '/groups': set(['ladies', 'people starting with A']), cs.CONN + '/contact-id': '*****@*****.**' }, attrs) for it_worked in (False, True): # remove a group with a member (the old API couldn't do this) call_async(q, conn.ContactGroups, 'RemoveGroup', 'people starting with A') iq = q.expect('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER) jid, groups = parse_roster_change_request(iq.query, iq.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(('ladies',)), groups) acknowledge_iq(stream, iq.stanza) # we emit these as soon as the IQ is ack'd, so that we can indicate # group removal... q.expect('dbus-signal', signal='GroupsRemoved', args=[['people starting with A']]) q.expect('dbus-signal', signal='GroupsChanged', args=[[amy], [], ['people starting with A']]) q.expect('dbus-return', method='RemoveGroup') if it_worked: # ... although in fact this is what *actually* removes Amy from the # group send_roster_push(stream, '*****@*****.**', ['ladies']) else: # if the change didn't "stick", this message will revert it send_roster_push(stream, '*****@*****.**', ['ladies', 'people starting with A']) q.expect('dbus-signal', signal='GroupsCreated', args=[['people starting with A']]) q.expect('dbus-signal', signal='GroupsChanged', args=[[amy], ['people starting with A'], []]) sync_dbus(bus, q, conn) sync_stream(q, stream) assertEquals({ cs.CONN_IFACE_CONTACT_GROUPS + '/groups': ['ladies', 'people starting with A'], cs.CONN + '/contact-id': '*****@*****.**' }, conn.Contacts.GetContactAttributes([amy], [cs.CONN_IFACE_CONTACT_GROUPS], False)[amy]) # sanity check: after all that, we expect Amy to be in group 'ladies' only sync_dbus(bus, q, conn) sync_stream(q, stream) assertEquals({ cs.CONN_IFACE_CONTACT_GROUPS + '/groups': ['ladies'], cs.CONN + '/contact-id': '*****@*****.**' }, conn.Contacts.GetContactAttributes([amy], [cs.CONN_IFACE_CONTACT_GROUPS], False)[amy]) # Rename group 'ladies' to 'girls' call_async(q, conn.ContactGroups, 'RenameGroup', 'ladies', 'girls') # Amy is added to 'girls' e = q.expect('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER) jid, groups = parse_roster_change_request(e.query, e.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(['girls', 'ladies']), groups) send_roster_push(stream, '*****@*****.**', ['girls', 'ladies']) acknowledge_iq(stream, e.stanza) # Amy is removed from 'ladies' e = q.expect('stream-iq', iq_type='set', query_name='query', query_ns=ns.ROSTER) jid, groups = parse_roster_change_request(e.query, e.stanza) assertEquals('*****@*****.**', jid) assertEquals(set(['girls']), groups) send_roster_push(stream, '*****@*****.**', ['girls']) acknowledge_iq(stream, e.stanza) q.expect('dbus-return', method='RenameGroup') # check everything has been updated groups = conn.Properties.Get(cs.CONN_IFACE_CONTACT_GROUPS, 'Groups') assertContains('girls', groups) assertDoesNotContain('ladies', groups) contacts = conn.ContactList.GetContactListAttributes([cs.CONN_IFACE_CONTACT_GROUPS], False) assertEquals(['girls'], contacts[amy][cs.CONN_IFACE_CONTACT_GROUPS + '/groups'])
def test(q, bus, conn, stream, remove, local, modern): # Gabble asks for the roster; the server sends back an empty roster. event = q.expect("stream-iq", query_ns=ns.ROSTER) event.stanza["type"] = "result" stream.send(event.stanza) pairs = expect_contact_list_signals(q, bus, conn, ["publish", "subscribe", "stored"]) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, "publish", []) subscribe = check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, "subscribe", []) stored = check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, "stored", []) assertLength(0, pairs) # i.e. we've checked all of them self_handle = conn.GetSelfHandle() h = conn.RequestHandles(cs.HT_CONTACT, [jid])[0] # Another client logged into our account (Gajim, say) wants to subscribe to # Marco's presence. First, per RFC 3921 it 'SHOULD perform a "roster set" # for the new roster item': # # <iq type='set'> # <query xmlns='jabber:iq:roster'> # <item jid='*****@*****.**'/> # </query> # </iq> # # 'As a result, the user's server (1) MUST initiate a roster push for the # new roster item to all available resources associated with this user that # have requested the roster, setting the 'subscription' attribute to a # value of "none"': iq = IQ(stream, "set") item = iq.addElement((ns.ROSTER, "query")).addElement("item") item["jid"] = jid item["subscription"] = "none" stream.send(iq) # In response, Gabble should add Marco to stored: q.expect_many( EventPattern("dbus-signal", signal="MembersChanged", args=["", [h], [], [], [], 0, 0], path=stored.object_path), EventPattern( "dbus-signal", signal="ContactsChanged", args=[{h: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_NO, "")}, []], ), ) # Gajim sends a <presence type='subscribe'/> to Marco. 'As a result, the # user's server MUST initiate a second roster push to all of the user's # available resources that have requested the roster, setting [...] # ask='subscribe' attribute in the roster item [for Marco]: iq = IQ(stream, "set") item = iq.addElement((ns.ROSTER, "query")).addElement("item") item["jid"] = jid item["subscription"] = "none" item["ask"] = "subscribe" stream.send(iq) # In response, Gabble should add Marco to subscribe:remote-pending: q.expect_many( EventPattern( "dbus-signal", signal="MembersChanged", args=["", [], [], [], [h], self_handle, 0], path=subscribe.object_path, ), EventPattern( "dbus-signal", signal="ContactsChanged", args=[{h: (cs.SUBSCRIPTION_STATE_ASK, cs.SUBSCRIPTION_STATE_NO, "")}, []], ), ) # The user decides that they don't care what Marco's baking after all # (maybe they read his blog instead?) and: if remove: # ...removes him from the roster... if local: # ...by telling Gabble to remove him from stored. if modern: call_async(q, conn.ContactList, "RemoveContacts", [h]) else: call_async(q, stored.Group, "RemoveMembers", [h], "") event = q.expect("stream-iq", iq_type="set", query_ns=ns.ROSTER) item = event.query.firstChildElement() assertEquals(jid, item["jid"]) assertEquals("remove", item["subscription"]) else: # ...using the other client. pass # The server must 'inform all of the user's available resources that # have requested the roster of the roster item removal': iq = IQ(stream, "set") item = iq.addElement((ns.ROSTER, "query")).addElement("item") item["jid"] = jid item["subscription"] = "remove" # When Marco found this bug, this roster update included: item["ask"] = "subscribe" # which is a bit weird: I don't think the server should send that when # the contact's being removed. I think Gabble should ignore it, so I'm # including it in the test. stream.send(iq) # In response, Gabble should announce that Marco has been removed from # subscribe:remote-pending and stored:members: q.expect_many( EventPattern( "dbus-signal", signal="MembersChanged", args=["", [], [h], [], [], 0, 0], path=subscribe.object_path ), EventPattern( "dbus-signal", signal="MembersChanged", args=["", [], [h], [], [], 0, 0], path=stored.object_path ), EventPattern("dbus-signal", signal="ContactsChanged", args=[{}, [h]]), ) if local and modern: acknowledge_iq(stream, event.stanza) q.expect("dbus-return", method="RemoveContacts") # FIXME: when we depend on a new enough tp-glib we can expect # RemoveMembers to return here in the local case, too else: # ...rescinds the subscription request... if local: # ...by telling Gabble to remove him from 'subscribe'. if modern: call_async(q, conn.ContactList, "Unsubscribe", [h]) else: subscribe.Group.RemoveMembers([h], "") events = [EventPattern("stream-presence", to=jid, presence_type="unsubscribe")] if modern: events.append(EventPattern("dbus-return", method="Unsubscribe")) event = q.expect_many(*events)[0] else: # ...in the other client. pass # In response, the server sends a roster update: iq = IQ(stream, "set") item = iq.addElement((ns.ROSTER, "query")).addElement("item") item["jid"] = jid item["subscription"] = "none" # no ask='subscribe' any more. stream.send(iq) # In response, Gabble should announce that Marco has been removed from # subscribe:remote-pending. It shouldn't wait for the <presence # type='unsubscribed'/> ack before doing so: empirical tests reveal # that it's never delivered. q.expect_many( EventPattern( "dbus-signal", signal="MembersChanged", args=["", [], [h], [], [], 0, 0], path=subscribe.object_path ), EventPattern( "dbus-signal", signal="ContactsChanged", args=[{h: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_NO, "")}, []], ), )
def test(q, bus, conn, stream): event = q.expect('stream-iq', query_ns=ns.ROSTER) event.stanza['type'] = 'result' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'both' item.addElement('group', content='women') item.addElement('group', content='affected-by-fdo-12791') # This is a broken roster - Amy appears twice. This should only happen # if the server is somehow buggy. This was my initial attempt at # reproducing fd.o #12791 - I doubt it's very realistic, but we shouldn't # assert, regardless of what input we get! item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'both' item.addElement('group', content='women') item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'from' item.addElement('group', content='men') # This is what was *actually* strange about the #12791 submitter's roster - # Bob appears, fully subscribed, but also there's an attempt to subscribe # to one of Bob's resources. We now ignore such items item = event.query.addElement('item') item['jid'] = '[email protected]/Resource' item['subscription'] = 'none' item['ask'] = 'subscribe' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'to' item.addElement('group', content='men') stream.send(event.stanza) pairs = expect_contact_list_signals(q, bus, conn, ['publish', 'subscribe', 'stored'], ['men', 'women', 'affected-by-fdo-12791']) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'publish', ['*****@*****.**', '*****@*****.**']) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'subscribe', ['*****@*****.**', '*****@*****.**']) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'stored', ['*****@*****.**', '*****@*****.**', '*****@*****.**']) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_GROUP, 'men', ['*****@*****.**', '*****@*****.**']) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_GROUP, 'women', ['*****@*****.**']) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_GROUP, 'affected-by-fdo-12791', []) assertLength(0, pairs) # i.e. we've checked all of them
def test(q, bus, conn, stream, modern=True, queued=False): event = q.expect('stream-iq', query_ns=ns.ROSTER) event.stanza['type'] = 'result' item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'none' quux_handle = conn.RequestHandles(cs.HT_CONTACT, ['*****@*****.**'])[0] stream.send(event.stanza) # slight implementation detail: TpBaseContactList emits ContactsChanged # before it announces its channels q.expect('dbus-signal', signal='ContactsChanged', interface=cs.CONN_IFACE_CONTACT_LIST, path=conn.object_path, args=[{quux_handle: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_NO, '')}, []]) pairs = expect_contact_list_signals(q, bus, conn, ['publish', 'subscribe', 'stored']) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'publish', []) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'subscribe', []) stored = check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'stored', ['*****@*****.**']) assertLength(0, pairs) # i.e. we've checked all of them if queued: conn.Aliasing.SetAliases({quux_handle: 'Quux'}) set_aliases = q.expect('stream-iq', query_ns=ns.ROSTER) item = set_aliases.query.firstChildElement() assertEquals('*****@*****.**', item['jid']) assertEquals('Quux', item['name']) expectations = [ EventPattern('stream-iq', iq_type='set', query_ns=ns.ROSTER), ] if modern: call_async(q, conn.ContactList, 'RemoveContacts', [quux_handle]) else: call_async(q, stored.Group, 'RemoveMembers', [quux_handle], '') if queued: # finish off the previous thing we were doing, so removal can proceed acknowledge_iq(stream, set_aliases.stanza) event = q.expect_many(*expectations)[0] item = event.query.firstChildElement() assertEquals('*****@*****.**', item['jid']) assertEquals('remove', item['subscription']) send_roster_push(stream, '*****@*****.**', 'remove') q.expect_many( EventPattern('dbus-signal', interface=cs.CHANNEL_IFACE_GROUP, path=stored.object_path, signal='MembersChanged', args=['', [], [quux_handle], [], [], 0, 0]), EventPattern('dbus-signal', interface=cs.CONN_IFACE_CONTACT_LIST, path=conn.object_path, signal='ContactsChanged', args=[{}, [quux_handle]]), EventPattern('stream-iq', iq_id='push', iq_type='result'), ) acknowledge_iq(stream, event.stanza) if modern: q.expect('dbus-return', method='RemoveContacts')
def test_inital_roster(q, bus, conn, stream): """ This part of the test checks that Gabble correctly alters on which lists contacts appear based on the google:roster attributes and special-cases. """ event = q.expect('stream-iq', query_ns=ns.ROSTER) query = event.query assertContains('gr', query.localPrefixes) assertEquals(ns.GOOGLE_ROSTER, query.localPrefixes['gr']) # We support version 2 of Google's extensions. assertEquals('2', query[(ns.GOOGLE_ROSTER, 'ext')]) result = make_result_iq(stream, event.stanza) query = result.firstChildElement() add_gr_attributes(query) # Gabble suppresses contacts labelled as "hidden" from all roster channels. add_roster_item(query, '*****@*****.**', 'both', False, {'gr:t': 'H'}) # Gabble should hide contacts on the Google roster with subscription="none" # and ask!="subscribe", to hide contacts which are actually just email # addresses. (This is in line with Pidgin; the code there was added by Sean # Egan, who worked on Google Talk for Google at the time.) add_roster_item(query, '*****@*****.**', 'none', False) # This contact is remote pending, so we shouldn't suppress it. add_roster_item(query, '*****@*****.**', 'none', True) add_roster_item(query, '*****@*****.**', 'both', False, {'gr:autosub': 'true'}) # These contacts are blocked but we're subscribed to them, so they should # show up in all of the lists. add_roster_item(query, '*****@*****.**', 'both', False, {'gr:t': 'B'}) add_roster_item(query, '*****@*****.**', 'both', False, {'gr:t': 'B'}) # This contact is blocked, and we have no other subscription to them; so, # they should not show up in 'stored'. add_roster_item(query, '*****@*****.**', 'none', False, {'gr:t': 'B'}) # Send back the roster stream.send(result) # Since s-b-h had the "hidden" flag set, we don't expect them to be on any # lists. But we do want the "autosub" contact to be visible; see # <https://bugs.launchpad.net/ubuntu/+source/telepathy-gabble/+bug/398293>, # where Gabble was incorrectly hiding valid contacts. mutually_subscribed_contacts = ['*****@*****.**', '*****@*****.**', '*****@*****.**'] rp_contacts = ['*****@*****.**'] blocked_contacts = ['*****@*****.**', '*****@*****.**', '*****@*****.**'] pairs = expect_contact_list_signals(q, bus, conn, ['publish', 'subscribe', 'stored', 'deny']) publish = check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'publish', mutually_subscribed_contacts) subscribe = check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'subscribe', mutually_subscribed_contacts, rp_contacts=rp_contacts) stored = check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'stored', mutually_subscribed_contacts + rp_contacts) deny = check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'deny', blocked_contacts) assertLength(0, pairs) # i.e. we've checked all of them return (publish, subscribe, stored, deny)
def test(q, bus, conn, stream, modern=True, remove=False): call_async(q, conn.ContactList, 'GetContactListAttributes', [], False) q.expect('dbus-error', method='GetContactListAttributes', name=cs.NOT_YET) event = q.expect('stream-iq', query_ns=ns.ROSTER) item = event.query.addElement('item') item['jid'] = '*****@*****.**' item['subscription'] = 'both' event.stanza['type'] = 'result' stream.send(event.stanza) holly, dave, arnold, kristine, cat = conn.RequestHandles(cs.HT_CONTACT, ['*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**']) # slight implementation detail: TpBaseContactList emits ContactsChanged # before it announces its channels s = q.expect('dbus-signal', signal='ContactsChanged', interface=cs.CONN_IFACE_CONTACT_LIST, path=conn.object_path) assertEquals([{ holly: (cs.SUBSCRIPTION_STATE_YES, cs.SUBSCRIPTION_STATE_YES, ''), }, []], s.args) pairs = expect_contact_list_signals(q, bus, conn, ['publish', 'subscribe', 'stored']) # this is emitted last, so clients can tell when the initial state dump # has finished q.expect('dbus-signal', signal='ContactListStateChanged', args=[cs.CONTACT_LIST_STATE_SUCCESS]) call_async(q, conn.ContactList, 'GetContactListAttributes', [], False) r = q.expect('dbus-return', method='GetContactListAttributes') assertEquals(({ holly: { cs.CONN_IFACE_CONTACT_LIST + '/publish': cs.SUBSCRIPTION_STATE_YES, cs.CONN_IFACE_CONTACT_LIST + '/subscribe': cs.SUBSCRIPTION_STATE_YES, cs.CONN + '/contact-id': '*****@*****.**', } },), r.value) # check that the channels were as we expected too publish = check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'publish', ['*****@*****.**']) check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'subscribe', ['*****@*****.**']) stored = check_contact_list_signals(q, bus, conn, pairs.pop(0), cs.HT_LIST, 'stored', ['*****@*****.**']) assertLength(0, pairs) # i.e. we've checked all of them # publication authorized for Dave, Holly (the former is pre-authorization, # the latter is a no-op) if modern: call_async(q, conn.ContactList, 'AuthorizePublication', [dave, holly]) event = q.expect('dbus-return', method='AuthorizePublication') else: call_async(q, publish.Group, 'AddMembers', [dave, holly], '') event = q.expect('dbus-return', method='AddMembers') # Receive authorization requests from the contacts # We pre-authorized Dave, so this is automatically approved presence = domish.Element(('jabber:client', 'presence')) presence['type'] = 'subscribe' presence['from'] = '*****@*****.**' stream.send(presence) q.expect_many( EventPattern('dbus-signal', signal='ContactsChanged', args=[{dave: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_ASK, '')}, []]), EventPattern('stream-presence', presence_type='subscribed', to='*****@*****.**'), ) # Our server responds to Dave being authorized send_roster_push(stream, '*****@*****.**', 'from') q.expect_many( EventPattern('stream-iq', iq_type='result', iq_id='push'), EventPattern('dbus-signal', signal='ContactsChanged', args=[{dave: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_YES, '')}, []]), ) # The request from Kristine needs authorization (below) presence['from'] = '*****@*****.**' stream.send(presence) q.expect('dbus-signal', signal='ContactsChanged', args=[{kristine: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_ASK, '')}, []]) # This request from Arnold is dealt with below presence['from'] = '*****@*****.**' stream.send(presence) q.expect('dbus-signal', signal='ContactsChanged', args=[{arnold: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_ASK, '')}, []]) if modern: returning_method = 'AuthorizePublication' call_async(q, conn.ContactList, 'AuthorizePublication', [kristine, holly]) else: returning_method = 'AddMembers' call_async(q, publish.Group, 'AddMembers', [kristine, holly], '') q.expect_many( EventPattern('dbus-return', method=returning_method), EventPattern('stream-presence', presence_type='subscribed', to='*****@*****.**'), ) # Our server acknowledges that we authorized Kristine. Holly's state # does not change. send_roster_push(stream, '*****@*****.**', 'from') q.expect_many( EventPattern('dbus-signal', signal='ContactsChanged', args=[{kristine: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_YES, '')}, []]), EventPattern('stream-iq', iq_type='result', iq_id='push'), ) # Arnold gives up waiting for us, and cancels his request presence['from'] = '*****@*****.**' presence['type'] = 'unsubscribe' stream.send(presence) q.expect_many( EventPattern('dbus-signal', signal='ContactsChanged', args=[{arnold: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_REMOVED_REMOTELY, '')}, []]), EventPattern('stream-presence', presence_type='unsubscribed', to='*****@*****.**'), ) # We can acknowledge that with RemoveContacts or with Unpublish. # The old Chan.T.ContactList API can't acknowledge RemovedRemotely, # because it sees it as "not there at all" and the group logic drops # the "redundant" request. if remove: returning_method = 'RemoveContacts' call_async(q, conn.ContactList, 'RemoveContacts', [arnold]) else: returning_method = 'Unpublish' call_async(q, conn.ContactList, 'Unpublish', [arnold]) # Even if we Unpublish() here, Arnold was never on our XMPP roster, # so setting his publish state to SUBSCRIPTION_STATE_NO should result # in his removal. q.expect_many( EventPattern('dbus-return', method=returning_method), EventPattern('dbus-signal', signal='ContactsChanged', args=[{}, [arnold]]), ) # Rejecting an authorization request also works presence = domish.Element(('jabber:client', 'presence')) presence['type'] = 'subscribe' presence['from'] = '*****@*****.**' stream.send(presence) q.expect('dbus-signal', signal='ContactsChanged', args=[{cat: (cs.SUBSCRIPTION_STATE_NO, cs.SUBSCRIPTION_STATE_ASK, '')}, []]) if modern: if remove: returning_method = 'RemoveContacts' call_async(q, conn.ContactList, 'RemoveContacts', [cat]) else: returning_method = 'Unpublish' call_async(q, conn.ContactList, 'Unpublish', [cat]) else: returning_method = 'RemoveMembers' if remove: call_async(q, stored.Group, 'RemoveMembers', [cat], '') else: call_async(q, publish.Group, 'RemoveMembers', [cat], '') # As above, the only reason the Cat is on our contact list is the pending # publish request, so Unpublish really results in removal. q.expect_many( EventPattern('dbus-return', method=returning_method), EventPattern('dbus-signal', signal='ContactsChanged', args=[{}, [cat]]), ) # Redundant API calls (removing an absent contact, etc.) cause no network # traffic, and succeed. forbidden = [EventPattern('stream-iq', query_ns=ns.ROSTER), EventPattern('stream-presence')] sync_stream(q, stream) sync_dbus(bus, q, conn) q.forbid_events(forbidden) call_async(q, conn.ContactList, 'AuthorizePublication', [kristine, holly, dave]) call_async(q, conn.ContactList, 'Unpublish', [arnold, cat]) call_async(q, conn.ContactList, 'RemoveContacts', [arnold, cat]) q.expect_many( EventPattern('dbus-return', method='AuthorizePublication'), EventPattern('dbus-return', method='Unpublish'), EventPattern('dbus-return', method='RemoveContacts'), ) sync_stream(q, stream) sync_dbus(bus, q, conn) q.unforbid_events(forbidden) # There's one more case: revoking the publish permission of someone who is # genuinely on the roster. if modern: if remove: returning_method = 'RemoveContacts' call_async(q, conn.ContactList, 'RemoveContacts', [holly]) else: returning_method = 'Unpublish' call_async(q, conn.ContactList, 'Unpublish', [holly]) else: returning_method = 'RemoveMembers' if remove: call_async(q, stored.Group, 'RemoveMembers', [holly], '') else: call_async(q, publish.Group, 'RemoveMembers', [holly], '') if remove: iq = q.expect('stream-iq', iq_type='set', query_ns=ns.ROSTER, query_name='query') acknowledge_iq(stream, iq.stanza) if modern: q.expect('dbus-return', method='RemoveContacts') # FIXME: when we depend on a new enough tp-glib, expect RemoveMembers # to return here too send_roster_push(stream, '*****@*****.**', 'remove') q.expect_many( EventPattern('stream-iq', iq_type='result', iq_id='push'), EventPattern('dbus-signal', signal='ContactsChanged', args=[{}, [holly]]), ) else: q.expect_many( EventPattern('dbus-return', method=returning_method), EventPattern('stream-presence', presence_type='unsubscribed', to='*****@*****.**'), ) send_roster_push(stream, '*****@*****.**', 'to') q.expect_many( EventPattern('stream-iq', iq_type='result', iq_id='push'), EventPattern('dbus-signal', signal='ContactsChanged', args=[{holly: (cs.SUBSCRIPTION_STATE_YES, cs.SUBSCRIPTION_STATE_NO, ''), }, []]), )