Initial implementation (close #1)
This commit is contained in:
parent
5bd690e5b1
commit
ae3fdb775f
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"java.configuration.updateBuildConfiguration": "interactive"
|
||||
}
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 gcg
|
||||
Copyright (c) 2023 Georg-Cantor-Gymnasium Halle (Saale)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
74
pom.xml
Normal file
74
pom.xml
Normal file
@ -0,0 +1,74 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<name>Keycloak mailcow</name>
|
||||
<description />
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>de.cantorgymnasium</groupId>
|
||||
<artifactId>keycloak-mailcow</artifactId>
|
||||
<version>0.0.2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<version.keycloak>23.0.1</version.keycloak>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
<version>${version.keycloak}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi</artifactId>
|
||||
<scope>provided</scope>
|
||||
<version>${version.keycloak}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-legacy</artifactId>
|
||||
<scope>provided</scope>
|
||||
<version>${version.keycloak}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi-private</artifactId>
|
||||
<scope>provided</scope>
|
||||
<version>${version.keycloak}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-services</artifactId>
|
||||
<scope>provided</scope>
|
||||
<version>${version.keycloak}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-crypto</artifactId>
|
||||
<version>6.2.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -0,0 +1,25 @@
|
||||
package de.cantorgymnasium.auth.provider.user;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
|
||||
import static de.cantorgymnasium.auth.provider.user.mailcowUserStorageProviderConstants.*;
|
||||
|
||||
public class DbUtil {
|
||||
public static Connection getConnection(ComponentModel config) throws SQLException {
|
||||
String driverClass = config.get(CONFIG_KEY_JDBC_DRIVER);
|
||||
try {
|
||||
Class.forName(driverClass);
|
||||
} catch (ClassNotFoundException nfe) {
|
||||
throw new RuntimeException(
|
||||
"Invalid JDBC driver: " + driverClass + ". Please check if your driver if properly installed");
|
||||
}
|
||||
|
||||
return DriverManager.getConnection(config.get(CONFIG_KEY_JDBC_URL),
|
||||
config.get(CONFIG_KEY_DB_USERNAME),
|
||||
config.get(CONFIG_KEY_DB_PASSWORD));
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
package de.cantorgymnasium.auth.provider.user;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.credential.LegacyUserCredentialManager;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.SubjectCredentialManager;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.storage.adapter.AbstractUserAdapter;
|
||||
|
||||
class mailcowUser extends AbstractUserAdapter {
|
||||
|
||||
private final String username;
|
||||
private final String email;
|
||||
private final String firstName;
|
||||
private final String lastName;
|
||||
private final String domain;
|
||||
|
||||
private mailcowUser(KeycloakSession session, RealmModel realm,
|
||||
ComponentModel storageProviderModel,
|
||||
String username,
|
||||
String email,
|
||||
String firstName,
|
||||
String lastName,
|
||||
String domain) {
|
||||
super(session, realm, storageProviderModel);
|
||||
this.username = username;
|
||||
this.email = email;
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.domain = domain;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
|
||||
attributes.add(UserModel.USERNAME, getUsername());
|
||||
attributes.add(UserModel.EMAIL, getEmail());
|
||||
attributes.add(UserModel.FIRST_NAME, getFirstName());
|
||||
attributes.add(UserModel.LAST_NAME, getLastName());
|
||||
attributes.add("domain", getDomain());
|
||||
return attributes;
|
||||
}
|
||||
|
||||
static class Builder {
|
||||
private final KeycloakSession session;
|
||||
private final RealmModel realm;
|
||||
private final ComponentModel storageProviderModel;
|
||||
private String username;
|
||||
private String email;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String domain;
|
||||
|
||||
Builder(KeycloakSession session, RealmModel realm, ComponentModel storageProviderModel, String username) {
|
||||
this.session = session;
|
||||
this.realm = realm;
|
||||
this.storageProviderModel = storageProviderModel;
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
mailcowUser.Builder email(String email) {
|
||||
this.email = email;
|
||||
return this;
|
||||
}
|
||||
|
||||
mailcowUser.Builder firstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
return this;
|
||||
}
|
||||
|
||||
mailcowUser.Builder lastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
return this;
|
||||
}
|
||||
|
||||
mailcowUser.Builder domain(String domain) {
|
||||
this.domain = domain;
|
||||
return this;
|
||||
}
|
||||
|
||||
mailcowUser build() {
|
||||
return new mailcowUser(
|
||||
session,
|
||||
realm,
|
||||
storageProviderModel,
|
||||
username,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
domain);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubjectCredentialManager credentialManager() {
|
||||
return new LegacyUserCredentialManager(session, realm, this);
|
||||
}
|
||||
}
|
@ -0,0 +1,282 @@
|
||||
package de.cantorgymnasium.auth.provider.user;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.credential.CredentialInput;
|
||||
import org.keycloak.credential.CredentialInputUpdater;
|
||||
import org.keycloak.credential.CredentialInputValidator;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.credential.PasswordCredentialModel;
|
||||
import org.keycloak.storage.ReadOnlyException;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
|
||||
import org.keycloak.storage.user.UserLookupProvider;
|
||||
import org.keycloak.storage.user.UserQueryProvider;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.crypto.bcrypt.BCrypt;
|
||||
|
||||
public class mailcowUserStorageProvider implements UserStorageProvider,
|
||||
UserLookupProvider,
|
||||
CredentialInputValidator,
|
||||
UserQueryProvider,
|
||||
CredentialInputUpdater {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(mailcowUserStorageProvider.class);
|
||||
private KeycloakSession ksession;
|
||||
private ComponentModel model;
|
||||
|
||||
public mailcowUserStorageProvider(KeycloakSession ksession, ComponentModel model) {
|
||||
this.ksession = ksession;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
logger.info("[mailcow] close()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserById(RealmModel realm, String id) {
|
||||
logger.info("[mailcow] getUserById({})", id);
|
||||
StorageId sid = new StorageId(id);
|
||||
return getUserByUsername(realm, sid.getExternalId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserByUsername(RealmModel realm, String username) {
|
||||
logger.info("[mailcow] getUserByUsername({})", username);
|
||||
try (Connection c = DbUtil.getConnection(this.model)) {
|
||||
PreparedStatement st = c.prepareStatement(
|
||||
"select username, name, `mailbox`.`domain`, local_part FROM `mailbox` INNER JOIN domain on mailbox.domain = domain.domain WHERE `mailbox`.`active` = '1' AND `domain`.`active`='1' AND username = ?");
|
||||
st.setString(1, username);
|
||||
st.execute();
|
||||
ResultSet rs = st.getResultSet();
|
||||
if (rs.next()) {
|
||||
return mapUser(realm, rs);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
throw new RuntimeException("Database error:" + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserByEmail(RealmModel realm, String email) {
|
||||
logger.info("[mailcow] getUserByEmail({})", email);
|
||||
try (Connection c = DbUtil.getConnection(this.model)) {
|
||||
PreparedStatement st = c.prepareStatement(
|
||||
"select username, name, `mailbox`.`domain`, local_part FROM `mailbox` INNER JOIN domain on mailbox.domain = domain.domain WHERE `mailbox`.`active` = '1' AND `domain`.`active`='1' AND username = ?");
|
||||
st.setString(1, email);
|
||||
st.execute();
|
||||
ResultSet rs = st.getResultSet();
|
||||
if (rs.next()) {
|
||||
return mapUser(realm, rs);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
throw new RuntimeException("Database error:" + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCredentialType(String credentialType) {
|
||||
logger.info("[mailcow] supportsCredentialType({})", credentialType);
|
||||
return PasswordCredentialModel.TYPE.endsWith(credentialType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
|
||||
logger.info("[mailcow] isConfiguredFor(realm={},user={},credentialType={})", realm.getName(),
|
||||
user.getUsername(), credentialType);
|
||||
// In our case, password is the only type of credential, so we allways return
|
||||
// 'true' if
|
||||
// this is the credentialType
|
||||
return supportsCredentialType(credentialType);
|
||||
}
|
||||
|
||||
private boolean verifyHash(String hash, String password) {
|
||||
logger.info("[mailcow] verifyHash");
|
||||
Pattern pattern = Pattern.compile("\\{(.+)\\}(.+)");
|
||||
Matcher matcher = pattern.matcher(hash);
|
||||
while (matcher.find()) {
|
||||
if (matcher.group(1).contains("BLF-CRYPT")) {
|
||||
return BCrypt.checkpw(password, matcher.group(2));
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(RealmModel realm, UserModel user, CredentialInput credentialInput) {
|
||||
logger.info("[mailcow] isValid(realm={},user={},credentialInput.type={})", realm.getName(), user.getUsername(),
|
||||
credentialInput.getType());
|
||||
if (!this.supportsCredentialType(credentialInput.getType())) {
|
||||
return false;
|
||||
}
|
||||
StorageId sid = new StorageId(user.getId());
|
||||
String username = sid.getExternalId();
|
||||
|
||||
try (Connection c = DbUtil.getConnection(this.model)) {
|
||||
PreparedStatement st = c.prepareStatement(
|
||||
"SELECT `password` FROM `mailbox` INNER JOIN domain on mailbox.domain = domain.domain WHERE `mailbox`.`active` = '1' AND `domain`.`active`='1' AND `username` = ?");
|
||||
st.setString(1, username);
|
||||
st.execute();
|
||||
ResultSet rs = st.getResultSet();
|
||||
if (rs.next()) {
|
||||
String hash = rs.getString(1);
|
||||
return verifyHash(hash, credentialInput.getChallengeResponse());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
throw new RuntimeException("Database error:" + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
// UserQueryProvider implementation
|
||||
|
||||
@Override
|
||||
public int getUsersCount(RealmModel realm) {
|
||||
logger.info("[mailcow] getUsersCount: realm={}", realm.getName());
|
||||
try (Connection c = DbUtil.getConnection(this.model)) {
|
||||
Statement st = c.createStatement();
|
||||
st.execute(
|
||||
"SELECT count(*) FROM `mailbox` INNER JOIN domain on mailbox.domain = domain.domain WHERE `mailbox`.`active` = '1' AND `domain`.`active`='1'");
|
||||
ResultSet rs = st.getResultSet();
|
||||
rs.next();
|
||||
return rs.getInt(1);
|
||||
} catch (SQLException ex) {
|
||||
throw new RuntimeException("Database error:" + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel realm, GroupModel group, Integer firstResult,
|
||||
Integer maxResults) {
|
||||
logger.info("[mailcow] getUsers: realm={}", realm.getName());
|
||||
|
||||
try (Connection c = DbUtil.getConnection(this.model)) {
|
||||
PreparedStatement st = c.prepareStatement(
|
||||
"select username, name, `mailbox`.`domain`, local_part FROM `mailbox` INNER JOIN domain on mailbox.domain = domain.domain WHERE `mailbox`.`active` = '1' AND `domain`.`active`='1' order by `username` limit ? offset ?");
|
||||
st.setInt(1, maxResults);
|
||||
st.setInt(2, firstResult);
|
||||
st.execute();
|
||||
ResultSet rs = st.getResultSet();
|
||||
logger.info(rs.toString());
|
||||
List<UserModel> users = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
users.add(mapUser(realm, rs));
|
||||
}
|
||||
return users.stream();
|
||||
} catch (SQLException ex) {
|
||||
throw new RuntimeException("Database error:" + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(RealmModel realm, Map<String, String> params, Integer firstResult,
|
||||
Integer maxResults) {
|
||||
String search = params.get(UserModel.SEARCH);
|
||||
|
||||
logger.info("[mailcow] searchForUser: realm={}, search={}", realm.getName(), search);
|
||||
|
||||
try (Connection c = DbUtil.getConnection(this.model)) {
|
||||
PreparedStatement st;
|
||||
if (search != null && !search.isEmpty() && !search.isBlank()) {
|
||||
if (search.contains("*")) {
|
||||
search = "@";
|
||||
}
|
||||
st = c.prepareStatement(
|
||||
"select username, name, `mailbox`.`domain`, local_part FROM `mailbox` INNER JOIN domain on mailbox.domain = domain.domain WHERE `mailbox`.`active` = '1' AND `domain`.`active`='1' AND `username` like ? order by `username` limit ? offset ?");
|
||||
st.setString(1, '%' + search + '%');
|
||||
st.setInt(2, maxResults);
|
||||
st.setInt(3, firstResult);
|
||||
} else {
|
||||
st = c.prepareStatement(
|
||||
"select username, name, `mailbox`.`domain`, local_part FROM `mailbox` INNER JOIN domain on mailbox.domain = domain.domain WHERE `mailbox`.`active` = '1' AND `domain`.`active`='1' order by `username` limit ? offset ?");
|
||||
st.setInt(1, maxResults);
|
||||
st.setInt(2, firstResult);
|
||||
}
|
||||
st.execute();
|
||||
ResultSet rs = st.getResultSet();
|
||||
List<UserModel> users = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
users.add(mapUser(realm, rs));
|
||||
}
|
||||
return users.stream();
|
||||
} catch (SQLException ex) {
|
||||
throw new RuntimeException("Database error:" + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserByUserAttributeStream(RealmModel realm, String attrName, String attrValue) {
|
||||
return getGroupMembersStream(realm, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
||||
if (input.getType().equals(PasswordCredentialModel.TYPE))
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getDisableableCredentialTypesStream(RealmModel realm, UserModel user) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
protected UserModel createAdapter(RealmModel realm, String username) {
|
||||
return new AbstractUserAdapterFederatedStorage(ksession, realm, model) {
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsername(String username) {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'setUsername'");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ------------------- Implementation
|
||||
private UserModel mapUser(RealmModel realm, ResultSet rs) throws SQLException {
|
||||
String[] name = rs.getString("name").trim().split("\\s+");
|
||||
mailcowUser user = new mailcowUser.Builder(ksession, realm, model, rs.getString("username"))
|
||||
.email(rs.getString("username"))
|
||||
.firstName(name.length > 0 ? name[0] : "")
|
||||
.lastName(name.length > 1 ? name[1] : "")
|
||||
.domain(rs.getString("domain"))
|
||||
.build();
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
}
|
9
src/main/java/de/cantorgymnasium/auth/provider/user/mailcowUserStorageProviderConstants.java
Normal file
9
src/main/java/de/cantorgymnasium/auth/provider/user/mailcowUserStorageProviderConstants.java
Normal file
@ -0,0 +1,9 @@
|
||||
package de.cantorgymnasium.auth.provider.user;
|
||||
|
||||
public final class mailcowUserStorageProviderConstants {
|
||||
public static final String CONFIG_KEY_JDBC_DRIVER = "jdbcDriver";
|
||||
public static final String CONFIG_KEY_JDBC_URL = "jdbcUrl";
|
||||
public static final String CONFIG_KEY_DB_USERNAME = "username";
|
||||
public static final String CONFIG_KEY_DB_PASSWORD = "password";
|
||||
public static final String CONFIG_KEY_VALIDATION_QUERY = "validationQuery";
|
||||
}
|
106
src/main/java/de/cantorgymnasium/auth/provider/user/mailcowUserStorageProviderFactory.java
Normal file
106
src/main/java/de/cantorgymnasium/auth/provider/user/mailcowUserStorageProviderFactory.java
Normal file
@ -0,0 +1,106 @@
|
||||
package de.cantorgymnasium.auth.provider.user;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.component.ComponentValidationException;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
import org.keycloak.storage.UserStorageProviderFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static de.cantorgymnasium.auth.provider.user.mailcowUserStorageProviderConstants.*;
|
||||
|
||||
public class mailcowUserStorageProviderFactory implements UserStorageProviderFactory<mailcowUserStorageProvider> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(mailcowUserStorageProviderFactory.class);
|
||||
protected final List<ProviderConfigProperty> configMetadata;
|
||||
|
||||
public mailcowUserStorageProviderFactory() {
|
||||
logger.info("[mailcow] mailcowUserStorageProviderFactory created");
|
||||
|
||||
// Create config metadata
|
||||
configMetadata = ProviderConfigurationBuilder.create()
|
||||
.property()
|
||||
.name(CONFIG_KEY_JDBC_DRIVER)
|
||||
.label("JDBC Driver Class")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.defaultValue("com.mysql.cj.jdbc.Driver")
|
||||
.helpText("Fully qualified class name of the JDBC driver")
|
||||
.add()
|
||||
.property()
|
||||
.name(CONFIG_KEY_JDBC_URL)
|
||||
.label("JDBC URL")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.defaultValue("jdbc:localhost:3306/testdb")
|
||||
.helpText("JDBC URL used to connect to the user database")
|
||||
.add()
|
||||
.property()
|
||||
.name(CONFIG_KEY_DB_USERNAME)
|
||||
.label("Database User")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.helpText("Username used to connect to the database")
|
||||
.add()
|
||||
.property()
|
||||
.name(CONFIG_KEY_DB_PASSWORD)
|
||||
.label("Database Password")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.helpText("Password used to connect to the database")
|
||||
.secret(true)
|
||||
.add()
|
||||
.property()
|
||||
.name(CONFIG_KEY_VALIDATION_QUERY)
|
||||
.label("SQL Validation Query")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.helpText("SQL query used to validate a connection")
|
||||
.defaultValue("select 1")
|
||||
.add()
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public mailcowUserStorageProvider create(KeycloakSession ksession, ComponentModel model) {
|
||||
logger.info("[mailcow] creating new mailcowUserStorageProvider");
|
||||
return new mailcowUserStorageProvider(ksession, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
logger.info("[mailcow] getId()");
|
||||
return "mailcow-user-provider";
|
||||
}
|
||||
|
||||
// Configuration support methods
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configMetadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config)
|
||||
throws ComponentValidationException {
|
||||
|
||||
try (Connection c = DbUtil.getConnection(config)) {
|
||||
logger.info("[mailcow] Testing connection...");
|
||||
c.createStatement().execute(config.get(CONFIG_KEY_VALIDATION_QUERY));
|
||||
logger.info("[mailcow] Connection OK !");
|
||||
} catch (Exception ex) {
|
||||
logger.warn("[mailcow] Unable to validate connection: ex={}", ex.getMessage());
|
||||
throw new ComponentValidationException("Unable to validate database connection", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(KeycloakSession session, RealmModel realm, ComponentModel oldModel, ComponentModel newModel) {
|
||||
logger.info("[mailcow] onUpdate()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) {
|
||||
logger.info("[mailcow] onCreate()");
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
de.cantorgymnasium.auth.provider.user.mailcowUserStorageProviderFactory
|
Loading…
x
Reference in New Issue
Block a user