Exemplo n.º 1
0
class OIDCAuthenticationBackendTestCase(TestCase):
    """Authentication tests."""

    @override_settings(OIDC_OP_TOKEN_ENDPOINT='https://server.example.com/token')
    @override_settings(OIDC_OP_USER_ENDPOINT='https://server.example.com/user')
    @override_settings(OIDC_RP_CLIENT_ID='example_id')
    @override_settings(OIDC_RP_CLIENT_SECRET='client_secret')
    def setUp(self):
        self.backend = OIDCAuthenticationBackend()

    def test_missing_request_arg(self):
        """Test authentication returns `None` when `request` is not provided."""
        self.assertEqual(self.backend.authenticate(request=None), None)

    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
    @patch('mozilla_django_oidc.auth.requests')
    def test_invalid_token(self, request_mock, token_mock):
        """Test authentication with an invalid token."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        token_mock.return_value = None
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'accesss_token': 'access_token'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request), None)

    @override_settings(OIDC_ALLOW_UNSECURED_JWT=True)
    def test_allowed_unsecured_token(self):
        """Test payload data from unsecured token (allowed)."""
        header = force_bytes(json.dumps({'alg': 'none'}))
        payload = force_bytes(json.dumps({'foo': 'bar'}))
        signature = ''
        token = force_bytes('{}.{}.{}'.format(
            smart_text(b64encode(header)),
            smart_text(b64encode(payload)),
            signature
        ))

        extracted_payload = self.backend.get_payload_data(token, None)
        self.assertEqual(payload, extracted_payload)

    @override_settings(OIDC_ALLOW_UNSECURED_JWT=False)
    def test_disallowed_unsecured_token(self):
        """Test payload data from unsecured token (disallowed)."""
        header = force_bytes(json.dumps({'alg': 'none'}))
        payload = force_bytes(json.dumps({'foo': 'bar'}))
        signature = ''
        token = force_bytes('{}.{}.{}'.format(
            smart_text(b64encode(header)),
            smart_text(b64encode(payload)),
            signature
        ))

        with self.assertRaises(KeyError):
            self.backend.get_payload_data(token, None)

    @override_settings(OIDC_ALLOW_UNSECURED_JWT=True)
    def test_allowed_unsecured_valid_token(self):
        """Test payload data from valid secured token (unsecured allowed)."""
        header = force_bytes(json.dumps({'alg': 'HS256', 'typ': 'JWT'}))
        payload = force_bytes(json.dumps({'foo': 'bar'}))

        # Compute signature
        key = b'mysupersecuretestkey'
        h = hmac.HMAC(key, hashes.SHA256(), backend=default_backend())
        msg = '{}.{}'.format(smart_text(b64encode(header)), smart_text(b64encode(payload)))
        h.update(force_bytes(msg))
        signature = b64encode(h.finalize())

        token = '{}.{}.{}'.format(
            smart_text(b64encode(header)),
            smart_text(b64encode(payload)),
            smart_text(signature)
        )
        token_bytes = force_bytes(token)
        key_text = smart_text(key)
        output = self.backend.get_payload_data(token_bytes, key_text)
        self.assertEqual(output, payload)

    @override_settings(OIDC_ALLOW_UNSECURED_JWT=False)
    def test_disallowed_unsecured_valid_token(self):
        """Test payload data from valid secure token (unsecured disallowed)."""
        header = force_bytes(json.dumps({'alg': 'HS256', 'typ': 'JWT'}))
        payload = force_bytes(json.dumps({'foo': 'bar'}))

        # Compute signature
        key = b'mysupersecuretestkey'
        h = hmac.HMAC(key, hashes.SHA256(), backend=default_backend())
        msg = '{}.{}'.format(smart_text(b64encode(header)), smart_text(b64encode(payload)))
        h.update(force_bytes(msg))
        signature = b64encode(h.finalize())

        token = '{}.{}.{}'.format(
            smart_text(b64encode(header)),
            smart_text(b64encode(payload)),
            smart_text(signature)
        )
        token_bytes = force_bytes(token)
        key_text = smart_text(key)
        output = self.backend.get_payload_data(token_bytes, key_text)
        self.assertEqual(output, payload)

    @override_settings(OIDC_ALLOW_UNSECURED_JWT=True)
    def test_allowed_unsecured_invalid_token(self):
        """Test payload data from invalid secure token (unsecured allowed)."""
        header = force_bytes(json.dumps({'alg': 'HS256', 'typ': 'JWT'}))
        payload = force_bytes(json.dumps({'foo': 'bar'}))

        # Compute signature
        key = b'mysupersecuretestkey'
        fake_key = b'mysupersecurefaketestkey'
        h = hmac.HMAC(key, hashes.SHA256(), backend=default_backend())
        msg = '{}.{}'.format(smart_text(b64encode(header)), smart_text(b64encode(payload)))
        h.update(force_bytes(msg))
        signature = b64encode(h.finalize())

        token = '{}.{}.{}'.format(
            smart_text(b64encode(header)),
            smart_text(b64encode(payload)),
            smart_text(signature)
        )
        token_bytes = force_bytes(token)
        key_text = smart_text(fake_key)

        with self.assertRaises(SuspiciousOperation) as ctx:
            self.backend.get_payload_data(token_bytes, key_text)
        self.assertEqual(ctx.exception.args[0], 'JWS token verification failed.')

    @override_settings(OIDC_ALLOW_UNSECURED_JWT=False)
    def test_disallowed_unsecured_invalid_token(self):
        """Test payload data from invalid secure token (unsecured disallowed)."""
        header = force_bytes(json.dumps({'alg': 'HS256', 'typ': 'JWT'}))
        payload = force_bytes(json.dumps({'foo': 'bar'}))

        # Compute signature
        key = b'mysupersecuretestkey'
        fake_key = b'mysupersecurefaketestkey'
        h = hmac.HMAC(key, hashes.SHA256(), backend=default_backend())
        msg = '{}.{}'.format(smart_text(b64encode(header)), smart_text(b64encode(payload)))
        h.update(force_bytes(msg))
        signature = b64encode(h.finalize())

        token = '{}.{}.{}'.format(
            smart_text(b64encode(header)),
            smart_text(b64encode(payload)),
            smart_text(signature)
        )
        token_bytes = force_bytes(token)
        key_text = smart_text(fake_key)

        with self.assertRaises(SuspiciousOperation) as ctx:
            self.backend.get_payload_data(token_bytes, key_text)
        self.assertEqual(ctx.exception.args[0], 'JWS token verification failed.')

    def test_get_user(self):
        """Test get_user method with valid user."""

        user = User.objects.create_user('example_username')
        self.assertEqual(self.backend.get_user(user.pk), user)

    def test_get_invalid_user(self):
        """Test get_user method with non existing user."""

        self.assertEqual(self.backend.get_user(user_id=1), None)

    @override_settings(ROOT_URLCONF='tests.namespaced_urls')
    @override_settings(OIDC_AUTHENTICATION_CALLBACK_URL='namespace:oidc_authentication_callback')
    @patch('mozilla_django_oidc.auth.requests')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
    def test_successful_authentication_existing_user_namespaced(self, token_mock, request_mock):
        """Test successful authentication for existing user."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        user = User.objects.create_user(username='******',
                                        email='*****@*****.**')
        token_mock.return_value = True
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock

        post_data = {
            'client_id': 'example_id',
            'client_secret': 'client_secret',
            'grant_type': 'authorization_code',
            'code': 'foo',
            'redirect_uri': 'http://testserver/namespace/callback/'
        }
        self.assertEqual(self.backend.authenticate(request=auth_request), user)
        token_mock.assert_called_once_with('id_token', nonce=None)
        request_mock.post.assert_called_once_with('https://server.example.com/token',
                                                  data=post_data,
                                                  auth=None,
                                                  verify=True)
        request_mock.get.assert_called_once_with(
            'https://server.example.com/user',
            headers={'Authorization': 'Bearer access_granted'},
            verify=True
        )

    @patch('mozilla_django_oidc.auth.requests')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
    def test_successful_authentication_existing_user(self, token_mock, request_mock):
        """Test successful authentication for existing user."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        user = User.objects.create_user(username='******',
                                        email='*****@*****.**')
        token_mock.return_value = True
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock

        post_data = {
            'client_id': 'example_id',
            'client_secret': 'client_secret',
            'grant_type': 'authorization_code',
            'code': 'foo',
            'redirect_uri': 'http://testserver/callback/'
        }
        self.assertEqual(self.backend.authenticate(request=auth_request), user)
        token_mock.assert_called_once_with('id_token', nonce=None)
        request_mock.post.assert_called_once_with('https://server.example.com/token',
                                                  data=post_data,
                                                  auth=None,
                                                  verify=True)
        request_mock.get.assert_called_once_with(
            'https://server.example.com/user',
            headers={'Authorization': 'Bearer access_granted'},
            verify=True
        )

    @override_settings(OIDC_STORE_ACCESS_TOKEN=True)
    @override_settings(OIDC_STORE_ID_TOKEN=True)
    @patch('mozilla_django_oidc.auth.requests')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
    def test_successful_authentication_existing_user_upper_case(self, token_mock, request_mock):
        """Test successful authentication for existing user regardless of email case."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        user = User.objects.create_user(username='******',
                                        email='*****@*****.**')
        token_mock.return_value = True
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock

        post_data = {
            'client_id': 'example_id',
            'client_secret': 'client_secret',
            'grant_type': 'authorization_code',
            'code': 'foo',
            'redirect_uri': 'http://testserver/callback/'
        }
        self.assertEqual(self.backend.authenticate(request=auth_request), user)
        token_mock.assert_called_once_with('id_token', nonce=None)
        request_mock.post.assert_called_once_with('https://server.example.com/token',
                                                  data=post_data,
                                                  auth=None,
                                                  verify=True)
        request_mock.get.assert_called_once_with(
            'https://server.example.com/user',
            headers={'Authorization': 'Bearer access_granted'},
            verify=True
        )
        self.assertEqual(auth_request.session.get('oidc_id_token'), 'id_token')
        self.assertEqual(auth_request.session.get('oidc_access_token'), 'access_granted')

    @patch('mozilla_django_oidc.auth.requests')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_claims')
    def test_failed_authentication_verify_claims(self, claims_mock, token_mock, request_mock):
        """Test successful authentication for existing user."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        User.objects.create_user(username='******',
                                 email='*****@*****.**')
        token_mock.return_value = True
        claims_mock.return_value = False
        get_json_mock = Mock()
        claims_response = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        get_json_mock.json.return_value = claims_response
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock

        post_data = {
            'client_id': 'example_id',
            'client_secret': 'client_secret',
            'grant_type': 'authorization_code',
            'code': 'foo',
            'redirect_uri': 'http://testserver/callback/'
        }
        self.assertIsNone(self.backend.authenticate(request=auth_request))
        token_mock.assert_called_once_with('id_token', nonce=None)
        claims_mock.assert_called_once_with(claims_response)
        request_mock.post.assert_called_once_with('https://server.example.com/token',
                                                  data=post_data,
                                                  auth=None,
                                                  verify=True)
        request_mock.get.assert_called_once_with(
            'https://server.example.com/user',
            headers={'Authorization': 'Bearer access_granted'},
            verify=True
        )

    @patch.object(settings, 'OIDC_USERNAME_ALGO')
    @patch('mozilla_django_oidc.auth.requests')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
    def test_successful_authentication_new_user(self, token_mock, request_mock, algo_mock):
        """Test successful authentication and user creation."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        algo_mock.return_value = 'username_algo'
        token_mock.return_value = True
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        post_data = {
            'client_id': 'example_id',
            'client_secret': 'client_secret',
            'grant_type': 'authorization_code',
            'code': 'foo',
            'redirect_uri': 'http://testserver/callback/',
        }
        self.assertEqual(User.objects.all().count(), 0)
        self.backend.authenticate(request=auth_request)
        self.assertEqual(User.objects.all().count(), 1)
        user = User.objects.all()[0]
        self.assertEquals(user.email, '*****@*****.**')
        self.assertEquals(user.username, 'username_algo')

        token_mock.assert_called_once_with('id_token', nonce=None)
        request_mock.post.assert_called_once_with('https://server.example.com/token',
                                                  data=post_data,
                                                  auth=None,
                                                  verify=True)
        request_mock.get.assert_called_once_with(
            'https://server.example.com/user',
            headers={'Authorization': 'Bearer access_granted'},
            verify=True
        )

    @override_settings(OIDC_TOKEN_USE_BASIC_AUTH=True)
    @override_settings(OIDC_STORE_ACCESS_TOKEN=True)
    @override_settings(OIDC_STORE_ID_TOKEN=True)
    @patch('mozilla_django_oidc.auth.requests')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
    def test_successful_authentication_basic_auth_token(self, token_mock, request_mock):
        """
        Test successful authentication when using HTTP basic authentication
        for token endpoint authentication.
        """
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        user = User.objects.create_user(username='******',
                                        email='*****@*****.**')
        token_mock.return_value = True
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock

        post_data = {
            'client_id': 'example_id',
            'client_secret': 'client_secret',
            'grant_type': 'authorization_code',
            'code': 'foo',
            'redirect_uri': 'http://testserver/callback/'
        }
        self.assertEqual(self.backend.authenticate(request=auth_request), user)
        token_mock.assert_called_once_with('id_token', nonce=None)

        # As the auth parameter is and object, we can't compare them directly
        request_mock.post.assert_called_once()
        post_params = request_mock.post.call_args
        _kwargs = post_params[1]

        self.assertEqual(post_params[0][0], 'https://server.example.com/token')
        # Test individual params separately
        sent_data = _kwargs['data']
        self.assertEqual(sent_data['client_id'], post_data['client_id'])
        self.assertTrue('client_secret' not in _kwargs['data'])
        self.assertEqual(sent_data['grant_type'], post_data['grant_type'])
        self.assertEqual(sent_data['code'], post_data['code'])
        self.assertEqual(sent_data['redirect_uri'], post_data['redirect_uri'])

        auth = _kwargs['auth']  # type: requests.auth.HTTPBasicAuth
        self.assertEqual(auth.username, 'example_id')
        self.assertEqual(auth.password, 'client_secret')
        self.assertEqual(_kwargs['verify'], True)

        request_mock.get.assert_called_once_with(
            'https://server.example.com/user',
            headers={'Authorization': 'Bearer access_granted'},
            verify=True
        )
        self.assertEqual(auth_request.session.get('oidc_id_token'), 'id_token')
        self.assertEqual(auth_request.session.get('oidc_access_token'), 'access_granted')

    def test_authenticate_no_code_no_state(self):
        """Test authenticate with wrong parameters."""

        # there are no GET params
        request = RequestFactory().get('/foo')
        request.session = {}
        self.assertIsNone(self.backend.authenticate(request=request))

    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_jwt_decode_params(self, request_mock, jws_mock):
        """Test jwt verification signature."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        jws_mock.return_value = json.dumps({
            'aud': 'audience'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'token',
            'access_token': 'access_token'
        }
        request_mock.post.return_value = post_json_mock
        self.backend.authenticate(request=auth_request)
        calls = [
            call(force_bytes('token'), 'client_secret')
        ]
        jws_mock.assert_has_calls(calls)

    @override_settings(OIDC_VERIFY_JWT=False)
    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_jwt_decode_params_verify_false(self, request_mock, jws_mock):
        """Test jwt verification signature with verify False"""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        jws_mock.return_value = json.dumps({
            'aud': 'audience'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'token',
            'access_token': 'access_token'
        }
        request_mock.post.return_value = post_json_mock
        calls = [
            call(force_bytes('token'), 'client_secret')
        ]
        self.backend.authenticate(request=auth_request)
        jws_mock.assert_has_calls(calls)

    @override_settings(OIDC_USE_NONCE=True)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    def test_jwt_failed_nonce(self, jws_mock):
        """Test Nonce verification."""

        jws_mock.return_value = json.dumps({
            'nonce': 'foobar',
            'aud': 'aud'
        }).encode('utf-8')
        id_token = 'my_token'
        with self.assertRaisesMessage(SuspiciousOperation, 'JWT Nonce verification failed.'):
            self.backend.verify_token(id_token, **{'nonce': 'foo'})

    @override_settings(OIDC_CREATE_USER=False)
    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_create_user_disabled(self, request_mock, jws_mock):
        """Test with user creation disabled and no user found."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        jws_mock.return_value = json.dumps({
            'nonce': 'nonce'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request), None)

    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    @override_settings(OIDC_USE_NONCE=False)
    def test_create_user_enabled(self, request_mock, jws_mock):
        """Test with user creation enabled and no user found."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        self.assertEqual(User.objects.filter(email='*****@*****.**').exists(), False)
        jws_mock.return_value = json.dumps({
            'nonce': 'nonce'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request),
                         User.objects.get(email='*****@*****.**'))

    @patch.object(settings, 'OIDC_USERNAME_ALGO')
    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_custom_username_algo(self, request_mock, jws_mock, algo_mock):
        """Test user creation with custom username algorithm."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        self.assertEqual(User.objects.filter(email='*****@*****.**').exists(), False)
        algo_mock.return_value = 'username_algo'
        jws_mock.return_value = json.dumps({
            'nonce': 'nonce'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request),
                         User.objects.get(username='******'))

    @override_settings(OIDC_USE_NONCE=False,
                       OIDC_USERNAME_ALGO='tests.test_auth.dotted_username_algo_callback')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_custom_username_algo_dotted_path(self, request_mock, jws_mock):
        """Test user creation with custom username algorithm with a dotted path."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        self.assertEqual(User.objects.filter(email='*****@*****.**').exists(), False)
        jws_mock.return_value = json.dumps({
            'nonce': 'nonce'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request),
                         User.objects.get(username='******'))

    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_duplicate_emails_exact(self, request_mock, jws_mock):
        """Test auth with two users having the same email."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        User.objects.create(username='******', email='*****@*****.**')
        User.objects.create(username='******', email='*****@*****.**')
        jws_mock.return_value = json.dumps({
            'nonce': 'nonce'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request), None)

    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_duplicate_emails_case_mismatch(self, request_mock, jws_mock):
        """Test auth with two users having the same email, with different case."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        User.objects.create(username='******', email='*****@*****.**')
        User.objects.create(username='******', email='*****@*****.**')
        jws_mock.return_value = json.dumps({
            'nonce': 'nonce'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request), None)

    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.update_user')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_custom_update_user(self, request_mock, jws_mock, update_user_mock):
        """User updated with new claims"""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        User.objects.create(username='******', email='*****@*****.**',
                            first_name='User')

        def update_user(user, claims):
            user.first_name = claims['nickname']
            user.save()
        update_user_mock.side_effect = update_user

        jws_mock.return_value = json.dumps({
            'nonce': 'nonce'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request), None)

        self.assertEqual(User.objects.get().first_name, 'a_username')
Exemplo n.º 2
0
class OIDCAuthenticationBackendTestCase(TestCase):
    """Authentication tests."""
    @override_settings(
        OIDC_OP_TOKEN_ENDPOINT='https://server.example.com/token')
    @override_settings(OIDC_OP_USER_ENDPOINT='https://server.example.com/user')
    @override_settings(OIDC_RP_CLIENT_ID='example_id')
    @override_settings(OIDC_RP_CLIENT_SECRET='client_secret')
    def setUp(self):
        self.backend = OIDCAuthenticationBackend()

    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
    @patch('mozilla_django_oidc.auth.requests')
    def test_invalid_token(self, request_mock, token_mock):
        """Test authentication with an invalid token."""
        auth_request = RequestFactory().get('/foo', {
            'code': 'foo',
            'state': 'bar'
        })
        auth_request.session = {}

        token_mock.return_value = None
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'accesss_token': 'access_token'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request), None)

    def test_get_user(self):
        """Test get_user method with valid user."""

        user = User.objects.create_user('example_username')
        self.assertEqual(self.backend.get_user(user.pk), user)

    def test_get_invalid_user(self):
        """Test get_user method with non existing user."""

        self.assertEqual(self.backend.get_user(user_id=1), None)

    @override_settings(SITE_URL='http://site-url.com')
    @patch('mozilla_django_oidc.auth.requests')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
    def test_successful_authentication_existing_user(self, token_mock,
                                                     request_mock):
        """Test successful authentication for existing user."""
        auth_request = RequestFactory().get('/foo', {
            'code': 'foo',
            'state': 'bar'
        })
        auth_request.session = {}

        user = User.objects.create_user(username='******',
                                        email='*****@*****.**')
        token_mock.return_value = True
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock

        post_data = {
            'client_id': 'example_id',
            'client_secret': 'client_secret',
            'grant_type': 'authorization_code',
            'code': 'foo',
            'redirect_uri': 'http://site-url.com/callback/'
        }
        self.assertEqual(self.backend.authenticate(request=auth_request), user)
        token_mock.assert_called_once_with('id_token', nonce=None)
        request_mock.post.assert_called_once_with(
            'https://server.example.com/token', data=post_data, verify=True)
        request_mock.get.assert_called_once_with(
            'https://server.example.com/user',
            headers={'Authorization': 'Bearer access_granted'})

    @patch.object(settings, 'OIDC_USERNAME_ALGO')
    @patch('mozilla_django_oidc.auth.requests')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
    @override_settings(SITE_URL='http://site-url.com')
    def test_successful_authentication_new_user(self, token_mock, request_mock,
                                                algo_mock):
        """Test successful authentication and user creation."""
        auth_request = RequestFactory().get('/foo', {
            'code': 'foo',
            'state': 'bar'
        })
        auth_request.session = {}

        algo_mock.return_value = 'username_algo'
        token_mock.return_value = True
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        post_data = {
            'client_id': 'example_id',
            'client_secret': 'client_secret',
            'grant_type': 'authorization_code',
            'code': 'foo',
            'redirect_uri': 'http://site-url.com/callback/',
        }
        self.assertEqual(User.objects.all().count(), 0)
        self.backend.authenticate(request=auth_request)
        self.assertEqual(User.objects.all().count(), 1)
        user = User.objects.all()[0]
        self.assertEquals(user.email, '*****@*****.**')
        self.assertEquals(user.username, 'username_algo')

        token_mock.assert_called_once_with('id_token', nonce=None)
        request_mock.post.assert_called_once_with(
            'https://server.example.com/token', data=post_data, verify=True)
        request_mock.get.assert_called_once_with(
            'https://server.example.com/user',
            headers={'Authorization': 'Bearer access_granted'})

    def test_authenticate_no_code_no_state(self):
        """Test authenticate with wrong parameters."""

        # there are no GET params
        request = RequestFactory().get('/foo')
        request.session = {}
        with self.assertRaisesMessage(SuspiciousOperation,
                                      'Code or state not found'):
            self.backend.authenticate(request=request)

    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.jws.verify')
    @patch('mozilla_django_oidc.auth.requests')
    def test_jwt_decode_params(self, request_mock, jws_mock):
        """Test jwt verification signature."""
        auth_request = RequestFactory().get('/foo', {
            'code': 'foo',
            'state': 'bar'
        })
        auth_request.session = {}

        jws_mock.return_value = json.dumps({'aud': 'audience'})
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'token',
            'access_token': 'access_token'
        }
        request_mock.post.return_value = post_json_mock
        self.backend.authenticate(request=auth_request)
        calls = [call('token', 'client_secret', algorithms=['HS256'])]
        jws_mock.assert_has_calls(calls)

    @override_settings(OIDC_VERIFY_JWT=False)
    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.jws.verify')
    @patch('mozilla_django_oidc.auth.requests')
    def test_jwt_decode_params_verify_false(self, request_mock, jws_mock):
        """Test jwt verification signature with verify False"""
        auth_request = RequestFactory().get('/foo', {
            'code': 'foo',
            'state': 'bar'
        })
        auth_request.session = {}

        jws_mock.return_value = json.dumps({'aud': 'audience'})
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'token',
            'access_token': 'access_token'
        }
        request_mock.post.return_value = post_json_mock
        calls = [call('token', 'client_secret', algorithms=['HS256'])]

        self.backend.authenticate(request=auth_request)
        jws_mock.assert_has_calls(calls)

    @override_settings(OIDC_USE_NONCE=True)
    @override_settings(OIDC_RP_CLIENT_SECRET_ENCODED=False)
    @patch('mozilla_django_oidc.auth.jws')
    def test_jwt_failed_nonce(self, jwt_mock):
        """Test Nonce verification."""

        jwt_mock.verify.return_value = json.dumps({
            'nonce': 'foobar',
            'aud': 'aud'
        })
        id_token = 'my_token'
        with self.assertRaisesMessage(SuspiciousOperation,
                                      'JWT Nonce verification failed.'):
            self.backend.verify_token(id_token, **{'nonce': 'foo'})

    @override_settings(OIDC_CREATE_USER=False)
    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.jws.verify')
    @patch('mozilla_django_oidc.auth.requests')
    def test_create_user_disabled(self, request_mock, jws_mock):
        """Test with user creation disabled and no user found."""
        auth_request = RequestFactory().get('/foo', {
            'code': 'foo',
            'state': 'bar'
        })
        auth_request.session = {}

        jws_mock.return_value = json.dumps({'nonce': 'nonce'})
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request), None)

    @patch('mozilla_django_oidc.auth.jws.verify')
    @patch('mozilla_django_oidc.auth.requests')
    @override_settings(OIDC_USE_NONCE=False)
    def test_create_user_enabled(self, request_mock, jws_mock):
        """Test with user creation enabled and no user found."""
        auth_request = RequestFactory().get('/foo', {
            'code': 'foo',
            'state': 'bar'
        })
        auth_request.session = {}

        self.assertEqual(
            User.objects.filter(email='*****@*****.**').exists(), False)
        jws_mock.return_value = json.dumps({'nonce': 'nonce'})
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request),
                         User.objects.get(email='*****@*****.**'))

    @patch.object(settings, 'OIDC_USERNAME_ALGO')
    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.jws.verify')
    @patch('mozilla_django_oidc.auth.requests')
    def test_custom_username_algo(self, request_mock, jws_mock, algo_mock):
        """Test user creation with custom username algorithm."""
        auth_request = RequestFactory().get('/foo', {
            'code': 'foo',
            'state': 'bar'
        })
        auth_request.session = {}

        self.assertEqual(
            User.objects.filter(email='*****@*****.**').exists(), False)
        algo_mock.return_value = 'username_algo'
        jws_mock.return_value = json.dumps({'nonce': 'nonce'})
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request),
                         User.objects.get(username='******'))

    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.jws.verify')
    @patch('mozilla_django_oidc.auth.requests')
    def test_duplicate_emails(self, request_mock, jws_mock):
        """Test auth with two users having the same email."""
        auth_request = RequestFactory().get('/foo', {
            'code': 'foo',
            'state': 'bar'
        })
        auth_request.session = {}

        User.objects.create(username='******', email='*****@*****.**')
        User.objects.create(username='******', email='*****@*****.**')
        jws_mock.return_value = json.dumps({'nonce': 'nonce'})
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request), None)
Exemplo n.º 3
0
class OIDCAuthenticationBackendTestCase(TestCase):
    """Authentication tests."""

    @override_settings(OIDC_OP_TOKEN_ENDPOINT='https://server.example.com/token')
    @override_settings(OIDC_OP_USER_ENDPOINT='https://server.example.com/user')
    @override_settings(OIDC_RP_CLIENT_ID='example_id')
    @override_settings(OIDC_RP_CLIENT_SECRET='client_secret')
    def setUp(self):
        self.backend = OIDCAuthenticationBackend()

    def test_missing_request_arg(self):
        """Test authentication returns `None` when `request` is not provided."""
        self.assertEqual(self.backend.authenticate(request=None), None)

    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
    @patch('mozilla_django_oidc.auth.requests')
    def test_invalid_token(self, request_mock, token_mock):
        """Test authentication with an invalid token."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        token_mock.return_value = None
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'accesss_token': 'access_token'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request), None)

    def test_get_user(self):
        """Test get_user method with valid user."""

        user = User.objects.create_user('example_username')
        self.assertEqual(self.backend.get_user(user.pk), user)

    def test_get_invalid_user(self):
        """Test get_user method with non existing user."""

        self.assertEqual(self.backend.get_user(user_id=1), None)

    @patch('mozilla_django_oidc.auth.requests')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
    def test_successful_authentication_existing_user(self, token_mock, request_mock):
        """Test successful authentication for existing user."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        user = User.objects.create_user(username='******',
                                        email='*****@*****.**')
        token_mock.return_value = True
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock

        post_data = {
            'client_id': 'example_id',
            'client_secret': 'client_secret',
            'grant_type': 'authorization_code',
            'code': 'foo',
            'redirect_uri': 'http://testserver/callback/'
        }
        self.assertEqual(self.backend.authenticate(request=auth_request), user)
        token_mock.assert_called_once_with('id_token', nonce=None)
        request_mock.post.assert_called_once_with('https://server.example.com/token',
                                                  data=post_data,
                                                  verify=True)
        request_mock.get.assert_called_once_with(
            'https://server.example.com/user',
            headers={'Authorization': 'Bearer access_granted'},
            verify=True
        )

    @override_settings(OIDC_STORE_ACCESS_TOKEN=True)
    @override_settings(OIDC_STORE_ID_TOKEN=True)
    @patch('mozilla_django_oidc.auth.requests')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
    def test_successful_authentication_existing_user_upper_case(self, token_mock, request_mock):
        """Test successful authentication for existing user regardless of email case."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        user = User.objects.create_user(username='******',
                                        email='*****@*****.**')
        token_mock.return_value = True
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock

        post_data = {
            'client_id': 'example_id',
            'client_secret': 'client_secret',
            'grant_type': 'authorization_code',
            'code': 'foo',
            'redirect_uri': 'http://testserver/callback/'
        }
        self.assertEqual(self.backend.authenticate(request=auth_request), user)
        token_mock.assert_called_once_with('id_token', nonce=None)
        request_mock.post.assert_called_once_with('https://server.example.com/token',
                                                  data=post_data,
                                                  verify=True)
        request_mock.get.assert_called_once_with(
            'https://server.example.com/user',
            headers={'Authorization': 'Bearer access_granted'},
            verify=True
        )
        self.assertEqual(auth_request.session.get('oidc_id_token'), 'id_token')
        self.assertEqual(auth_request.session.get('oidc_access_token'), 'access_granted')

    @patch('mozilla_django_oidc.auth.requests')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_claims')
    def test_failed_authentication_verify_claims(self, claims_mock, token_mock, request_mock):
        """Test successful authentication for existing user."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        User.objects.create_user(username='******',
                                 email='*****@*****.**')
        token_mock.return_value = True
        claims_mock.return_value = False
        get_json_mock = Mock()
        claims_response = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        get_json_mock.json.return_value = claims_response
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock

        post_data = {
            'client_id': 'example_id',
            'client_secret': 'client_secret',
            'grant_type': 'authorization_code',
            'code': 'foo',
            'redirect_uri': 'http://testserver/callback/'
        }
        self.assertIsNone(self.backend.authenticate(request=auth_request))
        token_mock.assert_called_once_with('id_token', nonce=None)
        claims_mock.assert_called_once_with(claims_response)
        request_mock.post.assert_called_once_with('https://server.example.com/token',
                                                  data=post_data,
                                                  verify=True)
        request_mock.get.assert_called_once_with(
            'https://server.example.com/user',
            headers={'Authorization': 'Bearer access_granted'},
            verify=True
        )

    @patch.object(settings, 'OIDC_USERNAME_ALGO')
    @patch('mozilla_django_oidc.auth.requests')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.verify_token')
    def test_successful_authentication_new_user(self, token_mock, request_mock, algo_mock):
        """Test successful authentication and user creation."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        algo_mock.return_value = 'username_algo'
        token_mock.return_value = True
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        post_data = {
            'client_id': 'example_id',
            'client_secret': 'client_secret',
            'grant_type': 'authorization_code',
            'code': 'foo',
            'redirect_uri': 'http://testserver/callback/',
        }
        self.assertEqual(User.objects.all().count(), 0)
        self.backend.authenticate(request=auth_request)
        self.assertEqual(User.objects.all().count(), 1)
        user = User.objects.all()[0]
        self.assertEquals(user.email, '*****@*****.**')
        self.assertEquals(user.username, 'username_algo')

        token_mock.assert_called_once_with('id_token', nonce=None)
        request_mock.post.assert_called_once_with('https://server.example.com/token',
                                                  data=post_data,
                                                  verify=True)
        request_mock.get.assert_called_once_with(
            'https://server.example.com/user',
            headers={'Authorization': 'Bearer access_granted'},
            verify=True
        )

    def test_authenticate_no_code_no_state(self):
        """Test authenticate with wrong parameters."""

        # there are no GET params
        request = RequestFactory().get('/foo')
        request.session = {}
        with self.assertRaisesMessage(SuspiciousOperation, 'Code or state not found'):
            self.backend.authenticate(request=request)

    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_jwt_decode_params(self, request_mock, jws_mock):
        """Test jwt verification signature."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        jws_mock.return_value = json.dumps({
            'aud': 'audience'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'token',
            'access_token': 'access_token'
        }
        request_mock.post.return_value = post_json_mock
        self.backend.authenticate(request=auth_request)
        calls = [
            call(force_bytes('token'), force_bytes('client_secret'))
        ]
        jws_mock.assert_has_calls(calls)

    @override_settings(OIDC_VERIFY_JWT=False)
    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_jwt_decode_params_verify_false(self, request_mock, jws_mock):
        """Test jwt verification signature with verify False"""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        jws_mock.return_value = json.dumps({
            'aud': 'audience'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'token',
            'access_token': 'access_token'
        }
        request_mock.post.return_value = post_json_mock
        calls = [
            call(force_bytes('token'), force_bytes('client_secret'))
        ]
        self.backend.authenticate(request=auth_request)
        jws_mock.assert_has_calls(calls)

    @override_settings(OIDC_USE_NONCE=True)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    def test_jwt_failed_nonce(self, jws_mock):
        """Test Nonce verification."""

        jws_mock.return_value = json.dumps({
            'nonce': 'foobar',
            'aud': 'aud'
        }).encode('utf-8')
        id_token = 'my_token'
        with self.assertRaisesMessage(SuspiciousOperation, 'JWT Nonce verification failed.'):
            self.backend.verify_token(id_token, **{'nonce': 'foo'})

    @override_settings(OIDC_CREATE_USER=False)
    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_create_user_disabled(self, request_mock, jws_mock):
        """Test with user creation disabled and no user found."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        jws_mock.return_value = json.dumps({
            'nonce': 'nonce'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request), None)

    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    @override_settings(OIDC_USE_NONCE=False)
    def test_create_user_enabled(self, request_mock, jws_mock):
        """Test with user creation enabled and no user found."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        self.assertEqual(User.objects.filter(email='*****@*****.**').exists(), False)
        jws_mock.return_value = json.dumps({
            'nonce': 'nonce'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request),
                         User.objects.get(email='*****@*****.**'))

    @patch.object(settings, 'OIDC_USERNAME_ALGO')
    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_custom_username_algo(self, request_mock, jws_mock, algo_mock):
        """Test user creation with custom username algorithm."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        self.assertEqual(User.objects.filter(email='*****@*****.**').exists(), False)
        algo_mock.return_value = 'username_algo'
        jws_mock.return_value = json.dumps({
            'nonce': 'nonce'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request),
                         User.objects.get(username='******'))

    @override_settings(OIDC_USE_NONCE=False,
                       OIDC_USERNAME_ALGO='tests.test_auth.dotted_username_algo_callback')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_custom_username_algo_dotted_path(self, request_mock, jws_mock):
        """Test user creation with custom username algorithm with a dotted path."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        self.assertEqual(User.objects.filter(email='*****@*****.**').exists(), False)
        jws_mock.return_value = json.dumps({
            'nonce': 'nonce'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request),
                         User.objects.get(username='******'))

    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_duplicate_emails_exact(self, request_mock, jws_mock):
        """Test auth with two users having the same email."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        User.objects.create(username='******', email='*****@*****.**')
        User.objects.create(username='******', email='*****@*****.**')
        jws_mock.return_value = json.dumps({
            'nonce': 'nonce'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request), None)

    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_duplicate_emails_case_mismatch(self, request_mock, jws_mock):
        """Test auth with two users having the same email, with different case."""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        User.objects.create(username='******', email='*****@*****.**')
        User.objects.create(username='******', email='*****@*****.**')
        jws_mock.return_value = json.dumps({
            'nonce': 'nonce'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request), None)

    @override_settings(OIDC_USE_NONCE=False)
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.update_user')
    @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws')
    @patch('mozilla_django_oidc.auth.requests')
    def test_custom_update_user(self, request_mock, jws_mock, update_user_mock):
        """User updated with new claims"""
        auth_request = RequestFactory().get('/foo', {'code': 'foo',
                                                     'state': 'bar'})
        auth_request.session = {}

        User.objects.create(username='******', email='*****@*****.**',
                            first_name='User')

        def update_user(user, claims):
            user.first_name = claims['nickname']
            user.save()
        update_user_mock.side_effect = update_user

        jws_mock.return_value = json.dumps({
            'nonce': 'nonce'
        }).encode('utf-8')
        get_json_mock = Mock()
        get_json_mock.json.return_value = {
            'nickname': 'a_username',
            'email': '*****@*****.**'
        }
        request_mock.get.return_value = get_json_mock
        post_json_mock = Mock()
        post_json_mock.json.return_value = {
            'id_token': 'id_token',
            'access_token': 'access_granted'
        }
        request_mock.post.return_value = post_json_mock
        self.assertEqual(self.backend.authenticate(request=auth_request), None)

        self.assertEqual(User.objects.get().first_name, 'a_username')