Skip to content
Open
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
59 changes: 58 additions & 1 deletion ansible_base/authentication/authenticator_plugins/oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,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 @@ -300,3 +304,56 @@ def get_alternative_uid(self, **kwargs):
return preferred_username

return None

def _discover_algorithms_from_config(self, config):
"""
Discover JWT algorithms from OIDC configuration
"""
# Try signing algorithms first (most common)
algorithms = config.get("id_token_signing_alg_values_supported")
if algorithms:
logger.debug(f"JWT signing algorithms supported by the IDP: {algorithms}")
return algorithms

# Fallback to encryption algorithms
algorithms = config.get("id_token_encryption_alg_values_supported")
if algorithms:
logger.debug(f"JWT encryption algorithms supported by the IDP: {algorithms}")
return algorithms

# Try userinfo signing algorithms as final fallback
algorithms = config.get("userinfo_signing_alg_values_supported")
if algorithms:
logger.debug(f"JWT userinfo signing algorithms supported by the IDP: {algorithms}")
return algorithms

return None

def _get_jwt_algorithms(self, existing_setting=None) -> list[str]:
"""
Get the JWT algorithms to pass to the decode
"""
if existing_setting:
algorithms = existing_setting
else:
try:
logger.debug("Attempting to get the JWT algorithms from the .well-known/openid-configuration")
config = self.oidc_config()
algorithms = self._discover_algorithms_from_config(config)

if not algorithms:
raise Exception("No algorithms found in OIDC config")

except Exception as e:
# Fallback to default algorithms if discovery fails
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

# Ensure we always return a list
if not isinstance(algorithms, list):
algorithms = [algorithms] if algorithms else []

# Set the property for use elsewhere
self.JWT_ALGORITHMS = algorithms
return algorithms
29 changes: 29 additions & 0 deletions ansible_base/authentication/models/authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,31 @@ class Authenticator(UniqueNamedCommonModel):
on_delete=SET_NULL,
)

def save_default_jwt_algorithms(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should move this out of the model. It shouldn't know about specifics for requirements of any of the plugins.

try:
# Create a proper plugin instance with the database instance
from ansible_base.authentication.authenticator_plugins.utils import get_authenticator_class

authenticator_class = get_authenticator_class(self.type)
plugin_instance = authenticator_class(database_instance=self)

# Try to get algorithms from .well-known endpoint
import logging

logger = logging.getLogger('ansible_base.authentication.models.authenticator')
logger.info("Auto-populating JWT algorithms for OIDC authenticator from .well-known endpoint")

algorithms = plugin_instance._get_jwt_algorithms()
if algorithms:
self.configuration['JWT_ALGORITHMS'] = algorithms
logger.info(f"Successfully populated JWT algorithms: {algorithms}")
except Exception as e:
import logging

logger = logging.getLogger('ansible_base.authentication.models.authenticator')
logger.warning(f"Could not auto-populate JWT algorithms for OIDC authenticator: {e}")
# Don't fail the save operation, just log the warning

def save(self, *args, **kwargs):
from ansible_base.lib.utils.encryption import ansible_encryption

Expand All @@ -65,6 +90,10 @@ def save(self, *args, **kwargs):
if field in self.configuration:
self.configuration[field] = ansible_encryption.encrypt_string(self.configuration[field])

# Auto-populate JWT algorithms for OIDC authenticators if not set
if self.type == "ansible_base.authentication.authenticator_plugins.oidc" and self.configuration and not self.configuration.get('JWT_ALGORITHMS'):
self.save_default_jwt_algorithms()

if not self.slug:
self.slug = generate_authenticator_slug()
super().save(*args, **kwargs)
Expand Down
Loading