mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
Sociallogin creates a Nextcloud user as "<provider name>-<keycloak sub>",
and the provider name was the Keycloak domain. On an onion variant that
domain alone is 67 chars, so every OIDC callback died with
InvalidArgumentException "Username is too long" and HTTP 500 at
/apps/sociallogin/custom_oidc/auth.<onion> (run 34615516009,
Nextcloud#1, 204 occurrences). The whiteboard and admin OIDC specs failed
on the same login.
This is the second derived identifier an onion domain overflows; the
kernel hostname is the first, clamped to 63 bytes by container_hostname
since the pretix and bookwyrm fixes. The domain-or-entity rule now lives in
one lookup, bounded_name(application_id, max_length): the primary domain
when it fits, otherwise the entity name. container_hostname delegates to it
at 63 and keeps its eight callers and its behaviour.
The provider name is bounded_name('web-app-keycloak', 64 - 1 - 36), the uid
limit minus the separator and the UUID sub. A short clearnet domain stays
the name, so existing users keep their uids; an onion deploy gets
"keycloak", which, unlike a hash of the domain, survives a rotated onion
address.
Tests cover the lookup's fit, boundary and fallback cases and its argument
validation, container_hostname's delegation at 63, and the provider name's
room for the sub.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
71 lines
2.4 KiB
Python
71 lines
2.4 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from ansible.errors import AnsibleError
|
|
from ansible.plugins.loader import lookup_loader
|
|
from ansible.plugins.lookup import LookupBase
|
|
|
|
from utils.domains.primary_domain import get_domain
|
|
from utils.roles.entity.name import get_entity_name
|
|
|
|
|
|
class LookupModule(LookupBase):
|
|
"""
|
|
Usage:
|
|
{{ lookup('bounded_name', application_id, max_length) }}
|
|
|
|
An application's canonical primary domain when it has at most max_length
|
|
characters, otherwise its short entity name (e.g. ``keycloak``).
|
|
|
|
Args:
|
|
application_id: the application whose domain names the value.
|
|
max_length: the longest value the consumer accepts; a positive integer.
|
|
"""
|
|
|
|
def run(self, terms, variables: dict[str, Any] | None = None, **kwargs):
|
|
if not terms or len(terms) != 2:
|
|
raise AnsibleError(
|
|
"lookup('bounded_name', application_id, max_length) expects exactly 2 terms"
|
|
)
|
|
|
|
application_id, max_length = terms
|
|
if not isinstance(application_id, str) or not application_id.strip():
|
|
raise AnsibleError(
|
|
"lookup('bounded_name'): application_id must be a "
|
|
f"non-empty string, got {application_id!r}"
|
|
)
|
|
application_id = application_id.strip()
|
|
|
|
try:
|
|
limit = int(max_length)
|
|
except (TypeError, ValueError) as e:
|
|
raise AnsibleError(
|
|
f"lookup('bounded_name'): max_length must be an integer, got {max_length!r}"
|
|
) from e
|
|
if limit < 1:
|
|
raise AnsibleError(
|
|
f"lookup('bounded_name'): max_length must be positive, got {limit}"
|
|
)
|
|
|
|
variables = variables or getattr(self._templar, "available_variables", {}) or {}
|
|
|
|
domains = lookup_loader.get(
|
|
"domains",
|
|
loader=getattr(self, "_loader", None),
|
|
templar=getattr(self, "_templar", None),
|
|
).run([], variables=variables, roles_dir=kwargs.get("roles_dir"))[0]
|
|
|
|
try:
|
|
domain = get_domain(domains, application_id)
|
|
except Exception as e:
|
|
raise AnsibleError(
|
|
f"lookup('bounded_name'): failed to resolve domain for "
|
|
f"'{application_id}': {e}"
|
|
) from e
|
|
|
|
name = str(domain or "")
|
|
if 0 < len(name) <= limit:
|
|
return [name]
|
|
return [get_entity_name(application_id)]
|