Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"permissions": {
"allow": [
"Bash(git add:*)",
"WebFetch(domain:www.keycloak.org)",
"Bash(mvn test:*)"
],
"deny": []
}
}
37 changes: 37 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Use the official Microsoft Java 21 dev container image as a base
FROM mcr.microsoft.com/devcontainers/java:1-21-bullseye

# Arguments for Maven version - can be overridden in devcontainer.json
ARG MAVEN_VERSION=3.9.10
ARG USER_HOME_DIR=/home/vscode # Default for vscode user in Microsoft devcontainer images
ARG SHA=6e9da326bd371b26a4a25693a62996309a81897aac0a5390c43e994d55018d8817d458971401b071779096910070700218b05085c900e6803631637177100bf3 # SHA512 for Maven 3.9.6 binary tar.gz

# Install necessary tools like wget, ca-certificates for downloading, and then Maven
USER root
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends \
wget \
ca-certificates \
# Add any other system dependencies you might need
# Download and install Maven
&& mkdir -p /usr/share/maven /usr/share/maven/ref \
&& echo "Downloading Maven ${MAVEN_VERSION}" \
&& wget -q -O /tmp/apache-maven.tar.gz "https://dlcdn.apache.org/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" \
# Verify checksum (optional but good practice, get SHA from Apache Maven website)
# && echo "${SHA} */tmp/apache-maven.tar.gz" | sha512sum -c - \
&& tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven --strip-components=1 \
&& rm -f /tmp/apache-maven.tar.gz \
&& ln -s /usr/share/maven/bin/mvn /usr/bin/mvn \
# Clean up apt lists
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
USER vscode

# Set Maven environment variables
ENV MAVEN_HOME /usr/share/maven
ENV MAVEN_CONFIG "${USER_HOME_DIR}/.m2"
# Add MAVEN_HOME/bin to PATH (though ln -s should make mvn globally available)
ENV PATH="${MAVEN_HOME}/bin:${PATH}"

# You can add more customizations here, like creating a .m2 directory or settings.xml
# RUN mkdir -p ${USER_HOME_DIR}/.m2
35 changes: 35 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "Keycloak 24 Dev Environment",
"build": {
"dockerfile": "Dockerfile",
// You can pass arguments to your Dockerfile if needed
"args": {
"MAVEN_VERSION": "3.9.10"
}
},
"customizations": {
"vscode": {
"settings": {
// The Java path should remain correct as the base image provides it
"java.jdt.ls.java.home": "/usr/lib/jvm/msopenjdk-21-amd64",
"java.configuration.runtimes": [
{
"name": "JavaSE-21",
"path": "/usr/lib/jvm/msopenjdk-21-amd64",
"default": true
}
]
},
"extensions": [
"vscjava.vscode-java-pack",
"redhat.java"
]
}
},
"forwardPorts": [
8080, // Keycloak HTTP
8443 // Keycloak HTTPS
],
"postCreateCommand": "java -version && mvn --version",
"remoteUser": "vscode"
}
133 changes: 133 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is a Keycloak User Storage Provider (SPI) that enables Keycloak 17+ (Quarkus-based) to authenticate users against external relational databases. It supports PostgreSQL, MySQL, Oracle, and SQL Server, allowing existing applications to integrate with Keycloak without migrating user data.

## Essential Commands

### Build and Development
```bash
# Full build with tests
mvn clean package

# Run tests only
mvn test

# Build without tests
mvn clean package -DskipTests

# Compile only
mvn compile
```

### Deployment
```bash
# Deploy to local Keycloak installation
./deployment.sh

# Deploy to Docker container
./deployment-to-docker.sh

# Manual deployment
mvn clean package
cp ./dist/* <keycloak_root_dir>/providers
```

### Testing
```bash
# Run specific test
mvn test -Dtest=DBUserStorageProviderTest

# Run all tests
mvn surefire:test
```

## High-Level Architecture

### Core Components

**DBUserStorageProviderFactory** - Entry point using factory pattern
- Manages provider lifecycle and configuration
- Caches provider configurations per instance
- Defines all configurable properties

