UNOMI-972: Harden default credentials, public profile binding, and privileged REST endpoints - #846
Conversation
bb2bffe to
e85fd29
Compare
Follow-up: login sample rewritePushed
Tested locally: create tenant+scope |
There was a problem hiding this comment.
Pull request overview
Hardens Unomi’s credential handling, public profile binding, privileged operations, and login integration.
Changes:
- Requires explicit administrator and health-check credentials across runtime, Docker, tests, and documentation.
- Restricts public profile/session binding and privileged REST/action behavior.
- Replaces the browser-trusted login sample with a server-side authenticated flow and expands security tests.
Reviewed changes
Copilot reviewed 92 out of 94 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
SECURITY.md |
Expands vulnerability-handling guidance. |
THREAT_MODEL.md |
Updates security assumptions and mitigations. |
api/src/main/java/org/apache/unomi/api/utils/LogSanitizer.java |
Adds shared audit-log sanitization. |
api/src/test/java/org/apache/unomi/api/utils/LogSanitizerTest.java |
Tests log sanitization. |
clear-elasticsearch.sh |
Clears added credential variables. |
clear-opensearch.sh |
Clears added credential variables. |
docker/README.md |
Documents required Docker credentials. |
docker/src/main/docker/docker-compose-build-es.yml |
Requires Elasticsearch runtime credentials. |
docker/src/main/docker/docker-compose-build-os.yml |
Requires OpenSearch runtime credentials. |
docker/src/main/docker/docker-compose-cluster.yml |
Requires cluster credentials. |
docker/src/main/docker/docker-compose-es.yml |
Removes default Elasticsearch credentials. |
docker/src/main/docker/docker-compose-os.yml |
Removes default OpenSearch credentials. |
docker/src/main/docker/entrypoint.sh |
Fails startup without required passwords. |
extensions/groovy-actions/karaf-kar/src/main/feature/feature.xml |
Updates Groovy feature dependencies. |
extensions/groovy-actions/rest/pom.xml |
Adds REST security test dependencies. |
extensions/groovy-actions/rest/src/main/java/org/apache/unomi/groovy/actions/rest/GroovyActionsEndPoint.java |
Restricts Groovy endpoints to administrators. |
extensions/groovy-actions/rest/src/test/java/org/apache/unomi/groovy/actions/rest/GroovyActionsEndPointRoleTest.java |
Tests Groovy endpoint roles. |
extensions/groovy-actions/services/src/main/java/org/apache/unomi/groovy/actions/services/impl/GroovyActionsServiceImpl.java |
Hardens script compilation and adds auditing. |
extensions/groovy-actions/services/src/test/java/org/apache/unomi/groovy/actions/services/impl/GroovyActionsServiceImplTest.java |
Tests Groovy upload hardening. |
extensions/groovy-actions/services/src/test/resources/META-INF/cxs/actions/fieldInitializerAction.groovy |
Adds initializer regression fixture. |
extensions/groovy-actions/services/src/test/resources/META-INF/cxs/actions/rceProofAction.groovy |
Adds execution-safety fixture. |
extensions/router/router-karaf-feature/src/main/feature/feature.xml |
Updates Router feature dependencies. |
extensions/router/router-rest/pom.xml |
Adds Router security test dependencies. |
extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ExportConfigurationServiceEndPoint.java |
Restricts configuration export. |
extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ImportConfigurationServiceEndPoint.java |
Restricts configuration import. |
extensions/router/router-rest/src/test/java/org/apache/unomi/router/rest/RouterConfigurationEndPointRoleTest.java |
Tests Router endpoint roles. |
itests/src/test/java/org/apache/unomi/itests/AllITs.java |
Registers new integration tests. |
itests/src/test/java/org/apache/unomi/itests/BaseIT.java |
Supplies hardened integration-test configuration. |
itests/src/test/java/org/apache/unomi/itests/BasicIT.java |
Updates baseline credential behavior. |
itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java |
Tests public and trusted profile binding. |
itests/src/test/java/org/apache/unomi/itests/CorePersistenceITs.java |
Registers persistence-related tests. |
itests/src/test/java/org/apache/unomi/itests/HealthCheckIT.java |
Tests health-check authentication. |
itests/src/test/java/org/apache/unomi/itests/ProfileMergeIT.java |
Tests trusted profile merging. |
itests/src/test/java/org/apache/unomi/itests/PropertiesUpdateActionIT.java |
Tests protected property updates. |
itests/src/test/java/org/apache/unomi/itests/RestEndpointRoleSecurityIT.java |
Tests privileged REST roles. |
itests/src/test/java/org/apache/unomi/itests/TenantIT.java |
Updates tenant credential handling. |
itests/src/test/java/org/apache/unomi/itests/V2CompatibilityModeIT.java |
Aligns compatibility tests with hardening. |
itests/src/test/java/org/apache/unomi/itests/graphql/BaseGraphQLIT.java |
Updates GraphQL test authentication. |
itests/src/test/resources/etc/users.properties |
Removes shipped test defaults. |
manual/src/main/asciidoc/5-min-quickstart.adoc |
Updates quickstart credentials. |
manual/src/main/asciidoc/builtin-event-types.adoc |
Documents trusted login events. |
manual/src/main/asciidoc/configuration.adoc |
Documents credential and cookie settings. |
manual/src/main/asciidoc/connectors/salesforce-connector.adoc |
Updates connector credential examples. |
manual/src/main/asciidoc/getting-started.adoc |
Requires explicit startup credentials. |
manual/src/main/asciidoc/graphql-examples.adoc |
Updates GraphQL authentication examples. |
manual/src/main/asciidoc/how-profile-tracking-works.adoc |
Documents cookie-owned profile binding. |
manual/src/main/asciidoc/javascript-tracker-guide.adoc |
Updates secure session-ID guidance. |
manual/src/main/asciidoc/jsonSchema/json-schema-api.adoc |
Updates authenticated API examples. |
manual/src/main/asciidoc/migrations/migrate-3.0-to-3.1.adoc |
Adds client hardening migration guidance. |
manual/src/main/asciidoc/migrations/migrations.adoc |
Links migration changes. |
manual/src/main/asciidoc/migrations/v2-compatibility-mode.adoc |
Updates compatibility security guidance. |
manual/src/main/asciidoc/migrations/v2-v3-compatibility.adoc |
Aligns compatibility documentation. |
manual/src/main/asciidoc/multitenancy.adoc |
Clarifies privileged tenant behavior. |
manual/src/main/asciidoc/privacy.adoc |
Documents profile identity protections. |
manual/src/main/asciidoc/recipes.adoc |
Reworks trusted login recipes. |
manual/src/main/asciidoc/request-examples.adoc |
Updates authenticated requests. |
manual/src/main/asciidoc/samples/login-sample.adoc |
Documents server-side login flow. |
manual/src/main/asciidoc/scheduler.adoc |
Updates credential examples. |
manual/src/main/asciidoc/security.adoc |
Documents new security contracts. |
manual/src/main/asciidoc/shell-commands.adoc |
Updates shell authentication guidance. |
manual/src/main/asciidoc/tutorial.adoc |
Updates tutorial credentials. |
manual/src/main/asciidoc/whats-new.adoc |
Announces security hardening. |
package/src/main/resources/bin/setenv |
Requires Unix runtime passwords. |
package/src/main/resources/bin/setenv.bat |
Requires Windows runtime passwords. |
package/src/main/resources/etc/custom.system.properties |
Enables HttpOnly cookies by default. |
package/src/main/resources/etc/users.properties |
Removes known administrator passwords. |
plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/MergeProfilesOnPropertyAction.java |
Gates cross-profile merging. |
plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/UpdatePropertiesAction.java |
Gates cross-profile and system updates. |
plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml |
Injects action security services. |
plugins/baseplugin/src/test/java/org/apache/unomi/plugins/baseplugin/actions/MergeProfilesOnPropertyActionTest.java |
Tests merge authorization. |
plugins/baseplugin/src/test/java/org/apache/unomi/plugins/baseplugin/actions/UpdatePropertiesActionTest.java |
Tests property-update authorization. |
rest/src/main/java/org/apache/unomi/rest/authentication/AuthenticationFilter.java |
Rejects blank-password authentication. |
rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java |
Passes caller trust information. |
rest/src/main/java/org/apache/unomi/rest/exception/LogSanitizer.java |
Migrates REST log sanitization. |
rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java |
Enforces profile-cookie ownership. |
rest/src/main/java/org/apache/unomi/utils/EventsRequestContext.java |
Tracks trusted caller state. |
rest/src/test/java/org/apache/unomi/rest/authentication/AuthenticationFilterBlankPasswordTest.java |
Tests blank-password rejection. |
rest/src/test/java/org/apache/unomi/rest/config/ShippedAdminPasswordConfigTest.java |
Detects shipped default credentials. |
rest/src/test/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImplProfileBindingTest.java |
Tests profile-binding rules. |
samples/login-integration/pom.xml |
Converts the sample to an OSGi service bundle. |
samples/login-integration/setup.sh |
Configures the trusted login sample. |
samples/login-integration/src/main/java/org/apache/unomi/samples/login/LoginSampleResources.java |
Registers static sample resources. |
samples/login-integration/src/main/java/org/apache/unomi/samples/login/LoginServlet.java |
Implements trusted server-side login. |
samples/login-integration/src/main/resources/static/index.html |
Adds the replacement login UI. |
samples/login-integration/src/main/resources/static/javascript/login-example.js |
Calls the server-side login endpoint. |
samples/login-integration/src/main/webapp/WEB-INF/web.xml |
Removes the legacy WAR descriptor. |
samples/login-integration/src/main/webapp/index.html |
Removes the insecure browser-driven UI. |
samples/login-integration/src/main/webapp/javascript/login-example.js |
Removes browser-to-Unomi login logic. |
samples/login-integration/src/test/java/org/apache/unomi/samples/login/LoginServletTest.java |
Tests the login servlet boundary. |
setup-elasticsearch.sh |
Loads all required Elasticsearch credentials. |
setup-opensearch.sh |
Loads all required OpenSearch credentials. |
setup-utils.sh |
Adds reusable password loading. |
web-servlets/src/main/java/org/apache/unomi/web/servlets/WebConfig.java |
Defaults profile cookies to HttpOnly. |
web-servlets/src/main/resources/org.apache.unomi.web.cfg |
Aligns the packaged HttpOnly default. |
Suppressed comments (4)
manual/src/main/asciidoc/5-min-quickstart.adoc:90
- The OpenSearch copy-paste compose example still embeds predictable public passwords instead of requiring operator-provided values. This undermines the stated removal of known credentials and leaves the shell variable used by subsequent curl examples unset.
manual/src/main/asciidoc/javascript-tracker-guide.adoc:1062 - This duplicate sample also incorrectly says the entire Web Crypto API is secure-context-only.
getRandomValues()remains exposed on non-HTTPS pages, so this branch indicates lack of either random API, not merely an insecure context.
manual/src/main/asciidoc/javascript-tracker-guide.adoc:1247 - This recommendation conflates
randomUUID()withgetRandomValues(): the latter is available in insecure contexts, so a current browser on plain HTTP will use the fallback rather than fail closed. Keep HTTPS as a general production recommendation, but do not claim it is required for this fallback.
manual/src/main/asciidoc/javascript-tracker-guide.adoc:1256 - The compatibility table is inaccurate:
crypto.getRandomValues()does not require a secure context, and a non-secure page is therefore not automatically unsupported. This could lead integrators to diagnose a tracker failure incorrectly.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if (!eventsRequestContext.getProfile().isAnonymousProfile() && | ||
| !anonymousSessionProfile && | ||
| !eventsRequestContext.getProfile().getItemId().equals(sessionProfile.getItemId())) { |
| final boolean trustedExplicitProfileOverride = trustedCaller | ||
| && requestedBodyProfileId != null | ||
| && cookieProfileIdAtRequest != null | ||
| && !requestedBodyProfileId.equals(cookieProfileIdAtRequest); |
| if (!trustedCaller && prop.startsWith(SYSTEM_PROPERTIES_PREFIX)) { | ||
| LOGGER.warn("Refusing systemProperties update for untrusted caller: {}", LogSanitizer.forLogging(prop)); | ||
| continue; | ||
| } |
| - UNOMI_ROOT_PASSWORD=choose-a-strong-password | ||
| - UNOMI_HEALTHCHECK_PASSWORD=choose-a-strong-health-password |
| validateNotEmpty(groovyScript, "Groovy script"); | ||
|
|
||
| long startTime = System.currentTimeMillis(); | ||
| auditScriptChange("save", actionName, groovyScript); |
| validateNotEmpty(actionName, "Action name"); | ||
| validateNoControlCharacters(actionName, "Action name"); | ||
|
|
||
| auditScriptChange("remove", actionName, null); |
| 'unsupported. Serve the page over HTTPS (the Web Crypto API is restricted to ' + | ||
| 'secure contexts) or install a Web Crypto polyfill.' |
…rtup
Reported issue 1. users.properties resolved the shipped karaf and health accounts
via ${...:-karaf} / ${...:-health}, so a deployment that set nothing authenticated
with a known password. Removing the fallback alone is not enough: an unset property
expands to the empty string, which Karaf's PropertiesLoginModule still accepts, so
the accounts would simply have accepted an empty password instead.
bin/setenv and the Docker entrypoint therefore refuse to start without the passwords,
and AuthenticationFilter rejects a blank Basic credential wherever one is consumed -
the launchers cannot cover every way the JVM is started (notably karaf.bat, whose
inability to halt startup is documented in setenv itself).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reported issue 3. /cxs/context.json and /cxs/eventcollector took profileId and sessionId from the request body and loaded them directly, so a caller holding only the tenant public key could read any profile whose identifier it knew. For an untrusted caller the cookie is now the sole profile bearer: the body profileId is ignored, including when no cookie is present, since otherwise knowing a UUID is itself sufficient. Adopting a session requires the cookie to already own it, and a refused session is detached rather than rebound and is not echoed back - a client told its session was accepted would keep replaying a rejected id. invalidateSession is closed as a route around the same rule. The profile cookie now defaults to HttpOnly, which addresses the XSS-to-identifier-theft chain the report describes. Trusted callers keep explicit binding, including when they present no cookie at all - a server-side integration has no cookie jar, so an explicit profileId is the only way it can name the profile it means. The new refusal paths log request-derived values, so a shared LogSanitizer is introduced and applied to them: an unsanitised newline in a session id would let a caller forge audit-shaped log records. The pre-existing REST sanitizer now delegates to it rather than keeping a second copy; equivalence was verified differentially across every BMP code unit, the truncation boundaries and 20k fuzz iterations. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reported issue 4. POST /cxs/groovyActions carried no @RequiresRole, and SecurityFilter only enforces where that annotation is present, so any authenticated subject reached it - including a tenant administrator, whose authority is meant to stop at its own tenant's data. The endpoint now requires the system ADMINISTRATOR role; tenant administrators are deliberately excluded, unlike the profile-binding trust check. The report also showed the upload itself executing. GroovyShell#parse returns a Script *instance*, and constructing it runs the script's field initializers, so a script with a Groovy @field initializer ran at save time, before any rule dispatched it. Every caller here only needs the compiled Class - to read the @action annotation or check for execute() - so compilation now uses parseClass and instantiates nothing. This also closes a second occurrence: the cache-refresh path re-instantiated persisted scripts on every refresh cycle. The regression test carries the reported payload shape and is preceded by a positive control that runs the same payload through a bare GroovyShell, so a payload that silently failed to execute could not make the assertion pass vacuously. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nistrators Reported issue 5. An ImportConfiguration's properties.source is a raw Apache Camel endpoint URI, guarded only by a scheme allowlist that permits file, ftp, sftp and ftps with no path canonicalisation or host validation. A tenant administrator could therefore read arbitrary server files into profile properties, or reach internal hosts - authority well outside that tenant's data plane. The same allowlist governs the export destinationEndpoint. Both endpoints now require the system ADMINISTRATOR role, which closes the reported reach. Confining the paths and hosts themselves is defence in depth against an actor who is already a system administrator, and is tracked separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…trusted caller Reported issue 6. MergeProfilesOnPropertyAction resolved its merge value from attacker-controlled event input and never checked that the event's profile was entitled to claim it, and UpdatePropertiesAction took targetId straight from the event and loaded any profile with it. Wired to a public event type - which the shipped login sample demonstrated - a credential-less caller could rebind its session to a victim's profile or write arbitrary properties onto one. Both actions now refuse a cross-profile merge or update, and any write to the reserved systemProperties area, unless the caller holds system access. The refusal is logged at WARN so an operator sees attempts. The systemProperties gate matches the bare key as well as the dotted prefix: for a flat name PropertyHelper falls through to BeanUtils.setProperty, which calls Profile#setSystemProperties(Map) and replaces the entire map - a superset of the per-key write the gate exists to block, and enough to plant the mergeIdentifier the merge action keys on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-side pattern Coupled to reported issue 6. The sample shipped exampleLogin.json, which wired a public login event to mergeProfilesOnPropertyAction - exactly the pattern the documentation warns against, and the pattern the report cites. The gate added for issue 6 refuses it, so the sample would otherwise ship broken as well as misleading. The browser now posts only to the sample's own /login/authenticate, which validates a demo password and then calls /cxs/context.json itself with trusted credentials, so the merge is performed by a caller the server can vouch for. The session id is server-generated and held on the container session rather than accepted from the request, and a same-origin check stands in for the per-session CSRF token a real integration would use. No demo password ships with the sample, for the same reason Unomi no longer ships a default admin password: a credential baked into published source is a credential everyone has. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…reak Adds the integration coverage for the fixes above, plus a before/after baseline for the two public client endpoints, which is the part that answers whether existing clients still work. ContextEndpointBaselineIT is written to compile and run against both master and this branch and is split into two groups with opposite expectations. Run on both, it gives: compat_* (7) master pass / branch pass hardened_publicBodyProfileIdIsIgnored master FAIL / branch pass hardened_publicCallerCannotAdoptAForeign* master FAIL / branch pass On master the first hardened test returns the victim's profile identifier and properties to a caller holding only the public API key - the reported issue reproduced end to end - and the branch answers 400. The compat group deliberately covers the client entry points that had no coverage at all: the GET forms carrying ?payload=, which is how a script tag or image beacon tracks and which route through the same binding code as POST. Also pins the areas a future ownership check is most likely to break, none of which was covered anywhere: all four branches of the anonymous-browsing handling, and persona binding. Personas short-circuit binding entirely and profileOverrides only apply to a Persona, so both are structurally isolated from these changes - now asserted rather than assumed. RestEndpointRoleSecurityIT covers the Groovy and router role gates over real HTTP; ProfileMergeIT and PropertiesUpdateActionIT cover the action gates. Every new IT is registered in AllITs and CorePersistenceITs, without which failsafe silently never runs them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The hardening changes behaviour that existing clients and operators depend on, so the documentation has to change with it or it teaches something the server now refuses. - a client-facing hardening table in the 3.0-to-3.1 migration guide, cross-referenced from the pages that describe the affected behaviour - public context examples switched from a body profileId to the context-profile-id cookie in request-examples and multitenancy; the remaining profileId occurrences elsewhere are response bodies or already cookie-based - credentials in examples no longer show karaf:karaf, and the quickstart, getting-started, configuration and Docker pages require the operator to choose passwords deliberately - the threat model records what the Groovy action endpoint is (equivalent to shell access, system administrator only, no sandbox planned and why), so a report of "Groovy is not sandboxed" triages consistently rather than being re-litigated Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fae021f to
532d855
Compare
…sanitizer against a bad limit
The tracker guide told readers the Web Crypto API is restricted to secure contexts and that plain
HTTP can therefore reach the fail-closed branch. That is wrong: only crypto.randomUUID() and
crypto.subtle are secure-context-only, and the fallback the tracker actually uses,
crypto.getRandomValues(), is available over plain HTTP. The branch is reached when Web Crypto is
absent entirely, so the error message now says that instead of prescribing HTTPS as the fix. HTTPS
is still recommended on its own merits - a session id in clear text is the larger problem - but the
support matrix no longer claims getRandomValues needs it.
The quickstart's Docker path put literal passwords in the compose example and then ran curl commands
reading ${UNOMI_ROOT_PASSWORD}, which is only exported in the Karaf path further down. A reader
following the Docker path pasted a literal and then hit 401s against an unset variable. Both compose
blocks now read the exported values, matching the shipped compose files, with the export step given
once before them.
LogSanitizer.forLogging(String, int) clamps a negative limit rather than letting substring throw.
No caller passes one today, but this helper exists to be safe to call from inside a log statement,
and a computed limit would be an easy way to turn a security-refusal log line into an uncaught
exception. Also drops a local repeat() helper in favour of String.repeat, which the Java 17 baseline
provides.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Superseded. This work has been split into one PR per issue so each can be reviewed, merged and backported independently:
Together those five carry the whole of this change: every line this branch removed from master is removed by one of them, and the production code is equivalent. The only content not carried forward is the THREAT_MODEL.md and SECURITY.md edits, which were dropped deliberately, and some Groovy hardening that was never in scope here and will get its own ticket. The branch is kept, so nothing here is lost. |
Summary
Implements UNOMI-972: defense-in-depth hardening for Unomi 3.1 around credentials, public context profile binding, and privileged extension/admin actions.
This PR replaces #842 (same change set; branch renamed to include the Jira key).
Changes
1. Explicit admin and health passwords at startup
UNOMI_ROOT_PASSWORDandUNOMI_HEALTHCHECK_PASSWORDbefore the runtime starts (setenv/setenv.bat, Docker entrypoint).setup-opensearch.sh/setup-elasticsearch.shload both passwords from.env.local; clear scripts unset them.2. Stronger public context / eventcollector profile binding
For unauthenticated / public callers of context and eventcollector:
profileIdin the request body (so public clients cannot select an arbitrary profile that way).HttpOnlyflag totrue(configurable via existing web settings); keepcustom.system.propertiesandorg.apache.unomi.web.cfgdefaults aligned.Authenticated / private-key callers keep the previous ability to bind profiles intentionally for admin and integration use cases.
3. Restrict privileged extension REST endpoints
@RequiresRole(ADMINISTRATOR)so only the system administrator role can use them (not a tenant administrator).4. Gate cross-profile merge and system property updates
MergeProfilesOnPropertyActionandUpdatePropertiesActionrefuse cross-profile merge/update andsystemProperties.*writes unless the caller is a trusted identity (system administrator or tenant administrator).5. Login sample rewrite (trusted server-side pattern)
LoginServlet(/login/authenticate) that validates a demo password then posts aloginevent to/cxs/context.jsonwith trusted Basic auth (tenantId:privateKeypreferred, orkaraf+X-Unomi-Tenant-Id)./login(redirect to/login/index.html), metatype cfg, Jackson JSON.login-sample.adoc(including PlantUML sequence) and align recipes / builtin login-event / threat-model wording so the sample no longer teaches an untrusted public merge path.Docs and threat model
how-profile-tracking-works,security, recipes, tracker guide, configuration, and What's new.#_client_facing_hardening_3_1).THREAT_MODEL.mdwith the HttpOnly default and the trusted login-sample pattern.Test plan
ShippedAdminPasswordConfigTestRestServiceUtilsImplProfileBindingTestGroovyActionsEndPointRoleTest,RouterConfigurationEndPointRoleTestMergeProfilesOnPropertyActionTest,UpdatePropertiesActionTestContextServletITpublic vs private binding casesRestEndpointRoleSecurityITProfileMergeIT/PropertiesUpdateActionITtrusted vs untrusteddefault, login at/login, verify merge after clearingcontext-profile-id