Files
computer-playbook/roles/web-app-shopware/templates/playwright.env.j2
kevinveenbirkenbachandClaude Opus 5 bb7167458f fix(web-app-shopware): block the conductor, not the sweep endpoints
The negative control aborted every <host>/logout and then asserted the
admin bearerAuth cookie survives. It failed in run 32597195876, job
Shopware#0, in 5 of 6 executions: the cookie was gone anyway.

The premise was right and the target was wrong. The trace of the failing
run shows all nine sweep aborts landing - 584 ms too late:

  51.534  -1   shop/api/oauth/sso/config       logoutSso() cut off by the
                                               injected logout redirect
  51.538  200  auth/.../openid-connect/logout
  52.842  200  auth/.../logout/logout-confirm
  53.377  200  logout.<domain>/?sid=...        conductor, Clear-Site-Data
  53.961  -1   9x <host>/logout                sweep, correctly blocked
  54.256  200  shop/admin                      cookie already gone

What deletes the cookie is the Clear-Site-Data header on web-svc-logout's
conductor page - "the ONLY path-agnostic cookie deletion mechanism" per
universal-logout's app.py - and it clears the whole registrable domain,
which is how an origin on logout.<domain> reaches a cookie on
shop.<domain>. The per-app /logout requests are the second mechanism, not
the first. Shopware's own clearAuthState never runs either: the injected
redirect preempts it.

The route now blocks the conductor, which is the honest simulation of
"web-svc-logout never lands". It loads after Keycloak's logout-confirm,
so blocking it cannot disturb that flow.

Two anchors identify it, because one is not enough on every axis.
`lookup('tls', ...)` family-aligns a cross-app reference per consumer
(utils/tls_common.py align_domain_to_consumer), so where web-svc-logout
is onion-primary and this role and Keycloak sit in different families,
the origin resolved here is not the one Keycloak framed. The front-channel
parameters Keycloak appends catch that case. LOGOUT_URL stays optional for
the same reason - an empty lookup must fall through to the second anchor
rather than fail the test.

The sibling's poll message names the real mechanism and keeps the
rationale for its 30s wait, which 5b6d12f12 added for a reproduced
Tor-only failure.

Verified on clearnet evidence only: the failing job skipped every Tor
task, and no Tor run in either pipeline produced a trace, since traces are
retain-on-failure and those jobs were green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 04:31:56 +02:00

48 lines
3.9 KiB
Bash

ADMIN_PASSWORD={{ lookup('users', 'administrator').password | dotenv_quote }}
ADMIN_USERNAME={{ lookup('users', 'administrator').username | dotenv_quote }}
APP_BASE_URL={{ lookup('tls', application_id, 'url.base') | dotenv_quote }}
BIBER_PASSWORD={{ lookup('users', 'biber').password | dotenv_quote }}
BIBER_USERNAME={{ lookup('users', 'biber').username | dotenv_quote }}
CANONICAL_DOMAIN={{ lookup('domain', application_id) | dotenv_quote }}
# nocheck: playwright-service-gate — the CSP helper gates css through a computed loop variable (personas/utils/csp.js:87-93), never a literal argument, so test_spec_env_gates.py's literal-only regex cannot see the gate
CSS_SERVICE_ENABLED={{ lookup('config', application_id, 'services.css.enabled') | string | lower }}
# nocheck: playwright-service-gate — the only mail wiring in the role is MAILER_DSN in templates/env.j2, rendered behind an email.enabled guard; no persona scenario triggers an outbound send
EMAIL_SERVICE_ENABLED={{ lookup('config', application_id, 'services.email.enabled') | string | lower }}
# nocheck: playwright-service-gate — the role installs no admin-LDAP plugin, so the directory never gets a surface of its own. Where sso is enabled the directory reaches the backend through Keycloak's user federation and the AdminOpenAuth provider; where it is disabled there is no path into Shopware at all, so neither polarity leaves an assertable surface
LDAP_SERVICE_ENABLED={{ lookup('config', application_id, 'services.ldap.enabled') | string | lower }}
LOGOUT_SERVICE_ENABLED={{ lookup('config', application_id, 'services.logout.enabled') | string | lower }}
LOGOUT_URL={{ lookup('tls', 'web-svc-logout', 'url.base') | dotenv_quote }}
# nocheck: playwright-service-gate — the shopware-init console installer aborts without the database, so every scenario already proves it
MARIADB_SERVICE_ENABLED={{ lookup('config', application_id, 'services.mariadb.enabled') | string | lower }}
MATOMO_BASE_URL={{ lookup('tls', 'web-app-matomo', 'url.base') | dotenv_quote }}
MATOMO_SERVICE_ENABLED={{ lookup('config', application_id, 'services.matomo.enabled') | string | lower }}
# nocheck: playwright-service-gate — storefront product-search index; no persona scenario submits a search query
OPENSEARCH_SERVICE_ENABLED={{ lookup('config', application_id, 'services.opensearch.enabled') | string | lower }}
# nocheck: persona-block-rationale — Shopware's only logout sits in .sw-admin-menu__user-actions, which sw-admin-menu.scss keeps at display:none until its toggle is clicked, and that toggle is a bare div with no role, aria-haspopup or dropdown class, so the shared helper's trigger set cannot open it; the settings fallback misses too because the admin is hash-routed. files/playwright/test-admin-native.js drives the same journey through Shopware's own selectors (see README "Persona contract opt-outs").
PERSONA_ADMINISTRATOR_BLOCKED=true
# nocheck: persona-block-rationale — tasks/01_admin.yml provisions only the administrator in Shopware's user table, and meta/services.yml pins sso.flavor: oidc, so no proxy fronts the storefront and the AdminOpenAuth provider covers the backend alone. biber therefore has no account in any variant; the authenticated backend journey is proven by files/playwright/test-admin-native.js instead (see README "Persona contract opt-outs").
PERSONA_BIBER_BLOCKED=true
# nocheck: playwright-service-gate — session, cache and messenger transport behind every request, no surface of its own
REDIS_SERVICE_ENABLED={{ lookup('config', application_id, 'services.redis.enabled') | string | lower }}
# nocheck: playwright-service-gate — reached only through the Flysystem amazon-s3 adapters; Shopware exposes no object-browser surface
SEAWEEDFS_SERVICE_ENABLED={{ lookup('config', application_id, 'services.seaweedfs.enabled') | string | lower }}
SSO_SERVICE_ENABLED={{ lookup('config', application_id, 'services.sso.enabled') | string | lower }}