Skip to content

Instantly share code, notes, and snippets.

@thomasdarimont
Created July 16, 2020 16:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thomasdarimont/26143644d2566704f597270783b4146a to your computer and use it in GitHub Desktop.
Save thomasdarimont/26143644d2566704f597270783b4146a to your computer and use it in GitHub Desktop.
PoC for exposing custom attributes and error messages in Keycloak login form
package demo.userstorage.demouserstorage;
import lombok.extern.jbosslog.JBossLog;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.AuthenticationFlowException;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialInputUpdater;
import org.keycloak.credential.CredentialInputValidator;
import org.keycloak.credential.CredentialModel;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserQueryProvider;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@JBossLog
public class DemoUserStorageProvider implements
UserStorageProvider,
UserLookupProvider,
UserQueryProvider,
CredentialInputUpdater,
CredentialInputValidator {
private final KeycloakSession session;
private final ComponentModel model;
private final UserRepository repository;
public DemoUserStorageProvider(KeycloakSession session, ComponentModel model, UserRepository repository) {
this.session = session;
this.model = model;
this.repository = repository;
}
@Override
public boolean supportsCredentialType(String credentialType) {
return CredentialModel.PASSWORD.equals(credentialType);
}
@Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
return supportsCredentialType(credentialType);
}
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
log.debugv("isValid user credential: userId={0}", user.getId());
if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) {
return false;
}
if (user.getUsername().toLowerCase().startsWith("katie")) {
// TODO hier auf validation execption reagieren
// login form generator beziehen
LoginFormsProvider form = session.getProvider(LoginFormsProvider.class);
// Setzt Fehlermeldung (etweder message key oder Nachricht direkt)
form.setError("Benutzer wurde für 5min gesperrt");
// exponiert attribut in login form ftl template
form.setAttribute("waiting_time_minutes", "5");
HttpRequest httpRequest = ResteasyProviderFactory.getContextData(HttpRequest.class);
form.setActionUri(URI.create(httpRequest.getUri().getRequestUri().toString()));
Response response = form.createLoginUsernamePassword();
throw new AuthenticationFlowException(AuthenticationFlowError.USER_TEMPORARILY_DISABLED, response);
}
UserCredentialModel cred = (UserCredentialModel) input;
return repository.validateCredentials(user.getUsername(), cred.getValue());
}
@Override
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
log.debugv("updating credential: realm={0} user={1}", realm.getId(), user.getUsername());
if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) {
return false;
}
UserCredentialModel cred = (UserCredentialModel) input;
return repository.updateCredentials(user.getUsername(), cred.getValue());
}
@Override
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
}
@Override
public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
return Collections.emptySet();
}
@Override
public void preRemove(RealmModel realm) {
log.debugv("pre-remove realm");
}
@Override
public void preRemove(RealmModel realm, GroupModel group) {
log.debugv("pre-remove group");
}
@Override
public void preRemove(RealmModel realm, RoleModel role) {
log.debugv("pre-remove role");
}
@Override
public void close() {
log.debugv("closing");
}
@Override
public UserModel getUserById(String id, RealmModel realm) {
log.debugv("lookup user by id: realm={0} userId={1}", realm.getId(), id);
String externalId = StorageId.externalId(id);
return new UserAdapter(session, realm, model, repository.findUserById(externalId));
}
@Override
public UserModel getUserByUsername(String username, RealmModel realm) {
log.debugv("lookup user by username: realm={0} username={1}", realm.getId(), username);
return new UserAdapter(session, realm, model, repository.findUserByUsernameOrEmail(username));
}
@Override
public UserModel getUserByEmail(String email, RealmModel realm) {
log.debugv("lookup user by username: realm={0} email={1}", realm.getId(), email);
return getUserByUsername(email, realm);
}
@Override
public int getUsersCount(RealmModel realm) {
return repository.getUsersCount();
}
@Override
public List<UserModel> getUsers(RealmModel realm) {
log.debugv("list users: realm={0}", realm.getId());
return repository.getAllUsers().stream()
.map(user -> new UserAdapter(session, realm, model, user))
.collect(Collectors.toList());
}
@Override
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
log.debugv("list users: realm={0} firstResult={1} maxResults={2}", realm.getId(), firstResult, maxResults);
return getUsers(realm);
}
@Override
public List<UserModel> searchForUser(String search, RealmModel realm) {
log.debugv("search for users: realm={0} search={1}", realm.getId(), search);
return repository.findUsers(search).stream()
.map(user -> new UserAdapter(session, realm, model, user))
.collect(Collectors.toList());
}
@Override
public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
log.debugv("search for users: realm={0} search={1} firstResult={2} maxResults={3}", realm.getId(), search, firstResult, maxResults);
return searchForUser(search, realm);
}
@Override
public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm) {
log.debugv("search for users with params: realm={0} params={1}", realm.getId(), params);
return searchForUser("", realm);
}
@Override
public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm, int firstResult, int maxResults) {
log.debugv("search for users with params: realm={0} params={1} firstResult={2} maxResults={3}", realm.getId(), params, firstResult, maxResults);
return searchForUser("", realm);
}
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
log.debugv("search for group members with params: realm={0} groupId={1} firstResult={2} maxResults={3}", realm.getId(), group.getId(), firstResult, maxResults);
return Collections.emptyList();
}
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
log.debugv("search for group members: realm={0} groupId={1} firstResult={2} maxResults={3}", realm.getId(), group.getId());
return Collections.emptyList();
}
@Override
public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
log.debugv("search for group members: realm={0} attrName={1} attrValue={2}", realm.getId(), attrName, attrValue);
return null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment