fix: select existing tenants by name in the operator portal
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Successful in 49s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
tegwick 2026-09-12 00:51:57 +02:00
parent 3758a4bb5e
commit d177d47975
8 changed files with 127 additions and 13 deletions

View file

@ -738,11 +738,26 @@ class PortalApplication:
if path == "/platform/tenant" and method == "GET":
self.service.resolve_tenant_context(actor, PLATFORM_TENANT)
query = parse_qs(str(environ.get("QUERY_STRING", "")))
lookup = query.get("tenant", [""])[0]
lookup = query.get("tenant", [""])[0].strip()
view = query.get("view", ["lifecycle"])[0]
if view not in {"lifecycle", "users"}:
raise ValidationError("unknown tenant view")
if not lookup.startswith("tenant:") or lookup == PLATFORM_TENANT:
if not lookup.startswith("tenant:"):
matches = tuple(
tenant for tenant in self._known_membership_tenants()
if tenant.rsplit(":", 1)[-1] == lookup
)
if len(matches) != 1:
message = (
"Several tenants use that name. Choose a tenant below."
if matches else
"No tenant with users matches that name. Choose a tenant below or enter its full identifier."
)
return self._html(start_response, self._platform(
self._csrf_token(environ), error=message,
), correlation_id)
lookup = matches[0]
if lookup == PLATFORM_TENANT:
raise ValidationError("a non-platform tenant identifier is required")
return self._redirect(
start_response,
@ -1690,10 +1705,32 @@ class PortalApplication:
<p>Your password and MFA remain on the identity-provider surface.</p>""",
)
def _platform(self, csrf_token: str) -> str:
def _known_membership_tenants(self) -> tuple[str, ...]:
# A navigation index of user-owned memberships, not an authority inventory.
return tuple(
tenant for tenant in self.service.store.membership_tenants()
if tenant.startswith("tenant:") and tenant != PLATFORM_TENANT
)
def _platform(self, csrf_token: str, *, error: str = "") -> str:
known_tenants = "".join(
f'<li><a href="/admin/{escape(quote(tenant, safe=""))}">'
f'{escape(tenant.rsplit(":", 1)[-1])}</a> '
f'<small>({escape(tenant)})</small></li>'
for tenant in self._known_membership_tenants()
) or '<li>No tenants with user memberships are recorded yet.</li>'
message = f'<p role="alert">{escape(error)}</p>' if error else ""
return self._page_html(
"Platform administration",
f"""<h1>Platform administration</h1>
<section aria-labelledby="manage-tenant"><h2 id="manage-tenant">Manage an existing tenant</h2>
{message}
<p>Choose a tenant to manage its users:</p><ul>{known_tenants}</ul>
<form method="get" action="/platform/tenant">
<label>Tenant name or full identifier <input name="tenant" required placeholder="demo-company" aria-describedby="tenant-lookup-help"></label>
<p id="tenant-lookup-help">Enter a listed name, such as demo-company. You can also use a full identifier, such as tenant:trial:demo-company.</p>
<button type="submit" name="view" value="users">Manage users</button>
<button type="submit" name="view" value="lifecycle">Open tenant lifecycle</button></form></section>
<section aria-labelledby="create-tenant"><h2 id="create-tenant">Create tenant</h2>
<form method="post" action="/platform/tenants">
<input type="hidden" name="csrf_token" value="{escape(csrf_token)}">
@ -1703,12 +1740,7 @@ class PortalApplication:
<label>Name <input name="admin_display_name" autocomplete="name"></label>
<label>Email <input name="admin_email" type="email" autocomplete="email"></label></fieldset>
<button type="submit">Create tenant</button></form></section>
<section aria-labelledby="manage-tenant"><h2 id="manage-tenant">Manage an existing tenant</h2>
<form method="get" action="/platform/tenant">
<label>Tenant identifier <input name="tenant" required pattern="tenant:.+" placeholder="tenant:friendly:example"></label>
<button type="submit" name="view" value="users">Manage users</button>
<button type="submit" name="view" value="lifecycle">Open tenant lifecycle</button></form>
<p>Tenant records, metadata, and retirement are owned by the tenant authority.</p></section>""",
""",
)
def _platform_tenant(self, record: Any, csrf_token: str) -> str: