Beispiel #1
0
    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().decode(), digits=no_digits)
                device = user.phonedevice_set.create(
                    name="backup", number="+31101234567", method="sms", key=random_hex().decode()
                )

                # 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")
                fake.return_value.send_sms.assert_called_with(
                    device=device, token=str(totp(device.bin_key, digits=no_digits)).zfill(no_digits)
                )

                # 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")
                fake.return_value.make_call.assert_called_with(
                    device=device, token=str(totp(device.bin_key, digits=no_digits)).zfill(no_digits)
                )

            # Valid token should be accepted.
            response = self._post({"token-otp_token": totp(device.bin_key), "login_view-current_step": "token"})
            self.assertRedirects(response, str(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=ANY, request=ANY, user=user, device=device)
    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().decode(),
                                           digits=no_digits)
                device = user.phonedevice_set.create(name='backup', number='+31101234567',
                                                     method='sms',
                                                     key=random_hex().decode())

                # 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')
                fake.return_value.send_sms.assert_called_with(
                    device=device,
                    token=str(totp(device.bin_key, digits=no_digits)).zfill(no_digits))

                # 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')
                fake.return_value.make_call.assert_called_with(
                    device=device,
                    token=str(totp(device.bin_key, digits=no_digits)).zfill(no_digits))

            # 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)
    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().decode(),
                                           digits=no_digits)
                device = user.phonedevice_set.create(name='backup', number='+31101234567',
                                                     method='sms',
                                                     key=random_hex().decode())

                # 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')
                fake.return_value.send_sms.assert_called_with(
                    device=device,
                    token=str(totp(device.bin_key, digits=no_digits)).zfill(no_digits))

                # 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')
                fake.return_value.make_call.assert_called_with(
                    device=device,
                    token=str(totp(device.bin_key, digits=no_digits)).zfill(no_digits))

            # Valid token should be accepted.
            response = self._post({'token-otp_token': totp(device.bin_key),
                                   'login_view-current_step': 'token'})
            self.assertRedirects(response, str(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=ANY, request=ANY, user=user, device=device)
Beispiel #4
0
def tf_setup(request):
    if request.method == 'POST':
        key = request.session.get('tf_key')
        form = PleioTOTPDeviceForm(data=request.POST,
                                   key=key,
                                   user=request.user)

        if form.is_valid():
            device = form.save()
            django_otp.login(request, device)
            return redirect('two_factor:setup_complete')

    else:
        key = random_hex(20).decode('ascii')
        rawkey = unhexlify(key.encode('ascii'))
        b32key = b32encode(rawkey).decode('utf-8')

        request.session['tf_key'] = key
        request.session['django_two_factor-qr_secret_key'] = b32key

    return render(
        request, 'tf_setup.html', {
            'form': PleioTOTPDeviceForm(key=key, user=request.user),
            'QR_URL': reverse('two_factor:qr')
        })
Beispiel #5
0
 def generate_token(self):
     print(self.key)
     self.t0 = time.time()
     self.key = random_hex(20)
     totp = TOTP(key=bytearray(self.key, 'utf-8'))
     self.last_t = totp
     return self.last_t
Beispiel #6
0
    def __init__(self, *args, **kwargs):

        self.step = kwargs.get('step', 120)
        self.last_t = kwargs.get('last_t', -1)
        self.key = kwargs.get('key', random_hex())
        self.digits = kwargs.get('digits', 6)
        self.verified = kwargs.get('verified', False)
    def test_with_backup_token(self):
        user = self.create_user()
        user.totpdevice_set.create(name='default', key=random_hex().decode())
        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.assertContains(response, 'Please enter your OTP token')

        # Valid code should be accepted
        response = self._post({
            'backup-otp_token': 'abcdef123',
            'login_view-current_step': 'backup'
        })
        self.assertRedirects(response, str(settings.LOGIN_REDIRECT_URL))
    def test_with_backup_token(self, mock_signal):
        user = self.create_user()
        user.totpdevice_set.create(name='default', key=random_hex().decode())
        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.']})

        # 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):
        user = self.create_user()
        device = user.totpdevice_set.create(name='default',
                                            key=random_hex().decode())

        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__': ['Please enter your OTP token']})

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

        self.assertEqual(device.persistent_id,
                         self.client.session.get(DEVICE_ID_SESSION_KEY))
Beispiel #10
0
    def test_with_backup_token(self, mock_signal):
        user = self.create_user()
        user.totpdevice_set.create(name='default', key=random_hex().decode())
        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.']})

        # 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)
Beispiel #11
0
class PhoneDevice(Device):
    """
    Model with phone number and token seed linked to a user.
    """
    number = models.CharField(max_length=16,
                              validators=[phone_number_validator],
                              verbose_name=_('number'))
    key = models.CharField(max_length=40,
                           validators=[hex_validator()],
                           default=lambda: random_hex(20),
                           help_text="Hex-encoded secret key")
    method = models.CharField(max_length=4,
                              choices=PHONE_METHODS,
                              verbose_name=_('method'))

    @property
    def bin_key(self):
        return unhexlify(self.key.encode())

    def verify_token(self, token):
        for drift in range(-5, 1):
            if totp(self.bin_key, drift=drift) == token:
                return True
        return False

    def generate_challenge(self):
        """
        Sends the current TOTP token to `self.number` using `self.method`.
        """
        token = '%06d' % totp(self.bin_key)
        if self.method == 'call':
            make_call(device=self, token=token)
        else:
            send_sms(device=self, token=token)
Beispiel #12
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(20).decode('ascii')
     self.storage.extra_data['keys'][step] = key
     return key
    def test_with_backup_phone(self, fake):
        user = User.objects.create_user("bouke", None, "secret")
        user.totpdevice_set.create(name="default", key=random_hex().decode())
        device = user.phonedevice_set.create(name="backup", number="123456789", method="sms", key=random_hex().decode())

        # 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 123****89")

        # 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 123****89")

        # 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")
        fake.return_value.send_sms.assert_called_with(device=device, token="%06d" % totp(device.bin_key))

        # 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")
        fake.return_value.make_call.assert_called_with(device=device, token="%06d" % totp(device.bin_key))
    def setUpTestData(cls):
        super(BaseTestCase, cls).setUpTestData()
        cls.wizard_url = reverse("login")

        cls.standard_user = User.objects.create(username="******",
                                                birth_date=datetime.date(
                                                    2000, 1, 1))
        cls.standard_user.set_password("1234")
        cls.standard_user.save()

        cls.twofa_user = User.objects.create(username="******",
                                             birth_date=datetime.date(
                                                 2000, 1, 1))
        cls.twofa_user.set_password("1234")
        cls.twofa_user.save()

        cls.totp_device = TOTPDevice.objects.create(user=cls.twofa_user,
                                                    name="default",
                                                    confirmed=True,
                                                    key=random_hex().decode())

        cls.super_user = User.objects.create_superuser(
            username="******",
            email="*****@*****.**",
            password="******",
            birth_date=datetime.date(2000, 1, 1))
        cls.super_user.save()
 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(20).decode('ascii')
     self.storage.extra_data['keys'][step] = key
     return key
 def test_verify(self):
     for no_digits in (6, 8):
         with self.settings(TWO_FACTOR_TOTP_DIGITS=no_digits):
             device = PhoneDevice(key=random_hex().decode())
             self.assertFalse(device.verify_token(-1))
             self.assertFalse(device.verify_token('foobar'))
             self.assertTrue(device.verify_token(totp(device.bin_key, digits=no_digits)))
Beispiel #17
0
    def test_with_backup_token(self, mock_signal):
        user = self.create_user()
        user.totpdevice_set.create(name="default", key=random_hex().decode())
        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.assertContains(response, "Please enter your OTP token")

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

        # Check that the signal was fired.
        mock_signal.assert_called_with(sender=ANY, request=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().decode())

        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_verify(self):
     for no_digits in (6, 8):
         with self.settings(TWO_FACTOR_TOTP_DIGITS=no_digits):
             device = PhoneDevice(key=random_hex().decode())
             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_generator(self, mock_signal):
        user = self.create_user()
        device = TOTPDevice.objects.create(
            user=user, name='default', key=random_hex().decode()
        )

        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__': ['Please enter your OTP token']})

        response = self._post({'token-otp_token': totp(device.bin_key),
                               'login_view-current_step': 'token'})
        self.assertRedirects(response, str(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=ANY, request=ANY, user=user, device=device)
    def test_with_backup_token(self):
        user = User.objects.create_user('bouke', None, 'secret')
        user.totpdevice_set.create(name='default', key=random_hex().decode())
        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.assertContains(response, 'Please enter your OTP token')

        # Valid code should be accepted
        response = self._post({'backup-otp_token': 'abcdef123',
                               'login_view-current_step': 'backup'})
        self.assertRedirects(response, str(settings.LOGIN_REDIRECT_URL))
    def test_with_backup_token(self, mock_signal):
        user = self.create_user()
        TOTPDevice.objects.create(user=user, name='default', key=random_hex().decode())
        device = StaticDevice.objects.create(user=user, 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.assertContains(response, 'Please enter your OTP token')

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

        # Check that the signal was fired.
        mock_signal.assert_called_with(sender=ANY, request=ANY, user=user, device=device)
    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())

        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))
    def test_login_different_user_with_otp_on_existing_session(self):
        self.create_user()
        vedran_user = self.create_user(username='******')
        device = vedran_user.totpdevice_set.create(name='default',
                                                   key=random_hex())

        response = self._post({
            'auth-username': '******',
            'auth-password': '******',
            'login_view-current_step': 'auth'
        })
        self.assertRedirects(response,
                             resolve_url(settings.LOGIN_REDIRECT_URL))

        response = self._post({
            'auth-username': '******',
            'auth-password': '******',
            'login_view-current_step': 'auth'
        })
        self.assertContains(response, 'Token:')
        response = self._post({
            'token-otp_token': totp(device.bin_key),
            'login_view-current_step': 'token',
            'token-remember': 'on'
        })
        self.assertRedirects(response,
                             resolve_url(settings.LOGIN_REDIRECT_URL))
Beispiel #25
0
    def test_throttle_with_generator(self, mock_signal):
        user = self.create_user()
        device = user.totpdevice_set.create(name='default',
                                            key=random_hex().decode())

        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.'
                ]
            })
Beispiel #26
0
def device_auth_user(client, auth_user, test_password):
    """ An two-factor authenticated user object using device token """
    auth_user.totpdevice_set.create(name='default',
                                    key=random_hex(),
                                    confirmed=True)
    client.login(username=auth_user.get_username(), password=test_password)
    return auth_user
Beispiel #27
0
    def test_with_generator(self, mock_signal):
        user = self.create_user()
        device = user.totpdevice_set.create(name='default',
                                            key=random_hex().decode())

        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.']})

        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_with_backup_phone(self, fake):
        user = self.create_user()
        user.totpdevice_set.create(name='default', key=random_hex().decode())
        device = user.phonedevice_set.create(name='backup',
                                             number='123456789',
                                             method='sms',
                                             key=random_hex().decode())

        # 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 123****89')

        # 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 123****89')

        # 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')
        fake.return_value.send_sms.assert_called_with(device=device,
                                                      token='%06d' %
                                                      totp(device.bin_key))

        # 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')
        fake.return_value.make_call.assert_called_with(device=device,
                                                       token='%06d' %
                                                       totp(device.bin_key))
    def test_with_backup_phone(self, mock_signal, fake):
        user = self.create_user()
        TOTPDevice.objects.create(user=user, name='default', key=random_hex().decode())
        device = PhoneDevice.objects.create(
            user=user, name='backup', number='00123456789',
            method='sms', key=random_hex().decode()
        )

        # 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 001******89')

        # 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 001******89')

        # 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')
        fake.return_value.send_sms.assert_called_with(
            device=device, token='%06d' % totp(device.bin_key))

        # 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')
        fake.return_value.make_call.assert_called_with(
            device=device, token='%06d' % totp(device.bin_key))

        # Valid token should be accepted.
        response = self._post({'token-otp_token': totp(device.bin_key),
                               'login_view-current_step': 'token'})
        self.assertRedirects(response, str(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=ANY, request=ANY, user=user, device=device)
 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
     """
     device = PhoneDevice(key=random_hex().decode())
     self.assertTrue(device.verify_token(str(totp(device.bin_key))))
 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(20).decode('ascii')
         self.storage.extra_data[self.key_name] = key
     return self.storage.extra_data[self.key_name]
Beispiel #32
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(20).decode('ascii')
         self.storage.extra_data[self.key_name] = key
     return self.storage.extra_data[self.key_name]
 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
     """
     device = PhoneDevice(key=random_hex().decode())
     self.assertTrue(device.verify_token(str(totp(device.bin_key))))
Beispiel #34
0
def two_factor_form(request, page_action):
    two_factor_authorization =  {}
    if page_action == '2fa_setup-a2f_configuration':
        key = random_hex(20).decode('ascii')
        rawkey = unhexlify(key.encode('ascii'))
        b32key = b32encode(rawkey).decode('utf-8')

        request.session['tf_key'] = key
        request.session['django_two_factor-qr_secret_key'] = b32key

        two_factor_authorization = ({
            'form': PleioTOTPDeviceForm(key=key, user=request.user),
            'QR_URL': reverse('two_factor:qr')
        })
        two_factor_authorization['state'] = 'setup'

    elif page_action == '2fa_setupnext-a2f_configurationsuivante':
        key = request.session.get('tf_key')
        form = PleioTOTPDeviceForm(data=request.POST, key=key, user=request.user)
        if form.is_valid():
            device = form.save()
            django_otp.login(request, device)
            two_factor_authorization['default_device'] = True
            two_factor_authorization['show_state'] = True
        else:
            two_factor_authorization['form'] = form
            two_factor_authorization['QR_URL'] = reverse('two_factor:qr')
            two_factor_authorization['state'] = 'setup'

    elif page_action == '2fa_disable-a2f_desactiver':
        two_factor_authorization = DisableView.as_view(template_name='security_pages.html')(request).context_data
        two_factor_authorization['state'] = 'disable'

    elif page_action == '2fa_disableconfirm-a2f_desactiverconfirmer':
        two_factor_authorization = DisableView.as_view(template_name='security_pages.html')(request)
        two_factor_authorization['state'] = 'default'
        two_factor_authorization['show_state'] = 'true'

    elif page_action == '2fa_showcodes-a2f_afficherlescodes':
        two_factor_authorization = PleioBackupTokensView.as_view(template_name='backup_tokens.html')(request).context_data
        two_factor_authorization['default_device'] = 'true'
        two_factor_authorization['state'] = 'codes'
        two_factor_authorization['show_state'] = 'true'

    elif page_action == '2fa_generatecodes-a2f_genererdescodes':
        two_factor_authorization = PleioBackupTokensView.as_view(template_name='security_pages.html')(request).context_data
        two_factor_authorization['default_device'] = 'true'
        two_factor_authorization['show_state'] = 'true'
        two_factor_authorization['state'] = 'codes'

    else:
        two_factor_authorization = PleioProfileView.as_view(template_name='security_pages.html')(request).context_data
        two_factor_authorization['state'] = 'default'
        two_factor_authorization['show_state'] = 'true'

    return two_factor_authorization
 def __init__(self):
     """
     Generating random key, verified counter,
     otp digit and validity period
     """
     self.key = random_hex(20)
     self.last_verified_counter = -1
     self.verified = False
     self.number_of_digits = 6
     self.token_validity_period = 300
 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().decode())
             self.assertTrue(device.verify_token(str(totp(device.bin_key, digits=no_digits))))
 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().decode())
             self.assertTrue(device.verify_token(str(totp(device.bin_key, digits=no_digits))))
