def otp_setup(request): confirmed_devices = list(devices_for_user(request.user, True)) tfa_enabled = len(confirmed_devices) > 0 if request.method == 'GET' and not tfa_enabled: unconfirmed_devices = list(devices_for_user(request.user, False)) device = unconfirmed_devices[0] form = DeviceValidationForm(device) return render(request, 'otp_setup.html', { 'device': device, 'form': form, }) elif request.method == 'POST': if request.POST.get(u'enable') and not tfa_enabled: device = TOTPDevice.objects.create(user=request.user, name=u'TOTP', confirmed=False) form = DeviceValidationForm(device) return render(request, 'otp_setup.html', { 'device': device, 'form': form, }) elif request.POST.get(u'disable') and tfa_enabled: for device in confirmed_devices: device.delete() messages.success( request, u_(u'Disabled two-factor authentication for this account.')) else: messages.error(request, u_(u'Unknown error.')) return redirect('otp_status')
def otp_setup(request): confirmed_devices = list(devices_for_user(request.user, True)) tfa_enabled = len(confirmed_devices) > 0 if request.method == 'GET' and not tfa_enabled: unconfirmed_devices = list(devices_for_user(request.user, False)) device = unconfirmed_devices[0] form = DeviceValidationForm(device) return render(request, 'otp_setup.html', { 'device': device, 'form': form, }) elif request.method == 'POST': if request.POST.get(u'enable') and not tfa_enabled: device = TOTPDevice.objects.create(user=request.user, name=u'TOTP', confirmed=False) form = DeviceValidationForm(device) return render(request, 'otp_setup.html', { 'device': device, 'form': form, }) elif request.POST.get(u'disable') and tfa_enabled: for device in confirmed_devices: device.delete() messages.success(request, u_( u'Disabled two-factor authentication for this account.')) else: messages.error(request, u_(u'Unknown error.')) return redirect('otp_status')
def test_happy_flow_multiple(self): usernames = ['*****@*****.**' % i for i in range(0, 3)] users = [self.create_user(username) for username in usernames] [self.enable_otp(user) for user in users] call_command('two_factor_disable', *usernames[:2]) self.assertEqual(list(devices_for_user(users[0])), []) self.assertEqual(list(devices_for_user(users[1])), []) self.assertNotEqual(list(devices_for_user(users[2])), [])
def test_happy_flow_multiple(self): usernames = ['*****@*****.**' % i for i in range(0, 3)] users = [self.create_user(username) for username in usernames] [self.enable_otp(user) for user in users] call_command('disable', *usernames[:2]) self.assertEqual(list(devices_for_user(users[0])), []) self.assertEqual(list(devices_for_user(users[1])), []) self.assertNotEqual(list(devices_for_user(users[2])), [])
def test_disable_account(self, mocked_send_task_delete_document): management.call_command("disable_account", org_slug="to-delete-a") # org has been disabled self.assertTrue(FTLDocument.objects.get(pk=self.doc.pk).deleted) self.assertFalse(FTLFolder.objects.filter(pk=self.folder.pk).exists()) self.assertFalse(FTLUser.objects.get(pk=self.user_a.pk).is_active) self.assertFalse(list(devices_for_user(self.user_a, confirmed=None))) self.assertFalse(FTLOrg.objects.get(pk=self.org.pk).deleted) # org_no_delete was not self.assertFalse(FTLDocument.objects.get(pk=self.doc_no_delete.pk).deleted) self.assertTrue(FTLFolder.objects.filter(pk=self.folder_no_delete.pk).exists()) self.assertTrue(FTLUser.objects.get(pk=self.user_no_delete_a.pk).is_active) self.assertTrue(list(devices_for_user(self.user_no_delete_a, confirmed=None)))
def ftl_account_data(request): if request.user and request.user.is_authenticated: # Calculate the current timezone offset while taking into account any DST offset tz = request.user.tz or getattr(settings, "TIME_ZONE", "UTC") tz_offset = ( datetime.datetime.utcnow().replace(tzinfo=pytz.utc).astimezone( pytz.timezone(tz)).utcoffset().total_seconds() / 60) return { "name": request.user.get_username(), "isSuperUser": request.user.is_superuser, "otp_warning": any([ True for d in devices_for_user(request.user, confirmed=None) if (isinstance(d, StaticDevice) and not d.token_set.exists()) or not d.confirmed ]), "supported_exts": mimes.MIMETYPES_EXT_DICT, "only_office_viewer": getattr(settings, "FTL_ENABLE_ONLY_OFFICE", False), "tz_offset": tz_offset, } return {}
def test_enable_two_fa_user_data_ok(self): """Test if all data returned by two fa activation method is present""" payload = { 'email': '*****@*****.**', 'password': '******' } user = get_user_model().objects.create_user(**payload) Profile.objects.filter(user=user).update(email_confirmed=True) res = self.client.post(TOKEN_URL, payload) token = res.data['token'] self.client.credentials(HTTP_AUTHORIZATION='Token ' + token) url = reverse("user:totp-create") res = self.client.get(url) self.assertEqual(res.status_code, status.HTTP_201_CREATED) # temporary token present self.assertIn('token', res.data) # image present self.assertIn('qrImg', res.data) devices = devices_for_user(user, confirmed=False) self.assertNotEqual(None, devices)
def get_device_challenges(self) -> list[dict]: """Get a list of all device challenges applicable for the current stage""" challenges = [] user_devices = devices_for_user(self.get_pending_user()) # static and totp are only shown once # since their challenges are device-independant seen_classes = [] stage: AuthenticatorValidateStage = self.executor.current_stage for device in user_devices: device_class = device.__class__.__name__.lower().replace("device", "") if device_class not in stage.device_classes: continue # Ensure only classes in PER_DEVICE_CLASSES are returned per device # otherwise only return a single challenge if device_class in seen_classes and device_class not in PER_DEVICE_CLASSES: continue if device_class not in seen_classes: seen_classes.append(device_class) challenges.append( DeviceChallenge( data={ "device_class": device_class, "device_uid": device.pk, "challenge": get_challenge_for_device(self.request, device), } ).initial_data ) return challenges
def form_valid(self, form): for device in devices_for_user(self.request.user): device.delete() if isinstance(device, RemoteYubikeyDevice): TrustedAgent.objects.filter(user_id=device.user_id).delete() return redirect(self.success_url or resolve_url(settings.LOGIN_REDIRECT_URL))
def handle(self, *args, **options): # Create an admin user or find one if it exists try: u = User.objects.get(username='******') except ObjectDoesNotExist: u = User(username='******', email='*****@*****.**') # (Re)set the password to rattic u.set_password('rattic') # Make them a staff member u.is_staff = True # Create private group and assign it to the user group = Group(name='private_admin', created=timezone.now()) group.save() u.self_group = group # Save the user u.save() u.groups.add(group) u.save() # Delete any tokens they may have for dev in devices_for_user(u): dev.delete()
def remember_agent(self): """ Returns True if a user, browser and device is remembered using the remember cookie. """ if not getattr(settings, 'TWO_FACTOR_REMEMBER_COOKIE_AGE', None): return False user = self.get_user() devices = list(devices_for_user(user)) for key, value in self.request.COOKIES.items(): if key.startswith(REMEMBER_COOKIE_PREFIX) and value: for device in devices: verify_is_allowed, extra = device.verify_is_allowed() try: if verify_is_allowed and validate_remember_device_cookie( value, user=user, otp_device_id=device.persistent_id): user.otp_device = device device.throttle_reset() return True except BadSignature: device.throttle_increment() # Remove remember cookies with invalid signature to omit unnecessary throttling self.cookies_to_delete.append(key) return False
def form_valid(self, form): from django_otp import devices_for_user username = form.cleaned_data['username'] user = User.objects.get(username__iexact=username) for device in devices_for_user(user): device.delete() disable_for_days = form.cleaned_data['disable_for_days'] if disable_for_days: couch_user = CouchUser.from_django_user(user) disable_until = datetime.utcnow() + timedelta( days=disable_for_days) couch_user.two_factor_auth_disabled_until = disable_until couch_user.save() verification = form.cleaned_data['verification_mode'] verified_by = form.cleaned_data['via_who'] or self.request.user.username log_model_change( self.request.user, user, f'Two factor disabled. Verified by: {verified_by}, verification mode: "{verification}"' ) mail_admins( "Two-Factor account reset", "Two-Factor auth was reset. Details: \n" " Account reset: {username}\n" " Reset by: {reset_by}\n" " Request Verificatoin Mode: {verification}\n" " Verified by: {verified_by}\n" " Two-Factor disabled for {days} days.".format( username=username, reset_by=self.request.user.username, verification=verification, verified_by=verified_by, days=disable_for_days), ) send_HTML_email( "%sTwo-Factor authentication reset" % settings.EMAIL_SUBJECT_PREFIX, username, render_to_string( 'hqadmin/email/two_factor_reset_email.html', context={ 'until': disable_until.strftime('%Y-%m-%d %H:%M:%S UTC') if disable_for_days else None, 'support_email': settings.SUPPORT_EMAIL, 'email_subject': "[URGENT] Possible Account Breach", 'email_body': "Two Factor Auth on my CommCare account " "was disabled without my request. My username is: %s" % username, }), ) messages.success(self.request, _('Two-Factor Auth successfully disabled.')) return redirect('{}?q={}'.format(reverse('web_user_lookup'), username))
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 get_user_totp_device(user, confirmed=None): """ Returns users' TOTP device """ devices = devices_for_user(user, confirmed=confirmed) for device in devices: if isinstance(device, totp_models.TOTPDevice): return device
def get_user_static_device(user, confirmed=None): """ Returns users' static device """ devices = devices_for_user(user, confirmed=confirmed) for device in devices: if isinstance(device, static_models.StaticDevice): return device
def devices_for_user(value): devices = list(django_otp.devices_for_user(value)) for device in devices: if isinstance(device, TOTPDevice): device.type = 'TOTP' device.icon = 'qrcode' return devices
def get_user_totp_device(user): devices = devices_for_user(user) for device in devices: if isinstance(device, TOTPDevice): return device device = user.totpdevice_set.create() return device
def device_choices(self, user): """Check which devices are compatible with this form.""" token_credentials = [] for d in devices_for_user(user): # This credential is a push token if d.is_interactive(): token_credentials.append((d.persistent_id, d.friendly_name)) return token_credentials
def _get_valid_choices(self, user): # Only show sets that still contains codes choices = [ d for d in devices_for_user(user) if StaticDevice.model_label() in d.persistent_id and d.token_set.exists() ] choices_with_token = list((d.persistent_id, d.name) for d in choices) return choices_with_token
def handle(self, *args, **options): for username in args: try: user = User.objects.get_by_natural_key(username) except User.DoesNotExist: raise CommandError('User "%s" does not exist' % username) for device in devices_for_user(user): device.delete()
def get_user_totp_device(user, confirmed=False): """ Find an existing user totp device and returning it """ devices = devices_for_user(user, confirmed=confirmed) for device in devices: if isinstance(device, TOTPDevice): return device
def post(self, request, format=None): user = request.user devices = devices_for_user(user) for device in devices: device.delete() user.jwt_secret = uuid.uuid4() user.save() token = get_custom_jwt(user, None) return Response({'token': token}, status=status.HTTP_200_OK)
def role_totp_link(self, obj): return format_html_join( mark_safe("<br>"), '<a href="{}">{}</a>', (( reverse("admin:otp_totp_totpdevice_change", args=[device.id]), device.name, ) for device in django_otp.devices_for_user(obj.role, confirmed=False)), )
def device_from_persistent_id(user, persistent_id, list_of_devices=None, confirmed=None): if list_of_devices is None: list_of_devices = devices_for_user(user, confirmed=confirmed) for device in list_of_devices: if device.persistent_id == persistent_id: return device
def otp_qrcode(request): response = None for device in devices_for_user(request.user, False): if not hasattr(device, 'secret_qrcode'): continue qrcode = device.secret_qrcode(request) response = HttpResponse(content_type='image/png') qrcode.save(response, 'PNG') if not response: raise Http404 return response
def post(self, request, *args, **kwargs): form = self.form_class(data=request.POST, user=request.user) if form.is_valid(): # Remove all the devices from the user for device in devices_for_user(self.request.user): device.delete() return redirect(resolve_url("account-admin:edit")) return render(request, self.template_name, {"form": form})
def clean(self): self.cleaned_data = super(CustomAuthForm, self).clean() user = self.get_user() # check if user has a otp device enabled, if not skip verifying with OTP nr_of_devices = 0 for device in devices_for_user(user): nr_of_devices += 1 if nr_of_devices > 0: self.clean_otp(self.get_user()) return self.cleaned_data
def otp_qrcode(request): response = None for device in devices_for_user(request.user, False): if not hasattr(device, 'secret_qrcode'): continue qrcode = device.secret_qrcode(request) response = HttpResponse(content_type='image/png') qrcode.save(response, 'PNG') if not response: raise Http404 return response
def clean_otp_token(self): otp_token = self.cleaned_data["otp_token"] user = self.user if not user_has_device(user): raise ValidationError( _("The system could not find a device (OTP card or generator on phone) for your account." "Contact support to add one.")) for device in devices_for_user(user): if device.verify_is_allowed() and device.verify_token(otp_token): return otp_token raise ValidationError(_("OTP token is not valid."))
def clean_otp_token(self): otp_token = self.cleaned_data["otp_token"] user = self.user if not user_has_device(user): raise ValidationError( "Le système n'a pas trouvé d'appareil (carte OTP ou générateur sur téléphone) pour votre compte. Contactez le support pour en ajouter un." ) for device in devices_for_user(user): if device.verify_is_allowed() and device.verify_token(otp_token): return otp_token raise ValidationError("Ce code n'est pas valide.")
def test(self): response = self.client.get(reverse("two_factor:disable")) self.assertContains(response, "Yes, I am sure") response = self.client.post(reverse("two_factor:disable")) self.assertEqual(response.context_data["form"].errors, {"understand": ["This field is required."]}) response = self.client.post(reverse("two_factor:disable"), {"understand": "1"}) self.assertRedirects(response, str(settings.LOGIN_REDIRECT_URL)) self.assertEqual(list(devices_for_user(self.user)), []) # cannot disable twice response = self.client.get(reverse("two_factor:disable")) self.assertRedirects(response, str(settings.LOGIN_REDIRECT_URL))
def otp_status(request): tfa_enabled = len(list(devices_for_user(request.user, True))) > 0 if tfa_enabled: tfa_status = ul_(u'enabled') tfa_btn_name = 'disable' tfa_btn_value = ul_(u'Disable') else: tfa_status = ul_(u'disabled') tfa_btn_name = 'enable' tfa_btn_value = ul_(u'Enable') return render(request, 'otp_status.html', { 'tfa_status': tfa_status, 'tfa_button_name': tfa_btn_name, 'tfa_button_value': tfa_btn_value, })
def test_totp_remove(client, create_user, create_device): client.login(username=create_user.user.get_username(), password=create_user.password) create_device(user=create_user.user) device2 = create_device(user=create_user.user, name='device2') response = client.post(get_rm_url(), { 'password': create_user.password, 'token': _totp(device2, now()), }) assert response.context['success'] totp_names = [ device.name for device in django_otp.devices_for_user(create_user.user) ] assert 'device2' in totp_names assert 'device' not in totp_names
def otp_setup_verify(request): if request.method == 'POST': unconfirmed_devices = list(devices_for_user(request.user, False)) device = unconfirmed_devices[0] form = DeviceValidationForm(device, request.POST) if form.is_valid(): device.confirmed = True device.save() messages.success(request, u_( u'Enabled two-factor authentication for this account.')) else: return redirect('otp_setup') else: messages.error(request, u_(u'Unknown error.')) return redirect('otp_status')
def custom_logout(request): print('Loggin out {}'.format(request.user)) my_device= None devices = devices_for_user(request.user, confirmed=None) for device in devices: if isinstance(device, TOTPDevice): my_device= device my_device.confirmed = False my_device.save() request.user.is_two_factor_enabled=False request.user.save() logout(request) print(request.user) return redirect('home')
def test(self): response = self.client.get(reverse('two_factor:disable')) self.assertContains(response, 'Yes, I am sure') response = self.client.post(reverse('two_factor:disable')) self.assertEqual(response.context_data['form'].errors, {'understand': ['This field is required.']}) response = self.client.post(reverse('two_factor:disable'), {'understand': '1'}) self.assertRedirects(response, str(settings.LOGIN_REDIRECT_URL)) self.assertEqual(list(devices_for_user(self.user)), []) # cannot disable twice response = self.client.get(reverse('two_factor:disable')) self.assertRedirects(response, str(settings.LOGIN_REDIRECT_URL))
def removetoken(request, uid): # Grab the user user = get_object_or_404(User, pk=uid) # Show confirm form on GET if request.method != 'POST': return render(request, 'staff_removetoken.html', { 'user': user, }) # Delete all devices (backup, token and phone) for dev in devices_for_user(user): dev.delete() # Redirect to the users detail page return HttpResponseRedirect(reverse('staff.views.userdetail', args=(uid,)))
def handle(self, *args, **options): # Create an admin user or find one if it exists try: u = User.objects.get(username='******') except ObjectDoesNotExist: u = User(username='******', email='*****@*****.**') # (Re)set the password to rattic u.set_password('rattic') # Make them a staff member u.is_staff = True # Save the user u.save() # Delete any tokens they may have for dev in devices_for_user(u): dev.delete()
def clean(self): username = self.cleaned_data.get('username') secure_password = self.cleaned_data.get('password') assert self.request is not None try: server_challenge = self.request.server_challenge except AttributeError as err: raise forms.ValidationError("request.server_challenge not set: %s" % err) # log.debug("Challenge from session: '%s'", server_challenge) if username and secure_password: if settings.DEBUG: secure_js_login_failed.connect(self._secure_js_login_failed_signal_handler) if app_settings.TOTP_NEEDED: otp_token = self.cleaned_data.get("otp_token") devices = tuple(django_otp.devices_for_user(self.user_cache)) if len(devices)!=1: raise forms.ValidationError("OTP devices count is not one, it's: %i" % len(devices)) device = devices[0] if device.verify_token(otp_token) != True: raise forms.ValidationError("OTP token wrong!") self.user_cache = authenticate( user=self.user_cache, user_profile=self.user_profile, secure_password=secure_password, server_challenge=server_challenge ) if settings.DEBUG: secure_js_login_failed.disconnect(self._secure_js_login_failed_signal_handler) # log.debug("Get '%s' back from authenticate()", self.user_cache) if self.user_cache is None: raise forms.ValidationError( "authenticate() check failed.", code='invalid_login', ) return self.cleaned_data
def test_disable_single(self): user = self.create_user() self.enable_otp(user) call_command("two_factor_disable", "*****@*****.**") self.assertEqual(list(devices_for_user(user)), [])
def form_valid(self, form): for device in devices_for_user(self.request.user): device.delete() return redirect(self.redirect_url or resolve_url(settings.LOGIN_REDIRECT_URL))
def richiedi_attivazione_2fa(self): return self.richiedi_2fa and not list(devices_for_user(self))
def test_disable_single(self): user = self.create_user() self.enable_otp(user) call_command('disable', '*****@*****.**') self.assertEqual(list(devices_for_user(user)), [])
def disable(self, request, pk=None): for device in devices_for_user(request.user): device.delete() return Response(status=status.HTTP_204_NO_CONTENT)
def default_device(user): if not user or user.is_anonymous(): return for device in devices_for_user(user): if device.name == 'default': return device
def form_valid(self, form): for device in devices_for_user(self.request.user): device.delete() return redirect(self.redirect_url or str(settings.DISABLE_TWOFACTOR_REDIRECT_URL) or str(settings.LOGIN_REDIRECT_URL))