diff --git a/lib/galaxy/authnz/managers.py b/lib/galaxy/authnz/managers.py
index baa644e460e4..15fdae4e5b83 100644
--- a/lib/galaxy/authnz/managers.py
+++ b/lib/galaxy/authnz/managers.py
@@ -176,6 +176,8 @@ def _parse_idp_config(self, config_xml):
rtv["pkce_support"] = asbool(config_xml.find("pkce_support").text)
if config_xml.find("accepted_audiences") is not None:
rtv["accepted_audiences"] = config_xml.find("accepted_audiences").text
+ if config_xml.find("username_key") is not None:
+ rtv["username_key"] = config_xml.find("username_key").text
# this is a EGI Check-in specific config
if config_xml.find("checkin_env") is not None:
rtv["checkin_env"] = config_xml.find("checkin_env").text
diff --git a/lib/galaxy/authnz/psa_authnz.py b/lib/galaxy/authnz/psa_authnz.py
index 2179144f16d8..e7cefebe8176 100644
--- a/lib/galaxy/authnz/psa_authnz.py
+++ b/lib/galaxy/authnz/psa_authnz.py
@@ -152,6 +152,8 @@ def _setup_idp(self, oidc_backend_config):
self.config[setting_name("API_URL")] = oidc_backend_config.get("api_url")
if oidc_backend_config.get("url") is not None:
self.config[setting_name("URL")] = oidc_backend_config.get("url")
+ if oidc_backend_config.get("username_key") is not None:
+ self.config[setting_name("USERNAME_KEY")] = oidc_backend_config.get("username_key")
def _get_helper(self, name, do_import=False):
this_config = self.config.get(setting_name(name), DEFAULTS.get(name, None))
diff --git a/lib/galaxy/authnz/xsd/oidc_backends_config.xsd b/lib/galaxy/authnz/xsd/oidc_backends_config.xsd
index cf88b04102ee..ebf9b8ff6cb9 100644
--- a/lib/galaxy/authnz/xsd/oidc_backends_config.xsd
+++ b/lib/galaxy/authnz/xsd/oidc_backends_config.xsd
@@ -156,6 +156,13 @@
+
+
+
+ Claim that will be used for the username (python-social-auth uses 'preferred_username' by default)
+
+
+
diff --git a/test/unit/authnz/__init__.py b/test/unit/authnz/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/test/unit/authnz/test_authnz.py b/test/unit/authnz/test_authnz.py
new file mode 100644
index 000000000000..460a71540391
--- /dev/null
+++ b/test/unit/authnz/test_authnz.py
@@ -0,0 +1,119 @@
+import tempfile
+from pathlib import Path
+from unittest.mock import MagicMock
+
+import pytest
+from social_core.utils import setting_name
+
+from galaxy.authnz.managers import AuthnzManager
+from galaxy.util import asbool
+
+
+@pytest.fixture
+def mock_app():
+ yield MagicMock()
+
+
+OIDC_BACKEND_CONFIG_TEMPLATE = """
+
+
+ {url}
+ {client_id}
+ {client_secret}
+ $galaxy_url/authnz/keycloak/callback
+ {enable_idp_logout}
+ {require_create_confirmation}
+ {accepted_audiences}
+ {username_key}
+
+
+"""
+
+
+OIDC_CONFIG_TEMPLATE = """
+
+
+ {extra_properties}
+
+"""
+
+
+def create_oidc_config(extra_properties: str = "") -> (str, Path):
+ contents = OIDC_CONFIG_TEMPLATE.format(extra_properties=extra_properties)
+ file = tempfile.NamedTemporaryFile(mode="w", delete=False)
+ file.write(contents)
+ return contents, file.name
+
+
+def create_backend_config(
+ provider_name="oidc",
+ url="https://example.com",
+ client_id="client_id",
+ client_secret="client_secret",
+ enable_idp_logout="true",
+ require_create_confirmation="false",
+ accepted_audiences="https://audience.example.com",
+ username_key="custom_username",
+) -> (str, Path):
+ contents = OIDC_BACKEND_CONFIG_TEMPLATE.format(
+ provider_name=provider_name,
+ url=url,
+ client_id=client_id,
+ client_secret=client_secret,
+ enable_idp_logout=enable_idp_logout,
+ require_create_confirmation=require_create_confirmation,
+ accepted_audiences=accepted_audiences,
+ username_key=username_key,
+ )
+ file = tempfile.NamedTemporaryFile(mode="w", delete=False)
+ file.write(contents)
+ return contents, file.name
+
+
+def test_parse_backend_config(mock_app):
+ config_values = {
+ "url": "https://example.com",
+ "client_id": "example_app",
+ "client_secret": "abcd1234",
+ "enable_idp_logout": "true",
+ "require_create_confirmation": "false",
+ "accepted_audiences": "https://audience.example.com",
+ "username_key": "custom_username",
+ }
+ oidc_contents, oidc_path = create_oidc_config()
+ backend_contents, backend_path = create_backend_config(provider_name="oidc", **config_values)
+ manager = AuthnzManager(app=mock_app, oidc_config_file=oidc_path, oidc_backends_config_file=backend_path)
+ assert isinstance(manager.oidc_backends_config["oidc"], dict)
+ parsed = manager.oidc_backends_config["oidc"]
+ assert parsed["url"] == config_values["url"]
+ assert parsed["client_id"] == config_values["client_id"]
+ assert parsed["client_secret"] == config_values["client_secret"]
+ assert parsed["accepted_audiences"] == config_values["accepted_audiences"]
+ assert parsed["username_key"] == config_values["username_key"]
+ # Boolean values should be parsed into bools
+ assert parsed["enable_idp_logout"] == asbool(config_values["enable_idp_logout"])
+ assert parsed["require_create_confirmation"] == asbool(config_values["require_create_confirmation"])
+
+
+def test_psa_authnz_config(mock_app):
+ """
+ Test config values are set correctly in PSAAuthnz
+ """
+ config_values = {
+ "url": "https://example.com",
+ "client_id": "example_app",
+ "client_secret": "abcd1234",
+ "enable_idp_logout": "true",
+ "require_create_confirmation": "false",
+ "accepted_audiences": "https://audience.example.com",
+ "username_key": "custom_username",
+ }
+ oidc_contents, oidc_path = create_oidc_config()
+ backend_contents, backend_path = create_backend_config(provider_name="oidc", **config_values)
+ manager = AuthnzManager(app=mock_app, oidc_config_file=oidc_path, oidc_backends_config_file=backend_path)
+ from galaxy.authnz.psa_authnz import PSAAuthnz
+
+ psa_authnz = PSAAuthnz(
+ provider="oidc", oidc_config=manager.oidc_config, oidc_backend_config=manager.oidc_backends_config["oidc"]
+ )
+ assert psa_authnz.config[setting_name("USERNAME_KEY")] == config_values["username_key"]