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)
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') })
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
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))
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)
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)
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)))
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))
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.' ] })
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
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]
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))))
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)
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))
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
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
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.']})
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))
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'))
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)))
def random_hex_str(): return random_hex().decode('utf-8')
def default_key(): return random_hex(20)