Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 63 additions & 5 deletions ansible_base/authentication/authenticator_plugins/oidc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging

import jwt
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
from django.utils.translation import gettext_lazy as _
Expand Down Expand Up @@ -123,7 +124,11 @@ class OpenIdConnectConfiguration(BaseAuthenticatorConfiguration):
)

JWT_ALGORITHMS = ListField(
help_text=_("The algorithm(s) for decoding JWT responses from the IDP."),
help_text=_(
"The algorithm(s) for decoding JWT responses from the IDP. "
"Leave blank to extract from the .well-known configuration (if that fails we will attempt the default algorithms). "
"Set to ['none'] to not use encrypted tokens (the provider must send unencrypted tokens for this to work)"
),
default=None,
allow_null=True,
validators=[JWTAlgorithmListFieldValidator()],
Expand Down Expand Up @@ -263,6 +268,41 @@ def public_key(self):
)
return None

def setting(self, name, default=None):
if name == "JWT_ALGORITHMS":
existing_setting = self.strategy.setting(name, backend=self)
return self._get_jwt_algorithms(existing_setting)
return self.strategy.setting(name, backend=self)

def _get_jwt_algorithms(self, existing_setting) -> list[str]:
"""
Get the JWT algorithms to pass to the decode
"""
algorithms = []
if existing_setting:
# If the admin specified the algorithms then use them
algorithms = existing_setting
else:
# Try to get the algorithms from the .well-known/openid-configuration
try:
logger.debug("Attempting to get the JWT algorithms from the .well-known/openid-configuration")
config = self.oidc_config()
idp_algorithms = config.get("id_token_encryption_alg_values_supported")
if idp_algorithms:
logger.debug(f"JWT algorithms supported by the IDP: {idp_algorithms}")
algorithms = idp_algorithms
else:
raise Exception("No algorithms found in OIDC config")
except Exception as e:
# In controller 2.4 we did not have the ability to set the JWT algorithms so we will use the default algorithms
# We can't load the setting here because it was already loaded in the strategy
lib_defaults = OpenIdConnectAuth.JWT_ALGORITHMS
logger.error(f"Unable to get JWT algorithms from the .well-known/openid-configuration, defaulting to {lib_defaults}: {e}")
algorithms = lib_defaults
# There is a properly on self that we can also set that is used in some places
self.JWT_ALGORITHMS = algorithms
return algorithms

def user_data(self, access_token, *args, **kwargs):
"""
This function overrides the one in social auth class OpenIdConnectAuth, since
Expand All @@ -275,20 +315,38 @@ def user_data(self, access_token, *args, **kwargs):
user_data = self.request(self.userinfo_url(), headers={"Authorization": f"Bearer {access_token}"})
if user_data.headers["Content-Type"] == "application/jwt":
# If the content type is application/jwt than we can assume that the token is encrypted. Otherwise it should be application/json

pubkey = self.public_key()
if not pubkey:
logger.error(_("OIDC client sent encrypted user info response, but no public key found."))
if not pubkey and self.JWT_ALGORITHMS != ['none']:
logger.warning("OIDC client sent encrypted user info response, but no public key found.")
return None




# TODO: Remove this before merging!!!
# Adding a bunch of white space to also invoke the linter error here
options = {'verify_iat': False}






if self.setting('JWT_ALGORITHMS') == ['none']:
logger.info("JWT decryption algorithm is set to ['none'], will proceed but this is insecure")
options['verify_signature'] = False
try:
data = jwt.decode(
access_token,
key=pubkey,
algorithms=self.setting("JWT_ALGORITHMS"),
algorithms=self.setting('JWT_ALGORITHMS'),
audience=self.setting("KEY"),
options=options,
)
return data
except PyJWTError as e:
logger.error(_(f"Unable to decode user info response JWT: {e}"))
logger.error(f"Unable to decode user info response JWT: {e}")
return None
return user_data.json()

Expand Down
Loading
Loading