8000 Reject blank usernames created by identity provider mappers by ItsRoy69 · Pull Request #53485 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

Reject blank usernames created by identity provider mappers - #53485

Open
ItsRoy69 wants to merge 6 commits into
keycloak:mainfrom
ItsRoy69:fix/53164-blank-username-from-idp-mappers
Open

ItsRoy69 wants to merge 6 commits into
keycloak:mainfrom
ItsRoy69:fix/53164-blank-username-from-idp-mappers

Conversation

@ItsRoy69
@ItsRoy69 ItsRoy69 commented Oct 3, 2026
Copy link
Copy Markdown

Closes #53164

Identity provider mappers could resolve a username template to a blank value. The federated account was then created with that blank username and could neither be found by username nor edited or deleted, because invalidating the user in the cache required a username.

Username template mappers now fail with a descriptive error instead of resolving to a blank value, addUser rejects blank usernames, and the username fallbacks in the first broker login and token exchange flows treat blank as absent. Accounts that were already created with a blank username can be edited and deleted again. An upgrading guide entry documents the behavior
change.

Verified: services (757 tests), model/infinispan (47), model/jpa (8) and model/storage-private (1) pass. The username template mapper tests were mutation-checked and fail if either guard is removed.

Copilot AI balanced review requested due to automatic review settings October 3, 2026 02:44
@ItsRoy69
ItsRoy69 requested review from a team as code owners October 3, 2026 02:44
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.

Copilot review overview

🟡 Changes recommended

The five-argument session-level user creation path still dereferences null usernames before the new validation runs.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Prevents identity-provider mappings from creating unmanageable users with blank usernames.

Changes:

  • Rejects unresolved or blank username templates and adds blank-aware fallbacks.
  • Validates usernames during user creation and permits cache invalidation for legacy null usernames.
  • Adds mapper tests and upgrade documentation.
File Description
tests/​base/​.../​IdpCreateUserIfUniqueAuthenticatorTest.java Tests unresolved broker templates.
services/​src/​test/​.../​saml/​UsernameTemplateMapperTest.java Adds SAML mapper coverage.
services/​src/​test/​.../​oidc/​mappers/​UsernameTemplateMapperTest.java Adds OIDC mapper coverage.
services/​.../​IdentityBrokerService.java Treats blank broker usernames as absent.
services/​.../​AbstractTokenExchangeProvider.java Applies blank-aware token-exchange fallbacks.
services/​.../​saml/​mappers/​UsernameTemplateMapper.java Rejects invalid SAML template results.
services/​.../​oidc/​mappers/​UsernameTemplateMapper.java Rejects invalid OIDC template results.
server-spi/​.../​UserProvider.java Updates the username validation contract.
model/​storage-private/​.../​UserStorageManager.java Rejects blank usernames.
model/​jpa/​.../​JpaUserProvider.java Validates usernames before persistence.
model/​infinispan/​.../​UserCacheManager.java Skips null username cache keys.
model/​infinispan/​.../​UserFullInvalidationEvent.java Supports legacy null usernames.
docs/​.../​changes-26_8_1.adoc Documents the behavior change.

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

Comment on lines +124 to +126
if (StringUtil.isBlank(username)) {
throw new ModelException("Cannot create a user without a username");
}
Copilot AI balanced review requested due to automatic review settings October 3, 2026 03:07
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.

Copilot review overview

🟡 Changes recommended

The five-argument UserStorageManager.addUser overload still throws NullPointerException for a null username.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)

Copilot AI balanced review requested due to automatic review settings October 3, 2026 03:44
Signed-off-by: Jyotirmoy Roy <jyotirmoyroy649@gmail.com>
Signed-off-by: Jyotirmoy Roy <jyotirmoyroy649@gmail.com>
Signed-off-by: Jyotirmoy Roy <jyotirmoyroy649@gmail.com>
@ItsRoy69
ItsRoy69 force-pushed the fix/53164-blank-username-from-idp-mappers branch from 6ccae19 to 8089c1c Compare October 3, 2026 03:45
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.

