diff --git a/distribution/server-overlay/cli/keycloak-install-ha.cli b/distribution/server-overlay/cli/keycloak-install-ha.cli index 4b8d399e36..6f61189630 100644 --- a/distribution/server-overlay/cli/keycloak-install-ha.cli +++ b/distribution/server-overlay/cli/keycloak-install-ha.cli @@ -4,6 +4,7 @@ embed-server --server-config=standalone-ha.xml /subsystem=infinispan/cache-container=keycloak/transport=TRANSPORT:add(lock-timeout=60000) /subsystem=infinispan/cache-container=keycloak/invalidation-cache=realms:add(mode="SYNC") /subsystem=infinispan/cache-container=keycloak/invalidation-cache=users:add(mode="SYNC") +/subsystem=infinispan/cache-container=keycloak/invalidation-cache=users/eviction=EVICTION:add(max-entries=10000,strategy=LRU) /subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:add(mode="SYNC",owners="1") /subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:add(mode="SYNC",owners="1") /subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:add(mode="SYNC",owners="1") diff --git a/distribution/server-overlay/cli/keycloak-install.cli b/distribution/server-overlay/cli/keycloak-install.cli index ce08e0a411..c29cd5f1e8 100644 --- a/distribution/server-overlay/cli/keycloak-install.cli +++ b/distribution/server-overlay/cli/keycloak-install.cli @@ -3,11 +3,12 @@ embed-server --server-config=standalone.xml /subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak") /subsystem=infinispan/cache-container=keycloak/local-cache=realms:add() /subsystem=infinispan/cache-container=keycloak/local-cache=users:add() +/subsystem=infinispan/cache-container=keycloak/local-cache=users/eviction=EVICTION:add(max-entries=10000,strategy=LRU) /subsystem=infinispan/cache-container=keycloak/local-cache=sessions:add() /subsystem=infinispan/cache-container=keycloak/local-cache=offlineSessions:add() /subsystem=infinispan/cache-container=keycloak/local-cache=loginFailures:add() /subsystem=infinispan/cache-container=keycloak/local-cache=work:add() -/subsystem=infinispan/cache-container=keycloak/local-cache=realmVersions:add() -/subsystem=infinispan/cache-container=keycloak/local-cache=realmVersions/transaction=TRANSACTION:add(mode=BATCH,locking=PESSIMISTIC) +/subsystem=infinispan/cache-container=keycloak/local-cache=authorization:add() +/subsystem=infinispan/cache-container=keycloak/local-cache=authorization/eviction=EVICTION:add(max-entries=100,strategy=LRU) /extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem) /subsystem=keycloak-server:add(web-context=auth) \ No newline at end of file diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java index 3bad384976..24b772b39f 100755 --- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java @@ -193,9 +193,13 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon private Configuration getRevisionCacheConfig(boolean managed, long maxEntries) { ConfigurationBuilder cb = new ConfigurationBuilder(); cb.invocationBatching().enable().transaction().transactionMode(TransactionMode.TRANSACTIONAL); - if (!managed) { + + // Workaround: Use Dummy manager even in managed ( wildfly/eap ) environment. Without this workaround, there is an issue in EAP7 overlay. + // After start+end revisions batch is left the JTA transaction in committed state. This is incorrect and causes other issues afterwards. + // TODO: Investigate + // if (!managed) cb.transaction().transactionManagerLookup(new DummyTransactionManagerLookup()); - } + cb.transaction().lockingMode(LockingMode.PESSIMISTIC); cb.eviction().strategy(EvictionStrategy.LRU).type(EvictionType.COUNT).size(maxEntries); diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml index 9b01aceeef..1d118b285f 100755 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml @@ -159,7 +159,7 @@ - + diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java index 85e316fb15..42fd549cb0 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java +++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java @@ -375,8 +375,15 @@ public class SamlProtocol implements LoginProtocol { Document samlDocument = null; try { ResponseType samlModel = builder.buildModel(); - transformAttributeStatement(attributeStatementMappers, samlModel, session, userSession, clientSession); - populateRoles(roleListMapper, samlModel, session, userSession, clientSession); + final AttributeStatementType attributeStatement = populateAttributeStatements(attributeStatementMappers, session, userSession, clientSession); + populateRoles(roleListMapper, session, userSession, clientSession, attributeStatement); + + // SAML Spec 2.7.3 AttributeStatement must contain one or more Attribute or EncryptedAttribute + if (attributeStatement.getAttributes().size() > 0) { + AssertionType assertion = samlModel.getAssertions().get(0).getAssertion(); + assertion.addStatement(attributeStatement); + } + samlModel = transformLoginResponse(loginResponseMappers, samlModel, session, userSession, clientSession); samlDocument = builder.buildDocument(samlModel); } catch (Exception e) { @@ -437,19 +444,14 @@ public class SamlProtocol implements LoginProtocol { } } - public void transformAttributeStatement(List> attributeStatementMappers, ResponseType response, KeycloakSession session, UserSessionModel userSession, - ClientSessionModel clientSession) { - AssertionType assertion = response.getAssertions().get(0).getAssertion(); + public AttributeStatementType populateAttributeStatements(List> attributeStatementMappers, KeycloakSession session, UserSessionModel userSession, + ClientSessionModel clientSession) { AttributeStatementType attributeStatement = new AttributeStatementType(); - for (ProtocolMapperProcessor processor : attributeStatementMappers) { processor.mapper.transformAttributeStatement(attributeStatement, processor.model, session, userSession, clientSession); } - // SAML Spec 2.7.3 AttributeStatement must contain one or more Attribute or EncryptedAttribute - if (attributeStatement.getAttributes().size() > 0) { - assertion.addStatement(attributeStatement); - } + return attributeStatement; } public ResponseType transformLoginResponse(List> mappers, ResponseType response, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { @@ -459,17 +461,11 @@ public class SamlProtocol implements LoginProtocol { return response; } - public void populateRoles(ProtocolMapperProcessor roleListMapper, ResponseType response, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { + public void populateRoles(ProtocolMapperProcessor roleListMapper, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession, + final AttributeStatementType existingAttributeStatement) { if (roleListMapper == null) return; - AssertionType assertion = response.getAssertions().get(0).getAssertion(); - AttributeStatementType attributeStatement = new AttributeStatementType(); - roleListMapper.mapper.mapRoles(attributeStatement, roleListMapper.model, session, userSession, clientSession); - - // SAML Spec 2.7.3 AttributeStatement must contain one or more Attribute or EncryptedAttribute - if (attributeStatement.getAttributes().size() > 0) { - assertion.addStatement(attributeStatement); - } + roleListMapper.mapper.mapRoles(existingAttributeStatement, roleListMapper.model, session, userSession, clientSession); } public static String getLogoutServiceUrl(UriInfo uriInfo, ClientModel client, String bindingType) { diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index a8c4af75d3..7a2f985586 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -32,6 +32,7 @@ + - - - - @@ -97,6 +98,7 @@ maven-surefire-plugin + ${exclude.test} ${exclude.console} ${exclude.account} ${exclude.client} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java index 5f8a51df68..5d12db4ca5 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java @@ -45,6 +45,7 @@ import javax.ws.rs.core.UriBuilder; import java.util.LinkedList; import java.util.List; import java.util.Map; + import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.arquillian.graphene.page.Page; import org.keycloak.representations.idm.EventRepresentation; @@ -207,10 +208,9 @@ public class AccountTest extends TestRealmKeycloakTest { testRealm.setPasswordPolicy(policy); testRealm().update(testRealm); } - @Test - public void changePasswordWithLengthPasswordPolicy() { - setPasswordPolicy("length"); + @Test + public void changePasswordWithBlankCurrentPassword() { changePasswordPage.open(); loginPage.login("test-user@localhost", "password"); events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent(); @@ -219,7 +219,130 @@ public class AccountTest extends TestRealmKeycloakTest { Assert.assertEquals("Please specify password.", profilePage.getError()); events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_MISSING).assertEvent(); - changePasswordPage.changePassword("password", "new-password", "new-password"); + changePasswordPage.changePassword("password", "new", "new"); + Assert.assertEquals("Your password has been updated.", profilePage.getSuccess()); + events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent(); + } + + @Test + public void changePasswordWithLengthPasswordPolicy() { + setPasswordPolicy("length(8)"); + + changePasswordPage.open(); + loginPage.login("test-user@localhost", "password"); + events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent(); + + changePasswordPage.changePassword("password", "1234", "1234"); + Assert.assertEquals("Invalid password: minimum length 8.", profilePage.getError()); + events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).assertEvent(); + + changePasswordPage.changePassword("password", "12345678", "12345678"); + Assert.assertEquals("Your password has been updated.", profilePage.getSuccess()); + events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent(); + } + + @Test + public void changePasswordWithDigitsPolicy() { + setPasswordPolicy("digits(2)"); + + changePasswordPage.open(); + loginPage.login("test-user@localhost", "password"); + events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent(); + + changePasswordPage.changePassword("password", "invalidPassword1", "invalidPassword1"); + Assert.assertEquals("Invalid password: must contain at least 2 numerical digits.", profilePage.getError()); + events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).assertEvent(); + + changePasswordPage.changePassword("password", "validPassword12", "validPassword12"); + Assert.assertEquals("Your password has been updated.", profilePage.getSuccess()); + events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent(); + } + + @Test + public void changePasswordWithLowerCasePolicy() { + setPasswordPolicy("lowerCase(2)"); + + changePasswordPage.open(); + loginPage.login("test-user@localhost", "password"); + events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent(); + + changePasswordPage.changePassword("password", "iNVALIDPASSWORD", "iNVALIDPASSWORD"); + Assert.assertEquals("Invalid password: must contain at least 2 lower case characters.", profilePage.getError()); + events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).assertEvent(); + + changePasswordPage.changePassword("password", "vaLIDPASSWORD", "vaLIDPASSWORD"); + Assert.assertEquals("Your password has been updated.", profilePage.getSuccess()); + events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent(); + } + + @Test + public void changePasswordWithUpperCasePolicy() { + setPasswordPolicy("upperCase(2)"); + + changePasswordPage.open(); + loginPage.login("test-user@localhost", "password"); + events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent(); + + changePasswordPage.changePassword("password", "Invalidpassword", "Invalidpassword"); + Assert.assertEquals("Invalid password: must contain at least 2 upper case characters.", profilePage.getError()); + events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).assertEvent(); + + + changePasswordPage.changePassword("password", "VAlidpassword", "VAlidpassword"); + Assert.assertEquals("Your password has been updated.", profilePage.getSuccess()); + events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent(); + } + + @Test + public void changePasswordWithSpecialCharsPolicy() { + setPasswordPolicy("specialChars(2)"); + + changePasswordPage.open(); + loginPage.login("test-user@localhost", "password"); + events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent(); + + changePasswordPage.changePassword("password", "invalidPassword*", "invalidPassword*"); + Assert.assertEquals("Invalid password: must contain at least 2 special characters.", profilePage.getError()); + events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).assertEvent(); + + + changePasswordPage.changePassword("password", "validPassword*#", "validPassword*#"); + Assert.assertEquals("Your password has been updated.", profilePage.getSuccess()); + events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent(); + } + + @Test + public void changePasswordWithNotUsernamePolicy() { + setPasswordPolicy("notUsername(1)"); + + changePasswordPage.open(); + loginPage.login("test-user@localhost", "password"); + events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent(); + + changePasswordPage.changePassword("password", "test-user@localhost", "test-user@localhost"); + Assert.assertEquals("Invalid password: must not be equal to the username.", profilePage.getError()); + events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).assertEvent(); + + + changePasswordPage.changePassword("password", "newPassword", "newPassword"); + Assert.assertEquals("Your password has been updated.", profilePage.getSuccess()); + events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent(); + } + + @Test + public void changePasswordWithRegexPatternsPolicy() { + setPasswordPolicy("regexPattern(^[A-Z]+#[a-z]{8}$)"); + + changePasswordPage.open(); + loginPage.login("test-user@localhost", "password"); + events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent(); + + changePasswordPage.changePassword("password", "invalidPassword", "invalidPassword"); + Assert.assertEquals("Invalid password: fails to match regex pattern(s).", profilePage.getError()); + events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).assertEvent(); + + + changePasswordPage.changePassword("password", "VALID#password", "VALID#password"); Assert.assertEquals("Your password has been updated.", profilePage.getSuccess()); events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent(); } diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/PasswordPolicy.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/PasswordPolicy.java index 1b1348d412..aef6e93c3d 100644 --- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/PasswordPolicy.java +++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/PasswordPolicy.java @@ -77,10 +77,10 @@ public class PasswordPolicy extends Authentication { public enum Type { - HASH_ITERATIONS("HashIterations"), LENGTH("Length"), DIGITS("Digits"), LOWER_CASE("LowerCase"), - UPPER_CASE("UpperCase"), SPECIAL_CHARS("SpecialChars"), NOT_USERNAME("NotUsername"), - REGEX_PATTERN("RegexPattern"), PASSWORD_HISTORY("PasswordHistory"), - FORCE_EXPIRED_PASSWORD_CHANGE("ForceExpiredPasswordChange"); + HASH_ITERATIONS("Hashing Iterations"), LENGTH("Minimum Length"), DIGITS("Digits"), LOWER_CASE("Lowercase Characters"), + UPPER_CASE("Uppercase Characters"), SPECIAL_CHARS("Special Characters"), NOT_USERNAME("Not Username"), + REGEX_PATTERN("Regular Expression"), PASSWORD_HISTORY("Not Recently Used"), + FORCE_EXPIRED_PASSWORD_CHANGE("Expire Password"), HASH_ALGORITHM("Hashing Algorithm"); private String name; diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/PasswordPolicyTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/PasswordPolicyTest.java index 68da020b1a..e70acd4f30 100644 --- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/PasswordPolicyTest.java +++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/PasswordPolicyTest.java @@ -178,6 +178,12 @@ public class PasswordPolicyTest extends AbstractConsoleTest { testUserCredentialsPage.resetPassword("firstPassword"); assertAlertDanger(); + + testUserCredentialsPage.resetPassword("thirdPassword"); + assertAlertSuccess(); + + testUserCredentialsPage.resetPassword("firstPassword"); + assertAlertSuccess(); } } diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/realm/SecurityDefensesTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/realm/SecurityDefensesTest.java index 9537661a5e..1f47c1ea3f 100644 --- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/realm/SecurityDefensesTest.java +++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/realm/SecurityDefensesTest.java @@ -73,7 +73,7 @@ public class SecurityDefensesTest extends AbstractRealmTest { @Test public void maxLoginFailuresTest() throws InterruptedException { - final short secondsToWait = 3; + final short secondsToWait = 10; // For slower browsers/webdrivers (like IE) we need higher value final short maxLoginFailures = 2; bruteForceDetectionPage.form().setProtectionEnabled(true); @@ -89,7 +89,7 @@ public class SecurityDefensesTest extends AbstractRealmTest { @Test public void quickLoginCheck() throws InterruptedException { - final short secondsToWait = 3; + final short secondsToWait = 10; bruteForceDetectionPage.form().setProtectionEnabled(true); bruteForceDetectionPage.form().setMaxLoginFailures("100"); @@ -104,7 +104,7 @@ public class SecurityDefensesTest extends AbstractRealmTest { @Test public void maxWaitLoginFailures() throws InterruptedException { - final short secondsToWait = 5; + final short secondsToWait = 15; bruteForceDetectionPage.form().setProtectionEnabled(true); bruteForceDetectionPage.form().setMaxLoginFailures("1"); @@ -120,7 +120,7 @@ public class SecurityDefensesTest extends AbstractRealmTest { @Test public void failureResetTime() throws InterruptedException { final short failureResetTime = 3; - final short waitIncrement = 3; + final short waitIncrement = 5; bruteForceDetectionPage.form().setProtectionEnabled(true); bruteForceDetectionPage.form().setMaxLoginFailures("1"); @@ -199,8 +199,8 @@ public class SecurityDefensesTest extends AbstractRealmTest { wait *= 1000; - log.debug("Wait: " + wait); - Thread.sleep(wait); + log.info("Wait: " + wait); + pause(wait); if (finalLogin) { testRealmLoginPage.form().login(testUser);