KEYCLOAK-186 Password policies

This commit is contained in:
Stian Thorgersen 2013-12-03 12:53:26 +00:00
parent 551ade29e8
commit 7f499b2833
23 changed files with 451 additions and 18 deletions

View file

@ -44,6 +44,13 @@
<input id="oauth" type="text" ui-select2="userCredentialOptions" ng-model="realm.requiredOAuthClientCredentials" placeholder="Type a role and enter"> <input id="oauth" type="text" ui-select2="userCredentialOptions" ng-model="realm.requiredOAuthClientCredentials" placeholder="Type a role and enter">
</div> </div>
</div> </div>
<div class="form-group">
<label for="policy">Password Policy <span class="required" data-ng-show="createRealm">*</span></label>
<div class="controls">
<input class="xlarge" type="text" id="policy" name="policy" data-ng-model="realm.passwordPolicy" autofocus required>
</div>
</div>
</fieldset> </fieldset>
<div class="form-actions"> <div class="form-actions">
<button type="submit" kc-save class="primary" data-ng-show="changed">Save <button type="submit" kc-save class="primary" data-ng-show="changed">Save

View file

@ -32,6 +32,7 @@ public class RealmRepresentation {
protected Set<String> requiredCredentials; protected Set<String> requiredCredentials;
protected Set<String> requiredApplicationCredentials; protected Set<String> requiredApplicationCredentials;
protected Set<String> requiredOAuthClientCredentials; protected Set<String> requiredOAuthClientCredentials;
protected String passwordPolicy;
protected List<UserRepresentation> users; protected List<UserRepresentation> users;
protected List<UserRoleMappingRepresentation> roleMappings; protected List<UserRoleMappingRepresentation> roleMappings;
protected List<ScopeMappingRepresentation> scopeMappings; protected List<ScopeMappingRepresentation> scopeMappings;
@ -199,6 +200,14 @@ public class RealmRepresentation {
this.requiredOAuthClientCredentials = requiredOAuthClientCredentials; this.requiredOAuthClientCredentials = requiredOAuthClientCredentials;
} }
public String getPasswordPolicy() {
return passwordPolicy;
}
public void setPasswordPolicy(String passwordPolicy) {
this.passwordPolicy = passwordPolicy;
}
public Integer getAccessCodeLifespan() { public Integer getAccessCodeLifespan() {
return accessCodeLifespan; return accessCodeLifespan;
} }

View file

@ -23,6 +23,8 @@ package org.keycloak.forms;
import org.keycloak.services.resources.flows.FormFlows; import org.keycloak.services.resources.flows.FormFlows;
import java.util.ResourceBundle;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
@ -32,13 +34,12 @@ public class MessageBean {
private FormFlows.MessageType type; private FormFlows.MessageType type;
// Message is considered ERROR by default public MessageBean(String summary, FormFlows.MessageType type, ResourceBundle rb) {
public MessageBean(String summary) { if (rb.containsKey(summary)) {
this(summary, FormFlows.MessageType.ERROR); this.summary = rb.getString(summary);
} } else {
this.summary = summary;
public MessageBean(String summary, FormFlows.MessageType type) { }
this.summary = summary;
this.type = type; this.type = type;
} }

View file

