sergehuber opened a new pull request, #921: URL: https://github.com/apache/unomi/pull/921
Implements [UNOMI-1013](https://issues.apache.org/jira/browse/UNOMI-1013), a follow-up to #920 (UNOMI-1005). ## The problem #920 added a server-wide list of website origins that may use the visitor cookie. Tenants are created and deleted at runtime, often by provisioning systems, and each has its own websites, so an operator-maintained list cannot keep up: every tenant change would need a configuration change on the servers. ## What this PR does **Per-tenant list.** A tenant lists its own origins in its `publicCorsAllowedOrigins` property (a JSON array or a comma-separated string), set through `/cxs/tenants` when the tenant is created or updated. That endpoint already requires a system administrator, so tenants cannot widen their own access. The list follows the same matching rules as the server list, and an invalid entry is refused with `400`, so provisioning fails rather than a website silently losing its visitors. **Scoped to the tenant.** A tenant's origins only apply to requests made with that tenant's public API key, for both the visitor cookie (`RestServiceUtilsImpl.isVisitorCookieAllowed`, the single decision point from #920) and the credentialed CORS response. The server list still applies to every tenant. **Preflights.** A preflight carries no API key, so the tenant is not known yet. It is answered for an origin the server or any tenant lists; that only lets the browser send the real request, which is then checked against its own tenant. This is the design from the ticket. **Dynamic tenants, no per-request cost.** Tenant origins live in an in-memory snapshot in `PublicTrackerSettings`; requests never load tenants. - Loaded at activation. - Reloaded at once when a tenant is created, updated or deleted through this node (`TenantEndpoint`), or removed (`TenantLifecycleListener`). - Refreshed every 30 seconds by one request while the others keep the previous snapshot. - Reloaded when an unknown tenant or origin shows up, at most every 5 seconds so requests cannot force reloads. A tenant created on another node therefore works on its first request. - A failed load keeps the previous snapshot. **Operator switch.** `org.apache.unomi.public.cors.tenantOrigins` (`UNOMI_PUBLIC_CORS_TENANT_ORIGINS`, default `true`) turns tenant lists off; only `false` does. `SameSite`, `HttpOnly` and the cookie domain stay server settings, since all tenants share one cookie on a host; `SameSite=None` is accepted once tenants may list origins. **Fix found by the IT.** In a JAX-RS response filter the current CXF message is the response; the security context set by `AuthenticationFilter` is on the request, reached through the exchange. `PublicTrackerSettings.tenantIdOf` reads it from there. ## Compatibility No change for existing deployments: tenants without the property behave exactly as with #920, and the server list keeps working. The new property is opt-in per tenant. ## Ticket test list | UNOMI-1013 | Test | |---|---| | An origin listed by tenant A works with tenant A's key | `TenantTrackerOriginsIT.aTenantsOwnOriginGetsCredentialsAndKeepsItsVisitor`, `PublicTrackerSettingsTenantTest.aTenantsOriginsApplyOnlyToThatTenant` | | The same origin with tenant B's key is not tied to the visitor's profile | `TenantTrackerOriginsIT.anotherTenantsOriginDoesNotUseTheVisitorCookie`, `PublicTrackerCorsFilterTest.actualRequestIsCheckedAgainstItsOwnTenant` | | With tenant lists off, only the operator list counts | `PublicTrackerSettingsTenantTest.theOperatorCanTurnTenantListsOff` | | Tenant origins follow the same matching rules | `PublicTrackerCorsPolicyTest.tenantOriginsAcceptAStringOrAList`, `invalidTenantOriginsAreReported`, `TenantEndpointTrackerOriginsTest`, `TenantTrackerOriginsIT.invalidOriginsAreRejectedWhenTheTenantIsCreated` | | All UNOMI-1005 tests still pass | `PublicTrackerCorsIT` 9/9, unchanged UNOMI-1005 unit tests | Also: `aNewTenantWorksAtOnceWithoutRestart`, `unknownOriginsCannotForceReloads`, `tenantsAreNotLoadedForEachRequest`, `theSnapshotIsRefreshedPeriodically`, `aFailedLoadKeepsThePreviousOrigins`, `theTenantIsFoundFromTheResponseMessageToo`, and `TenantTrackerOriginsIT.aTenantCreatedElsewhereWorksWithoutRestart`. ## Documentation `configuration.adoc`: new "Websites listed by each tenant" section (provisioning example, scope, preflights, propagation across nodes, operator switch), the tenant-creation example, settings list and compatibility table; `whats-new.adoc`; comments in `custom.system.properties` and `org.apache.unomi.web.cfg`. ## Test plan - [x] Unit (rest, 237 tests; web-servlets `WebConfigTest`) - [x] `TenantTrackerOriginsIT` 4/4, `PublicTrackerCorsIT` 9/9 on Elasticsearch - [x] `SingleTenantCompatibilityModeIT` 8/8, `ContextServletIT` 39/39 on Elasticsearch (before the last fix, which only touches the CORS response path) - [x] Manual (docs) builds - [ ] OpenSearch run (CI) ## Not in this PR - `TenantServiceImpl.getTenantByApiKey` loads every tenant from storage on every public request. That cost predates this PR and matters at the same scale; worth its own ticket. 🤖 Generated with [Claude Code](https://claude.com/claude-code) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
