def test_with_backup_phone(self, mock_signal, fake):
        user = self.create_user()
        for no_digits in (6, 8):
            with self.settings(TWO_FACTOR_TOTP_DIGITS=no_digits):
                user.totpdevice_set.create(name='default', key=random_hex_str(),
                                           digits=no_digits)
                device = user.phonedevice_set.create(name='backup', number='+31101234567',
                                                     method='sms',
                                                     key=random_hex_str())

                # Backup phones should be listed on the login form
                response = self._post({'auth-username': '******',
                                       'auth-password': '******',
                                       'login_view-current_step': 'auth'})
                self.assertContains(response, 'Send text message to +31 ** *** **67')

                # Ask for challenge on invalid device
                response = self._post({'auth-username': '******',
                                       'auth-password': '******',
                                       'challenge_device': 'MALICIOUS/INPUT/666'})
                self.assertContains(response, 'Send text message to +31 ** *** **67')

                # Ask for SMS challenge
                response = self._post({'auth-username': '******',
                                       'auth-password': '******',
                                       'challenge_device': device.persistent_id})
                self.assertContains(response, 'We sent you a text message')

                test_call_kwargs = fake.return_value.send_sms.call_args[1]
                self.assertEqual(test_call_kwargs['device'], device)
                self.assertIn(test_call_kwargs['token'],
                              [str(totp(device.bin_key, digits=no_digits, drift=i)).zfill(no_digits)
                               for i in [-1, 0]])

                # Ask for phone challenge
                device.method = 'call'
                device.save()
                response = self._post({'auth-username': '******',
                                       'auth-password': '******',
                                       'challenge_device': device.persistent_id})
                self.assertContains(response, 'We are calling your phone right now')

                test_call_kwargs = fake.return_value.make_call.call_args[1]
                self.assertEqual(test_call_kwargs['device'], device)
                self.assertIn(test_call_kwargs['token'],
                              [str(totp(device.bin_key, digits=no_digits, drift=i)).zfill(no_digits)
                               for i in [-1, 0]])

            # Valid token should be accepted.
            response = self._post({'token-otp_token': totp(device.bin_key),
                                   'login_view-current_step': 'token'})
            self.assertRedirects(response, resolve_url(settings.LOGIN_REDIRECT_URL))
            self.assertEqual(device.persistent_id,
                             self.client.session.get(DEVICE_ID_SESSION_KEY))

            # Check that the signal was fired.
            mock_signal.assert_called_with(sender=mock.ANY, request=mock.ANY, user=user, device=device)
Exemplo n.º 2
0
 def get_key(self, step):
     self.storage.extra_data.setdefault('keys', {})
     if step in self.storage.extra_data['keys']:
         return self.storage.extra_data['keys'].get(step)
     key = random_hex_str(20)
     self.storage.extra_data['keys'][step] = key
     return key
    def test_throttle_with_generator(self, mock_signal):
        user = self.create_user()
        device = user.totpdevice_set.create(name='default',
                                            key=random_hex_str())

        self._post({
            'auth-username': '******',
            'auth-password': '******',
            'login_view-current_step': 'auth'
        })

        # throttle device
        device.throttle_increment()

        response = self._post({
            'token-otp_token': totp(device.bin_key),
            'login_view-current_step': 'token'
        })
        self.assertEqual(
            response.context_data['wizard']['form'].errors, {
                '__all__': [
                    'Invalid token. Please make sure you '
                    'have entered it correctly.'
                ]
            })
    def test_valid_login_no_timeout(self, mock_time):
        mock_time.time.return_value = 12345.12
        user = self.create_user()
        device = user.totpdevice_set.create(name='default',
                                            key=random_hex_str())

        response = self._post({
            'auth-username': '******',
            'auth-password': '******',
            'login_view-current_step': 'auth'
        })
        self.assertContains(response, 'Token:')

        self.assertEqual(self.client.session['wizard_login_view']['user_pk'],
                         str(user.pk))
        self.assertEqual(
            self.client.session['wizard_login_view']['user_backend'],
            'django.contrib.auth.backends.ModelBackend')
        self.assertEqual(
            self.client.session['wizard_login_view']['authentication_time'],
            12345)

        mock_time.time.return_value = 20345.12

        response = self._post({
            'token-otp_token': totp(device.bin_key),
            'login_view-current_step': 'token'
        })
        self.assertRedirects(response,
                             resolve_url(settings.LOGIN_REDIRECT_URL))
        self.assertEqual(self.client.session['_auth_user_id'], str(user.pk))
Exemplo n.º 5
0
 def test_verify(self):
     for no_digits in (6, 8):
         with self.settings(TWO_FACTOR_TOTP_DIGITS=no_digits):
             device = PhoneDevice(key=random_hex_str())
             self.assertFalse(device.verify_token(-1))
             self.assertFalse(device.verify_token('foobar'))
             self.assertTrue(device.verify_token(totp(device.bin_key, digits=no_digits)))
    def test_with_backup_token(self, mock_signal):
        user = self.create_user()
        user.totpdevice_set.create(name='default', key=random_hex_str())
        device = user.staticdevice_set.create(name='backup')
        device.token_set.create(token='abcdef123')

        # Backup phones should be listed on the login form
        response = self._post({'auth-username': '******',
                               'auth-password': '******',
                               'login_view-current_step': 'auth'})
        self.assertContains(response, 'Backup Token')

        # Should be able to go to backup tokens step in wizard
        response = self._post({'wizard_goto_step': 'backup'})
        self.assertContains(response, 'backup tokens')

        # Wrong codes should not be accepted
        response = self._post({'backup-otp_token': 'WRONG',
                               'login_view-current_step': 'backup'})
        self.assertEqual(response.context_data['wizard']['form'].errors,
                         {'__all__': ['Invalid token. Please make sure you '
                                      'have entered it correctly.']})
        # static devices are throttled
        device.throttle_reset()

        # Valid token should be accepted.
        response = self._post({'backup-otp_token': 'abcdef123',
                               'login_view-current_step': 'backup'})
        self.assertRedirects(response, resolve_url(settings.LOGIN_REDIRECT_URL))

        # Check that the signal was fired.
        mock_signal.assert_called_with(sender=mock.ANY, request=mock.ANY, user=user, device=device)
    def test_with_generator(self, mock_signal):
        user = self.create_user()
        device = user.totpdevice_set.create(name='default',
                                            key=random_hex_str())

        response = self._post({'auth-username': '******',
                               'auth-password': '******',
                               'login_view-current_step': 'auth'})
        self.assertContains(response, 'Token:')

        response = self._post({'token-otp_token': '123456',
                               'login_view-current_step': 'token'})
        self.assertEqual(response.context_data['wizard']['form'].errors,
                         {'__all__': ['Invalid token. Please make sure you '
                                      'have entered it correctly.']})

        # reset throttle because we're not testing that
        device.throttle_reset()

        response = self._post({'token-otp_token': totp(device.bin_key),
                               'login_view-current_step': 'token'})
        self.assertRedirects(response, resolve_url(settings.LOGIN_REDIRECT_URL))

        self.assertEqual(device.persistent_id,
                         self.client.session.get(DEVICE_ID_SESSION_KEY))

        # Check that the signal was fired.
        mock_signal.assert_called_with(sender=mock.ANY, request=mock.ANY, user=user, device=device)
    def test_valid_login_expired(self, mock_time, mock_logger):
        mock_time.time.return_value = 12345.12
        user = self.create_user()
        device = user.totpdevice_set.create(name='default',
                                            key=random_hex_str())

        response = self._post({'auth-username': '******',
                               'auth-password': '******',
                               'login_view-current_step': 'auth'})
        self.assertContains(response, 'Token:')

        self.assertEqual(self.client.session['wizard_login_view']['user_pk'], str(user.pk))
        self.assertEqual(
            self.client.session['wizard_login_view']['user_backend'],
            'django.contrib.auth.backends.ModelBackend')
        self.assertEqual(self.client.session['wizard_login_view']['authentication_time'], 12345)

        mock_time.time.return_value = 20345.12

        response = self._post({'token-otp_token': totp(device.bin_key),
                               'login_view-current_step': 'token'})
        self.assertEqual(response.status_code, 200)
        self.assertNotContains(response, 'Token:')
        self.assertContains(response, 'Password:'******'Your session has timed out. Please login again.')

        # Check that a message was logged.
        mock_logger.info.assert_called_with(
            "User's authentication flow has timed out. The user "
            "has been redirected to the initial auth form.")
