Esempio n. 1
0
 def post(self, request: HttpRequest):
     """
     Check the Code
     """
     form = TestYourAppOtpForm(request.POST)
     if not form.is_valid():
         return self._send_template(request, form)
     chosen_otp = form.cleaned_data['otp_to_be_tested']
     success = chosen_otp in get_possible_otps(self.secret)
     list_of_otps = get_possible_otps(self.secret, -10, 10)
     return self._send_template(request, form, True, chosen_otp,
                                list_of_otps, success)
Esempio n. 2
0
 def post(self, request: HttpRequest):
     """
     Set the new secret
     """
     form = NewOtpSecretForm(request.POST)
     if not form.is_valid():
         return self._send_otp_form(request, form)
     secret = request.session.get('encrypted_new_totp_secret', None)
     if secret is None:
         form.add_error(
             None, _('No Secret set. Please create a new secret first.'))
         return self._send_otp_form(request, form)
     secret = SymmetricCrypt().decrypt(b64decode(secret.encode('us-ascii')))
     if form.cleaned_data['otp_confirm'] not in get_possible_otps(secret):
         form.add_error('otp_confirm', _('One-time password invalid'))
         return self._send_otp_form(request, form)
     del request.session['encrypted_new_totp_secret']
     try:
         user = HubUser.objects.get(id=request.user.id)
         user.set_totp_secret(secret)
         user.save()
     except DatabaseError:  # pragma: no cover  # Database Safeguard
         form.add_error(
             None,
             _('We are currently unable to update your OTP Secret. Please try again later.'
               ))
         return self._send_otp_form(request, form)
     return self._send_otp_form(request, success=True)
 def test_inactive_user(self):
     """
     User is not allowed to login
     """
     self.assertIsNone(
         TotpAuthenticationBackend().authenticate(  # nosec
             self.request_factory.get('/'),
             username='******',
             password='******',
             one_time_pw=get_possible_otps(b'SUPERSECRETSUPER-SUPERSECRETSUPER', 0, 0)[0]
         )
     )
 def test_all_good(self):
     """
     Everything is correct
     """
     self.assertEqual(
         self.user,
         TotpAuthenticationBackend().authenticate(  # nosec
             self.request_factory.get('/'),
             username='******',
             password='******',
             one_time_pw=get_possible_otps(b'SUPERSECRETSUPER-SUPERSECRETSUPER', 0, 0)[0]
         )
     )
 def post(self, request: HttpRequest, recovery: UUID):
     """
     Set the new secret
     """
     encrypted_secret = request.session.get(
         'encrypted_temporary_otp_secret', None)
     if encrypted_secret is None:  # pragma: no cover  # Safeguard for tinkered session
         return deny_step(request, 'EA10')
     auth_form = ForgottenCredentialsStep3BaseForm(request.POST)
     if not auth_form.is_valid(
     ):  # pragma: no cover  # Safeguard Client Manipulation
         return deny_step(request, 'EA11')
     new_secret = SymmetricCrypt().decrypt(b64decode(encrypted_secret))
     recovery_str = str(recovery)
     try:
         auth = Signer(salt=recovery_str).unsign(
             auth_form.cleaned_data['auth'])
         recovery_data = PendingCredentialRecovery.objects.filter(
             user__is_active=True,
             valid_until__gte=now(),
             recovery_type='otp-secret').get(uuid=recovery, key=auth)
         user = recovery_data.user
     except PendingCredentialRecovery.DoesNotExist:  # pragma: no cover  # Database Safeguard
         return deny_step(request, 'EA12')
     except (ValueError,
             BadSignature):  # pragma: no cover  # Manipulation Safeguard
         return deny_step(request, 'EA13')
     form = ForgottenCredentialsStep3ConfirmOtpForm(request.POST)
     if not form.is_valid():
         return self.show_form(request, new_secret, user.username,
                               recovery_str, form)
     if form.cleaned_data['otp'] not in get_possible_otps(new_secret):
         form.add_error('otp', _('This one time password is not valid.'))
         return self.show_form(request, new_secret, user.username,
                               recovery_str, form)
     with transaction.atomic():
         tx_id = transaction.savepoint()
         user.set_totp_secret(new_secret)
         user.save()
         recovery_data.delete()
         transaction.savepoint_commit(tx_id)
     logout(request)
     messages.add_message(
         request, messages.SUCCESS,
         _('Your new OTP secret has been set. You can login now.'))
     return redirect(reverse_lazy('ha:auth:login'))
