feat(sync): add configurable role/group synchronization modes with ignore filters#399
feat(sync): add configurable role/group synchronization modes with ignore filters#399clairtonluz wants to merge 3 commits intodaniel-frak:masterfrom
Conversation
…lters Introduce configurable sync modes for groups and roles: - SYNC_FIRST_LOGIN - SYNC_EVERY_LOGIN - SYNC_EVERY_LOGIN_ONLY_ADD - NO_SYNC Add UserSyncMode enum and migrate provider logic from boolean flags to mode-based behavior. Implement first-login import, full reconcile, add-only reconcile, and no-sync paths for roles/groups. Add admin-configured ignore lists: - IGNORED_SYNC_GROUPS - IGNORED_SYNC_ROLES Set default ignored role patterns to protect Keycloak defaults: - default-roles-* - realm-management - offline_access - uma_authorization Apply ignore filters during sync so selected legacy items are not imported and existing Keycloak-only assignments are not removed. Improve backward compatibility and safety: - map legacy boolean values (true/false) to sync modes - guard login refresh with exception handling to avoid login breakage Refactor and cleanup: - remove obsolete migration helpers in UserModelFactory - update tests (provider + factory + test user model) for new sync behavior - update README and config help text with mode semantics and ignore guidance
There was a problem hiding this comment.
Pull request overview
Adds configurable per-login synchronization of legacy-backed users’ roles and groups, including “first login only”, “every login”, “add only”, and “no sync” modes with ignore filters to protect Keycloak-managed items.
Changes:
- Introduces
UserSyncModeand new admin config properties for role/group sync modes and ignored patterns (with wildcard support). - Implements role/group reconciliation + add-only sync paths in
UserModelFactory, and wires them intoLegacyProviderrefresh-on-login flow. - Expands test coverage for sync mode dispatch and reconciliation/ignore behavior; updates README and docker Keycloak image tag.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/main/java/com/danielfrak/code/keycloak/providers/rest/remote/UserModelFactory.java |
Adds first-login import gating, ignore filters, and reconcile/add-only role/group sync helpers. |
src/main/java/com/danielfrak/code/keycloak/providers/rest/UserSyncMode.java |
New enum + parsing to support legacy boolean config values and new mode names. |
src/main/java/com/danielfrak/code/keycloak/providers/rest/LegacyProvider.java |
Refresh flow updated to optionally sync roles/groups during login based on sync mode. |
src/main/java/com/danielfrak/code/keycloak/providers/rest/ConfigurationProperties.java |
Adds new config keys + provider UI options + default ignored role patterns. |
src/test/java/com/danielfrak/code/keycloak/providers/rest/remote/UserModelFactoryTest.java |
Adds reconciliation/add-only/ignore/no-sync tests for roles and groups. |
src/test/java/com/danielfrak/code/keycloak/providers/rest/remote/TestUserModel.java |
Implements additional UserModel methods needed by new test paths (attributes, group/role removal). |
src/test/java/com/danielfrak/code/keycloak/providers/rest/LegacyProviderTest.java |
Adds tests asserting the correct sync method is invoked for configured modes. |
README.md |
Documents new sync mode configs and ignore behavior + default ignored roles. |
docker/.env |
Updates Keycloak image tag used in the docker demo environment. |
Comments suppressed due to low confidence (1)
src/main/java/com/danielfrak/code/keycloak/providers/rest/LegacyProvider.java:71
isValidrefreshes/synchronizes user attributes/groups/roles before verifying the provided credentials. With the new group/role sync side-effects this means a failed login attempt can still mutate the user’s roles/groups (and potentially create realm roles/groups viaUserModelFactory). Consider movingrefreshUserFromLegacy(...)to run only afterlegacyUserService.isPasswordValid(...)returns true, or guard group/role updates behind successful credential validation.
public boolean isValid(RealmModel realmModel, UserModel userModel, CredentialInput input) {
LOG.debugf("isValid called for user %s with credential type %s",
userModel == null ? "null" : userModel.getUsername(), input == null ? "null" : input.getType());
if (!supportsCredentialType(input.getType())) {
return false;
}
refreshUserFromLegacy(realmModel, userModel);
var userIdentifier = getUserIdentifier(userModel);
if (!legacyUserService.isPasswordValid(userIdentifier, input.getChallengeResponse())) {
return false;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private boolean wildcardMatch(String pattern, String value) { | ||
| if (pattern == null || pattern.isBlank()) { | ||
| return false; | ||
| } | ||
| String regex = Pattern.quote(pattern.trim()).replace("*", "\\E.*\\Q"); | ||
| return value.matches("^" + regex + "$"); | ||
| } |
There was a problem hiding this comment.
wildcardMatch uses String#matches, which recompiles a regex on every invocation. During login sync this can be called many times (roles/groups × ignore patterns) and can become a noticeable hotspot. Consider precompiling the configured wildcard patterns once (e.g., to List<Pattern>) and using pattern.matcher(value).matches() here.
| ] | ||
| ``` | ||
| If organization does not exist inside KeyCloak, This extension will create the organization first then it will assign the user. No newline at end of file | ||
| If organization does not exist inside KeyCloak, This extension will create the organization first then it will assign the user. |
There was a problem hiding this comment.
Spelling/grammar in this sentence: “KeyCloak” should be “Keycloak”, and “, This extension” should not capitalize “This” mid-sentence. Consider rephrasing for readability (e.g., split into two sentences).
| If organization does not exist inside KeyCloak, This extension will create the organization first then it will assign the user. | |
| If the organization does not exist in Keycloak, this extension will first create the organization and then assign the user to it. |
Summary
This PR adds a new synchronization feature for user roles and groups based on legacy identity data.
It introduces sync modes, ignore filters, and safer defaults to support different migration/sync strategies without breaking Keycloak-managed access.
Why
Role/group sync behavior needed to support multiple strategies:
It also needed admin controls to prevent importing or removing specific roles/groups, especially Keycloak defaults.
Changes
Behavior Matrix (Groups/Roles)
Validation
Manual Test Plan