def unique_key():
    """
    :return: create random key to send sms
    """
    key = random_hex(20)
    try:
        User.objects.get(key=key)
    except ObjectDoesNotExist:
        return key
    else:
        unique_key()
    def dispatch(self, request, *args, **kwargs):

        if 'allauth_otp_qr_secret_key' not in request.session:
            self.secret_key = random_hex(20).decode('ascii')
            request.session['allauth_otp_qr_secret_key'] = self.secret_key
        else:
            self.secret_key = request.session['allauth_otp_qr_secret_key']

        if request.user.totpdevice_set.exists():
            return HttpResponseRedirect(reverse_lazy('profile'))
        return super(TwoFactorSetup, self).dispatch(request, *args, **kwargs)
    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_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().decode(),
                                           digits=no_digits)
                device = user.phonedevice_set.create(name='backup',
                                                     number='+31101234567',
                                                     method='sms',
                                                     key=random_hex().decode())

                # 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')

                self._phone_validation(mock_signal, fake, device, no_digits)

            # 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)
Beispiel #42
0
 def __init__(self):
     # secret key that will be used to generate a token,
     # User can provide a custom value to the key.
     self.key = random_hex(20)
     # counter with which last token was verified.
     # Next token must be generated at a higher counter value.
     self.last_verified_counter = -1
     # this value will return True, if a token has been successfully
     # verified.
     self.verified = False
     # number of digits in a token. Default is 6
     self.number_of_digits = 6
     # validity period of a token. Default is 5 minute.
     self.token_validity_period = 300
    def test_with_generator(self):
        user = User.objects.create_user("bouke", None, "secret")
        device = user.totpdevice_set.create(name="default", key=random_hex().decode())

        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__": ["Please enter your OTP token"]})

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

        self.assertEqual(device.persistent_id, self.client.session.get(DEVICE_ID_SESSION_KEY))
