This is an automated email from the ASF dual-hosted git repository.
sergehuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/unomi.git
The following commit(s) were added to refs/heads/master by this push:
new 20f004bca UNOMI-1009 UNOMI-1010 UNOMI-1012: Document 4.0 breaking
changes and compatibility mode (#941)
20f004bca is described below
commit 20f004bca2a68bf16be697794e81f524a55d355a
Author: Serge Huber <[email protected]>
AuthorDate: Mon Nov 2 14:19:30 2026 +0100
UNOMI-1009 UNOMI-1010 UNOMI-1012: Document 4.0 breaking changes and
compatibility mode (#941)
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
.../itests/SingleTenantCompatibilityModeIT.java | 6 +-
.../src/main/asciidoc/building-and-deploying.adoc | 2 +-
manual/src/main/asciidoc/builtin-event-types.adoc | 5 +-
manual/src/main/asciidoc/condition-validation.adoc | 2 +-
manual/src/main/asciidoc/configuration.adoc | 49 +++++--
.../asciidoc/migrations/migrate-3.0-to-4.0.adoc | 56 ++++++--
.../src/main/asciidoc/migrations/migrations.adoc | 7 +-
.../asciidoc/migrations/v2-compatibility-mode.adoc | 141 +++++++++------------
.../asciidoc/migrations/v2-v3-compatibility.adoc | 60 ++++-----
manual/src/main/asciidoc/privacy.adoc | 4 +-
manual/src/main/asciidoc/recipes.adoc | 5 +-
manual/src/main/asciidoc/security.adoc | 8 +-
manual/src/main/asciidoc/shell-commands.adoc | 104 ++++-----------
manual/src/main/asciidoc/tutorial.adoc | 15 ++-
manual/src/main/asciidoc/whats-new.adoc | 2 +-
.../rest/authentication/AuthenticationFilter.java | 20 +--
.../authentication/RestAuthenticationConfig.java | 8 +-
.../authentication/V2ThirdPartyConfigService.java | 10 +-
.../rest/service/impl/RestServiceUtilsImpl.java | 8 +-
.../org.apache.unomi.rest.authentication.cfg | 10 +-
.../AuthenticationFilterBlankPasswordTest.java | 8 +-
.../migrate-4.0.0-10-tenantInitialization.groovy | 10 +-
22 files changed, 264 insertions(+), 276 deletions(-)
diff --git
a/itests/src/test/java/org/apache/unomi/itests/SingleTenantCompatibilityModeIT.java
b/itests/src/test/java/org/apache/unomi/itests/SingleTenantCompatibilityModeIT.java
index fec79f77f..3450adc4b 100644
---
a/itests/src/test/java/org/apache/unomi/itests/SingleTenantCompatibilityModeIT.java
+++
b/itests/src/test/java/org/apache/unomi/itests/SingleTenantCompatibilityModeIT.java
@@ -55,7 +55,7 @@ import static org.junit.Assert.*;
/**
* Integration tests for single-tenant compatibility mode authentication.
- * Tests the behavior when switching between V2 and V3 authentication modes
+ * Tests the behavior when switching between single-tenant compatibility mode
and normal multi-tenant mode
* using OSGi configuration admin without restarting bundles.
*/
@RunWith(PaxExam.class)
@@ -97,12 +97,12 @@ public class SingleTenantCompatibilityModeIT extends BaseIT
{
keepTrying("Scope "+ TEST_SCOPE +" not found in the required time", ()
-> scopeService.getScope(TEST_SCOPE),
Objects::nonNull, DEFAULT_TRYING_TIMEOUT,
DEFAULT_TRYING_TRIES);
- // Store original V2 mode setting and default tenant ID
+ // Store original compatibility mode setting and default tenant ID
originalV2Mode =
restAuthenticationConfig.isSingleTenantCompatibilityModeEnabled();
// Configure single-tenant compatibility mode to use the BaseIT test
tenant as default
Map<String, Object> v2Config = new HashMap<>();
- v2Config.put("singletenantcompatibility.enabled", false); // Start in
V3 mode
+ v2Config.put("singletenantcompatibility.enabled", false); // Start in
normal multi-tenant mode
updateConfiguration(null,
"org.apache.unomi.rest.authentication",
diff --git a/manual/src/main/asciidoc/building-and-deploying.adoc
b/manual/src/main/asciidoc/building-and-deploying.adoc
index 470b58dc5..be6d99452 100644
--- a/manual/src/main/asciidoc/building-and-deploying.adoc
+++ b/manual/src/main/asciidoc/building-and-deploying.adoc
@@ -574,6 +574,6 @@ A default test page is provided at the following URL:
----
This test page loads `/cxs/context.js`, which retrieves or creates a user
context.
-On Unomi 4.0, public context endpoints require a tenant public API key
(`X-Unomi-Api-Key`) unless <<_v2_compatibility_mode,V2 compatibility mode>> is
enabled.
+On Unomi 4.0, public context endpoints require a tenant public API key
(`X-Unomi-Api-Key`) unless <<_v2_compatibility_mode,single-tenant compatibility
mode>> is enabled.
Create a tenant and regenerate keys first (see
<<_five_minutes_quickstart,5-minute quickstart>> or
<<_multitenancy,Multi-tenancy>>).
The page also contains an experimental Facebook Login integration that does
not yet save the context back to the server.
diff --git a/manual/src/main/asciidoc/builtin-event-types.adoc
b/manual/src/main/asciidoc/builtin-event-types.adoc
index 00d59b4aa..de6402f90 100644
--- a/manual/src/main/asciidoc/builtin-event-types.adoc
+++ b/manual/src/main/asciidoc/builtin-event-types.adoc
@@ -242,8 +242,9 @@ image::form-event-type.png[]
This event is usually used by user interfaces that make it possible to modify
profile properties, for example a form where a user can edit his profile
properties, or a management UI to modify.
-Note that this event type is a protected event type that is only accepted from
configured third-party servers, which are
-authorized by source IP address (and in V2 compatibility mode by an
`X-Unomi-Peer` key valid for that address). That
+Note that this event type is a protected event type that is only accepted from
authorized source IP addresses (the tenant's
+`authorizedIPs` in normal mode, see <<_secured_events_configuration,Secured
events configuration>>; in <<_v2_compatibility_mode,single-tenant compatibility
mode>>
+instead an `X-Unomi-Peer` key valid for the provider's `ipAddresses`). That
gate is separate from the public-versus-trusted caller distinction that
governs which profile and session a request may
bind to — see <<_client_facing_hardening_4_0,client-facing hardening>>. Once
the event is accepted, the property
names in `add`, `update`, `addToSet` and `delete` are checked against an
allowlist. A public-key caller may only write
diff --git a/manual/src/main/asciidoc/condition-validation.adoc
b/manual/src/main/asciidoc/condition-validation.adoc
index fd4aaebcf..06d310af9 100644
--- a/manual/src/main/asciidoc/condition-validation.adoc
+++ b/manual/src/main/asciidoc/condition-validation.adoc
@@ -184,7 +184,7 @@ Invalid segment condition:
- Required parameter is missing (parameter: propertyName, condition:
profilePropertyCondition, location:
condition[profilePropertyCondition].propertyName)
----
-REST clients receive this text in the error response body when the save fails.
+REST clients receive a generic `badRequest` response when the save fails. The
detailed text above is written to the server log.
==== Rules and invalid conditions
diff --git a/manual/src/main/asciidoc/configuration.adoc
b/manual/src/main/asciidoc/configuration.adoc
index 778f71627..b5de02ecf 100644
--- a/manual/src/main/asciidoc/configuration.adoc
+++ b/manual/src/main/asciidoc/configuration.adoc
@@ -172,11 +172,31 @@ Authentication Rules:
3. Private paths require both tenantId and private API key
4. All other requests are denied
+[#_secured_events_configuration]
=== Secured events configuration
-Apache Unomi secures some events by default. It comes out of the box with a
default configuration that you can adjust
-by using the centralized configuration file override in
`$MY_KARAF_HOME/etc/unomi.custom.system.properties`
+Apache Unomi can restrict some event types so only callers from allowed IP
addresses may send them. How that works
+depends on whether <<_v2_compatibility_mode,single-tenant compatibility mode>>
is on.
+==== Normal multi-tenant mode
+
+In normal 4.0 mode, protected events are controlled with two fields on the
tenant object, set through REST
+`/cxs/tenants` or `crud update tenant` (where `restrictedEventTypes` is named
`restrictedEventPermissions`):
+
+* `restrictedEventTypes` — extra event type ids that need an authorized source
IP (for example `login`)
+* `authorizedIPs` — IP addresses or CIDR ranges allowed to send protected
events
+
+Some event types are always protected, whatever the tenant lists:
`sessionCreated`, `sessionReassigned`,
+`profileUpdated`, `ruleFired` and `updateProperties`. Unomi checks the caller
IP of a protected event against
+`authorizedIPs`. See <<_multitenancy,Multi-tenancy>> for tenant updates.
+
+The global third-party provider files below are **not** used in this mode.
+
+==== Single-tenant compatibility mode (third-party / peer servers)
+
+When single-tenant compatibility mode is enabled, Unomi keeps the Unomi 2 /
Unomi 3 third-party provider model. Peer
+servers send the secret in the `X-Unomi-Peer` HTTP header. You can adjust the
defaults with the centralized configuration
+file override in `$MY_KARAF_HOME/etc/unomi.custom.system.properties`.
You can find the default configuration in the following file:
@@ -189,7 +209,7 @@ The properties start with the prefix :
`org.apache.unomi.thirdparty.*` and here
org.apache.unomi.thirdparty.provider1.allowedEvents=${env:UNOMI_THIRDPARTY_PROVIDER1_ALLOWEDEVENTS:-login,updateProperties}
The events set in allowedEvents will be secured and will only be accepted if
the call comes from the specified IP
-address, and if the secret-key is passed in the X-Unomi-Api-Key HTTP request
header. The "env:" part means that it will
+address, and if the secret is passed in the `X-Unomi-Peer` HTTP request
header. The "env:" part means that it will
attempt to read an environment variable by that name, and if it's not found it
will default to the value after the ":-"
marker.
@@ -223,16 +243,19 @@ the changes when upgrading) :
$MY_KARAF_HOME/etc/org.apache.unomi.thirdparty.cfg
-Usually, login events, which operate on profiles and do merge on protected
properties, must be secured. For each
-trusted third party server, you need to add these 3 lines :
+These provider settings apply only while single-tenant compatibility mode is
enabled. Usually, login events, which operate
+on profiles and do merge on protected properties, must be secured. For each
trusted third party server, you need to add
+these 3 lines :
[source]
----
-thirdparty.provider1.key=secret-key
+thirdparty.provider1.key=replace-with-a-long-unique-secret
thirdparty.provider1.ipAddresses=127.0.0.1,::1
thirdparty.provider1.allowedEvents=login,updateProperties
----
+Full peer abilities and troubleshooting for this mode are in
<<_v2_compatibility_mode,single-tenant compatibility mode>>.
+
=== Installing the MaxMind GeoIPLite2 IP lookup database
@@ -358,16 +381,16 @@ Using Karaf shell (requires admin access to Karaf
console). See <<_shell_command
[source,bash]
----
# Create a tenant (JSON properties)
-unomi:crud create tenant -d '{"itemId":"mytenant","name":"My
Company","description":"My Company tenant"}'
+unomi:crud create tenant '{"itemId":"mytenant","name":"My
Company","description":"My Company tenant"}'
# List tenants
unomi:crud list tenant
# View tenant details
-unomi:crud read tenant -i mytenant
+unomi:crud read tenant mytenant
# Delete a tenant
-unomi:crud delete tenant -i mytenant
+unomi:crud delete tenant mytenant
----
==== API Keys and Authentication
@@ -380,7 +403,7 @@ The API keys are generated when creating a tenant, but the
tenant resource only
[source,bash]
----
# Using Karaf shell (requires admin access) — metadata only
-unomi:crud read tenant -i mytenant
+unomi:crud read tenant mytenant
# Obtain plaintext (store immediately; it is not persisted)
curl -X POST "http://localhost:8181/cxs/tenants/mytenant/apikeys?type=PUBLIC" \
@@ -419,7 +442,7 @@ curl -X POST
"http://localhost:8181/cxs/tenants/mytenant/apikeys?type=PUBLIC&val
}
# Using Karaf shell (requires admin access)
-unomi:crud create apikey -d
'{"tenantId":"mytenant","keyType":"PUBLIC","validityPeriod":30}'
+unomi:crud create apikey
'{"tenantId":"mytenant","keyType":"PUBLIC","validityPeriod":30}'
----
==== Accessing API Endpoints
@@ -470,10 +493,10 @@ curl -X GET "http://localhost:8181/cxs/tenants" \
Authentication rules (Unomi 4.0):
-1. Public paths (for example `/cxs/context.json`, `/cxs/eventcollector`,
`/cxs/client/*`) require a valid **public** API key via `X-Unomi-Api-Key`
(unless <<_v2_compatibility_mode,V2 compatibility mode>> is enabled). Browser
clients may call them from any origin, but only listed origins and Unomi's own
origin may use the visitor cookie; see <<_public_tracker_origins,Websites on
other origins>>.
+1. Public paths (for example `/cxs/context.json`, `/cxs/eventcollector`,
`/cxs/client/*`) require a valid **public** API key via `X-Unomi-Api-Key`
(unless <<_v2_compatibility_mode,single-tenant compatibility mode>> is
enabled). Browser clients may call them from any origin, but only listed
origins and Unomi's own origin may use the visitor cookie; see
<<_public_tracker_origins,Websites on other origins>>.
2. Private paths accept `tenantId:privateApiKey` Basic auth, or JAAS admin
credentials. With JAAS, `X-Unomi-Tenant-Id` selects the tenant; when it is
omitted, Unomi uses `org.apache.unomi.rest.authentication.defaultTenantId` (env
`UNOMI_REST_AUTHENTICATION_DEFAULTTENANTID`, default `default`; not used yet by
<<_v2_compatibility_mode,single-tenant compatibility mode>>, whose REST
requests always run on `default`). Send `X-Unomi-Tenant-Id: system` only when a
full administrator must work i [...]
3. Tenant administration (`/cxs/tenants`) requires system administrator JAAS
credentials. Without `X-Unomi-Tenant-Id` it runs in the system context, so it
works on a fresh install before any tenant exists.
-4. JAAS admin does **not** replace the public API key on public endpoints.
+4. JAAS admin credentials are also accepted on public endpoints in place of
the public API key; without `X-Unomi-Tenant-Id` they run in the default admin
tenant (rule 2).
==== Public vs Private Endpoints
Public endpoints (requiring only public API key):
diff --git a/manual/src/main/asciidoc/migrations/migrate-3.0-to-4.0.adoc
b/manual/src/main/asciidoc/migrations/migrate-3.0-to-4.0.adoc
index 1cde93606..4cc2fac83 100644
--- a/manual/src/main/asciidoc/migrations/migrate-3.0-to-4.0.adoc
+++ b/manual/src/main/asciidoc/migrations/migrate-3.0-to-4.0.adoc
@@ -65,7 +65,7 @@ The main change in 4.0 is the introduction of tenant-based
authentication. The s
|Event Collection (`/context.json`)
|None
-|Public API Key only
+|Tenant public API key (or tenant private key / System Admin)
|Administrative Operations
|System Admin (`karaf:$UNOMI_ROOT_PASSWORD`)
@@ -194,14 +194,14 @@ public void updateKeys(String publicKey, String
privateKey) {
}
----
-==== No API Contract Changes
+==== API paths stay the same; behaviour does not
-All API endpoints remain the same between 3.0 and 4.0, and request/response
payloads are unchanged in shape. The differences are in the authentication
mechanism, tenant resolution, and which of the identity fields a public caller
sends to `/context.json` and `/eventcollector` the server acts on — see
<<_client_facing_hardening_4_0,Client-facing hardening (4.0)>> below.
+API paths stay the same between 3.0 and 4.0, and most request/response
payloads keep the same shape. Authentication, tenant resolution, and which
identity fields a public caller may set on `/context.json` and
`/eventcollector` do change — see <<_client_facing_hardening_4_0,Client-facing
hardening (4.0)>> below. Other breaking changes are listed in
<<_breaking_changes_3_0_to_4_0,Breaking changes>>.
[#_client_facing_hardening_4_0]
==== Client-facing hardening (4.0)
-`/cxs/context.json` and `/cxs/eventcollector` are reachable without
authentication, or with a tenant *public* API key. In 4.0 the identity fields
those endpoints accept are treated as claims to be checked rather than as
instructions.
+`/cxs/context.json` and `/cxs/eventcollector` need a tenant *public* API key
(or a tenant private key or administrator credentials); without one Unomi
answers `401`, unless <<_v2_compatibility_mode,single-tenant compatibility
mode>> is enabled. In 4.0 the identity fields those endpoints accept are
treated as claims to be checked rather than as instructions.
A caller is **trusted** when it holds a tenant private key or authenticates as
a system administrator (`ROLE_UNOMI_ADMIN` or `ROLE_UNOMI_TENANT_ADMIN`). A
tenant *public* API key is **not** trusted: it identifies the tenant, not the
visitor.
@@ -534,6 +534,34 @@ console (port 8102) is not covered by that check.
- **Compliance**: Easier to meet data privacy regulations with clear tenant
boundaries
- **Cost Efficiency**: Shared infrastructure with isolated data reduces
operational costs
+[#_breaking_changes_3_0_to_4_0]
+=== Breaking changes
+
+The sections above cover authentication and client-facing hardening. The lists
below gather other changes that can break an existing 3.0 installation,
integration, or plugin. Related tickets:
https://issues.apache.org/jira/browse/UNOMI-1008[UNOMI-1008] (admin default
tenant), https://issues.apache.org/jira/browse/UNOMI-1005[UNOMI-1005] (public
cookie origins), https://issues.apache.org/jira/browse/UNOMI-1007[UNOMI-1007]
(health check), https://issues.apache.org/jira/browse/UNOMI-1001[UNO [...]
+
+==== Operators
+
+* **Shell commands** such as `profile-list`, `rule-list`, and `segment-view`
were removed. Use `unomi:crud` instead (for example `crud list profile`, `crud
read rule RULE_ID`). See <<_shell_commands,Shell commands>>.
+* **Document ids** in the search index now start with the tenant id. Scripts
or dashboards that read ids directly must be updated.
+* **GraphQL** is enabled through the GraphQL distributions
(`unomi-distribution-elasticsearch-graphql` or
`unomi-distribution-opensearch-graphql`) or the `cdp-graphql-feature` feature,
not the older 3.0 wiring alone. See <<_graphql_api,GraphQL API>>.
+* **Health check password**: `UNOMI_HEALTHCHECK_PASSWORD` no longer defaults
to `health` and must be set before start (like `UNOMI_ROOT_PASSWORD`). The
other `UNOMI_HEALTHCHECK_*` variables keep their 3.0 names and defaults. See
<<_configuration,Configuration>>.
+* **Third-party / peer servers**: `org.apache.unomi.thirdparty.*` and the
`X-Unomi-Peer` header apply only in <<_v2_compatibility_mode,single-tenant
compatibility mode>>. In normal multi-tenant mode, protected events are checked
against each tenant's `authorizedIPs` (plus any extra types in
`restrictedEventTypes`); see <<_secured_events_configuration,Secured events
configuration>>.
+* **Migration key file**: `migrate-4.0.0-10-tenantInitialization` writes API
keys under `{karaf.data}/migration/secrets/`. The public key goes in the
`X-Unomi-Api-Key` header; the private key is used with HTTP Basic
authentication as `tenantId:privateKey`. Store the file securely and delete it
after the keys are saved elsewhere.
+
+==== Integrators / REST clients
+
+* Creating a profile without an `itemId` is refused.
+* Condition validation failures on REST return a generic `badRequest`
response. Detailed messages stay in the server log. See
<<_condition_validation,Condition validation>>.
+* Public endpoints need a tenant public API key unless
<<_v2_compatibility_mode,single-tenant compatibility mode>> is on (for clients
written for Unomi 2 or Unomi 3). See
<<_updating_applications_consuming_unomi,Updating applications consuming
Unomi>> and the client-facing hardening notes above.
+* Websites on another origin must be listed for the visitor cookie; see
<<_public_tracker_origins,Websites on other origins>>.
+
+==== Plugin developers
+
+* The **task scheduler** is a new service surface (`SchedulerService` and
related APIs). Plugins that scheduled work their own way should move to it when
they need cluster-aware tasks. See <<_scheduler,Task scheduler>>.
+* The `pastEventCondition` and `sourceEventPropertyCondition` evaluators moved
from the base plugin to the advanced-conditions plugin (package
`org.apache.unomi.plugins.advancedconditions.conditions`). Condition type ids
are unchanged, so JSON definitions keep working; only Java code that references
the evaluator classes must be updated.
+* **Query builder ids** were renamed. Legacy `*ESQueryBuilder` ids in
condition definitions are still mapped to the new ids at run time, with a
warning in the log, and `migrate-4.0.0-15-updateLegacyQueryBuilder` rewrites
the stored ones. Update plugin definitions to the new ids to remove the warning.
+* Event, definitions, and profile **Java service** method signatures and
condition parameter default-value behaviour may differ from 3.0. Rebuild
plugins against the 4.0 API and run your tests.
+
=== Migration Checklist
Before starting the migration, please ensure that:
@@ -542,9 +570,9 @@ Before starting the migration, please ensure that:
- You did practice the migration in a staging environment, NEVER migrate a
production environment without prior validation
- You are currently running Apache Unomi 3.0 (or a later 3.0.x version)
- You understand the multi-tenancy impact on your data model
-- You have a plan to update client applications to tenant API keys (or
temporary <<_v2_compatibility_mode,single-tenant compatibility mode>> only if
coming from 2.x)
-- You have reviewed the <<_client_facing_hardening_4_0,client-facing
hardening>> notes (cookie-only public profile binding, HttpOnly default,
required passwords, privileged REST roles)
-- You know how to obtain plaintext API keys after upgrade (regenerate via
`/cxs/tenants/{id}/apikeys`; create responses expose masked keys only)
+- You have a plan to update client applications to tenant API keys (or
temporary <<_v2_compatibility_mode,single-tenant compatibility mode>> for Unomi
2 or Unomi 3 clients)
+- You have reviewed the <<_breaking_changes_3_0_to_4_0,breaking changes>> and
<<_client_facing_hardening_4_0,client-facing hardening>> notes
+- You know how to obtain plaintext API keys after upgrade (regenerate via
`/cxs/tenants/{id}/apikeys`; create responses expose masked keys only; the
migration also writes a key file under `data/migration/secrets/`)
- Your recurrent import and export `file` endpoints resolve under
`{baseDir}/{tenantId}`, or the router's base-directory properties point at the
directories already in use (see above)
=== Migration Process
@@ -569,21 +597,21 @@ Scripts applied for the 4.0 line include (under
`tools/shell-commands/.../META-I
* `migrate-4.0.0-05-fixSystemItemIds`
* `migrate-4.0.0-10-tenantInitialization` (creates the default tenant used for
isolation)
* `migrate-4.0.0-15-updateLegacyQueryBuilder`
-4. **Update client applications** to the 4.0 authentication model (public key
header / tenant private key), or enable <<_v2_compatibility_mode,single-tenant
compatibility mode>> only when migrating **from Unomi 2.x**.
+4. **Update client applications** to the 4.0 authentication model (public key
header / tenant private key), or enable <<_v2_compatibility_mode,single-tenant
compatibility mode>> while Unomi 2 or Unomi 3 clients are still in use.
5. **Start your Apache Unomi 4.0 cluster**.
-6. **Regenerate and store tenant API keys** (plaintext is returned only from
the key-creation endpoint), then **test** applications end to end.
+6. **Store tenant API keys** from the migration key file under
`data/migration/secrets/` (or regenerate via `/cxs/tenants/{id}/apikeys`), then
**test** applications end to end.
=== Transitional authentication options
-Unomi 4.0 does **not** provide a separate "3.0 compatibility mode" system
property. Use one of the following approaches while migrating clients:
+Use one of the following approaches while migrating clients:
==== Option 1: Update clients to 4.0 authentication (recommended)
Adopt tenant public API keys for public endpoints and tenant private keys or
JAAS for administrative work. See <<_multitenancy,Multi-tenancy>> and the
authentication examples in this guide. For the Unomi web tracker, set `apiKey`;
see <<_web_tracker_server_versions,Configuring the tracker for your Unomi
server>>.
-==== Option 2: single-tenant compatibility mode (2.x clients only)
+==== Option 2: single-tenant compatibility mode (Unomi 2 and Unomi 3 clients)
-If you are migrating from **Unomi 2.x** (not 3.0), you can enable
<<_v2_compatibility_mode,single-tenant compatibility mode>> so legacy clients
work without API keys during a phased rollout:
+If clients were written for **Unomi 2.x or Unomi 3.x**, you can enable
<<_v2_compatibility_mode,single-tenant compatibility mode>> so they work
without tenant API keys during a phased rollout:
[source,properties]
----
@@ -592,11 +620,11 @@ singletenantcompatibility.enabled = true
----
This mode always uses tenant `default`. There is no property to point it at
another tenant: `defaultTenantId` does not
-apply to it yet, so leave that setting at `default` while the mode is on
(GraphQL still follows it). If tenant `default` does not exist yet, the first
public request creates it. Migrated 2.x data has to live in that tenant to be
visible while the mode is on.
+apply to it yet, so leave that setting at `default` while the mode is on
(GraphQL still follows it). If tenant `default` does not exist yet, the first
public request creates it. Migrated data has to live in that tenant to be
visible while the mode is on.
Environment variable equivalent:
`UNOMI_REST_AUTHENTICATION_SINGLETENANTCOMPATIBILITYMODEENABLED=true`
-WARNING: single-tenant compatibility mode is intended for migration from 2.x
only. It bypasses tenant API key requirements on public endpoints. Disable it
once all clients use 4.0 authentication.
+WARNING: single-tenant compatibility mode is a temporary bridge for Unomi 2
and Unomi 3 clients. It bypasses tenant API key requirements on public
endpoints. Disable it once all clients use 4.0 authentication.
=== Post Migration
diff --git a/manual/src/main/asciidoc/migrations/migrations.adoc
b/manual/src/main/asciidoc/migrations/migrations.adoc
index 660a9f1db..4cc8f2b83 100644
--- a/manual/src/main/asciidoc/migrations/migrations.adoc
+++ b/manual/src/main/asciidoc/migrations/migrations.adoc
@@ -17,7 +17,7 @@ This section contains information and steps to migrate
between major Unomi versi
Use this decision guide to pick the right runbook:
* **Unomi 2.x → 3.0** (platform): <<_migrate_from_2_x_to_3_0,Migrate from 2.x
to 3.0>> (+ <<_migrate_from_elasticsearch_7_to_elasticsearch_9,ES7→ES9>> if
needed)
-* **Unomi 3.0 → 4.0** (tenants / API keys / client hardening):
<<_migrate_from_3_0_to_4_0,Migrate from 3.0 to 4.0>> (`unomi:migrate`);
optional <<_v2_compatibility_mode,V2 compatibility mode>> for 2.x clients; see
also <<_client_facing_hardening_4_0,client-facing hardening>>
+* **Unomi 3.0 → 4.0** (tenants / API keys / client hardening):
<<_migrate_from_3_0_to_4_0,Migrate from 3.0 to 4.0>> (`unomi:migrate`);
optional <<_v2_compatibility_mode,single-tenant compatibility mode>> for Unomi
2 or Unomi 3 clients; see also <<_client_facing_hardening_4_0,client-facing
hardening>>
* **Elasticsearch → OpenSearch** (same Unomi version, backend swap):
<<_migrate_from_elasticsearch_to_opensearch,Migrate from Elasticsearch to
OpenSearch>> (not `unomi:migrate`)
[plantuml]
@@ -33,10 +33,11 @@ if (Changing Unomi version?) then (yes)
:ES7 to ES9 data copy if needed;
if (Need multi-tenancy / API keys?) then (yes)
:3.0 to 4.0 via unomi:migrate;
- :Optional V2 compatibility mode;
+ :Optional single-tenant compatibility mode;
endif
else (from 3.0)
:3.0 to 4.0 via unomi:migrate;
+ :Optional single-tenant compatibility mode;
endif
else (no)
endif
@@ -49,7 +50,7 @@ stop
include::writing-migration-scripts.adoc[]
-=== V2/V3 API Compatibility Guide
+=== Unomi 2 / 3 to 4.0 API compatibility
include::v2-v3-compatibility.adoc[]
diff --git a/manual/src/main/asciidoc/migrations/v2-compatibility-mode.adoc
b/manual/src/main/asciidoc/migrations/v2-compatibility-mode.adoc
index b69621c79..38f4eff9d 100644
--- a/manual/src/main/asciidoc/migrations/v2-compatibility-mode.adoc
+++ b/manual/src/main/asciidoc/migrations/v2-compatibility-mode.adoc
@@ -15,40 +15,55 @@
[#_v2_compatibility_mode]
=== single-tenant compatibility mode
-This document explains how to use the single-tenant compatibility mode in
Apache Unomi V3, which allows V2 client applications to work with Unomi V3
without requiring API keys.
+[[_single_tenant_compatibility_mode]]This document explains how to use
single-tenant compatibility mode in Apache Unomi 4.0. The mode lets client
applications
+written for Unomi 2.x or Unomi 3.x keep working without tenant API keys while
you move them to the 4.0 authentication model.
==== Overview
-The single-tenant compatibility mode is designed to ease the migration from
Unomi V2 to V3 by allowing V2 clients to continue working without immediate
changes to their authentication logic. This mode provides backward
compatibility while still leveraging the multi-tenant architecture of V3.
+The mode is a temporary bridge for Unomi 2 and Unomi 3 clients. It keeps
public endpoints reachable without API keys, while
+Unomi 4 still stores data under tenants. Every REST request in this mode uses
tenant `default`.
===== How It Works
When single-tenant compatibility mode is enabled:
-- **Public endpoints** (like `/context.json`) require no authentication (like
V2)
-- **Protected events** (like `login`, `updateProperties`) require IP +
X-Unomi-Peer (like V2)
-- **Private endpoints** require system administrator authentication (like V2)
+- **Public endpoints** (like `/context.json`) require no authentication (like
Unomi 2 and Unomi 3)
+- **Protected events** (like `login`, `updateProperties`) require IP +
`X-Unomi-Peer` (like Unomi 2 and Unomi 3 third-party servers)
+- **Private endpoints** require system administrator authentication (like
Unomi 2 and Unomi 3)
- **Tenant `default`** is used for every operation. The mode cannot be pointed
at another tenant: it ignores
`X-Unomi-Tenant-Id` and the `defaultTenantId` setting on REST. GraphQL is
not affected by the mode and still uses
`defaultTenantId` for JAAS admin requests without a header, so keep that
setting at `default` while the mode is on.
The tenant is created on a public request if it is missing. Each node then
keeps it in memory for
about 30 seconds, so public requests do not wait on each other or re-read it
from the database
every time. A change made on another node is picked up after about that time.
-- **No authentication** is required for non-protected events (like V2)
-- **The visitor cookie is limited to listed websites** (unlike V2). A website
on another origin, another subdomain included, must be listed, in
`UNOMI_PUBLIC_CORS_ALLOWED_ORIGINS` or in the `default` tenant's
`publicCorsAllowedOrigins` property, for its requests to use and receive the
cookie, whether it calls `context.json` or embeds `context.js` with a
`<script>` tag. A plain `<script>` tag (without a `crossorigin` attribute)
sends no `Origin`, so the listed website is then recognised b [...]
-
-This allows V2 clients to work with Unomi V3 immediately after migration,
giving you time to gradually update client applications to use the new V3
authentication model.
+- **No authentication** is required for non-protected events (like Unomi 2 and
Unomi 3)
+- **The visitor cookie is limited to listed websites** (unlike Unomi 2.x and
3.x, which sent credentialed CORS headers to every origin; their `SameSite=Lax`
cookie still
+ reached only pages on the same site).
+ A website on another origin, another subdomain included, must be listed in
`UNOMI_PUBLIC_CORS_ALLOWED_ORIGINS` or in the
+ `default` tenant's `publicCorsAllowedOrigins` property, for its requests to
use and receive the cookie, whether it calls
+ `context.json` or embeds `context.js` with a `<script>` tag. A plain
`<script>` tag (without a `crossorigin` attribute)
+ sends no `Origin`, so the listed website is then recognised by its
`Referer`, which must be turned on with
+ `org.apache.unomi.public.cors.matchReferer=true` (read
<<_public_tracker_script_tag,the trade-offs>> first). A website
+ served from Unomi's own origin needs no change. See
<<_public_tracker_compatibility,Compatibility with Unomi 2.x and 3.x>>.
+
+This allows Unomi 2 and Unomi 3 clients to work with Unomi 4.0 immediately
after migration, giving you time to update
+applications to tenant API keys.
+
+Unomi 2 and Unomi 3 clients behave the same way in this mode. Third-party /
peer servers keep using `X-Unomi-Peer` and
+`etc/org.apache.unomi.thirdparty.cfg` while the mode is on; that path is not
used in normal multi-tenant mode, where the
+tenant's `authorizedIPs` apply instead. See
<<_secured_events_configuration,Secured events configuration>> and
+https://issues.apache.org/jira/browse/UNOMI-1001[UNOMI-1001].
==== Prerequisites
Before enabling single-tenant compatibility mode, ensure that:
-1. **Data Migration Completed**: Your V2 data has been migrated into tenant
`default` using the migration scripts. The mode does not read data that lives
in any other tenant.
-2. **V3 Installation**: Unomi V3 is properly installed and configured. If
tenant `default` is missing, the first public request creates it.
+1. **Data Migration Completed**: Your data has been migrated into tenant
`default` using the migration scripts. The mode does not read data that lives
in any other tenant.
+2. **Unomi 4.0 Installation**: Unomi 4.0 is properly installed and configured.
If tenant `default` is missing, the first public request creates it.
==== Configuration
-===== Enable V2 Compatibility Mode
+===== Enable single-tenant compatibility mode
You can also set the environment variable
`UNOMI_REST_AUTHENTICATION_SINGLETENANTCOMPATIBILITYMODEENABLED=true` (maps to
`org.apache.unomi.rest.authentication.singleTenantCompatibilityModeEnabled` in
`custom.system.properties`).
@@ -91,7 +106,7 @@ unomi:migrate 2.0.0
The `migrate-4.0.0-10-tenantInitialization` script (run as part of that chain
when migrating through 4.0) creates tenant `default`, which is the only tenant
this mode uses. If that tenant is missing when the mode is on, the first public
request creates it. See <<_migrate_from_3_0_to_4_0,Migrate from 3.0 to 4.0>>
and <<_shell_commands,Shell commands>>.
-===== Step 2: Enable V2 Compatibility Mode
+===== Step 2: Enable single-tenant compatibility mode
Enable single-tenant compatibility mode by updating the configuration file:
@@ -107,12 +122,12 @@ vi etc/org.apache.unomi.rest.authentication.cfg
./bin/start
```
-===== Step 3: Test V2 Clients
+===== Step 3: Test Unomi 2 / Unomi 3 clients
-Your V2 clients should now work without any changes:
+Your existing clients should now work without API key changes:
```java
-// V2-style authentication still works
+// Unomi 2 / 3 style authentication still works
RestAssured.authentication = RestAssured.preemptive()
.basic("karaf", System.getenv("UNOMI_ROOT_PASSWORD"));
@@ -126,17 +141,17 @@ RestAssured.given()
===== Step 4: Gradual Migration
-Over time, gradually update your clients to use V3 authentication:
+Over time, gradually update your clients to use 4.0 authentication:
1. **Update client applications** to use API keys
-2. **Test with V3 authentication** while keeping single-tenant compatibility
mode enabled
+2. **Test with 4.0 authentication** while keeping single-tenant compatibility
mode enabled
3. **Disable single-tenant compatibility mode** once all clients are updated
==== Client Migration Examples
-===== From V2 to V3 (with V2 Compatibility Mode)
+===== From Unomi 2 or 3 to Unomi 4 (with compatibility mode)
-**V2 Client (continues to work)**:
+**Legacy client (continues to work)**:
```java
// This continues to work in single-tenant compatibility mode
RestAssured.authentication = RestAssured.preemptive()
@@ -149,9 +164,9 @@ RestAssured.given()
.post("/context.json");
```
-**V3 Client (new implementation)**:
+**Unomi 4 client (new implementation)**:
```java
-// New V3 client using API keys
+// New 4.0 client using API keys
given()
.header("X-Unomi-Api-Key", publicKey)
.contentType(ContentType.JSON)
@@ -161,22 +176,22 @@ given()
===== Gradual Migration Strategy
-1. **Phase 1**: Enable single-tenant compatibility mode, V2 clients continue
working
-2. **Phase 2**: Develop and test V3 clients alongside V2 clients
-3. **Phase 3**: Migrate clients one by one to V3 authentication
+1. **Phase 1**: Enable single-tenant compatibility mode, Unomi 2 / 3 clients
continue working
+2. **Phase 2**: Develop and test 4.0 clients alongside legacy clients
+3. **Phase 3**: Migrate clients one by one to 4.0 authentication
4. **Phase 4**: Disable single-tenant compatibility mode once all clients are
migrated
==== Security Considerations
-===== V2 Compatibility Mode Security
+===== Compatibility mode security
When single-tenant compatibility mode is enabled:
-- **Public endpoints** are accessible without authentication (same as V2)
-- **Protected events** require IP + X-Unomi-Peer authentication (same as V2)
-- **Private endpoints** require system administrator authentication (same as
V2)
+- **Public endpoints** are accessible without authentication (same as Unomi 2
and Unomi 3)
+- **Protected events** require IP + `X-Unomi-Peer` authentication (same as
Unomi 2 / 3 peer servers)
+- **Private endpoints** require system administrator authentication (same as
Unomi 2 and Unomi 3)
- **All operations** use tenant `default`
-- **Non-protected events** require no authentication (same as V2)
+- **Non-protected events** require no authentication (same as Unomi 2 and
Unomi 3)
===== Protected Events and Peer Abilities
@@ -202,7 +217,7 @@ For protected events, clients must:
All other event types are non-protected and require no peer header.
-===== V2 Third-Party Configuration
+===== Third-party configuration (compatibility mode only)
Providers are configured in `etc/org.apache.unomi.thirdparty.cfg` with the
pattern `thirdparty.{providerName}.{property}`:
@@ -234,11 +249,11 @@ A provider whose key is empty, shorter than 16
characters, or equal to the well-
===== Configuration Management
-The V2 third-party configuration supports dynamic updates:
+The third-party configuration supports dynamic updates:
1. **Edit the configuration file**:
```bash
- # Edit the V2 third-party configuration
+ # Edit the third-party configuration
vi etc/org.apache.unomi.thirdparty.cfg
```
@@ -280,7 +295,7 @@ Operators should still treat IP allowlists as a second
factor next to the peer k
===== Recommendations
1. **Use single-tenant compatibility mode temporarily** during migration
-2. **Plan for gradual migration** to V3 authentication
+2. **Plan for gradual migration** to 4.0 authentication
3. **Monitor access patterns** during the transition
4. **Disable single-tenant compatibility mode** once migration is complete
5. **Configure non-empty IP allowlists** and minimal `trustedProxies` before
production use
@@ -289,7 +304,7 @@ Operators should still treat IP allowlists as a second
factor next to the peer k
===== Common Issues
-**V2 clients still not working**:
+**Legacy clients still not working**:
- Check configuration file: `etc/org.apache.unomi.rest.authentication.cfg`
- Verify `singletenantcompatibility.enabled = true`
- Confirm migrated data is in tenant `default`. The mode cannot be pointed at
another tenant.
@@ -320,9 +335,9 @@ Check authentication filter logs:
log:display | grep AuthenticationFilter
```
-==== Disabling V2 Compatibility Mode
+==== Disabling single-tenant compatibility mode
-Once all clients are migrated to V3 authentication:
+Once all clients are migrated to 4.0 authentication:
1. **Update configuration**:
```properties
@@ -335,54 +350,24 @@ Once all clients are migrated to V3 authentication:
./bin/start
```
-3. **Verify all clients work** with V3 authentication
+3. **Verify all clients work** with 4.0 authentication
4. **Monitor for any issues** and address them before final deployment
-==== Testing V2 Compatibility Mode
-
-The existing test framework supports testing single-tenant compatibility mode
using system properties.
-
-===== Running Tests in V2 Compatibility Mode
-
-To run tests with single-tenant compatibility mode enabled:
-
-```bash
-# Enable single-tenant compatibility mode for tests
-mvn test -Dunomi.v2.compatibility.mode=true
-
-# Or set the property in your test environment
-export UNOMI_V2_COMPATIBILITY_MODE=true
-mvn test
-```
-
-===== Test Framework Integration
-
-The test framework automatically detects single-tenant compatibility mode and
uses the appropriate client:
-
-- **V2 Compatibility Mode Enabled**: Uses `UnomiV2Client` for all tests
-- **V2 Compatibility Mode Disabled**: Uses normal V2/V3 detection logic
-
-This allows you to test both single-tenant compatibility mode and normal V3
mode using the same test suite.
+==== Testing single-tenant compatibility mode
-===== Example Test Execution
-
-```bash
-# Test with single-tenant compatibility mode (server should be configured for
V2 compatibility)
-mvn test -Dunomi.v2.compatibility.mode=true -Dunomi.url=http://localhost:8181
-
-# Test with normal V3 mode
-mvn test -Dunomi.url=http://localhost:8181
-```
+The integration test `SingleTenantCompatibilityModeIT` (in `itests`) covers
this mode. It turns the mode on and off at
+runtime through the `singletenantcompatibility.enabled` setting of
`org.apache.unomi.rest.authentication`, so it runs
+as part of the normal integration test suite; no extra system property is
needed.
==== Conclusion
-The single-tenant compatibility mode provides a smooth migration path from
Unomi V2 to V3, allowing you to:
+The single-tenant compatibility mode provides a smooth migration path from
Unomi 2 or Unomi 3 to Unomi 4, allowing you to:
-- **Maintain existing V2 clients** during migration
-- **Gradually migrate** to V3 authentication
-- **Leverage V3 features** while maintaining backward compatibility
+- **Maintain existing clients** during migration
+- **Gradually migrate** to 4.0 authentication
+- **Leverage Unomi 4 features** while maintaining backward compatibility
- **Minimize downtime** during the migration process
-- **Test both modes** using the existing test framework
+- **Test both modes** with the integration test suite
-Use this mode as a temporary solution during your migration journey, and plan
to disable it once all clients are updated to use V3 authentication.
+Use this mode as a temporary solution during your migration journey, and plan
to disable it once all clients are updated to use 4.0 authentication.
diff --git a/manual/src/main/asciidoc/migrations/v2-v3-compatibility.adoc
b/manual/src/main/asciidoc/migrations/v2-v3-compatibility.adoc
index 6a5aa83ae..d92236528 100644
--- a/manual/src/main/asciidoc/migrations/v2-v3-compatibility.adoc
+++ b/manual/src/main/asciidoc/migrations/v2-v3-compatibility.adoc
@@ -12,9 +12,9 @@
// limitations under the License.
//
-=== Apache Unomi V2/V3 API Differences Guide
+=== Apache Unomi 2 / 3 to 4.0 API differences
-This document explains the key differences between Apache Unomi 2.x and 3.x
versions from an API perspective.
+This document explains the key API differences between Apache Unomi 2.x / 3.x
and Unomi 4.0.
==== Overview
@@ -31,13 +31,13 @@ The key innovation in Unomi 4.0 is the introduction of
**tenant isolation** for
This multi-tenancy support necessitates the authentication changes described
below, as the system must now identify which tenant context to operate in for
every request.
-==== Key Differences Between V2 and V3
+==== Key differences between Unomi 2 / 3 and Unomi 4
===== Authentication Model
[cols="1,1,1", options="header"]
|===
-|Aspect |Unomi V2 |Unomi 4.0
+|Aspect |Unomi 2 / 3 |Unomi 4.0
|Authentication Method
|System Administrator Authentication (`karaf` user)
@@ -56,9 +56,9 @@ This multi-tenancy support necessitates the authentication
changes described bel
|System Administrator Authentication (`karaf` user)
|===
-===== API Key Types (V3 Only)
+===== API Key Types (4.0 only)
-V3 introduces two types of API keys per tenant:
+Unomi 4.0 introduces two types of API keys per tenant:
- **Public Key**: Used for public endpoints (event collection via
`/context.json`)
- **Private Key**: Used with tenantId for tenant-specific administrative
operations
@@ -67,7 +67,7 @@ V3 introduces two types of API keys per tenant:
[cols="1,1,1", options="header"]
|===
-|Endpoint Category |V2 Authentication |V3 Authentication
+|Endpoint Category |Unomi 2 / 3 Authentication |Unomi 4.0 Authentication
|Event Collection (`/context.json`)
|None
@@ -82,9 +82,9 @@ V3 introduces two types of API keys per tenant:
|System Admin (`karaf` user)
|===
-==== Authentication Flow (V3)
+==== Authentication Flow (4.0)
-The AuthenticationFilter in V3 follows this resolution order:
+The AuthenticationFilter in 4.0 follows this resolution order:
1. **Tenant endpoints** (`/cxs/tenants`): Requires system administrator
authentication only
2. **Public endpoints** (e.g., `/context.json`): Requires public API key via
`X-Unomi-Api-Key` header
@@ -94,7 +94,7 @@ The AuthenticationFilter in V3 follows this resolution order:
==== Code Examples
-===== V2 Authentication
+===== Unomi 2 / 3 Authentication
[source,java]
----
@@ -110,7 +110,7 @@ RestAssured.given()
.post("/context.json");
----
-===== V3 Authentication
+===== Unomi 4.0 Authentication
[source,java]
----
@@ -151,12 +151,12 @@ given()
----
public class UnomiConfiguration {
public UnomiClient createClient(String baseUrl) {
- String version = System.getProperty("unomi.version", "3");
+ String version = System.getProperty("unomi.version", "4.0");
- if ("3".equals(version)) {
- return new UnomiV3Client(baseUrl);
+ if ("4.0".equals(version)) {
+ return new UnomiV40Client(baseUrl);
} else {
- return new UnomiV2Client(baseUrl);
+ return new UnomiLegacyClient(baseUrl);
}
}
}
@@ -166,14 +166,14 @@ public class UnomiConfiguration {
[source,java]
----
-// V2 Client
+// Unomi 2 / 3 Client
public void init() {
RestAssured.baseURI = baseUrl;
RestAssured.authentication = RestAssured.preemptive()
.basic("karaf", System.getenv("UNOMI_ROOT_PASSWORD"));
}
-// V3 Client
+// Unomi 4.0 Client
public void init() {
RestAssured.baseURI = baseUrl;
}
@@ -186,7 +186,7 @@ public void updateKeys(String publicKey, String privateKey)
{
==== Migration Guidelines
-===== From V2 to V3
+===== From Unomi 2 or 3 to Unomi 4
1. **Understand Multi-Tenancy Impact**
- All data (profiles, events, segments, rules, schemas) becomes
tenant-specific
@@ -194,8 +194,8 @@ public void updateKeys(String publicKey, String privateKey)
{
- Tenant context must be established for every API operation
2. **Update Authentication Configuration**
- - Remove global system administrator authentication
- Configure tenant-specific public and private API keys
+ - Or, while clients are being updated, enable
<<_v2_compatibility_mode,single-tenant compatibility mode>>
- Implement endpoint-specific authentication logic
3. **Endpoint-Specific Changes**
@@ -204,12 +204,12 @@ public void updateKeys(String publicKey, String
privateKey) {
- Keep system administrator authentication as fallback for administrative
operations
- Continue using system administrator authentication for tenant
administration
-4. **No API Contract Changes**
- - All endpoints remain the same
- - Request/response payloads are unchanged
- - Only authentication mechanism differs
+4. **API paths stay the same**
+ - Endpoint paths remain the same
+ - Most request/response payloads keep the same shape
+ - Authentication and tenant resolution differ; see the 3.0→4.0 migration
guide for other breaking changes
-===== Benefits of Multi-Tenancy in V3
+===== Benefits of Multi-Tenancy in 4.0
- **Data Isolation**: Complete separation ensures tenant data never crosses
boundaries
- **Scalability**: Support for multiple customers/organizations in a single
Unomi instance
@@ -219,12 +219,12 @@ public void updateKeys(String publicKey, String
privateKey) {
==== Conclusion
-The fundamental difference between Unomi V2 and V3 is the introduction of
**comprehensive multi-tenancy support**:
+The fundamental difference between Unomi 2 / 3 and Unomi 4 is the introduction
of **comprehensive multi-tenancy support**:
-- **V2**: Single-tenant architecture with system administrator authentication
for all operations
-- **V3**: Multi-tenant architecture with complete data isolation and
tenant-specific authentication
-- **API Endpoints**: Identical between versions - no breaking changes to
existing integrations
-- **Data Model**: All entities (profiles, events, segments, rules, schemas)
become tenant-specific in V3
+- **Unomi 2 / 3**: Single-tenant architecture with system administrator
authentication for all operations
+- **Unomi 4**: Multi-tenant architecture with complete data isolation and
tenant-specific authentication
+- **API Endpoints**: Paths stay the same; authentication and some behaviours
change (see the migration guide)
+- **Data Model**: All entities (profiles, events, segments, rules, schemas)
become tenant-specific in 4.0
- **Authentication**: New tenant-based authentication model with system
administrator authentication as fallback
-The authentication changes in V3 are driven by the need to establish tenant
context for every operation, ensuring complete data isolation while maintaining
backward compatibility for administrative operations.
\ No newline at end of file
+The authentication changes in 4.0 are driven by the need to establish tenant
context for every operation, ensuring complete data isolation while maintaining
backward compatibility for administrative operations.
\ No newline at end of file
diff --git a/manual/src/main/asciidoc/privacy.adoc
b/manual/src/main/asciidoc/privacy.adoc
index e929ec57c..0a430e578 100644
--- a/manual/src/main/asciidoc/privacy.adoc
+++ b/manual/src/main/asciidoc/privacy.adoc
@@ -64,9 +64,9 @@ curl -X GET http://localhost:8181/cxs/client/myprofile/json \
--cookie "context-profile-id=PROFILE-ID"
----
-where the last path segment is the format (`json`, `csv`, `yaml` or `text`),
`PROFILE-ID` is the profile identifier for which to download the profile, and
`YOUR_PUBLIC_API_KEY` is the tenant public API key (required on Unomi 4.0
public `/cxs/client/*` endpoints unless <<_v2_compatibility_mode,V2
compatibility mode>> is enabled).
+where the last path segment is the format (`json`, `csv`, `yaml` or `text`),
`PROFILE-ID` is the profile identifier for which to download the profile, and
`YOUR_PUBLIC_API_KEY` is the tenant public API key (required on Unomi 4.0
public `/cxs/client/*` endpoints unless <<_v2_compatibility_mode,single-tenant
compatibility mode>> is enabled).
-In V2 compatibility mode a plain link on the website to this endpoint also
works, as a top-level navigation; see <<_public_tracker_origins,Websites on
other origins>>.
+In single-tenant compatibility mode a plain link on the website to this
endpoint also works, as a top-level navigation; see
<<_public_tracker_origins,Websites on other origins>>.
=== Deleting a profile
diff --git a/manual/src/main/asciidoc/recipes.adoc
b/manual/src/main/asciidoc/recipes.adoc
index 513c64616..d974ca630 100644
--- a/manual/src/main/asciidoc/recipes.adoc
+++ b/manual/src/main/asciidoc/recipes.adoc
@@ -134,8 +134,9 @@ event data to the profile. This is simpler than it sounds,
as usually all it req
defining the corresponding JSON schema and you're ready to update profiles
using events.
- Use the protected built-in "updateProperties" event. This event is designed
to be used for administrative purposes
-only. Being a protected event type, it is only accepted from an authorized
source address — a configured third-party
-server, or in V2 compatibility mode an `X-Unomi-Peer` key valid for that
source address. Note that this is an
+only. Being a protected event type, it is only accepted from an authorized
source address — the tenant's
+`authorizedIPs` (it is always protected, whatever the tenant's
`restrictedEventTypes`), or in <<_v2_compatibility_mode,single-tenant
compatibility mode>>
+an `X-Unomi-Peer` key valid for that source address. Note that this is an
address-based gate: it is independent of the public-versus-trusted caller
distinction that governs profile and session
binding (see <<_client_facing_hardening_4_0,client-facing hardening>>), and
once the event is accepted a public-key
caller may only write plain `properties.*` paths on the current profile;
`targetId`, `systemProperties`, `segments`,
diff --git a/manual/src/main/asciidoc/security.adoc
b/manual/src/main/asciidoc/security.adoc
index a459d94e0..ed7293413 100644
--- a/manual/src/main/asciidoc/security.adoc
+++ b/manual/src/main/asciidoc/security.adoc
@@ -44,7 +44,7 @@ or not the tenant exists. For tenant data, the system context
is never the silen
(`/cxs/tenants`) is the exception: it manages the tenant registry, so a full
administrator without the header works in
the system context, even before any tenant exists.
-Temporary exception: <<_v2_compatibility_mode,V2 compatibility mode>> allows
public endpoints without API keys while migrating 2.x clients.
+Temporary exception: <<_v2_compatibility_mode,single-tenant compatibility
mode>> allows public endpoints without API keys while migrating Unomi 2 or
Unomi 3 clients.
In that mode every REST request, JAAS admin requests included, runs on tenant
`default` and the default tenant setting
is not used yet; GraphQL still follows the setting, so keep it at `default`
while the mode is on.
@@ -76,9 +76,9 @@ else Private path + Basic tenantId:privateKey
else Private path + JAAS
Auth -> Tenant : X-Unomi-Tenant-Id or admin default tenant
Auth -> API : Tenant context (system only with explicit header)
-else V2 compatibility mode
+else single-tenant compatibility mode
Auth -> Tenant : Compatibility tenant
- Auth -> API : Legacy 2.x-style access
+ Auth -> API : Legacy Unomi 2 / 3 style access
else
Auth --> Client : 401 Unauthorized
end
@@ -146,7 +146,7 @@ end note
Configuration keys live in `etc/org.apache.unomi.rest.authentication.cfg` and
`custom.system.properties`. Tenant API key settings are in
`etc/org.apache.unomi.tenant.cfg`.
-For tenant API keys, roles (`ROLE_UNOMI_TENANT_USER`,
`ROLE_UNOMI_TENANT_ADMIN`, system administrator), operation permissions, and
curl examples, see <<_multitenancy,Multi-tenancy>> and
<<_configuration,Configuration>> (tenant management). For migration from 2.x,
see <<_v2_compatibility_mode,V2 compatibility mode>> and
<<_migrate_from_3_0_to_4_0,Migrate from 3.0 to 4.0>>.
+For tenant API keys, roles (`ROLE_UNOMI_TENANT_USER`,
`ROLE_UNOMI_TENANT_ADMIN`, system administrator), operation permissions, and
curl examples, see <<_multitenancy,Multi-tenancy>> and
<<_configuration,Configuration>> (tenant management). For migrating Unomi 2 or
Unomi 3 clients, see <<_v2_compatibility_mode,single-tenant compatibility
mode>> and <<_migrate_from_3_0_to_4_0,Migrate from 3.0 to 4.0>>.
===== Multi-tenancy isolation
diff --git a/manual/src/main/asciidoc/shell-commands.adoc
b/manual/src/main/asciidoc/shell-commands.adoc
index b4ea60626..bd25cef41 100644
--- a/manual/src/main/asciidoc/shell-commands.adoc
+++ b/manual/src/main/asciidoc/shell-commands.adoc
@@ -92,23 +92,28 @@ using the specified distribution feature name
(unomi-distribution-elasticsearch
These commands are available once the application is running. If an argument
is between brackets [] it means it is optional.
+Most list/view/remove operations on profiles, events, rules, segments,
sessions, actions and conditions now go through
+`unomi:crud`. Set the tenant first with `tenant-set` (see
<<_tenant_commands,Tenant commands>>). Use `unomi:crud help TYPE`
+for the fields each type accepts.
+
.Table Runtime commands
|===
|Command|Arguments|Description
-|rule-list
-|[--csv] [maxEntries]
-|Lists all the rules registered in the Apache Unomi server. The maxEntries
(defaults to 100) will allow you to specify
-how many entries need to be retrieved. If the value is inferior to the total
value, a message will display the total
-value of rules registered in the server. If you add the "--csv" option the
list will be output as a CSV formatted table.
-Note: Options must come before arguments, so use `rule-list --csv 100` not
`rule-list 100 --csv`.
-|rule-view
-|rule-id
-|Dumps a single rule in JSON. The rule-id argument can be retrieved from the
`rule-list` command output.
-|rule-remove
-|rule-id
-|Removes a single rule from Apache Unomi. The `rule-id` argument can be
retrieved from the `rule-list` command output.
-Warning: no confirmation is asked, be careful with this command.
+|crud list
+|type [[--csv] [-n maxEntries]]
+|Lists items of the given type for the current tenant. Common types:
`profile`, `event`, `rule`, `segment`, `session`,
+`actiontype`, `conditiontype`. Example: `crud list profile` or `crud list rule
--csv`.
+|crud read
+|type item-id
+|Dumps one item as JSON. Example: `crud read profile PROFILE_ID` or `crud read
rule RULE_ID`.
+|crud delete
+|type item-id
+|Deletes one item. Warning: no confirmation is asked. Example: `crud delete
profile PROFILE_ID`.
+|crud create / crud update
+|type JSON-or-file-URI [for update: item-id then JSON]
+|Create or update an item from a JSON string or a `file://` path. Use `crud
help TYPE` for required fields.
+
|rule-reset-stats
|n/a
|Resets the rule statistics. This is notably useful when trying to understand
rule performance and impact
@@ -129,64 +134,6 @@ created, EXECUTE means the rule's actions are being
executed.
|event-tail
|n/a
|Dumps any incoming events to the Apache Unomi server to the console. Use
CTRL+C to exit tail
-|event-view
-|event-id
-|Dumps a single event in JSON. The `event-id` can be retrieved from the
event-tail command output.
-|event-list
-|[--csv] [max-entries] [event-type]
-|List the last events processed by Apache Unomi. The `max-entries` parameter
can be used to control how many events are
-displayed (default is 100). The `event-type` makes it possible to filter the
list by event type. The `--csv` argument is used to output the list as a CSV
list instead of an ASCII table.
-Note: Options must come before arguments, so use `event-list --csv 100 view`
not `event-list 100 view --csv`.
-Note: Options must come before arguments, so use `event-list --csv 100 view`
not `event-list 100 view --csv`.
-|event-search
-|profile-id [event-type] [max-entries]
-|This command makes it possible to search for the last events by `profile-id`
and by `event-type`. A `max-entries`
-parameter (with a default value of 100) is also accepted to control the number
of results returned by the search.
-
-|action-list
-|[--csv]
-|Lists all the rule actions registered in the Apache Unomi server. This
command is useful when developing plugins to
-check that everything is properly registered. If you add the "--csv" option
the list will be output as a CSV formatted table
-|action-view
-|action-id
-|Dumps a single action in JSON. The action-id argument can be retrieved from
the `action-list` command output.
-
-|condition-list
-|[--csv]
-|List all the conditions registered in the server. If you add the "--csv"
option the list will be output as a CSV formatted table
-|condition-view
-|condition-id
-|Dumps a single condition in JSON. The condition-id can be retrieved from the
`condition-list` command output.
-
-|profile-list
-|[--csv]
-|List the last 10 modified profiles. If you add the "--csv" option the list
will be output as a CSV formatted table
-|profile-view
-|profile-id
-|Dumps a single profile in JSON. The profile-id argument can be retrieved from
the `profile-list` command output.
-|profile-remove
-|profile-id
-|Removes a profile identified by `profile-id` argument. Warning: no
confirmation is asked so be careful with this command!
-
-|segment-list
-|[--csv]
-|Lists all the segments registered in the Apache Unomi server. If you add the
"--csv" option the list will be output as a CSV formatted table
-|segment-view
-|segment-id
-|Dumps a single segment in JSON. The segment-id argument can be retrieved from
the `segment-list` command output.
-|segment-remove
-|segment-id
-|Removes a single segment identified by the `segment-id` argument. Warning: no
confirmation is asked so be careful with
-this command!
-
-|session-list
-|[--csv]
-|Lists the last 10 sessions by last event date. If you add the "--csv" option
the list will be output
- as a CSV formatted table
-|session-view
-|session-id
-|Dumps a single session in JSON. The session-id argument can be retrieved from
the `session-list`, `profile-list` or
- `event-tail` command output.
|deploy-definition
|[bundleId] [type] [fileName]
@@ -207,6 +154,7 @@ command: `undeploy-definition BUNDLE_ID * *` when
`BUNDLE_ID` is the identifier
|===
+[#_tenant_commands]
==== Tenant commands
Multi-tenant shell operations (Unomi 4.0+). See
<<_multitenancy,Multi-tenancy>> for REST equivalents.
@@ -228,19 +176,19 @@ Multi-tenant shell operations (Unomi 4.0+). See
<<_multitenancy,Multi-tenancy>>
|List tenants (excludes the built-in `system` tenant from normal listings).
|crud read tenant
-|-i tenant-id
+|tenant-id
|Show one tenant as JSON.
|crud create tenant
-|-d JSON or --file path
-|Create a tenant. Use `help` on the command for required properties.
+|JSON or `file://` path
+|Create a tenant. Use `crud help tenant` for the accepted properties.
|crud update tenant
-|-i tenant-id -d JSON or --file path
-|Update tenant metadata.
+|tenant-id JSON or `file://` path
+|Update tenant metadata. The shell names the restricted event types property
`restrictedEventPermissions`.
|crud delete tenant
-|-i tenant-id
+|tenant-id
|Delete a tenant.
|crud list apikey
@@ -248,7 +196,7 @@ Multi-tenant shell operations (Unomi 4.0+). See
<<_multitenancy,Multi-tenancy>>
|List API keys across all tenants (keys are masked in the table).
|crud create apikey
-|-d JSON
+|JSON
|Create an API key for a tenant (`tenantId`, `keyType`, optional
`validityPeriod`).
|cache
diff --git a/manual/src/main/asciidoc/tutorial.adoc
b/manual/src/main/asciidoc/tutorial.adoc
index 73e933336..20088e1f2 100644
--- a/manual/src/main/asciidoc/tutorial.adoc
+++ b/manual/src/main/asciidoc/tutorial.adoc
@@ -211,17 +211,18 @@ Another (powerful) way to look at events is to use the
SSH Console. You can conn
Using the same credentials (the `karaf` user and your configured
`UNOMI_ROOT_PASSWORD`) and then you can use command such as :
- `event-tail` to view in realtime the events as they come in (CTRL+C to stop)
-- `event-list` to view the latest events
-- `event-view EVENT_ID` to view the details of a specific event
+- `tenant-set default` then `crud list event` to view recent events for that
tenant
+- `crud read event EVENT_ID` to view the details of a specific event
==== Viewing the current profile
-By default, Unomi uses a cookie called context-profile-id to keep track of the
current profile. You can use this the value of this cookie which contains a
UUID to lookup the details of the profile. For example with the SSH console you
can simply to:
+By default, Unomi uses a cookie called context-profile-id to keep track of the
current profile. You can use this the value of this cookie which contains a
UUID to lookup the details of the profile. For example with the SSH console you
can simply do:
- profile-view PROFILE_UUID
+ tenant-set default
+ crud read profile PROFILE_UUID
Which will print out the details of the profile with the associated ID.
-Another interesting command is `profile-list` to list all the recently
modified profiles
+Another interesting command is `crud list profile` to list recently modified
profiles for the current tenant
You could also retrieve the profile details using the REST API by using a
request such as this one:
@@ -267,9 +268,9 @@ curl --location --request POST
'http://localhost:8181/cxs/rules' \
The above rule will execute when a view event is received (which is
automatically sent by the tracker when a page is loaded) and increments a
property called `pageViewCount` on the user's profile.
-You can then reload then page and check with the `profile-view PROFILE_UUID`
SSH command that the profile was updated with the new property and that it is
incremented on each page reload.
+You can then reload the page and check with `crud read profile PROFILE_UUID`
that the profile was updated with the new property and that it is incremented
on each page reload.
-You can also use the `rule-list` command to display all the rules in the
system and the `rule-tail` to watch in real-time which rules are executed. The
`rule-view RULE_ID` command will let you view the contents of a rule.
+You can also use `crud list rule` to display the rules for the current tenant
and `rule-tail` to watch in real-time which rules are executed. `crud read rule
RULE_ID` shows the contents of a rule.
==== Adding personalization
diff --git a/manual/src/main/asciidoc/whats-new.adoc
b/manual/src/main/asciidoc/whats-new.adoc
index df268e6fa..65fa44f40 100644
--- a/manual/src/main/asciidoc/whats-new.adoc
+++ b/manual/src/main/asciidoc/whats-new.adoc
@@ -22,7 +22,7 @@ Complete tenant isolation for profiles, events, segments,
rules, and schemas. Pu
* Operator guide: <<_multitenancy,Multi-tenancy>>
* Migration: <<_migrate_from_3_0_to_4_0,Migrate from 3.0 to 4.0>> (includes
<<_client_facing_hardening_4_0,client-facing hardening>>)
-* Migrating from Unomi 2.x: <<_v2_compatibility_mode,single-tenant
compatibility mode>> (`singletenantcompatibility.enabled` in
`org.apache.unomi.rest.authentication.cfg`)
+* Migrating Unomi 2 or Unomi 3 clients: <<_v2_compatibility_mode,single-tenant
compatibility mode>> (`singletenantcompatibility.enabled` in
`org.apache.unomi.rest.authentication.cfg`)
* Public and GraphQL API key checks no longer read every tenant on each
request, and refuse revoked and expired keys
(https://issues.apache.org/jira/browse/UNOMI-1016[UNOMI-1016])
* JAAS admin requests without `X-Unomi-Tenant-Id` use a configurable default
tenant (default `default`), so 3.0 admin scripts keep seeing migrated data
(https://issues.apache.org/jira/browse/UNOMI-1008[UNOMI-1008])
* Health check environment variables `UNOMI_HEALTHCHECK_ENABLED`,
`UNOMI_HEALTHCHECK_PROVIDERS` and `UNOMI_HEALTHCHECK_TIMEOUT` work again
(https://issues.apache.org/jira/browse/UNOMI-1007[UNOMI-1007])
diff --git
a/rest/src/main/java/org/apache/unomi/rest/authentication/AuthenticationFilter.java
b/rest/src/main/java/org/apache/unomi/rest/authentication/AuthenticationFilter.java
index 126102488..0598fd47e 100644
---
a/rest/src/main/java/org/apache/unomi/rest/authentication/AuthenticationFilter.java
+++
b/rest/src/main/java/org/apache/unomi/rest/authentication/AuthenticationFilter.java
@@ -294,16 +294,16 @@ public class AuthenticationFilter implements
ContainerRequestFilter {
/**
* Handle authentication in single-tenant compatibility mode.
* In this mode:
- * - Public endpoints (like /context.json) require no authentication (like
V2)
- * - Protected events require IP + X-Unomi-Peer (like V2)
- * - Private endpoints require system administrator authentication (like
V2)
- * - A default tenant is automatically used for all operations
+ * - Public endpoints (like /context.json) require no authentication (like
Unomi 2 / 3)
+ * - Protected events require IP + X-Unomi-Peer (like Unomi 2 / 3)
+ * - Private endpoints require system administrator authentication (like
Unomi 2 / 3)
+ * - Tenant {@code default} is used for all REST operations
*/
private void handleSingleTenantCompatibilityMode(ContainerRequestContext
requestContext, String path) throws IOException {
- // For public paths, allow access without authentication (like V2)
+ // For public paths, allow access without authentication (like Unomi 2
/ 3)
if (isPublicPath(requestContext)) {
String tenantId = COMPATIBILITY_TENANT_ID;
- // V2 knew no tenant, so a V2 client cannot name one and cannot
create one. Create the
+ // Legacy Unomi 2 / 3 clients do not name a tenant and cannot
create one. Create the
// tenant this mode runs on, rather than refuse every request
until an operator creates
// it by hand.
try {
@@ -327,7 +327,7 @@ public class AuthenticationFilter implements
ContainerRequestFilter {
return;
}
- // For private endpoints, require system administrator authentication
(like V2)
+ // For private endpoints, require system administrator authentication
(like Unomi 2 / 3)
String authHeader =
requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authHeader != null && authHeader.startsWith(BASIC_AUTH_PREFIX)) {
if (rejectBlankBasicAuthPassword(requestContext, authHeader)) {
@@ -346,7 +346,7 @@ public class AuthenticationFilter implements
ContainerRequestFilter {
Subject jaasSubject = ((RolePrefixSecurityContextImpl)
securityContext).getSubject();
// Private endpoints in single-tenant compatibility mode
require system administrator
- // authentication (like V2) — a JAAS login alone isn't enough,
since any Karaf
+ // authentication (like Unomi 2 / 3) — a JAAS login alone
isn't enough, since any Karaf
// user (not just admins) can authenticate against the realm.
if
(!securityService.extractRolesFromSubject(jaasSubject).contains(UnomiRoles.ADMINISTRATOR))
{
logger.debug("single-tenant compatibility mode:
authenticated user lacks administrator role, denying access to private
endpoint");
@@ -392,8 +392,8 @@ public class AuthenticationFilter implements
ContainerRequestFilter {
* (notably {@code karaf.bat}), so an empty credential is never accepted
over REST either.
* <p>
* Deliberately called at each point where a Basic credential is actually
consumed rather than
- * once at the top of {@link #filter}: anonymous traffic on public paths —
and every path in V2
- * compatibility mode — ignores {@code Authorization} entirely, so
rejecting up front would turn
+ * once at the top of {@link #filter}: anonymous traffic on public paths —
in normal and in
+ * single-tenant compatibility mode — ignores {@code Authorization}
entirely, so rejecting up front would turn
* a stray or stale Basic header (a cached browser credential, an
injecting proxy) into a 401 on
* a request that is supposed to succeed without authentication.
*
diff --git
a/rest/src/main/java/org/apache/unomi/rest/authentication/RestAuthenticationConfig.java
b/rest/src/main/java/org/apache/unomi/rest/authentication/RestAuthenticationConfig.java
index 0d030f161..52c4e59f3 100644
---
a/rest/src/main/java/org/apache/unomi/rest/authentication/RestAuthenticationConfig.java
+++
b/rest/src/main/java/org/apache/unomi/rest/authentication/RestAuthenticationConfig.java
@@ -64,10 +64,10 @@ public interface RestAuthenticationConfig extends
AdminTenantConfig {
/**
* Check if single-tenant compatibility mode is enabled.
- * When enabled, V2 clients can use Unomi V3 without requiring API keys:
- * - Public endpoints (like /context.json) require no authentication (like
V2)
- * - Private endpoints require system administrator authentication (like
V2)
- * - A default tenant is automatically used for all operations
+ * When enabled, Unomi 2 and Unomi 3 clients can use Unomi 4.0 without
requiring API keys:
+ * - Public endpoints (like /context.json) require no authentication (like
Unomi 2 / 3)
+ * - Private endpoints require system administrator authentication (like
Unomi 2 / 3)
+ * - Tenant {@code default} is used for all REST operations
*
* @return true if single-tenant compatibility mode is enabled, false
otherwise
*/
diff --git
a/rest/src/main/java/org/apache/unomi/rest/authentication/V2ThirdPartyConfigService.java
b/rest/src/main/java/org/apache/unomi/rest/authentication/V2ThirdPartyConfigService.java
index 84d87d4a7..3dffe258a 100644
---
a/rest/src/main/java/org/apache/unomi/rest/authentication/V2ThirdPartyConfigService.java
+++
b/rest/src/main/java/org/apache/unomi/rest/authentication/V2ThirdPartyConfigService.java
@@ -58,7 +58,7 @@ public class V2ThirdPartyConfigService {
private static final String PROP_TRUSTED_PROXIES = "trustedProxies";
/**
- * OSGi configuration for V2 third-party providers.
+ * OSGi configuration for third-party providers (single-tenant
compatibility mode).
*/
@ObjectClassDefinition(
name = "Apache Unomi Third-Party Configuration",
@@ -232,7 +232,7 @@ public class V2ThirdPartyConfigService {
int totalEvents = newProviders.values().stream()
.mapToInt(config -> config.getAllowedEvents().size())
.sum();
- LOGGER.info("V2 Third-Party Configuration updated - {} providers with
{} total protected events"
+ LOGGER.info("Third-party configuration updated - {} providers with {}
total protected events"
+ " (allowExampleKey={}, trustedProxies={})",
newProviders.size(), totalEvents, allowExample,
proxies.size());
}
@@ -311,7 +311,7 @@ public class V2ThirdPartyConfigService {
Optional<ProviderConfig> match = findProviderByKey(providerKey);
if (match.isEmpty()) {
- LOGGER.debug("V2 compatibility mode: Unknown provider key: {}",
SecurityUtils.maskSecret(providerKey));
+ LOGGER.debug("single-tenant compatibility mode: Unknown provider
key: {}", SecurityUtils.maskSecret(providerKey));
return false;
}
@@ -319,14 +319,14 @@ public class V2ThirdPartyConfigService {
String foundProviderId = config.getProviderId();
if (!config.getAllowedEvents().contains(eventType)) {
- LOGGER.debug("V2 compatibility mode: Event type {} not allowed for
provider {} (key: {})",
+ LOGGER.debug("single-tenant compatibility mode: Event type {} not
allowed for provider {} (key: {})",
eventType, foundProviderId,
SecurityUtils.maskSecret(providerKey));
return false;
}
boolean ipAuthorized = isIpAllowed(config, sourceIP);
if (!ipAuthorized) {
- LOGGER.debug("V2 compatibility mode: IP {} not authorized for
provider {} (key: {})",
+ LOGGER.debug("single-tenant compatibility mode: IP {} not
authorized for provider {} (key: {})",
sourceIP, foundProviderId,
SecurityUtils.maskSecret(providerKey));
}
return ipAuthorized;
diff --git
a/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java
b/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java
index 2e87437dd..caa989457 100644
---
a/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java
+++
b/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java
@@ -662,20 +662,20 @@ public class RestServiceUtilsImpl implements
RestServiceUtils {
/**
* Check if an event is allowed in single-tenant compatibility mode.
- * In V2, protected events required IP + X-Unomi-Peer (third-party key)
authentication.
+ * As in Unomi 2 and 3, protected events require IP + X-Unomi-Peer
(third-party key) authentication.
*
* @param event the event to check
* @param request the HTTP request
* @return true if the event is allowed, false otherwise
*/
private boolean isEventAllowedInV2CompatibilityMode(Event event,
HttpServletRequest request) {
- // Check if this is a protected event type using the V2 third-party
configuration
+ // Check if this is a protected event type using the third-party
configuration
if
(!v2ThirdPartyConfigService.isProtectedEventType(event.getEventType())) {
// Non-protected events are always allowed in single-tenant
compatibility mode
return true;
}
- // For protected events, check IP + third-party key (V2-style)
+ // For protected events, check IP + third-party key (as in Unomi 2 and
3)
String sourceIP = v2ThirdPartyConfigService.resolveClientIp(request);
String thirdPartyKey = request.getHeader("X-Unomi-Peer");
@@ -684,7 +684,7 @@ public class RestServiceUtilsImpl implements
RestServiceUtils {
return false;
}
- // Validate the third-party provider using the V2 configuration
+ // Validate the third-party provider using the third-party
configuration
if (!v2ThirdPartyConfigService.validateProviderByKey(thirdPartyKey,
event.getEventType(), sourceIP)) {
LOGGER.debug("single-tenant compatibility mode: Protected event {}
rejected - invalid third-party provider key: {} from IP: {}",
event.getEventType(),
SecurityUtils.maskSecret(thirdPartyKey), sourceIP);
diff --git a/rest/src/main/resources/org.apache.unomi.rest.authentication.cfg
b/rest/src/main/resources/org.apache.unomi.rest.authentication.cfg
index a18dae6ac..bba44989e 100644
--- a/rest/src/main/resources/org.apache.unomi.rest.authentication.cfg
+++ b/rest/src/main/resources/org.apache.unomi.rest.authentication.cfg
@@ -17,11 +17,11 @@
# Unomi REST Authentication Configuration
# This file configures authentication settings for Unomi REST endpoints
-# V2 Compatibility Mode
-# When enabled, allows V2 clients to use Unomi V3 without requiring API keys
-# - Public endpoints (like /context.json) require no authentication (like V2)
-# - Private endpoints require system administrator authentication (like V2)
-# - A default tenant is automatically used for all operations
+# Single-tenant compatibility mode
+# When enabled, clients written for Unomi 2 or Unomi 3 can use Unomi 4.0
without API keys
+# - Public endpoints (like /context.json) require no authentication (like
Unomi 2 / 3)
+# - Private endpoints require system administrator authentication (like Unomi
2 / 3)
+# - Tenant default is used for all REST operations
singletenantcompatibility.enabled =
${org.apache.unomi.rest.authentication.singleTenantCompatibilityModeEnabled:-false}
# Default tenant (UNOMI-1008)
diff --git
a/rest/src/test/java/org/apache/unomi/rest/authentication/AuthenticationFilterBlankPasswordTest.java
b/rest/src/test/java/org/apache/unomi/rest/authentication/AuthenticationFilterBlankPasswordTest.java
index fa707202a..c4a97e9bb 100644
---
a/rest/src/test/java/org/apache/unomi/rest/authentication/AuthenticationFilterBlankPasswordTest.java
+++
b/rest/src/test/java/org/apache/unomi/rest/authentication/AuthenticationFilterBlankPasswordTest.java
@@ -143,9 +143,9 @@ class AuthenticationFilterBlankPasswordTest {
}
/**
- * The ordinary V3 branch: not {@code tenants}, not a public path, V2
compatibility off. This is
+ * The ordinary multi-tenant branch: not {@code tenants}, not a public
path, compatibility mode off. This is
* the route most private REST calls take, and it consumes the Basic
credential at its own call
- * site — with only the {@code tenants} and V2 tests above, deleting the
guard here would leave
+ * site — with only the {@code tenants} and compatibility-mode tests
above, deleting the guard here would leave
* the whole suite green.
*/
@Test
@@ -159,7 +159,7 @@ class AuthenticationFilterBlankPasswordTest {
}
/**
- * Control for the ordinary V3 branch: a non-blank credential must still
be offered to the tenant
+ * Control for the ordinary multi-tenant branch: a non-blank credential
must still be offered to the tenant
* private-key check and then to JAAS.
*/
@Test
@@ -190,7 +190,7 @@ class AuthenticationFilterBlankPasswordTest {
assertUnauthorizedWithoutReachingJaas(requestContext);
}
- /** Control for the V2 branch: a non-blank credential must still reach
JAAS there too. */
+ /** Control for the compatibility-mode branch: a non-blank credential must
still reach JAAS there too. */
@Test
void
filterPassesNonBlankPasswordToJaasOnAPrivatePathInV2CompatibilityMode() throws
IOException {
when(restAuthenticationConfig.isSingleTenantCompatibilityModeEnabled()).thenReturn(true);
diff --git
a/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-4.0.0-10-tenantInitialization.groovy
b/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-4.0.0-10-tenantInitialization.groovy
index 2cb53fdac..c9fcc575c 100644
---
a/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-4.0.0-10-tenantInitialization.groovy
+++
b/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-4.0.0-10-tenantInitialization.groovy
@@ -88,7 +88,7 @@ context.performMigrationStep("4.0.0-create-tenant-index", ()
-> {
String publicKeyId = UUID.randomUUID().toString()
String privateKeyId = UUID.randomUUID().toString()
- // Only the salted hash and a masked, display-safe representation are
ever persisted (see UNOMI-938).
+ // Only the SHA-256 hash and a masked, display-safe representation are
ever persisted (see UNOMI-938).
// The plaintext values below are only available now, at generation
time.
String publicKeyHash = hashApiKey(generatedPublicKey)
String privateKeyHash = hashApiKey(generatedPrivateKey)
@@ -115,8 +115,8 @@ PRIVATE KEY (HTTP Basic authentication as
tenantId:privateKey -- protected / ad
IMPORTANT: These keys cannot be recovered after this file is deleted.
Save them in a password manager or secrets vault now.
- This file is automatically deleted 24 hours after creation
- by the next migration run. Delete it manually once saved.
+ A later migration run deletes this file once it is more than
+ 24 hours old. Delete it manually once the keys are saved.
${sep}
"""
Files.write(keyFile, fileContent.getBytes("UTF-8"))
@@ -126,7 +126,7 @@ ${sep}
context.printMessage(" Public key : ${generatedPublicKey}")
context.printMessage(" Private key : ${generatedPrivateKey}")
context.printMessage(" Keys file : ${keyFile}")
- context.printMessage(" (auto-deleted after 24 h; delete manually once
keys are saved)")
+ context.printMessage(" (delete it once the keys are saved; a later
migration run removes it after 24 h)")
context.printMessage(sep)
// Create the default tenant document
@@ -135,7 +135,7 @@ ${sep}
"itemType": "tenant",
"name": "Default Tenant",
"tenantId": "system",
- "description": "Default tenant created during migration to Unomi
V3",
+ "description": "Default tenant created during migration to Unomi
4.0",
"createdBy": "system-migration-4.0.0",
"lastModifiedBy": "system-migration-4.0.0",
"creationDate": "${isoDate}",