def get(self, request, *args, **kwargs): if request.user.is_anonymous(): raise Http404() content_type = 'image/svg+xml; charset=utf-8' name = request.GET.get('name', 'default') device = request.user.totpdevice_set\ .filter(confirmed=False, name=name).first() if device is None or name == 'default': request.user.totpdevice_set.filter(confirmed=False).delete() device = TOTPDevice(user=request.user, name=name, confirmed=False) device.save() secret_key = b32encode(device.bin_key).decode('utf-8') issuer = 'getstream.io' otpauth_url = 'otpauth://totp/{label}?{query}'.format( label=quote('{issuer}: {username}'.format( issuer=issuer, username=request.user.get_username() )), query=urlencode(( ('secret', secret_key), ('digits', device.get_digits_display()), ('issuer', issuer), )) ) img = qrcode.make(otpauth_url, image_factory=SvgPathImage) response = HttpResponse(content_type=content_type) img.save(response) return response
def add_topt(request, next_page=None): next_page, okay_auth, okay_2fa = _setup_view(request, next_page) if not okay_auth: return ForbiddenResponse(request) # This enforces that users have exactly one TOTP. That *seems* like the best practice. devices = TOTPDevice.objects.devices_for_user(request.maybe_stale_user, confirmed=True) if devices: return ForbiddenResponse(request, "You have already configured an authenticator with this account, and cannot add another. Contact %s if you are unable to authenticate with CourSys", (help_email(request),)) device = TOTPDevice(user=request.maybe_stale_user, name='Authenticator, enabled %s' % (datetime.date.today())) device.save() l = LogEntry(userid=request.user.username, description=("Added TOPT from %s") % (ip.get_ip(request),), related_object=device) l.save() # build QR code uri = totpauth_url(device) qr = qrcode.make(uri, image_factory=qrcode.image.svg.SvgPathImage) qrdata = BytesIO() qr.save(qrdata) # This is the OTP secret (bits) encoded as base32, wrapped in an otpauth URL, encoded as a QR code, encoded as an # SVG, encoded as base64, wrapped in a data URL. I'm strangely proud. dataurl = (b'data:image/svg+xml;base64,' + base64.b64encode(qrdata.getvalue())).decode("utf-8") context = { 'device': device, 'dataurl': dataurl, 'next_page': next_page, } return render(request, 'otp/add_topt.html', context)
def add_topt(request, next_page=None): next_page, okay_auth, okay_2fa = _setup_view(request, next_page) if not okay_auth: return ForbiddenResponse(request) # This enforces that users have exactly one TOTP. That *seems* like the best practice. devices = TOTPDevice.objects.devices_for_user(request.maybe_stale_user, confirmed=True) if devices: return ForbiddenResponse(request, "You have already configured an authenticator with this account, and cannot add another. Contact %s if you are unable to authenticate with CourSys", (help_email(request),)) device = TOTPDevice(user=request.maybe_stale_user, name='Authenticator, enabled %s' % (datetime.date.today())) device.save() l = LogEntry(userid=request.user.username, description=("Added TOPT from %s") % (get_client_ip(request),), related_object=device) l.save() # build QR code uri = totpauth_url(device) qr = qrcode.make(uri, image_factory=qrcode.image.svg.SvgPathImage) qrdata = BytesIO() qr.save(qrdata) # This is the OTP secret (bits) encoded as base32, wrapped in an otpauth URL, encoded as a QR code, encoded as an # SVG, encoded as base64, wrapped in a data URL. I'm strangely proud. dataurl = (b'data:image/svg+xml;base64,' + base64.b64encode(qrdata.getvalue())).decode("utf-8") context = { 'device': device, 'dataurl': dataurl, 'next_page': next_page, } return render(request, 'otp/add_topt.html', context)
def post(self, request, *args, **kwargs): user = request.user tfa_activated = user_has_device(user) reqdata = request.POST.copy() if tfa_activated: form = TwoFactorAuthFormEnabled(prefix='to_enable', data=reqdata) if form.is_valid(): if form.cleaned_data['status']: for device in devices_for_user(user): device.delete() return render( request, 'registration/2fa-disabled-now.html', { 'form': form, 'tfa_activated': tfa_activated, 'status_changed': form.cleaned_data['status'] }, ) else: return render( request, 'registration/2fa.html', { 'form': form, 'tfa_activated': tfa_activated, }, ) else: form = TwoFactorAuthFormDisabled(prefix='to_disable', data=reqdata) if form.is_valid(): if form.cleaned_data['status']: device = TOTPDevice(user=user, name=user.username + '\'s device') device.save() from base64 import b32encode secret_key = b32encode(device.bin_key) return render( request, 'registration/2fa-enabled-now.html', { 'form': form, 'tfa_activated': tfa_activated, 'status_changed': form.cleaned_data['status'], 'device': device, 'secret_key': secret_key, }, ) else: return render( request, 'registration/2fa.html', { 'form': form, 'tfa_activated': tfa_activated, }, )
def create_device_for_tim(self, confirmed=True): self.device = TOTPDevice( tolerance=30, key=self.carte.seed, user=self.aidant_tim, step=60, confirmed=confirmed, ) self.device.save()
def new(user): p = UserProfile(user=user) p.save() totpdevice = user.totpdevice_set.filter(confirmed=1) if not totpdevice: # 创建t-otp device,使user支持t-otp验证 t = TOTPDevice(name='自动创建', user=user) t.save() return p
def otp_config(request): """View to manage two-factor (OTP) auth. - Show QR code if the device is not confirmed. - Allow user to confirm their code, if it not confirmed. Then mark the device as confirmed. - If device is already confirmed, then don't show the QR code since that would risk it being lost. However, do show it if the user has logged in using OTP (user.is_verified) and you have just verified your token. """ if request.user.is_anonymous(): return HttpResponse("Forbidden: must be logged in", status=403) context = c = {} # Get devices devices = list(django_otp.devices_for_user(request.user, confirmed=None)) # conf # and unconf If there is more than one device, log an error. # FIXME: this does not support one-time passwords and multiple # devices yet. if len(devices) > 1: logger.critical("User has more than one OTP device: %s", request.user.id) # If the user has no devices, create one now. if len(devices) == 0: from django_otp.plugins.otp_totp.models import TOTPDevice device = TOTPDevice(user=request.user, confirmed=False, name='Auto device') device.save() # If everything is normal, then select the user's device. else: device = devices[0] c['confirmed'] = device.confirmed # Attempt to verify code or confirm the unconfirmed device. if request.method == 'POST': form = c['otp_form'] = OTPVerifyForm(request.POST) form.is_valid() # validate form - validation is null op. success = c['success'] = device.verify_token( form.cleaned_data['otp_token']) # Success: if the device is unconfirmed, then confirm it. It # can then be used for logging in, etc. if success: # Do device confirmation if not device.confirmed: device.confirmed = True device.save() c['confirmed'] = device.confirmed message = c['message'] = 'Code verified.' else: message = c['message'] = 'Code not successful.' # Note that there is no other special action on success. # Reset the form - don't display code again or anything. form = c['otp_form'] = OTPVerifyForm() else: form = c['otp_form'] = OTPVerifyForm() return TemplateResponse(request, 'koota/otp.html', context)
def setUpTestData(cls): cls.client = Client() # Create one responsable cls.responsable_tom = AidantFactory(username="******") cls.responsable_tom.responsable_de.add( cls.responsable_tom.organisation) # Create one aidant cls.aidant_tim = AidantFactory( username="******", organisation=cls.responsable_tom.organisation, first_name="Tim", last_name="Onier", ) # Create one carte TOTP cls.carte = CarteTOTPFactory(serial_number="A123", seed="FA169F10A9", aidant=cls.aidant_tim) cls.org_id = cls.responsable_tom.organisation.id # Create one TOTP Device cls.device = TOTPDevice(tolerance=30, key=cls.carte.seed, user=cls.aidant_tim, step=60) cls.device.save() cls.organisation_url = f"/espace-responsable/organisation/{cls.org_id}" cls.aidant_url = f"/espace-responsable/aidant/{cls.aidant_tim.id}/" cls.validation_url = ( f"/espace-responsable/aidant/{cls.aidant_tim.id}/valider-carte")
def _update_form(self, user): super()._update_form(user) if "otp_device" in self.fields: # filter user OTP devices to keep only TOTP ones self.fields["otp_device"].widget.choices = [ d for d in self.device_choices(user) if TOTPDevice.model_label() in d[0] ]
def __init__(self, user, request=None, *args, **kwargs): super(OTPTokenForm, self).__init__(*args, **kwargs) self.user = user # filter user OTP devices to keep only TOTP ones self.fields["otp_device"].choices = [ d for d in self.device_choices(user) if TOTPDevice.model_label() in d[0] ]
def signup(request): try: if request.method == 'POST': form = SignUpForm(request.POST) print("ef") if form.is_valid(): print("hI") user = form.save() user.save() totp = TOTPDevice() totp.user = user totp.name = user.username totp.save() raw_password = form.cleaned_data.get('password1') user = authenticate(username=user.username, password=raw_password) login(request, user) return HttpResponseRedirect(reverse('otpsetup')) else: return redirect(reverse('welcome')) else: form = SignUpForm() return render(request, 'users/register.html', {'form': form}) except: return redirect(reverse('homepage'))
def login_view(request, *args, **kwargs): form = UserLoginForm(request.POST or None) if request.POST: if form.is_valid(): print("if") user_obj = form.cleaned_data.get('user_obj') login(request, user_obj) user = request.user return redirect('wallet') else: if 'otp_device' in form.errors: user = request.user user_gr = MyUsers.objects.get(username=user) device = get_user_totp_device(user) if not device: device = TOTPDevice(user=user_gr, name=user.username) device.save() return HttpResponseRedirect( reverse('qrcode', kwargs={'pk': device.pk})) return render(request, "auth/login.html", {"form": form})
def test_delete_static_device_for_confirmed_simple_aidants(self): aidants = [ AidantFactory(), AidantFactory(is_staff=True), AidantFactory(is_staff=True, is_superuser=True), ] for aidant in aidants: add_static_token(aidant.username, 123456) TOTPDevice(user=aidant, confirmed=True).save() self.assertEqual(StaticToken.objects.count(), 3) self.assertEqual(TOTPDevice.objects.count(), 3) call_command(self.command_name) # only the first aidant's static device was deleted self.assertEqual(StaticToken.objects.count(), 2)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER) if not user: LOGGER.debug("No pending user, continuing") return self.executor.stage_ok() # Currently, this stage only supports one device per user. If the user already # has a device, just skip to the next stage if TOTPDevice.objects.filter(user=user).exists(): return self.executor.stage_ok() stage: AuthenticatorTOTPStage = self.executor.current_stage if SESSION_TOTP_DEVICE not in self.request.session: device = TOTPDevice(user=user, confirmed=True, digits=stage.digits) self.request.session[SESSION_TOTP_DEVICE] = device return super().get(request, *args, **kwargs)
def get(self, request, *args, **kwargs): # Reduce session expiration to 10 minutes during 2FA check (in case user afk). request.session.set_expiry(600) devices = list( (d.persistent_id, d.name) for d in devices_for_user(request.user)) _next = request.GET.get("next", None) if _next: request.session["next"] = _next # Redirect to available device check page, from most secure to less secure one if [d for d in devices if Fido2Device.model_label() in d[0]]: return redirect("otp_fido2_check", *args, **kwargs) if [d for d in devices if TOTPDevice.model_label() in d[0]]: return redirect("otp_totp_check", *args, **kwargs) if [d for d in devices if StaticDevice.model_label() in d[0]]: return redirect("otp_static_check", *args, **kwargs)
def setUpTestData(cls): cls.client = Client() orga = OrganisationFactory() # Mario is a responsable without TOTP Device activated # => He should see the messages cls.responsable_mario = AidantFactory(username="******", organisation=orga) cls.responsable_mario.responsable_de.add( cls.responsable_mario.organisation) cls.responsable_mario.responsable_de.add(OrganisationFactory()) # Mickey is a responsable with a TOTP Device activated # => He should not see the messages cls.responsable_mickey = AidantFactory(username="******", organisation=orga) cls.responsable_mickey.responsable_de.add( cls.responsable_mickey.organisation) cls.responsable_mickey.responsable_de.add(OrganisationFactory()) device = TOTPDevice(user=cls.responsable_mickey) device.save() # Roger is a responsable with an *inactive* TOTP Device # (e.g. after an unfinished card activation) # => He should see the messages cls.responsable_hubert = AidantFactory(username="******", organisation=orga) cls.responsable_hubert.responsable_de.add( cls.responsable_hubert.organisation) cls.responsable_hubert.responsable_de.add(OrganisationFactory()) device = TOTPDevice(user=cls.responsable_hubert, confirmed=False) device.save() # Guy has no TOTP Device but is a simple Aidant # => He should not see the messages. cls.aidant_guy = AidantFactory(username="******") cls.urls_responsables = ( "/espace-aidant/", "/espace-responsable/", f"/espace-responsable/organisation/{orga.id}/", )
class DissociateCarteTOTPTests(TestCase): @classmethod def setUpTestData(cls): cls.client = Client() # Create one responsable : Tom cls.responsable_tom = AidantFactory(username="******") cls.responsable_tom.responsable_de.add( cls.responsable_tom.organisation) cls.org_id = cls.responsable_tom.organisation.id # Create one aidant : Tim cls.aidant_tim = AidantFactory( username="******", organisation=cls.responsable_tom.organisation, first_name="Tim", last_name="Onier", ) cls.dissociation_url = ( f"/espace-responsable/aidant/{cls.aidant_tim.id}/supprimer-carte/") cls.aidant_url = f"/espace-responsable/aidant/{cls.aidant_tim.id}/" def create_carte_for_tim(self): self.carte = CarteTOTPFactory(serial_number="A123", seed="FA169F10A9", aidant=self.aidant_tim) def create_device_for_tim(self, confirmed=True): self.device = TOTPDevice( tolerance=30, key=self.carte.seed, user=self.aidant_tim, step=60, confirmed=confirmed, ) self.device.save() def do_the_checks(self): # Submit post and check redirection is correct self.client.force_login(self.responsable_tom) response = self.client.post( self.dissociation_url, data={"reason": "perte"}, ) self.assertRedirects(response, self.aidant_url, fetch_redirect_response=False) response = self.client.get(self.aidant_url) response_content = response.content.decode("utf-8") self.assertIn("Tout s’est bien passé", response_content) # Check card still exists but that TOTP Device doesn't carte = CarteTOTP.objects.last() self.assertEqual(carte.serial_number, self.carte.serial_number) self.assertIsNone(carte.aidant) self.assertEqual(TOTPDevice.objects.count(), 0) # Check journal entry creation journal_entry = Journal.objects.last() self.assertEqual( journal_entry.action, "card_dissociation", "A Journal entry should have been created on card validation.", ) def test_dissociation_in_nominal_case(self): self.create_carte_for_tim() self.create_device_for_tim() self.do_the_checks() def test_dissociation_without_existing_totp_device(self): self.create_carte_for_tim() self.do_the_checks() def test_dissociation_with_unconfirmed_totp_device(self): self.create_carte_for_tim() self.create_device_for_tim(confirmed=False) self.do_the_checks()
def test_otp_auth(self): c = Client() person = Person.objects.get(userid='ggbaker') url = reverse('dashboard:index') # no auth: should redirect to 2fa login, and then to password login resp = c.get(url) self.assertEqual(resp.status_code, 302) self.assertTrue(resp['location'].startswith(str(settings.LOGIN_URL) + '?')) nexturl = resp['location'] resp = c.get(nexturl) self.assertEqual(resp.status_code, 302) self.assertTrue(resp['location'].startswith(str(settings.PASSWORD_LOGIN_URL) + '?')) # do the standard Django auth: should be okay for now, since user doesn't need 2FA. c.login_user('ggbaker') user = User.objects.get(username='******') session_info = SessionInfo.for_sessionstore(c.session) resp = c.get(url) self.assertEqual(resp.status_code, 200) # set the user's account to need 2FA: now should be redirected to create a TOPT. person.config['2fa'] = True person.save() resp = c.get(url) self.assertEqual(resp.status_code, 302) self.assertTrue(resp['location'].startswith(str(settings.LOGIN_URL) + '?')) nexturl = resp['location'] resp = c.get(nexturl) self.assertEqual(resp.status_code, 302) self.assertTrue(resp['location'].startswith(reverse('otp:add_topt'))) test_views(self, c, 'otp:', ['add_topt'], {}) # create a fake TOTP device for the user TOTPDevice.objects.filter(user=user).delete() key = '0'*20 device = TOTPDevice(user=user, name='test device', confirmed=True, key=key) device.save() # ... now we should see the 2FA screen resp = c.get(url) self.assertEqual(resp.status_code, 302) self.assertTrue(resp['location'].startswith(str(settings.LOGIN_URL) + '?')) nexturl = resp['location'] resp = c.get(nexturl) self.assertEqual(resp.status_code, 200) test_views(self, c, 'otp:', ['login_2fa'], {}) # if we fake the 2FA login, we should be seeing pages again session_info.last_2fa = timezone.now() session_info.save() # (mock django_otp's login()) from django_otp import DEVICE_ID_SESSION_KEY session = c.session session._session[DEVICE_ID_SESSION_KEY] = device.persistent_id session.save() resp = c.get(url) self.assertEqual(resp.status_code, 200) # now fiddle with the ages of things and check for the right failures # old password login: should redirect -> login -> password login session_info.last_auth = timezone.now() - datetime.timedelta(days=8) session_info.save() resp = c.get(url, follow=True) self.assertEqual(len(resp.redirect_chain), 2) self.assertTrue(resp.redirect_chain[-1][0].startswith(str(settings.PASSWORD_LOGIN_URL) + '?')) # old 2fa: should redirect -> 2fa login session_info.last_auth = timezone.now() - datetime.timedelta(hours=1) session_info.last_2fa = timezone.now() - datetime.timedelta(days=32) session_info.save() resp = c.get(url, follow=True) self.assertEqual(len(resp.redirect_chain), 1) self.assertTrue(resp.redirect_chain[-1][0].startswith(str(settings.LOGIN_URL) + '?'))
def create_overtolerant_device(self, aidant): tolerant_device = TOTPDevice(user=aidant, tolerance=50, confirmed=True) tolerant_device.save()
def create_correct_device(self, aidant): correct_device = TOTPDevice(user=aidant, tolerance=0, confirmed=True) correct_device.save()
def save(self, user): totp_device = TOTPDevice(name=self.cleaned_data["name"], user=user) totp_device.confirmed = False totp_device.save() return totp_device