Disable username prohibited chars validator when email as username is… (#31140)
* Disable username prohibited chars validator when email as the username is set Closes #25339 Signed-off-by: Martin Kanis <mkanis@redhat.com> Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com> Co-authored-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
d475833361
commit
922eaa9fc8
3 changed files with 51 additions and 14 deletions
|
@ -285,6 +285,7 @@ The list below provides a list of all the built-in validators:
|
|||
|
||||
|username-prohibited-characters
|
||||
| Check if the value is a valid username as an additional barrier for attacks such as script injection. The validation is based on a default RegEx pattern that blocks characters not common in usernames.
|
||||
When the realm setting `Email as username` is enabled, this validator is skipped to allow email values.
|
||||
|
|
||||
|
||||
*error-message*: the key of the error message in i18n bundle. If not set a generic message is used.
|
||||
|
|
|
@ -20,6 +20,8 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ConfiguredProvider;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.validate.AbstractStringValidator;
|
||||
|
@ -40,11 +42,11 @@ public class UsernameProhibitedCharactersValidator extends AbstractStringValidat
|
|||
public static final UsernameProhibitedCharactersValidator INSTANCE = new UsernameProhibitedCharactersValidator();
|
||||
|
||||
protected static final Pattern PATTERN = Pattern.compile("^[^<>&\"'\\s\\v\\h$%!#?§,;:*~/\\\\|^=\\[\\]{}()`\\p{Cntrl}]+$");
|
||||
|
||||
|
||||
public static final String MESSAGE_NO_MATCH = "error-username-invalid-character";
|
||||
|
||||
|
||||
public static final String CFG_ERROR_MESSAGE = "error-message";
|
||||
|
||||
|
||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
|
||||
|
||||
static {
|
||||
|
@ -64,12 +66,22 @@ public class UsernameProhibitedCharactersValidator extends AbstractStringValidat
|
|||
|
||||
@Override
|
||||
protected void doValidate(String value, String inputHint, ValidationContext context, ValidatorConfig config) {
|
||||
KeycloakSession session = context.getSession();
|
||||
|
||||
if (session != null) {
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
|
||||
if (realm.isRegistrationEmailAsUsername()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PATTERN.matcher(value).matches()) {
|
||||
context.addError(new ValidationError(ID, inputHint, config.getStringOrDefault(CFG_ERROR_MESSAGE, MESSAGE_NO_MATCH)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Basic Username validator disallowing bunch of characters we really do not expect in username.";
|
||||
|
|
|
@ -321,6 +321,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
|||
public void testValidation() {
|
||||
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::failValidationWhenEmptyAttributes);
|
||||
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testAttributeValidation);
|
||||
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testEmailAsUsernameValidation);
|
||||
}
|
||||
|
||||
private static void failValidationWhenEmptyAttributes(KeycloakSession session) {
|
||||
|
@ -398,7 +399,30 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
|||
assertFalse(profile.getAttributes().validate(UserModel.EMAIL, (Consumer<ValidationError>) errors::add));
|
||||
assertTrue(containsErrorMessage(errors, EmailValidator.MESSAGE_INVALID_EMAIL));
|
||||
}
|
||||
|
||||
|
||||
private static void testEmailAsUsernameValidation(KeycloakSession session) {
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
UserProfileProvider provider = session.getProvider(UserProfileProvider.class);
|
||||
provider.setConfiguration(null);
|
||||
UserProfile profile;
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
|
||||
try {
|
||||
realm.setRegistrationEmailAsUsername(true);
|
||||
attributes.clear();
|
||||
attributes.put(UserModel.FIRST_NAME, "Joe");
|
||||
attributes.put(UserModel.LAST_NAME, "Doe");
|
||||
// valid email but invalid as username
|
||||
attributes.put(UserModel.EMAIL, "foo%bar@example.com");
|
||||
profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes);
|
||||
profile.validate();
|
||||
} catch (ValidationException ve) {
|
||||
Assert.fail("Should be OK email as username");
|
||||
} finally {
|
||||
realm.setRegistrationEmailAsUsername(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean containsErrorMessage(List<ValidationError> errors, String message){
|
||||
for(ValidationError err : errors) {
|
||||
if(err.getMessage().equals(message)) {
|
||||
|
@ -407,7 +431,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testValidateComplianceWithUserProfile() {
|
||||
|
@ -516,14 +540,14 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
|||
|
||||
assertThat(attributes.nameSet(),
|
||||
containsInAnyOrder(UserModel.USERNAME, UserModel.EMAIL, UserModel.FIRST_NAME, UserModel.LAST_NAME, UserModel.LOCALE, "address", "second"));
|
||||
|
||||
|
||||
|
||||
|
||||
AttributeGroupMetadata companyAddressGroup = attributes.getMetadata("address").getAttributeGroupMetadata();
|
||||
assertEquals("companyaddress", companyAddressGroup.getName());
|
||||
assertEquals("header", companyAddressGroup.getDisplayHeader());
|
||||
assertEquals("description", companyAddressGroup.getDisplayDescription());
|
||||
assertNull(companyAddressGroup.getAnnotations());
|
||||
|
||||
|
||||
AttributeGroupMetadata groupwithannoGroup = attributes.getMetadata("second").getAttributeGroupMetadata();
|
||||
assertEquals("groupwithanno", groupwithannoGroup.getName());
|
||||
assertNull(groupwithannoGroup.getDisplayHeader());
|
||||
|
@ -572,9 +596,9 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
|||
attributesUpdatedOldValues.put(UserModel.FIRST_NAME, "Joe");
|
||||
attributesUpdatedOldValues.put(UserModel.LAST_NAME, "Doe");
|
||||
attributesUpdatedOldValues.put(UserModel.EMAIL, "user@keycloak.org");
|
||||
|
||||
|
||||
profile.update((attributeName, userModel, oldValue) -> {
|
||||
assertTrue(attributesUpdated.add(attributeName));
|
||||
assertTrue(attributesUpdated.add(attributeName));
|
||||
assertEquals(attributesUpdatedOldValues.get(attributeName), getSingleValue(oldValue));
|
||||
assertEquals(attributes.get(attributeName), userModel.getFirstAttribute(attributeName));
|
||||
});
|
||||
|
@ -593,13 +617,13 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
|||
|
||||
assertEquals("fixed-business-address", user.getFirstAttribute("business.address"));
|
||||
}
|
||||
|
||||
|
||||
private static String getSingleValue(List<String> vals) {
|
||||
if(vals==null || vals.isEmpty())
|
||||
return null;
|
||||
return vals.get(0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testReadonlyUpdates() {
|
||||
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testReadonlyUpdates);
|
||||
|
|
Loading…
Reference in a new issue