Password policy for not having username in the password

closes #27643

Signed-off-by: Gilvan Filho <gfilho@redhat.com>
This commit is contained in:
Gilvan Filho 2024-03-07 14:26:14 -03:00 committed by Marek Posolda
parent b9a7152a29
commit 757c524cc5
11 changed files with 169 additions and 0 deletions

View file

@ -19,6 +19,10 @@ The following methods for setting custom cookies have been removed:
* `HttpCookie` - replaced by `NewCookie.Builder`
* `HttpResponse.setCookieIfAbsent(HttpCookie cookie)` - replaced by `HttpResponse.setCookieIfAbsent(NewCookie cookie)`
= Password policy for check if password contains Username
Keycloak supports a new password policy that allows you to deny user passwords which contains the user username.
= Searching by user attribute no longer case insensitive
When searching for users by user attribute, {project_name} no longer searches for user attribute names forcing lower case comparisons. The goal of this change was to speed up searches by using {project_name}'s native index on the user attribute table. If your database collation is case-insensitive, your search results will stay the same. If your database collation is case-sensitive, you might see less search results than before.

View file

@ -0,0 +1,56 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.policy;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
public class NotContainsUsernamePasswordPolicyProvider implements PasswordPolicyProvider {
private static final String ERROR_MESSAGE = "invalidPasswordNotContainsUsernameMessage";
private KeycloakContext context;
public NotContainsUsernamePasswordPolicyProvider(KeycloakContext context) {
this.context = context;
}
@Override
public PolicyError validate(String username, String password) {
if (username == null) {
return null;
}
return username.contains(password) ? new PolicyError(ERROR_MESSAGE) : null;
}
@Override
public PolicyError validate(RealmModel realm, UserModel user, String password) {
return validate(user.getUsername(), password);
}
@Override
public Object parseConfig(String value) {
return null;
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.policy;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
public class NotContainsUsernamePasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
public static final String ID = "notContainsUsername";
@Override
public String getId() {
return ID;
}
@Override
public PasswordPolicyProvider create(KeycloakSession session) {
return new NotContainsUsernamePasswordPolicyProvider(session.getContext());
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public String getDisplayName() {
return "Not Contains Username";
}
@Override
public String getConfigType() {
return null;
}
@Override
public String getDefaultConfigValue() {
return null;
}
@Override
public boolean isMultiplSupported() {
return false;
}
@Override
public void close() {
}
}

View file

@ -24,6 +24,7 @@ org.keycloak.policy.LengthPasswordPolicyProviderFactory
org.keycloak.policy.LowerCasePasswordPolicyProviderFactory
org.keycloak.policy.MaximumLengthPasswordPolicyProviderFactory
org.keycloak.policy.NotUsernamePasswordPolicyProviderFactory
org.keycloak.policy.NotContainsUsernamePasswordPolicyProviderFactory
org.keycloak.policy.RegexPatternsPasswordPolicyProviderFactory
org.keycloak.policy.SpecialCharsPasswordPolicyProviderFactory
org.keycloak.policy.UpperCasePasswordPolicyProviderFactory

View file

@ -597,6 +597,38 @@ public class RegisterTest extends AbstractTestRealmKeycloakTest {
}
}
// KEYCLOAK-27643
@Test
public void registerUserNotContainsUsernamePasswordPolicy() throws IOException {
try (RealmAttributeUpdater rau = getRealmAttributeUpdater().setPasswordPolicy("notContainsUsername").update()) {
loginPage.open();
assertTrue(loginPage.isCurrent());
loginPage.clickRegister();
registerPage.assertCurrent();
registerPage.register("firstName", "lastName", "registerUserNotContainsUsername@email", "registerUserNotContainsUsername", "registerUserNotContainsUsername", "registerUserNotContainsUsername");
assertTrue(registerPage.isCurrent());
assertEquals("Invalid password: Can not contains the username.", registerPage.getInputPasswordErrors().getPasswordError());
try (Response response = adminClient.realm("test").users().create(UserBuilder.create().username("registerUserNotContainsUsername").build())) {
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
}
registerPage.register("firstName", "lastName", "registerUserNotContainsUsername@email", "registerUserNotContainsUsername", "registerUserNotContainsUsername", "registerUserNotContainsUsername");
assertTrue(registerPage.isCurrent());
assertEquals("Username already exists.", registerPage.getInputAccountErrors().getUsernameError());
registerPage.register("firstName", "lastName", "registerUserNotContainsUsername@email", null, "password", "password");
assertTrue(registerPage.isCurrent());
assertEquals("Please specify username.", registerPage.getInputAccountErrors().getUsernameError());
}
}
// KEYCLOAK-12729
@Test
public void registerUserNotEmailPasswordPolicy() throws IOException {

View file

@ -214,6 +214,7 @@ invalidPasswordMinDigitsMessage=Senha inválida\: deve conter pelo menos {0} nú
invalidPasswordMinUpperCaseCharsMessage=Senha inválida\: deve conter pelo menos {0} letra(s) maiúscula(s).
invalidPasswordMinSpecialCharsMessage=Senha inválida\: deve conter pelo menos {0} caractere(s) especial(is).
invalidPasswordNotUsernameMessage=Senha inválida\: não pode ser igual ao nome de usuário.
invalidPasswordNotContainsUsernameMessage=Senha inválida\: não pode conter o nome de usuário.
invalidPasswordNotEmailMessage=Senha inválida: não pode ser igual ao endereço de e-mail.
invalidPasswordRegexPatternMessage=Senha inválida\: não corresponde ao(s) padrão(ões) da expressão regular.
invalidPasswordHistoryMessage=Senha inválida\: não pode ser igual a qualquer uma da(s) última(s) {0} senha(s).

View file

@ -4,6 +4,7 @@ invalidPasswordMinDigitsMessage=Senha inválida: deve conter ao menos {0} digito
invalidPasswordMinUpperCaseCharsMessage=Senha inválida: deve conter ao menos {0} caracteres maiúsculos.
invalidPasswordMinSpecialCharsMessage=Senha inválida: deve conter ao menos {0} caracteres especiais.
invalidPasswordNotUsernameMessage=Senha inválida: não deve ser igual ao nome de usuário.
invalidPasswordNotContainsUsernameMessage=Senha inválida\: não pode conter o nome de usuário.
invalidPasswordRegexPatternMessage=Senha inválida: falha ao passar por padrões.
invalidPasswordHistoryMessage=Senha inválida: não deve ser igual às últimas {0} senhas.

View file

@ -241,6 +241,7 @@ invalidPasswordMinLowerCaseCharsMessage=Senha inválida\: deve conter pelo menos
invalidPasswordMinUpperCaseCharsMessage=Senha inválida\: deve conter pelo menos {0} letra(s) maiúscula(s).
invalidPasswordMinSpecialCharsMessage=Senha inválida\: deve conter pelo menos {0} caractere(s) especial(is).
invalidPasswordNotUsernameMessage=Senha inválida\: não pode ser igual ao nome de usuário
invalidPasswordNotContainsUsernameMessage=Senha inválida\: não pode conter o nome de usuário.
invalidPasswordNotEmailMessage=Senha inválida: não pode ser igual ao endereço de e-mail.
invalidPasswordRegexPatternMessage=Senha inválida\: não corresponde ao(s) padrão(ões) de expressão regular.
invalidPasswordHistoryMessage=Senha inválida\: não pode ser igual a qualquer uma da(s) última(s) {0} senha(s).

View file

@ -230,6 +230,7 @@ invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} nume
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters.
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
invalidPasswordNotContainsUsernameMessage=Invalid password: Can not contains the username.
invalidPasswordNotEmailMessage=Invalid password: must not be equal to the email.
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.

View file

@ -5,6 +5,7 @@ invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} nume
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters.
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
invalidPasswordNotContainsUsernameMessage=Invalid password: Can not contains the username.
invalidPasswordNotEmailMessage=Invalid password: must not be equal to the email.
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.

View file

@ -307,6 +307,7 @@ invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters.
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
invalidPasswordNotContainsUsernameMessage=Invalid password: Can not contains the username.
invalidPasswordNotEmailMessage=Invalid password: must not be equal to the email.
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.