fix(joomla): route server-side OIDC discovery through the Tor SOCKS proxy

Joomla's Keycloak plugin builds a jumbojett OpenIDConnectClient, which bottoms
out in libcurl — and libcurl refuses a .onion host at name resolution, before it
ever consults /etc/hosts, extra_hosts or DNS (RFC 7686). 20ddda8c1 measured that
verbatim: `curl --noproxy '*'` exits 6, and even
`--resolve auth.<onion>:80:127.0.0.1` exits 6. Only a configured proxy makes curl
skip the check.

So the `onion_oidc_socks.yml.j2` include the role already carries is a no-op for
this client: its extra_hosts entry can never be reached. Server-side discovery
against an onion IdP could not work, whatever the scheme — this class is
unaffected by TLS on the onion, which is why it needs its own fix.

buildOidcClient now calls setHttpProxy when JOOMLA_OIDC_SOCKS_PROXY is set, and
env.j2 sets it only when the issuer is an .onion, so a clearnet deployment is
untouched and never routes discovery through Tor. web-app-nextcloud is the
working reference for the same construction. The swarm side needs nothing: the
onion_sso overlay is declared provider-side in svc-net-tor/meta/networks.yml and
attaches to every app with sso enabled.

The six narration comments that fell out of Keycloak.php were pre-existing and
lost their grandfathering when the file was touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-08-10 21:55:45 +02:00
1 parent e64ed1cd93
commit 238b8b41fa
2 files changed
+8 -6

No files matched your search

@@ -36,6 +36,7 @@ class Keycloak extends CMSPlugin
private const ENV_CLIENT_ID = 'JOOMLA_OIDC_CLIENT_ID';
private const ENV_CLIENT_SECRET = 'JOOMLA_OIDC_CLIENT_SECRET';
private const ENV_REDIRECT_URI = 'JOOMLA_OIDC_REDIRECT_URI';
private const ENV_SOCKS_PROXY = 'JOOMLA_OIDC_SOCKS_PROXY';
private const ENV_FALLBACK = 'JOOMLA_OIDC_FALLBACK_ENABLED';
private const ENV_GROUP_ADMIN = 'JOOMLA_OIDC_GROUP_ADMIN';
private const ENV_GROUP_EDITOR = 'JOOMLA_OIDC_GROUP_EDITOR';
@@ -91,12 +92,6 @@ class Keycloak extends CMSPlugin
}
$input = $this->app->getInput();
$fallbackRequested = (string) $input->getCmd('fallback', '') === 'local';
// The form-login POST that follows the initial GET does NOT
// carry the `fallback=local` query, so we sticky-bit a
// short-lived cookie when the query is first seen and honour
// it for the next ~10 minutes (covers the local form-login
// round-trip, then either the user is logged in or the
// cookie expires and the IdP gate re-engages).
if ($fallbackRequested) {
setcookie(self::FALLBACK_COOKIE, '1', [
'expires' => time() + self::FALLBACK_COOKIE_TTL,
@@ -170,6 +165,10 @@ class Keycloak extends CMSPlugin
);
}
$client = new OpenIDConnectClient($issuer, $clientId, $clientSecret);
$socksProxy = (string) (getenv(self::ENV_SOCKS_PROXY) ?: '');
if ($socksProxy !== '') {
$client->setHttpProxy($socksProxy);
}
$client->setRedirectURL($redirectUri);
$client->addScope(['openid', 'profile', 'email', 'groups']);
$client->setVerifyHost(true);
+3
View File
@@ -20,6 +20,9 @@ JOOMLA_OIDC_ISSUER_URL={{ JOOMLA_OIDC.issuer_url }}
JOOMLA_OIDC_CLIENT_ID={{ JOOMLA_OIDC.client_id }}
JOOMLA_OIDC_CLIENT_SECRET={{ JOOMLA_OIDC.client_secret | dotenv_quote }}
JOOMLA_OIDC_REDIRECT_URI={{ JOOMLA_OIDC.redirect_uri }}
{% if JOOMLA_OIDC.issuer_url is search('\.onion(/|:|$)') %}
JOOMLA_OIDC_SOCKS_PROXY={{ lookup('tor_socks_proxy') }}
{% endif %}
JOOMLA_OIDC_END_SESSION_URL={{ JOOMLA_OIDC.end_session_url }}
JOOMLA_OIDC_FALLBACK_ENABLED={{ JOOMLA_OIDC.fallback_enabled }}
JOOMLA_OIDC_GROUP_ADMIN={{ JOOMLA_OIDC.group_admin }}