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"]