Beispiel #44
0
    def post_save(sender, **kwargs):
        instance = kwargs['instance']
        obj, created = PhoneDevice.objects.get_or_create(
            user=instance,
            defaults={
                'name': 'default',
                'number': instance.phone,
                'method': 'sms',
                'key': random_hex().decode('ascii')
            }
        )

        if not created:
            obj.number = instance.phone
            obj.save()
    def test_with_backup_phone(self, fake):
        user = User.objects.create_user('bouke', None, 'secret')
        user.totpdevice_set.create(name='default', key=random_hex().decode())
        device = user.phonedevice_set.create(name='backup', number='123456789',
                                             method='sms',
                                             key=random_hex().decode())

        # 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 123****89')

        # 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 123****89')

        # 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')
        fake.return_value.send_sms.assert_called_with(
            device=device, token='%06d' % totp(device.bin_key))

        # 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')
        fake.return_value.make_call.assert_called_with(
            device=device, token='%06d' % totp(device.bin_key))
    def verify_email(self, request=None):
        if getattr(self, 'email', None):
            service = self.get_email_service()

            if not service:
                service = EmailService(
                    user=self,
                    name='default', # temporal
                    email=self.email,
                    key=random_hex(20), # OBSOLETED: .decode('ascii'),
                    confirmed=False,
                )

                service.save()

            service.generate_challenge(request)
            return service
