-
Notifications
You must be signed in to change notification settings - Fork 1
Feature/sync 2 #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @fsch, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
Summary of Changes
Gemini here, providing a summary for this pull request. This PR introduces user synchronization capabilities to the database user storage provider. The core changes involve implementing Keycloak's ImportSynchronization interface to allow pulling user data from the external database into Keycloak. Additionally, a new REST endpoint is added to manually trigger this synchronization process. Supporting changes include updating the project's build environment to Java 17 and Keycloak 25, adding necessary dependencies like Mockito for testing, and setting up a VS Code dev container for easier development.
Highlights
- User Synchronization: Implemented the
ImportSynchronizationinterface inDBUserStorageProviderto enable pulling users from the configured database into Keycloak. This includes logic for adding new users and updating existing ones based on theallowDatabaseToOverwriteKeycloaksetting. - Manual Sync Trigger: Added a new
RealmResourceProvider(DBUserStorageResource) with a/syncendpoint to allow manual triggering of the user synchronization process via a REST call. - Build Environment and Dependencies Update: Updated the project's Java compiler source/target to 17 and the Keycloak version dependency to 25.0.0. Added necessary dependencies for testing (Mockito) and Keycloak internal APIs (JAX-RS, Resteasy, keycloak-services). A VS Code dev container configuration was added to standardize the development environment.
- Configuration Options: Added new configuration properties to the user storage provider factory (
DBUserStorageProviderFactory) to control synchronization:syncEnabled(to enable/disable sync) andlistAllForSync(a specific query for fetching users during sync). - Testing: Added new JUnit tests for the
DBUserStorageProviderand the newDBUserStorageResourceto cover the synchronization logic and the REST endpoint functionality.
Changelog
Click here to see the changelog
- .devcontainer/Dockerfile
- Added instructions to install Maven 3.9.6.
- .devcontainer/devcontainer.json
- Configured VS Code dev container to use the new Dockerfile.
- Set Java runtime to JavaSE-17 (via base image) and added Java extensions.
- Forwarded Keycloak ports 8080 and 8443.
- Added post-create command to verify Java and Maven versions.
- pom.xml
- Added blank line after junit dependency (line 145).
- Added Mockito Core (v5.11.0), JAX-RS API (v2.1.1), Resteasy JAXRS (v3.15.1.Final), and Keycloak Services (v${keycloak.version}) dependencies (lines 161-187).
- Added configuration for maven-surefire-plugin to run tests (lines 228-240).
- Updated
maven.compiler.sourceandmaven.compiler.targetto 17 (lines 246-247). - Updated
keycloak.versionproperty to 25.0.0 (line 253).
- src/main/java/org/opensingular/dbuserprovider/DBUserStorageProvider.java
- Implemented
ImportSynchronizationinterface (line 33). - Changed constructor to accept
UserStorageProviderModel(line 41). - Added
syncEnabledfield (line 39). - Modified
updateCredentialto check user's federation link before attempting DB update (lines 106-115). - Added
unlinkUsermethod (lines 272-285). - Implemented
syncmethod for full user synchronization (lines 288-338). - Implemented
syncSincemethod (currently calls full sync) (lines 340-346). - Added
mapUserAttributeshelper method (lines 348-353).
- Implemented
- src/main/java/org/opensingular/dbuserprovider/DBUserStorageProviderFactory.java
- Changed
createmethod signature to acceptUserStorageProviderModeland added overloaded method forComponentModel(lines 50-54). - Changed
configuremethod signature to acceptUserStorageProviderModel(line 59). - Added
syncEnabledandlistAllForSyncparameters toQueryConfigurationsconstructor call (lines 80-81). - Changed
validateConfigurationmethod signature to acceptUserStorageProviderModeland added overloaded method forComponentModel(lines 88-91). - Updated
getConfigPropertiesto addsyncEnabledandlistAllForSyncproperties and refined help texts/default queries (lines 109-229).
- Changed
- src/main/java/org/opensingular/dbuserprovider/DBUserStorageResource.java
- Added new file implementing
RealmResourceProvider(lines 1-57). - Added
/syncPOST endpoint to trigger user synchronization (lines 40-56).
- Added new file implementing
- src/main/java/org/opensingular/dbuserprovider/DBUserStorageResourceProviderFactory.java
- Added new file implementing
RealmResourceProviderFactoryand registering the new resource factory (lines 1-40).
- Added new file implementing
- src/main/java/org/opensingular/dbuserprovider/model/QueryConfigurations.java
- Added
syncEnabledandlistAllForSyncfields (lines 19-20). - Updated constructor to include new fields (line 22).
- Added getter methods for
syncEnabledandlistAllForSync(lines 95-100).
- Added
- src/main/java/org/opensingular/dbuserprovider/persistence/UserRepository.java
- Added
getAllUsersForSyncmethod to execute the sync query (lines 210-213).
- Added
- src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory
- Added new file to register
DBUserStorageResourceProviderFactory.
- Added new file to register
- src/test/java/org/opensingular/dbuserprovider/DBUserStorageProviderTest.java
- Added new file with JUnit tests for
DBUserStorageProvidersync functionality (lines 1-125).
- Added new file with JUnit tests for
- src/test/java/org/opensingular/dbuserprovider/DBUserStorageResourceTest.java
- Added new file with JUnit tests for
DBUserStorageResourcesync endpoint (lines 1-104).
- Added new file with JUnit tests for
- src/test/java/org/opensingular/dbuserprovider/mocks/MockDataSourceProvider.java
- Added new file providing a mock
DataSourceimplementation for testing (lines 1-71).
- Added new file providing a mock
- src/test/java/org/opensingular/dbuserprovider/util/TestUtils.java
- Added new file with basic Mockito utility methods (lines 1-15).
- src/test/resources/logging.properties
- Added new file to configure logging for tests (lines 1-5).
- src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
- Added new file to configure Mockito for inline mocking (lines 1-1).
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Database users, synced with care,
From SQL tables, brought to share.
Keycloak knows them, one and all,
Ready to answer freedom's call.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces a user synchronization feature, allowing users from an external database to be synced into Keycloak. It also includes a JAX-RS endpoint to trigger this synchronization, updates to Java 17 and Keycloak 25, and a dev container setup. These are significant and valuable enhancements.
The overall structure is good, and the addition of tests is appreciated. However, there are a few critical and high-severity issues, particularly in the new synchronization logic and the JAX-RS resource setup, that need to be addressed before this can be merged. I've detailed these in the comments below.
It would also be beneficial to update the pull request description with more details about the changes and their purpose.
Summary of Findings
- Critical: User Creation in Sync Logic: The
DBUserStorageProvider.sync()method usesthis.addUser()to create new users in Keycloak. Sincethis.addUser()always returnsnull, new users from the database will not be created, and aNullPointerExceptionwill occur when trying to set the federation link. - Critical: NullPointerException in JAX-RS Resource: The
DBUserStorageResourceis initialized with anullmodel by its factory, which will cause aNullPointerExceptionwhen the/syncendpoint is called and tries to accessmodel.getProviderId(). - High: Potential SQL Error in Sync Query: If the
listAllForSyncquery is not configured (defaults to an empty string),UserRepository.getAllUsersForSync()will attempt to execute an empty SQL query, likely leading to anSQLException. - Medium: Redundant
pom.xmlFile: A new file namedpom.xml (update with Mockito dependencies)has been added. This file appears to be redundant as Mockito dependencies are already managed in the mainpom.xml. - Medium: Incorrect Mock in Test:
DBUserStorageProviderTest.testSync()mocksuserRepository.getAllUsers()instead of the actually calleduserRepository.getAllUsersForSync(). - Java and Keycloak Version Upgrades: The project has been upgraded to Java 17 and Keycloak 25.0.0, which are positive updates.
- Dev Container Setup: The addition of a dev container configuration (
.devcontainer/Dockerfileand.devcontainer/devcontainer.json) is a good step for improving the development environment consistency.
Merge Readiness
This pull request introduces valuable synchronization functionality and important version upgrades. However, due to the identified critical and high severity issues, particularly concerning the core sync logic and the JAX-RS endpoint, I recommend that these issues be addressed before merging. Once these are resolved, the PR will be in much better shape. As an AI, I am not authorized to approve pull requests; please ensure further review and approval from team members.
|
|
||
| if (keycloakUser == null) { | ||
| log.infov("User {0} not found in Keycloak, creating...", username); | ||
| keycloakUser = this.addUser(realm, username); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sync method attempts to add new users to Keycloak using keycloakUser = this.addUser(realm, username);. However, the addUser method in this class (lines 255-258) is implemented to always return null:
// src/main/java/org/opensingular/dbuserprovider/DBUserStorageProvider.java:255-258
@Override
public UserModel addUser(RealmModel realm, String username) {
// from documentation: "If your provider has a configuration switch to turn off adding a user, returning null from this method will skip the provider and call the next one."
return null;
}This means that if keycloakUser is null at line 313, the subsequent keycloakUser.setFederationLink(model.getId()); on line 318 will result in a NullPointerException, and new users from the database will not be correctly created and linked in Keycloak.
To fix this, you should use Keycloak's UserProvider to create the user, for example:
keycloakUser = session.users().addUser(realm, username);
// After this, you can set the federation link and map attributes.Could you review this logic to ensure new users are correctly provisioned in Keycloak during synchronization?
src/main/java/org/opensingular/dbuserprovider/DBUserStorageResource.java
Outdated
Show resolved
Hide resolved
|
|
||
| public List<Map<String, String>> getAllUsersForSync() { | ||
| log.infov("Getting all users for sync using query: {0}", queryConfigurations.getListAllForSync()); | ||
| return doQuery(queryConfigurations.getListAllForSync(), null, this::readMap); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The getAllUsersForSync method uses queryConfigurations.getListAllForSync() to fetch users. The default value for the listAllForSync configuration property is an empty string (see DBUserStorageProviderFactory.java:184).
If this property is not explicitly configured by the administrator, an empty string will be passed as the SQL query to doQuery. This will likely result in an SQLException when the database attempts to execute an empty query.
Consider adding a check for an empty or null listAllForSync query. If it's not configured, should it perhaps fall back to queryConfigurations.getListAll() or log a clear error message and return an empty list if synchronization is enabled but the required query is missing?
src/test/java/org/opensingular/dbuserprovider/DBUserStorageProviderTest.java
Outdated
Show resolved
Hide resolved
This commit introduces several enhancements to the Keycloak DB User Storage Provider:
1. **User Synchronization:**
- The provider can now periodically synchronize users from the external database into Keycloak.
- A new configuration option `syncEnabled` controls this feature.
- Another configuration `listAllForSync` allows specifying a custom query for fetching all users during synchronization.
- Synchronized users have a federation link set, associating them with this provider instance.
- User attributes (username, email, firstName, lastName) are mapped during synchronization.
- The `allowDatabaseToOverwriteKeycloak` flag controls if existing Keycloak user attributes are updated from the database.
2. **User Unlinking:**
- Users can now be unlinked from the federation provider.
- A new REST API endpoint `POST /db-user-storage/{componentId}/users/{userId}/unlink` is available for administrators to trigger unlinking.
- Unlinking removes the federation link from the user, allowing them to be managed locally within Keycloak (e.g., set a new password).
3. **Password Management for Unlinked Users:**
- Unlinked users can now set and update their passwords directly within Keycloak. The provider delegates password updates for such users to Keycloak's local storage.
4. **Keycloak Version Update:**
- The project's Keycloak dependency has been updated to version 25.0.0.
5. **Testing:**
- Unit tests have been added for the new synchronization, unlinking, and credential update logic in `DBUserStorageProvider`.
- Unit tests have been added for the new `DBUserStorageResource` JAX-RS endpoint.
These changes address your requirements by enabling user provisioning into Keycloak from the external database and providing a mechanism to decouple users from the external federation, preventing data loss if the provider is removed and allowing local credential management.
tests still failing.
There was a problem hiding this 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 full user synchronization support to the DB user storage provider, including a new REST endpoint, configuration flags, and test coverage.
- Introduces
syncEnabledandlistAllForSyncinQueryConfigurationsandUserRepository.getAllUsersForSync(). - Implements
syncandsyncSinceinDBUserStorageProviderand exposes a/syncendpoint inDBUserStorageResource. - Updates factories to pass new sync settings and adds unit tests for the sync flows.
Reviewed Changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/java/org/opensingular/dbuserprovider/model/QueryConfigurations.java | Added syncEnabled and listAllForSync fields and accessors |
| src/main/java/org/opensingular/dbuserprovider/persistence/UserRepository.java | Added getAllUsersForSync() for full user list |
| src/main/java/org/opensingular/dbuserprovider/DBUserStorageProvider.java | Implemented sync, syncSince, and user unlinking logic |
| src/main/java/org/opensingular/dbuserprovider/DBUserStorageResource.java | Added POST /sync endpoint for triggering import sync |
| src/main/java/org/opensingular/dbuserprovider/DBUserStorageProviderFactory.java | Updated factory to configure new sync flags |
| src/main/java/org/opensingular/dbuserprovider/DBUserStorageResourceProviderFactory.java | Registered the new resource provider |
| src/test/java/org/opensingular/dbuserprovider/DBUserStorageProviderTest.java | Added tests for full sync and disabled-sync scenarios |
| src/test/java/org/opensingular/dbuserprovider/DBUserStorageResourceTest.java | Added tests for the REST sync endpoint |
| src/test/java/org/opensingular/dbuserprovider/util/TestUtils.java | Introduced Mockito helper methods |
| pom.xml | Added Mockito dependencies and Surefire plugin for logging config |
| src/test/resources/logging.properties | Configured console logging for tests |
Comments suppressed due to low confidence (3)
src/main/java/org/opensingular/dbuserprovider/DBUserStorageProvider.java:341
- The new
syncSincemethod isn’t covered by any unit tests. Consider adding tests to verify its behavior (especially that it delegates correctly tosync).
public SynchronizationResult syncSince(Date lastSync, KeycloakSessionFactory sessionFactory, String realmId, UserStorageProviderModel model) {
src/main/java/org/opensingular/dbuserprovider/DBUserStorageProviderFactory.java:38
- The
DBUserStorageProviderFactoryclass invokeslog.info(...)without defining or generating alogfield. Add a logger annotation (e.g.,@JBossLog) or declare a logger instance to avoid a compilation error.
log.info("Initializing DBUserStorageProviderFactory");
src/main/java/org/opensingular/dbuserprovider/persistence/UserRepository.java:211
- The call to
log.infov(...)assumes alogfield exists. Ensure the class defines a logger (e.g., via@JBossLogor a manual logger declaration) before usinglog.
log.infov("Getting all users for sync using query: {0}", queryConfigurations.getListAllForSync());
src/main/java/org/opensingular/dbuserprovider/DBUserStorageProvider.java
Outdated
Show resolved
Hide resolved
src/main/java/org/opensingular/dbuserprovider/DBUserStorageResource.java
Outdated
Show resolved
Hide resolved
…vider and update resource provider for improved functionality
…UserStorageResource and DBUserStorageProviderTest
…vider.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ssion in DBUserStorageProvider; adjust test cases accordingly
…ethod Fix realm retrieval in sync
This commit addresses several critical issues found during code review and significantly improves test coverage and reliability. ## Critical Bug Fixes - **Session Management**: Fixed session handling in sync operations with proper try-finally blocks and error handling - **Sync Logic**: Corrected sync method to check Keycloak user store instead of database when determining if users exist - **Duplicate Code**: Removed duplicate null check in addUser method - **Credential Validation**: Uncommented and fixed critical credential validation tests ## Enhanced Error Handling - Added comprehensive exception handling throughout sync operations - Improved logging with detailed error messages for troubleshooting - Enhanced input validation for user data from database - Proper session cleanup in all code paths ## Test Coverage Improvements - Fixed and enabled credential validation tests (valid, invalid, unsupported types) - Added comprehensive resource endpoint tests (8 new scenarios) - Enhanced session factory mocking for better test isolation - Improved test setup with proper dependency injection patterns - Total test coverage: 24 passing tests ## Code Quality Enhancements - Better separation of concerns in sync operations - Consistent error handling patterns - Detailed logging for sync operations with result summaries - Improved code readability and maintainability ## Documentation - Added comprehensive CLAUDE.md with project architecture and development guidance - Documented sync feature implementation and configuration options
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces user synchronization capabilities into the DB user storage provider and enhances the associated configuration and testing.
- Added sync methods to DBUserStorageProvider implementing ImportSynchronization.
- Updated provider factory and configuration to support new sync parameters (syncEnabled, listAllForSync).
- Extended tests and documentation to cover synchronization scenarios.
Reviewed Changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker | Added Mockito inline mock configuration. |
| src/test/resources/logging.properties | Configured logging levels for tests. |
| src/test/java/org/opensingular/dbuserprovider/util/TestUtils.java | Introduced simple Mockito test utilities. |
| src/test/java/org/opensingular/dbuserprovider/mocks/MockDataSourceProvider.java | Added a mock data source provider for testing. |
| src/test/java/org/opensingular/dbuserprovider/DBUserStorageResourceTest.java | Added tests for sync endpoints and error scenarios. |
| src/test/java/org/opensingular/dbuserprovider/DBUserStorageProviderTest.java | Expanded tests to cover sync, credential validation, and unlinking. |
| src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory | Registered resource provider factory. |
| src/main/java/org/opensingular/dbuserprovider/DBUserStorageResource.java | Implemented REST endpoints for synchronization. |
| src/main/java/org/opensingular/dbuserprovider/DBUserStorageProviderFactory.java | Extended factory to support sync configurations. |
| src/main/java/org/opensingular/dbuserprovider/DBUserStorageProvider.java | Extended provider to implement ImportSynchronization and added sync logic. |
| pom.xml, CLAUDE.md, Devcontainer files | Updated dependencies and documentation to support new features. |
Comments suppressed due to low confidence (1)
src/main/java/org/opensingular/dbuserprovider/DBUserStorageProviderFactory.java:179
- The 'listAllForSync' SQL query property currently defaults to an empty string. Consider providing a meaningful default query or adding documentation indicating that it must be explicitly set when synchronization is enabled.
.property().name("listAllForSync")
| for (Map<String, String> dbUserMap : usersFromDb) { | ||
| try { | ||
| String username = dbUserMap.get("username"); | ||
| if (username == null || username.trim().isEmpty()) { | ||
| log.warnv("User from DB is missing or empty username: {0}", dbUserMap); | ||
| result.increaseFailed(); | ||
| continue; | ||
| } | ||
|
|
||
| UserModel keycloakUser = syncSession.users().getUserByUsername(realm, username); | ||
|
|
||
| if (keycloakUser == null) { |
Copilot
AI
Jun 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] If the external database can contain a large number of users, consider processing synchronization in batches or implementing parallel processing to improve performance.
| for (Map<String, String> dbUserMap : usersFromDb) { | |
| try { | |
| String username = dbUserMap.get("username"); | |
| if (username == null || username.trim().isEmpty()) { | |
| log.warnv("User from DB is missing or empty username: {0}", dbUserMap); | |
| result.increaseFailed(); | |
| continue; | |
| } | |
| UserModel keycloakUser = syncSession.users().getUserByUsername(realm, username); | |
| if (keycloakUser == null) { | |
| List<List<Map<String, String>>> batches = PagingUtil.createBatches(usersFromDb, BATCH_SIZE); | |
| for (List<Map<String, String>> batch : batches) { | |
| batch.parallelStream().forEach(dbUserMap -> { | |
| try { | |
| String username = dbUserMap.get("username"); | |
| if (username == null || username.trim().isEmpty()) { | |
| log.warnv("User from DB is missing or empty username: {0}", dbUserMap); | |
| result.increaseFailed(); | |
| return; | |
| } | |
| UserModel keycloakUser = syncSession.users().getUserByUsername(realm, username); | |
| if (keycloakUser == null) { |
…inking
- Add syncPasswords configuration option to enable password hash synchronization
- Implement syncUserPassword() method to copy BCrypt hashes from external DB to Keycloak
- Add listAllForSyncWithPasswords query configuration for retrieving users with password hashes
- Update UserRepository with getAllUsersForSyncWithPasswords() method
- Fix Keycloak 26 credential management API compatibility:
- Use user.credentialManager() instead of session.userCredentialManager()
- Use removeStoredCredentialById() instead of removeStoredCredential()
- Use correct PasswordCredentialModel.createFromValues() signature
- Add comprehensive test coverage for password sync functionality
- Update documentation in CLAUDE.md
This feature allows users to continue authenticating with Keycloak after the
federation link is removed by syncing password hashes during user synchronization.
Only BCrypt password hashes are supported. The feature is disabled by default.
- Remove unsafe cast from ComponentModel to UserStorageProviderModel in validateConfiguration - Add configureFromComponentModel method to handle ComponentModel directly - Add getBooleanConfig helper for proper boolean value parsing from ComponentModel - Fixes validation errors preventing provider configuration in Keycloak 26 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
No description provided.