Compare commits
10 Commits
f64d9c3320
...
119b7b4edd
Author | SHA1 | Date | |
---|---|---|---|
|
119b7b4edd | ||
|
f93e7c7496 | ||
|
4642aa476b | ||
|
df60257603 | ||
|
81514b06a3 | ||
|
a0efd5999e | ||
|
afa1ec202c | ||
|
e118cf5d7b | ||
|
5dee8d05cf | ||
|
cb0296aa56 |
5
.github/workflows/deploy-maven.yml
vendored
5
.github/workflows/deploy-maven.yml
vendored
@ -8,9 +8,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Maven Central Repository
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
server-id: ossrh
|
||||
server-username: MAVEN_USERNAME
|
||||
server-password: MAVEN_PASSWORD
|
||||
|
6
.github/workflows/maven.yml
vendored
6
.github/workflows/maven.yml
vendored
@ -15,10 +15,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 1.8
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@ -11,10 +11,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
|
12
README.md
12
README.md
@ -1,6 +1,6 @@
|
||||
# Keycloak - Whitelist email domain for registration
|
||||
# Keycloak - Email domain validation for registration
|
||||
|
||||
This extension allows you to validate email domain used for registration in keycloak to accept only a finite list of domain.
|
||||
This extension allows you to validate email domain used for registration in keycloak to accept or deny a finite list of domain.
|
||||
|
||||
You can use basic [glob syntax](https://en.wikipedia.org/wiki/Glob_(programming))
|
||||
(only `*` and `?` are supported)
|
||||
@ -23,15 +23,15 @@ The plugin directory is `$KEYCLOAK_HOME\providers`.
|
||||
|
||||
- Go to the admin console, in authentication menu.
|
||||
- Copy the registration flow
|
||||
- add a new execution below "Profile Validation" and choose "Profile Validation With Email Domain Check"
|
||||
- add a new execution below "Profile Validation" and choose "Profile Validation With Email Domain Check" or "Profile Validation with domain block"
|
||||
- Set the execution "Required"
|
||||
- Configure this new execution (otherwise, keycloak will only accept "exemple.org" domains)
|
||||
- Configure this new execution with the allowed or blocked domains, otherwise, keycloak will only accept or block "exemple.org" domains
|
||||
- Change the registration binding to this new flow
|
||||
- Configure the realm to accept registration and verify email (this is important!)
|
||||
|
||||
## Display authorized mail domains in register forms
|
||||
## Display mail domains in register forms
|
||||
|
||||
This extension provides the list of authorized patterns in the `authorizedMailDomains` attribute of the registration page.
|
||||
This extension provides the list of authorized patterns in the `authorizedMailDomains` and `unauthorizedMailDomains` attribute of the registration page.
|
||||
|
||||
This can be used like this :
|
||||
|
||||
|
14
pom.xml
14
pom.xml
@ -2,7 +2,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>net.micedre.keycloak</groupId>
|
||||
<artifactId>keycloak-mail-whitelisting</artifactId>
|
||||
<version>1.6-SNAPSHOT</version>
|
||||
<version>1.9-SNAPSHOT</version>
|
||||
|
||||
<name>Keycloak mail whitelisting extension</name>
|
||||
<description>A keycloak extension to block non authorized domain to register</description>
|
||||
@ -30,9 +30,9 @@
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<keycloak.version>9.0.3</keycloak.version>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<keycloak.version>22.0.0</keycloak.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@ -66,7 +66,7 @@
|
||||
<plugin>
|
||||
<groupId>org.wildfly.plugins</groupId>
|
||||
<artifactId>wildfly-maven-plugin</artifactId>
|
||||
<version>2.0.2.Final</version>
|
||||
<version>3.0.2.Final</version>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
@ -87,7 +87,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<version>3.4.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
@ -128,7 +128,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
|
@ -0,0 +1,100 @@
|
||||
package net.micedre.keycloak.registration;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
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.models.AuthenticatorConfigModel;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class RegistrationProfileDomainValidation extends RegistrationProfile implements FormAction {
|
||||
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 = "##";
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
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<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
|
||||
List<FormMessage> 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) {
|
||||
context.getEvent().detail(Details.EMAIL, email);
|
||||
errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.INVALID_EMAIL));
|
||||
}
|
||||
if (errors.size() > 0) {
|
||||
context.error(eventError);
|
||||
context.validationError(formData, errors);
|
||||
} else {
|
||||
context.success();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract String[] getDomainList(AuthenticatorConfigModel mailDomainConfig);
|
||||
|
||||
public abstract boolean isEmailValid(String email, String[] domains);
|
||||
}
|
||||
|
@ -0,0 +1,72 @@
|
||||
package net.micedre.keycloak.registration;
|
||||
|
||||
import org.keycloak.authentication.FormContext;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.models.AuthenticatorConfigModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class RegistrationProfileWithDomainBlock extends RegistrationProfileDomainValidation {
|
||||
|
||||
public static final String PROVIDER_ID = "registration-domain-block-action";
|
||||
|
||||
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<>();
|
||||
|
||||
public static String domainListConfigName = "invalidDomains";
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName(domainListConfigName);
|
||||
property.setLabel("Invalid domain for emails");
|
||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||
property.setHelpText("List mail domains not authorized to register, separated by '##'");
|
||||
CONFIG_PROPERTIES.add(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Profile Validation with domain block";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Adds validation of not accepted domain emails for registration";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return CONFIG_PROPERTIES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildPage(FormContext context, LoginFormsProvider form) {
|
||||
List<String> unauthorizedMailDomains = Arrays.asList(
|
||||
context.getAuthenticatorConfig().getConfig().getOrDefault(domainListConfigName, DEFAULT_DOMAIN_LIST).split(DOMAIN_LIST_SEPARATOR));
|
||||
form.setAttribute("unauthorizedMailDomains", unauthorizedMailDomains);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDomainList(AuthenticatorConfigModel mailDomainConfig) {
|
||||
return mailDomainConfig.getConfig().getOrDefault(domainListConfigName, DEFAULT_DOMAIN_LIST).split(DOMAIN_LIST_SEPARATOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmailValid(String email, String[] domains) {
|
||||
for (String domain : domains) {
|
||||
if (email.endsWith("@" + domain) || email.equals(domain) || globmatches(email, "*@" + domain)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,151 +1,72 @@
|
||||
package net.micedre.keycloak.registration;
|
||||
|
||||
import org.keycloak.authentication.FormContext;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.models.AuthenticatorConfigModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
|
||||
import org.keycloak.authentication.FormAction;
|
||||
import org.keycloak.authentication.FormContext;
|
||||
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.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.models.AuthenticatorConfigModel;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
|
||||
public class RegistrationProfileWithMailDomainCheck extends RegistrationProfile implements FormAction {
|
||||
public class RegistrationProfileWithMailDomainCheck extends RegistrationProfileDomainValidation {
|
||||
|
||||
public static final String PROVIDER_ID = "registration-mail-check-action";
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Profile Validation with email domain check";
|
||||
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Profile Validation with email domain check";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Adds validation of domain emails for registration";
|
||||
}
|
||||
|
||||
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<ProviderConfigProperty>();
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
property = new ProviderConfigProperty();
|
||||
property.setName("validDomains");
|
||||
property.setLabel("Valid domain for emails");
|
||||
property.setType(ProviderConfigProperty.MULTIVALUED_STRING_TYPE);
|
||||
property.setHelpText("List mail domains authorized to register");
|
||||
CONFIG_PROPERTIES.add(property);
|
||||
}
|
||||
|
||||
|
||||
private 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;
|
||||
|
||||
// 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 List<ProviderConfigProperty> getConfigProperties() {
|
||||
return CONFIG_PROPERTIES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ValidationContext context) {
|
||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
|
||||
List<FormMessage> errors = new ArrayList<>();
|
||||
String email = formData.getFirst(Validation.FIELD_EMAIL);
|
||||
|
||||
boolean emailDomainValid = false;
|
||||
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[] domains = mailDomainConfig.getConfig().getOrDefault("validDomains","exemple.org").split("##");
|
||||
for (String domain : domains) {
|
||||
if (email.endsWith("@" + domain) || email.equals(domain)) {
|
||||
emailDomainValid = true;
|
||||
break;
|
||||
} else if (globmatches(email, "*@" + domain)) {
|
||||
emailDomainValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!emailDomainValid) {
|
||||
context.getEvent().detail(Details.EMAIL, email);
|
||||
errors.add(new FormMessage(RegistrationPage.FIELD_EMAIL, Messages.INVALID_EMAIL));
|
||||
}
|
||||
if (errors.size() > 0) {
|
||||
context.error(eventError);
|
||||
context.validationError(formData, errors);
|
||||
return;
|
||||
|
||||
} else {
|
||||
context.success();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void buildPage(FormContext context, LoginFormsProvider form) {
|
||||
List<String> authorizedMailDomains = Arrays.asList(
|
||||
context.getAuthenticatorConfig().getConfig().getOrDefault("validDomains","exemple.org").split("##"));
|
||||
context.getAuthenticatorConfig().getConfig().getOrDefault(domainListConfigName,DEFAULT_DOMAIN_LIST).split(DOMAIN_LIST_SEPARATOR));
|
||||
form.setAttribute("authorizedMailDomains", authorizedMailDomains);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDomainList(AuthenticatorConfigModel mailDomainConfig) {
|
||||
return mailDomainConfig.getConfig().getOrDefault(domainListConfigName, DEFAULT_DOMAIN_LIST).split(DOMAIN_LIST_SEPARATOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
}
|
@ -1 +1,2 @@
|
||||
net.micedre.keycloak.registration.RegistrationProfileWithMailDomainCheck
|
||||
net.micedre.keycloak.registration.RegistrationProfileWithMailDomainCheck
|
||||
net.micedre.keycloak.registration.RegistrationProfileWithDomainBlock
|
||||
|
Loading…
Reference in New Issue
Block a user