@ -79,19 +79,18 @@ public class FormServiceImpl implements FormService {
} }
public String process(String pageId, FormServiceDataBean dataBean){ public String process(String pageId, FormServiceDataBean dataBean){
Map<String, Object> attributes = new HashMap<String, Object>(); Map<String, Object> attributes = new HashMap<String, Object>();
ResourceBundle rb = ResourceBundle.getBundle(BUNDLE);
attributes.put("rb", rb);
if (dataBean.getMessage() != null){ if (dataBean.getMessage() != null){
attributes.put("message", new MessageBean(dataBean.getMessage(), dataBean.getMessageType())); attributes.put("message", new MessageBean(dataBean.getMessage(), dataBean.getMessageType(), rb));
} }
RealmBean realm = new RealmBean(dataBean.getRealm()); RealmBean realm = new RealmBean(dataBean.getRealm());
attributes.put("template", new TemplateBean(realm, dataBean.getContextPath())); attributes.put("template", new TemplateBean(realm, dataBean.getContextPath()));
ResourceBundle rb = ResourceBundle.getBundle(BUNDLE);
attributes.put("rb", rb);
if (commandMap.containsKey(pageId)){ if (commandMap.containsKey(pageId)){
commandMap.get(pageId).exec(attributes, dataBean); commandMap.get(pageId).exec(attributes, dataBean);
} }

View file

@ -20,7 +20,7 @@
<div class="feedback-aligner"> <div class="feedback-aligner">
<#if message?has_content && message.warning> <#if message?has_content && message.warning>
<div class="feedback warning show"> <div class="feedback warning show">
<p><strong>${rb.getString('actionWarningHeader')} ${rb.getString(message.summary)}</strong><br/>${rb.getString('actionFollow')}</p> <p><strong>${rb.getString('actionWarningHeader')} ${message.summary}</strong><br/>${rb.getString('actionFollow')}</p>
</div> </div>
</#if> </#if>
</div> </div>
@ -47,13 +47,13 @@
<#if message.error> <#if message.error>
<div class="feedback error bottom-left show"> <div class="feedback error bottom-left show">
<p> <p>
<strong id="loginError">${rb.getString(message.summary)}</strong><br/>${rb.getString('emailErrorInfo')} <strong id="loginError">${message.summary}</strong><br/>${rb.getString('emailErrorInfo')}
</p> </p>
</div> </div>
<#elseif message.success> <#elseif message.success>
<div class="feedback success bottom-left show"> <div class="feedback success bottom-left show">
<p> <p>
<strong>${rb.getString('successHeader')}</strong> ${rb.getString(message.summary)} <strong>${rb.getString('successHeader')}</strong> ${message.summary}
</p> </p>
</div> </div>
</#if> </#if>

View file

@ -40,7 +40,7 @@
<#if message?has_content && message.error> <#if message?has_content && message.error>
<div class="feedback error bottom-left show"> <div class="feedback error bottom-left show">
<p> <p>
<strong id="loginError">${rb.getString(message.summary)}</strong><br/>${rb.getString('emailErrorInfo')} <strong id="loginError">${message.summary}</strong><br/>${rb.getString('emailErrorInfo')}
</p> </p>
</div> </div>
</#if> </#if>

View file

@ -31,10 +31,10 @@
<#if message?has_content> <#if message?has_content>
<div class="feedback-aligner"> <div class="feedback-aligner">
<#if message.success> <#if message.success>
<div class="feedback success show"><p><strong>${rb.getString('successHeader')}</strong> ${rb.getString(message.summary)}</p></div> <div class="feedback success show"><p><strong>${rb.getString('successHeader')}</strong> ${message.summary}</p></div>
</#if> </#if>
<#if message.error> <#if message.error>
<div class="feedback error show"><p><strong>${rb.getString('errorHeader')}</strong> ${rb.getString(message.summary)}</p></div> <div class="feedback error show"><p><strong>${rb.getString('errorHeader')}</strong> ${message.summary}</p></div>
</#if> </#if>
</div> </div>
</#if> </#if>

View file

@ -13,6 +13,11 @@
<description/> <description/>
<dependencies> <dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>

View file

@ -0,0 +1,182 @@
package org.keycloak.models;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class PasswordPolicy {
private List<Policy> policies;
private String policyString;
public PasswordPolicy(String policyString) {
if (policyString == null || policyString.length() == 0) {
this.policyString = null;
policies = Collections.emptyList();
} else {
this.policyString = policyString;
policies = parse(policyString);
}
}
private static List<Policy> parse(String policyString) {
List<Policy> list = new LinkedList<Policy>();
String[] policies = policyString.split(" and ");
for (String policy : policies) {
policy = policy.trim();
String name;
String[] args = null;
int i = policy.indexOf('(');
if (i == -1) {
name = policy.trim();
} else {
name = policy.substring(0, i).trim();
args = policy.substring(i + 1, policy.length() - 1).split(",");
for (int j = 0; j < args.length; j++) {
args[j] = args[j].trim();
}
}
if (name.equals(Length.NAME)) {
list.add(new Length(args));
} else if (name.equals(Digits.NAME)) {
list.add(new Digits(args));
} else if (name.equals(LowerCase.NAME)) {
list.add(new LowerCase(args));
} else if (name.equals(UpperCase.NAME)) {
list.add(new UpperCase(args));
} else if (name.equals(SpecialChars.NAME)) {
list.add(new SpecialChars(args));
}
}
return list;
}
public String validate(String password) {
for (Policy p : policies) {
String error = p.validate(password);
if (error != null) {
return error;
}
}
return null;
}
private static interface Policy {
public String validate(String password);
}
private static class Length implements Policy {
private static final String NAME = "length";
private int min;
public Length(String[] args) {
min = intArg(NAME, 8, args);
}
@Override
public String validate(String password) {
return password.length() < min ? "Invalid password: minimum length " + min : null;
}
}
private static class Digits implements Policy {
private static final String NAME = "digits";
private int min;
public Digits(String[] args) {
min = intArg(NAME, 1, args);
}
@Override
public String validate(String password) {
int count = 0;
for (char c : password.toCharArray()) {
if (Character.isDigit(c)) {
count++;
}
}
return count < min ? "Invalid password: must contain at least " + count + " numerical digits" : null;
}
}
private static class LowerCase implements Policy {
private static final String NAME = "lowerCase";
private int min;
public LowerCase(String[] args) {
min = intArg(NAME, 1, args);
}
@Override
public String validate(String password) {
int count = 0;
for (char c : password.toCharArray()) {
if (Character.isLowerCase(c)) {
count++;
}
}
return count < min ? "Invalid password: must contain at least " + count + " lower case characters": null;
}
}
private static class UpperCase implements Policy {
private static final String NAME = "upperCase";
private int min;
public UpperCase(String[] args) {
min = intArg(NAME, 1, args);
}
@Override
public String validate(String password) {
int count = 0;
for (char c : password.toCharArray()) {
if (Character.isUpperCase(c)) {
count++;
}
}
return count < min ? "Invalid password: must contain at least " + count + " upper case characters" : null;
}
}
private static class SpecialChars implements Policy {
private static final String NAME = "specialChars";
private int min;
public SpecialChars(String[] args) {
min = intArg(NAME, 1, args);
}
@Override
public String validate(String password) {
int count = 0;
for (char c : password.toCharArray()) {
if (!Character.isLetterOrDigit(c)) {
count++;
}
}
return count < min ? "Invalid password: must contain at least " + count + " special characters" : null;
}
}
private static int intArg(String policy, int defaultValue, String... args) {
if (args == null || args.length == 0) {
return defaultValue;
} else if (args.length == 1) {
return Integer.parseInt(args[0]);
} else {
throw new IllegalArgumentException("Invalid arguments to " + policy + ", expect no argument or single integer");
}
}
@Override
public String toString() {
return policyString;
}
}

View file

@ -74,6 +74,10 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
void addRequiredCredential(String cred); void addRequiredCredential(String cred);
PasswordPolicy getPasswordPolicy();
void setPasswordPolicy(PasswordPolicy policy);
boolean validatePassword(UserModel user, String password); boolean validatePassword(UserModel user, String password);
boolean validateTOTP(UserModel user, String password, String token); boolean validateTOTP(UserModel user, String password, String token);

View file

@ -0,0 +1,78 @@
package org.keycloak.models;
import org.junit.Assert;
import org.junit.Test;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class PasswordPolicyTest {
@Test
public void testLength() {
PasswordPolicy policy = new PasswordPolicy("length");
Assert.assertNotNull(policy.validate("1234567"));
Assert.assertNull(policy.validate("12345678"));
policy = new PasswordPolicy("length(4)");
Assert.assertNotNull(policy.validate("123"));
Assert.assertNull(policy.validate("1234"));
}
@Test
public void testDigits() {
PasswordPolicy policy = new PasswordPolicy("digits");
Assert.assertNotNull(policy.validate("abcd"));
Assert.assertNull(policy.validate("abcd1"));
policy = new PasswordPolicy("digits(2)");
Assert.assertNotNull(policy.validate("abcd1"));
Assert.assertNull(policy.validate("abcd12"));
}
@Test
public void testLowerCase() {
PasswordPolicy policy = new PasswordPolicy("lowerCase");
Assert.assertNotNull(policy.validate("ABCD1234"));
Assert.assertNull(policy.validate("ABcD1234"));
policy = new PasswordPolicy("lowerCase(2)");
Assert.assertNotNull(policy.validate("ABcD1234"));
Assert.assertNull(policy.validate("aBcD1234"));
}
@Test
public void testUpperCase() {
PasswordPolicy policy = new PasswordPolicy("upperCase");
Assert.assertNotNull(policy.validate("abcd1234"));
Assert.assertNull(policy.validate("abCd1234"));
policy = new PasswordPolicy("upperCase(2)");
Assert.assertNotNull(policy.validate("abCd1234"));
Assert.assertNull(policy.validate("AbCd1234"));
}
@Test
public void testSpecialChars() {
PasswordPolicy policy = new PasswordPolicy("specialChars");
Assert.assertNotNull(policy.validate("abcd1234"));
Assert.assertNull(policy.validate("ab&d1234"));
policy = new PasswordPolicy("specialChars(2)");
Assert.assertNotNull(policy.validate("ab&d1234"));
Assert.assertNull(policy.validate("ab&d-234"));
}
@Test
public void testComplex() {
PasswordPolicy policy = new PasswordPolicy("length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2)");
Assert.assertNotNull(policy.validate("12aaBB&"));
Assert.assertNotNull(policy.validate("aaaaBB&-"));
Assert.assertNotNull(policy.validate("12AABB&-"));
Assert.assertNotNull(policy.validate("12aabb&-"));
Assert.assertNotNull(policy.validate("12aaBBcc"));
Assert.assertNull(policy.validate("12aaBB&-"));
}
}

View file

@ -4,6 +4,7 @@ import org.bouncycastle.openssl.PEMWriter;
import org.keycloak.PemUtils; import org.keycloak.PemUtils;
import org.keycloak.models.ApplicationModel; import org.keycloak.models.ApplicationModel;
import org.keycloak.models.OAuthClientModel; import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
@ -37,6 +38,7 @@ public class RealmAdapter implements RealmModel {
protected EntityManager em; protected EntityManager em;
protected volatile transient PublicKey publicKey; protected volatile transient PublicKey publicKey;
protected volatile transient PrivateKey privateKey; protected volatile transient PrivateKey privateKey;
private PasswordPolicy passwordPolicy;
public RealmAdapter(EntityManager em, RealmEntity realm) { public RealmAdapter(EntityManager em, RealmEntity realm) {
this.em = em; this.em = em;
@ -1037,4 +1039,18 @@ public class RealmAdapter implements RealmModel {
em.flush(); em.flush();
} }
@Override
public PasswordPolicy getPasswordPolicy() {
if (passwordPolicy == null) {
passwordPolicy = new PasswordPolicy(realm.getPasswordPolicy());
}
return passwordPolicy;
}
@Override
public void setPasswordPolicy(PasswordPolicy policy) {
this.passwordPolicy = policy;
realm.setPasswordPolicy(policy.toString());
em.flush();
}
} }

View file

@ -34,6 +34,7 @@ public class RealmEntity {
protected boolean resetPasswordAllowed; protected boolean resetPasswordAllowed;
protected boolean social; protected boolean social;
protected boolean automaticRegistrationAfterSocialLogin; protected boolean automaticRegistrationAfterSocialLogin;
protected String passwordPolicy;
protected int tokenLifespan; protected int tokenLifespan;
protected int accessCodeLifespan; protected int accessCodeLifespan;
@ -269,4 +270,13 @@ public class RealmEntity {
public void setDefaultRoles(Collection<RoleEntity> defaultRoles) { public void setDefaultRoles(Collection<RoleEntity> defaultRoles) {
this.defaultRoles = defaultRoles; this.defaultRoles = defaultRoles;
} }
public String getPasswordPolicy() {
return passwordPolicy;
}
public void setPasswordPolicy(String passwordPolicy) {
this.passwordPolicy = passwordPolicy;
}
} }

View file

@ -6,6 +6,7 @@ import org.keycloak.models.ApplicationModel;
import org.keycloak.models.IdGenerator; import org.keycloak.models.IdGenerator;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OAuthClientModel; import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
@ -62,6 +63,7 @@ public class RealmAdapter implements RealmModel {
protected PartitionManager partitionManager; protected PartitionManager partitionManager;
protected RelationshipManager relationshipManager; protected RelationshipManager relationshipManager;
protected KeycloakSession session; protected KeycloakSession session;
private PasswordPolicy passwordPolicy;
public RealmAdapter(KeycloakSession session, RealmData realm, PartitionManager partitionManager) { public RealmAdapter(KeycloakSession session, RealmData realm, PartitionManager partitionManager) {
this.session = session; this.session = session;
@ -977,4 +979,19 @@ public class RealmAdapter implements RealmModel {
realm.setSocialConfig(socialConfig); realm.setSocialConfig(socialConfig);
updateRealm(); updateRealm();
} }
@Override
public PasswordPolicy getPasswordPolicy() {
if (passwordPolicy == null) {
passwordPolicy = new PasswordPolicy(realm.getPasswordPolicy());
}
return passwordPolicy;
}
@Override
public void setPasswordPolicy(PasswordPolicy policy) {
this.passwordPolicy = policy;
realm.setPasswordPolicy(policy.toString());
updateRealm();
}
} }

View file

@ -27,6 +27,7 @@ public class RealmData extends AbstractPartition {
private String[] defaultRoles; private String[] defaultRoles;
private Map<String, String> smtpConfig; private Map<String, String> smtpConfig;
private Map<String, String> socialConfig; private Map<String, String> socialConfig;
private String passwordPolicy;
public RealmData() { public RealmData() {
super(null); super(null);
@ -185,4 +186,13 @@ public class RealmData extends AbstractPartition {
public void setSocialConfig(Map<String, String> socialConfig) { public void setSocialConfig(Map<String, String> socialConfig) {
this.socialConfig = socialConfig; this.socialConfig = socialConfig;
} }
@AttributeProperty
public String getPasswordPolicy() {
return passwordPolicy;
}
public void setPasswordPolicy(String passwordPolicy) {
this.passwordPolicy = passwordPolicy;
}
} }

View file

@ -5,6 +5,7 @@ import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OAuthClientModel; import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
@ -110,6 +111,9 @@ public class RealmManager {
if (rep.getRequiredApplicationCredentials() != null) { if (rep.getRequiredApplicationCredentials() != null) {
realm.updateRequiredApplicationCredentials(rep.getRequiredApplicationCredentials()); realm.updateRequiredApplicationCredentials(rep.getRequiredApplicationCredentials());
} }
realm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
if (rep.getDefaultRoles() != null) { if (rep.getDefaultRoles() != null) {
realm.updateDefaultRoles(rep.getDefaultRoles().toArray(new String[rep.getDefaultRoles().size()])); realm.updateDefaultRoles(rep.getDefaultRoles().toArray(new String[rep.getDefaultRoles().size()]));
} }
@ -222,6 +226,8 @@ public class RealmManager {
addOAuthClientRequiredCredential(newRealm, CredentialRepresentation.PASSWORD); addOAuthClientRequiredCredential(newRealm, CredentialRepresentation.PASSWORD);
} }
newRealm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
if (rep.getUsers() != null) { if (rep.getUsers() != null) {
for (UserRepresentation userRep : rep.getUsers()) { for (UserRepresentation userRep : rep.getUsers()) {
UserModel user = createUser(newRealm, userRep); UserModel user = createUser(newRealm, userRep);
@ -473,6 +479,9 @@ public class RealmManager {
rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction()); rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
rep.setSmtpServer(realm.getSmtpConfig()); rep.setSmtpServer(realm.getSmtpConfig());
rep.setSocialProviders(realm.getSocialConfig()); rep.setSocialProviders(realm.getSocialConfig());
if (realm.getPasswordPolicy() != null) {
rep.setPasswordPolicy(realm.getPasswordPolicy().toString());
}
ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION); ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
rep.setAccountManagement(accountManagementApplication != null && accountManagementApplication.isEnabled()); rep.setAccountManagement(accountManagementApplication != null && accountManagementApplication.isEnabled());

View file

@ -255,6 +255,11 @@ public class AccountService {
return forms.setError(Messages.INVALID_PASSWORD_EXISTING).forwardToPassword(); return forms.setError(Messages.INVALID_PASSWORD_EXISTING).forwardToPassword();
} }
String error = Validation.validatePassword(formData, realm.getPasswordPolicy());
if (error != null) {
return forms.setError(error).forwardToPassword();
}
UserCredentialModel credentials = new UserCredentialModel(); UserCredentialModel credentials = new UserCredentialModel();
credentials.setType(CredentialRepresentation.PASSWORD); credentials.setType(CredentialRepresentation.PASSWORD);
credentials.setValue(passwordNew); credentials.setValue(passwordNew);

View file

@ -297,6 +297,10 @@ public class TokenService {
} }
String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes); String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes);
if (error == null) {
error = Validation.validatePassword(formData, realm.getPasswordPolicy());
}
if (error != null) { if (error != null) {
return Flows.forms(realm, request, uriInfo).setError(error).setFormData(formData) return Flows.forms(realm, request, uriInfo).setError(error).setFormData(formData)
.setSocialRegistration(isSocialRegistration).forwardToRegistration(); .setSocialRegistration(isSocialRegistration).forwardToRegistration();

View file

@ -1,5 +1,6 @@
package org.keycloak.services.validation; package org.keycloak.services.validation;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
@ -38,6 +39,10 @@ public class Validation {
return null; return null;
} }
public static String validatePassword(MultivaluedMap<String, String> formData, PasswordPolicy policy) {
return policy.validate(formData.getFirst("password"));
}
public static String validateUpdateProfileForm(MultivaluedMap<String, String> formData) { public static String validateUpdateProfileForm(MultivaluedMap<String, String> formData) {
if (isEmpty(formData.getFirst("firstName"))) { if (isEmpty(formData.getFirst("firstName"))) {
return Messages.MISSING_FIRST_NAME; return Messages.MISSING_FIRST_NAME;

View file

@ -6,6 +6,7 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
@ -46,6 +47,7 @@ public class ModelTest extends AbstractKeycloakServerTest {
realm.setSslNotRequired(true); realm.setSslNotRequired(true);
realm.setVerifyEmail(true); realm.setVerifyEmail(true);
realm.setTokenLifespan(1000); realm.setTokenLifespan(1000);
realm.setPasswordPolicy(new PasswordPolicy("length"));
realm.setAccessCodeLifespan(1001); realm.setAccessCodeLifespan(1001);
realm.setAccessCodeLifespanUserAction(1002); realm.setAccessCodeLifespanUserAction(1002);
realm.setPublicKeyPem("0234234"); realm.setPublicKeyPem("0234234");

View file

@ -163,6 +163,36 @@ public class AccountTest {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
} }
@Test
public void changePasswordWithPasswordPolicy() {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.setPasswordPolicy(new PasswordPolicy("length"));
}
});
try {
changePasswordPage.open();
loginPage.login("test-user@localhost", "password");
changePasswordPage.changePassword("", "new", "new");
Assert.assertTrue(profilePage.isError());
changePasswordPage.changePassword("password", "new-password", "new-password");
Assert.assertTrue(profilePage.isSuccess());
} finally {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.setPasswordPolicy(new PasswordPolicy(null));
}
});
}
}
@Test @Test
public void changeProfile() { public void changeProfile() {
profilePage.open(); profilePage.open();

View file

@ -25,6 +25,9 @@ import org.junit.Assert;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType; import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
@ -93,6 +96,37 @@ public class RegisterTest {
Assert.assertEquals("Please specify password.", registerPage.getError()); Assert.assertEquals("Please specify password.", registerPage.getError());
} }
@Test
public void registerPasswordPolicy() {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.setPasswordPolicy(new PasswordPolicy("length"));
}
});
try {
loginPage.open();
loginPage.clickRegister();
registerPage.assertCurrent();
registerPage.register("firstName", "lastName", "email", "registerPasswordPolicy", "pass", "pass");
registerPage.assertCurrent();
Assert.assertEquals("Invalid password: minimum length 8", registerPage.getError());
registerPage.register("firstName", "lastName", "email", "registerPasswordPolicy", "password", "password");
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
} finally {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.setPasswordPolicy(new PasswordPolicy(null));
}
});
}
}
@Test @Test
public void registerUserMissingUsername() { public void registerUserMissingUsername() {
loginPage.open(); loginPage.open();

View file

@ -54,26 +54,32 @@ public class RegisterPage extends AbstractPage {
private WebElement loginErrorMessage; private WebElement loginErrorMessage;
public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) { public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) {
firstNameInput.clear();
if (firstName != null) { if (firstName != null) {
firstNameInput.sendKeys(firstName); firstNameInput.sendKeys(firstName);
} }
lastNameInput.clear();
if (lastName != null) { if (lastName != null) {
lastNameInput.sendKeys(lastName); lastNameInput.sendKeys(lastName);
} }
emailInput.clear();
if (email != null) { if (email != null) {
emailInput.sendKeys(email); emailInput.sendKeys(email);
} }
usernameInput.clear();
if (username != null) { if (username != null) {
usernameInput.sendKeys(username); usernameInput.sendKeys(username);
} }
passwordInput.clear();
if (password != null) { if (password != null) {
passwordInput.sendKeys(password); passwordInput.sendKeys(password);
} }
passwordConfirmInput.clear();
if (passwordConfirm != null) { if (passwordConfirm != null) {
passwordConfirmInput.sendKeys(passwordConfirm); passwordConfirmInput.sendKeys(passwordConfirm);
} }