def test_get_otpauth_url(self): for num_digits in (6, 8): self.assertEqualUrl( 'otpauth://totp/bouke%40example.com?secret=abcdef123&digits=' + str(num_digits), get_otpauth_url(accountname='*****@*****.**', secret='abcdef123', digits=num_digits)) self.assertEqualUrl( 'otpauth://totp/Bouke%20Haarsma?secret=abcdef123&digits=' + str(num_digits), get_otpauth_url(accountname='Bouke Haarsma', secret='abcdef123', digits=num_digits)) self.assertEqualUrl( 'otpauth://totp/example.com%3A%20bouke%40example.com?' 'secret=abcdef123&digits=' + str(num_digits) + '&issuer=example.com', get_otpauth_url(accountname='*****@*****.**', issuer='example.com', secret='abcdef123', digits=num_digits)) self.assertEqualUrl( 'otpauth://totp/My%20Site%3A%20bouke%40example.com?' 'secret=abcdef123&digits=' + str(num_digits) + '&issuer=My+Site', get_otpauth_url(accountname='*****@*****.**', issuer='My Site', secret='abcdef123', digits=num_digits)) self.assertEqualUrl( 'otpauth://totp/%E6%B5%8B%E8%AF%95%E7%BD%91%E7%AB%99%3A%20' '%E6%88%91%E4%B8%8D%E6%98%AF%E9%80%97%E6%AF%94?' 'secret=abcdef123&digits=' + str(num_digits) + '&issuer=测试网站', get_otpauth_url(accountname='我不是逗比', issuer='测试网站', secret='abcdef123', digits=num_digits))
def test_get_otpauth_url(self): for num_digits in (6, 8): self.assertEqualUrl( "otpauth://totp/bouke%40example.com?secret=abcdef123&digits=" + str(num_digits), get_otpauth_url(accountname="*****@*****.**", secret="abcdef123", digits=num_digits), ) self.assertEqualUrl( "otpauth://totp/Bouke%20Haarsma?secret=abcdef123&digits=" + str(num_digits), get_otpauth_url(accountname="Bouke Haarsma", secret="abcdef123", digits=num_digits), ) self.assertEqualUrl( "otpauth://totp/example.com%3A%20bouke%40example.com?" "secret=abcdef123&digits=" + str(num_digits) + "&issuer=example.com", get_otpauth_url( accountname="*****@*****.**", issuer="example.com", secret="abcdef123", digits=num_digits ), ) self.assertEqualUrl( "otpauth://totp/My%20Site%3A%20bouke%40example.com?" "secret=abcdef123&digits=" + str(num_digits) + "&issuer=My+Site", get_otpauth_url( accountname="*****@*****.**", issuer="My Site", secret="abcdef123", digits=num_digits ), ) self.assertEqualUrl( "otpauth://totp/%E6%B5%8B%E8%AF%95%E7%BD%91%E7%AB%99%3A%20" "%E6%88%91%E4%B8%8D%E6%98%AF%E9%80%97%E6%AF%94?" "secret=abcdef123&digits=" + str(num_digits) + "&issuer=测试网站", get_otpauth_url(accountname="我不是逗比", issuer="测试网站", secret="abcdef123", digits=num_digits), )
def test_with_secret(self, mockqrcode): # Setup the mock data def side_effect(resp): resp.write(self.test_img) mockimg = Mock() mockimg.save.side_effect = side_effect mockqrcode.return_value = mockimg # Setup the session session = self.client.session session['django_two_factor-qr_secret_key'] = self.test_secret session.save() # Get default image factory default_factory = qrcode.image.svg.SvgPathImage # Get the QR code response = self.client.get(reverse('two_factor:qr')) # Check things went as expected mockqrcode.assert_called_with( get_otpauth_url('testserver:[email protected]', self.test_secret, "testserver"), image_factory=default_factory) mockimg.save.assert_called() self.assertEquals(response.status_code, 200) self.assertEquals(response.content.decode('utf-8'), self.test_img) self.assertEquals(response['Content-Type'], 'image/svg+xml; charset=utf-8')
def test_with_secret(self, mockqrcode): # Setup the mock data def side_effect(resp): resp.write(self.test_img) mockimg = Mock() mockimg.save.side_effect = side_effect mockqrcode.return_value = mockimg # Setup the session session = self.client.session session['django_two_factor-qr_secret_key'] = self.test_secret session.save() # Get default image factory default_factory = qrcode.image.svg.SvgPathImage # Get the QR code response = self.client.get(reverse('two_factor:qr')) # Check things went as expected mockqrcode.assert_called_with( get_otpauth_url(accountname=self.user.get_username(), secret=self.test_secret, issuer="testserver"), image_factory=default_factory) mockimg.save.assert_called_with(ANY) self.assertEquals(response.status_code, 200) self.assertEquals(response.content.decode('utf-8'), self.test_img) self.assertEquals(response['Content-Type'], 'image/svg+xml; charset=utf-8')
def test_with_secret(self, mockqrcode): # Setup the mock data def side_effect(resp): resp.write(self.test_img) mockimg = Mock() mockimg.save.side_effect = side_effect mockqrcode.return_value = mockimg # Setup the session session = self.client.session session["django_two_factor-qr_secret_key"] = self.test_secret session.save() # Get default image factory default_factory = qrcode.image.svg.SvgPathImage # Get the QR code response = self.client.get(reverse("two_factor:qr")) # Check things went as expected mockqrcode.assert_called_with( get_otpauth_url(accountname=self.user.get_username(), secret=self.test_secret, issuer="testserver"), image_factory=default_factory, ) mockimg.save.assert_called_with(ANY) self.assertEquals(response.status_code, 200) self.assertEquals(response.content.decode("utf-8"), self.test_img) self.assertEquals(response["Content-Type"], "image/svg+xml; charset=utf-8")
def test_get_otpauth_url(self): self.assertEqualUrl( get_otpauth_url(accountname='*****@*****.**', secret='abcdef123'), 'otpauth://totp/bouke%40example.com?secret=abcdef123') self.assertEqualUrl( get_otpauth_url(accountname='Bouke Haarsma', secret='abcdef123'), 'otpauth://totp/Bouke%20Haarsma?secret=abcdef123') self.assertEqualUrl( get_otpauth_url(accountname='*****@*****.**', issuer='example.com', secret='abcdef123'), 'otpauth://totp/example.com%3A%20bouke%40example.com?' 'secret=abcdef123&issuer=example.com') self.assertEqualUrl( get_otpauth_url(accountname='*****@*****.**', issuer='My Site', secret='abcdef123'), 'otpauth://totp/My%20Site%3A%20bouke%40example.com?' 'secret=abcdef123&issuer=My+Site')
def get_context_data(self, form, **kwargs): context = super().get_context_data(form, **kwargs) if self.steps.current == 'generator': try: username = self.request.user.get_username() except AttributeError: username = self.request.user.username otpauth_url = get_otpauth_url(accountname=username, issuer=self.get_issuer(), secret=context['secret_key'], digits=totp_digits()) context.update({ 'otpauth_url': otpauth_url, }) return context
def provision(self, request, *args, **kwargs): """ Provision TOTP and backup code devices. """ serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) if ( request.user.totpdevice_set.filter(confirmed=True).exists() or request.user.staticdevice_set.filter(confirmed=True).exists() ): raise drf_serializers.ValidationError( 'OTP devices already exist for this user') totp_device, totp_created = ( request.user.totpdevice_set.get_or_create(**self.totp_device)) backup_device, backup_created = ( request.user.staticdevice_set.get_or_create(**self.backup_device)) if backup_created: backup_codes = [ models.StaticToken.random_token() for n in range(self.number_of_tokens)] backup_device.token_set.bulk_create([ models.StaticToken(token=token, device=backup_device) for token in backup_codes]) else: backup_codes = backup_device.token_set.values_list( 'token', flat=True) b32key = base64.b32encode(binascii.unhexlify(totp_device.key)) otpauth_url = utils.get_otpauth_url( accountname=request.user.email, issuer=shortcuts.get_current_site(self.request).name, secret=b32key, digits=totp_device.digits) return response.Response(dict( otpauth_url=otpauth_url, backup_codes=backup_codes))
def test_otp_provision(self): """ A POST with credentials will provision new OTP devices. """ # Provision fails before login self.post( '/otp/provision/', data=dict(username=self.USERNAME, password=self.PASS), status_code=403) self.post( '/otp/login/', data=dict(username=self.USERNAME, password=self.PASS), status_code=200) # Requesting backup codes without posting login credentials fails provision_response = self.post( '/otp/provision/', data={}, status_code=400) provision_response = self.post( '/otp/provision/', status_code=400) provision_response = self.post( '/otp/provision/', data=dict(username=self.USERNAME, password=self.PASS), status_code=200) self.assertEqual( self.user.totpdevice_set.count(), 1, 'Wrong number of TOTP devices') self.assertIn( 'otpauth_url', provision_response.json, 'Provision response missing the TOTP oath URL') totp_device = self.user.totpdevice_set.get() b32key = base64.b32encode(binascii.unhexlify(totp_device.key)) otpauth_url = utils.get_otpauth_url( accountname=self.EMAIL, issuer=shortcuts.get_current_site( provision_response.request).name, secret=b32key, digits=totp_device.digits) self.assertEqual( provision_response.json['otpauth_url'], otpauth_url, 'Wrong provision response TOTP oath URL') # Subsequent provision requests prior to confirming return the # existing devices. subsequent_response = self.post( '/otp/provision/', data=dict(username=self.USERNAME, password=self.PASS), status_code=200) self.assertEqual( self.user.totpdevice_set.count(), 1, 'Wrong number of TOTP devices') self.assertIn( 'otpauth_url', subsequent_response.json, 'Subsequent response missing the TOTP oath URL') self.assertEqual( subsequent_response.json['otpauth_url'], otpauth_url, 'Wrong subsequent response TOTP oath URL') self.assertEqual( self.user.staticdevice_set.count(), 1, 'Wrong number of backup code devices') static_device = self.user.staticdevice_set.get() self.assertEqual( self.user.staticdevice_set.get().token_set.count(), 10, 'Wrong number of backup codes generated') self.assertIn( 'backup_codes', provision_response.json, 'Provision response missing backup codes') for token in self.user.staticdevice_set.get().token_set.all(): self.assertIn( token.token, provision_response.json['backup_codes'], 'Provision response missing backup code') # The TOTP device is not confirmed initially self.assertFalse( totp_device.confirmed, 'TOTP device confirmed after initial provisioning') self.assertFalse( static_device.confirmed, 'Backup device confirmed after initial provisioning') totp_data = dict(otp_device=totp_device.persistent_id) # OTP verification fails prior to confirming self.post( '/otp/verify/', data=dict( totp_data, otp_token=get_token(totp_device)), status_code=400) backup_token = static_device.token_set.all()[0].token backup_data = dict( otp_device=static_device.persistent_id, otp_token=backup_token) self.post( '/otp/verify/', data=backup_data, status_code=400) # TOTP confirmation fails without credentials self.post( '/otp/confirm/', data=dict(otp_token=get_token(totp_device)), status_code=400) # Confirmation fails with backup codes self.post( '/otp/confirm/', data=dict( otp_token=backup_token, username=self.USERNAME, password=self.PASS), status_code=400) confirm_response = self.post( '/otp/confirm/', data=dict( otp_token=get_token(totp_device), username=self.USERNAME, password=self.PASS), status_code=200) self.assertIn( 'success', confirm_response.json, 'TOTP device confirmation response missing success message') # OTP verification works after confirming self.post( '/otp/verify/', data=dict( totp_data, otp_token=get_token(totp_device)), status_code=200) self.post('/otp/verify/', data=backup_data, status_code=200) # Cannot provision when already provisioned duplicate_response = self.post( '/otp/provision/', data=dict(username=self.USERNAME, password=self.PASS), status_code=400) self.assertEqual( self.user.totpdevice_set.count(), 1, 'Wrong number of TOTP devices after duplicate provision') self.assertEqual( self.user.staticdevice_set.count(), 1, 'Wrong number of backup code devices after duplicate provision') self.assertEqual( self.user.staticdevice_set.get().token_set.count(), 9, 'Wrong number of backup codes after duplicate provision') self.assertIn( 'already', duplicate_response.json[0], 'Wrong duplicate provision response error message') # Requesting backup codes without posting login credentials fails self.post('/otp/backup/', data={}, status_code=400) self.post('/otp/backup/', status_code=400) backup_response = self.post( '/otp/backup/', data=dict(username=self.USERNAME, password=self.PASS), status_code=200) self.assertEqual( self.user.staticdevice_set.count(), 1, 'Wrong number of backup code devices') self.assertEqual( self.user.staticdevice_set.get().token_set.count(), 9, 'Wrong number of backup codes generated') self.assertIn( 'backup_codes', backup_response.json, 'Provision response missing the backup codes') for token in self.user.staticdevice_set.get().token_set.all(): self.assertIn( token.token, backup_response.json['backup_codes'], 'Provision response missing backup code')
def handle(self, *args, **options): self.stdout.write(self.style.SUCCESS("Creating clients...")) c, created = Client.objects.update_or_create( client_id="client_id_1", defaults={ "name": "Wagtail Demo 1 Site 1", "client_secret": "super_client_secret_1", "response_type": "code", "jwt_alg": "HS256", "redirect_uris": [ os.environ.get("WAGTAIL_1_CALLBACK", 'http://example.com/'), os.environ.get("WAGTAIL_1_LOGOUT_REDIRECT", 'http://example.com/') + "register-redirect/", os.environ.get("WAGTAIL_1_LOGOUT_REDIRECT", 'http://example.com/') ], "post_logout_redirect_uris": [ os.environ.get("WAGTAIL_1_LOGOUT_REDIRECT", 'http://example.com/') ], }) self.stdout.write( self.style.SUCCESS("{} {}".format( "Created" if created else "Updated", c.client_id))) c, created = Client.objects.update_or_create( client_id="client_id_2", defaults={ "name": "Wagtail Demo 2 Site 1", "client_secret": "super_client_secret_2", "response_type": "code", "jwt_alg": "HS256", "redirect_uris": [ os.environ.get("WAGTAIL_2_CALLBACK", 'http://example.com/'), os.environ.get("WAGTAIL_2_LOGOUT_REDIRECT", 'http://example.com/') + "register-redirect/", os.environ.get("WAGTAIL_2_LOGOUT_REDIRECT", 'http://example.com/') ], "post_logout_redirect_uris": [ os.environ.get("WAGTAIL_2_LOGOUT_REDIRECT", 'http://example.com/') ], }) self.stdout.write( self.style.SUCCESS("{} {}".format( "Created" if created else "Updated", c.client_id))) c, created = Client.objects.update_or_create( client_id="client_id_3", defaults={ "name": "Wagtail Demo 1 Site 2", "client_secret": "super_client_secret_3", "response_type": "code", "jwt_alg": "HS256", "redirect_uris": [ os.environ.get("WAGTAIL_3_CALLBACK", 'http://example.com/'), os.environ.get("WAGTAIL_3_LOGOUT_REDIRECT", 'http://example.com/') + "register-redirect/", os.environ.get("WAGTAIL_3_LOGOUT_REDIRECT", 'http://example.com/') ], "post_logout_redirect_uris": [ os.environ.get("WAGTAIL_3_LOGOUT_REDIRECT", 'http://example.com/') ], }) self.stdout.write( self.style.SUCCESS("{} {}".format( "Created" if created else "Updated", c.client_id))) c, created = Client.objects.update_or_create( client_id="management_layer_workaround", defaults={ "name": "Management Layer Workaround", "client_secret": "management_layer_workaround", "response_type": "code", "jwt_alg": "HS256", "redirect_uris": [ os.environ.get( "MANAGEMENT_LAYER_WORKAROUND_REDIRECT", "http://*****:*****@here.com", "nickname": "l33t", "birth_date": datetime.date(2000, 1, 1) }) end_user.set_password("enduser") end_user.save() self.stdout.write( self.style.SUCCESS("{} {}".format( "Created" if created else "Updated", end_user.username))) # System User system_user, created = get_user_model().objects.update_or_create( username="******", defaults={ "first_name": "System", "last_name": "User", "email": "*****@*****.**", "nickname": "5y5", "birth_date": datetime.date(2000, 1, 1) }) system_user.set_password("sysuser") system_user.save() self.stdout.write( self.style.SUCCESS("{} {}".format( "Created" if created else "Updated", system_user.username))) # System User 2FA Device. We set up a device that will always generate # the following URL that can be used to create a QR code: # otpauth://totp/Girl%2520Effect%2520Demo%3A%20sysuser?secret=VFFGMP7P36Q7TIZV3YZ65ZLHKQPAPXIM&digits=6&issuer=Girl%2520Effect%2520Demo totp_device, created = TOTPDevice.objects.update_or_create( key="a94a663fefdfa1f9a335de33eee567541e07dd0c", user=system_user, name="default", confirmed=True) totp_device.save() sys.stdout.write( self.style.SUCCESS("Created system user with OTP URL: {}".format( get_otpauth_url(accountname=system_user.username, secret=b32encode(totp_device.bin_key), issuer="Girl Effect Demo", digits=totp_device.digits)))) call_command("load_security_questions") call_command("load_countries") call_command("load_organisations")