Skip to content

Commit

Permalink
refactor for consolidated auth code request
Browse files Browse the repository at this point in the history
  • Loading branch information
escattone committed Jan 3, 2024
1 parent f75ff62 commit ca1af40
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 194 deletions.
62 changes: 29 additions & 33 deletions mozilla_django_oidc/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from requests.auth import HTTPBasicAuth
from requests.exceptions import HTTPError

from mozilla_django_oidc.utils import absolutify, import_from_settings
from mozilla_django_oidc.utils import absolutify, get_setting

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -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 = get_setting("OIDC_OP_TOKEN_ENDPOINT")
self.OIDC_OP_USER_ENDPOINT = get_setting("OIDC_OP_USER_ENDPOINT")
self.OIDC_OP_JWKS_ENDPOINT = get_setting("OIDC_OP_JWKS_ENDPOINT", None)
self.OIDC_RP_CLIENT_ID = get_setting("OIDC_RP_CLIENT_ID")
self.OIDC_RP_CLIENT_SECRET = get_setting("OIDC_RP_CLIENT_SECRET")
self.OIDC_RP_SIGN_ALGO = get_setting("OIDC_RP_SIGN_ALGO", "HS256")
self.OIDC_RP_IDP_SIGN_KEY = get_setting("OIDC_RP_IDP_SIGN_KEY", None)

if (
self.OIDC_RP_SIGN_ALGO.startswith("RS")
Expand All @@ -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)
Expand All @@ -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 = get_setting("OIDC_RP_SCOPES", "openid email")
if "email" in scopes.split():
return "email" in claims

Expand All @@ -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 = get_setting("OIDC_USERNAME_ALGO", None)

if username_algo:
if isinstance(username_algo, str):
Expand Down Expand Up @@ -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=get_setting("OIDC_VERIFY_SSL", True),
timeout=get_setting("OIDC_TIMEOUT", None),
proxies=get_setting("OIDC_PROXY", None),
)
response_jwks.raise_for_status()
jwks = response_jwks.json()
Expand All @@ -173,9 +169,9 @@ def retrieve_matching_jwk(self, token):

key = None
for jwk in jwks["keys"]:
if import_from_settings("OIDC_VERIFY_KID", True) and jwk[
"kid"
] != smart_str(header.kid):
if get_setting("OIDC_VERIFY_KID", True) and jwk["kid"] != smart_str(
header.kid
):
continue
if "alg" in jwk and jwk["alg"] != smart_str(header.alg):
continue
Expand All @@ -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 get_setting("OIDC_ALLOW_UNSECURED_JWT", False):
header, payload_data, signature = token.split(b".")
header = json.loads(smart_str(b64decode(header)))

Expand Down Expand Up @@ -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 get_setting("OIDC_USE_NONCE", True) and nonce != token_nonce:
msg = "JWT Nonce verification failed."
raise SuspiciousOperation(msg)
return payload
Expand All @@ -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 get_setting("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")
Expand All @@ -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=get_setting("OIDC_VERIFY_SSL", True),
timeout=get_setting("OIDC_TIMEOUT", None),
proxies=get_setting("OIDC_PROXY", None),
)
self.raise_token_response_error(response)
return response.json()
Expand All @@ -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=get_setting("OIDC_VERIFY_SSL", True),
timeout=get_setting("OIDC_TIMEOUT", None),
proxies=get_setting("OIDC_PROXY", None),
)
user_response.raise_for_status()
return user_response.json()
Expand All @@ -296,7 +292,7 @@ def authenticate(self, request, **kwargs):
if not code or not state:
return None

reverse_url = self.get_settings(
reverse_url = get_setting(
"OIDC_AUTHENTICATION_CALLBACK_URL", "oidc_authentication_callback"
)

Expand Down Expand Up @@ -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 get_setting("OIDC_STORE_ACCESS_TOKEN", False):
session["oidc_access_token"] = access_token

if self.get_settings("OIDC_STORE_ID_TOKEN", False):
if get_setting("OIDC_STORE_ID_TOKEN", False):
session["oidc_id_token"] = id_token

def get_or_create_user(self, access_token, id_token, payload):
Expand All @@ -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 get_setting("OIDC_CREATE_USER", True):
user = self.create_user(user_info)
return user
else:
Expand Down
4 changes: 2 additions & 2 deletions mozilla_django_oidc/contrib/drf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from mozilla_django_oidc.auth import OIDCAuthenticationBackend
from mozilla_django_oidc.utils import (
import_from_settings,
get_setting,
parse_www_authenticate_header,
)

Expand All @@ -29,7 +29,7 @@ def get_oidc_backend():
# allow the user to force which back backend to use. this is mostly
# convenient if you want to use OIDC with DRF but don't want to configure
# OIDC for the "normal" Django auth.
backend_setting = import_from_settings("OIDC_DRF_AUTH_BACKEND", None)
backend_setting = get_setting("OIDC_DRF_AUTH_BACKEND", None)
if backend_setting:
backend = import_string(backend_setting)()
if not isinstance(backend, OIDCAuthenticationBackend):
Expand Down
57 changes: 7 additions & 50 deletions mozilla_django_oidc/middleware.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
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,
import_from_settings,
get_url_for_authorization_code_request,
get_setting,
)

LOGGER = logging.getLogger(__name__)
Expand All @@ -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 = get_setting("OIDC_EXEMPT_URLS", [])

@cached_property
def exempt_urls(self):
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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_params_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
Expand Down
8 changes: 3 additions & 5 deletions mozilla_django_oidc/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@
from django.utils.module_loading import import_string

from mozilla_django_oidc import views
from mozilla_django_oidc.utils import import_from_settings
from mozilla_django_oidc.utils import get_setting

DEFAULT_CALLBACK_CLASS = "mozilla_django_oidc.views.OIDCAuthenticationCallbackView"
CALLBACK_CLASS_PATH = import_from_settings(
"OIDC_CALLBACK_CLASS", DEFAULT_CALLBACK_CLASS
)
CALLBACK_CLASS_PATH = get_setting("OIDC_CALLBACK_CLASS", DEFAULT_CALLBACK_CLASS)

OIDCCallbackClass = import_string(CALLBACK_CLASS_PATH)


DEFAULT_AUTHENTICATE_CLASS = "mozilla_django_oidc.views.OIDCAuthenticationRequestView"
AUTHENTICATE_CLASS_PATH = import_from_settings(
AUTHENTICATE_CLASS_PATH = get_setting(
"OIDC_AUTHENTICATE_CLASS", DEFAULT_AUTHENTICATE_CLASS
)

Expand Down
Loading

0 comments on commit ca1af40

Please sign in to comment.