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

Commits:

6 changed files:

Changes:

  • nbxmpp/errors.py
    ... ... @@ -135,3 +135,21 @@ class CancelledError(BaseError):
    135 135
         def __init__(self):
    
    136 136
             BaseError.__init__(self, is_fatal=True)
    
    137 137
             self.text = 'Task has been cancelled'
    
    138
    +
    
    139
    +
    
    140
    +class RegisterStanzaError(StanzaError):
    
    141
    +    def __init__(self, stanza, data):
    
    142
    +        StanzaError.__init__(self, stanza)
    
    143
    +        self._data = data
    
    144
    +
    
    145
    +    def get_data(self):
    
    146
    +        return self._data
    
    147
    +
    
    148
    +
    
    149
    +class ChangePasswordStanzaError(StanzaError):
    
    150
    +    def __init__(self, stanza, form):
    
    151
    +        StanzaError.__init__(self, stanza)
    
    152
    +        self._form = form
    
    153
    +
    
    154
    +    def get_form(self):
    
    155
    +        return self._form

  • nbxmpp/modules/register.py deleted
    1
    -# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
    
    2
    -#
    
    3
    -# This file is part of nbxmpp.
    
    4
    -#
    
    5
    -# This program is free software; you can redistribute it and/or
    
    6
    -# modify it under the terms of the GNU General Public License
    
    7
    -# as published by the Free Software Foundation; either version 3
    
    8
    -# of the License, or (at your option) any later version.
    
    9
    -#
    
    10
    -# This program is distributed in the hope that it will be useful,
    
    11
    -# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    12
    -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    13
    -# GNU General Public License for more details.
    
    14
    -#
    
    15
    -# You should have received a copy of the GNU General Public License
    
    16
    -# along with this program; If not, see <http://www.gnu.org/licenses/>.
    
    17
    -
    
    18
    -from nbxmpp.namespaces import Namespace
    
    19
    -from nbxmpp.protocol import Iq
    
    20
    -from nbxmpp.protocol import isResultNode
    
    21
    -from nbxmpp.structs import CommonResult
    
    22
    -from nbxmpp.structs import CommonError
    
    23
    -from nbxmpp.structs import RegisterData
    
    24
    -from nbxmpp.structs import ChangePasswordResult
    
    25
    -from nbxmpp.util import call_on_response
    
    26
    -from nbxmpp.util import callback
    
    27
    -from nbxmpp.util import raise_error
    
    28
    -from nbxmpp.util import get_form
    
    29
    -from nbxmpp.const import REGISTER_FIELDS
    
    30
    -from nbxmpp.modules.bits_of_binary import parse_bob_data
    
    31
    -from nbxmpp.modules.dataforms import extend_form
    
    32
    -from nbxmpp.modules.dataforms import create_field
    
    33
    -from nbxmpp.modules.dataforms import SimpleDataForm
    
    34
    -from nbxmpp.modules.base import BaseModule
    
    35
    -
    
    36
    -
    
    37
    -class Register(BaseModule):
    
    38
    -    def __init__(self, client):
    
    39
    -        BaseModule.__init__(self, client)
    
    40
    -
    
    41
    -        self._client = client
    
    42
    -        self.handlers = []
    
    43
    -
    
    44
    -    @call_on_response('_default_response')
    
    45
    -    def unregister(self, jid=None):
    
    46
    -        iq = Iq('set', to=jid)
    
    47
    -        query = iq.setQuery()
    
    48
    -        query.setNamespace(Namespace.REGISTER)
    
    49
    -        query.addChild('remove')
    
    50
    -        return iq
    
    51
    -
    
    52
    -    @call_on_response('_on_register_form')
    
    53
    -    def request_register_form(self, jid=None):
    
    54
    -        if jid is None:
    
    55
    -            jid = self._client.domain
    
    56
    -        return Iq('get', Namespace.REGISTER, to=jid)
    
    57
    -
    
    58
    -    @callback
    
    59
    -    def _on_register_form(self, stanza):
    
    60
    -        if not isResultNode(stanza):
    
    61
    -            return raise_error(self._log.info, stanza)
    
    62
    -
    
    63
    -        query = stanza.getQuery()
    
    64
    -        instructions = query.getTagData('instructions') or None
    
    65
    -
    
    66
    -        data = RegisterData(instructions=instructions,
    
    67
    -                            form=self._parse_form(stanza),
    
    68
    -                            fields_form=self._parse_fields_form(query),
    
    69
    -                            oob_url=self._parse_oob_url(query),
    
    70
    -                            bob_data=parse_bob_data(query))
    
    71
    -
    
    72
    -        if (data.form is None and
    
    73
    -                data.fields_form is None and
    
    74
    -                data.oob_url is None):
    
    75
    -            return raise_error(self._log.info, stanza, 'stanza-malformed')
    
    76
    -        return data
    
    77
    -
    
    78
    -    @call_on_response('_on_submit_result')
    
    79
    -    def submit_register_form(self, jid, form):
    
    80
    -        if jid is None:
    
    81
    -            jid = self._client.domain
    
    82
    -
    
    83
    -        iq = Iq('set', Namespace.REGISTER, to=jid)
    
    84
    -
    
    85
    -        if form.is_fake_form():
    
    86
    -            query = iq.getTag('query')
    
    87
    -            for field in form.iter_fields():
    
    88
    -                if field.var == 'fakeform':
    
    89
    -                    continue
    
    90
    -                query.addChild(field.var, payload=[field.value])
    
    91
    -            return iq
    
    92
    -
    
    93
    -        iq.setQueryPayload(form)
    
    94
    -        return iq
    
    95
    -
    
    96
    -    @callback
    
    97
    -    def _on_submit_result(self, stanza):
    
    98
    -        if isResultNode(stanza):
    
    99
    -            return CommonResult(jid=stanza.getFrom())
    
    100
    -
    
    101
    -        query = stanza.getTag('query', namespace=Namespace.REGISTER)
    
    102
    -        if query is None:
    
    103
    -            return RegisterError(stanza, None)
    
    104
    -
    
    105
    -        instructions = query.getTagData('instructions') or None
    
    106
    -
    
    107
    -        data = RegisterData(instructions=instructions,
    
    108
    -                            form=self._parse_form(stanza),
    
    109
    -                            fields_form=self._parse_fields_form(query),
    
    110
    -                            oob_url=self._parse_oob_url(query),
    
    111
    -                            bob_data=parse_bob_data(query))
    
    112
    -
    
    113
    -        return RegisterError(stanza, data)
    
    114
    -
    
    115
    -    @callback
    
    116
    -    def _default_response(self, stanza):
    
    117
    -        if not isResultNode(stanza):
    
    118
    -            return raise_error(self._log.info, stanza)
    
    119
    -        return CommonResult(jid=stanza.getFrom())
    
    120
    -
    
    121
    -    @staticmethod
    
    122
    -    def _parse_oob_url(query):
    
    123
    -        oob = query.getTag('x', namespace=Namespace.X_OOB)
    
    124
    -        if oob is not None:
    
    125
    -            return oob.getTagData('url') or None
    
    126
    -        return None
    
    127
    -
    
    128
    -    def _parse_form(self, stanza):
    
    129
    -        query = stanza.getTag('query', namespace=Namespace.REGISTER)
    
    130
    -        form = query.getTag('x', namespace=Namespace.DATA)
    
    131
    -        if form is None:
    
    132
    -            return None
    
    133
    -
    
    134
    -        form = extend_form(node=form)
    
    135
    -        field = form.vars.get('FORM_TYPE')
    
    136
    -        if field is None:
    
    137
    -            self._log.warning('No FORM_TYPE found')
    
    138
    -            self._log.warning(stanza)
    
    139
    -            return None
    
    140
    -
    
    141
    -        # Invalid urn:xmpp:captcha used by ejabberd
    
    142
    -        # See https://github.com/processone/ejabberd/issues/3093
    
    143
    -        if field.value in ('jabber:iq:register', 'urn:xmpp:captcha'):
    
    144
    -            return form
    
    145
    -        return None
    
    146
    -
    
    147
    -    @staticmethod
    
    148
    -    def _parse_fields_form(query):
    
    149
    -        fields = []
    
    150
    -        for field in query.getChildren():
    
    151
    -            field_name = field.getName()
    
    152
    -            if field_name not in REGISTER_FIELDS:
    
    153
    -                continue
    
    154
    -
    
    155
    -            required = field_name in ('username', 'password')
    
    156
    -            typ = 'text-single' if field_name != 'password' else 'text-private'
    
    157
    -            fields.append(create_field(typ=typ,
    
    158
    -                                       var=field_name,
    
    159
    -                                       required=required))
    
    160
    -
    
    161
    -        if not fields:
    
    162
    -            return None
    
    163
    -
    
    164
    -        fields.append(create_field(typ='hidden', var='fakeform'))
    
    165
    -        return SimpleDataForm(type_='form',
    
    166
    -                              instructions=query.getTagData('instructions'),
    
    167
    -                              fields=fields)
    
    168
    -
    
    169
    -    @call_on_response('_on_password_change')
    
    170
    -    def change_password(self, password):
    
    171
    -        domain = self._client.get_bound_jid().domain
    
    172
    -        username = self._client.get_bound_jid().localpart
    
    173
    -        iq = Iq('set', Namespace.REGISTER, to=domain)
    
    174
    -        query = iq.getQuery()
    
    175
    -        query.setTagData('username', username)
    
    176
    -        query.setTagData('password', password)
    
    177
    -        return iq
    
    178
    -
    
    179
    -    @callback
    
    180
    -    def _on_password_change(self, stanza):
    
    181
    -        if isResultNode(stanza):
    
    182
    -            return ChangePasswordResult(successful=True)
    
    183
    -
    
    184
    -        if stanza.getQuery() is None:
    
    185
    -            return raise_error(self._log.info, stanza)
    
    186
    -
    
    187
    -        form = get_form(stanza.getQuery(),
    
    188
    -                        'jabber:iq:register:changepassword')
    
    189
    -        if form is None:
    
    190
    -            return raise_error(self._log.info, stanza)
    
    191
    -        return ChangePasswordResult(successful=False, form=form)
    
    192
    -
    
    193
    -    @call_on_response('_default_response')
    
    194
    -    def change_password_with_form(self, form):
    
    195
    -        domain = self._client.get_bound_jid().domain
    
    196
    -        iq = Iq('set', Namespace.REGISTER, to=domain)
    
    197
    -        iq.setQueryPayload(form)
    
    198
    -        return iq
    
    199
    -
    
    200
    -
    
    201
    -class RegisterError(CommonError):
    
    202
    -    def __init__(self, stanza, data):
    
    203
    -        CommonError.__init__(self, stanza)
    
    204
    -        self._data = data
    
    205
    -
    
    206
    -    def get_data(self):
    
    207
    -        return self._data

  • nbxmpp/modules/register/__init__.py
    1
    +from .register import Register

  • nbxmpp/modules/register/register.py
    1
    +# Copyright (C) 2020 Philipp Hörist <philipp AT hoerist.com>
    
    2
    +#
    
    3
    +# This file is part of nbxmpp.
    
    4
    +#
    
    5
    +# This program is free software; you can redistribute it and/or
    
    6
    +# modify it under the terms of the GNU General Public License
    
    7
    +# as published by the Free Software Foundation; either version 3
    
    8
    +# of the License, or (at your option) any later version.
    
    9
    +#
    
    10
    +# This program is distributed in the hope that it will be useful,
    
    11
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    12
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    13
    +# GNU General Public License for more details.
    
    14
    +#
    
    15
    +# You should have received a copy of the GNU General Public License
    
    16
    +# along with this program; If not, see <http://www.gnu.org/licenses/>.
    
    17
    +
    
    18
    +from nbxmpp.namespaces import Namespace
    
    19
    +from nbxmpp.protocol import Iq
    
    20
    +from nbxmpp.util import get_form
    
    21
    +from nbxmpp.task import iq_request_task
    
    22
    +from nbxmpp.errors import StanzaError
    
    23
    +from nbxmpp.errors import RegisterStanzaError
    
    24
    +from nbxmpp.errors import ChangePasswordStanzaError
    
    25
    +from nbxmpp.modules.base import BaseModule
    
    26
    +from nbxmpp.modules.util import process_response
    
    27
    +from nbxmpp.modules.register.util import _make_unregister_request
    
    28
    +from nbxmpp.modules.register.util import _make_register_form
    
    29
    +from nbxmpp.modules.register.util import _make_password_change_request
    
    30
    +from nbxmpp.modules.register.util import _make_password_change_with_form
    
    31
    +from nbxmpp.modules.register.util import _parse_register_data
    
    32
    +
    
    33
    +
    
    34
    +class Register(BaseModule):
    
    35
    +    def __init__(self, client):
    
    36
    +        BaseModule.__init__(self, client)
    
    37
    +
    
    38
    +        self._client = client
    
    39
    +        self.handlers = []
    
    40
    +
    
    41
    +    @iq_request_task
    
    42
    +    def unregister(self, jid=None):
    
    43
    +        _task = yield
    
    44
    +
    
    45
    +        response = yield _make_unregister_request(jid)
    
    46
    +        yield process_response(response)
    
    47
    +
    
    48
    +    @iq_request_task
    
    49
    +    def request_register_form(self, jid=None):
    
    50
    +        _task = yield
    
    51
    +
    
    52
    +        if jid is None:
    
    53
    +            jid = self._client.domain
    
    54
    +
    
    55
    +        response = yield Iq('get', Namespace.REGISTER, to=jid)
    
    56
    +        if response.isError():
    
    57
    +            raise StanzaError(response)
    
    58
    +
    
    59
    +        yield _parse_register_data(response)
    
    60
    +
    
    61
    +    @iq_request_task
    
    62
    +    def submit_register_form(self, form, jid=None):
    
    63
    +        _task = yield
    
    64
    +
    
    65
    +        if jid is None:
    
    66
    +            jid = self._client.domain
    
    67
    +
    
    68
    +        response = yield _make_register_form(jid, form)
    
    69
    +        if not response.isError():
    
    70
    +            yield process_response(response)
    
    71
    +
    
    72
    +        else:
    
    73
    +            data = _parse_register_data(response)
    
    74
    +            raise RegisterStanzaError(response, data)
    
    75
    +
    
    76
    +    @iq_request_task
    
    77
    +    def change_password(self, password):
    
    78
    +        _task = yield
    
    79
    +
    
    80
    +        response = yield _make_password_change_request(
    
    81
    +            self._client.domain, self._client.username, password)
    
    82
    +        if not response.isError():
    
    83
    +            yield process_response(response)
    
    84
    +
    
    85
    +        else:
    
    86
    +            query = response.getQuery()
    
    87
    +            if query is None:
    
    88
    +                raise StanzaError(response)
    
    89
    +
    
    90
    +            form = get_form(query, 'jabber:iq:register:changepassword')
    
    91
    +            if form is None or response.getType() != 'modify':
    
    92
    +                raise StanzaError(response)
    
    93
    +
    
    94
    +            raise ChangePasswordStanzaError(response, form)
    
    95
    +
    
    96
    +    @iq_request_task
    
    97
    +    def change_password_with_form(self, form):
    
    98
    +        _task = yield
    
    99
    +
    
    100
    +        response = yield _make_password_change_with_form(self._client.domain,
    
    101
    +                                                         form)
    
    102
    +        yield process_response(response)

  • nbxmpp/modules/register/util.py
    1
    +# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
    
    2
    +#
    
    3
    +# This file is part of nbxmpp.
    
    4
    +#
    
    5
    +# This program is free software; you can redistribute it and/or
    
    6
    +# modify it under the terms of the GNU General Public License
    
    7
    +# as published by the Free Software Foundation; either version 3
    
    8
    +# of the License, or (at your option) any later version.
    
    9
    +#
    
    10
    +# This program is distributed in the hope that it will be useful,
    
    11
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    12
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    13
    +# GNU General Public License for more details.
    
    14
    +#
    
    15
    +# You should have received a copy of the GNU General Public License
    
    16
    +# along with this program; If not, see <http://www.gnu.org/licenses/>.
    
    17
    +
    
    18
    +from nbxmpp.namespaces import Namespace
    
    19
    +from nbxmpp.protocol import Iq
    
    20
    +from nbxmpp.const import REGISTER_FIELDS
    
    21
    +from nbxmpp.structs import RegisterData
    
    22
    +from nbxmpp.errors import StanzaError
    
    23
    +from nbxmpp.errors import MalformedStanzaError
    
    24
    +from nbxmpp.modules.dataforms import create_field
    
    25
    +from nbxmpp.modules.dataforms import extend_form
    
    26
    +from nbxmpp.modules.dataforms import SimpleDataForm
    
    27
    +from nbxmpp.modules.bits_of_binary import parse_bob_data
    
    28
    +
    
    29
    +
    
    30
    +def _make_password_change_request(domain, username, password):
    
    31
    +    iq = Iq('set', Namespace.REGISTER, to=domain)
    
    32
    +    query = iq.getQuery()
    
    33
    +    query.setTagData('username', username)
    
    34
    +    query.setTagData('password', password)
    
    35
    +    return iq
    
    36
    +
    
    37
    +
    
    38
    +def _make_password_change_with_form(domain, form):
    
    39
    +    iq = Iq('set', Namespace.REGISTER, to=domain)
    
    40
    +    iq.setQueryPayload(form)
    
    41
    +    return iq
    
    42
    +
    
    43
    +
    
    44
    +def _make_register_form(jid, form):
    
    45
    +    iq = Iq('set', Namespace.REGISTER, to=jid)
    
    46
    +    if form.is_fake_form():
    
    47
    +        query = iq.getTag('query')
    
    48
    +        for field in form.iter_fields():
    
    49
    +            if field.var == 'fakeform':
    
    50
    +                continue
    
    51
    +            query.addChild(field.var, payload=[field.value])
    
    52
    +        return iq
    
    53
    +
    
    54
    +    iq.setQueryPayload(form)
    
    55
    +    return iq
    
    56
    +
    
    57
    +
    
    58
    +def _make_unregister_request(jid):
    
    59
    +    iq = Iq('set', to=jid)
    
    60
    +    query = iq.setQuery()
    
    61
    +    query.setNamespace(Namespace.REGISTER)
    
    62
    +    query.addChild('remove')
    
    63
    +    return iq
    
    64
    +
    
    65
    +
    
    66
    +def _parse_oob_url(query):
    
    67
    +    oob = query.getTag('x', namespace=Namespace.X_OOB)
    
    68
    +    if oob is not None:
    
    69
    +        return oob.getTagData('url') or None
    
    70
    +    return None
    
    71
    +
    
    72
    +
    
    73
    +def _parse_form(stanza):
    
    74
    +    query = stanza.getTag('query', namespace=Namespace.REGISTER)
    
    75
    +    form = query.getTag('x', namespace=Namespace.DATA)
    
    76
    +    if form is None:
    
    77
    +        return None
    
    78
    +
    
    79
    +    form = extend_form(node=form)
    
    80
    +    field = form.vars.get('FORM_TYPE')
    
    81
    +    if field is None:
    
    82
    +        return None
    
    83
    +
    
    84
    +    # Invalid urn:xmpp:captcha used by ejabberd
    
    85
    +    # See https://github.com/processone/ejabberd/issues/3045
    
    86
    +    if field.value in ('jabber:iq:register', 'urn:xmpp:captcha'):
    
    87
    +        return form
    
    88
    +    return None
    
    89
    +
    
    90
    +
    
    91
    +def _parse_fields_form(query):
    
    92
    +    fields = []
    
    93
    +    for field in query.getChildren():
    
    94
    +        field_name = field.getName()
    
    95
    +        if field_name not in REGISTER_FIELDS:
    
    96
    +            continue
    
    97
    +
    
    98
    +        required = field_name in ('username', 'password')
    
    99
    +        typ = 'text-single' if field_name != 'password' else 'text-private'
    
    100
    +        fields.append(create_field(typ=typ,
    
    101
    +                                   var=field_name,
    
    102
    +                                   required=required))
    
    103
    +
    
    104
    +    if not fields:
    
    105
    +        return None
    
    106
    +
    
    107
    +    fields.append(create_field(typ='hidden', var='fakeform'))
    
    108
    +    return SimpleDataForm(type_='form',
    
    109
    +                          instructions=query.getTagData('instructions'),
    
    110
    +                          fields=fields)
    
    111
    +
    
    112
    +
    
    113
    +def _parse_register_data(response):
    
    114
    +    query = response.getTag('query', namespace=Namespace.REGISTER)
    
    115
    +    if query is None:
    
    116
    +        raise StanzaError(response)
    
    117
    +
    
    118
    +    instructions = query.getTagData('instructions') or None
    
    119
    +
    
    120
    +    data = RegisterData(instructions=instructions,
    
    121
    +                        form=_parse_form(response),
    
    122
    +                        fields_form=_parse_fields_form(query),
    
    123
    +                        oob_url=_parse_oob_url(query),
    
    124
    +                        bob_data=parse_bob_data(query))
    
    125
    +
    
    126
    +    if (data.form is None and
    
    127
    +            data.fields_form is None and
    
    128
    +            data.oob_url is None):
    
    129
    +        raise MalformedStanzaError('invalid register response', response)
    
    130
    +
    
    131
    +    return data

  • nbxmpp/structs.py
    ... ... @@ -120,9 +120,6 @@ SecurityLabel = namedtuple('SecurityLabel', 'displaymarking')
    120 120
     
    
    121 121
     RegisterData = namedtuple('RegisterData', 'instructions form fields_form oob_url bob_data')
    
    122 122
     
    
    123
    -ChangePasswordResult = namedtuple('ChangePasswordResult', 'successful form')
    
    124
    -ChangePasswordResult.__new__.__defaults__ = (None,)
    
    125
    -
    
    126 123
     HTTPUploadData = namedtuple('HTTPUploadData', 'put_uri get_uri headers')
    
    127 124
     HTTPUploadData.__new__.__defaults__ = (None,)
    
    128 125