From 861824661d669e467d5a23b986c6695ae22ded00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Dubois?= Date: Sat, 28 Jun 2025 21:16:59 +0200 Subject: [PATCH] feat: allow to specify userinfo endpoint method --- news/78.feature | 1 + src/pas/plugins/oidc/browser/view.py | 3 ++- src/pas/plugins/oidc/controlpanel/classic.py | 8 ++++++++ src/pas/plugins/oidc/interfaces.py | 7 +++++++ .../oidc/locales/de/LC_MESSAGES/pas.plugins.oidc.po | 10 +++++++++- .../oidc/locales/en/LC_MESSAGES/pas.plugins.oidc.po | 10 +++++++++- .../oidc/locales/es/LC_MESSAGES/pas.plugins.oidc.po | 12 ++++++++++-- src/pas/plugins/oidc/locales/pas.plugins.oidc.pot | 12 ++++++++++-- src/pas/plugins/oidc/plugins.py | 12 ++++++++++++ src/pas/plugins/oidc/services/oidc/oidc.py | 3 ++- src/pas/plugins/oidc/utils.py | 4 ++-- tests/services/conftest.py | 5 +++-- 12 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 news/78.feature diff --git a/news/78.feature b/news/78.feature new file mode 100644 index 0000000..a7f6810 --- /dev/null +++ b/news/78.feature @@ -0,0 +1 @@ +Allow to specify userinfo endpoint method. @remdub \ No newline at end of file diff --git a/src/pas/plugins/oidc/browser/view.py b/src/pas/plugins/oidc/browser/view.py index 9fa922b..5080a5b 100644 --- a/src/pas/plugins/oidc/browser/view.py +++ b/src/pas/plugins/oidc/browser/view.py @@ -118,6 +118,7 @@ def __call__(self): args, state = utils.parse_authorization_response( self.context, qs, client, session ) + method = self.context.getProperty("userinfo_endpoint_method", "POST") if self.context.getProperty("use_modified_openid_schema"): IdToken.c_param.update({ "email_verified": utils.SINGLE_OPTIONAL_BOOLEAN_AS_STRING, @@ -126,7 +127,7 @@ def __call__(self): # The response you get back is an instance of an AccessTokenResponse # or again possibly an ErrorResponse instance. - user_info = utils.get_user_info(client, state, args) + user_info = utils.get_user_info(client, state, args, method) if user_info: self.context.rememberIdentity(user_info) self.request.response.setHeader( diff --git a/src/pas/plugins/oidc/controlpanel/classic.py b/src/pas/plugins/oidc/controlpanel/classic.py index 4580388..6854aec 100644 --- a/src/pas/plugins/oidc/controlpanel/classic.py +++ b/src/pas/plugins/oidc/controlpanel/classic.py @@ -153,6 +153,14 @@ def identity_domain_name(self): def identity_domain_name(self, value): self.settings.identity_domain_name = value + @property + def userinfo_endpoint_method(self): + return self.settings.userinfo_endpoint_method + + @userinfo_endpoint_method.setter + def userinfo_endpoint_method(self, value): + self.settings.userinfo_endpoint_method = value + class OIDCSettingsForm(controlpanel.RegistryEditForm): schema = IOIDCSettings diff --git a/src/pas/plugins/oidc/interfaces.py b/src/pas/plugins/oidc/interfaces.py index 66d8e18..1160b01 100644 --- a/src/pas/plugins/oidc/interfaces.py +++ b/src/pas/plugins/oidc/interfaces.py @@ -136,6 +136,13 @@ class IOIDCSettings(Interface): default="", ) + userinfo_endpoint_method = schema.Choice( + title=_("Userinfo Endpoint Method"), + description=_("HTTP Method to use for the userinfo endpoint"), + values=["GET", "POST"], + default="POST", + ) + class IOIDCControlpanel(IControlpanel): """OIDC Control panel""" diff --git a/src/pas/plugins/oidc/locales/de/LC_MESSAGES/pas.plugins.oidc.po b/src/pas/plugins/oidc/locales/de/LC_MESSAGES/pas.plugins.oidc.po index 29d065f..abc813e 100644 --- a/src/pas/plugins/oidc/locales/de/LC_MESSAGES/pas.plugins.oidc.po +++ b/src/pas/plugins/oidc/locales/de/LC_MESSAGES/pas.plugins.oidc.po @@ -61,6 +61,10 @@ msgstr "" msgid "Group" msgstr "" +#: pas/plugins/oidc/interfaces.py:141 +msgid "HTTP Method to use for the userinfo endpoint" +msgstr "" + #: pas/plugins/oidc/www/oidcPluginForm.zpt:26 msgid "Id" msgstr "" @@ -77,7 +81,7 @@ msgstr "Installiert das Add-on pas.plugins.oidc." msgid "List of groups that are allowed to log in." msgstr "" -#: pas/plugins/oidc/controlpanel/classic.py:160 +#: pas/plugins/oidc/controlpanel/classic.py:168 msgid "OIDC Plugin Settings" msgstr "" @@ -178,6 +182,10 @@ msgstr "" msgid "User info property used as userid, default 'sub'." msgstr "" +#: pas/plugins/oidc/interfaces.py:140 +msgid "Userinfo Endpoint Method" +msgstr "" + #: pas/plugins/oidc/configure.zcml:33 msgid "pas.plugins.oidc" msgstr "pas.plugins.oidc" diff --git a/src/pas/plugins/oidc/locales/en/LC_MESSAGES/pas.plugins.oidc.po b/src/pas/plugins/oidc/locales/en/LC_MESSAGES/pas.plugins.oidc.po index 8a7b43b..d60f73e 100644 --- a/src/pas/plugins/oidc/locales/en/LC_MESSAGES/pas.plugins.oidc.po +++ b/src/pas/plugins/oidc/locales/en/LC_MESSAGES/pas.plugins.oidc.po @@ -58,6 +58,10 @@ msgstr "" msgid "Group" msgstr "" +#: pas/plugins/oidc/interfaces.py:141 +msgid "HTTP Method to use for the userinfo endpoint" +msgstr "" + #: pas/plugins/oidc/www/oidcPluginForm.zpt:26 msgid "Id" msgstr "" @@ -74,7 +78,7 @@ msgstr "" msgid "List of groups that are allowed to log in." msgstr "" -#: pas/plugins/oidc/controlpanel/classic.py:160 +#: pas/plugins/oidc/controlpanel/classic.py:168 msgid "OIDC Plugin Settings" msgstr "" @@ -175,6 +179,10 @@ msgstr "" msgid "User info property used as userid, default 'sub'." msgstr "" +#: pas/plugins/oidc/interfaces.py:140 +msgid "Userinfo Endpoint Method" +msgstr "" + #: pas/plugins/oidc/configure.zcml:33 msgid "pas.plugins.oidc" msgstr "" diff --git a/src/pas/plugins/oidc/locales/es/LC_MESSAGES/pas.plugins.oidc.po b/src/pas/plugins/oidc/locales/es/LC_MESSAGES/pas.plugins.oidc.po index 9b4f161..de9ee85 100644 --- a/src/pas/plugins/oidc/locales/es/LC_MESSAGES/pas.plugins.oidc.po +++ b/src/pas/plugins/oidc/locales/es/LC_MESSAGES/pas.plugins.oidc.po @@ -7,7 +7,6 @@ msgstr "" "PO-Revision-Date: 2025-04-12 13:28+0200\n" "Last-Translator: Leonardo J. Caballero G. \n" "Language-Team: ES \n" -"Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -16,6 +15,7 @@ msgstr "" "Language-Name: Español\n" "Preferred-Encodings: utf-8\n" "Domain: pas.plugins.oidc\n" +"Language: es\n" "X-Is-Fallback-For: es-ar es-bo es-cl es-co es-cr es-do es-ec es-es es-sv es-gt es-hn es-mx es-ni es-pa es-py es-pe es-pr es-us es-uy es-ve\n" "X-Generator: Poedit 3.5\n" @@ -63,6 +63,10 @@ msgstr "Crear usuario/actualizar las propiedades de usuario" msgid "Group" msgstr "Grupo" +#: pas/plugins/oidc/interfaces.py:141 +msgid "HTTP Method to use for the userinfo endpoint" +msgstr "" + #: pas/plugins/oidc/www/oidcPluginForm.zpt:26 msgid "Id" msgstr "Id" @@ -79,7 +83,7 @@ msgstr "Instala el complemento pas.plugins.oidc." msgid "List of groups that are allowed to log in." msgstr "Lista de grupos que pueden iniciar sesión." -#: pas/plugins/oidc/controlpanel/classic.py:160 +#: pas/plugins/oidc/controlpanel/classic.py:168 msgid "OIDC Plugin Settings" msgstr "Configuración del complemento OIDC" @@ -180,6 +184,10 @@ msgstr "Propiedad de información de usuario utilizada como groupid, 'groups' po msgid "User info property used as userid, default 'sub'." msgstr "Propiedad de información de usuario utilizada como userid, 'sub' por defecto." +#: pas/plugins/oidc/interfaces.py:140 +msgid "Userinfo Endpoint Method" +msgstr "" + #: pas/plugins/oidc/configure.zcml:33 msgid "pas.plugins.oidc" msgstr "pas.plugins.oidc" diff --git a/src/pas/plugins/oidc/locales/pas.plugins.oidc.pot b/src/pas/plugins/oidc/locales/pas.plugins.oidc.pot index 10f3a4a..39d1d9f 100644 --- a/src/pas/plugins/oidc/locales/pas.plugins.oidc.pot +++ b/src/pas/plugins/oidc/locales/pas.plugins.oidc.pot @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2025-03-26 10:49+0000\n" +"POT-Creation-Date: 2025-06-28 18:33+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -65,6 +65,10 @@ msgstr "" msgid "Group" msgstr "" +#: pas/plugins/oidc/interfaces.py:141 +msgid "HTTP Method to use for the userinfo endpoint" +msgstr "" + #: pas/plugins/oidc/www/oidcPluginForm.zpt:26 msgid "Id" msgstr "" @@ -81,7 +85,7 @@ msgstr "" msgid "List of groups that are allowed to log in." msgstr "" -#: pas/plugins/oidc/controlpanel/classic.py:160 +#: pas/plugins/oidc/controlpanel/classic.py:168 msgid "OIDC Plugin Settings" msgstr "" @@ -182,6 +186,10 @@ msgstr "" msgid "User info property used as userid, default 'sub'." msgstr "" +#: pas/plugins/oidc/interfaces.py:140 +msgid "Userinfo Endpoint Method" +msgstr "" + #: pas/plugins/oidc/configure.zcml:33 msgid "pas.plugins.oidc" msgstr "" diff --git a/src/pas/plugins/oidc/plugins.py b/src/pas/plugins/oidc/plugins.py index f027838..6682738 100644 --- a/src/pas/plugins/oidc/plugins.py +++ b/src/pas/plugins/oidc/plugins.py @@ -126,6 +126,8 @@ class OIDCPlugin(BasePlugin): use_modified_openid_schema: bool = False user_property_as_userid: str = "sub" identity_domain_name: str = "" + userinfo_endpoint_method: str = "POST" + userinfo_endpoint_method_values: tuple[str, ...] = ("GET", "POST") _properties: tuple[dict] = ( {"id": "title", "type": "string", "mode": "w", "label": "Title"}, @@ -220,6 +222,16 @@ class OIDCPlugin(BasePlugin): "(required for Oracle Authentication Manager) only." ), }, + { + "id": "userinfo_endpoint_method", + "type": "selection", + "select_variable": "userinfo_endpoint_method_values", + "mode": "w", + "label": ( + "Userinfo Endpoint Method " + "(HTTP Method to use for the userinfo endpoint)" + ), + }, ) def __init__(self, id, title=None): # noQA: A002 diff --git a/src/pas/plugins/oidc/services/oidc/oidc.py b/src/pas/plugins/oidc/services/oidc/oidc.py index b415922..174416c 100644 --- a/src/pas/plugins/oidc/services/oidc/oidc.py +++ b/src/pas/plugins/oidc/services/oidc/oidc.py @@ -203,6 +203,7 @@ def reply(self) -> dict: qs = data.get("qs", "") qs = qs[1:] if qs.startswith("?") else qs args, state = utils.parse_authorization_response(plugin, qs, client, session) + method = plugin.getProperty("userinfo_endpoint_method", "POST") if plugin.getProperty("use_modified_openid_schema"): IdToken.c_param.update({ "email_verified": utils.SINGLE_OPTIONAL_BOOLEAN_AS_STRING, @@ -211,7 +212,7 @@ def reply(self) -> dict: # The response you get back is an instance of an AccessTokenResponse # or again possibly an ErrorResponse instance. - user_info = utils.get_user_info(client, state, args) + user_info = utils.get_user_info(client, state, args, method) if user_info: alsoProvides(self.request, IDisableCSRFProtection) action = "login" diff --git a/src/pas/plugins/oidc/utils.py b/src/pas/plugins/oidc/utils.py index f2535b1..68779be 100644 --- a/src/pas/plugins/oidc/utils.py +++ b/src/pas/plugins/oidc/utils.py @@ -174,7 +174,7 @@ def parse_authorization_response( return args, aresp["state"] -def get_user_info(client, state, args) -> message.OpenIDSchema | dict: +def get_user_info(client, state, args, method="POST") -> message.OpenIDSchema | dict: resp = client.do_access_token_request( state=state, request_args=args, @@ -188,7 +188,7 @@ def get_user_info(client, state, args) -> message.OpenIDSchema | dict: if client.userinfo_endpoint: # https://openid.net/specs/openid-connect-core-1_0.html#UserInfo try: - user_info = client.do_user_info_request(state=state) + user_info = client.do_user_info_request(method=method, state=state) except RequestError as exc: logger.error( "Authentication failed, probably missing openid scope", diff --git a/tests/services/conftest.py b/tests/services/conftest.py index 5a70c9b..dcb528e 100644 --- a/tests/services/conftest.py +++ b/tests/services/conftest.py @@ -14,8 +14,8 @@ import transaction -@pytest.fixture(scope="session") -def keycloak(keycloak_service): +@pytest.fixture(scope="session", params=["GET", "POST"]) +def keycloak(keycloak_service, request): return { "issuer": f"{keycloak_service}/realms/plone-test", "client_id": "plone", @@ -24,6 +24,7 @@ def keycloak(keycloak_service): "redirect_uris": ("/login_oidc/oidc",), "create_restapi_ticket": True, "identity_domain_name": "blah", # ensure non OAM SP ignores extra params/header + "userinfo_endpoint_method": request.param, }