diff --git a/pom.xml b/pom.xml index a1960c6..dbe7a1b 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ UTF-8 17 17 - 22.0.0 + 23.0.0 diff --git a/src/main/java/com/github/thomasdarimont/keycloak/auth/RegistrationProfileDomainValidation.java b/src/main/java/com/github/thomasdarimont/keycloak/auth/RegistrationProfileDomainValidation.java index 61eeb0b..75a4b75 100644 --- a/src/main/java/com/github/thomasdarimont/keycloak/auth/RegistrationProfileDomainValidation.java +++ b/src/main/java/com/github/thomasdarimont/keycloak/auth/RegistrationProfileDomainValidation.java @@ -1,20 +1,24 @@ package com.github.thomasdarimont.keycloak.auth; // import org.jboss.logging.Logger; +import org.keycloak.authentication.AuthenticationFlowError; +import org.keycloak.authentication.AuthenticationFlowException; import org.keycloak.authentication.FormAction; import org.keycloak.authentication.ValidationContext; import org.keycloak.authentication.forms.RegistrationPage; -import org.keycloak.authentication.forms.RegistrationProfile; import org.keycloak.events.Details; import org.keycloak.events.Errors; +import org.keycloak.events.EventType; import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.FormMessage; import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.services.messages.Messages; import org.keycloak.services.validation.Validation; +import org.keycloak.userprofile.UserProfile; import jakarta.ws.rs.core.MultivaluedMap; import java.util.Arrays; @@ -32,143 +36,195 @@ import com.google.auto.service.AutoService; @AutoService(FormActionFactory.class) public class RegistrationProfileDomainValidation extends RegistrationUserCreation { - protected static final Logger logger = Logger.getLogger(RegistrationProfileDomainValidation.class); + protected static final Logger logger = Logger.getLogger(RegistrationProfileDomainValidation.class); - protected static final String DEFAULT_DOMAIN_LIST = "example.org"; - protected static final String DOMAIN_LIST_SEPARATOR = "##"; + protected static final String DEFAULT_DOMAIN_LIST = "example.org"; + protected static final String DOMAIN_LIST_SEPARATOR = "##"; - @Override - public boolean isConfigurable() { - return true; - } + @Override + public boolean isConfigurable() { + return true; + } - protected static final boolean globmatches(String text, String glob) { - if (text.length() > 200) { - return false; - } - String rest = null; - int pos = glob.indexOf('*'); - if (pos != -1) { - rest = glob.substring(pos + 1); - glob = glob.substring(0, pos); - } + protected static final boolean globmatches(String text, String glob) { + if (text.length() > 200) { + return false; + } + String rest = null; + int pos = glob.indexOf('*'); + if (pos != -1) { + rest = glob.substring(pos + 1); + glob = glob.substring(0, pos); + } + if (glob.length() > text.length()) + return false; + // handle the part up to the first * + for (int i = 0; i < glob.length(); i++) + if (glob.charAt(i) != '?' + && !glob.substring(i, i + 1).equalsIgnoreCase(text.substring(i, i + 1))) + return false; - if (glob.length() > text.length()) - return false; - - // handle the part up to the first * - for (int i = 0; i < glob.length(); i++) - if (glob.charAt(i) != '?' - && !glob.substring(i, i + 1).equalsIgnoreCase(text.substring(i, i + 1))) - return false; - - // recurse for the part after the first *, if any - if (rest == null) { - return glob.length() == text.length(); - } else { - for (int i = glob.length(); i <= text.length(); i++) { - if (globmatches(text.substring(i), rest)) - return true; - } - return false; - } - } - - @Override - public void validate(ValidationContext context) { - MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); - - List errors = new ArrayList<>(); - String email = formData.getFirst(Validation.FIELD_EMAIL); - - AuthenticatorConfigModel mailDomainConfig = context.getAuthenticatorConfig(); - String eventError = Errors.INVALID_REGISTRATION; - - if(email == null){ - context.getEvent().detail(Details.EMAIL, email); - errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.INVALID_EMAIL)); - context.error(eventError); - context.validationError(formData, errors); - return; - } - - String[] domainList = getDomainList(mailDomainConfig); - - boolean emailDomainValid = isEmailValid(email, domainList); - - if (!emailDomainValid) { - super.success(context); - KeycloakSession session = context.getSession(); - RealmModel realm = context.getRealm(); - UserModel user = context.getUser(); - user.addRequiredAction("USER_MUST_BE_APPROVED"); - setRequiredActions(session, realm, user); - - } - if (errors.size() > 0) { - context.error(eventError); - context.validationError(formData, errors); - } else { - context.success(); - } - } - - public String[] getDomainList(AuthenticatorConfigModel mailDomainConfig) { - return mailDomainConfig.getConfig().getOrDefault(domainListConfigName, DEFAULT_DOMAIN_LIST).split(DOMAIN_LIST_SEPARATOR); - } - - public boolean isEmailValid(String email, String[] domains) { - for (String domain : domains) { - if (email.endsWith("@" + domain) || email.equals(domain) || globmatches(email, "*@" + domain)) { - return true; - } + // recurse for the part after the first *, if any + if (rest == null) { + return glob.length() == text.length(); + } else { + for (int i = glob.length(); i <= text.length(); i++) { + if (globmatches(text.substring(i), rest)) + return true; } return false; - } + } + } + + @Override + public void success(FormContext context) { + + if (context.getUser() != null) { + // the user probably did some back navigation in the browser, hitting this page in a strange state + context.getEvent().detail(Details.EXISTING_USER, context.getUser().getUsername()); + throw new AuthenticationFlowException(AuthenticationFlowError.GENERIC_AUTHENTICATION_ERROR, Errors.DIFFERENT_USER_AUTHENTICATING, Messages.EXPIRED_ACTION); + } + + MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); + + String email = formData.getFirst(UserModel.EMAIL); + String username = formData.getFirst(UserModel.USERNAME); + + if (context.getRealm().isRegistrationEmailAsUsername()) { + username = email; + } + // get the allowlist of mail domains + AuthenticatorConfigModel mailDomainConfig = context.getAuthenticatorConfig(); + String eventError = Errors.INVALID_REGISTRATION; + + String[] domainList = getDomainList(mailDomainConfig); + + boolean emailDomainValid = isEmailValid(email, domainList); + + context.getEvent().detail(Details.USERNAME, username).detail(Details.REGISTER_METHOD, "form").detail(Details.EMAIL, email); + + UserProfile profile = getOrCreateUserProfile(context, formData); + UserModel user = profile.create(); + if (!emailDomainValid) + user.addRequiredAction("USER_MUST_BE_APPROVED"); + + user.setEnabled(true); + + context.setUser(user); + + if (!emailDomainValid) { + user.addRequiredAction("USER_MUST_BE_APPROVED"); + } + context.getAuthenticationSession().setClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, username); + + context.getEvent().user(user); + context.getEvent().success(); + context.newEvent().event(EventType.LOGIN); + context.getEvent().client(context.getAuthenticationSession().getClient().getClientId()) + .detail(Details.REDIRECT_URI, context.getAuthenticationSession().getRedirectUri()) + .detail(Details.AUTH_METHOD, context.getAuthenticationSession().getProtocol()); + String authType = context.getAuthenticationSession().getAuthNote(Details.AUTH_TYPE); + if (authType != null) { + context.getEvent().detail(Details.AUTH_TYPE, authType); + } +} + +/* @Override + public void validate(ValidationContext context) { + MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); + + List errors = new ArrayList<>(); + String email = formData.getFirst(Validation.FIELD_EMAIL); + + AuthenticatorConfigModel mailDomainConfig = context.getAuthenticatorConfig(); + String eventError = Errors.INVALID_REGISTRATION; + + if(email == null){ + context.getEvent().detail(Details.EMAIL, email); + errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.INVALID_EMAIL)); + context.error(eventError); + context.validationError(formData, errors); + return; + } + + String[] domainList = getDomainList(mailDomainConfig); + + boolean emailDomainValid = isEmailValid(email, domainList); + + if (!emailDomainValid) { + super.success(context); + KeycloakSession session = context.getSession(); + RealmModel realm = context.getRealm(); + UserModel user = context.getUser(); + user.addRequiredAction("USER_MUST_BE_APPROVED"); + setRequiredActions(session, realm, user); + + } + if (errors.size() > 0) { + context.error(eventError); + context.validationError(formData, errors); + } else { + context.success(); + } + */ + + + public String[] getDomainList(AuthenticatorConfigModel mailDomainConfig) { + return mailDomainConfig.getConfig().getOrDefault(domainListConfigName, DEFAULT_DOMAIN_LIST).split(DOMAIN_LIST_SEPARATOR); + } + + public boolean isEmailValid(String email, String[] domains) { + for (String domain : domains) { + if (email.endsWith("@" + domain) || email.equals(domain) || globmatches(email, "*@" + domain)) { + return true; + } + } + return false; + } - public static final String PROVIDER_ID = "registration-mail-check-action"; + public static final String PROVIDER_ID = "registration-mail-check-action"; - private static final List CONFIG_PROPERTIES = new ArrayList<>(); + private static final List CONFIG_PROPERTIES = new ArrayList<>(); - public static String domainListConfigName = "validDomains"; + public static String domainListConfigName = "validDomains"; - static { - ProviderConfigProperty property; - property = new ProviderConfigProperty(); - property.setName(domainListConfigName); - property.setLabel("Valid domains for emails"); - property.setType(ProviderConfigProperty.STRING_TYPE); - property.setHelpText("List mail domains authorized to register, separated by '##'"); - CONFIG_PROPERTIES.add(property); - } + static { + ProviderConfigProperty property; + property = new ProviderConfigProperty(); + property.setName(domainListConfigName); + property.setLabel("Valid domains for emails"); + property.setType(ProviderConfigProperty.STRING_TYPE); + property.setHelpText("List mail domains authorized to register, separated by '##'"); + CONFIG_PROPERTIES.add(property); + } - @Override - public String getDisplayType() { - return "Profile Validation with email domain check"; - } + @Override + public String getDisplayType() { + return "Profile Validation with email domain check"; + } - @Override - public String getId() { - return PROVIDER_ID; - } + @Override + public String getId() { + return PROVIDER_ID; + } - @Override - public String getHelpText() { - return "Adds validation of domain emails for registration"; - } + @Override + public String getHelpText() { + return "Adds validation of domain emails for registration"; + } - @Override - public List getConfigProperties() { - return CONFIG_PROPERTIES; - } + @Override + public List getConfigProperties() { + return CONFIG_PROPERTIES; + } - @Override - public void buildPage(FormContext context, LoginFormsProvider form) { - List authorizedMailDomains = Arrays.asList( - context.getAuthenticatorConfig().getConfig().getOrDefault(domainListConfigName,DEFAULT_DOMAIN_LIST).split(DOMAIN_LIST_SEPARATOR)); + @Override + public void buildPage(FormContext context, LoginFormsProvider form) { + List authorizedMailDomains = Arrays.asList( + context.getAuthenticatorConfig().getConfig().getOrDefault(domainListConfigName,DEFAULT_DOMAIN_LIST).split(DOMAIN_LIST_SEPARATOR)); form.setAttribute("authorizedMailDomains", authorizedMailDomains); - } + } }