Exemplo n.º 9
0
 def get_key(self):
     """
     The key is preserved between steps and stored as ascii in the session.
     """
     if self.key_name not in self.storage.extra_data:
         key = random_hex_str(20)
         self.storage.extra_data[self.key_name] = key
     return self.storage.extra_data[self.key_name]
Exemplo n.º 10
0
 def test_verify_token_as_string(self):
     """
     The field used to read the token may be a CharField,
     so the PhoneDevice must be able to validate tokens
     read as strings
     """
     for no_digits in (6, 8):
         with self.settings(TWO_FACTOR_TOTP_DIGITS=no_digits):
             device = PhoneDevice(key=random_hex_str())
             self.assertTrue(device.verify_token(str(totp(device.bin_key, digits=no_digits))))
Exemplo n.º 11
0
    def test_random_hex(self):
        # test that returned random_hex_str is string
        h = random_hex_str()
        self.assertIsInstance(h, six.string_types)
        # hex string must be 40 characters long. If cannot be longer, because CharField max_length=40
        self.assertEqual(len(h), 40)

        # Added tests to verify that we can safely remove IF statement from random_hex_str function
        hh = random_hex().decode('utf-8')
        self.assertIsInstance(hh, six.string_types)
        self.assertEqual(len(hh), 40)
    def test_valid_login_primary_key_stored(self, mock_time):
        mock_time.time.return_value = 12345.12
        user = self.create_user()
        user.totpdevice_set.create(name='default',
                                   key=random_hex_str())

        response = self._post({'auth-username': '******',
                               'auth-password': '******',
                               'login_view-current_step': 'auth'})
        self.assertContains(response, 'Token:')

        self.assertEqual(self.client.session['wizard_login_view']['user_pk'], str(user.pk))
        self.assertEqual(
            self.client.session['wizard_login_view']['user_backend'],
            'django.contrib.auth.backends.ModelBackend')
        self.assertEqual(self.client.session['wizard_login_view']['authentication_time'], 12345)
    def test_valid_login_post_auth_session_clear_of_form_data(self, mock_time):
        mock_time.time.return_value = 12345.12
        user = self.create_user()
        user.totpdevice_set.create(name='default',
                                   key=random_hex_str())

        response = self._post({'auth-username': '******',
                               'auth-password': '******',
                               'login_view-current_step': 'auth'})
        self.assertContains(response, 'Token:')

        self.assertEqual(self.client.session['wizard_login_view']['user_pk'], str(user.pk))
        self.assertEqual(self.client.session['wizard_login_view']['step'], 'token')
        self.assertEqual(self.client.session['wizard_login_view']['step_data'], {'auth': None})
        self.assertEqual(self.client.session['wizard_login_view']['step_files'], {'auth': {}})
        self.assertEqual(self.client.session['wizard_login_view']['validated_step_data'], {})
 def setUp(self):
     super().setUp()
     self.user = self.create_user()
     self.device = self.user.totpdevice_set.create(name='default',
                                                   key=random_hex_str())
Exemplo n.º 15
0
 def test_random_hex(self):
     # test that returned random_hex_str is string
     h = random_hex_str()
     self.assertIsInstance(h, str)
     # hex string must be 40 characters long. If cannot be longer, because CharField max_length=40
     self.assertEqual(len(h), 40)