예제 #1
0
def test_plugin():
    plugin = registry.get_plugin('u2f')
    assert plugin.get_create_form_class() == U2fDeviceCreateForm
    assert plugin.get_verify_form_class() == U2fVerifyForm

    user = UserFactory()
    # When a user logs in the OTP device is added as a property.
    user.otp_device = U2fDeviceFactory(user=user)
    assert registry.user_authentication_method(user) == 'u2f'
예제 #2
0
    def test_recovery(self):
        user = UserFactory()
        response = self.login(user)
        self.assertContains(response,
                            'These are your 2 step authentication methods.')

        # Create recovery codes to secure the account.
        response = self.client.post('/recovery-code/create/', follow=True)
        self.assertContains(response,
                            'Your recovery codes have been generated')

        # Login with a recovery code.
        self.client.logout()
        device = user.staticdevice_set.get()
        self.assertEqual(device.token_set.count(), 10)
        device_url = '/recovery-code/verify/{}/'.format(device.pk)
        response = self.login(user, '{}?next=/list/'.format(device_url))

        context_user = response.context['user']
        self.assertTrue(context_user.is_anonymous)

        # Try a faulty code with a unicode BOM.
        response = self.client.post(device_url, {'otp_token': '\ufeffxxx'},
                                    follow=True)
        self.assertEqual(response.status_code, 200)
        self.assertContains(response,
                            'The token is not valid for this device.')

        # Continue authentication using recovery code.
        token = device.token_set.first()
        response = self.client.post(device_url, {'otp_token': token.token},
                                    follow=True)
        self.assertRedirects(response, '/list/')
        self.assertEqual(device.token_set.count(), 9)

        context_user = response.context['user']
        self.assertTrue(context_user.is_authenticated)
        self.assertTrue(context_user.is_verified)

        # Verify that the authentication method matches.
        self.assertEqual(registry.user_authentication_method(context_user),
                         'recovery-code')

        # The create and update form for recovery codes replace existing codes.
        self.client.post('/recovery-code/update/{}/'.format(device.pk))
        self.assertEqual(device.token_set.count(), 10)

        # Secure access is revoked when all authentication methods are removed.
        response = self.client.post('/recovery-code/delete/{}/'.format(
            device.pk),
                                    follow=True)
        self.assertRedirects(response, '/list/')

        context_user = response.context['user']
        self.assertTrue(context_user.is_single_factor_authenticated)
        self.assertFalse(context_user.is_authenticated)
        self.assertFalse(context_user.is_verified)
