Philipp Hörist pushed to branch master at gajim / python-nbxmpp

Commits:

7 changed files:

Changes:

  • nbxmpp/modules/activity.py
    ... ... @@ -22,6 +22,8 @@ from nbxmpp.structs import StanzaHandler
    22 22
     from nbxmpp.structs import ActivityData
    
    23 23
     from nbxmpp.const import ACTIVITIES
    
    24 24
     from nbxmpp.modules.base import BaseModule
    
    25
    +from nbxmpp.modules.util import finalize
    
    26
    +from nbxmpp.task import iq_request_task
    
    25 27
     
    
    26 28
     
    
    27 29
     class Activity(BaseModule):
    
    ... ... @@ -87,7 +89,10 @@ class Activity(BaseModule):
    87 89
                     return sub.getName()
    
    88 90
             return None
    
    89 91
     
    
    92
    +    @iq_request_task
    
    90 93
         def set_activity(self, data):
    
    94
    +        task = yield
    
    95
    +
    
    91 96
             item = Node('activity', {'xmlns': Namespace.ACTIVITY})
    
    92 97
             if data is not None and data.activity:
    
    93 98
                 activity_node = item.addChild(data.activity)
    
    ... ... @@ -96,4 +101,6 @@ class Activity(BaseModule):
    96 101
                 if data.text:
    
    97 102
                     item.addChild('text', payload=data.text)
    
    98 103
     
    
    99
    -        self.publish(Namespace.ACTIVITY, item, id_='current')
    104
    +        result = yield self.publish(Namespace.ACTIVITY, item, id_='current')
    
    105
    +
    
    106
    +        yield finalize(task, result)

  • nbxmpp/modules/location.py
    ... ... @@ -21,6 +21,8 @@ from nbxmpp.structs import StanzaHandler
    21 21
     from nbxmpp.structs import LocationData
    
    22 22
     from nbxmpp.const import LOCATION_DATA
    
    23 23
     from nbxmpp.modules.base import BaseModule
    
    24
    +from nbxmpp.modules.util import finalize
    
    25
    +from nbxmpp.task import iq_request_task
    
    24 26
     
    
    25 27
     
    
    26 28
     class Location(BaseModule):
    
    ... ... @@ -67,7 +69,10 @@ class Location(BaseModule):
    67 69
     
    
    68 70
             properties.pubsub_event = pubsub_event
    
    69 71
     
    
    72
    +    @iq_request_task
    
    70 73
         def set_location(self, data):
    
    74
    +        task = yield
    
    75
    +
    
    71 76
             item = Node('geoloc', {'xmlns': Namespace.LOCATION})
    
    72 77
             if data is not None:
    
    73 78
                 data = data._asdict()
    
    ... ... @@ -75,4 +80,6 @@ class Location(BaseModule):
    75 80
                     if value is not None:
    
    76 81
                         item.addChild(tag, payload=value)
    
    77 82
     
    
    78
    -        self.publish(Namespace.LOCATION, item, id_='current')
    83
    +        result = yield self.publish(Namespace.LOCATION, item, id_='current')
    
    84
    +
    
    85
    +        yield finalize(task, result)

  • nbxmpp/modules/mood.py
    ... ... @@ -22,6 +22,8 @@ from nbxmpp.structs import StanzaHandler
    22 22
     from nbxmpp.structs import MoodData
    
    23 23
     from nbxmpp.const import MOODS
    
    24 24
     from nbxmpp.modules.base import BaseModule
    
    25
    +from nbxmpp.modules.util import finalize
    
    26
    +from nbxmpp.task import iq_request_task
    
    25 27
     
    
    26 28
     
    
    27 29
     class Mood(BaseModule):
    
    ... ... @@ -77,7 +79,10 @@ class Mood(BaseModule):
    77 79
     
    
    78 80
             properties.pubsub_event = pubsub_event
    
    79 81
     
    
    82
    +    @iq_request_task
    
    80 83
         def set_mood(self, data):
    
    84
    +        task = yield
    
    85
    +
    
    81 86
             item = Node('mood', {'xmlns': Namespace.MOOD})
    
    82 87
             if data is not None and data.mood:
    
    83 88
                 item.addChild(data.mood)
    
    ... ... @@ -85,4 +90,6 @@ class Mood(BaseModule):
    85 90
                 if data.text:
    
    86 91
                     item.addChild('text', payload=data.text)
    
    87 92
     
    
    88
    -        self.publish(Namespace.MOOD, item, id_='current')
    93
    +        result = yield self.publish(Namespace.MOOD, item, id_='current')
    
    94
    +
    
    95
    +        yield finalize(task, result)

  • nbxmpp/modules/nickname.py
    ... ... @@ -20,6 +20,8 @@ from nbxmpp.protocol import Node
    20 20
     from nbxmpp.structs import StanzaHandler
    
    21 21
     from nbxmpp.const import PresenceType
    
    22 22
     from nbxmpp.modules.base import BaseModule
    
    23
    +from nbxmpp.modules.util import finalize
    
    24
    +from nbxmpp.task import iq_request_task
    
    23 25
     
    
    24 26
     
    
    25 27
     class Nickname(BaseModule):
    
    ... ... @@ -88,9 +90,14 @@ class Nickname(BaseModule):
    88 90
                 return None
    
    89 91
             return nickname.getData() or None
    
    90 92
     
    
    93
    +    @iq_request_task
    
    91 94
         def set_nickname(self, nickname):
    
    95
    +        task = yield
    
    96
    +
    
    92 97
             item = Node('nick', {'xmlns': Namespace.NICK})
    
    93 98
             if nickname is not None:
    
    94 99
                 item.addData(nickname)
    
    95 100
     
    
    96
    -        self.publish(Namespace.NICK, item, id_='current')
    101
    +        result = yield self.publish(Namespace.NICK, item, id_='current')
    
    102
    +
    
    103
    +        yield finalize(task, result)

  • nbxmpp/modules/omemo.py
    ... ... @@ -19,25 +19,24 @@ from nbxmpp.namespaces import Namespace
    19 19
     from nbxmpp.protocol import NodeProcessed
    
    20 20
     from nbxmpp.protocol import Node
    
    21 21
     from nbxmpp.protocol import Message
    
    22
    -from nbxmpp.protocol import isResultNode
    
    23
    -from nbxmpp.protocol import StanzaMalformed
    
    24
    -from nbxmpp.util import call_on_response
    
    25
    -from nbxmpp.util import callback
    
    26 22
     from nbxmpp.util import b64decode
    
    27 23
     from nbxmpp.util import b64encode
    
    28
    -from nbxmpp.util import raise_error
    
    29 24
     from nbxmpp.util import from_xs_boolean
    
    30 25
     from nbxmpp.structs import StanzaHandler
    
    31 26
     from nbxmpp.structs import OMEMOMessage
    
    32 27
     from nbxmpp.structs import OMEMOBundle
    
    33
    -from nbxmpp.modules.pubsub import get_pubsub_request
    
    34 28
     from nbxmpp.modules.base import BaseModule
    
    29
    +from nbxmpp.modules.util import finalize
    
    30
    +from nbxmpp.modules.util import raise_if_error
    
    31
    +from nbxmpp.task import iq_request_task
    
    32
    +from nbxmpp.errors import MalformedStanzaError
    
    35 33
     
    
    36 34
     
    
    37 35
     class OMEMO(BaseModule):
    
    38 36
     
    
    39 37
         _depends = {
    
    40
    -        'publish': 'PubSub'
    
    38
    +        'publish': 'PubSub',
    
    39
    +        'request_items': 'PubSub',
    
    41 40
         }
    
    42 41
     
    
    43 42
         def __init__(self, client):
    
    ... ... @@ -57,82 +56,13 @@ class OMEMO(BaseModule):
    57 56
     
    
    58 57
         def _process_omemo_message(self, _client, stanza, properties):
    
    59 58
             try:
    
    60
    -            properties.omemo = self._parse_omemo_message(stanza)
    
    59
    +            properties.omemo = _parse_omemo_message(stanza)
    
    61 60
                 self._log.info('Received message')
    
    62
    -        except StanzaMalformed as error:
    
    61
    +        except MalformedStanzaError as error:
    
    63 62
                 self._log.warning(error)
    
    64 63
                 self._log.warning(stanza)
    
    65 64
                 return
    
    66 65
     
    
    67
    -    @staticmethod
    
    68
    -    def _parse_omemo_message(stanza):
    
    69
    -        '''
    
    70
    -        <message>
    
    71
    -          <encrypted xmlns='eu.siacs.conversations.axolotl'>
    
    72
    -            <header sid='27183'>
    
    73
    -              <key rid='31415'>BASE64ENCODED...</key>
    
    74
    -              <key prekey="true" rid='12321'>BASE64ENCODED...</key>
    
    75
    -              <!-- ... -->
    
    76
    -              <iv>BASE64ENCODED...</iv>
    
    77
    -            </header>
    
    78
    -            <payload>BASE64ENCODED</payload>
    
    79
    -          </encrypted>
    
    80
    -          <store xmlns='urn:xmpp:hints'/>
    
    81
    -        </message>
    
    82
    -        '''
    
    83
    -        encrypted = stanza.getTag('encrypted', namespace=Namespace.OMEMO_TEMP)
    
    84
    -        if encrypted is None:
    
    85
    -            raise StanzaMalformed('No encrypted node found')
    
    86
    -
    
    87
    -        header = encrypted.getTag('header')
    
    88
    -        if header is None:
    
    89
    -            raise StanzaMalformed('header node not found')
    
    90
    -
    
    91
    -        try:
    
    92
    -            sid = int(header.getAttr('sid'))
    
    93
    -        except Exception as error:
    
    94
    -            raise StanzaMalformed('sid attr not found')
    
    95
    -
    
    96
    -        iv_node = header.getTag('iv')
    
    97
    -        try:
    
    98
    -            iv = b64decode(iv_node.getData(), bytes)
    
    99
    -        except Exception as error:
    
    100
    -            raise StanzaMalformed('failed to decode iv: %s' % error)
    
    101
    -
    
    102
    -        payload = None
    
    103
    -        payload_node = encrypted.getTag('payload')
    
    104
    -        if payload_node is not None:
    
    105
    -            try:
    
    106
    -                payload = b64decode(payload_node.getData(), bytes)
    
    107
    -            except Exception as error:
    
    108
    -                raise StanzaMalformed('failed to decode payload: %s' % error)
    
    109
    -
    
    110
    -        key_nodes = header.getTags('key')
    
    111
    -        if not key_nodes:
    
    112
    -            raise StanzaMalformed('no keys found')
    
    113
    -
    
    114
    -        keys = {}
    
    115
    -        for kn in key_nodes:
    
    116
    -            rid = kn.getAttr('rid')
    
    117
    -            if rid is None:
    
    118
    -                raise StanzaMalformed('rid not found')
    
    119
    -
    
    120
    -            prekey = kn.getAttr('prekey')
    
    121
    -            if prekey is None:
    
    122
    -                prekey = False
    
    123
    -            else:
    
    124
    -                try:
    
    125
    -                    prekey = from_xs_boolean(prekey)
    
    126
    -                except ValueError as error:
    
    127
    -                    raise StanzaMalformed(error)
    
    128
    -
    
    129
    -            try:
    
    130
    -                keys[int(rid)] = (b64decode(kn.getData(), bytes), prekey)
    
    131
    -            except Exception as error:
    
    132
    -                raise StanzaMalformed('failed to decode key: %s' % error)
    
    133
    -
    
    134
    -        return OMEMOMessage(sid=sid, iv=iv, keys=keys, payload=payload)
    
    135
    -
    
    136 66
         def _process_omemo_devicelist(self, _client, stanza, properties):
    
    137 67
             if not properties.is_pubsub_event:
    
    138 68
                 return
    
    ... ... @@ -146,8 +76,8 @@ class OMEMO(BaseModule):
    146 76
                 return
    
    147 77
     
    
    148 78
             try:
    
    149
    -            devices = self._parse_devicelist(item)
    
    150
    -        except StanzaMalformed as error:
    
    79
    +            devices = _parse_devicelist(item)
    
    80
    +        except MalformedStanzaError as error:
    
    151 81
                 self._log.warning(error)
    
    152 82
                 self._log.warning(stanza)
    
    153 83
                 raise NodeProcessed
    
    ... ... @@ -163,232 +93,328 @@ class OMEMO(BaseModule):
    163 93
     
    
    164 94
             properties.pubsub_event = pubsub_event
    
    165 95
     
    
    166
    -    @staticmethod
    
    167
    -    def _parse_devicelist(item):
    
    168
    -        '''
    
    169
    -        <items node='eu.siacs.conversations.axolotl.devicelist'>
    
    170
    -          <item id='current'>
    
    171
    -            <list xmlns='eu.siacs.conversations.axolotl'>
    
    172
    -              <device id='12345' />
    
    173
    -              <device id='4223' />
    
    174
    -            </list>
    
    175
    -          </item>
    
    176
    -        </items>
    
    177
    -        '''
    
    178
    -        list_node = item.getTag('list', namespace=Namespace.OMEMO_TEMP)
    
    179
    -        if list_node is None:
    
    180
    -            raise StanzaMalformed('No list node found')
    
    181
    -
    
    182
    -        if not list_node.getChildren():
    
    183
    -            return []
    
    184
    -
    
    185
    -        result = []
    
    186
    -        devices_nodes = list_node.getChildren()
    
    187
    -        for dn in devices_nodes:
    
    188
    -            _id = dn.getAttr('id')
    
    189
    -            if _id:
    
    190
    -                result.append(int(_id))
    
    191
    -
    
    192
    -        return result
    
    193
    -
    
    194
    -    def set_devicelist(self, devicelist=None):
    
    195
    -        item = Node('list', attrs={'xmlns': Namespace.OMEMO_TEMP})
    
    196
    -        for device in devicelist:
    
    197
    -            item.addChild('device').setAttr('id', device)
    
    96
    +    @iq_request_task
    
    97
    +    def set_devicelist(self, devicelist=None, public=True):
    
    98
    +        task = yield
    
    198 99
     
    
    199 100
             self._log.info('Set devicelist: %s', devicelist)
    
    200 101
     
    
    201
    -        self.publish(Namespace.OMEMO_TEMP_DL, item, id_='current')
    
    102
    +        access_model = 'open' if public else 'presence'
    
    103
    +
    
    104
    +        options = {
    
    105
    +            'pubsub#persist_items': 'true',
    
    106
    +            'pubsub#access_model': access_model,
    
    107
    +        }
    
    108
    +
    
    109
    +        result = yield self.publish(Namespace.OMEMO_TEMP_DL,
    
    110
    +                                    _make_devicelist(devicelist),
    
    111
    +                                    id_='current',
    
    112
    +                                    options=options,
    
    113
    +                                    force_node_options=True)
    
    202 114
     
    
    203
    -    @call_on_response('_devicelist_received')
    
    115
    +        yield finalize(task, result)
    
    116
    +
    
    117
    +    @iq_request_task
    
    204 118
         def request_devicelist(self, jid=None):
    
    205
    -        if jid is None:
    
    206
    -            jid = self._client.get_bound_jid().bare
    
    207
    -        self._log.info('Request devicelist from: %s', jid)
    
    208
    -        return get_pubsub_request(jid, Namespace.OMEMO_TEMP_DL, max_items=1)
    
    209
    -
    
    210
    -    @callback
    
    211
    -    def _devicelist_received(self, stanza):
    
    212
    -        if not isResultNode(stanza):
    
    213
    -            return raise_error(self._log.info, stanza)
    
    214
    -
    
    215
    -        pubsub_node = stanza.getTag('pubsub')
    
    216
    -        items_node = pubsub_node.getTag('items')
    
    217
    -        item = items_node.getTag('item')
    
    218
    -        if item is None:
    
    219
    -            return []
    
    119
    +        task = yield
    
    220 120
     
    
    221
    -        try:
    
    222
    -            return self._parse_devicelist(item)
    
    223
    -        except StanzaMalformed as error:
    
    224
    -            return raise_error(self._log.warning, stanza,
    
    225
    -                               'stanza-malformed', str(error))
    
    121
    +        items = yield self.request_items(Namespace.OMEMO_TEMP_DL,
    
    122
    +                                         max_items=1,
    
    123
    +                                         jid=jid)
    
    124
    +
    
    125
    +        raise_if_error(items)
    
    126
    +
    
    127
    +        if items is None:
    
    128
    +            yield task.set_result(None)
    
    129
    +
    
    130
    +        yield _parse_devicelist(items[0])
    
    131
    +
    
    132
    +    @iq_request_task
    
    133
    +    def set_bundle(self, bundle, device_id, public=True):
    
    134
    +        task = yield
    
    226 135
     
    
    227
    -    def set_bundle(self, bundle, device_id):
    
    228
    -        item = self._create_bundle(bundle)
    
    229 136
             self._log.info('Set bundle')
    
    230 137
     
    
    231
    -        node = '%s:%s' % (Namespace.OMEMO_TEMP_BUNDLE, device_id)
    
    232
    -
    
    233
    -        self.publish(node, item, id_='current')
    
    234
    -
    
    235
    -    @staticmethod
    
    236
    -    def _create_bundle(bundle):
    
    237
    -        '''
    
    238
    -        <publish node='eu.siacs.conversations.axolotl.bundles:31415'>
    
    239
    -          <item id='current'>
    
    240
    -            <bundle xmlns='eu.siacs.conversations.axolotl'>
    
    241
    -              <signedPreKeyPublic signedPreKeyId='1'>
    
    242
    -                BASE64ENCODED...
    
    243
    -              </signedPreKeyPublic>
    
    244
    -              <signedPreKeySignature>
    
    245
    -                BASE64ENCODED...
    
    246
    -              </signedPreKeySignature>
    
    247
    -              <identityKey>
    
    248
    -                BASE64ENCODED...
    
    249
    -              </identityKey>
    
    250
    -              <prekeys>
    
    251
    -                <preKeyPublic preKeyId='1'>
    
    252
    -                  BASE64ENCODED...
    
    253
    -                </preKeyPublic>
    
    254
    -                <preKeyPublic preKeyId='2'>
    
    255
    -                  BASE64ENCODED...
    
    256
    -                </preKeyPublic>
    
    257
    -                <preKeyPublic preKeyId='3'>
    
    258
    -                  BASE64ENCODED...
    
    259
    -                </preKeyPublic>
    
    260
    -                <!-- ... -->
    
    261
    -              </prekeys>
    
    262
    -            </bundle>
    
    263
    -          </item>
    
    264
    -        </publish>
    
    265
    -        '''
    
    266
    -        bundle_node = Node('bundle', attrs={'xmlns': Namespace.OMEMO_TEMP})
    
    267
    -        prekey_pub_node = bundle_node.addChild(
    
    268
    -            'signedPreKeyPublic',
    
    269
    -            attrs={'signedPreKeyId': bundle.spk['id']})
    
    270
    -        prekey_pub_node.addData(b64encode(bundle.spk['key']))
    
    271
    -
    
    272
    -        prekey_sig_node = bundle_node.addChild('signedPreKeySignature')
    
    273
    -        prekey_sig_node.addData(b64encode(bundle.spk_signature))
    
    274
    -
    
    275
    -        identity_key_node = bundle_node.addChild('identityKey')
    
    276
    -        identity_key_node.addData(b64encode(bundle.ik))
    
    277
    -
    
    278
    -        prekeys = bundle_node.addChild('prekeys')
    
    279
    -        for key in bundle.otpks:
    
    280
    -            pre_key_public = prekeys.addChild('preKeyPublic',
    
    281
    -                                              attrs={'preKeyId': key['id']})
    
    282
    -            pre_key_public.addData(b64encode(key['key']))
    
    283
    -        return bundle_node
    
    284
    -
    
    285
    -    @call_on_response('_bundle_received')
    
    138
    +        access_model = 'open' if public else 'presence'
    
    139
    +
    
    140
    +        options = {
    
    141
    +            'pubsub#persist_items': 'true',
    
    142
    +            'pubsub#access_model': access_model,
    
    143
    +        }
    
    144
    +
    
    145
    +        result = yield self.publish(
    
    146
    +            f'{Namespace.OMEMO_TEMP_BUNDLE}:{device_id}',
    
    147
    +            _make_bundle(bundle),
    
    148
    +            id_='current',
    
    149
    +            options=options,
    
    150
    +            force_node_options=True)
    
    151
    +
    
    152
    +        yield finalize(task, result)
    
    153
    +
    
    154
    +    @iq_request_task
    
    286 155
         def request_bundle(self, jid, device_id):
    
    156
    +        task = yield
    
    157
    +
    
    287 158
             self._log.info('Request bundle from: %s %s', jid, device_id)
    
    288
    -        node = '%s:%s' % (Namespace.OMEMO_TEMP_BUNDLE, device_id)
    
    289
    -        return get_pubsub_request(jid, node, max_items=1)
    
    290 159
     
    
    291
    -    @callback
    
    292
    -    def _bundle_received(self, stanza):
    
    293
    -        if not isResultNode(stanza):
    
    294
    -            return raise_error(self._log.info, stanza)
    
    160
    +        items = yield self.request_items(
    
    161
    +            f'{Namespace.OMEMO_TEMP_BUNDLE}:{device_id}',
    
    162
    +            max_items=1,
    
    163
    +            jid=jid)
    
    295 164
     
    
    296
    -        pubsub_node = stanza.getTag('pubsub')
    
    297
    -        items_node = pubsub_node.getTag('items')
    
    298
    -        item = items_node.getTag('item')
    
    165
    +        raise_if_error(items)
    
    299 166
     
    
    300
    -        try:
    
    301
    -            return self._parse_bundle(item)
    
    302
    -        except StanzaMalformed as error:
    
    303
    -            return raise_error(self._log.warning, stanza,
    
    304
    -                               'stanza-malformed', str(error))
    
    305
    -
    
    306
    -    @staticmethod
    
    307
    -    def _parse_bundle(item):
    
    308
    -        '''
    
    309
    -        <item id='current'>
    
    310
    -          <bundle xmlns='eu.siacs.conversations.axolotl'>
    
    311
    -            <signedPreKeyPublic signedPreKeyId='1'>
    
    312
    -              BASE64ENCODED...
    
    313
    -            </signedPreKeyPublic>
    
    314
    -            <signedPreKeySignature>
    
    315
    -              BASE64ENCODED...
    
    316
    -            </signedPreKeySignature>
    
    317
    -            <identityKey>
    
    318
    -              BASE64ENCODED...
    
    319
    -            </identityKey>
    
    320
    -            <prekeys>
    
    321
    -              <preKeyPublic preKeyId='1'>
    
    322
    -                BASE64ENCODED...
    
    323
    -              </preKeyPublic>
    
    324
    -              <preKeyPublic preKeyId='2'>
    
    325
    -               BASE64ENCODED...
    
    326
    -              </preKeyPublic>
    
    327
    -              <preKeyPublic preKeyId='3'>
    
    328
    -                BASE64ENCODED...
    
    329
    -              </preKeyPublic>
    
    330
    -              <!-- ... -->
    
    331
    -            </prekeys>
    
    332
    -          </bundle>
    
    333
    -        </item>
    
    334
    -        '''
    
    335
    -        if item is None:
    
    336
    -            raise StanzaMalformed('No item in node found')
    
    167
    +        if items is None:
    
    168
    +            yield task.set_result(None)
    
    169
    +
    
    170
    +        yield _parse_bundle(items[0])
    
    337 171
     
    
    338
    -        bundle = item.getTag('bundle', namespace=Namespace.OMEMO_TEMP)
    
    339
    -        if bundle is None:
    
    340
    -            raise StanzaMalformed('No bundle node found')
    
    341 172
     
    
    342
    -        result = {}
    
    343
    -        signed_prekey_node = bundle.getTag('signedPreKeyPublic')
    
    173
    +def _parse_omemo_message(stanza):
    
    174
    +    '''
    
    175
    +    <message>
    
    176
    +      <encrypted xmlns='eu.siacs.conversations.axolotl'>
    
    177
    +        <header sid='27183'>
    
    178
    +          <key rid='31415'>BASE64ENCODED...</key>
    
    179
    +          <key prekey="true" rid='12321'>BASE64ENCODED...</key>
    
    180
    +          <!-- ... -->
    
    181
    +          <iv>BASE64ENCODED...</iv>
    
    182
    +        </header>
    
    183
    +        <payload>BASE64ENCODED</payload>
    
    184
    +      </encrypted>
    
    185
    +      <store xmlns='urn:xmpp:hints'/>
    
    186
    +    </message>
    
    187
    +    '''
    
    188
    +    encrypted = stanza.getTag('encrypted', namespace=Namespace.OMEMO_TEMP)
    
    189
    +    if encrypted is None:
    
    190
    +        raise MalformedStanzaError('No encrypted node found', stanza)
    
    191
    +
    
    192
    +    header = encrypted.getTag('header')
    
    193
    +    if header is None:
    
    194
    +        raise MalformedStanzaError('header node not found', stanza)
    
    195
    +
    
    196
    +    try:
    
    197
    +        sid = int(header.getAttr('sid'))
    
    198
    +    except Exception as error:
    
    199
    +        raise MalformedStanzaError('sid attr not found', stanza)
    
    200
    +
    
    201
    +    iv_node = header.getTag('iv')
    
    202
    +    try:
    
    203
    +        iv = b64decode(iv_node.getData(), bytes)
    
    204
    +    except Exception as error:
    
    205
    +        raise MalformedStanzaError('failed to decode iv: %s' % error, stanza)
    
    206
    +
    
    207
    +    payload = None
    
    208
    +    payload_node = encrypted.getTag('payload')
    
    209
    +    if payload_node is not None:
    
    344 210
             try:
    
    345
    -            result['spk'] = {'key': b64decode(signed_prekey_node.getData(),
    
    346
    -                                              bytes)}
    
    211
    +            payload = b64decode(payload_node.getData(), bytes)
    
    347 212
             except Exception as error:
    
    348
    -            raise StanzaMalformed('Failed to decode '
    
    349
    -                                  'signedPreKeyPublic: %s' % error)
    
    213
    +            raise MalformedStanzaError('failed to decode payload: %s' % error,
    
    214
    +                                       stanza)
    
    215
    +
    
    216
    +    key_nodes = header.getTags('key')
    
    217
    +    if not key_nodes:
    
    218
    +        raise MalformedStanzaError('no keys found', stanza)
    
    219
    +
    
    220
    +    keys = {}
    
    221
    +    for kn in key_nodes:
    
    222
    +        rid = kn.getAttr('rid')
    
    223
    +        if rid is None:
    
    224
    +            raise MalformedStanzaError('rid not found', stanza)
    
    225
    +
    
    226
    +        prekey = kn.getAttr('prekey')
    
    227
    +        if prekey is None:
    
    228
    +            prekey = False
    
    229
    +        else:
    
    230
    +            try:
    
    231
    +                prekey = from_xs_boolean(prekey)
    
    232
    +            except ValueError as error:
    
    233
    +                raise MalformedStanzaError(error, stanza)
    
    350 234
     
    
    351
    -        signed_prekey_id = signed_prekey_node.getAttr('signedPreKeyId')
    
    352 235
             try:
    
    353
    -            result['spk']['id'] = int(signed_prekey_id)
    
    236
    +            keys[int(rid)] = (b64decode(kn.getData(), bytes), prekey)
    
    354 237
             except Exception as error:
    
    355
    -            raise StanzaMalformed('Invalid signedPreKeyId: %s' % error)
    
    238
    +            raise MalformedStanzaError('failed to decode key: %s' % error,
    
    239
    +                                       stanza)
    
    240
    +
    
    241
    +    return OMEMOMessage(sid=sid, iv=iv, keys=keys, payload=payload)
    
    356 242
     
    
    357
    -        signed_signature_node = bundle.getTag('signedPreKeySignature')
    
    243
    +
    
    244
    +def _parse_bundle(item):
    
    245
    +    '''
    
    246
    +    <item id='current'>
    
    247
    +      <bundle xmlns='eu.siacs.conversations.axolotl'>
    
    248
    +        <signedPreKeyPublic signedPreKeyId='1'>
    
    249
    +          BASE64ENCODED...
    
    250
    +        </signedPreKeyPublic>
    
    251
    +        <signedPreKeySignature>
    
    252
    +          BASE64ENCODED...
    
    253
    +        </signedPreKeySignature>
    
    254
    +        <identityKey>
    
    255
    +          BASE64ENCODED...
    
    256
    +        </identityKey>
    
    257
    +        <prekeys>
    
    258
    +          <preKeyPublic preKeyId='1'>
    
    259
    +            BASE64ENCODED...
    
    260
    +          </preKeyPublic>
    
    261
    +          <preKeyPublic preKeyId='2'>
    
    262
    +           BASE64ENCODED...
    
    263
    +          </preKeyPublic>
    
    264
    +          <preKeyPublic preKeyId='3'>
    
    265
    +            BASE64ENCODED...
    
    266
    +          </preKeyPublic>
    
    267
    +          <!-- ... -->
    
    268
    +        </prekeys>
    
    269
    +      </bundle>
    
    270
    +    </item>
    
    271
    +    '''
    
    272
    +    if item is None:
    
    273
    +        raise MalformedStanzaError('No item in node found', item)
    
    274
    +
    
    275
    +    bundle = item.getTag('bundle', namespace=Namespace.OMEMO_TEMP)
    
    276
    +    if bundle is None:
    
    277
    +        raise MalformedStanzaError('No bundle node found', item)
    
    278
    +
    
    279
    +    result = {}
    
    280
    +    signed_prekey_node = bundle.getTag('signedPreKeyPublic')
    
    281
    +    try:
    
    282
    +        result['spk'] = {'key': b64decode(signed_prekey_node.getData(),
    
    283
    +                                          bytes)}
    
    284
    +    except Exception as error:
    
    285
    +        error = 'Failed to decode signedPreKeyPublic: %s' % error
    
    286
    +        raise MalformedStanzaError(error, item)
    
    287
    +
    
    288
    +    signed_prekey_id = signed_prekey_node.getAttr('signedPreKeyId')
    
    289
    +    try:
    
    290
    +        result['spk']['id'] = int(signed_prekey_id)
    
    291
    +    except Exception as error:
    
    292
    +        raise MalformedStanzaError('Invalid signedPreKeyId: %s' % error, item)
    
    293
    +
    
    294
    +    signed_signature_node = bundle.getTag('signedPreKeySignature')
    
    295
    +    try:
    
    296
    +        result['spk_signature'] = b64decode(signed_signature_node.getData(),
    
    297
    +                                            bytes)
    
    298
    +    except Exception as error:
    
    299
    +        error = 'Failed to decode signedPreKeySignature: %s' % error
    
    300
    +        raise MalformedStanzaError(error, item)
    
    301
    +
    
    302
    +    identity_key_node = bundle.getTag('identityKey')
    
    303
    +    try:
    
    304
    +        result['ik'] = b64decode(identity_key_node.getData(), bytes)
    
    305
    +    except Exception as error:
    
    306
    +        error = 'Failed to decode IdentityKey: %s' % error
    
    307
    +        raise MalformedStanzaError(error, item)
    
    308
    +
    
    309
    +    prekeys = bundle.getTag('prekeys')
    
    310
    +    if prekeys is None or not prekeys.getChildren():
    
    311
    +        raise MalformedStanzaError('No prekeys node found', item)
    
    312
    +
    
    313
    +    result['otpks'] = []
    
    314
    +    for prekey in prekeys.getChildren():
    
    358 315
             try:
    
    359
    -            result['spk_signature'] = b64decode(signed_signature_node.getData(),
    
    360
    -                                                bytes)
    
    316
    +            id_ = int(prekey.getAttr('preKeyId'))
    
    361 317
             except Exception as error:
    
    362
    -            raise StanzaMalformed('Failed to decode '
    
    363
    -                                  'signedPreKeySignature: %s' % error)
    
    318
    +            raise MalformedStanzaError('Invalid prekey: %s' % error, item)
    
    364 319
     
    
    365
    -        identity_key_node = bundle.getTag('identityKey')
    
    366 320
             try:
    
    367
    -            result['ik'] = b64decode(identity_key_node.getData(), bytes)
    
    321
    +            key = b64decode(prekey.getData(), bytes)
    
    368 322
             except Exception as error:
    
    369
    -            raise StanzaMalformed('Failed to decode '
    
    370
    -                                  'signedPreKeySignature: %s' % error)
    
    323
    +            raise MalformedStanzaError(
    
    324
    +                'Failed to decode preKeyPublic: %s' % error, item)
    
    371 325
     
    
    372
    -        prekeys = bundle.getTag('prekeys')
    
    373
    -        if prekeys is None or not prekeys.getChildren():
    
    374
    -            raise StanzaMalformed('No prekeys node found')
    
    326
    +        result['otpks'].append({'key': key, 'id': id_})
    
    375 327
     
    
    376
    -        result['otpks'] = []
    
    377
    -        for prekey in prekeys.getChildren():
    
    378
    -            try:
    
    379
    -                id_ = int(prekey.getAttr('preKeyId'))
    
    380
    -            except Exception as error:
    
    381
    -                raise StanzaMalformed('Invalid prekey: %s' % error)
    
    328
    +    return OMEMOBundle(**result)
    
    382 329
     
    
    383
    -            try:
    
    384
    -                key = b64decode(prekey.getData(), bytes)
    
    385
    -            except Exception as error:
    
    386
    -                raise StanzaMalformed(
    
    387
    -                    'Failed to decode preKeyPublic: %s' % error)
    
    388 330
     
    
    389
    -            result['otpks'].append({'key': key, 'id': id_})
    
    331
    +def _make_bundle(bundle):
    
    332
    +    '''
    
    333
    +    <publish node='eu.siacs.conversations.axolotl.bundles:31415'>
    
    334
    +      <item id='current'>
    
    335
    +        <bundle xmlns='eu.siacs.conversations.axolotl'>
    
    336
    +          <signedPreKeyPublic signedPreKeyId='1'>
    
    337
    +            BASE64ENCODED...
    
    338
    +          </signedPreKeyPublic>
    
    339
    +          <signedPreKeySignature>
    
    340
    +            BASE64ENCODED...
    
    341
    +          </signedPreKeySignature>
    
    342
    +          <identityKey>
    
    343
    +            BASE64ENCODED...
    
    344
    +          </identityKey>
    
    345
    +          <prekeys>
    
    346
    +            <preKeyPublic preKeyId='1'>
    
    347
    +              BASE64ENCODED...
    
    348
    +            </preKeyPublic>
    
    349
    +            <preKeyPublic preKeyId='2'>
    
    350
    +              BASE64ENCODED...
    
    351
    +            </preKeyPublic>
    
    352
    +            <preKeyPublic preKeyId='3'>
    
    353
    +              BASE64ENCODED...
    
    354
    +            </preKeyPublic>
    
    355
    +            <!-- ... -->
    
    356
    +          </prekeys>
    
    357
    +        </bundle>
    
    358
    +      </item>
    
    359
    +    </publish>
    
    360
    +    '''
    
    361
    +    bundle_node = Node('bundle', attrs={'xmlns': Namespace.OMEMO_TEMP})
    
    362
    +    prekey_pub_node = bundle_node.addChild(
    
    363
    +        'signedPreKeyPublic',
    
    364
    +        attrs={'signedPreKeyId': bundle.spk['id']})
    
    365
    +    prekey_pub_node.addData(b64encode(bundle.spk['key']))
    
    366
    +
    
    367
    +    prekey_sig_node = bundle_node.addChild('signedPreKeySignature')
    
    368
    +    prekey_sig_node.addData(b64encode(bundle.spk_signature))
    
    369
    +
    
    370
    +    identity_key_node = bundle_node.addChild('identityKey')
    
    371
    +    identity_key_node.addData(b64encode(bundle.ik))
    
    372
    +
    
    373
    +    prekeys = bundle_node.addChild('prekeys')
    
    374
    +    for key in bundle.otpks:
    
    375
    +        pre_key_public = prekeys.addChild('preKeyPublic',
    
    376
    +                                          attrs={'preKeyId': key['id']})
    
    377
    +        pre_key_public.addData(b64encode(key['key']))
    
    378
    +    return bundle_node
    
    379
    +
    
    380
    +
    
    381
    +def _make_devicelist(devicelist):
    
    382
    +    if devicelist is None:
    
    383
    +        devicelist = []
    
    384
    +
    
    385
    +    devicelist_node = Node('list', attrs={'xmlns': Namespace.OMEMO_TEMP})
    
    386
    +    for device in devicelist:
    
    387
    +        devicelist_node.addChild('device').setAttr('id', device)
    
    388
    +
    
    389
    +    return devicelist_node
    
    390
    +
    
    391
    +
    
    392
    +def _parse_devicelist(item):
    
    393
    +    '''
    
    394
    +    <items node='eu.siacs.conversations.axolotl.devicelist'>
    
    395
    +      <item id='current'>
    
    396
    +        <list xmlns='eu.siacs.conversations.axolotl'>
    
    397
    +          <device id='12345' />
    
    398
    +          <device id='4223' />
    
    399
    +        </list>
    
    400
    +      </item>
    
    401
    +    </items>
    
    402
    +    '''
    
    403
    +    list_node = item.getTag('list', namespace=Namespace.OMEMO_TEMP)
    
    404
    +    if list_node is None:
    
    405
    +        raise MalformedStanzaError('No list node found', item)
    
    406
    +
    
    407
    +    if not list_node.getChildren():
    
    408
    +        return []
    
    409
    +
    
    410
    +    result = []
    
    411
    +    devices_nodes = list_node.getChildren()
    
    412
    +    for dn in devices_nodes:
    
    413
    +        _id = dn.getAttr('id')
    
    414
    +        if _id:
    
    415
    +            result.append(int(_id))
    
    390 416
     
    
    391
    -        return OMEMOBundle(**result)
    
    417
    +    return result
    
    392 418
     
    
    393 419
     
    
    394 420
     def create_omemo_message(stanza, omemo_message, store_hint=True,
    

  • nbxmpp/modules/software_version.py
    ... ... @@ -17,16 +17,15 @@
    17 17
     
    
    18 18
     from nbxmpp.namespaces import Namespace
    
    19 19
     from nbxmpp.protocol import Iq
    
    20
    -from nbxmpp.protocol import isResultNode
    
    21 20
     from nbxmpp.protocol import ErrorNode
    
    22 21
     from nbxmpp.protocol import NodeProcessed
    
    23 22
     from nbxmpp.protocol import ERR_SERVICE_UNAVAILABLE
    
    24 23
     from nbxmpp.structs import SoftwareVersionResult
    
    25 24
     from nbxmpp.structs import StanzaHandler
    
    26
    -from nbxmpp.util import call_on_response
    
    27
    -from nbxmpp.util import callback
    
    28
    -from nbxmpp.util import raise_error
    
    29 25
     from nbxmpp.modules.base import BaseModule
    
    26
    +from nbxmpp.modules.util import raise_if_error
    
    27
    +from nbxmpp.task import iq_request_task
    
    28
    +from nbxmpp.errors import MalformedStanzaError
    
    30 29
     
    
    31 30
     
    
    32 31
     class SoftwareVersion(BaseModule):
    
    ... ... @@ -50,32 +49,17 @@ class SoftwareVersion(BaseModule):
    50 49
         def disable(self):
    
    51 50
             self._enabled = False
    
    52 51
     
    
    53
    -    @call_on_response('_software_version_received')
    
    52
    +    @iq_request_task
    
    54 53
         def request_software_version(self, jid):
    
    55
    -        self._log.info('Request software version for %s', jid)
    
    56
    -        return Iq(typ='get', to=jid, queryNS=Namespace.VERSION)
    
    57
    -
    
    58
    -    @callback
    
    59
    -    def _software_version_received(self, stanza):
    
    60
    -        if not isResultNode(stanza):
    
    61
    -            return raise_error(self._log.info, stanza)
    
    54
    +        _task = yield
    
    62 55
     
    
    63
    -        try:
    
    64
    -            return SoftwareVersionResult(*self._parse_info(stanza))
    
    65
    -        except Exception as error:
    
    66
    -            self._log.warning(error)
    
    67
    -            return raise_error(self._log.warning, stanza, 'stanza-malformed')
    
    56
    +        self._log.info('Request software version for %s', jid)
    
    68 57
     
    
    69
    -    @staticmethod
    
    70
    -    def _parse_info(stanza):
    
    71
    -        name = stanza.getQueryChild('name').getData()
    
    72
    -        version = stanza.getQueryChild('version').getData()
    
    58
    +        result = yield Iq(typ='get', to=jid, queryNS=Namespace.VERSION)
    
    73 59
     
    
    74
    -        os_info = stanza.getQueryChild('os')
    
    75
    -        if os_info is not None:
    
    76
    -            os_info = os_info.getData()
    
    60
    +        raise_if_error(result)
    
    77 61
     
    
    78
    -        return name, version, os_info
    
    62
    +        yield _parse_info(result)
    
    79 63
     
    
    80 64
         def set_software_version(self, name, version, os=None):
    
    81 65
             self._name, self._version, self._os = name, version, os
    
    ... ... @@ -102,3 +86,21 @@ class SoftwareVersion(BaseModule):
    102 86
     
    
    103 87
             self._client.send_stanza(iq)
    
    104 88
             raise NodeProcessed
    
    89
    +
    
    90
    +
    
    91
    +def _parse_info(stanza):
    
    92
    +    try:
    
    93
    +        name = stanza.getQueryChild('name').getData()
    
    94
    +    except Exception:
    
    95
    +        raise MalformedStanzaError('name node missing', stanza)
    
    96
    +
    
    97
    +    try:
    
    98
    +        version = stanza.getQueryChild('version').getData()
    
    99
    +    except Exception:
    
    100
    +        raise MalformedStanzaError('version node missing', stanza)
    
    101
    +
    
    102
    +    os_info = stanza.getQueryChild('os')
    
    103
    +    if os_info is not None:
    
    104
    +        os_info = os_info.getData()
    
    105
    +
    
    106
    +    return SoftwareVersionResult(name, version, os_info)

  • nbxmpp/modules/tune.py
    ... ... @@ -21,6 +21,8 @@ from nbxmpp.structs import StanzaHandler
    21 21
     from nbxmpp.structs import TuneData
    
    22 22
     from nbxmpp.const import TUNE_DATA
    
    23 23
     from nbxmpp.modules.base import BaseModule
    
    24
    +from nbxmpp.modules.util import finalize
    
    25
    +from nbxmpp.task import iq_request_task
    
    24 26
     
    
    25 27
     
    
    26 28
     class Tune(BaseModule):
    
    ... ... @@ -67,7 +69,10 @@ class Tune(BaseModule):
    67 69
     
    
    68 70
             properties.pubsub_event = pubsub_event
    
    69 71
     
    
    72
    +    @iq_request_task
    
    70 73
         def set_tune(self, data):
    
    74
    +        task = yield
    
    75
    +
    
    71 76
             item = Node('tune', {'xmlns': Namespace.TUNE})
    
    72 77
             if data is not None:
    
    73 78
                 data = data._asdict()
    
    ... ... @@ -75,4 +80,6 @@ class Tune(BaseModule):
    75 80
                     if value is not None:
    
    76 81
                         item.addChild(tag, payload=value)
    
    77 82
     
    
    78
    -        self.publish(Namespace.TUNE, item, id_='current')
    83
    +        result = yield self.publish(Namespace.TUNE, item, id_='current')
    
    84
    +
    
    85
    +        yield finalize(task, result)