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

Commits:

5 changed files:

Changes:

  • .gitlab-ci.yml
    ... ... @@ -17,7 +17,9 @@ test-pylint:
    17 17
       coverage: "/TOTAL.+ ([0-9]{1,3}%)/"
    
    18 18
       artifacts:
    
    19 19
         reports:
    
    20
    -      cobertura: coverage.xml
    
    20
    +      coverage_report: 
    
    21
    +        coverage_format: cobertura
    
    22
    +        path: coverage.xml
    
    21 23
     
    
    22 24
     build-linux:
    
    23 25
       image: nbxmpp-deb-build:latest
    

  • nbxmpp/client.py
    ... ... @@ -39,7 +39,7 @@ from nbxmpp.addresses import NoMoreAddresses
    39 39
     from nbxmpp.tcp import TCPConnection
    
    40 40
     from nbxmpp.websocket import WebsocketConnection
    
    41 41
     from nbxmpp.smacks import Smacks
    
    42
    -from nbxmpp.auth import SASL
    
    42
    +from nbxmpp.sasl import SASL
    
    43 43
     from nbxmpp.const import StreamState
    
    44 44
     from nbxmpp.const import StreamError
    
    45 45
     from nbxmpp.const import ConnectionType
    

  • nbxmpp/protocol.py
    ... ... @@ -44,6 +44,7 @@ def ascii_upper(s):
    44 44
         return s.upper()
    
    45 45
     
    
    46 46
     SASL_AUTH_MECHS = [
    
    47
    +    'SCRAM-SHA-512-PLUS',
    
    47 48
         'SCRAM-SHA-512',
    
    48 49
         'SCRAM-SHA-256-PLUS',
    
    49 50
         'SCRAM-SHA-256',
    

  • nbxmpp/auth.py → nbxmpp/sasl.py
    ... ... @@ -21,6 +21,7 @@ import binascii
    21 21
     import logging
    
    22 22
     import hashlib
    
    23 23
     from hashlib import pbkdf2_hmac
    
    24
    +from typing import Optional
    
    24 25
     
    
    25 26
     from nbxmpp.namespaces import Namespace
    
    26 27
     from nbxmpp.protocol import Node
    
    ... ... @@ -32,7 +33,7 @@ from nbxmpp.util import LogAdapter
    32 33
     from nbxmpp.const import StreamState
    
    33 34
     
    
    34 35
     
    
    35
    -log = logging.getLogger('nbxmpp.auth')
    
    36
    +log = logging.getLogger('nbxmpp.sasl')
    
    36 37
     
    
    37 38
     try:
    
    38 39
         gssapi = __import__('gssapi')
    
    ... ... @@ -51,9 +52,20 @@ class SASL:
    51 52
     
    
    52 53
             self._password = None
    
    53 54
     
    
    55
    +        self._mechanism_classes = {
    
    56
    +            'PLAIN': PLAIN,
    
    57
    +            'EXTERNAL': EXTERNAL,
    
    58
    +            'GSSAPI': GSSAPI,
    
    59
    +            'SCRAM-SHA-1': SCRAM_SHA_1,
    
    60
    +            'SCRAM-SHA-1-PLUS': SCRAM_SHA_1_PLUS,
    
    61
    +            'SCRAM-SHA-256': SCRAM_SHA_256,
    
    62
    +            'SCRAM-SHA-256-PLUS': SCRAM_SHA_256_PLUS,
    
    63
    +            'SCRAM-SHA-512': SCRAM_SHA_512,
    
    64
    +            'SCRAM-SHA-512-PLUS': SCRAM_SHA_512_PLUS
    
    65
    +        }
    
    66
    +
    
    54 67
             self._allowed_mechs = None
    
    55 68
             self._enabled_mechs = None
    
    56
    -        self._method = None
    
    57 69
             self._error = None
    
    58 70
     
    
    59 71
             self._log = LogAdapter(log, {'context': client.log_context})
    
    ... ... @@ -82,13 +94,14 @@ class SASL:
    82 94
         def start_auth(self, features):
    
    83 95
             self._allowed_mechs = self._client.mechs
    
    84 96
             self._enabled_mechs = self._allowed_mechs
    
    85
    -        self._method = None
    
    97
    +        self._mechanism = None
    
    86 98
             self._error = None
    
    87 99
     
    
    88 100
             # -PLUS variants need TLS channel binding data
    
    89 101
             # This is currently not supported via GLib
    
    90 102
             self._enabled_mechs.discard('SCRAM-SHA-1-PLUS')
    
    91 103
             self._enabled_mechs.discard('SCRAM-SHA-256-PLUS')
    
    104
    +        self._enabled_mechs.discard('SCRAM-SHA-512-PLUS')
    
    92 105
             # channel_binding_data = None
    
    93 106
     
    
    94 107
             if not GSSAPI_AVAILABLE:
    
    ... ... @@ -127,69 +140,49 @@ class SASL:
    127 140
                     self._on_sasl_finished(False, 'no-password')
    
    128 141
                     return
    
    129 142
     
    
    130
    -        # if chosen_mechanism == 'SCRAM-SHA-256-PLUS':
    
    131
    -        #     self._method = SCRAM_SHA_256_PLUS(self._client,
    
    132
    -        #                                       channel_binding_data)
    
    133
    -        #     self._method.initiate(self._client.username, self._password)
    
    134
    -
    
    135
    -        # elif chosen_mechanism == 'SCRAM-SHA-1-PLUS':
    
    136
    -        #     self._method = SCRAM_SHA_1_PLUS(self._client,
    
    137
    -        #                                     channel_binding_data)
    
    138
    -        #     self._method.initiate(self._client.username, self._password)
    
    139
    -
    
    140
    -        if chosen_mechanism == 'SCRAM-SHA-512':
    
    141
    -            self._method = SCRAM_SHA_512(self._client, None)
    
    142
    -            self._method.initiate(self._client.username, self._password)
    
    143
    -
    
    144
    -        elif chosen_mechanism == 'SCRAM-SHA-256':
    
    145
    -            self._method = SCRAM_SHA_256(self._client, None)
    
    146
    -            self._method.initiate(self._client.username, self._password)
    
    147
    -
    
    148
    -        elif chosen_mechanism == 'SCRAM-SHA-1':
    
    149
    -            self._method = SCRAM_SHA_1(self._client, None)
    
    150
    -            self._method.initiate(self._client.username, self._password)
    
    143
    +        mech_class = self._mechanism_classes[chosen_mechanism]
    
    144
    +        self._mechanism = mech_class(self._client.username,
    
    145
    +                                     self._password,
    
    146
    +                                     domain_based_name or self._client.domain)
    
    151 147
     
    
    152
    -        elif chosen_mechanism == 'PLAIN':
    
    153
    -            self._method = PLAIN(self._client)
    
    154
    -            self._method.initiate(self._client.username, self._password)
    
    155
    -
    
    156
    -        elif chosen_mechanism == 'ANONYMOUS':
    
    157
    -            self._method = ANONYMOUS(self._client)
    
    158
    -            self._method.initiate()  # pylint: disable=E1120
    
    159
    -
    
    160
    -        elif chosen_mechanism == 'EXTERNAL':
    
    161
    -            self._method = EXTERNAL(self._client)
    
    162
    -            self._method.initiate(self._client.username, self._client.Server)
    
    148
    +        try:
    
    149
    +            self._send_initiate()
    
    150
    +        except AuthFail as error:
    
    151
    +            self._log.error(error)
    
    152
    +            self._abort_auth()
    
    153
    +            return
    
    163 154
     
    
    164
    -        elif chosen_mechanism == 'GSSAPI':
    
    165
    -            self._method = GSSAPI(self._client)
    
    166
    -            if domain_based_name:
    
    167
    -                hostname = domain_based_name
    
    168
    -            else:
    
    169
    -                hostname = self._client.domain
    
    170
    -            try:
    
    171
    -                self._method.initiate(hostname)  # pylint: disable=E1120
    
    172
    -            except AuthFail as error:
    
    173
    -                self._log.error(error)
    
    174
    -                self._abort_auth()
    
    175
    -                return
    
    176
    -        else:
    
    177
    -            self._log.error('Unknown auth mech')
    
    155
    +    def _send_initiate(self) -> None:
    
    156
    +        data = self._mechanism.get_initiate_data()
    
    157
    +        node = Node('auth',
    
    158
    +                    attrs={'xmlns': Namespace.SASL,
    
    159
    +                           'mechanism': self._mechanism.name})
    
    160
    +        if data is not None:
    
    161
    +            node.setData(data)
    
    162
    +        self._client.send_nonza(node)
    
    178 163
     
    
    179
    -    def _on_challenge(self, stanza):
    
    164
    +    def _on_challenge(self, stanza) -> None:
    
    180 165
             try:
    
    181
    -            self._method.response(stanza.getData())
    
    166
    +            data = self._mechanism.get_response_data(stanza.getData())
    
    182 167
             except AttributeError:
    
    183 168
                 self._log.info('Mechanism has no response method')
    
    184 169
                 self._abort_auth()
    
    170
    +            return
    
    171
    +
    
    185 172
             except AuthFail as error:
    
    186 173
                 self._log.error(error)
    
    187 174
                 self._abort_auth()
    
    175
    +            return
    
    176
    +
    
    177
    +        node = Node('response',
    
    178
    +                    attrs={'xmlns': Namespace.SASL},
    
    179
    +                    payload=[data])
    
    180
    +        self._client.send_nonza(node)
    
    188 181
     
    
    189 182
         def _on_success(self, stanza):
    
    190 183
             self._log.info('Successfully authenticated with remote server')
    
    191 184
             try:
    
    192
    -            self._method.success(stanza.getData())
    
    185
    +            self._mechanism.get_success_data(stanza.getData())
    
    193 186
             except AttributeError:
    
    194 187
                 pass
    
    195 188
             except AuthFail as error:
    
    ... ... @@ -227,61 +220,59 @@ class SASL:
    227 220
                 self._client.set_state(StreamState.AUTH_SUCCESSFUL)
    
    228 221
     
    
    229 222
     
    
    230
    -class PLAIN:
    
    223
    +class BaseMechanism:
    
    231 224
     
    
    232
    -    _mechanism = 'PLAIN'
    
    225
    +    name: str
    
    233 226
     
    
    234
    -    def __init__(self, client):
    
    235
    -        self._client = client
    
    227
    +    def __init__(self, username: str, password: str, domain: str):
    
    228
    +        self._username = username
    
    229
    +        self._password = password
    
    230
    +        self._domain = domain
    
    236 231
     
    
    237
    -    def initiate(self, username, password):
    
    238
    -        payload = b64encode('\x00%s\x00%s' % (username, password))
    
    239
    -        node = Node('auth',
    
    240
    -                    attrs={'xmlns': Namespace.SASL, 'mechanism': 'PLAIN'},
    
    241
    -                    payload=[payload])
    
    242
    -        self._client.send_nonza(node)
    
    232
    +    def get_initiate_data(self) -> Optional[str]:
    
    233
    +        raise NotImplementedError
    
    243 234
     
    
    235
    +    def get_response_data(self, data: str) -> str:
    
    236
    +        raise NotImplementedError
    
    244 237
     
    
    245
    -class EXTERNAL:
    
    238
    +    def validate_success_data(self, data: str) -> None:
    
    239
    +        raise NotImplementedError
    
    246 240
     
    
    247
    -    _mechanism = 'EXTERNAL'
    
    248 241
     
    
    249
    -    def __init__(self, client):
    
    250
    -        self._client = client
    
    242
    +class PLAIN(BaseMechanism):
    
    251 243
     
    
    252
    -    def initiate(self, username, server):
    
    253
    -        payload = b64encode('%s@%s' % (username, server))
    
    254
    -        node = Node('auth',
    
    255
    -                    attrs={'xmlns': Namespace.SASL, 'mechanism': 'EXTERNAL'},
    
    256
    -                    payload=[payload])
    
    257
    -        self._client.send_nonza(node)
    
    244
    +    name = 'PLAIN'
    
    258 245
     
    
    246
    +    def get_initiate_data(self) -> str:
    
    247
    +        return b64encode('\x00%s\x00%s' % (self._username, self._password))
    
    259 248
     
    
    260
    -class ANONYMOUS:
    
    261 249
     
    
    262
    -    _mechanism = 'ANONYMOUS'
    
    250
    +class EXTERNAL(BaseMechanism):
    
    263 251
     
    
    264
    -    def __init__(self, client):
    
    265
    -        self._client = client
    
    252
    +    name = 'EXTERNAL'
    
    266 253
     
    
    267
    -    def initiate(self):
    
    268
    -        node = Node('auth', attrs={'xmlns': Namespace.SASL,
    
    269
    -                                   'mechanism': 'ANONYMOUS'})
    
    270
    -        self._client.send_nonza(node)
    
    254
    +    def get_initiate_data(self) -> str:
    
    255
    +        return b64encode('%s@%s' % (self._username, self._domain))
    
    271 256
     
    
    272 257
     
    
    273
    -class GSSAPI:
    
    258
    +class ANONYMOUS(BaseMechanism):
    
    274 259
     
    
    275
    -    # See https://tools.ietf.org/html/rfc4752#section-3.1
    
    260
    +    name = 'ANONYMOUS'
    
    276 261
     
    
    277
    -    _mechanism = 'GSSAPI'
    
    262
    +    def get_initiate_data(self) -> None:
    
    263
    +        return None
    
    278 264
     
    
    279
    -    def __init__(self, client):
    
    280
    -        self._client = client
    
    281 265
     
    
    282
    -    def initiate(self, hostname):
    
    266
    +class GSSAPI(BaseMechanism):
    
    267
    +
    
    268
    +    # See https://tools.ietf.org/html/rfc4752#section-3.1
    
    269
    +
    
    270
    +    name = 'GSSAPI'
    
    271
    +
    
    272
    +    def get_initiate_data(self) -> str:
    
    283 273
             service = gssapi.Name(
    
    284
    -            'xmpp@%s' % hostname, name_type=gssapi.NameType.hostbased_service)
    
    274
    +            'xmpp@%s' % self._domain,
    
    275
    +            name_type=gssapi.NameType.hostbased_service)
    
    285 276
             try:
    
    286 277
                 self.ctx = gssapi.SecurityContext(
    
    287 278
                     name=service, usage="initiate",
    
    ... ... @@ -289,75 +280,66 @@ class GSSAPI:
    289 280
                 token = self.ctx.step()
    
    290 281
             except (gssapi.exceptions.GeneralError, gssapi.raw.misc.GSSError) as e:
    
    291 282
                 raise AuthFail(e)
    
    292
    -        node = Node('auth',
    
    293
    -                    attrs={'xmlns': Namespace.SASL, 'mechanism': 'GSSAPI'},
    
    294
    -                    payload=b64encode(token))
    
    295
    -        self._client.send_nonza(node)
    
    296 283
     
    
    297
    -    def response(self, server_message, *args, **kwargs):
    
    298
    -        server_message = b64decode(server_message)
    
    284
    +        return b64encode(token)
    
    285
    +
    
    286
    +    def get_response_data(self, data: str) -> str:
    
    287
    +        byte_data = b64decode(data)
    
    299 288
             try:
    
    300 289
                 if not self.ctx.complete:
    
    301
    -                output_token = self.ctx.step(server_message)
    
    290
    +                output_token = self.ctx.step(byte_data)
    
    302 291
                 else:
    
    303
    -                _result = self.ctx.unwrap(server_message)
    
    292
    +                _result = self.ctx.unwrap(byte_data)
    
    304 293
                     # TODO(jelmer): Log result.message
    
    305 294
                     data = b'\x00\x00\x00\x00' + bytes(self.ctx.initiator_name)
    
    306 295
                     output_token = self.ctx.wrap(data, False).message
    
    307 296
             except (gssapi.exceptions.GeneralError, gssapi.raw.misc.GSSError) as e:
    
    308 297
                 raise AuthFail(e)
    
    309
    -        response = b64encode(output_token)
    
    310
    -        node = Node('response',
    
    311
    -                    attrs={'xmlns': Namespace.SASL},
    
    312
    -                    payload=response)
    
    313
    -        self._client.send_nonza(node)
    
    298
    +
    
    299
    +        return b64encode(output_token)
    
    314 300
     
    
    315 301
     
    
    316
    -class SCRAM:
    
    302
    +class SCRAM(BaseMechanism):
    
    317 303
     
    
    318
    -    _mechanism = ''
    
    304
    +    name = ''
    
    319 305
         _channel_binding = ''
    
    320 306
         _hash_method = ''
    
    321 307
     
    
    322
    -    def __init__(self, client, channel_binding):
    
    323
    -        self._client = client
    
    324
    -        self._channel_binding_data = channel_binding
    
    308
    +    def __init__(self, *args, **kwargs) -> None:
    
    309
    +        BaseMechanism.__init__(self, *args, **kwargs)
    
    310
    +        self._channel_binding_data = None
    
    325 311
             self._client_nonce = '%x' % int(binascii.hexlify(os.urandom(24)), 16)
    
    326 312
             self._client_first_message_bare = None
    
    327 313
             self._server_signature = None
    
    328
    -        self._password = None
    
    314
    +
    
    315
    +    def set_channel_binding_data(self, data: bytes) -> None:
    
    316
    +        self._channel_binding_data = data
    
    329 317
     
    
    330 318
         @property
    
    331
    -    def nonce_length(self):
    
    319
    +    def nonce_length(self) -> int:
    
    332 320
             return len(self._client_nonce)
    
    333 321
     
    
    334 322
         @property
    
    335
    -    def _b64_channel_binding_data(self):
    
    336
    -        if self._mechanism.endswith('PLUS'):
    
    323
    +    def _b64_channel_binding_data(self) -> str:
    
    324
    +        if self.name.endswith('PLUS'):
    
    337 325
                 return b64encode(b'%s%s' % (self._channel_binding.encode(),
    
    338 326
                                             self._channel_binding_data))
    
    339 327
             return b64encode(self._channel_binding)
    
    340 328
     
    
    341 329
         @staticmethod
    
    342
    -    def _scram_parse(scram_data):
    
    330
    +    def _scram_parse(scram_data: str) -> dict[str, str]:
    
    343 331
             return dict(s.split('=', 1) for s in scram_data.split(','))
    
    344 332
     
    
    345
    -    def initiate(self, username, password):
    
    346
    -        self._password = password
    
    347
    -        self._client_first_message_bare = 'n=%s,r=%s' % (username,
    
    333
    +    def get_initiate_data(self) -> str:
    
    334
    +        self._client_first_message_bare = 'n=%s,r=%s' % (self._username,
    
    348 335
                                                              self._client_nonce)
    
    349 336
             client_first_message = '%s%s' % (self._channel_binding,
    
    350 337
                                              self._client_first_message_bare)
    
    351 338
     
    
    352
    -        payload = b64encode(client_first_message)
    
    353
    -        node = Node('auth',
    
    354
    -                    attrs={'xmlns': Namespace.SASL,
    
    355
    -                           'mechanism': self._mechanism},
    
    356
    -                    payload=[payload])
    
    357
    -        self._client.send_nonza(node)
    
    339
    +        return b64encode(client_first_message)
    
    358 340
     
    
    359
    -    def response(self, server_first_message):
    
    360
    -        server_first_message = b64decode(server_first_message).decode()
    
    341
    +    def get_response_data(self, data) -> str:
    
    342
    +        server_first_message = b64decode(data).decode()
    
    361 343
             challenge = self._scram_parse(server_first_message)
    
    362 344
     
    
    363 345
             client_nonce = challenge['r'][:self.nonce_length]
    
    ... ... @@ -397,64 +379,66 @@ class SCRAM:
    397 379
             server_key = self._hmac(salted_password, 'Server Key')
    
    398 380
             self._server_signature = self._hmac(server_key, auth_message)
    
    399 381
     
    
    400
    -        payload = b64encode(client_finale_message)
    
    401
    -        node = Node('response',
    
    402
    -                    attrs={'xmlns': Namespace.SASL},
    
    403
    -                    payload=[payload])
    
    404
    -        self._client.send_nonza(node)
    
    382
    +        return b64encode(client_finale_message)
    
    405 383
     
    
    406
    -    def success(self, server_last_message):
    
    407
    -        server_last_message = b64decode(server_last_message).decode()
    
    384
    +    def validate_success_data(self, data: str) -> None:
    
    385
    +        server_last_message = b64decode(data).decode()
    
    408 386
             success = self._scram_parse(server_last_message)
    
    409 387
             server_signature = b64decode(success['v'])
    
    410 388
             if server_signature != self._server_signature:
    
    411 389
                 raise AuthFail('Invalid server signature')
    
    412 390
     
    
    413
    -    def _hmac(self, key, message):
    
    391
    +    def _hmac(self, key: bytes, message: str) -> bytes:
    
    414 392
             return hmac.new(key=key,
    
    415 393
                             msg=message.encode(),
    
    416 394
                             digestmod=self._hash_method).digest()
    
    417 395
     
    
    418 396
         @staticmethod
    
    419
    -    def _xor(x, y):
    
    397
    +    def _xor(x: bytes, y: bytes) -> bytes:
    
    420 398
             return bytes([px ^ py for px, py in zip(x, y)])
    
    421 399
     
    
    422
    -    def _h(self, data):
    
    400
    +    def _h(self, data: bytes) -> bytes:
    
    423 401
             return hashlib.new(self._hash_method, data).digest()
    
    424 402
     
    
    425 403
     
    
    426 404
     class SCRAM_SHA_1(SCRAM):
    
    427 405
     
    
    428
    -    _mechanism = 'SCRAM-SHA-1'
    
    406
    +    name = 'SCRAM-SHA-1'
    
    429 407
         _channel_binding = 'n,,'
    
    430 408
         _hash_method = 'sha1'
    
    431 409
     
    
    432 410
     
    
    433 411
     class SCRAM_SHA_1_PLUS(SCRAM_SHA_1):
    
    434 412
     
    
    435
    -    _mechanism = 'SCRAM-SHA-1-PLUS'
    
    413
    +    name = 'SCRAM-SHA-1-PLUS'
    
    436 414
         _channel_binding = 'p=tls-unique,,'
    
    437 415
     
    
    438 416
     
    
    439 417
     class SCRAM_SHA_256(SCRAM):
    
    440 418
     
    
    441
    -    _mechanism = 'SCRAM-SHA-256'
    
    419
    +    name = 'SCRAM-SHA-256'
    
    442 420
         _channel_binding = 'n,,'
    
    443 421
         _hash_method = 'sha256'
    
    444 422
     
    
    445 423
     
    
    446 424
     class SCRAM_SHA_256_PLUS(SCRAM_SHA_256):
    
    447 425
     
    
    448
    -    _mechanism = 'SCRAM-SHA-256-PLUS'
    
    426
    +    name = 'SCRAM-SHA-256-PLUS'
    
    449 427
         _channel_binding = 'p=tls-unique,,'
    
    450 428
     
    
    451 429
     
    
    452 430
     class SCRAM_SHA_512(SCRAM):
    
    453 431
     
    
    454
    -    _mechanism = 'SCRAM-SHA-512'
    
    432
    +    name = 'SCRAM-SHA-512'
    
    455 433
         _channel_binding = 'n,,'
    
    456 434
         _hash_method = 'sha512'
    
    457 435
     
    
    458 436
     
    
    437
    +class SCRAM_SHA_512_PLUS(SCRAM_SHA_512):
    
    438
    +
    
    439
    +    name = 'SCRAM-SHA-512-PLUS'
    
    440
    +    _channel_binding = 'p=tls-unique,,'
    
    441
    +
    
    442
    +
    
    459 443
     class AuthFail(Exception):
    
    460 444
         pass

  • test/unit/test_sasl_scram.py
    1 1
     import unittest
    
    2 2
     from unittest.mock import Mock
    
    3 3
     
    
    4
    -from nbxmpp.auth import SCRAM_SHA_1
    
    4
    +from nbxmpp.sasl import SCRAM_SHA_1
    
    5 5
     from nbxmpp.util import b64encode
    
    6 6
     
    
    7 7
     # Test vector from https://wiki.xmpp.org/web/SASL_and_SCRAM-SHA-1
    
    8 8
     
    
    9
    +
    
    9 10
     class SCRAM(unittest.TestCase):
    
    10 11
         def setUp(self):
    
    11 12
             self.con = Mock()
    
    12
    -        self._method = SCRAM_SHA_1(self.con, None)
    
    13
    -        self._method._client_nonce = 'fyko+d2lbbFgONRv9qkxdawL'
    
    14 13
             self.maxDiff = None
    
    15 14
             self._username = 'user'
    
    16 15
             self._password = 'pencil'
    
    17
    -
    
    18
    -        self.auth = '<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="SCRAM-SHA-1">%s</auth>' % b64encode('n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL')
    
    19
    -        self.challenge = b64encode('r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096')
    
    20
    -        self.response = '<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">%s</response>' % b64encode('c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=')
    
    21
    -        self.success = b64encode('v=rmF9pqV8S7suAoZWja4dJRkFsKQ=')
    
    16
    +        self._mechanism = SCRAM_SHA_1(self._username, self._password, None)
    
    17
    +        self._mechanism._client_nonce = 'fyko+d2lbbFgONRv9qkxdawL'
    
    22 18
     
    
    23 19
         def test_auth(self):
    
    24
    -        self._method.initiate(self._username, self._password)
    
    25
    -        self.assertEqual(self.auth, str(self.con.send_nonza.call_args[0][0]))
    
    20
    +        initial = b64encode('n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL')
    
    21
    +        data = self._mechanism.get_initiate_data()
    
    22
    +        self.assertEqual(data, initial)
    
    23
    +
    
    24
    +        challenge = b64encode('r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096')
    
    25
    +        data = self._mechanism.get_response_data(challenge)
    
    26 26
     
    
    27
    -        self._method.response(self.challenge)
    
    28
    -        self.assertEqual(self.response, str(self.con.send_nonza.call_args[0][0]))
    
    27
    +        response = b64encode('c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=')
    
    28
    +        self.assertEqual(data, response)
    
    29 29
     
    
    30
    -        self._method.success(self.success)
    
    30
    +        success = b64encode('v=rmF9pqV8S7suAoZWja4dJRkFsKQ=')
    
    31
    +        self._mechanism.validate_success_data(success)
    
    31 32
     
    
    32 33
     
    
    33 34
     if __name__ == '__main__':