Skip to content

Combine search query param with other filters in users endpoint - #50003

Open
hakdogan wants to merge 1 commit into
keycloak:mainfrom
hakdogan:fix-users-search-ignores-other-filters
Open

Combine search query param with other filters in users endpoint#50003
hakdogan wants to merge 1 commit into
keycloak:mainfrom
hakdogan:fix-users-search-ignores-other-filters

Conversation

@hakdogan

Copy link
Copy Markdown
Contributor

Closes #49995

Problem

On GET /admin/realms/{realm}/users, the search query param overrode the other attribute filters instead of stacking with them. A request like:

GET /admin/realms/my-realm/users?email=foo@bar&username=baz&search=bam

ignored email and username and only applied the full-text search, so it returned every user matching bam in username, first/last name or email. The same happened with idpAlias, idpUserId, firstName, lastName, exact and q.

Cause

In UsersResource#getUsers, the logic was an if (search != null) … else if (other filters) … else … chain. When search was present, the branch that handles all the other filters was never reached, and the search branch built
an attribute map containing only SEARCH (plus enabled / emailVerified / created timestamps) before returning early.

Fix

The branching is reworked so that all provided filters are collected into a single attribute map and applied together, letting search stack with the other filters. The special prefix lookups (id: / username: / email:) keep their dedicated path and short-circuit as before. The includeServiceAccounts behaviour is preserved: service accounts are excluded when search is used and included for plain attribute filtering.

The underlying store layer (JpaUserProvider#predicates) already supports combining SEARCH with the individual field predicates, so no model changes were needed.

Testing

Added UserSearchTest#searchStacksWithOtherFilters, which creates two users sharing a common last name and verifies that combining search with username / email narrows the result to the matching user instead of returning both.

@ch4mpy

ch4mpy commented Jun 16, 2026

Copy link
Copy Markdown

@hakdogan thank you; that will greatly improve my user search, where I have to filter by IDP in a multi-tenant application (each "tenant" is identified as an IDP, and users authenticated by a provider should not "see" users from other providers).

@hakdogan

Copy link
Copy Markdown
Contributor Author

Thanks @ch4mpy that per-IDP, multi-tenant scenario is exactly the kind of use case this addresses. With the change, search now stacks with idpAlias (and the other filters) instead of overriding them, so e.g.

GET /admin/realms/{realm}/users?idpAlias=<tenant>&search=<term>

returns only users linked to that identity provider and matching the search term.

The PR is open and waiting on a maintainer review. Worth flagging for the reviewers: the earlier "other params are ignored" point comes from the UserQueryMethodsProvider / getUsersCount Javadoc, while the public REST/OpenAPI docs describe only the search semantics so if this change lands, that SPI Javadoc should be updated to match. I'm happy to adjust the scope based on what the maintainers prefer.

The 'search' query param on GET /admin/realms/{realm}/users overrode the
other attribute filters (email, username, idpAlias, idpUserId, firstName,
lastName, exact, q) instead of stacking with them, so a request like
?email=foo&username=baz&search=bam ignored email and username and only
applied the full-text search.

The branching is reworked so that, unless the search uses a special prefix
(id:/username:/email:) which keeps its dedicated lookup path, all provided
filters are collected into a single attribute map and applied together.

Closes keycloak#49995

Signed-off-by: Hüseyin Akdoğan <huseyin@keymate.io>
@hakdogan
hakdogan force-pushed the fix-users-search-ignores-other-filters branch from 0469cce to 6cc3881 Compare August 11, 2026 06:54
Copilot AI balanced review requested due to automatic review settings August 11, 2026 06:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Combines full-text user search with explicit filters in the Admin REST API.

Changes:

  • Consolidates search and attribute filters.
  • Preserves prefix lookup and service-account behavior.
  • Adds database-backed regression coverage.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
UsersResource.java Combines user-search filters.
UserSearchTest.java Tests username and email combinations.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +323 to +324
if (search != null) {
attributes.put(UserModel.SEARCH, search.trim());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accurate observation, but this is pre-existing behaviour in the LDAP provider rather than something introduced here, and it matches the current SPI contract documented in UserQueryMethodsProvider#searchForUserStream ("when SEARCH is set all other params are ignored"). Changing it means changing that contract and reworking the LDAP query building, so I have raised the scoping question with the maintainers in the conversation below rather than expanding this PR unilaterally.

@keycloak-github-bot

Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.webauthn.passwordless.WebAuthnPwdLessPropertyTest#timeout

Keycloak CI - WebAuthn IT

org.openqa.selenium.WebDriverException: 
unknown error: unhandled inspector error: {"code":-32000,"message":"Node with given id does not belong to the document"}
  (Session info: chrome=150.0.7871.128)
Build info: version: '4.28.1', revision: '73f5ad48a2'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '6.17.0-1020-azure', java.version: '25.0.3'
...

Report flaky test

@keycloak-github-bot keycloak-github-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unreported flaky test detected, please review

@hakdogan

hakdogan commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

The automated review flagged that this PR does not fix the same problem for LDAP backed users, and that is correct. Before expanding the scope I would like maintainer input on the contract question behind it.

LDAPStorageProvider.searchForUserStream branches to searchLDAP(...) whenever UserModel.SEARCH is present and drops the remaining entries of the map (federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java:400-404). That behaviour predates this PR and is not a regression introduced here. It also matches the current SPI contract, which explicitly documents it in UserQueryMethodsProvider#searchForUserStream:

UserModel#SEARCH - search for users whose username, email, first name or last name contain any of the strings in search separated by whitespace, when SEARCH is set all other params are ignored

So the real question is the contract itself, not just the LDAP provider. This PR changes the admin REST endpoint so that search stacks with the other filters instead of overriding them, which is what #49995 asks for. If that is the semantics we want, then the SPI javadoc above should be updated and every UserQueryMethodsProvider implementation, LDAP included, needs to follow it. Doing that inside this PR would turn a REST layer fix into a change of a documented SPI contract plus a rework of the LDAP query building, which I think deserves its own review.

My suggestion: keep this PR scoped to the REST endpoint and the JPA path, and track the LDAP provider and the SPI javadoc as a follow up issue that I am happy to pick up. Technically the LDAP side is feasible: searchLDAPByAttributes already builds conditions from a mixed attribute map, it simply does not recognise SEARCH, so the two paths could be merged into a single LDAPQuery that ANDs the attribute conditions with the OR condition built by searchLDAP. It does need LDAP integration coverage on top.

If you would rather see it all land as one change, say the word and I will extend this PR accordingly.

For the record, the branch was rebased on current main and the full CI run is green. The flaky-test label comes from WebAuthnPwdLessPropertyTest#timeout, which is unrelated to this change and was previously reported as #32753.

This has been open since June without a maintainer review, so I would appreciate maintainer guidance on the scoping question above.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

when using the search query param on the GET /admin/realms/{realm}/users endpoint, other params are ignored

3 participants