def test_backwards_compatible_url(self): """Ensure that the old 2FA URLs still work.""" user = get_user_model().objects.create(username='******') user.set_password('doe') user.save() totp_model = user.totpdevice_set.create() resp = self.client.post(reverse('account_login'), {'login': '******', 'password': '******'}) self.assertRedirects(resp, reverse('two-factor-authenticate'), fetch_redirect_response=False) # Now ensure that logging in actually works. totp = TOTP(totp_model.bin_key, totp_model.step, totp_model.t0, totp_model.digits) # The old URL doesn't have a trailing slash. url = reverse('two-factor-authenticate').rstrip('/') resp = self.client.post(url, {'otp_token': totp.token()}) self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False) # Ensure the signal is received as expected. self.assertEqual(self.user_logged_in_count, 1)
def clean(self): cleaned_data = super().clean() try: token = int(cleaned_data.get('otp_token')) except (TypeError, ValueError): verified = False else: # django-otp setting. OTP_TOTP_SYNC = getattr(settings, 'OTP_TOTP_SYNC', True) # Device verification using the current instance. totp = TOTP(self.instance.bin_key, self.instance.step, self.instance.t0, self.instance.digits, self.instance.drift) totp.time = time.time() verified = totp.verify(token, self.instance.tolerance, self.instance.last_t) if verified: # Device is verified, update attributes and prepare the # instance to be saved. self.instance.last_t = totp.t() if OTP_TOTP_SYNC: self.instance.drift = totp.drift if not verified: raise forms.ValidationError(self.error_messages['invalid']) try: return cleaned_data finally: if TOTP_SESSION_KEY in self.request.session: # pragma: no cover del self.request.session[TOTP_SESSION_KEY]
def test_2fa_login(self): """Test login behavior when 2FA is configured.""" user = get_user_model().objects.create(username='******') user.set_password('doe') user.save() totp_model = user.totpdevice_set.create() resp = self.client.post(reverse('account_login'), { 'login': '******', 'password': '******' }) self.assertRedirects(resp, reverse('two-factor-authenticate'), fetch_redirect_response=False) # Now ensure that logging in actually works. totp = TOTP(totp_model.bin_key, totp_model.step, totp_model.t0, totp_model.digits) resp = self.client.post(reverse('two-factor-authenticate'), {'otp_token': totp.token()}) self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False) # Ensure the signal is received as expected. self.assertEqual(self.user_logged_in_count, 1)
def verify_token(self, token): OTP_TOTP_SYNC = getattr(settings, 'OTP_TOTP_SYNC', True) verify_allowed, _ = self.verify_is_allowed() if not verify_allowed: return False try: token = int(token) except Exception: verified = False else: key = self.bin_key totp = TOTP(key, self.step, self.t0, self.digits, self.drift) totp.time = time.time() verified = totp.verify(token, self.tolerance, self.last_t + 1) if verified: self.last_t = totp.t() if OTP_TOTP_SYNC: self.drift = totp.drift self.throttle_reset(commit=False) self.save() if not verified: self.throttle_increment(commit=True) return verified
def test_redirect_to_2fa_to_settings(self): self.user.require_2fa = True self.user.needs_password_change = True self.user.save() response = self.client.post('/control/login?next=/control/events/', { 'email': '*****@*****.**', 'password': '******', }) self.assertEqual(response.status_code, 302) self.assertIn('/control/login/2fa?next=/control/events/', response['Location']) d = TOTPDevice.objects.create(user=self.user, name='test') totp = TOTP(d.bin_key, d.step, d.t0, d.digits, d.drift) totp.time = time.time() self.client.post( '/control/login/2fa?next=/control/events/'.format(d.pk), {'token': str(totp.token())}) response = self.client.get('/control/events/') self.assertEqual(response.status_code, 302) self.assertIn('/control/settings?next=/control/events/', response['Location'])
def test_totp_validate(self): """test flow with otp stages""" sleep(1) # Setup TOTP Device user = USER() device = TOTPDevice.objects.create(user=user, confirmed=True, digits=6) flow: Flow = Flow.objects.get(slug="default-authentication-flow") FlowStageBinding.objects.create( target=flow, order=30, stage=AuthenticatorValidateStage.objects.create()) self.driver.get(self.url("authentik_core:if-flow", flow_slug=flow.slug)) self.login() # Get expected token totp = TOTP(device.bin_key, device.step, device.t0, device.digits, device.drift) flow_executor = self.get_shadow_root("ak-flow-executor") validation_stage = self.get_shadow_root( "ak-stage-authenticator-validate", flow_executor) code_stage = self.get_shadow_root( "ak-stage-authenticator-validate-code", validation_stage) code_stage.find_element(By.CSS_SELECTOR, "input[name=code]").send_keys(totp.token()) code_stage.find_element(By.CSS_SELECTOR, "input[name=code]").send_keys(Keys.ENTER) self.wait_for_url(self.if_admin_url("/library")) self.assert_user(USER())
def totp_object(self): # Create TOTP object totp = TOTP(key=self.bin_key, step=self.step, digits=self.digits) # Set TOTP time as current time totp.time = time.time() return totp
def verify_token(self, token): verify_allowed, _ = self.verify_is_allowed() if not verify_allowed: return False try: token = int(token) except Exception: verified = False else: key = self.bin_key totp = TOTP(key, step=self.step, t0=self.start_time, digits=self.digits) verified = totp.verify( token, tolerance=settings.MULTIFACTOR_TOLERANCE, min_t=self.start_time + 1, ) if verified: self.last_time = totp.t() self.throttle_reset(commit=False) self.save() if not verified: self.throttle_increment(commit=True) return verified
def totp_obj(self): # create a TOTP object totp = TOTP(key=self.key, step=self.token_validity_period, digits=self.number_of_digits) # the current time will be used to generate a counter totp.time = time.time() return totp
def get_token(user, phone_number, email=''): if email and user.email_id != email: raise totp = TOTP(TOTP_SECRET_KEY+str(randint(10000, 99999))+str(phone_number)) totp.time = 30 token = totp.token() save_otp(user, token, email) return token
def totp_obj(self): """ create a TOTP object """ totp = TOTP(key=self.key, step=self.token_validity_period, digits=self.number_of_digits) totp.time = time.time() return totp
def totp_obj(self): """ create a TOTP object. the current time will be used to generate a counter """ totp = TOTP(key=bytes(self.key.encode()), step=self.token_validity_period, digits=self.number_of_digits) totp.time = time.time() return totp
def test_totp_invalid(self): response = self.client.get('/control/login/2fa') assert 'token' in response.rendered_content d = TOTPDevice.objects.create(user=self.user, name='test') totp = TOTP(d.bin_key, d.step, d.t0, d.digits, d.drift) totp.time = time.time() response = self.client.post('/control/login/2fa'.format(d.pk), {'token': str(totp.token() + 2)}) self.assertEqual(response.status_code, 302) self.assertIn('/control/login/2fa', response['Location'])
def code(username, time, offset=0): token = get_token(username) totp = TOTP( token.bin_key, token.step, token.t0, token.digits, token.drift + offset, ) totp.time = time return totp.token()
def test_totp_invalid(self): response = self.client.get('/control/login/2fa') assert 'token' in response.rendered_content d = TOTPDevice.objects.create(user=self.user, name='test') totp = TOTP(d.bin_key, d.step, d.t0, d.digits, d.drift) totp.time = time.time() response = self.client.post('/control/login/2fa'.format(d.pk), { 'token': str(totp.token() + 2) }) self.assertEqual(response.status_code, 302) self.assertIn('/control/login/2fa', response['Location'])
def authenticate_with_otp(self): totp_device = TOTPDevice.objects.create(name='totp device', user=self.current_person.user) totp_device.refresh_from_db() totp = TOTP(key=totp_device.bin_key) data = { 'username': self.current_person.user.username, 'password': self.current_person_password, 'otp_token': totp.token() } response = self.client.post('/api-auth', data=data) self.client.defaults['HTTP_AUTHORIZATION'] = 'Bearer {}'.format(response.data['token'])
def save(self, commit=True): user = super(RegistrationForm, self).save(commit=False) user.phone_number = self.cleaned_data['phone_number'] # user.is_verified = False totp = TOTP("JBSWY3DPEHPK3PXP") token_number = totp.token() user.token_number = token_number user.set_password(self.cleaned_data['password1']) if commit: user.save() return user
def test_totp_valid(self): response = self.client.get('/control/login/2fa') assert 'token' in response.rendered_content d = TOTPDevice.objects.create(user=self.user, name='test') totp = TOTP(d.bin_key, d.step, d.t0, d.digits, d.drift) totp.time = time.time() response = self.client.post('/control/login/2fa?next=/control/events/'.format(d.pk), { 'token': str(totp.token()) }) self.assertEqual(response.status_code, 302) self.assertIn('/control/events/', response['Location']) assert time.time() - self.client.session['pretix_auth_login_time'] < 60 assert not self.client.session['pretix_auth_long_session']
def test_totp_valid(self): response = self.client.get('/control/login/2fa') assert 'token' in response.content.decode() d = TOTPDevice.objects.create(user=self.user, name='test') totp = TOTP(d.bin_key, d.step, d.t0, d.digits, d.drift) totp.time = time.time() response = self.client.post( '/control/login/2fa?next=/control/events/'.format(d.pk), {'token': str(totp.token())}) self.assertEqual(response.status_code, 302) self.assertIn('/control/events/', response['Location']) assert time.time() - self.client.session['pretix_auth_login_time'] < 60 assert not self.client.session['pretix_auth_long_session']
def test_confirm_totp_failed(self): self.client.post('/control/settings/2fa/add', { 'devicetype': 'totp', 'name': 'Foo' }, follow=True) d = TOTPDevice.objects.first() totp = TOTP(d.bin_key, d.step, d.t0, d.digits, d.drift) totp.time = time.time() r = self.client.post('/control/settings/2fa/totp/{}/confirm'.format(d.pk), { 'token': str(totp.token() - 2) }, follow=True) assert 'alert-danger' in r.content.decode() d.refresh_from_db() assert not d.confirmed
def test_confirm_totp_failed(self): self.client.post('/control/settings/2fa/add', { 'devicetype': 'totp', 'name': 'Foo' }, follow=True) d = TOTPDevice.objects.first() totp = TOTP(d.bin_key, d.step, d.t0, d.digits, d.drift) totp.time = time.time() r = self.client.post('/control/settings/2fa/totp/{}/confirm'.format(d.pk), { 'token': str(totp.token() - 2) }, follow=True) assert 'alert-danger' in r.rendered_content d.refresh_from_db() assert not d.confirmed
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 tokens(self, instance): """ Just display current acceptable TOTP tokens """ if not instance.pk: # e.g.: Use will create a new TOTP entry return "-" totp = TOTP(instance.bin_key, instance.step, instance.t0, instance.digits) tokens = [] for offset in range(-instance.tolerance, instance.tolerance + 1): totp.drift = instance.drift + offset tokens.append(totp.token()) return " ".join(["%s" % token for token in tokens])
def totp_from_device(self, device): url = urlsplit(device.config_url) params = parse_qs(url.query) return TOTP(b32decode(params['secret'][0]), step=int(params['period'][0]), digits=int(params['digits'][0]), t0=0, drift=0)
def test_confirm_totp(self): self.client.post('/control/settings/2fa/add', { 'devicetype': 'totp', 'name': 'Foo' }, follow=True) d = TOTPDevice.objects.first() totp = TOTP(d.bin_key, d.step, d.t0, d.digits, d.drift) totp.time = time.time() r = self.client.post('/control/settings/2fa/totp/{}/confirm'.format(d.pk), { 'token': str(totp.token()), 'activate': 'on' }, follow=True) d.refresh_from_db() assert d.confirmed assert 'alert-success' in r.content.decode() self.user.refresh_from_db() assert self.user.require_2fa
def test_confirm_totp(self): self.client.post('/control/settings/2fa/add', { 'devicetype': 'totp', 'name': 'Foo' }, follow=True) d = TOTPDevice.objects.first() totp = TOTP(d.bin_key, d.step, d.t0, d.digits, d.drift) totp.time = time.time() r = self.client.post('/control/settings/2fa/totp/{}/confirm'.format(d.pk), { 'token': str(totp.token()), 'activate': 'on' }, follow=True) d.refresh_from_db() assert d.confirmed assert 'alert-success' in r.rendered_content self.user.refresh_from_db() assert self.user.require_2fa
def verify_token(self, token): OTP_TOTP_SYNC = getattr(settings, 'OTP_TOTP_SYNC', True) try: token = int(token) except Exception: verified = False else: key = self.bin_key totp = TOTP(key, self.step, self.t0, self.digits) totp.time = time.time() for offset in range(-self.tolerance, self.tolerance + 1): totp.drift = self.drift + offset if (totp.t() > self.last_t) and (totp.token() == token): self.last_t = totp.t() if (offset != 0) and OTP_TOTP_SYNC: self.drift += offset self.save() verified = True break else: verified = False return verified
def test_totp_setup(self): """test TOTP Setup stage""" flow: Flow = Flow.objects.get(slug="default-authentication-flow") self.driver.get(self.url("authentik_core:if-flow", flow_slug=flow.slug)) self.login() self.wait_for_url(self.if_admin_url("/library")) self.assert_user(USER()) self.driver.get( self.url( "authentik_flows:configure", stage_uuid=AuthenticatorTOTPStage.objects.first().stage_uuid, )) flow_executor = self.get_shadow_root("ak-flow-executor") totp_stage = self.get_shadow_root("ak-stage-authenticator-totp", flow_executor) wait = WebDriverWait(totp_stage, self.wait_timeout) wait.until( ec.presence_of_element_located( (By.CSS_SELECTOR, "input[name=otp_uri]"))) otp_uri = totp_stage.find_element( By.CSS_SELECTOR, "input[name=otp_uri]").get_attribute("value") # Parse the OTP URI, extract the secret and get the next token otp_args = urlparse(otp_uri) self.assertEqual(otp_args.scheme, "otpauth") otp_qs = parse_qs(otp_args.query) secret_key = b32decode(otp_qs["secret"][0]) totp = TOTP(secret_key) totp_stage.find_element(By.CSS_SELECTOR, "input[name=code]").send_keys(totp.token()) totp_stage.find_element(By.CSS_SELECTOR, "input[name=code]").send_keys(Keys.ENTER) sleep(3) self.assertTrue( TOTPDevice.objects.filter(user=USER(), confirmed=True).exists())
def verify_token(self, token): OTP_TOTP_SYNC = getattr(settings, 'OTP_TOTP_SYNC', True) try: token = int(token) except Exception: verified = False else: key = self.bin_key totp = TOTP(key, self.step, self.t0, self.digits, self.drift) totp.time = time.time() verified = totp.verify(token, self.tolerance, self.last_t + 1) if verified: self.last_t = totp.t() if OTP_TOTP_SYNC: self.drift = totp.drift self.save() return verified
def test_2fa(self): """Test login behavior when 2FA is configured.""" user = get_user_model().objects.create(username='******') user.set_password('doe') user.save() totp_model = user.totpdevice_set.create() resp = self.client.post(reverse('account_login'), {'login': '******', 'password': '******'}) self.assertRedirects(resp, reverse('two-factor-authenticate'), fetch_redirect_response=False) # Now ensure that logging in actually works. totp = TOTP(totp_model.bin_key, totp_model.step, totp_model.t0, totp_model.digits) resp = self.client.post(reverse('two-factor-authenticate'), {'otp_token': totp.token()}) # The user ends up on the normal redirect login page. self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False)
def post(self, request, *args, **kwargs): code = request.data.get("code", None) if code is None: return Response(status=status.HTTP_400_BAD_REQUEST) model = OTP.objects.get(user=request.user) if model is None: return Response(status=status.HTTP_400_BAD_REQUEST) if model.validated: return Response(status=status.HTTP_423_LOCKED) if TOTP(base64.b32decode(model.secret)).verify(code): model.validated = True model.save() return Response(status=status.HTTP_200_OK) return Response(status=status.HTTP_401_UNAUTHORIZED)
def test_2fa(self): """Test login behavior when 2FA is configured.""" user = get_user_model().objects.create(username="******") user.set_password("doe") user.save() totp_model = user.totpdevice_set.create() resp = self.client.post(reverse("account_login"), { "login": "******", "password": "******" }) self.assertRedirects(resp, reverse("two-factor-authenticate"), fetch_redirect_response=False) # Now ensure that logging in actually works. totp = TOTP(totp_model.bin_key, totp_model.step, totp_model.t0, totp_model.digits) resp = self.client.post(reverse("two-factor-authenticate"), {"otp_token": totp.token()}) # The user ends up on the normal redirect login page. self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False)
def generate_otp(phone_number): totp = TOTP(settings.TOTP_SECRET_KEY+str(randint(10000,99999))+str(phone_number)) totp.time = 30 token = totp.token() return token
def _totp(device, now): totp = TOTP(device.bin_key, device.step, device.t0, device.digits) totp.time = now.timestamp() return totp.token()
def generate_totp(self, request=None): key = self.bin_key totp = TOTP(key, self.step, self.t0, self.digits, self.drift) totp.time = time.time() return totp
def totp_obj(self): totp = TOTP(key=self.bin_key, step=self.step, digits=self.digits) totp.time = time.time() return totp
def totp_obj(self): totp = TOTP(self.bin_key, step=1) totp.time = time.time() return totp
def totp_obj(key): totp = TOTP(key=key, step=100, digits=6) totp.time = time.time() return totp