Beispiel #47
0
    def verify_whatsapp(self, request=None):
        if getattr(self, 'whatsapp', None):
            service = self.get_whatsapp_service()

            if not service:
                service = WhatsappService(
                    user=self,
                    name='default',  # temporal
                    whatsapp=self.whatsapp,
                    key=random_hex(20),  # OBSOLETED: .decode('ascii'),
                    confirmed=False,
                )

                service.save()

            service.generate_challenge(request)
            return service
Beispiel #48
0
    def verify_telegram(self, request=None):
        if getattr(self, 'telegram', None):
            service = self.get_telegram_service()

            if not service:
                service = TelegramService(
                    user=self,
                    name='default',  # temporal
                    telegram=self.telegram,
                    key=random_hex(20),  # OBSOLETED: .decode('ascii'),
                    confirmed=False,
                )

                service.save()

            service.generate_challenge(request)
            return service
    def test_throttle_with_generator(self, mock_signal):
        user = self.create_user()
        device = user.totpdevice_set.create(name='default',
                                            key=random_hex().decode())

        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.']})
Beispiel #50
0
    def test_with_generator(self, mock_signal):
        user = self.create_user()
        device = user.totpdevice_set.create(name="default", key=random_hex().decode())

        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__": ["Please enter your OTP token"]})

        response = self._post({"token-otp_token": totp(device.bin_key), "login_view-current_step": "token"})
        self.assertRedirects(response, str(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=ANY, request=ANY, user=user, device=device)
    def test_with_backup_token(self):
        user = User.objects.create_user("bouke", None, "secret")
        user.totpdevice_set.create(name="default", key=random_hex().decode())
        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.assertContains(response, "Please enter your OTP token")

        # Valid code should be accepted
        response = self._post({"backup-otp_token": "abcdef123", "login_view-current_step": "backup"})
        self.assertRedirects(response, str(settings.LOGIN_REDIRECT_URL))
    def test_with_generator(self):
        user = User.objects.create_user('bouke', None, 'secret')
        device = user.totpdevice_set.create(name='default',
                                            key=random_hex().decode())

        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__': ['Please enter your OTP token']})

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

        self.assertEqual(device.persistent_id,
                         self.client.session.get(DEVICE_ID_SESSION_KEY))
Beispiel #53
0
    def form_valid(self, form):
        cleaned_data = form.cleaned_data

        # If the user was invited, get the invitation data out of the session.
        invitation_data = self.request.session.get(settings.REGISTRATION_SESSION_KEY, {}).get('invitation_data', {})

        if invitation_data and invitation_data['email'] == cleaned_data['email']:
            # The user was invited and has chosen to register with the same email as they got the invite in.
            # This means we can skip the email address verification.
            user = LilyUser.objects.create_user(
                tenant_id=invitation_data['tenant_id'],
                first_name=invitation_data['first_name'],
                email=invitation_data['email'],
                password=cleaned_data['password']
            )

            # Because we don't call `authenticate` we need to set the authentication backend manually.
            user.backend = settings.AUTHENTICATION_MODEL_BACKEND

            # Log the new user in.
            login(self.request, user)

            # The email used by this user can no longer have valid invites, so delete them all.
            UserInvite.objects.filter(email=user.email).delete()

            # Send welcome mail to the new user.
            send_templated_mail(
                template_name='users/registration/email/welcome.email',
                recipient_list=[user.email, ],
                context={
                    'user': user,
                },
                from_email=settings.EMAIL_PERSONAL_HOST_USER,
                auth_user=settings.EMAIL_PERSONAL_HOST_USER,
                auth_password=settings.EMAIL_PERSONAL_HOST_PASSWORD
            )

            return HttpResponseRedirect(reverse('register_profile'))

        else:
            # The user was not invited or has chosen to register with a different email address.
            # This means we will have to verify the address by sending a verification mail there.
            code = totp(random_hex(20), digits=6)  # Generate six digit code for validation.

            # Use the invite's first name or guess the first name from the given email address.
            first_name = invitation_data.get('first_name') or guess_name_from_email(cleaned_data['email'])[0]

            # Capitalize the first name, but only use it if it's more than one letter.
            first_name = first_name.capitalize() if len(first_name) > 1 else ''

            self.request.session[settings.REGISTRATION_SESSION_KEY]['auth_data'] = {
                'email': cleaned_data['email'],
                'password': cleaned_data['password'],
                'first_name': first_name,
                'code': code,
            }

            # Because we don't modify the session key, but a subkey the save is not done automatically.
            self.request.session.save()

            # Send welcome mail to the new user and include their email verification code.
            send_templated_mail(
                template_name='users/registration/email/welcome_with_verification.email',
                recipient_list=[cleaned_data['email'], ],
                context={
                    'first_name': first_name,
                    'code': code,
                },
                from_email=settings.EMAIL_PERSONAL_HOST_USER,
                auth_user=settings.EMAIL_PERSONAL_HOST_USER,
                auth_password=settings.EMAIL_PERSONAL_HOST_PASSWORD
            )

        return HttpResponseRedirect(reverse('register_verify_email'))
Beispiel #54
0
def default_key():
    return force_text(random_hex(20))
 def test_verify(self):
     device = PhoneDevice(key=random_hex().decode())
     self.assertFalse(device.verify_token(-1))
     self.assertTrue(device.verify_token(totp(device.bin_key)))
Beispiel #56
0
def random_hex_str():
    return random_hex().decode('utf-8')
Beispiel #57
0
def default_key():
    return random_hex(20)