Hey Bruno , thanks a ton .. It works !!!
SearchUser works well but getUserByUsername("test") fails with following error , Kindly guide what's that I am doing wrong
-------------------------------CustomUser-------------------------------------------------
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialInputValidator;
import org.keycloak.models.*;
import org.keycloak.models.credential.PasswordCredentialModel;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.adapter.AbstractUserAdapter;
import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserQueryProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CustomUserStorageProvider implements UserStorageProvider,
UserLookupProvider,
CredentialInputValidator,
UserQueryProvider {
private static final Logger
log = LoggerFactory.
getLogger(CustomUserStorageProvider.class);
private KeycloakSession ksession;
private ComponentModel model;
public CustomUserStorageProvider(KeycloakSession ksession, ComponentModel model) {
this.ksession = ksession;
this.model = model;
}
@Override
public void close() {
log.info("[I30] close()");
}
@Override
public UserModel getUserById(RealmModel realm,String id) {
log.info("[I35] getUserById({})",id);
StorageId sid = new StorageId(id);
return getUserByUsername(realm,sid.getExternalId());
}
@Override
public UserModel getUserByUsername( RealmModel realm,String username) {
log.info("[I41] getUserByUsername({})",username);
try ( Connection c = DbUtil.
getConnection(this.model)) {
log.info(" got connection");
PreparedStatement st = c.prepareStatement("select username, firstName,lastName, email, birthDate from users where username = 'test'");
log.info("created");
// st.setString(1, username);
log.info("set string skipped");
st.execute();
log.info("Done ");
ResultSet rs = st.getResultSet();
log.info("ResultSet is set ");
if ( rs.next()) {
log.info("Inside result set");
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) {
log.info("[I48] getUserByEmail({})",email);
try ( Connection c = DbUtil.
getConnection(this.model)) {
PreparedStatement st = c.prepareStatement("select username, firstName,lastName, email, birthDate from users where email = ?");
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) {
log.info("[I57] supportsCredentialType({})",credentialType);
return PasswordCredentialModel.
TYPE.endsWith(credentialType);
}
@Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
log.info("[I57] 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);
}
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput credentialInput) {
log.info("[I57] 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 users where username = ?");
st.setString(1, username);
st.execute();
ResultSet rs = st.getResultSet();
if ( rs.next()) {
String pwd = rs.getString(1);
return pwd.equals(credentialInput.getChallengeResponse());
}
else {
return false;
}
}
catch(SQLException ex) {
throw new RuntimeException("Database error:" + ex.getMessage(),ex);
}
}
// UserQueryProvider implementation
@Override
public int getUsersCount(RealmModel realm) {
int i =1 ;
log.info("[I93] getUsersCount: realm={}", realm.getName() );
// try ( Connection c = DbUtil.getConnection(this.model)) {
// Statement st = c.createStatement();
// st.execute("select count(*) from users");
// ResultSet rs = st.getResultSet();
// rs.next();
// String str= rs.getString(1);
// log.info("Users count "+str);
// log.info("In val of users count "+Integer.valueOf(str));
// return i;
// }
// catch(SQLException ex) {
// throw new RuntimeException("Database error:" + ex.getMessage(),ex);
// }
return i;
}
//looks like this is called for * search
@Override
public Stream<UserModel> searchForUserStream(RealmModel realmModel, String s, Integer firstResult, Integer maxResult) {
log.info("searchForUserStream(RealmModel realmModel, String s, Integer firstResult, Integer maxResult):: with params String s ::" +s
+" firstResult:: "+firstResult+" maxResult :: "+maxResult);
return
convertListToStream(getUsers( realmModel, firstResult,maxResult ));
}
//This is called for search by name , last name etc
@Override
public Stream<UserModel> searchForUserStream(RealmModel realmModel, Map<String, String> map, Integer firstResult, Integer maxResult) {
log.info("searchForUserStream(RealmModel realmModel, Map<String, String> map, Integer firstResult, Integer maxResult) :: with map ::" +map.size()
+ " firstResult:: "+firstResult+" maxResult :: "+maxResult);
return
convertListToStream(searchForUser( map, realmModel));
}
@Override
public Stream<UserModel> getGroupMembersStream(RealmModel realmModel, GroupModel groupModel, Integer integer, Integer integer1) {
return null;
}
@Override
public Stream<UserModel> searchForUserByUserAttributeStream(RealmModel realmModel, String s, String s1) {
return null;
}
// @Override
public List<UserModel> getUsers(RealmModel realm) {
return getUsers(realm,0, 5000);
// Keep a reasonable maxResults
}
// @Override
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
log.info("[I113] getUsers: realm={}", realm.getName());
try ( Connection c = DbUtil.
getConnection(this.model)) {
PreparedStatement st = c.prepareStatement("select username, firstName,lastName, email, birthDate from users order by username ");
// 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;
}
catch(SQLException ex) {
throw new RuntimeException("Database error:" + ex.getMessage(),ex);
}
}
//@Override
public List<UserModel> searchForUser(String search, RealmModel realm) {
return searchForUser(search,realm,0,5000);
}
//@Override
public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
log.info("[I139] searchForUser: realm={}", realm.getName());
try ( Connection c = DbUtil.
getConnection(this.model)) {
PreparedStatement st = c.prepareStatement("select username, firstName,lastName, email, birthDate from users where username like ? order by username limit ? offset ?");
st.setString(1, search);
st.setInt(2, maxResults);
st.setInt(3, firstResult);
st.execute();
ResultSet rs = st.getResultSet();
List<UserModel> users = new ArrayList<>();
while(rs.next()) {
users.add(mapUser(realm,rs));
}
return users;
}
catch(SQLException ex) {
throw new RuntimeException("Database error:" + ex.getMessage(),ex);
}
}
// @Override
public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm) {
return searchForUser(params,realm,0,5000);
}
//@Override
public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm, int firstResult, int maxResults) {
return getUsers(realm, firstResult, maxResults);
}
// @Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
return Collections.
emptyList();
}
//@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
return Collections.
emptyList();
}
//@Override
public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
return Collections.
emptyList();
}
//------------------- Implementation
private UserModel mapUser(RealmModel realm, ResultSet rs) throws SQLException {
log.info(" mapUser(RealmModel realm, ResultSet rs) :: username ::"+rs.getString("username")
+"firstName ::"+rs.getString("firstName")+" lastName :: "+rs.getString("lastName")+" birthDate:: "+(rs.getDate("birthDate")));
DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
CustomUser user = new CustomUser.Builder(ksession, realm, model, rs.getString("username"))
.email(rs.getString("email"))
.firstName(rs.getString("firstName"))
.lastName(rs.getString("lastName"))
.birthDate(rs.getDate("birthDate"))
.build();
return user;
}
// Generic function to convert a list to stream
private static <T> Stream<T> convertListToStream(List<T> list)
{
return list.stream();
}
private UserModel createAdapter(RealmModel realm, String username) {
System.
out.println("Called createAdapter");
return new AbstractUserAdapter(ksession, realm, model) {
@Override
public String getUsername() {
return username;
}
@Override
public SubjectCredentialManager credentialManager() {
return null;
}
};
}
}
package com.cgi.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 com.cgi.auth.provider.user.CustomUserStorageProviderConstants.*;
public class CustomUserStorageProviderFactory implements UserStorageProviderFactory<CustomUserStorageProvider> {
private static final Logger
log = LoggerFactory.
getLogger(CustomUserStorageProviderFactory.class);
protected final List<ProviderConfigProperty> configMetadata;
public CustomUserStorageProviderFactory() {
log.info("[I24] CustomUserStorageProviderFactory created");
// Create config metadata
configMetadata = ProviderConfigurationBuilder.
create()
.property()
.name(
CONFIG_KEY_JDBC_DRIVER)
.label("JDBC Driver Class")
.type(ProviderConfigProperty.
STRING_TYPE)
.defaultValue("oracle.jdbc.driver.OracleDriver")
.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:oracle:thin:@10.126.82.82:1532/DEVTPS19")
.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 * from users")
.add()
.build();
}
@Override
public CustomUserStorageProvider create(KeycloakSession ksession, ComponentModel model) {
log.info("[I63] creating new CustomUserStorageProvider");
return new CustomUserStorageProvider(ksession,model);
}
@Override
public String getId() {
log.info("[I69] getId()");
return "custom-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)) {
log.info("[I84] Testing connection..." );
c.createStatement().execute(config.get(
CONFIG_KEY_VALIDATION_QUERY));
log.info("[I92] Connection OK !" );
}
catch(Exception ex) {
log.warn("[W94] 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) {
log.info("[I94] onUpdate()" );
}
@Override
public void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) {
log.info("[I99] onCreate()" );
}
}