diff --git a/mozilla_django_oidc_db/admin.py b/mozilla_django_oidc_db/admin.py index eb57aba..4c0009a 100644 --- a/mozilla_django_oidc_db/admin.py +++ b/mozilla_django_oidc_db/admin.py @@ -35,6 +35,7 @@ class OpenIDConnectConfigAdmin(SingletonModelAdmin): "oidc_op_jwks_endpoint", "oidc_op_authorization_endpoint", "oidc_op_token_endpoint", + "oidc_token_use_basic_auth", "oidc_op_user_endpoint", ) }, diff --git a/mozilla_django_oidc_db/migrations/0015_openidconnectconfig_oidc_token_use_basic_auth.py b/mozilla_django_oidc_db/migrations/0015_openidconnectconfig_oidc_token_use_basic_auth.py new file mode 100644 index 0000000..872bd1a --- /dev/null +++ b/mozilla_django_oidc_db/migrations/0015_openidconnectconfig_oidc_token_use_basic_auth.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.23 on 2024-02-05 16:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("mozilla_django_oidc_db", "0014_alter_openidconnectconfig_groups_claim"), + ] + + operations = [ + migrations.AddField( + model_name="openidconnectconfig", + name="oidc_token_use_basic_auth", + field=models.BooleanField( + default=False, + help_text="If enabled, the client ID and secret are sent in the HTTP Basic auth header when obtaining the access token. Otherwise, they are sent in the request body.", + verbose_name="Use Basic auth for token endpoint", + ), + ), + ] diff --git a/mozilla_django_oidc_db/models.py b/mozilla_django_oidc_db/models.py index c404e83..7a672d8 100644 --- a/mozilla_django_oidc_db/models.py +++ b/mozilla_django_oidc_db/models.py @@ -161,6 +161,15 @@ class OpenIDConnectConfigBase(SingletonModel): max_length=1000, help_text=_("URL of your OpenID Connect provider token endpoint"), ) + oidc_token_use_basic_auth = models.BooleanField( + _("Use Basic auth for token endpoint"), + default=False, + help_text=_( + "If enabled, the client ID and secret are sent in the HTTP Basic auth " + "header when obtaining the access token. Otherwise, they are sent in the " + "request body.", + ), + ) oidc_op_user_endpoint = models.URLField( _("User endpoint"), max_length=1000, diff --git a/tests/cassettes/test_integration_oidc_flow_variants/test_credentials_in_basic_auth_header.yaml b/tests/cassettes/test_integration_oidc_flow_variants/test_credentials_in_basic_auth_header.yaml new file mode 100644 index 0000000..6f4d143 --- /dev/null +++ b/tests/cassettes/test_integration_oidc_flow_variants/test_credentials_in_basic_auth_header.yaml @@ -0,0 +1,319 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.31.0 + method: GET + uri: http://localhost:8080/realms/test/.well-known/openid-configuration + response: + body: + string: '{"issuer":"http://localhost:8080/realms/test","authorization_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/auth","token_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/token","introspection_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/token/introspect","userinfo_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/userinfo","end_session_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/logout","frontchannel_logout_session_supported":true,"frontchannel_logout_supported":true,"jwks_uri":"http://localhost:8080/realms/test/protocol/openid-connect/certs","check_session_iframe":"http://localhost:8080/realms/test/protocol/openid-connect/login-status-iframe.html","grant_types_supported":["authorization_code","implicit","refresh_token","password","client_credentials","urn:openid:params:grant-type:ciba","urn:ietf:params:oauth:grant-type:device_code"],"acr_values_supported":["0","1"],"response_types_supported":["code","none","id_token","token","id_token + token","code id_token","code token","code id_token token"],"subject_types_supported":["public","pairwise"],"id_token_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"id_token_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"id_token_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"userinfo_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"userinfo_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"userinfo_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"request_object_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"request_object_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"request_object_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"response_modes_supported":["query","fragment","form_post","query.jwt","fragment.jwt","form_post.jwt","jwt"],"registration_endpoint":"http://localhost:8080/realms/test/clients-registrations/openid-connect","token_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"token_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"introspection_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"introspection_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"authorization_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"claims_supported":["aud","sub","iss","auth_time","name","given_name","family_name","preferred_username","email","acr"],"claim_types_supported":["normal"],"claims_parameter_supported":true,"scopes_supported":["openid","email","roles","phone","profile","address","kvk","web-origins","microprofile-jwt","acr","offline_access","bsn"],"request_parameter_supported":true,"request_uri_parameter_supported":true,"require_request_uri_registration":true,"code_challenge_methods_supported":["plain","S256"],"tls_client_certificate_bound_access_tokens":true,"revocation_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/revoke","revocation_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"revocation_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"device_authorization_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/auth/device","backchannel_token_delivery_modes_supported":["poll","ping"],"backchannel_authentication_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/ext/ciba/auth","backchannel_authentication_request_signing_alg_values_supported":["PS384","ES384","RS384","ES256","RS256","ES512","PS256","PS512","RS512"],"require_pushed_authorization_requests":false,"pushed_authorization_request_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/ext/par/request","mtls_endpoint_aliases":{"token_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/token","revocation_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/revoke","introspection_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/token/introspect","device_authorization_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/auth/device","registration_endpoint":"http://localhost:8080/realms/test/clients-registrations/openid-connect","userinfo_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/userinfo","pushed_authorization_request_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/ext/par/request","backchannel_authentication_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/ext/ciba/auth"},"authorization_response_iss_parameter_supported":true}' + headers: + Cache-Control: + - no-cache, must-revalidate, no-transform, no-store + Content-Type: + - application/json;charset=UTF-8 + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - 1; mode=block + content-length: + - '5847' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.31.0 + method: GET + uri: http://localhost:8080/realms/test/protocol/openid-connect/auth?response_type=code&scope=openid+email+profile+bsn+kvk&client_id=testid&redirect_uri=http%3A%2F%2Ftestserver%2Foidc%2Fcallback%2F&state=not-a-random-string&nonce=not-a-random-string + response: + body: + string: "\n\n\n\n \n + \ \n \n\n \n Sign + in to test\n \n \n \n \n \n \n \n\n\n\n
\n + \
\n
test
\n
\n
\n + \
\n

+ \ Sign in to your account\n\n

\n
\n
\n + \
\n\n\n
\n + \
\n
\n
\n \n\n \n\n\n
\n\n
\n \n\n
\n + \ \n \n + \
\n\n\n
\n\n
\n
\n + \
\n
\n + \
\n\n
\n\n
\n \n \n
\n + \
\n
\n
\n \n\n\n\n\n\n + \
\n
\n\n
\n
\n\n\n" + headers: + Cache-Control: + - no-store, must-revalidate, max-age=0 + Content-Language: + - en + Content-Security-Policy: + - frame-src 'self'; frame-ancestors 'self'; object-src 'none'; + Content-Type: + - text/html;charset=utf-8 + Referrer-Policy: + - no-referrer + Set-Cookie: + - AUTH_SESSION_ID=5d288792-2e5a-40ef-a0ad-b16c8fe0502d; Version=1; Path=/realms/test/; + SameSite=None; Secure; HttpOnly + - AUTH_SESSION_ID_LEGACY=5d288792-2e5a-40ef-a0ad-b16c8fe0502d; Version=1; Path=/realms/test/; + HttpOnly + - KC_RESTART=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlNzE1ZTA1MS02Y2RiLTQ4Y2MtYjRmNC1mMDcyMmM4MWY5ZDMifQ.eyJjaWQiOiJ0ZXN0aWQiLCJwdHkiOiJvcGVuaWQtY29ubmVjdCIsInJ1cmkiOiJodHRwOi8vdGVzdHNlcnZlci9vaWRjL2NhbGxiYWNrLyIsImFjdCI6IkFVVEhFTlRJQ0FURSIsIm5vdGVzIjp7InNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUgYnNuIGt2ayIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWFsbXMvdGVzdCIsInJlc3BvbnNlX3R5cGUiOiJjb2RlIiwicmVkaXJlY3RfdXJpIjoiaHR0cDovL3Rlc3RzZXJ2ZXIvb2lkYy9jYWxsYmFjay8iLCJzdGF0ZSI6Im5vdC1hLXJhbmRvbS1zdHJpbmciLCJub25jZSI6Im5vdC1hLXJhbmRvbS1zdHJpbmcifX0.i8j5h2oK7wCQJD0j4WgiObNnD6QLrcy1MjXZSIiFrD0; + Version=1; Path=/realms/test/; HttpOnly + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Robots-Tag: + - none + X-XSS-Protection: + - 1; mode=block + content-length: + - '4474' + status: + code: 200 + message: OK +- request: + body: username=testuser&password=testuser&credentialId=&login=Sign+In + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '63' + Content-Type: + - application/x-www-form-urlencoded + Cookie: + - AUTH_SESSION_ID_LEGACY=5d288792-2e5a-40ef-a0ad-b16c8fe0502d; KC_RESTART=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlNzE1ZTA1MS02Y2RiLTQ4Y2MtYjRmNC1mMDcyMmM4MWY5ZDMifQ.eyJjaWQiOiJ0ZXN0aWQiLCJwdHkiOiJvcGVuaWQtY29ubmVjdCIsInJ1cmkiOiJodHRwOi8vdGVzdHNlcnZlci9vaWRjL2NhbGxiYWNrLyIsImFjdCI6IkFVVEhFTlRJQ0FURSIsIm5vdGVzIjp7InNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUgYnNuIGt2ayIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWFsbXMvdGVzdCIsInJlc3BvbnNlX3R5cGUiOiJjb2RlIiwicmVkaXJlY3RfdXJpIjoiaHR0cDovL3Rlc3RzZXJ2ZXIvb2lkYy9jYWxsYmFjay8iLCJzdGF0ZSI6Im5vdC1hLXJhbmRvbS1zdHJpbmciLCJub25jZSI6Im5vdC1hLXJhbmRvbS1zdHJpbmcifX0.i8j5h2oK7wCQJD0j4WgiObNnD6QLrcy1MjXZSIiFrD0 + User-Agent: + - python-requests/2.31.0 + method: POST + uri: http://localhost:8080/realms/test/login-actions/authenticate?session_code=Ox1pJ-7SUPl_PYPJtvTMpWGwIpTKXCi0LfdYB3UOGko&execution=5d476fe6-1c62-4f0a-9d3e-9e1f49df4766&client_id=testid&tab_id=CbUnOGyIbv4 + response: + body: + string: '' + headers: + Cache-Control: + - no-store, must-revalidate, max-age=0 + Content-Security-Policy: + - frame-src 'self'; frame-ancestors 'self'; object-src 'none'; + Location: + - http://testserver/oidc/callback/?state=not-a-random-string&session_state=5d288792-2e5a-40ef-a0ad-b16c8fe0502d&iss=http%3A%2F%2Flocalhost%3A8080%2Frealms%2Ftest&code=a068e3fe-40c3-4d0d-8b16-871a80326589.5d288792-2e5a-40ef-a0ad-b16c8fe0502d.adf4ad83-4550-4619-9231-73bd8d700f45 + Referrer-Policy: + - no-referrer + Set-Cookie: + - KEYCLOAK_LOCALE=; Version=1; Comment=Expiring cookie; Expires=Thu, 01-Jan-1970 + 00:00:10 GMT; Max-Age=0; Path=/realms/test/; HttpOnly + - KC_RESTART=; Version=1; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; + Path=/realms/test/; HttpOnly + - KC_AUTH_STATE=; Version=1; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; + Path=/realms/test/ + - KEYCLOAK_IDENTITY=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlNzE1ZTA1MS02Y2RiLTQ4Y2MtYjRmNC1mMDcyMmM4MWY5ZDMifQ.eyJleHAiOjE3MDcyNDc0NjUsImlhdCI6MTcwNzIxMTQ2NSwianRpIjoiNWRkZDMzYzMtMTVhNi00MmY5LTg3YWUtMTkxNDY4ZmViNDA0IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy90ZXN0Iiwic3ViIjoiYWExMGNmYzctMmM0ZC00MWY2LThmYWMtN2JmNDA1YzU3MmM0IiwidHlwIjoiU2VyaWFsaXplZC1JRCIsInNlc3Npb25fc3RhdGUiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQiLCJzaWQiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQiLCJzdGF0ZV9jaGVja2VyIjoiOWxCcmYwUWs0NUdYS0VwZjA3Nk1kdllZT08xZGVIQ0VCdGtQMk9VY2VXVSJ9.p1Rw-J6y-xMvBv9Ys6YfZDGYsLLGmT4sO15qCczwsbY; + Version=1; Path=/realms/test/; SameSite=None; Secure; HttpOnly + - KEYCLOAK_IDENTITY_LEGACY=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlNzE1ZTA1MS02Y2RiLTQ4Y2MtYjRmNC1mMDcyMmM4MWY5ZDMifQ.eyJleHAiOjE3MDcyNDc0NjUsImlhdCI6MTcwNzIxMTQ2NSwianRpIjoiNWRkZDMzYzMtMTVhNi00MmY5LTg3YWUtMTkxNDY4ZmViNDA0IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy90ZXN0Iiwic3ViIjoiYWExMGNmYzctMmM0ZC00MWY2LThmYWMtN2JmNDA1YzU3MmM0IiwidHlwIjoiU2VyaWFsaXplZC1JRCIsInNlc3Npb25fc3RhdGUiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQiLCJzaWQiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQiLCJzdGF0ZV9jaGVja2VyIjoiOWxCcmYwUWs0NUdYS0VwZjA3Nk1kdllZT08xZGVIQ0VCdGtQMk9VY2VXVSJ9.p1Rw-J6y-xMvBv9Ys6YfZDGYsLLGmT4sO15qCczwsbY; + Version=1; Path=/realms/test/; HttpOnly + - KEYCLOAK_SESSION=test/aa10cfc7-2c4d-41f6-8fac-7bf405c572c4/5d288792-2e5a-40ef-a0ad-b16c8fe0502d; + Version=1; Expires=Tue, 06-Feb-2024 19:24:25 GMT; Max-Age=36000; Path=/realms/test/; + SameSite=None; Secure + - KEYCLOAK_SESSION_LEGACY=test/aa10cfc7-2c4d-41f6-8fac-7bf405c572c4/5d288792-2e5a-40ef-a0ad-b16c8fe0502d; + Version=1; Expires=Tue, 06-Feb-2024 19:24:25 GMT; Max-Age=36000; Path=/realms/test/ + - KEYCLOAK_REMEMBER_ME=; Version=1; Comment=Expiring cookie; Expires=Thu, 01-Jan-1970 + 00:00:10 GMT; Max-Age=0; Path=/realms/test/; HttpOnly + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Robots-Tag: + - none + X-XSS-Protection: + - 1; mode=block + content-length: + - '0' + status: + code: 302 + message: Found +- request: + body: client_id=testid&grant_type=authorization_code&code=a068e3fe-40c3-4d0d-8b16-871a80326589.5d288792-2e5a-40ef-a0ad-b16c8fe0502d.adf4ad83-4550-4619-9231-73bd8d700f45&redirect_uri=http%3A%2F%2Ftestserver%2Foidc%2Fcallback%2F + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Basic dGVzdGlkOjdEQjNLVUFBaXpZQ2NtWnVmcEhSVk9jRDBUT2tOTzNJ + Connection: + - keep-alive + Content-Length: + - '220' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - python-requests/2.31.0 + method: POST + uri: http://localhost:8080/realms/test/protocol/openid-connect/token + response: + body: + string: '{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0VU5RQWN2VWN2LURGVU94XzRPMWd0MTNPZEpTb3RxRUtQWnVyczJ2UVc4In0.eyJleHAiOjE3MDcyMTE3NjUsImlhdCI6MTcwNzIxMTQ2NSwiYXV0aF90aW1lIjoxNzA3MjExNDY1LCJqdGkiOiIzN2E5MmJiNy1mZjViLTRkMGYtOTk2MC1kNDljOTU2YmMxMGYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiYWExMGNmYzctMmM0ZC00MWY2LThmYWMtN2JmNDA1YzU3MmM0IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdGlkIiwibm9uY2UiOiJub3QtYS1yYW5kb20tc3RyaW5nIiwic2Vzc2lvbl9zdGF0ZSI6IjVkMjg4NzkyLTJlNWEtNDBlZi1hMGFkLWIxNmM4ZmUwNTAyZCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovLzEyNy4wLjAuMTo4MDAwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLXRlc3QiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSBrdmsgYnNuIiwic2lkIjoiNWQyODg3OTItMmU1YS00MGVmLWEwYWQtYjE2YzhmZTA1MDJkIiwia3ZrIjoiMDEyMzQ1Njc4IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0dXNlciIsImJzbiI6IjAwMDAwMDAwMCJ9.trACfjxXgErqoTdRhKSyrU08XeTZwYB_oEMn-GykJ6wBf1lUuuU5w9ahhWbA4xOdrFumZ-A732NaSg5FCAlPfXO5zvu8JlN4omlf_iL_ciTxEqhDM9p6wgGsrmxEiHO9JGWgVOXhViP_RinVHig1dniOLenvFhbPi6irhtFwPdymoCr9rnBmCHGyv6uUXlNqulrlLV3hnjoPr9BmySm1fe8_hDjXaXWCQPhZxcKC_1d6H9XixVgbV2MWQSyNnTAvpt87JxI-JZbtdzXOITXiysXLRDojFLx3NLoq4TY12C9zDjoTKQ8EE1T3PTF23YQ70lpj_m5Lr-ij3ICVx4M3Sg","expires_in":300,"refresh_expires_in":1799,"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlNzE1ZTA1MS02Y2RiLTQ4Y2MtYjRmNC1mMDcyMmM4MWY5ZDMifQ.eyJleHAiOjE3MDcyMTMyNjQsImlhdCI6MTcwNzIxMTQ2NSwianRpIjoiMGM3NDkyMWMtMWY0NS00NGEyLTk2ZDUtMzE4MDdiZWVkMmZkIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy90ZXN0IiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy90ZXN0Iiwic3ViIjoiYWExMGNmYzctMmM0ZC00MWY2LThmYWMtN2JmNDA1YzU3MmM0IiwidHlwIjoiUmVmcmVzaCIsImF6cCI6InRlc3RpZCIsIm5vbmNlIjoibm90LWEtcmFuZG9tLXN0cmluZyIsInNlc3Npb25fc3RhdGUiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQiLCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIGt2ayBic24iLCJzaWQiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQifQ.WzctyL39U8RvNEcyxu3rWoBm_ZIOoIzJbng6jGX40FE","token_type":"Bearer","id_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0VU5RQWN2VWN2LURGVU94XzRPMWd0MTNPZEpTb3RxRUtQWnVyczJ2UVc4In0.eyJleHAiOjE3MDcyMTE3NjUsImlhdCI6MTcwNzIxMTQ2NSwiYXV0aF90aW1lIjoxNzA3MjExNDY1LCJqdGkiOiI2OWQ2NTk1ZC0xMWZiLTRhMDEtYjkzYi02N2RhZDAwZTZmMTciLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL3Rlc3QiLCJhdWQiOiJ0ZXN0aWQiLCJzdWIiOiJhYTEwY2ZjNy0yYzRkLTQxZjYtOGZhYy03YmY0MDVjNTcyYzQiLCJ0eXAiOiJJRCIsImF6cCI6InRlc3RpZCIsIm5vbmNlIjoibm90LWEtcmFuZG9tLXN0cmluZyIsInNlc3Npb25fc3RhdGUiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQiLCJhdF9oYXNoIjoiR0xSNGF0QVJUcjhISFNFcWx3MGZXUSIsImFjciI6IjEiLCJzaWQiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQiLCJrdmsiOiIwMTIzNDU2NzgiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3R1c2VyIiwiYnNuIjoiMDAwMDAwMDAwIn0.Qh3SN-o-HJX_zMHQFY5B3cz-s_W8ymJkKbldxr3WkD1L7zG5eUxaqzFbCo0TQfsWfKNFQoFXLHzQY_3rkAECgJUE5hWLxbJyd9FQH06i1WqcM6qCWxGqq8EelrSHrSJ7kdBT3LGWriLAn_x64WqBmRSiKDdMAGSbQTZO2Ag8e0k3buAL3CR4Erch3cYFwfjrkRtGz5e78SfghnWbzhAK73gyP190-OWlHLG7n7fPT1CuiFw3Uuxl2t10sGadn1kNHOmbycbs8lzJhbGew5BD-dne9pLGN64uqwGc4k7fhTTxXGV9Fg6J6SFH23jvRp2EOnHkz6i-0N_FChjZppbcIQ","not-before-policy":0,"session_state":"5d288792-2e5a-40ef-a0ad-b16c8fe0502d","scope":"openid + email profile kvk bsn"}' + headers: + Cache-Control: + - no-store + Content-Type: + - application/json + Pragma: + - no-cache + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - 1; mode=block + content-length: + - '3475' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.31.0 + method: GET + uri: http://localhost:8080/realms/test/protocol/openid-connect/certs + response: + body: + string: '{"keys":[{"kid":"4UNQAcvUcv-DFUOx_4O1gt13OdJSotqEKPZurs2vQW8","kty":"RSA","alg":"RS256","use":"sig","n":"2DOZ0qHie73SuFVR7civrl6r82YUiAghfzaMowjCg0o06AF--2lIS7vNV_PbsVVznPAAMqVrNG-8CcevEzvVZMQD9nH4DI7xlOxK0lrYu8rmMeSfOvXVbBVsWBZe0jnGNukZqjwmRE5__ttJdxPfIBT5-2L6mguQbDfhSUEEdIW7y7UfOXvqLqEcBtoIEB-ORKDTUIQwGZM5mSCy-cY3cHvvZfZVgaUUy5NvujPRXTMje4n_hG0KfEV-40G9qC2_Xvx4EooJzBZ6FSThiWhCpwhIvzcQqB6M9lHW7nU6wADhYPNCa2OKWvphwZ_zbrF4B9dmS6Zli5rBvbox9Hh45w","e":"AQAB","x5c":["MIIClzCCAX8CBgGNeYaMLTANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDAR0ZXN0MB4XDTI0MDIwNTEzNDYxN1oXDTM0MDIwNTEzNDc1N1owDzENMAsGA1UEAwwEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANgzmdKh4nu90rhVUe3Ir65eq/NmFIgIIX82jKMIwoNKNOgBfvtpSEu7zVfz27FVc5zwADKlazRvvAnHrxM71WTEA/Zx+AyO8ZTsStJa2LvK5jHknzr11WwVbFgWXtI5xjbpGao8JkROf/7bSXcT3yAU+fti+poLkGw34UlBBHSFu8u1Hzl76i6hHAbaCBAfjkSg01CEMBmTOZkgsvnGN3B772X2VYGlFMuTb7oz0V0zI3uJ/4RtCnxFfuNBvagtv178eBKKCcwWehUk4YloQqcISL83EKgejPZR1u51OsAA4WDzQmtjilr6YcGf826xeAfXZkumZYuawb26MfR4eOcCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAsnQG/Yi2g1XTCJn74hWv9MjxVAaZb4gBAc2AWm5VgAjhFEM9h6x6m1mQkq7JM4rIdAj8jw55Ok9CBVBIqq4G4cME3eUvVytkj2lC9zcRoAivjjZF2HPg7zNPa2TTR50asmHPRokppV6gewO/C+o5as+4P2zqDXBh61aRd/9kdQfkg14LBbH5/dYccAuvUqlTYC4IEPCvVmBNC1xsMjf0vohvoSjm9vL2bfqG/RJH0ScdCjOd5d2zju4/e2oVdluWm+vzKBQplc7tVMuKpn6LcLmVHiGNAl+EBIZH+WVLlTx0D1+kbHZsfLYG53lQg2LsvurRbWyF/a5fVM/oLTn5ag=="],"x5t":"H5xfs1pRtvX0HyVTskx7eTXx88U","x5t#S256":"XurVtKAIEyc4w9HCGOhnjoRHnYu4d9HCn_5YHmkScJg"},{"kid":"TV3Tl5jIY1nrJLSb53UKEubLR5gYiq9slq1SsDDg1HU","kty":"RSA","alg":"RSA-OAEP","use":"enc","n":"pNvU3ecpVHbJT4bCOEpw6cnV1yi65tB3I0bRF2ilLVOY944QRAGnjBBECPIzNbgqavghYp1j75F2nq6_ny1CYfoaxTV2iDpRUw8_f7sliYbl8FrLLat0S25ItlZrg5TEJHObvOqlG2_nXoeH36MRWwNhms2uCqfhn5VgtenIzpQIBolnM7zzGp21NvdJ1C_ZAUzkXC-l3oQ-BXTtpEVM4h2KpYh4gfZJWCbYij5d1e1YApKD6V61_Cs3Oa2OY7CAUyq5kgAWJZFDB6CpzIr226u3bV7F9RbrQu3Ybc_Lv33EwykscLznKWZY2Mbs3Iz_rFNv3sVX_vHpH4DHWlKu7Q","e":"AQAB","x5c":["MIIClzCCAX8CBgGNeYaMlzANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDAR0ZXN0MB4XDTI0MDIwNTEzNDYxN1oXDTM0MDIwNTEzNDc1N1owDzENMAsGA1UEAwwEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKTb1N3nKVR2yU+GwjhKcOnJ1dcouubQdyNG0RdopS1TmPeOEEQBp4wQRAjyMzW4Kmr4IWKdY++Rdp6uv58tQmH6GsU1dog6UVMPP3+7JYmG5fBayy2rdEtuSLZWa4OUxCRzm7zqpRtv516Hh9+jEVsDYZrNrgqn4Z+VYLXpyM6UCAaJZzO88xqdtTb3SdQv2QFM5Fwvpd6EPgV07aRFTOIdiqWIeIH2SVgm2Io+XdXtWAKSg+letfwrNzmtjmOwgFMquZIAFiWRQwegqcyK9turt21exfUW60Lt2G3Py799xMMpLHC85ylmWNjG7NyM/6xTb97FV/7x6R+Ax1pSru0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAQGJHeTYSMvp0yndbIn7DLohO9lom5nRrx/bLyb7TiRfogyJEF6rQZ66CAkQFk5eMF878fsHTuMVjtmXVBnhojhVmK91HwjsNQu/8xR6QMXNKJQMvHR245vwUGxlWRw/36ObM1D7QjCd/q+FonpBEY4m5Y6Uz1U0HR2Cbh0E2afVlPLeV+F0LKrlyVMdIaWBGWftCGIKDAHaG/PD66zbAKtxerv2fBIDq100WHPhd57BZxX+2aGJp1IaRDgkxV0E/CjEy3+Knd8xbAgUSW0Tl6OTC75exIvlbzeluEBe0wlapAb7WvBKYsipSW8G8Ey7tjoolDT4AU82EaKUPstiMnA=="],"x5t":"AlfHDI0FOPQpt3RBAILt0dtW1yw","x5t#S256":"a7bhm8-JsnfY7bL_m8Yl72hgmp5516VZlFcVloKzk08"}]}' + headers: + Cache-Control: + - no-cache + Content-Type: + - application/json;charset=UTF-8 + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - 1; mode=block + content-length: + - '2909' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0VU5RQWN2VWN2LURGVU94XzRPMWd0MTNPZEpTb3RxRUtQWnVyczJ2UVc4In0.eyJleHAiOjE3MDcyMTE3NjUsImlhdCI6MTcwNzIxMTQ2NSwiYXV0aF90aW1lIjoxNzA3MjExNDY1LCJqdGkiOiIzN2E5MmJiNy1mZjViLTRkMGYtOTk2MC1kNDljOTU2YmMxMGYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiYWExMGNmYzctMmM0ZC00MWY2LThmYWMtN2JmNDA1YzU3MmM0IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdGlkIiwibm9uY2UiOiJub3QtYS1yYW5kb20tc3RyaW5nIiwic2Vzc2lvbl9zdGF0ZSI6IjVkMjg4NzkyLTJlNWEtNDBlZi1hMGFkLWIxNmM4ZmUwNTAyZCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovLzEyNy4wLjAuMTo4MDAwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLXRlc3QiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSBrdmsgYnNuIiwic2lkIjoiNWQyODg3OTItMmU1YS00MGVmLWEwYWQtYjE2YzhmZTA1MDJkIiwia3ZrIjoiMDEyMzQ1Njc4IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0dXNlciIsImJzbiI6IjAwMDAwMDAwMCJ9.trACfjxXgErqoTdRhKSyrU08XeTZwYB_oEMn-GykJ6wBf1lUuuU5w9ahhWbA4xOdrFumZ-A732NaSg5FCAlPfXO5zvu8JlN4omlf_iL_ciTxEqhDM9p6wgGsrmxEiHO9JGWgVOXhViP_RinVHig1dniOLenvFhbPi6irhtFwPdymoCr9rnBmCHGyv6uUXlNqulrlLV3hnjoPr9BmySm1fe8_hDjXaXWCQPhZxcKC_1d6H9XixVgbV2MWQSyNnTAvpt87JxI-JZbtdzXOITXiysXLRDojFLx3NLoq4TY12C9zDjoTKQ8EE1T3PTF23YQ70lpj_m5Lr-ij3ICVx4M3Sg + Connection: + - keep-alive + User-Agent: + - python-requests/2.31.0 + method: GET + uri: http://localhost:8080/realms/test/protocol/openid-connect/userinfo + response: + body: + string: '{"sub":"aa10cfc7-2c4d-41f6-8fac-7bf405c572c4","kvk":"012345678","email_verified":false,"preferred_username":"testuser","bsn":"000000000"}' + headers: + Cache-Control: + - no-cache + Content-Type: + - application/json + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-XSS-Protection: + - 1; mode=block + content-length: + - '137' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/conftest.py b/tests/conftest.py index f2f4161..3fc39c0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +from typing import Iterator + import pytest from mozilla_django_oidc_db.forms import OpenIDConnectConfigForm @@ -15,7 +17,7 @@ def mock_state_and_nonce(mocker): @pytest.fixture -def keycloak_config(db): +def keycloak_config(db) -> Iterator[OpenIDConnectConfig]: """ Keycloak configuration for the provided docker-compose.yml setup. @@ -46,6 +48,6 @@ def keycloak_config(db): # in case caching is setup, ensure that it is invalidated config.save() - yield + yield config OpenIDConnectConfig.clear_cache() diff --git a/tests/test_integration_oidc_flow_variants.py b/tests/test_integration_oidc_flow_variants.py index 7c074e4..cacd4dc 100644 --- a/tests/test_integration_oidc_flow_variants.py +++ b/tests/test_integration_oidc_flow_variants.py @@ -2,6 +2,8 @@ import pytest +from mozilla_django_oidc_db.models import OpenIDConnectConfig + from .utils import keycloak_login KEYCLOAK_BASE_URL = "http://localhost:8080/realms/test/" @@ -38,3 +40,41 @@ def test_client_id_secret_full_flow( assert b"client_id=testid" in token_request.body assert b"secret=7DB3KUAAizYCcmZufpHRVOcD0TOkNO3I" in token_request.body assert "Authorization" not in token_request.headers + + +@pytest.mark.vcr +def test_credentials_in_basic_auth_header( + keycloak_config: OpenIDConnectConfig, + mock_state_and_nonce, + client, + django_user_model, + vcr, +): + keycloak_config.oidc_token_use_basic_auth = True + keycloak_config.save() + + django_login_response = client.get(reverse("login")) + # simulate login to Keycloak + redirect_uri = keycloak_login(django_login_response["Location"]) + + # complete the login flow on our end + callback_response = client.get(redirect_uri) + assert callback_response.status_code == 302 + assert callback_response["Location"] == "/admin/" + + # check that the token request was performed as expected + token_request = next( + req + for req in vcr.requests + if req.uri == f"{KEYCLOAK_BASE_URL}protocol/openid-connect/token" + and req.method == "POST" + ) + assert token_request is not None + + assert "Authorization" in token_request.headers + bits = token_request.headers["Authorization"].split(" ") + assert len(bits) == 2 + assert bits[0] == "Basic" + + assert b"client_id=testid" in token_request.body + assert b"secret=" not in token_request.body