**DBUserStorageProvider** - Main provider implementing multiple Keycloak SPIs
- `UserLookupProvider`: User lookup by ID/username/email
- `CredentialInputValidator`: Password validation against external DB
- `ImportSynchronization`: User synchronization capabilities
- `UserQueryProvider`: User search and listing

**UserAdapter** - Bridges external DB users to Keycloak's UserModel
- Extends `AbstractUserAdapterFederatedStorage`
- Maps database columns to Keycloak attributes
- Supports attribute preservation vs. overwrite modes

**UserRepository** - Data access layer with JDBC + HikariCP
- Handles all database operations with prepared statements
- Supports multiple password hash algorithms (BCrypt, Argon2, PBKDF2-SHA256)
- Manages connection pooling and database-specific queries

### Key Design Patterns

1. **Factory Pattern with Configuration Caching** - Avoids repeated config parsing per instance
2. **Lazy Loading** - Users loaded on-demand from external database
3. **Flexible Query Configuration** - All SQL queries customizable through Keycloak admin UI
4. **Multi-Database Abstraction** - Supports 4 database types through RDBMS enum

### Data Flow
User Request → DBUserStorageProvider → UserRepository → External Database → UserAdapter → Keycloak

## Project Structure

```
src/main/java/org/opensingular/dbuserprovider/
├── model/ # UserAdapter, QueryConfigurations
├── persistence/ # DataSourceProvider, UserRepository, RDBMS
└── util/ # Password hashing, paging, SQL helpers

src/test/java/
├── mocks/ # MockDataSourceProvider
└── util/ # TestUtils
```

## Important Configuration

- **Java Version**: Java 17 (source and target)
- **Keycloak Version**: 26.2.0
- **Build Output**: `dist/` directory contains JAR and dependencies
- **Test Framework**: JUnit 4.13.2 with Mockito 5.11.0

## Password Synchronization Feature

The provider supports synchronizing password hashes from the external database to Keycloak's user store, enabling users to authenticate against Keycloak even after federation unlinking.

### Configuration Options

- **Sync Passwords**: Enable copying password hashes during user synchronization
- **Sync Query with Passwords**: Custom SQL query that includes `password_hash` column
- **Supported Hash Format**: BCrypt (Blowfish) with `$2a$`, `$2b$`, or `$2y$` prefixes

### Use Cases

1. **Federation Migration**: Gradually migrate users from external authentication to Keycloak
2. **Backup Authentication**: Provide fallback authentication when external system is unavailable
3. **User Unlinking**: Allow users to continue authenticating after removing federation link

### Security Considerations

- Password hashes are copied, not plain-text passwords
- Only BCrypt format is supported for security
- Existing Keycloak credentials are replaced during sync
- Database access controls should protect password hash columns

## Development Notes

- Uses Google AutoService for automatic SPI registration
- Connection pooling via HikariCP for database efficiency
- Supports both user sync and on-demand loading
- Read-mostly pattern - primarily sources from external DB
- All SQL queries are configurable per deployment
- Password hashing supports multiple algorithms for compatibility
Binary file added dist/byte-buddy-agent-1.14.12.jar
Binary file not shown.
Binary file added dist/mockito-core-5.11.0.jar
Binary file not shown.
Binary file added dist/objenesis-3.3.jar
Binary file not shown.
Binary file added dist/postgresql-42.7.2.jar
Binary file not shown.
Binary file modified dist/singular-user-storage-provider.jar
Binary file not shown.
42 changes: 42 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
<version>4.13.2</version>
<scope>test</scope>
</dependency>


<dependency>
<groupId>org.hibernate</groupId>
Expand All @@ -157,6 +158,34 @@
<version>2.11</version>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.1.1</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>3.15.1.Final</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>

</dependencies>


Expand Down Expand Up @@ -196,6 +225,19 @@
<outputDirectory>${project.basedir}/dist</outputDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
<systemPropertyVariables>
<java.util.logging.config.file>src/test/resources/logging.properties</java.util.logging.config.file>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>

Expand Down
Loading