Esempio n. 6
0
 def authenticate(self,
                  request: HttpRequest,
                  username=None,
                  password=None,
                  one_time_pw=None) -> Optional[HubUser]:
     # pylint: disable=arguments-differ
     random = SystemRandom()
     for i in range(random.randrange(1, 5)):  # nosec
         HubUser().set_password('against-timing-attack' +
                                str(i))  # Mitigation against timing attack
     if username is None or password is None or one_time_pw is None:
         return None
     if any([
             len(username) < 1,
             len(username) > 150,
             len(password) < 1,
             len(password) > 1000,
             len(one_time_pw) != 6
     ]):
         return None
     c_user = TotpAuthenticationBackend.clean_username(username)
     user_object = super(TotpAuthenticationBackend,
                         self).authenticate(request, c_user,
                                            password)  # type: HubUser
     if user_object is None:
         return None
     if user_object.totp_secret is None:
         return None
     possible_tokens = get_possible_otps(SymmetricCrypt().decrypt(
         user_object.totp_secret))
     if one_time_pw not in possible_tokens:
         return None
     current_time = now()
     starting_at = current_time - datetime.timedelta(hours=1)
     try:
         BurnedOtp.objects.filter(
             user=user_object,
             burned_timestamp__gte=starting_at).get(token=one_time_pw)
         return None
     except BurnedOtp.DoesNotExist:
         BurnedOtp.objects.create(user=user_object, token=one_time_pw)
     return user_object
Esempio n. 7
0
 def test_token_generator(self):
     """
     Test token generation
     """
     test_items = (
         (b'12345678123456781234567812345678', -3, 3),
         (b'12345678123456781234567812345678', -1, 1),
         (b'12345678123456781234567812345678', -0, 0),
         (b'1234567812345678123456781234567812345678123456781234567812345678',
          -3, 3),
         (b'1234567812345678123456781234567812345678123456781234567812345678',
          -1, 1),
         (b'1234567812345678123456781234567812345678123456781234567812345678',
          -0, 0),
         (b'123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678',
          -3, 3),
         (b'123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678',
          -1, 1),
         (b'123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678',
          -0, 0),
         (bytes(SystemRandom().getrandbits(8) for _ in range(32)), -3, 3),
         (bytes(SystemRandom().getrandbits(8) for _ in range(32)), -1, 1),
         (bytes(SystemRandom().getrandbits(8) for _ in range(32)), -0, 0),
         (bytes(SystemRandom().getrandbits(8) for _ in range(64)), -3, 3),
         (bytes(SystemRandom().getrandbits(8) for _ in range(64)), -1, 1),
         (bytes(SystemRandom().getrandbits(8) for _ in range(64)), -0, 0),
         (bytes(SystemRandom().getrandbits(8) for _ in range(96)), -3, 3),
         (bytes(SystemRandom().getrandbits(8) for _ in range(96)), -1, 1),
         (bytes(SystemRandom().getrandbits(8) for _ in range(96)), -0, 0),
     )
     for secret, offset_lower, offset_upper in test_items:
         with self.subTest(
                 'Testing with a secret of "{}", a lower offset of {} and an upper offset of {}'
                 .format(secret, offset_lower, offset_upper)):
             tokens = get_possible_otps(secret, offset_lower, offset_upper)
             self.assertIsNotNone(tokens)
             expected_count = abs(offset_upper) + abs(offset_lower) + 1
             self.assertEqual(expected_count, len(tokens))
             for token in tokens:
                 self.assertRegex(token, r'^\d{6}$')
 def test_only_one_part_wrong(self):
     """
     Testing with correct OTP, but one wrong other part
     """
     test_items = (
         ('mr_right', 'wrong_pass'),
         ('mr_wrong', 'right_pass'),
         ('', 'right_pass'),
         ('mr_right', ''),
         (None, 'right_pass'),
         ('mr_right', None),
     )
     for username, password in test_items:
         with self.subTest(msg='Testing with user "{}" and password "{}"'.format(username, password)):
             self.assertIsNone(
                 TotpAuthenticationBackend().authenticate(
                     self.request_factory.get('/'),
                     username=username,
                     password=password,
                     one_time_pw=get_possible_otps(b'SUPERSECRETSUPER-SUPERSECRETSUPER', 0, 0)[0]
                 )
             )
 def test_separate_use_of_otp(self):
     """
     Test that logging in with independent OTPs works
     """
     tokens = get_possible_otps(b'SUPERSECRETSUPER-SUPERSECRETSUPER', 0, 1)
     self.assertEqual(
         self.user,
         TotpAuthenticationBackend().authenticate(  # nosec
             self.request_factory.get('/'),
             username='******',
             password='******',
             one_time_pw=tokens[0]
         )
     )
     self.assertEqual(
         self.user,
         TotpAuthenticationBackend().authenticate(  # nosec
             self.request_factory.get('/'),
             username='******',
             password='******',
             one_time_pw=tokens[1]
         )
     )
 def test_double_use_of_otp(self):
     """
     What happens, when a otp is used twice?
     """
     otp = get_possible_otps(b'SUPERSECRETSUPER-SUPERSECRETSUPER', 0, 0)[0]
     self.assertEqual(
         self.user,
         TotpAuthenticationBackend().authenticate(  # nosec
             self.request_factory.get('/'),
             username='******',
             password='******',
             one_time_pw=otp
         )
     )
     self.assertEqual(
         None,
         TotpAuthenticationBackend().authenticate(  # nosec
             self.request_factory.get('/'),
             username='******',
             password='******',
             one_time_pw=otp
         )
     )
