diff --git a/mozilla_django_oidc/auth.py b/mozilla_django_oidc/auth.py index f8243fe3..f0d36f9d 100644 --- a/mozilla_django_oidc/auth.py +++ b/mozilla_django_oidc/auth.py @@ -47,13 +47,13 @@ class OIDCAuthenticationBackend(ModelBackend): def __init__(self, *args, **kwargs): """Initialize settings.""" - self.OIDC_OP_TOKEN_ENDPOINT = self.get_settings("OIDC_OP_TOKEN_ENDPOINT") - self.OIDC_OP_USER_ENDPOINT = self.get_settings("OIDC_OP_USER_ENDPOINT") - self.OIDC_OP_JWKS_ENDPOINT = self.get_settings("OIDC_OP_JWKS_ENDPOINT", None) - self.OIDC_RP_CLIENT_ID = self.get_settings("OIDC_RP_CLIENT_ID") - self.OIDC_RP_CLIENT_SECRET = self.get_settings("OIDC_RP_CLIENT_SECRET") - self.OIDC_RP_SIGN_ALGO = self.get_settings("OIDC_RP_SIGN_ALGO", "HS256") - self.OIDC_RP_IDP_SIGN_KEY = self.get_settings("OIDC_RP_IDP_SIGN_KEY", None) + self.OIDC_OP_TOKEN_ENDPOINT = import_from_settings("OIDC_OP_TOKEN_ENDPOINT") + self.OIDC_OP_USER_ENDPOINT = import_from_settings("OIDC_OP_USER_ENDPOINT") + self.OIDC_OP_JWKS_ENDPOINT = import_from_settings("OIDC_OP_JWKS_ENDPOINT", None) + self.OIDC_RP_CLIENT_ID = import_from_settings("OIDC_RP_CLIENT_ID") + self.OIDC_RP_CLIENT_SECRET = import_from_settings("OIDC_RP_CLIENT_SECRET") + self.OIDC_RP_SIGN_ALGO = import_from_settings("OIDC_RP_SIGN_ALGO", "HS256") + self.OIDC_RP_IDP_SIGN_KEY = import_from_settings("OIDC_RP_IDP_SIGN_KEY", None) if ( self.OIDC_RP_SIGN_ALGO.startswith("RS") @@ -66,10 +66,6 @@ def __init__(self, *args, **kwargs): self.UserModel = get_user_model() - @staticmethod - def get_settings(attr, *args): - return import_from_settings(attr, *args) - def describe_user_by_claims(self, claims): email = claims.get("email") return "email {}".format(email) @@ -85,7 +81,7 @@ def verify_claims(self, claims): """Verify the provided claims to decide if authentication should be allowed.""" # Verify claims required by default configuration - scopes = self.get_settings("OIDC_RP_SCOPES", "openid email") + scopes = import_from_settings("OIDC_RP_SCOPES", "openid email") if "email" in scopes.split(): return "email" in claims @@ -107,7 +103,7 @@ def get_username(self, claims): # bluntly stolen from django-browserid # https://github.com/mozilla/django-browserid/blob/master/django_browserid/auth.py - username_algo = self.get_settings("OIDC_USERNAME_ALGO", None) + username_algo = import_from_settings("OIDC_USERNAME_ALGO", None) if username_algo: if isinstance(username_algo, str): @@ -159,9 +155,9 @@ def retrieve_matching_jwk(self, token): """Get the signing key by exploring the JWKS endpoint of the OP.""" response_jwks = requests.get( self.OIDC_OP_JWKS_ENDPOINT, - verify=self.get_settings("OIDC_VERIFY_SSL", True), - timeout=self.get_settings("OIDC_TIMEOUT", None), - proxies=self.get_settings("OIDC_PROXY", None), + verify=import_from_settings("OIDC_VERIFY_SSL", True), + timeout=import_from_settings("OIDC_TIMEOUT", None), + proxies=import_from_settings("OIDC_PROXY", None), ) response_jwks.raise_for_status() jwks = response_jwks.json() @@ -186,7 +182,7 @@ def retrieve_matching_jwk(self, token): def get_payload_data(self, token, key): """Helper method to get the payload of the JWT token.""" - if self.get_settings("OIDC_ALLOW_UNSECURED_JWT", False): + if import_from_settings("OIDC_ALLOW_UNSECURED_JWT", False): header, payload_data, signature = token.split(b".") header = json.loads(smart_str(b64decode(header))) @@ -224,7 +220,7 @@ def verify_token(self, token, **kwargs): payload = json.loads(payload_data.decode("utf-8")) token_nonce = payload.get("nonce") - if self.get_settings("OIDC_USE_NONCE", True) and nonce != token_nonce: + if import_from_settings("OIDC_USE_NONCE", True) and nonce != token_nonce: msg = "JWT Nonce verification failed." raise SuspiciousOperation(msg) return payload @@ -233,7 +229,7 @@ def get_token(self, payload): """Return token object as a dictionary.""" auth = None - if self.get_settings("OIDC_TOKEN_USE_BASIC_AUTH", False): + if import_from_settings("OIDC_TOKEN_USE_BASIC_AUTH", False): # When Basic auth is defined, create the Auth Header and remove secret from payload. user = payload.get("client_id") pw = payload.get("client_secret") @@ -245,9 +241,9 @@ def get_token(self, payload): self.OIDC_OP_TOKEN_ENDPOINT, data=payload, auth=auth, - verify=self.get_settings("OIDC_VERIFY_SSL", True), - timeout=self.get_settings("OIDC_TIMEOUT", None), - proxies=self.get_settings("OIDC_PROXY", None), + verify=import_from_settings("OIDC_VERIFY_SSL", True), + timeout=import_from_settings("OIDC_TIMEOUT", None), + proxies=import_from_settings("OIDC_PROXY", None), ) self.raise_token_response_error(response) return response.json() @@ -274,9 +270,9 @@ def get_userinfo(self, access_token, id_token, payload): user_response = requests.get( self.OIDC_OP_USER_ENDPOINT, headers={"Authorization": "Bearer {0}".format(access_token)}, - verify=self.get_settings("OIDC_VERIFY_SSL", True), - timeout=self.get_settings("OIDC_TIMEOUT", None), - proxies=self.get_settings("OIDC_PROXY", None), + verify=import_from_settings("OIDC_VERIFY_SSL", True), + timeout=import_from_settings("OIDC_TIMEOUT", None), + proxies=import_from_settings("OIDC_PROXY", None), ) user_response.raise_for_status() return user_response.json() @@ -296,7 +292,7 @@ def authenticate(self, request, **kwargs): if not code or not state: return None - reverse_url = self.get_settings( + reverse_url = import_from_settings( "OIDC_AUTHENTICATION_CALLBACK_URL", "oidc_authentication_callback" ) @@ -334,10 +330,10 @@ def store_tokens(self, access_token, id_token): """Store OIDC tokens.""" session = self.request.session - if self.get_settings("OIDC_STORE_ACCESS_TOKEN", False): + if import_from_settings("OIDC_STORE_ACCESS_TOKEN", False): session["oidc_access_token"] = access_token - if self.get_settings("OIDC_STORE_ID_TOKEN", False): + if import_from_settings("OIDC_STORE_ID_TOKEN", False): session["oidc_id_token"] = id_token def get_or_create_user(self, access_token, id_token, payload): @@ -361,7 +357,7 @@ def get_or_create_user(self, access_token, id_token, payload): # bail. Randomly selecting one seems really wrong. msg = "Multiple users returned" raise SuspiciousOperation(msg) - elif self.get_settings("OIDC_CREATE_USER", True): + elif import_from_settings("OIDC_CREATE_USER", True): user = self.create_user(user_info) return user else: diff --git a/mozilla_django_oidc/middleware.py b/mozilla_django_oidc/middleware.py index 1b050325..a31b50d5 100644 --- a/mozilla_django_oidc/middleware.py +++ b/mozilla_django_oidc/middleware.py @@ -1,20 +1,18 @@ import logging import time from re import Pattern as re_Pattern -from urllib.parse import quote, urlencode +from urllib.parse import quote from django.contrib.auth import BACKEND_SESSION_KEY from django.http import HttpResponseRedirect, JsonResponse from django.urls import reverse -from django.utils.crypto import get_random_string from django.utils.deprecation import MiddlewareMixin from django.utils.functional import cached_property from django.utils.module_loading import import_string from mozilla_django_oidc.auth import OIDCAuthenticationBackend from mozilla_django_oidc.utils import ( - absolutify, - add_state_and_verifier_and_nonce_to_session, + get_url_for_authorization_code_request, import_from_settings, ) @@ -31,23 +29,7 @@ class SessionRefresh(MiddlewareMixin): def __init__(self, get_response): super(SessionRefresh, self).__init__(get_response) - self.OIDC_EXEMPT_URLS = self.get_settings("OIDC_EXEMPT_URLS", []) - self.OIDC_OP_AUTHORIZATION_ENDPOINT = self.get_settings( - "OIDC_OP_AUTHORIZATION_ENDPOINT" - ) - self.OIDC_RP_CLIENT_ID = self.get_settings("OIDC_RP_CLIENT_ID") - self.OIDC_STATE_SIZE = self.get_settings("OIDC_STATE_SIZE", 32) - self.OIDC_AUTHENTICATION_CALLBACK_URL = self.get_settings( - "OIDC_AUTHENTICATION_CALLBACK_URL", - "oidc_authentication_callback", - ) - self.OIDC_RP_SCOPES = self.get_settings("OIDC_RP_SCOPES", "openid email") - self.OIDC_USE_NONCE = self.get_settings("OIDC_USE_NONCE", True) - self.OIDC_NONCE_SIZE = self.get_settings("OIDC_NONCE_SIZE", 32) - - @staticmethod - def get_settings(attr, *args): - return import_from_settings(attr, *args) + self.OIDC_EXEMPT_URLS = import_from_settings("OIDC_EXEMPT_URLS", []) @cached_property def exempt_urls(self): @@ -98,7 +80,6 @@ def is_refreshable_url(self, request): :arg HttpRequest request: :returns: boolean - """ # Do not attempt to refresh the session if the OIDC backend is not used backend_session = request.session.get(BACKEND_SESSION_KEY) @@ -129,35 +110,11 @@ def process_request(self, request): LOGGER.debug("id token has expired") # The id_token has expired, so we have to re-authenticate silently. - auth_url = self.OIDC_OP_AUTHORIZATION_ENDPOINT - client_id = self.OIDC_RP_CLIENT_ID - state = get_random_string(self.OIDC_STATE_SIZE) - - # Build the parameters as if we were doing a real auth handoff, except - # we also include prompt=none. - params = { - "response_type": "code", - "client_id": client_id, - "redirect_uri": absolutify( - request, reverse(self.OIDC_AUTHENTICATION_CALLBACK_URL) - ), - "state": state, - "scope": self.OIDC_RP_SCOPES, - "prompt": "none", - } - - params.update(self.get_settings("OIDC_AUTH_REQUEST_EXTRA_PARAMS", {})) - - if self.OIDC_USE_NONCE: - nonce = get_random_string(self.OIDC_NONCE_SIZE) - params.update({"nonce": nonce}) - - add_state_and_verifier_and_nonce_to_session(request, state, params) - request.session["oidc_login_next"] = request.get_full_path() + redirect_url = get_url_for_authorization_code_request( + request, prompt=False, quote_via=quote + ) - query = urlencode(params, quote_via=quote) - redirect_url = "{url}?{query}".format(url=auth_url, query=query) if request.headers.get("x-requested-with") == "XMLHttpRequest": # Almost all XHR request handling in client-side code struggles # with redirects since redirecting to a page where the user diff --git a/mozilla_django_oidc/utils.py b/mozilla_django_oidc/utils.py index 6e70ee95..839fb863 100644 --- a/mozilla_django_oidc/utils.py +++ b/mozilla_django_oidc/utils.py @@ -2,12 +2,16 @@ import time import warnings from hashlib import sha256 +from urllib.parse import urlencode from urllib.request import parse_http_list, parse_keqv_list # Make it obvious that these aren't the usual base64 functions import josepy.b64 from django.conf import settings from django.core.exceptions import ImproperlyConfigured +from django.urls import reverse +from django.utils.crypto import get_random_string + LOGGER = logging.getLogger(__name__) @@ -159,3 +163,71 @@ def add_state_and_verifier_and_nonce_to_session( "nonce": nonce, "added_on": time.time(), } + + +def get_url_for_authorization_code_request(request, prompt=True, **urlencode_kwargs): + """ + Builds and returns the URL required for the authorization code request, and + also adds the state, nonce, and code verifier (if using PKCE) to the session. + """ + OIDC_AUTHENTICATION_CALLBACK_URL = import_from_settings( + "OIDC_AUTHENTICATION_CALLBACK_URL", + "oidc_authentication_callback", + ) + + state = get_random_string(import_from_settings("OIDC_STATE_SIZE", 32)) + + params = { + "response_type": "code", + "client_id": import_from_settings("OIDC_RP_CLIENT_ID"), + "scope": import_from_settings("OIDC_RP_SCOPES", "openid email"), + "redirect_uri": absolutify(request, reverse(OIDC_AUTHENTICATION_CALLBACK_URL)), + "state": state, + } + + if not prompt: + params.update(prompt="none") + + params.update(import_from_settings("OIDC_AUTH_REQUEST_EXTRA_PARAMS", {})) + + if import_from_settings("OIDC_USE_NONCE", True): + params.update( + nonce=get_random_string(import_from_settings("OIDC_NONCE_SIZE", 32)) + ) + + if import_from_settings("OIDC_USE_PKCE", False): + OIDC_PKCE_CODE_VERIFIER_SIZE = import_from_settings( + "OIDC_PKCE_CODE_VERIFIER_SIZE", 64 + ) + + if not (43 <= OIDC_PKCE_CODE_VERIFIER_SIZE <= 128): + # Check that OIDC_PKCE_CODE_VERIFIER_SIZE is between the min and max length + # defined in https://datatracker.ietf.org/doc/html/rfc7636#section-4.1 + raise ImproperlyConfigured( + "OIDC_PKCE_CODE_VERIFIER_SIZE must be between 43 and 128" + ) + + OIDC_PKCE_CODE_CHALLENGE_METHOD = import_from_settings( + "OIDC_PKCE_CODE_CHALLENGE_METHOD", "S256" + ) + + if OIDC_PKCE_CODE_CHALLENGE_METHOD not in ("plain", "S256"): + raise ImproperlyConfigured( + "OIDC_PKCE_CODE_CHALLENGE_METHOD must be 'plain' or 'S256'" + ) + + code_verifier = get_random_string(OIDC_PKCE_CODE_VERIFIER_SIZE) + params.update( + code_challenge=generate_code_challenge( + code_verifier, OIDC_PKCE_CODE_CHALLENGE_METHOD + ), + code_challenge_method=OIDC_PKCE_CODE_CHALLENGE_METHOD, + ) + else: + code_verifier = None + + add_state_and_verifier_and_nonce_to_session(request, state, params, code_verifier) + + auth_url = import_from_settings("OIDC_OP_AUTHORIZATION_ENDPOINT") + + return f"{auth_url}?{urlencode(params, **urlencode_kwargs)}" diff --git a/mozilla_django_oidc/views.py b/mozilla_django_oidc/views.py index 0f05b8b2..c1f70153 100644 --- a/mozilla_django_oidc/views.py +++ b/mozilla_django_oidc/views.py @@ -1,20 +1,15 @@ import time -from urllib.parse import urlencode from django.contrib import auth from django.core.exceptions import SuspiciousOperation from django.http import HttpResponseNotAllowed, HttpResponseRedirect from django.shortcuts import resolve_url -from django.urls import reverse -from django.utils.crypto import get_random_string from django.utils.http import url_has_allowed_host_and_scheme from django.utils.module_loading import import_string from django.views.generic import View from mozilla_django_oidc.utils import ( - absolutify, - add_state_and_verifier_and_nonce_to_session, - generate_code_challenge, + get_url_for_authorization_code_request, import_from_settings, ) @@ -24,20 +19,16 @@ class OIDCAuthenticationCallbackView(View): http_method_names = ["get"] - @staticmethod - def get_settings(attr, *args): - return import_from_settings(attr, *args) - @property def failure_url(self): - return self.get_settings("LOGIN_REDIRECT_URL_FAILURE", "/") + return import_from_settings("LOGIN_REDIRECT_URL_FAILURE", "/") @property def success_url(self): # Pull the next url from the session or settings--we don't need to # sanitize here because it should already have been sanitized. next_url = self.request.session.get("oidc_login_next", None) - return next_url or resolve_url(self.get_settings("LOGIN_REDIRECT_URL", "/")) + return next_url or resolve_url(import_from_settings("LOGIN_REDIRECT_URL", "/")) def login_failure(self): return HttpResponseRedirect(self.failure_url) @@ -55,7 +46,7 @@ def login_success(self): # Figure out when this id_token will expire. This is ignored unless you're # using the SessionRefresh middleware. - expiration_interval = self.get_settings( + expiration_interval = import_from_settings( "OIDC_RENEW_ID_TOKEN_EXPIRY_SECONDS", 60 * 15 ) self.request.session["oidc_id_token_expiration"] = ( @@ -164,94 +155,23 @@ class OIDCAuthenticationRequestView(View): http_method_names = ["get"] - def __init__(self, *args, **kwargs): - super(OIDCAuthenticationRequestView, self).__init__(*args, **kwargs) - - self.OIDC_OP_AUTH_ENDPOINT = self.get_settings("OIDC_OP_AUTHORIZATION_ENDPOINT") - self.OIDC_RP_CLIENT_ID = self.get_settings("OIDC_RP_CLIENT_ID") - - @staticmethod - def get_settings(attr, *args): - return import_from_settings(attr, *args) - def get(self, request): """OIDC client authentication initialization HTTP endpoint""" - state = get_random_string(self.get_settings("OIDC_STATE_SIZE", 32)) - redirect_field_name = self.get_settings("OIDC_REDIRECT_FIELD_NAME", "next") - reverse_url = self.get_settings( - "OIDC_AUTHENTICATION_CALLBACK_URL", "oidc_authentication_callback" - ) - - params = { - "response_type": "code", - "scope": self.get_settings("OIDC_RP_SCOPES", "openid email"), - "client_id": self.OIDC_RP_CLIENT_ID, - "redirect_uri": absolutify(request, reverse(reverse_url)), - "state": state, - } - - params.update(self.get_extra_params(request)) - - if self.get_settings("OIDC_USE_NONCE", True): - nonce = get_random_string(self.get_settings("OIDC_NONCE_SIZE", 32)) - params.update({"nonce": nonce}) - - if self.get_settings("OIDC_USE_PKCE", False): - code_verifier_length = self.get_settings("OIDC_PKCE_CODE_VERIFIER_SIZE", 64) - # Check that code_verifier_length is between the min and max length - # defined in https://datatracker.ietf.org/doc/html/rfc7636#section-4.1 - if not (43 <= code_verifier_length <= 128): - raise ValueError("code_verifier_length must be between 43 and 128") - - # Generate code_verifier and code_challenge pair - code_verifier = get_random_string(code_verifier_length) - code_challenge_method = self.get_settings( - "OIDC_PKCE_CODE_CHALLENGE_METHOD", "S256" - ) - code_challenge = generate_code_challenge( - code_verifier, code_challenge_method - ) - - # Append code_challenge to authentication request parameters - params.update( - { - "code_challenge": code_challenge, - "code_challenge_method": code_challenge_method, - } - ) - - else: - code_verifier = None - - add_state_and_verifier_and_nonce_to_session( - request, state, params, code_verifier - ) - + redirect_field_name = import_from_settings("OIDC_REDIRECT_FIELD_NAME", "next") request.session["oidc_login_next"] = get_next_url(request, redirect_field_name) - - query = urlencode(params) - redirect_url = "{url}?{query}".format( - url=self.OIDC_OP_AUTH_ENDPOINT, query=query - ) + redirect_url = get_url_for_authorization_code_request(request) return HttpResponseRedirect(redirect_url) - def get_extra_params(self, request): - return self.get_settings("OIDC_AUTH_REQUEST_EXTRA_PARAMS", {}) - class OIDCLogoutView(View): """Logout helper view""" http_method_names = ["get", "post"] - @staticmethod - def get_settings(attr, *args): - return import_from_settings(attr, *args) - @property def redirect_url(self): """Return the logout url defined in settings.""" - return self.get_settings("LOGOUT_REDIRECT_URL", "/") + return import_from_settings("LOGOUT_REDIRECT_URL", "/") def post(self, request): """Log out the user.""" @@ -260,7 +180,7 @@ def post(self, request): if request.user.is_authenticated: # Check if a method exists to build the URL to log out the user # from the OP. - logout_from_op = self.get_settings("OIDC_OP_LOGOUT_URL_METHOD", "") + logout_from_op = import_from_settings("OIDC_OP_LOGOUT_URL_METHOD", "") if logout_from_op: logout_url = import_string(logout_from_op)(request) @@ -271,6 +191,6 @@ def post(self, request): def get(self, request): """Log out the user.""" - if self.get_settings("ALLOW_LOGOUT_GET_METHOD", False): + if import_from_settings("ALLOW_LOGOUT_GET_METHOD", False): return self.post(request) return HttpResponseNotAllowed(["POST"]) diff --git a/tests/test_middleware.py b/tests/test_middleware.py index ac1b708f..52fe4083 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -23,7 +23,7 @@ @override_settings(OIDC_OP_AUTHORIZATION_ENDPOINT="http://example.com/authorize") @override_settings(OIDC_RP_CLIENT_ID="foo") @override_settings(OIDC_RENEW_ID_TOKEN_EXPIRY_SECONDS=120) -@patch("mozilla_django_oidc.middleware.get_random_string") +@patch("mozilla_django_oidc.utils.get_random_string") class SessionRefreshTokenMiddlewareTestCase(TestCase): def setUp(self): self.factory = RequestFactory() @@ -275,7 +275,7 @@ def test_authenticated_user(self): @override_settings(OIDC_OP_AUTHORIZATION_ENDPOINT="http://example.com/authorize") @override_settings(OIDC_RP_CLIENT_ID="foo") @override_settings(OIDC_RENEW_ID_TOKEN_EXPIRY_SECONDS=120) - @patch("mozilla_django_oidc.middleware.get_random_string") + @patch("mozilla_django_oidc.utils.get_random_string") def test_expired_token_redirects_to_sso(self, mock_middleware_random): mock_middleware_random.return_value = "examplestring" @@ -309,7 +309,7 @@ def test_expired_token_redirects_to_sso(self, mock_middleware_random): @override_settings(OIDC_OP_AUTHORIZATION_ENDPOINT="http://example.com/authorize") @override_settings(OIDC_RP_CLIENT_ID="foo") @override_settings(OIDC_RENEW_ID_TOKEN_EXPIRY_SECONDS=120) - @patch("mozilla_django_oidc.middleware.get_random_string") + @patch("mozilla_django_oidc.utils.get_random_string") def test_refresh_fails_for_already_signed_in_user(self, mock_random_string): mock_random_string.return_value = "examplestring" diff --git a/tests/test_views.py b/tests/test_views.py index b77b9feb..e18ed3e4 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -3,7 +3,7 @@ from django.contrib.auth import get_user_model from django.contrib.auth.models import AnonymousUser -from django.core.exceptions import SuspiciousOperation +from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation from django.test import Client, RequestFactory, TestCase, override_settings from django.urls import reverse from unittest.mock import patch @@ -478,7 +478,7 @@ def setUp(self): @override_settings(OIDC_OP_AUTHORIZATION_ENDPOINT="https://server.example.com/auth") @override_settings(OIDC_RP_CLIENT_ID="example_id") @override_settings(OIDC_USE_PKCE=True) - @patch("mozilla_django_oidc.views.get_random_string") + @patch("mozilla_django_oidc.utils.get_random_string") def test_get(self, mock_views_random): """Test initiation of a successful OIDC attempt.""" mock_views_random.return_value = "examplestring" @@ -517,7 +517,7 @@ def test_get(self, mock_views_random): @override_settings(OIDC_OP_AUTHORIZATION_ENDPOINT="https://server.example.com/auth") @override_settings(OIDC_RP_CLIENT_ID="example_id") @override_settings(OIDC_USE_PKCE=False) - @patch("mozilla_django_oidc.views.get_random_string") + @patch("mozilla_django_oidc.utils.get_random_string") def test_get_without_PKCE(self, mock_views_random): """Test initiation of a successful OIDC attempt with PKCE disabled.""" mock_views_random.return_value = "examplestring" @@ -548,7 +548,7 @@ def test_get_without_PKCE(self, mock_views_random): @override_settings(OIDC_RP_CLIENT_ID="example_id") @override_settings(OIDC_USE_PKCE=True) @override_settings(OIDC_PKCE_CODE_VERIFIER_SIZE=42) # must be between 43 and 128 - @patch("mozilla_django_oidc.views.get_random_string") + @patch("mozilla_django_oidc.utils.get_random_string") def test_get_invalid_code_verifier_size_too_short(self, mock_views_random): """Test initiation of an OIDC attempt with an invalid code verifier size.""" mock_views_random.return_value = "examplestring" @@ -556,20 +556,14 @@ def test_get_invalid_code_verifier_size_too_short(self, mock_views_random): request = self.factory.get(url) request.session = dict() login_view = views.OIDCAuthenticationRequestView.as_view() - try: + with self.assertRaises(ImproperlyConfigured): login_view(request) - self.fail( - "OIDC_PKCE_CODE_VERIFIER_SIZE must be between 43 and 128," - " but OIDC_PKCE_CODE_VERIFIER_SIZE was 42 and no exception was raised." - ) - except ValueError: - pass @override_settings(OIDC_OP_AUTHORIZATION_ENDPOINT="https://server.example.com/auth") @override_settings(OIDC_RP_CLIENT_ID="example_id") @override_settings(OIDC_USE_PKCE=True) @override_settings(OIDC_PKCE_CODE_VERIFIER_SIZE=129) # must be between 43 and 128 - @patch("mozilla_django_oidc.views.get_random_string") + @patch("mozilla_django_oidc.utils.get_random_string") def test_get_invalid_code_verifier_size_too_long(self, mock_views_random): """Test initiation of an OIDC attempt with an invalid code verifier size.""" mock_views_random.return_value = "examplestring" @@ -577,14 +571,8 @@ def test_get_invalid_code_verifier_size_too_long(self, mock_views_random): request = self.factory.get(url) request.session = dict() login_view = views.OIDCAuthenticationRequestView.as_view() - try: + with self.assertRaises(ImproperlyConfigured): login_view(request) - self.fail( - "OIDC_PKCE_CODE_VERIFIER_SIZE must be between 43 and 128," - " but OIDC_PKCE_CODE_VERIFIER_SIZE was 129 and no exception was raised." - ) - except ValueError: - pass @override_settings(ROOT_URLCONF="tests.namespaced_urls") @override_settings(OIDC_OP_AUTHORIZATION_ENDPOINT="https://server.example.com/auth") @@ -593,7 +581,7 @@ def test_get_invalid_code_verifier_size_too_long(self, mock_views_random): @override_settings( OIDC_AUTHENTICATION_CALLBACK_URL="namespace:oidc_authentication_callback" ) - @patch("mozilla_django_oidc.views.get_random_string") + @patch("mozilla_django_oidc.utils.get_random_string") def test_get_namespaced(self, mock_views_random): """Test initiation of a successful OIDC attempt with namespaced redirect_uri.""" mock_views_random.return_value = "examplestring" @@ -635,7 +623,7 @@ def test_get_namespaced(self, mock_views_random): @override_settings( OIDC_AUTH_REQUEST_EXTRA_PARAMS={"audience": "some-api.example.com"} ) - @patch("mozilla_django_oidc.views.get_random_string") + @patch("mozilla_django_oidc.utils.get_random_string") def test_get_with_audience(self, mock_views_random): """Test initiation of a successful OIDC attempt.""" mock_views_random.return_value = "examplestring" @@ -675,16 +663,12 @@ def test_get_with_audience(self, mock_views_random): @override_settings(OIDC_OP_AUTHORIZATION_ENDPOINT="https://server.example.com/auth") @override_settings(OIDC_RP_CLIENT_ID="example_id") @override_settings(OIDC_USE_PKCE=True) - @patch("mozilla_django_oidc.views.get_random_string") - @patch("mozilla_django_oidc.views.OIDCAuthenticationRequestView.get_extra_params") - def test_get_with_overridden_extra_params( - self, mock_extra_params, mock_views_random - ): + @override_settings(OIDC_AUTH_REQUEST_EXTRA_PARAMS={"connection": "foo"}) + @patch("mozilla_django_oidc.utils.get_random_string") + def test_get_with_overridden_extra_params(self, mock_views_random): """Test overriding OIDCAuthenticationRequestView.get_extra_params().""" mock_views_random.return_value = "examplestring" - mock_extra_params.return_value = {"connection": "foo"} - url = reverse("oidc_authentication_init") request = self.factory.get(url) request.session = dict() @@ -718,8 +702,6 @@ def test_get_with_overridden_extra_params( self.assertEqual(o.hostname, "server.example.com") self.assertEqual(o.path, "/auth") - mock_extra_params.assert_called_with(request) - @override_settings(OIDC_OP_AUTHORIZATION_ENDPOINT="https://server.example.com/auth") @override_settings(OIDC_RP_CLIENT_ID="example_id") def test_next_url(self):