Skip to content

Comments

feat(sync): add configurable role/group synchronization modes with ignore filters#399

Open
clairtonluz wants to merge 3 commits intodaniel-frak:masterfrom
clairtonluz:master
Open

feat(sync): add configurable role/group synchronization modes with ignore filters#399
clairtonluz wants to merge 3 commits intodaniel-frak:masterfrom
clairtonluz:master

Conversation

@clairtonluz
Copy link

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:

  • Import only on first login.
  • Keep in sync on every login.
  • Sync by adding only.
  • Disable sync completely.

It also needed admin controls to prevent importing or removing specific roles/groups, especially Keycloak defaults.

Changes

  1. Added sync mode support for groups and roles:
  • SYNC_FIRST_LOGIN
  • SYNC_EVERY_LOGIN
  • SYNC_EVERY_LOGIN_ONLY_ADD
  • NO_SYNC
  1. Added UserSyncMode enum and parsing logic.
  2. Added new admin config fields:
  • IGNORED_SYNC_GROUPS
  • IGNORED_SYNC_ROLES
  • Wildcard support (*).
  1. Added default ignored roles:
  • default-roles-*
  • realm-management
  • offline_access
  • uma_authorization
  1. Implemented sync logic in provider/factory:
  • First-login import flow.
  • Every-login full reconciliation (add/remove).
  • Every-login add-only flow.
  • No-sync flow.
  • Ignore filters applied for import and removal checks.
  1. Added defensive refresh error handling to avoid login hard-failures during sync.
  2. Updated docs/help text:
  • README updated with sync modes and ignore behavior.
  • Provider help text explains ignore use cases.
  1. Updated tests:
  • Provider tests for mode dispatch.
  • Factory tests for reconciliation and ignored-item behavior.
  • Test model support methods completed for new sync paths.

Behavior Matrix (Groups/Roles)

  • SYNC_FIRST_LOGIN: Import only on first login.
  • SYNC_EVERY_LOGIN: Add/remove on each login, excluding ignored items.
  • SYNC_EVERY_LOGIN_ONLY_ADD: Add missing only, excluding ignored items.
  • NO_SYNC: Do not import or update from legacy.

Validation

  • ./mvnw -DskipTests package passed.
  • Full tests should run in CI/runtime environment.

Manual Test Plan

  1. Configure role/group sync mode in admin UI.
  2. Configure ignored roles/groups (including wildcard patterns).
  3. Login with a legacy-backed user and verify:
  • Expected roles/groups are synced based on selected mode.
  • Ignored roles/groups are not imported.
  • Ignored Keycloak-only roles/groups are not removed.
  1. Validate Account Console access remains healthy (/account/* no 401).

Clairton Luz added 3 commits February 13, 2026 10:21
…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
Copilot AI review requested due to automatic review settings February 20, 2026 21:41
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 UserSyncMode and 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 into LegacyProvider refresh-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

  • isValid refreshes/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 via UserModelFactory). Consider moving refreshUserFromLegacy(...) to run only after legacyUserService.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.

Comment on lines +391 to +397
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 + "$");
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
]
```
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.
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
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.

Copilot uses AI. Check for mistakes.
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.

1 participant