class OIDCAuthenticationBackendRS256WithJwksEndpointTestCase(TestCase): """Authentication tests with ALG RS256 and IpD JWKS Endpoint.""" @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') @override_settings(OIDC_RP_SIGN_ALGO='RS256') @override_settings(OIDC_OP_JWKS_ENDPOINT='https://server.example.com/jwks') def setUp(self): self.backend = OIDCAuthenticationBackend() @override_settings(OIDC_USE_NONCE=False) @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws') @patch( 'mozilla_django_oidc.auth.OIDCAuthenticationBackend.retrieve_matching_jwk' ) @patch('mozilla_django_oidc.auth.requests') def test_jwt_verify_sign_key(self, request_mock, jwk_mock, jws_mock): """Test jwt verification signature.""" auth_request = RequestFactory().get('/foo', { 'code': 'foo', 'state': 'bar' }) auth_request.session = {} jwk_mock_ret = { "kty": "RSA", "alg": "RS256", "use": "sig", "kid": "cc7d29c9cb3780741cc0876633c9107a0f33c289", "n": "20LvblCBaPicNV3-NnJuahqbpi-b8hFD", "e": "AQAB" } jwk_mock.return_value = jwk_mock_ret 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'), jwk_mock_ret)] jws_mock.assert_has_calls(calls)
class OIDCAuthenticationBackendRS256WithKeyTestCase(TestCase): """Authentication tests with ALG RS256 and provided IdP Sign Key.""" @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') @override_settings(OIDC_RP_SIGN_ALGO='RS256') @override_settings(OIDC_RP_IDP_SIGN_KEY='sign_key') def setUp(self): self.backend = OIDCAuthenticationBackend() @override_settings(OIDC_USE_NONCE=False) @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws') @patch('mozilla_django_oidc.auth.requests') def test_jwt_verify_sign_key(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'), 'sign_key') ] jws_mock.assert_has_calls(calls)
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)
class OIDCAuthenticationBackendRS256WithJwksEndpointTestCase(TestCase): """Authentication tests with ALG RS256 and IpD JWKS Endpoint.""" @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') @override_settings(OIDC_RP_SIGN_ALGO='RS256') @override_settings(OIDC_OP_JWKS_ENDPOINT='https://server.example.com/jwks') def setUp(self): self.backend = OIDCAuthenticationBackend() @override_settings(OIDC_USE_NONCE=False) @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend._verify_jws') @patch('mozilla_django_oidc.auth.OIDCAuthenticationBackend.retrieve_matching_jwk') @patch('mozilla_django_oidc.auth.requests') def test_jwt_verify_sign_key_calls(self, request_mock, jwk_mock, jws_mock): """Test jwt verification signature.""" auth_request = RequestFactory().get('/foo', {'code': 'foo', 'state': 'bar'}) auth_request.session = {} jwk_mock_ret = { "kty": "RSA", "alg": "RS256", "use": "sig", "kid": "cc7d29c9cb3780741cc0876633c9107a0f33c289", "n": "20LvblCBaPicNV3-NnJuahqbpi-b8hFD", "e": "AQAB" } jwk_mock.return_value = jwk_mock_ret 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'), jwk_mock_ret) ] jws_mock.assert_has_calls(calls) @patch('mozilla_django_oidc.auth.requests') def test_retrieve_matching_jwk(self, mock_requests): """Test retrieving valid jwk""" get_json_mock = Mock() get_json_mock.json.return_value = { "keys": [ { "alg": "RS256", "kid": "foobar", }, { "alg": "RS512", "kid": "foobar512", } ] } mock_requests.get.return_value = get_json_mock header = force_bytes(json.dumps({'alg': 'RS256', 'typ': 'JWT', 'kid': 'foobar'})) 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) ) jwk_key = self.backend.retrieve_matching_jwk(force_bytes(token)) self.assertEqual(jwk_key, get_json_mock.json.return_value['keys'][0]) @patch('mozilla_django_oidc.auth.requests') def test_retrieve_mismatcing_jwk_alg(self, mock_requests): """Test retrieving mismatching jwk alg""" get_json_mock = Mock() get_json_mock.json.return_value = { "keys": [ { "alg": "foo", "kid": "bar", } ] } mock_requests.get.return_value = get_json_mock header = force_bytes(json.dumps({'alg': 'HS256', 'typ': 'JWT', 'kid': 'bar'})) 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) ) with self.assertRaises(SuspiciousOperation) as ctx: self.backend.retrieve_matching_jwk(force_bytes(token)) self.assertEqual(ctx.exception.args[0], 'alg values do not match.') @patch('mozilla_django_oidc.auth.requests') def test_retrieve_mismatcing_jwk_kid(self, mock_requests): """Test retrieving mismatching jwk kid""" get_json_mock = Mock() get_json_mock.json.return_value = { "keys": [ { "alg": "HS256", "kid": "foobar", } ] } mock_requests.get.return_value = get_json_mock header = force_bytes(json.dumps({'alg': 'HS256', 'typ': 'JWT', 'kid': 'bar'})) 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) ) with self.assertRaises(SuspiciousOperation) as ctx: self.backend.retrieve_matching_jwk(force_bytes(token)) self.assertEqual(ctx.exception.args[0], 'Could not find a valid JWKS.') @patch('mozilla_django_oidc.auth.requests') def test_retrieve_jwk_optional_alg(self, mock_requests): """Test retrieving jwk with optional alg""" get_json_mock = Mock() get_json_mock.json.return_value = { "keys": [ { "kid": "kid", } ] } mock_requests.get.return_value = get_json_mock header = force_bytes(json.dumps({'alg': 'HS256', 'typ': 'JWT', 'kid': 'kid'})) 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) ) jwk_key = self.backend.retrieve_matching_jwk(force_bytes(token)) self.assertEqual(jwk_key, get_json_mock.json.return_value['keys'][0]) @patch('mozilla_django_oidc.auth.requests') def test_retrieve_not_existing_jwk(self, mock_requests): """Test retrieving jwk that doesn't exist.""" get_json_mock = Mock() get_json_mock.json.return_value = { "keys": [ { "alg": "RS256", "kid": "kid" } ] } mock_requests.get.return_value = get_json_mock header = force_bytes(json.dumps({'alg': 'RS256', 'typ': 'JWT', 'kid': 'differentkid'})) 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) ) with self.assertRaises(SuspiciousOperation) as ctx: self.backend.retrieve_matching_jwk(force_bytes(token)) self.assertEqual(ctx.exception.args[0], 'Could not find a valid JWKS.')
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')
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')