예제 #3
0
    def test_totp(self):
        user = UserFactory()
        self.login(user)

        response = self.client.post('/totp/create/', {
            'otp_token': 'XXX',
            'name': 'My Phone'
        })
        self.assertContains(response,
                            'Unable to validate the token with the device.')
        response = self.client.post('/totp/create/', {
            'otp_token': '123456',
            'name': 'My Phone'
        })
        self.assertContains(response,
                            'Unable to validate the token with the device.')

        # User takes the TOTP config and applies it to his device.
        response = self.client.get('/totp/create/')
        totp = self.totp_from_device(response.context['form'].instance)

        # Confirm the user can generate tokens and the device is configured.
        registration_token = totp.token()
        with handle_signal(mfa_added) as handler:
            response = self.client.post('/totp/create/', {
                'otp_token': registration_token,
                'name': 'My Phone'
            })
            handler.assert_called_once_with(instance=mock.ANY,
                                            sender=mock.ANY,
                                            request=mock.ANY,
                                            signal=mfa_added)
        self.assertRedirects(response, '/list/')

        device = user.totpdevice_set.get()
        self.assertTrue(device.confirmed)
        self.assertEqual(device.name, 'My Phone')

        response = self.client.post('/totp/update/{}/'.format(device.pk),
                                    {'name': 'Acme ID'})
        self.assertRedirects(response, '/list/')
        device = user.totpdevice_set.get()
        self.assertEqual(device.name, 'Acme ID')

        self.client.logout()

        # Login without TOTP.
        verify_url = '/totp/verify/{}/'.format(device.pk)
        response = self.login(user, '{}?next=/list/'.format(verify_url))

        context_user = response.context['user']
        self.assertTrue(context_user.is_anonymous)

        # User is forced to use 2 step authentication.
        response = self.client.get('/list/')
        self.assertRedirects(response, '/login/?next=/list/')
        response = self.client.post('/totp/delete/{}/'.format(device.pk))
        self.assertRedirects(response,
                             '/login/?next=/totp/delete/{}/'.format(device.pk))

        # Token reuse is not allowed so the registration is no longer valid.
        with handle_signal(user_login_failed) as handler:
            response = self.client.post(verify_url,
                                        {'otp_token': registration_token})
            handler.assert_called_once_with(credentials={},
                                            user=user,
                                            device=device,
                                            sender=mock.ANY,
                                            request=mock.ANY,
                                            signal=user_login_failed)
        self.assertContains(response,
                            'The token is not valid for this device.')

        # Default tolerance is 1, increase drift to force next token.
        totp.drift = 1
        with handle_signal(user_logged_in) as handler:
            response = self.client.post(verify_url,
                                        {'otp_token': totp.token()},
                                        follow=True)
            handler.assert_called_once_with(user=user,
                                            sender=mock.ANY,
                                            request=mock.ANY,
                                            signal=user_logged_in)
        self.assertRedirects(response, '/list/')

        context_user = response.context['user']
        self.assertTrue(context_user.is_authenticated)
        self.assertTrue(context_user.is_verified)

        # Verify that the authentication method matches.
        self.assertEqual(registry.user_authentication_method(context_user),
                         'totp')

        with handle_signal(mfa_removed) as handler:
            response = self.client.post('/totp/delete/{}/'.format(device.pk),
                                        follow=True)
            handler.assert_called_once_with(instance=device,
                                            sender=mock.ANY,
                                            request=mock.ANY,
                                            signal=mfa_removed)
        self.assertContains(
            response, 'The TOTP "Acme ID" was deleted successfully.')
예제 #4
0
    def test_yubikey(self, mock_verify_token):
        user = UserFactory()
        self.login(user)

        # Test bad/missing input.
        mock_verify_token.return_value = False
        service = ValidationService.objects.latest('pk')
        response = self.client.post(
            '/yubikey/create/', {'name': 'Keychain'})
        self.assertContains(
            response, 'This field is required.')
        self.assertTrue(
            response.context['form'].fields['service'].widget.is_hidden)
        # Test field visibility with multiple services.
        ValidationService.objects.create(
            name='YubiCustom', param_sl='', param_timeout='')
        response = self.client.post(
            '/yubikey/create/', {
                'service': service.pk, 'otp_token': 'XXX',
                'name': 'Keychain'})
        self.assertContains(
            response, 'Unable to validate the token with the device.')
        self.assertFalse(
            response.context['form'].fields['service'].widget.is_hidden)

        # Test success.
        mock_verify_token.return_value = True
        yubikey = YubiKey(unhexlify(default_id()), 6, 0)
        registration_token = yubikey.generate()
        response = self.client.post(
            '/yubikey/create/', {
                'service': service.pk, 'otp_token': registration_token,
                'name': 'Keychain'})
        self.assertRedirects(response, '/list/')
        device = user.remoteyubikeydevice_set.get()
        self.assertTrue(device.confirmed)
        self.assertEqual(device.name, 'Keychain')

        # Test update.
        response = self.client.post(
            '/yubikey/update/{}/'.format(device.pk), {'name': 'Acme Inc.'})
        self.assertRedirects(response, '/list/')
        device = user.remoteyubikeydevice_set.get()
        self.assertEqual(device.name, 'Acme Inc.')

        self.client.logout()

        # Login without Yubikey.
        verify_url = '/yubikey/verify/{}/'.format(device.pk)
        response = self.login(user, '{}?next=/list/'.format(verify_url))

        context_user = response.context['user']
        self.assertTrue(context_user.is_anonymous)

        # User is forced to use 2 step authentication.
        mock_verify_token.return_value = False
        response = self.client.get('/list/')
        self.assertRedirects(response, '/login/?next=/list/')
        response = self.client.post('/yubikey/delete/{}/'.format(device.pk))
        self.assertRedirects(
            response, '/login/?next=/yubikey/delete/{}/'.format(device.pk))

        response = self.client.post(verify_url, {'otp_token': ''})
        self.assertContains(
            response, 'This field is required.')

        # Token reuse is not allowed so now the registration token is invalid.
        response = self.client.post(
            verify_url, {'otp_token': registration_token})
        self.assertContains(
            response, 'The token is not valid for this device.')

        # Complete authentication with a new token.
        mock_verify_token.return_value = True
        response = self.client.post(
            verify_url, {'otp_token': yubikey.generate()}, follow=True)
        self.assertRedirects(response, '/list/')

        context_user = response.context['user']
        self.assertTrue(context_user.is_authenticated)
        self.assertTrue(context_user.is_verified)

        # Verify that the authentication method matches.
        self.assertEqual(
            registry.user_authentication_method(context_user), 'yubikey')

        response = self.client.post(
            '/yubikey/delete/{}/'.format(device.pk), follow=True)
        self.assertContains(
            response,
            'The Yubikey "Acme Inc." was deleted successfully.')
