Skip to content

[SYNCOPE-1978] Search audit events by username#1443

Merged
ilgrosso merged 1 commit into
apache:masterfrom
ozimakov:SYNCOPE-1978-username
Jun 27, 2026
Merged

[SYNCOPE-1978] Search audit events by username#1443
ilgrosso merged 1 commit into
apache:masterfrom
ozimakov:SYNCOPE-1978-username

Conversation

@ozimakov

Copy link
Copy Markdown
Contributor

SYNCOPE-1978

What

Restores the ability to search audit events by the username of the entity whose payload was logged, complementing the existing entityKey (UUID) match. A new repeatable username query parameter on AuditQuery matches the token "username":"<value>" embedded in the serialized audit payload (before, inputs, output, throwable):

  • single — ?username=jdoe
  • multiple, OR-ed — ?username=jdoe&username=asmith

It is an exact match (no wildcards) and composes (AND) with the existing audit search filters (entityKey, who, type, category, op, outcome, before/after).

Why

Today an audit search can be pinned to a specific user only via entityKey, which requires the user's UUID. That UUID is unavailable once the user has been deleted, so there is no way to retrieve a deleted user's audit trail. The username, however, is preserved inside the before snapshot of the relevant audit events, so matching by username makes that trail searchable — and saves a resolve-then-query round-trip for live users.

Syncope 3.0 allowed an approximation by abusing entityKey=<username> (an imprecise LIKE '%key%<value>%'); the 4.0 audit refactor correctly tightened entityKey to match only the UUID, which removed that side effect. This change reintroduces the capability deliberately and precisely, as a dedicated filter rather than re-loosening entityKey.

Implementation

The filter is threaded through AuditServiceImplAuditLogic → the AuditEventDAO interface and all of its implementations:

  • JPA: per value, (beforeValue/inputs/output/throwable LIKE ? ESCAPE '#') matching %"username":"<value>"%, OR-ed; the value is bound as a parameter and its LIKE metacharacters are escaped (so %/_ in a username match literally);
  • Neo4j: ANY(u IN $usernames WHERE n.before CONTAINS u OR ...) — a literal CONTAINS with a bound list parameter (no regex);
  • Elasticsearch / OpenSearch: a multi_match phrase query of "username":"<value>" over the payload fields, OR-ed via bool/should.

All values are bound as query parameters (SQL/Cypher) or passed as structured phrase queries (Elasticsearch/OpenSearch), so there is no injection surface; the pre-existing entityKey predicate is intentionally left unchanged.

Tests

Integration tests in AuditITCase cover exact match (asserting the matched event's payload truly carries the username), multiple values (OR), non-match exclusion, exact-not-prefix matching, and the deleted-user case (a user is created then deleted, and is still found by username via the delete event's before snapshot). Verified end-to-end against embedded PostgreSQL, Neo4j and Elasticsearch.

Restore the ability to search audit events by the username of the entity
whose payload was logged, complementing the existing entityKey (UUID) match.
A new repeatable "username" query parameter on AuditQuery matches the token
"username":"<value>" embedded in the serialized audit payload (before, inputs,
output, throwable), with exact match and multiple values OR'ed, composing with
the existing audit search filters. This is useful to retrieve a user's audit
trail by login name, in particular after the user has been deleted and its key
is no longer available.

The filter is threaded through AuditServiceImpl, AuditLogic and the
AuditEventDAO interface and all of its implementations (JPA, Neo4j,
Elasticsearch, OpenSearch). The username value is bound as a query parameter
(SQL/Cypher) or passed as a structured phrase query (Elasticsearch/OpenSearch),
so there is no injection surface; the JPA LIKE predicate escapes metacharacters
so only exact matches are returned. Covered by integration tests in AuditITCase,
including the deleted-user case.
@ilgrosso ilgrosso merged commit dc11318 into apache:master Jun 27, 2026
30 of 34 checks passed
@ilgrosso

Copy link
Copy Markdown
Member

Thank you @ozimakov I have cherry-picked this work to branches 4_1_X and 4_0_X.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants