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))
Esempio n. 3
0
    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),
            )
Esempio n. 4
0
    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')
Esempio n. 6
0
    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")
Esempio n. 7
0
    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')
Esempio n. 8
0
    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')
Esempio n. 9
0
    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
Esempio n. 10
0
    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))
Esempio n. 11
0
    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')
Esempio n. 12
0
    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")