예제 #5
0
    def test_initial_account_setup(self):
        # With single factor authentication a user can see the empty
        # list of available authentication methods as well as add a device.
        # The user cannot do anything else without 2 step authentication.
        user = UserFactory()
        with handle_signal(user_logged_in) as handler:
            response = self.login(user)
            handler.assert_called_once_with(user=user,
                                            sender=mock.ANY,
                                            request=mock.ANY,
                                            signal=user_logged_in)
            authenticated_user = handler.call_args[1]['user']
            self.assertIsNone(
                registry.user_authentication_method(authenticated_user))
        self.assertContains(response,
                            'These are your 2 step authentication methods.')
        self.assertFalse(registry.user_has_device(user))

        context_user = response.context['user']
        self.assertTrue(context_user.is_single_factor_authenticated)
        self.assertFalse(context_user.is_authenticated)
        self.assertFalse(context_user.is_verified)

        # User is allowed to add a authentication method.
        response = self.client.get('/totp/create/')
        self.assertEqual(response.status_code, 200)

        # Force 2 step login setup.
        with handle_signal(user_logged_in) as handler:
            self.login_with_mfa(user)
            handler.assert_called_once_with(user=user,
                                            sender=mock.ANY,
                                            request=mock.ANY,
                                            signal=user_logged_in)
            authenticated_user = handler.call_args[1]['user']
            self.assertEqual(
                registry.user_authentication_method(authenticated_user),
                'totp')

        response = self.client.get('/list/')

        context_user = response.context['user']
        self.assertTrue(context_user.is_single_factor_authenticated)
        self.assertTrue(context_user.is_authenticated)
        self.assertTrue(context_user.is_verified)

        # Single factor authentication no longer authenticates the user.
        self.client.logout()
        device = user.totpdevice_set.get(name='test')
        response = self.login(user,
                              '/totp/verify/{}/?next=/list/'.format(device.pk))

        context_user = response.context['user']
        self.assertTrue(context_user.is_anonymous)

        # No access to the device listing with single factor auth.
        response = self.client.get('/list/')
        self.assertRedirects(response, '/login/?next=/list/')
        response = self.client.get('/totp/create/')
        self.assertRedirects(response, '/login/?next=/totp/create/')

        # Start new MFA session.
        self.login_with_mfa(user)

        update_url = '/totp/update/{}/'.format(device.pk)
        response = self.client.get(update_url)
        self.assertEqual(response.status_code, 200)

        context_user = response.context['user']
        self.assertTrue(context_user.is_single_factor_authenticated)
        self.assertTrue(context_user.is_authenticated)
        self.assertTrue(context_user.is_verified)

        # Update TOTP name.
        response = self.client.post('/totp/update/{}/'.format(device.pk),
                                    {'name': 'Secure Phone'},
                                    follow=True)
        self.assertRedirects(response, '/list/')
        self.assertContains(
            response,
            'The TOTP "Secure Phone" was changed successfully.')

        # Secure access is revoked when all authentication methods are removed.
        response = self.client.post('/totp/delete/{}/'.format(device.pk),
                                    follow=True)
        self.assertRedirects(response, '/list/')
        self.assertContains(
            response,
            'The TOTP "Secure Phone" was deleted successfully.')

        context_user = response.context['user']
        self.assertTrue(context_user.is_single_factor_authenticated)
        self.assertFalse(context_user.is_authenticated)
        self.assertFalse(context_user.is_verified)