Copilot review overview

🟢 Approval recommended

The behavior is consistently implemented and tested; the remaining contract wording issue is non-blocking.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)
Resolved since last review (1)

*
* @throws NullPointerException when username or realm is {@code null}
* @throws NullPointerException when realm is {@code null}
* @throws ModelException when username is {@code null} or empty
Copilot AI balanced review requested due to automatic review settings October 3, 2026 03:47
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.

Copilot review overview

🟢 Approval recommended

The behavior is consistently enforced and well tested, with only a minor documentation precision issue remaining.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)

@vramik
vramik requested a review from sguilhen October 15, 2026 19:57
@vramik vramik 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.

Thanks @ItsRoy69 for the PR! I need to look into it more closely, but it seems to me tahat the new IdentityBrokerException thrown by the username template mappers is handled gracefully in the broker login flow (IdentityBrokerService catches it at line 1062 and redirects to an error page), but in the token exchange flow AbstractTokenExchangeProvider.exchangeExternalToken() calls importUserFromExternalIdentity() at line 302 without catching IdentityBrokerException. If a mapper cannot resolve the username template during a token exchange, the exception propagates uncaught and surfaces as a 500 Internal Server Error instead of a 400 Bad Request like the other error
responses in that method.

Should importUserFromExternalIdentity (or exchangeExternalToken) catch IdentityBrokerException and throw a CorsErrorResponseException with 400 status to match the existing error handling pattern?

@sguilhen I'd welcome your eyes here, ptal

@sguilhen sguilhen 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.

@vramik good catch - confirmed. IdentityBrokerException is a plain RuntimeException, so once it escapes importUserFromExternalIdentity() (called from exchangeExternalToken() around line 302) it isn't caught anywhere in the token exchange path and falls through to KeycloakErrorHandler (the generic Throwable -> 500 mapper), unlike every other failure in exchangeExternalToken() which goes through CorsErrorResponseException and returns 400.

Suggested fix, reusing the same CorsErrorResponseException/OAuthErrorException.INVALID_REQUEST pattern already used elsewhere in this method:

UserModel user;
try {
    user = importUserFromExternalIdentity(context);
} catch (IdentityBrokerException e) {
    event.detail(Details.REASON, e.getMessage());
    event.error(Errors.IDENTITY_PROVIDER_ERROR);
    throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, e.getMessage(), Response.Status.BAD_REQUEST);
}

It'd also be good to add a test that exercises token exchange against an IdP whose username template mapper can't resolve a username, to lock in the 400 response.

Copilot AI balanced review requested due to automatic review settings November 13, 2026 15:26
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.

🟡 Changes recommended

Unicode-whitespace usernames bypass broker and token-exchange fallbacks despite being rejected by storage, while the regression test does not reliably verify that failed login leaves no new account.

4 open issues

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings November 13, 2026 17:57
@ItsRoy69
ItsRoy69 requested review from a team as code owners November 13, 2026 17:57
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.

🟡 Changes recommended

When the auth server hosts differ, an issuer mismatch causes token validation to fail before the username mapper runs, so the test passes without exercising mapper rejection.

4 open issues
1 resolved since last review

Review effort: Balanced

Comment on lines +114 to +115
RealmResource consumerRealm = realmsResouce().realm(bc.consumerRealmName());
IdentityProviderResource identityProviderResource = consumerRealm.identityProviders().get(bc.getIDPAlias());
Copilot AI balanced review requested due to automatic review settings November 13, 2026 18:03
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.

🟡 Changes recommended

The regression test can pass without exercising the username guard when hostnames differ, and the upgrade guidance incorrectly suggests unlinking a federated identity assigns a local username.

4 open issues
1 resolved since last review

Review effort: Balanced


Review the username template of any identity provider mapper that does not resolve for every user, before upgrading.

No database constraint was added, so existing accounts are not rejected. Use the following query to find accounts that are affected, and assign them a username, for example by resetting their federated identity:
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.

Identity provider mappers can create users with null username, which cannot be edited or deleted

5 participants

0