Esempio n. 11
0
 def post(self, request):
     """
     Handle the POST request
     """
     form = RegistrationStep2Form(request.POST)
     if not form.is_valid():
         return self._send_form(request, form)
     additional_errors = False
     if form.cleaned_data['password1'] != form.cleaned_data['password2']:
         additional_errors = True
         form.add_error('password2', _('The passwords do not match'))
     if any([
             'registration_step2_user_id' not in request.session,
             'registration_step2_username' not in request.session,
             'registration_step2_totp' not in request.session,
     ]):
         return render(request,
                       self.bad_link_template_name, {},
                       content_type=self.content_type)
     user = None
     try:
         user = HubUser.objects.filter(
             is_active=True, totp_secret__isnull=False).get(
                 id=request.session['registration_step2_user_id'])
         validate_password(password=form.cleaned_data['password1'],
                           user=user)
     except HubUser.DoesNotExist:  # pragma: no cover  # Simple safeguard for a User deletion in-between
         return render(request,
                       self.bad_link_template_name, {},
                       content_type=self.content_type)
     except ValidationError as error:
         additional_errors = True
         form.add_error('password1', error)
     if additional_errors:
         form.add_error(
             'otp',
             _('Your one time password may be expired, be sure to provide a current one.'
               ))
         return self._send_form(request, form)
     # Check the OTP
     otp = form.cleaned_data['otp']
     if otp not in get_possible_otps(
             b64decode(request.session['registration_step2_totp'].encode(
                 'ascii'))):
         form.add_error('otp', _('Your one time password has expired'))
         form.add_error('password1', _('Please re-enter your password'))
         return self._send_form(request, form)
     # Put it into the database
     with transaction.atomic():
         tx_id = transaction.savepoint()
         try:
             pending = PendingRegistration.objects.filter(
                 valid_until__gte=now(),
                 user__is_active=True,
                 user__totp_secret__isnull=False,
                 user=user).select_related('user').get(
                     uuid=form.cleaned_data['reg']
                 )  # type: PendingRegistration
         except PendingRegistration.DoesNotExist:  # pragma: no cover  # Safeguard for deletion in-between
             transaction.savepoint_rollback(tx_id)
             return render(request,
                           self.bad_link_template_name, {},
                           content_type=self.content_type)
         signer = Signer(salt=str(form.cleaned_data['reg']))
         try:
             key = signer.unsign(form.cleaned_data['key'])
         except (BadSignature, ValueError
                 ):  # pragma: no cover  # Just a signature safeguard
             transaction.savepoint_rollback(tx_id)
             return render(request,
                           self.bad_link_template_name, {},
                           content_type=self.content_type)
         if key != pending.key:  # pragma: no cover  # Double-safeguard, unreachable without compromised secret key
             transaction.savepoint_rollback(tx_id)
             return render(request,
                           self.bad_link_template_name, {},
                           content_type=self.content_type)
         pending.delete()
         user.set_password(form.cleaned_data['password1'])
         user.save()
         transaction.savepoint_commit(tx_id)
     logout(request)
     return render(request,
                   self.success_template_name, {},
                   content_type=self.content_type)
 def verify_otp(user: HubUser, otp: str):
     """
     Verify that the OTP matches the user
     """
     return otp in get_possible_otps(user.get_totp_secret())