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

Commits:

6 changed files:

Changes:

  • nbxmpp/const.py
    ... ... @@ -553,4 +553,21 @@ CHATSTATES = [
    553 553
         'gone',
    
    554 554
         'composing',
    
    555 555
         'paused'
    
    556
    -]
    \ No newline at end of file
    556
    +]
    
    557
    +
    
    558
    +REGISTER_FIELDS = [
    
    559
    +    'username',
    
    560
    +    'nick',
    
    561
    +    'password',
    
    562
    +    'name',
    
    563
    +    'first',
    
    564
    +    'last',
    
    565
    +    'email',
    
    566
    +    'address',
    
    567
    +    'city',
    
    568
    +    'state',
    
    569
    +    'zip',
    
    570
    +    'phone',
    
    571
    +    'url',
    
    572
    +    'date',
    
    573
    +]

  • nbxmpp/modules/bits_of_binary.py
    ... ... @@ -19,7 +19,6 @@ import logging
    19 19
     import hashlib
    
    20 20
     
    
    21 21
     from nbxmpp.protocol import NS_BOB
    
    22
    -from nbxmpp.protocol import InvalidStanza
    
    23 22
     from nbxmpp.structs import BobData
    
    24 23
     from nbxmpp.util import b64decode
    
    25 24
     
    
    ... ... @@ -29,7 +28,7 @@ log = logging.getLogger('nbxmpp.m.bob')
    29 28
     def parse_bob_data(stanza):
    
    30 29
         data_node = stanza.getTag('data', namespace=NS_BOB)
    
    31 30
         if data_node is None:
    
    32
    -        return
    
    31
    +        return None
    
    33 32
     
    
    34 33
         cid = data_node.getAttr('cid')
    
    35 34
         type_ = data_node.getAttr('type')
    
    ... ... @@ -39,42 +38,42 @@ def parse_bob_data(stanza):
    39 38
                 max_age = int(max_age)
    
    40 39
             except Exception:
    
    41 40
                 log.exception(stanza)
    
    42
    -            raise InvalidStanza
    
    41
    +            return None
    
    43 42
     
    
    44 43
         if cid is None or type_ is None:
    
    45 44
             log.warning('Invalid data node (no cid or type attr): %s', stanza)
    
    46
    -        raise InvalidStanza
    
    45
    +        return None
    
    47 46
     
    
    48 47
         try:
    
    49 48
             algo_hash = cid.split('@')[0]
    
    50 49
             algo, hash_ = algo_hash.split('+')
    
    51 50
         except Exception:
    
    52 51
             log.exception('Invalid cid: %s', stanza)
    
    53
    -        raise InvalidStanza
    
    52
    +        return None
    
    54 53
     
    
    55 54
         bob_data = data_node.getData()
    
    56 55
         if not bob_data:
    
    57 56
             log.warning('No bob data found: %s', stanza)
    
    58
    -        raise InvalidStanza
    
    57
    +        return None
    
    59 58
     
    
    60 59
         try:
    
    61 60
             bob_data = b64decode(bob_data, return_type=bytes)
    
    62 61
         except Exception:
    
    63 62
             log.warning('Unable to decode data')
    
    64 63
             log.exception(stanza)
    
    65
    -        raise InvalidStanza
    
    64
    +        return None
    
    66 65
     
    
    67 66
         try:
    
    68 67
             sha = hashlib.new(algo)
    
    69 68
         except ValueError as error:
    
    70 69
             log.warning(stanza)
    
    71 70
             log.warning(error)
    
    72
    -        raise InvalidStanza
    
    71
    +        return None
    
    73 72
     
    
    74 73
         sha.update(bob_data)
    
    75 74
         if sha.hexdigest() != hash_:
    
    76 75
             log.warning('Invalid hash: %s', stanza)
    
    77
    -        raise InvalidStanza
    
    76
    +        return None
    
    78 77
     
    
    79 78
         return BobData(algo=algo,
    
    80 79
                        hash_=hash_,
    

  • nbxmpp/modules/captcha.py
    ... ... @@ -19,7 +19,6 @@ import logging
    19 19
     
    
    20 20
     from nbxmpp.protocol import NS_CAPTCHA
    
    21 21
     from nbxmpp.protocol import NS_DATA
    
    22
    -from nbxmpp.protocol import InvalidStanza
    
    23 22
     from nbxmpp.structs import StanzaHandler
    
    24 23
     from nbxmpp.structs import CaptchaData
    
    25 24
     from nbxmpp.modules.dataforms import extend_form
    
    ... ... @@ -51,10 +50,7 @@ class Captcha:
    51 50
                 return
    
    52 51
     
    
    53 52
             form = extend_form(node=data_form)
    
    54
    -        try:
    
    55
    -            bob_data = parse_bob_data(stanza)
    
    56
    -        except InvalidStanza:
    
    57
    -            bob_data = None
    
    53
    +        bob_data = parse_bob_data(stanza)
    
    58 54
     
    
    59 55
             properties.captcha = CaptchaData(form=form,
    
    60 56
                                              bob_data=bob_data)

  • nbxmpp/modules/register.py
    ... ... @@ -17,13 +17,23 @@
    17 17
     
    
    18 18
     import logging
    
    19 19
     
    
    20
    +from nbxmpp.protocol import NS_X_OOB
    
    21
    +from nbxmpp.protocol import NS_DATA
    
    20 22
     from nbxmpp.protocol import NS_REGISTER
    
    21 23
     from nbxmpp.protocol import Iq
    
    22 24
     from nbxmpp.protocol import isResultNode
    
    23 25
     from nbxmpp.structs import CommonResult
    
    26
    +from nbxmpp.structs import RegisterData
    
    27
    +from nbxmpp.structs import ChangePasswordResult
    
    24 28
     from nbxmpp.util import call_on_response
    
    25 29
     from nbxmpp.util import callback
    
    26 30
     from nbxmpp.util import raise_error
    
    31
    +from nbxmpp.util import get_form
    
    32
    +from nbxmpp.const import REGISTER_FIELDS
    
    33
    +from nbxmpp.modules.bits_of_binary import parse_bob_data
    
    34
    +from nbxmpp.modules.dataforms import extend_form
    
    35
    +from nbxmpp.modules.dataforms import create_field
    
    36
    +from nbxmpp.modules.dataforms import SimpleDataForm
    
    27 37
     
    
    28 38
     log = logging.getLogger('nbxmpp.m.register')
    
    29 39
     
    
    ... ... @@ -42,8 +52,103 @@ class Register:
    42 52
             query.addChild('remove')
    
    43 53
             return iq
    
    44 54
     
    
    55
    +    @call_on_response('_on_register_form')
    
    56
    +    def request_register_form(self, jid=None):
    
    57
    +        if jid is None:
    
    58
    +            jid = self._client.Server
    
    59
    +        return Iq('get', NS_REGISTER, to=jid)
    
    60
    +
    
    61
    +    @callback
    
    62
    +    def _on_register_form(self, stanza):
    
    63
    +        query = stanza.getQuery()
    
    64
    +
    
    65
    +        instructions = query.getTagData('instructions') or None
    
    66
    +
    
    67
    +        return RegisterData(instructions=instructions,
    
    68
    +                            form=self._parse_form(stanza),
    
    69
    +                            fields_form=self._parse_fields_form(query),
    
    70
    +                            oob_url=self._parse_oob_url(query),
    
    71
    +                            bob_data=parse_bob_data(query))
    
    72
    +
    
    45 73
         @callback
    
    46 74
         def _default_response(self, stanza):
    
    47 75
             if not isResultNode(stanza):
    
    48 76
                 return raise_error(log.info, stanza)
    
    49 77
             return CommonResult(jid=stanza.getFrom())
    
    78
    +
    
    79
    +    @staticmethod
    
    80
    +    def _parse_oob_url(query):
    
    81
    +        oob = query.getTag('x', namespace=NS_X_OOB)
    
    82
    +        if oob is not None:
    
    83
    +            return oob.getTagData('url') or None
    
    84
    +        return None
    
    85
    +
    
    86
    +    @staticmethod
    
    87
    +    def _parse_form(stanza):
    
    88
    +        form = stanza.getQuery().getTag('x', namespace=NS_DATA)
    
    89
    +        if form is None:
    
    90
    +            return None
    
    91
    +
    
    92
    +        form = extend_form(node=form)
    
    93
    +        field = form.vars.get('FORM_TYPE')
    
    94
    +        if field is None:
    
    95
    +            log.warning('No FORM_TYPE found')
    
    96
    +            log.warning(stanza)
    
    97
    +            return None
    
    98
    +
    
    99
    +        # Invalid urn:xmpp:captcha used by ejabberd
    
    100
    +        # See https://github.com/processone/ejabberd/issues/3093
    
    101
    +        if field.value in ('jabber:iq:register', 'urn:xmpp:captcha'):
    
    102
    +            return form
    
    103
    +        return None
    
    104
    +
    
    105
    +    @staticmethod
    
    106
    +    def _parse_fields_form(query):
    
    107
    +        fields = []
    
    108
    +        for field in query.getChildren():
    
    109
    +            field_name = field.getName()
    
    110
    +            if field_name not in REGISTER_FIELDS:
    
    111
    +                continue
    
    112
    +
    
    113
    +            required = field_name in ('username', 'password')
    
    114
    +            typ = 'text-single' if field_name != 'password' else 'text-private'
    
    115
    +            fields.append(create_field(typ=typ,
    
    116
    +                                       var=field_name,
    
    117
    +                                       required=required))
    
    118
    +
    
    119
    +        if not fields:
    
    120
    +            return None
    
    121
    +
    
    122
    +        fields.append(create_field(typ='hidden', var='fakeform'))
    
    123
    +        return SimpleDataForm(type_='form', fields=fields)
    
    124
    +
    
    125
    +    @call_on_response('_on_password_change')
    
    126
    +    def change_password(self, password):
    
    127
    +        hostname = self._client.get_bound_jid().getDomain()
    
    128
    +        username = self._client.get_bound_jid().getNode()
    
    129
    +        iq = Iq('set', NS_REGISTER, to=hostname)
    
    130
    +        query = iq.getQuery()
    
    131
    +        query.setTagData('username', username)
    
    132
    +        query.setTagData('password', password)
    
    133
    +        return iq
    
    134
    +
    
    135
    +    @callback
    
    136
    +    def _on_password_change(self, stanza):
    
    137
    +        if isResultNode(stanza):
    
    138
    +            return ChangePasswordResult(successful=True)
    
    139
    +
    
    140
    +        if stanza.getQuery() is None:
    
    141
    +            return raise_error(log.info, stanza)
    
    142
    +
    
    143
    +        form = get_form(stanza.getQuery(),
    
    144
    +                        'jabber:iq:register:changepassword')
    
    145
    +        if form is None:
    
    146
    +            return raise_error(log.info, stanza)
    
    147
    +        return ChangePasswordResult(successful=False, form=form)
    
    148
    +
    
    149
    +    @call_on_response('_default_response')
    
    150
    +    def change_password_with_form(self, form):
    
    151
    +        hostname = self._client.get_bound_jid().getDomain()
    
    152
    +        iq = Iq('set', NS_REGISTER, to=hostname)
    
    153
    +        iq.setQueryPayload(form)
    
    154
    +        return iq

  • nbxmpp/structs.py
    ... ... @@ -131,6 +131,10 @@ CorrectionData = namedtuple('CorrectionData', 'id')
    131 131
     DisplayMarking = namedtuple('DisplayMarking', 'label fgcolor bgcolor')
    
    132 132
     SecurityLabel = namedtuple('SecurityLabel', 'displaymarking')
    
    133 133
     
    
    134
    +RegisterData = namedtuple('RegisterData', 'instructions form fields_form oob_url bob_data')
    
    135
    +
    
    136
    +ChangePasswordResult = namedtuple('ChangePasswordResult', 'successful form')
    
    137
    +ChangePasswordResult.__new__.__defaults__ = (None,)
    
    134 138
     
    
    135 139
     class DiscoInfo(namedtuple('DiscoInfo', 'stanza identities features dataforms timestamp')):
    
    136 140
     
    

  • nbxmpp/util.py
    ... ... @@ -321,3 +321,19 @@ def compute_caps_hash(info, compare=True):
    321 321
     
    
    322 322
     def generate_id():
    
    323 323
         return str(uuid.uuid4())
    
    324
    +
    
    325
    +
    
    326
    +def get_form(stanza, form_type):
    
    327
    +    forms = stanza.getTags('x', namespace=NS_DATA)
    
    328
    +    if not forms:
    
    329
    +        return None
    
    330
    +
    
    331
    +    for form in forms:
    
    332
    +        form = extend_form(node=form)
    
    333
    +        field = form.vars.get('FORM_TYPE')
    
    334
    +        if field is None:
    
    335
    +            continue
    
    336
    +
    
    337
    +        if field.value == form_type:
    
    338
    +            return form
    
    339
    +    return None