diff --git a/adapters/oidc/adapter-core/pom.xml b/adapters/oidc/adapter-core/pom.xml
index c44e1f535e..015fbf49fe 100755
--- a/adapters/oidc/adapter-core/pom.xml
+++ b/adapters/oidc/adapter-core/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
index 06830758fa..cf51bdf39e 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
@@ -206,7 +206,7 @@ public class OAuthRequestAuthenticator {
tokenStore.saveRequest();
log.debug("Sending redirect to login page: " + redirect);
exchange.getResponse().setStatus(302);
- exchange.getResponse().setCookie(deployment.getStateCookieName(), state, /* need to set path? */ null, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), false);
+ exchange.getResponse().setCookie(deployment.getStateCookieName(), state, /* need to set path? */ null, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
exchange.getResponse().setHeader("Location", redirect);
return true;
}
diff --git a/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml b/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml
index 2253a4a7af..03d68cca7d 100755
--- a/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml
+++ b/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml
@@ -21,7 +21,7 @@
keycloak-as7-integration-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/oidc/as7-eap6/as7-adapter/pom.xml b/adapters/oidc/as7-eap6/as7-adapter/pom.xml
index 79f7a89076..88625c55c2 100755
--- a/adapters/oidc/as7-eap6/as7-adapter/pom.xml
+++ b/adapters/oidc/as7-eap6/as7-adapter/pom.xml
@@ -21,7 +21,7 @@
keycloak-as7-integration-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/oidc/as7-eap6/as7-subsystem/pom.xml b/adapters/oidc/as7-eap6/as7-subsystem/pom.xml
index 8943fb5e65..35ff17a015 100755
--- a/adapters/oidc/as7-eap6/as7-subsystem/pom.xml
+++ b/adapters/oidc/as7-eap6/as7-subsystem/pom.xml
@@ -21,7 +21,7 @@
org.keycloak
keycloak-as7-integration-pom
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
diff --git a/adapters/oidc/as7-eap6/pom.xml b/adapters/oidc/as7-eap6/pom.xml
index 65333f2dbb..edd1761e35 100755
--- a/adapters/oidc/as7-eap6/pom.xml
+++ b/adapters/oidc/as7-eap6/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
Keycloak AS7 / JBoss EAP 6 Integration
diff --git a/adapters/oidc/installed/pom.xml b/adapters/oidc/installed/pom.xml
index 5935dda1ab..aede1dbfb1 100755
--- a/adapters/oidc/installed/pom.xml
+++ b/adapters/oidc/installed/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/jaxrs-oauth-client/pom.xml b/adapters/oidc/jaxrs-oauth-client/pom.xml
index 75ec83a9ca..dd04fd1fc0 100755
--- a/adapters/oidc/jaxrs-oauth-client/pom.xml
+++ b/adapters/oidc/jaxrs-oauth-client/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/jetty/jetty-core/pom.xml b/adapters/oidc/jetty/jetty-core/pom.xml
index 66f747ae31..9c95f53f7e 100755
--- a/adapters/oidc/jetty/jetty-core/pom.xml
+++ b/adapters/oidc/jetty/jetty-core/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java b/adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java
index f8d72783f6..56d8f51298 100755
--- a/adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java
+++ b/adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java
@@ -18,6 +18,8 @@
package org.keycloak.adapters.jetty.core;
import org.eclipse.jetty.security.DefaultUserIdentity;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.security.authentication.DeferredAuthentication;
@@ -135,10 +137,44 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
return new DefaultUserIdentity(theSubject, principal, theRoles);
}
+ private class DummyLoginService implements LoginService {
+ @Override
+ public String getName() {
+ return null;
+ }
+
+ @Override
+ public UserIdentity login(String username, Object credentials) {
+ return null;
+ }
+
+ @Override
+ public boolean validate(UserIdentity user) {
+ return false;
+ }
+
+ @Override
+ public IdentityService getIdentityService() {
+ return null;
+ }
+
+ @Override
+ public void setIdentityService(IdentityService service) {
+
+ }
+
+ @Override
+ public void logout(UserIdentity user) {
+
+ }
+ }
+
@Override
public void setConfiguration(AuthConfiguration configuration) {
//super.setConfiguration(configuration);
initializeKeycloak();
+ // need this so that getUserPrincipal does not throw NPE
+ _loginService = new DummyLoginService();
String error = configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
setErrorPage(error);
}
diff --git a/adapters/oidc/jetty/jetty8.1/pom.xml b/adapters/oidc/jetty/jetty8.1/pom.xml
index 71d2710dec..51431bfc34 100755
--- a/adapters/oidc/jetty/jetty8.1/pom.xml
+++ b/adapters/oidc/jetty/jetty8.1/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/oidc/jetty/jetty9.1/pom.xml b/adapters/oidc/jetty/jetty9.1/pom.xml
index c489ff0c59..2263af2101 100755
--- a/adapters/oidc/jetty/jetty9.1/pom.xml
+++ b/adapters/oidc/jetty/jetty9.1/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/oidc/jetty/jetty9.2/pom.xml b/adapters/oidc/jetty/jetty9.2/pom.xml
index ae5bf10503..c090f8f581 100755
--- a/adapters/oidc/jetty/jetty9.2/pom.xml
+++ b/adapters/oidc/jetty/jetty9.2/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/oidc/jetty/pom.xml b/adapters/oidc/jetty/pom.xml
index c5d37eade8..6300953185 100755
--- a/adapters/oidc/jetty/pom.xml
+++ b/adapters/oidc/jetty/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
Keycloak Jetty Integration
diff --git a/adapters/oidc/js/pom.xml b/adapters/oidc/js/pom.xml
index e147af2aaa..315f4b7fa0 100755
--- a/adapters/oidc/js/pom.xml
+++ b/adapters/oidc/js/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/osgi-adapter/pom.xml b/adapters/oidc/osgi-adapter/pom.xml
index dd3115e431..f453ffd5f6 100755
--- a/adapters/oidc/osgi-adapter/pom.xml
+++ b/adapters/oidc/osgi-adapter/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/pom.xml b/adapters/oidc/pom.xml
index fc0e302c81..139867271f 100755
--- a/adapters/oidc/pom.xml
+++ b/adapters/oidc/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../pom.xml
Keycloak OIDC Client Adapter Modules
diff --git a/adapters/oidc/servlet-filter/pom.xml b/adapters/oidc/servlet-filter/pom.xml
index 808272c989..2e607c2b7b 100755
--- a/adapters/oidc/servlet-filter/pom.xml
+++ b/adapters/oidc/servlet-filter/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java b/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java
index 8a3010ddd9..70a67de8a5 100755
--- a/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java
+++ b/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java
@@ -89,6 +89,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
protected void cleanSession(HttpSession session) {
session.removeAttribute(KeycloakAccount.class.getName());
+ session.removeAttribute(KeycloakSecurityContext.class.getName());
clearSavedRequest(session);
}
@@ -160,6 +161,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
SerializableKeycloakAccount sAccount = new SerializableKeycloakAccount(roles, account.getPrincipal(), securityContext);
HttpSession httpSession = request.getSession();
httpSession.setAttribute(KeycloakAccount.class.getName(), sAccount);
+ httpSession.setAttribute(KeycloakSecurityContext.class.getName(), sAccount.getKeycloakSecurityContext());
if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getClientSession(), account.getPrincipal().getName(), httpSession.getId());
//String username = securityContext.getToken().getSubject();
//log.fine("userSessionManagement.login: " + username);
diff --git a/adapters/oidc/servlet-oauth-client/pom.xml b/adapters/oidc/servlet-oauth-client/pom.xml
index acebeec9f3..c3f9a9a9e0 100755
--- a/adapters/oidc/servlet-oauth-client/pom.xml
+++ b/adapters/oidc/servlet-oauth-client/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/spring-boot/pom.xml b/adapters/oidc/spring-boot/pom.xml
index 1c4998f7ed..e06f6efb94 100755
--- a/adapters/oidc/spring-boot/pom.xml
+++ b/adapters/oidc/spring-boot/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/spring-security/pom.xml b/adapters/oidc/spring-security/pom.xml
index 6943e5e9b3..5644c0fd06 100755
--- a/adapters/oidc/spring-security/pom.xml
+++ b/adapters/oidc/spring-security/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/tomcat/pom.xml b/adapters/oidc/tomcat/pom.xml
index dcbe1b4018..a3b172ad6b 100755
--- a/adapters/oidc/tomcat/pom.xml
+++ b/adapters/oidc/tomcat/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
Keycloak Tomcat Integration
diff --git a/adapters/oidc/tomcat/tomcat-core/pom.xml b/adapters/oidc/tomcat/tomcat-core/pom.xml
index 66ca303354..27f7acbd93 100755
--- a/adapters/oidc/tomcat/tomcat-core/pom.xml
+++ b/adapters/oidc/tomcat/tomcat-core/pom.xml
@@ -21,7 +21,7 @@
keycloak-tomcat-integration-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java
index 7734c2d5d5..0a07e9e6a3 100755
--- a/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java
+++ b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java
@@ -69,12 +69,22 @@ public class CatalinaSessionTokenStore extends CatalinaAdapterSessionStore imple
// just in case session got serialized
if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
- if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
+ if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) {
+ request.setAttribute(KeycloakSecurityContext.class.getName(), session);
+ request.setUserPrincipal(account.getPrincipal());
+ request.setAuthType("KEYCLOAK");
+ return;
+ }
// FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
// not be updated
boolean success = session.refreshExpiredToken(false);
- if (success && session.isActive()) return;
+ if (success && session.isActive()) {
+ request.setAttribute(KeycloakSecurityContext.class.getName(), session);
+ request.setUserPrincipal(account.getPrincipal());
+ request.setAuthType("KEYCLOAK");
+ return;
+ }
// Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
log.fine("Cleanup and expire session " + catalinaSession.getId() + " after failed refresh");
@@ -85,6 +95,8 @@ public class CatalinaSessionTokenStore extends CatalinaAdapterSessionStore imple
}
protected void cleanSession(Session catalinaSession) {
+ catalinaSession.getSession().removeAttribute(KeycloakSecurityContext.class.getName());
+ catalinaSession.getSession().removeAttribute(SerializableKeycloakAccount.class.getName());
catalinaSession.getSession().removeAttribute(OidcKeycloakAccount.class.getName());
catalinaSession.setPrincipal(null);
catalinaSession.setAuthType(null);
@@ -164,6 +176,7 @@ public class CatalinaSessionTokenStore extends CatalinaAdapterSessionStore imple
session.setPrincipal(principal);
session.setAuthType("KEYCLOAK");
session.getSession().setAttribute(SerializableKeycloakAccount.class.getName(), sAccount);
+ session.getSession().setAttribute(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
String username = securityContext.getToken().getSubject();
log.fine("userSessionManagement.login: " + username);
this.sessionManagement.login(session);
diff --git a/adapters/oidc/tomcat/tomcat6/pom.xml b/adapters/oidc/tomcat/tomcat6/pom.xml
index 6dddf972d7..8130e60fdd 100755
--- a/adapters/oidc/tomcat/tomcat6/pom.xml
+++ b/adapters/oidc/tomcat/tomcat6/pom.xml
@@ -21,7 +21,7 @@
keycloak-tomcat-integration-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/oidc/tomcat/tomcat7/pom.xml b/adapters/oidc/tomcat/tomcat7/pom.xml
index fc5e76d0de..eeed2c8c86 100755
--- a/adapters/oidc/tomcat/tomcat7/pom.xml
+++ b/adapters/oidc/tomcat/tomcat7/pom.xml
@@ -21,7 +21,7 @@
keycloak-tomcat-integration-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/oidc/tomcat/tomcat8/pom.xml b/adapters/oidc/tomcat/tomcat8/pom.xml
index 74e8d1d698..5eea623809 100755
--- a/adapters/oidc/tomcat/tomcat8/pom.xml
+++ b/adapters/oidc/tomcat/tomcat8/pom.xml
@@ -21,7 +21,7 @@
keycloak-tomcat-integration-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/oidc/undertow/pom.xml b/adapters/oidc/undertow/pom.xml
index 71f7dc78e4..4a4df89894 100755
--- a/adapters/oidc/undertow/pom.xml
+++ b/adapters/oidc/undertow/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
index 63c27d78da..5db3eadca3 100755
--- a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
@@ -92,7 +92,8 @@ public class ServletSessionTokenStore implements AdapterTokenStore {
} else {
log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session");
try {
- session.setAttribute(KeycloakUndertowAccount.class.getName(), null);
+ session.removeAttribute(KeycloakUndertowAccount.class.getName());
+ session.removeAttribute(KeycloakSecurityContext.class.getName());
session.invalidate();
} catch (Exception e) {
log.debug("Failed to invalidate session, might already be invalidated");
@@ -106,6 +107,7 @@ public class ServletSessionTokenStore implements AdapterTokenStore {
final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
HttpSession session = getSession(true);
session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
+ session.setAttribute(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
sessionManagement.login(servletRequestContext.getDeployment().getSessionManager());
}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
index de57268635..80a71099ff 100755
--- a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
@@ -22,6 +22,7 @@ import io.undertow.server.HttpServerExchange;
import io.undertow.server.session.Session;
import io.undertow.util.Sessions;
import org.jboss.logging.Logger;
+import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OidcKeycloakAccount;
@@ -82,6 +83,7 @@ public class UndertowSessionTokenStore implements AdapterTokenStore {
} else {
log.debug("Account was not active, returning false");
session.removeAttribute(KeycloakUndertowAccount.class.getName());
+ session.removeAttribute(KeycloakSecurityContext.class.getName());
session.invalidate(exchange);
return false;
}
@@ -101,6 +103,7 @@ public class UndertowSessionTokenStore implements AdapterTokenStore {
public void saveAccountInfo(OidcKeycloakAccount account) {
Session session = Sessions.getOrCreateSession(exchange);
session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
+ session.setAttribute(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
sessionManagement.login(session.getSessionManager());
}
@@ -111,6 +114,7 @@ public class UndertowSessionTokenStore implements AdapterTokenStore {
KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
if (account == null) return;
session.removeAttribute(KeycloakUndertowAccount.class.getName());
+ session.removeAttribute(KeycloakSecurityContext.class.getName());
}
@Override
diff --git a/adapters/oidc/wildfly/pom.xml b/adapters/oidc/wildfly/pom.xml
index 1e9267b2b1..69d41aa945 100755
--- a/adapters/oidc/wildfly/pom.xml
+++ b/adapters/oidc/wildfly/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
Keycloak WildFly Integration
diff --git a/adapters/oidc/wildfly/wf8-subsystem/pom.xml b/adapters/oidc/wildfly/wf8-subsystem/pom.xml
index 8d66b1d7f7..edddfed455 100755
--- a/adapters/oidc/wildfly/wf8-subsystem/pom.xml
+++ b/adapters/oidc/wildfly/wf8-subsystem/pom.xml
@@ -21,7 +21,7 @@
org.keycloak
keycloak-parent
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
diff --git a/adapters/oidc/wildfly/wildfly-adapter/pom.xml b/adapters/oidc/wildfly/wildfly-adapter/pom.xml
index fd585c4605..3f55e63c72 100755
--- a/adapters/oidc/wildfly/wildfly-adapter/pom.xml
+++ b/adapters/oidc/wildfly/wildfly-adapter/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/pom.xml b/adapters/oidc/wildfly/wildfly-subsystem/pom.xml
index d17c12e629..5d36bb813c 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/pom.xml
+++ b/adapters/oidc/wildfly/wildfly-subsystem/pom.xml
@@ -21,7 +21,7 @@
org.keycloak
keycloak-parent
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
diff --git a/adapters/pom.xml b/adapters/pom.xml
index 0003a4fc76..060978ea5d 100755
--- a/adapters/pom.xml
+++ b/adapters/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
Keycloak Integration
diff --git a/adapters/saml/as7-eap6/adapter/pom.xml b/adapters/saml/as7-eap6/adapter/pom.xml
index 43016f84c5..13787efc32 100755
--- a/adapters/saml/as7-eap6/adapter/pom.xml
+++ b/adapters/saml/as7-eap6/adapter/pom.xml
@@ -21,7 +21,7 @@
keycloak-saml-eap-integration-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/saml/as7-eap6/pom.xml b/adapters/saml/as7-eap6/pom.xml
index e9194012a8..265a1f8c6f 100755
--- a/adapters/saml/as7-eap6/pom.xml
+++ b/adapters/saml/as7-eap6/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
Keycloak SAML EAP Integration
diff --git a/adapters/saml/as7-eap6/subsystem/pom.xml b/adapters/saml/as7-eap6/subsystem/pom.xml
index 993d01f6a4..4c92714dcf 100755
--- a/adapters/saml/as7-eap6/subsystem/pom.xml
+++ b/adapters/saml/as7-eap6/subsystem/pom.xml
@@ -21,7 +21,7 @@
org.keycloak
keycloak-saml-eap-integration-pom
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
diff --git a/adapters/saml/core/pom.xml b/adapters/saml/core/pom.xml
index 7751fb2f86..27b658e24a 100755
--- a/adapters/saml/core/pom.xml
+++ b/adapters/saml/core/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/saml/jetty/jetty-core/pom.xml b/adapters/saml/jetty/jetty-core/pom.xml
index 674c74c0d4..81a081cc73 100755
--- a/adapters/saml/jetty/jetty-core/pom.xml
+++ b/adapters/saml/jetty/jetty-core/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java b/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java
index 07f2a40f0e..eb17feed55 100755
--- a/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java
+++ b/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java
@@ -18,6 +18,8 @@
package org.keycloak.adapters.saml.jetty;
import org.eclipse.jetty.security.DefaultUserIdentity;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.security.authentication.DeferredAuthentication;
@@ -135,12 +137,46 @@ public abstract class AbstractSamlAuthenticator extends LoginAuthenticator {
}
+ private class DummyLoginService implements LoginService {
+ @Override
+ public String getName() {
+ return null;
+ }
+
+ @Override
+ public UserIdentity login(String username, Object credentials) {
+ return null;
+ }
+
+ @Override
+ public boolean validate(UserIdentity user) {
+ return false;
+ }
+
+ @Override
+ public IdentityService getIdentityService() {
+ return null;
+ }
+
+ @Override
+ public void setIdentityService(IdentityService service) {
+
+ }
+
+ @Override
+ public void logout(UserIdentity user) {
+
+ }
+ }
+
@Override
public void setConfiguration(AuthConfiguration configuration) {
//super.setConfiguration(configuration);
initializeKeycloak();
+ // need this so that getUserPrincipal does not throw NPE
+ _loginService = new DummyLoginService();
String error = configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
setErrorPage(error);
}
diff --git a/adapters/saml/jetty/jetty8.1/pom.xml b/adapters/saml/jetty/jetty8.1/pom.xml
index 36f97e7154..85b49d1be8 100755
--- a/adapters/saml/jetty/jetty8.1/pom.xml
+++ b/adapters/saml/jetty/jetty8.1/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/saml/jetty/jetty9.1/pom.xml b/adapters/saml/jetty/jetty9.1/pom.xml
index 5ec3c72d7b..dd7f2b16c8 100755
--- a/adapters/saml/jetty/jetty9.1/pom.xml
+++ b/adapters/saml/jetty/jetty9.1/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/saml/jetty/jetty9.2/pom.xml b/adapters/saml/jetty/jetty9.2/pom.xml
index dd370fdba0..b194a2e87d 100755
--- a/adapters/saml/jetty/jetty9.2/pom.xml
+++ b/adapters/saml/jetty/jetty9.2/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/saml/jetty/pom.xml b/adapters/saml/jetty/pom.xml
index 70e130cd9f..62b951eaec 100755
--- a/adapters/saml/jetty/pom.xml
+++ b/adapters/saml/jetty/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
Keycloak SAML Jetty Integration
diff --git a/adapters/saml/pom.xml b/adapters/saml/pom.xml
index 22b03c22d0..646a0093c9 100755
--- a/adapters/saml/pom.xml
+++ b/adapters/saml/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../pom.xml
Keycloak SAML Client Adapter Modules
diff --git a/adapters/saml/servlet-filter/pom.xml b/adapters/saml/servlet-filter/pom.xml
index 25b8b3476c..3f1fea4262 100755
--- a/adapters/saml/servlet-filter/pom.xml
+++ b/adapters/saml/servlet-filter/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/saml/tomcat/pom.xml b/adapters/saml/tomcat/pom.xml
index be42d180fa..e863a4511b 100755
--- a/adapters/saml/tomcat/pom.xml
+++ b/adapters/saml/tomcat/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
Keycloak SAML Tomcat Integration
diff --git a/adapters/saml/tomcat/tomcat-core/pom.xml b/adapters/saml/tomcat/tomcat-core/pom.xml
index b776ec5fde..a377a941c3 100755
--- a/adapters/saml/tomcat/tomcat-core/pom.xml
+++ b/adapters/saml/tomcat/tomcat-core/pom.xml
@@ -21,7 +21,7 @@
keycloak-saml-tomcat-integration-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java b/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java
index 51bdb4b758..aa75439d11 100755
--- a/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java
+++ b/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java
@@ -167,9 +167,9 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
log.fine("*********************** SAML ************");
+ CatalinaHttpFacade facade = new CatalinaHttpFacade(response, request);
+ SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
if (request.getRequestURI().substring(request.getContextPath().length()).endsWith("/saml")) {
- CatalinaHttpFacade facade = new CatalinaHttpFacade(response, request);
- SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
if (deployment != null && deployment.isConfigured()) {
SamlSessionStore tokenStore = getSessionStore(request, facade, deployment);
SamlAuthenticator authenticator = new CatalinaSamlEndpoint(facade, deployment, tokenStore);
@@ -180,6 +180,7 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i
}
try {
+ getSessionStore(request, facade, deployment).isLoggedIn(); // sets request UserPrincipal if logged in. we do this so that the UserPrincipal is available on unsecured, unconstrainted URLs
super.invoke(request, response);
} finally {
}
diff --git a/adapters/saml/tomcat/tomcat6/pom.xml b/adapters/saml/tomcat/tomcat6/pom.xml
index 1e1c7bdc38..edac214997 100755
--- a/adapters/saml/tomcat/tomcat6/pom.xml
+++ b/adapters/saml/tomcat/tomcat6/pom.xml
@@ -21,7 +21,7 @@
keycloak-saml-tomcat-integration-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/saml/tomcat/tomcat7/pom.xml b/adapters/saml/tomcat/tomcat7/pom.xml
index ea6dd789ec..1278ec2180 100755
--- a/adapters/saml/tomcat/tomcat7/pom.xml
+++ b/adapters/saml/tomcat/tomcat7/pom.xml
@@ -21,7 +21,7 @@
keycloak-saml-tomcat-integration-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/saml/tomcat/tomcat8/pom.xml b/adapters/saml/tomcat/tomcat8/pom.xml
index 4867e82dc9..038d499888 100755
--- a/adapters/saml/tomcat/tomcat8/pom.xml
+++ b/adapters/saml/tomcat/tomcat8/pom.xml
@@ -21,7 +21,7 @@
keycloak-saml-tomcat-integration-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/saml/undertow/pom.xml b/adapters/saml/undertow/pom.xml
index 9a829188d0..74cfa78c56 100755
--- a/adapters/saml/undertow/pom.xml
+++ b/adapters/saml/undertow/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/saml/wildfly/pom.xml b/adapters/saml/wildfly/pom.xml
index 035af90d96..708a4c8ebd 100755
--- a/adapters/saml/wildfly/pom.xml
+++ b/adapters/saml/wildfly/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
Keycloak SAML Wildfly Integration
diff --git a/adapters/saml/wildfly/wildfly-adapter/pom.xml b/adapters/saml/wildfly/wildfly-adapter/pom.xml
index cd64e12d63..2dd2ed628b 100755
--- a/adapters/saml/wildfly/wildfly-adapter/pom.xml
+++ b/adapters/saml/wildfly/wildfly-adapter/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/saml/wildfly/wildfly-subsystem/pom.xml b/adapters/saml/wildfly/wildfly-subsystem/pom.xml
index 6fc49fe083..78611506b9 100755
--- a/adapters/saml/wildfly/wildfly-subsystem/pom.xml
+++ b/adapters/saml/wildfly/wildfly-subsystem/pom.xml
@@ -21,7 +21,7 @@
org.keycloak
keycloak-parent
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
diff --git a/adapters/spi/adapter-spi/pom.xml b/adapters/spi/adapter-spi/pom.xml
index d9eabbd82b..d0f283788d 100755
--- a/adapters/spi/adapter-spi/pom.xml
+++ b/adapters/spi/adapter-spi/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/spi/jboss-adapter-core/pom.xml b/adapters/spi/jboss-adapter-core/pom.xml
index f9bf0b065e..503ca0868f 100755
--- a/adapters/spi/jboss-adapter-core/pom.xml
+++ b/adapters/spi/jboss-adapter-core/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/spi/jetty-adapter-spi/pom.xml b/adapters/spi/jetty-adapter-spi/pom.xml
index 908d6c150c..568d718413 100755
--- a/adapters/spi/jetty-adapter-spi/pom.xml
+++ b/adapters/spi/jetty-adapter-spi/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/spi/pom.xml b/adapters/spi/pom.xml
index 5f7f2313cd..362a249da0 100755
--- a/adapters/spi/pom.xml
+++ b/adapters/spi/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../pom.xml
Keycloak Client Adapter SPI Modules
diff --git a/adapters/spi/servlet-adapter-spi/pom.xml b/adapters/spi/servlet-adapter-spi/pom.xml
index 45095ec8e4..dbe6e96046 100755
--- a/adapters/spi/servlet-adapter-spi/pom.xml
+++ b/adapters/spi/servlet-adapter-spi/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/spi/tomcat-adapter-spi/pom.xml b/adapters/spi/tomcat-adapter-spi/pom.xml
index d830f52546..c09447444c 100755
--- a/adapters/spi/tomcat-adapter-spi/pom.xml
+++ b/adapters/spi/tomcat-adapter-spi/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/spi/undertow-adapter-spi/pom.xml b/adapters/spi/undertow-adapter-spi/pom.xml
index 06dc8c6428..e057685c56 100755
--- a/adapters/spi/undertow-adapter-spi/pom.xml
+++ b/adapters/spi/undertow-adapter-spi/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/common/pom.xml b/common/pom.xml
index 9cbe55c6c3..ad165ad00a 100755
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/services/src/main/java/org/keycloak/messages/MessagesProvider.java b/common/src/main/java/org/keycloak/common/util/Environment.java
similarity index 72%
rename from services/src/main/java/org/keycloak/messages/MessagesProvider.java
rename to common/src/main/java/org/keycloak/common/util/Environment.java
index 4243e1e515..94a4d453fb 100644
--- a/services/src/main/java/org/keycloak/messages/MessagesProvider.java
+++ b/common/src/main/java/org/keycloak/common/util/Environment.java
@@ -15,15 +15,13 @@
* limitations under the License.
*/
-package org.keycloak.messages;
-
-import org.keycloak.provider.Provider;
+package org.keycloak.common.util;
/**
- * @author Leonardo Zanivan
+ * @author Marek Posolda
*/
-public interface MessagesProvider extends Provider {
+public class Environment {
- String getMessage(String messageKey, Object... parameters);
+ public static final boolean IS_IBM_JAVA = System.getProperty("java.vendor").contains("IBM");
}
diff --git a/common/src/main/java/org/keycloak/common/util/KerberosJdkProvider.java b/common/src/main/java/org/keycloak/common/util/KerberosJdkProvider.java
index 00a5f12885..ffd42f7bbc 100644
--- a/common/src/main/java/org/keycloak/common/util/KerberosJdkProvider.java
+++ b/common/src/main/java/org/keycloak/common/util/KerberosJdkProvider.java
@@ -56,7 +56,7 @@ public abstract class KerberosJdkProvider {
return kerberosTicketToGSSCredential(kerberosTicket, GSSCredential.DEFAULT_LIFETIME, GSSCredential.INITIATE_ONLY);
}
- // Actually same on both JDKs
+ // Actually can use same on both JDKs
public GSSCredential kerberosTicketToGSSCredential(KerberosTicket kerberosTicket, final int lifetime, final int usage) {
try {
final GSSManager gssManager = GSSManager.getInstance();
@@ -85,7 +85,7 @@ public abstract class KerberosJdkProvider {
public static KerberosJdkProvider getProvider() {
- if (KerberosSerializationUtils.JAVA_INFO.contains("IBM")) {
+ if (Environment.IS_IBM_JAVA) {
return new IBMJDKProvider();
} else {
return new SunJDKProvider();
diff --git a/core/pom.xml b/core/pom.xml
index 72768b29cf..3bcd9efd44 100755
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/core/src/main/java/org/keycloak/AbstractOAuthClient.java b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
old mode 100755
new mode 100644
index bf75b5700f..5eeb399d08
--- a/core/src/main/java/org/keycloak/AbstractOAuthClient.java
+++ b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
@@ -110,6 +110,14 @@ public class AbstractOAuthClient {
this.publicClient = publicClient;
}
+ public boolean isSecure() {
+ return isSecure;
+ }
+
+ public void setSecure(boolean secure) {
+ isSecure = secure;
+ }
+
public RelativeUrlsUsed getRelativeUrlsUsed() {
return relativeUrlsUsed;
}
diff --git a/core/src/main/java/org/keycloak/Config.java b/core/src/main/java/org/keycloak/Config.java
index f619393c66..06240065a5 100755
--- a/core/src/main/java/org/keycloak/Config.java
+++ b/core/src/main/java/org/keycloak/Config.java
@@ -135,7 +135,11 @@ public class Config {
@Override
public Boolean getBoolean(String key, Boolean defaultValue) {
String v = get(key, null);
- return v != null ? Boolean.parseBoolean(v) : defaultValue;
+ if (v != null) {
+ return Boolean.parseBoolean(v);
+ } else {
+ return defaultValue;
+ }
}
@Override
diff --git a/core/src/main/java/org/keycloak/KeyPairVerifier.java b/core/src/main/java/org/keycloak/KeyPairVerifier.java
new file mode 100644
index 0000000000..c8b6dce612
--- /dev/null
+++ b/core/src/main/java/org/keycloak/KeyPairVerifier.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016 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;
+
+import org.keycloak.common.VerificationException;
+import org.keycloak.common.util.PemUtils;
+import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class KeyPairVerifier {
+
+ public static void verify(String privateKeyPem, String publicKeyPem) throws VerificationException {
+ PrivateKey privateKey;
+ try {
+ privateKey = PemUtils.decodePrivateKey(privateKeyPem);
+ } catch (Exception e) {
+ throw new VerificationException("Failed to decode private key");
+ }
+
+ PublicKey publicKey;
+ try {
+ publicKey = PemUtils.decodePublicKey(publicKeyPem);
+ } catch (Exception e) {
+ throw new VerificationException("Failed to decode public key");
+ }
+
+ try {
+ String jws = new JWSBuilder().content("content".getBytes()).rsa256(privateKey);
+ if (!RSAProvider.verify(new JWSInput(jws), publicKey)) {
+ throw new VerificationException("Keys don't match");
+ }
+ } catch (Exception e) {
+ throw new VerificationException("Keys don't match");
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/keycloak/KeycloakSecurityContext.java b/core/src/main/java/org/keycloak/KeycloakSecurityContext.java
index 3806ffeaab..118fd1be73 100755
--- a/core/src/main/java/org/keycloak/KeycloakSecurityContext.java
+++ b/core/src/main/java/org/keycloak/KeycloakSecurityContext.java
@@ -28,6 +28,9 @@ import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
+ * Available in secured requests under HttpServlerRequest.getAttribute()
+ * Also available in HttpSession.getAttribute under the classname of this class
+ *
* @author Bill Burke
* @version $Revision: 1 $
*/
diff --git a/core/src/main/java/org/keycloak/representations/KeyStoreConfig.java b/core/src/main/java/org/keycloak/representations/KeyStoreConfig.java
new file mode 100644
index 0000000000..782669472c
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/KeyStoreConfig.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.representations;
+
+/**
+ * Configuration of KeyStore.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class KeyStoreConfig {
+
+ protected Boolean realmCertificate;
+ protected String storePassword;
+ protected String keyPassword;
+ protected String keyAlias;
+ protected String realmAlias;
+ protected String format;
+
+ public Boolean isRealmCertificate() {
+ return realmCertificate;
+ }
+
+ public void setRealmCertificate(Boolean realmCertificate) {
+ this.realmCertificate = realmCertificate;
+ }
+
+ public String getStorePassword() {
+ return storePassword;
+ }
+
+ public void setStorePassword(String storePassword) {
+ this.storePassword = storePassword;
+ }
+
+ public String getKeyPassword() {
+ return keyPassword;
+ }
+
+ public void setKeyPassword(String keyPassword) {
+ this.keyPassword = keyPassword;
+ }
+
+ public String getKeyAlias() {
+ return keyAlias;
+ }
+
+ public void setKeyAlias(String keyAlias) {
+ this.keyAlias = keyAlias;
+ }
+
+ public String getRealmAlias() {
+ return realmAlias;
+ }
+
+ public void setRealmAlias(String realmAlias) {
+ this.realmAlias = realmAlias;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+ public void setFormat(String format) {
+ this.format = format;
+ }
+}
diff --git a/core/src/test/java/org/keycloak/KeyPairVerifierTest.java b/core/src/test/java/org/keycloak/KeyPairVerifierTest.java
new file mode 100644
index 0000000000..da76a611b9
--- /dev/null
+++ b/core/src/test/java/org/keycloak/KeyPairVerifierTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016 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;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.common.VerificationException;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class KeyPairVerifierTest {
+
+ String privateKey1 = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=";
+ String publicKey1 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB";
+
+ String privateKey2048 = "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEpQIBAAKCAQEA4V3MpOnuKsdBbR1UzNjK9o5meEMQ4s5Vpykhv1DpqTilKOiE\n"
+ + "H7VQ/XtjNxw0yjnFBilCnpK6yN9mDEHbBEzaRjtdrgVhkIejiaXFBP5MBhUQ5l9u\n" + "8E3IZC3E8pwDjVF0Z9u0R4lGeUg2k6O+NKumqIvxoLCTuG0zf53bctGsRd57LuFi\n"
+ + "pgCkNyxvscOhulsbEMYrLwlb5bMGgx9v+RCnwvunNEb7RK+5pzP+iH1MRejRsX+U\n" + "7h9zHRn2gQhIl7SzG9GXebuPWr4KKwfMHWy0PEuQrsfWRXm9/dTEavbfNkv5E53z\n"
+ + "WXjWyf93ezkVhBX0YoXmf6UO7PAlvsrjno3TuwIDAQABAoIBAQC5iCAOcCtLemhp\n" + "bOlADwXgPtErFoNTROyMxjbrKrCCSIjniawj8oAvfiHq38Sx6ydBcDxREZjF/+wi\n"
+ + "ESE+hAp6ISt5NSLh+lhu3FK7TqLFqxgTn+NT36Umm+t0k231LGa5jcz3y5KCDCoq\n" + "F3ZiJCH6xeLxGA00mmn4GLvt5aF+jiO80ICGs4iUg99IoXhc5u/VU0hB5J78BinW\n"
+ + "inkCABuBNkDLgIqc9BoH4L5MOx3zDqzmHffeq9+2V4X7NiD5QyiyWtABaQpEIY5k\n" + "R48RTno6xN3hvG48/DwkO2gABSLQ/OJd3Hupv4wlmmSc1xo93CaV44hq2i2GsU1i\n"
+ + "m6d3xDW5AoGBAPCfkvPkqr88xg+8Cu3G/3GFpUsQ0VEme+8dIjXMTJHa13K7xaRh\n" + "GHCVg4a8oHJ/P/vNSwvPyR71iRX4csqkKSaprvJk8vxbU539unmHWKkfUHrywQlz\n"
+ + "q4OuXOjOdvILLOTsu3/+k6vAIE6SZJiDmf2eGxi9Qbm5rlxE3h3HRAKfAoGBAO/E\n" + "ogHV86LmnJTJbx1hP3IfRHk0qaiSj35ljlAz+3v6GN/KSUYCWTtp2GjRIKY3qQ8I\n"
+ + "7l+PVTFg3SY7cPq2C9TE+6xroiWkUd2JldPLYSxpWpFNYlo709SzmLquDho+fwJC\n" + "nAxoxKghsXJarz7TRfNyFqDXscS6oQLurU9P5lVlAoGBAJh1QvLtS5Jnu0Z06qfF\n"
+ + "kkwnVZe+TCGStKvIVciobUts0V2Mw6lnK8kJspBIK5DgN3YfmREe0lufTwBwrqre\n" + "YIRytro2ZA6o/s332ZLuwqpFgQSlktGeTGnerFeFma+6jPNvW025y27jCJVABCTu\n"
+ + "HT+oUZrXLzGyCFvF9sX/X4QZAoGBAICap4r0h0nJCBOGN+M6Vh2QR9n7NUUF15Gk\n" + "R0EdoLZO3yiqB8NVXydPDpSqFykQkc1OrQz0hG2H1xa6q07OdmoZfiRtVvt5t69s\n"
+ + "LMD9RZHcsIdfSnG7xVNBQZpf4ZCSFO3RbIH7b//+kn8TxQudptd9SkXba65prBM2\n" + "kh8IbDNBAoGAVsKvkruH7RK7CimDSWcdAKvHARqkjs/PoeKEEY8Yu6zf0Z9TQM5l\n"
+ + "uC9EwBamYcSusWRcdcz+9HYG58XFnmXq+3EUuFbJ+Ljb8YWBgePjSHDoS/6+/+zq\n" + "B1b5uQp/jYFbYQl50UPRPTF+ul1eQoy7F43Ngj3/5cDRarFZe3ZTzZo=\n"
+ + "-----END RSA PRIVATE KEY-----";
+ String publicKey2048 = "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4V3MpOnuKsdBbR1UzNjK\n"
+ + "9o5meEMQ4s5Vpykhv1DpqTilKOiEH7VQ/XtjNxw0yjnFBilCnpK6yN9mDEHbBEza\n" + "RjtdrgVhkIejiaXFBP5MBhUQ5l9u8E3IZC3E8pwDjVF0Z9u0R4lGeUg2k6O+NKum\n"
+ + "qIvxoLCTuG0zf53bctGsRd57LuFipgCkNyxvscOhulsbEMYrLwlb5bMGgx9v+RCn\n" + "wvunNEb7RK+5pzP+iH1MRejRsX+U7h9zHRn2gQhIl7SzG9GXebuPWr4KKwfMHWy0\n"
+ + "PEuQrsfWRXm9/dTEavbfNkv5E53zWXjWyf93ezkVhBX0YoXmf6UO7PAlvsrjno3T\n" + "uwIDAQAB\n" + "-----END PUBLIC KEY-----";
+
+ @Test
+ public void verify() throws Exception {
+ KeyPairVerifier.verify(privateKey1, publicKey1);
+ KeyPairVerifier.verify(privateKey2048, publicKey2048);
+
+ try {
+ KeyPairVerifier.verify(privateKey1, publicKey2048);
+ Assert.fail("Expected VerificationException");
+ } catch (VerificationException e) {
+ }
+
+ try {
+ KeyPairVerifier.verify(privateKey2048, publicKey1);
+ Assert.fail("Expected VerificationException");
+ } catch (VerificationException e) {
+ }
+ }
+
+}
diff --git a/dependencies/pom.xml b/dependencies/pom.xml
index 1682881d1a..622b1e0c2b 100755
--- a/dependencies/pom.xml
+++ b/dependencies/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index 33c80b1d18..75bda3e14b 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/dependencies/server-min/pom.xml b/dependencies/server-min/pom.xml
index fb072f63e6..126ff9e93d 100755
--- a/dependencies/server-min/pom.xml
+++ b/dependencies/server-min/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml b/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml
index a6e510deaa..33a30cd94c 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
diff --git a/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml b/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml
index bc7d813d0e..189495c85b 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml
@@ -25,7 +25,7 @@
keycloak-as7-eap6-adapter-dist-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
diff --git a/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml b/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml
index 31014069dc..a73e60832e 100755
--- a/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml
+++ b/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-as7-eap6-adapter-dist-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
diff --git a/distribution/adapters/as7-eap6-adapter/pom.xml b/distribution/adapters/as7-eap6-adapter/pom.xml
index 50c75af90d..a209f7363e 100644
--- a/distribution/adapters/as7-eap6-adapter/pom.xml
+++ b/distribution/adapters/as7-eap6-adapter/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
Keycloak AS7 / JBoss EAP 6 Adapter Distros
diff --git a/distribution/adapters/jetty81-adapter-zip/pom.xml b/distribution/adapters/jetty81-adapter-zip/pom.xml
index ee2c736c25..53cda9b1a3 100755
--- a/distribution/adapters/jetty81-adapter-zip/pom.xml
+++ b/distribution/adapters/jetty81-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
diff --git a/distribution/adapters/jetty91-adapter-zip/pom.xml b/distribution/adapters/jetty91-adapter-zip/pom.xml
index a8942941fc..23771719da 100755
--- a/distribution/adapters/jetty91-adapter-zip/pom.xml
+++ b/distribution/adapters/jetty91-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
diff --git a/distribution/adapters/jetty92-adapter-zip/pom.xml b/distribution/adapters/jetty92-adapter-zip/pom.xml
index 0dab89c396..b1f4b419c3 100755
--- a/distribution/adapters/jetty92-adapter-zip/pom.xml
+++ b/distribution/adapters/jetty92-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
diff --git a/distribution/adapters/js-adapter-zip/pom.xml b/distribution/adapters/js-adapter-zip/pom.xml
index a26f432ecb..3a2836fd79 100755
--- a/distribution/adapters/js-adapter-zip/pom.xml
+++ b/distribution/adapters/js-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
diff --git a/distribution/adapters/osgi/features/pom.xml b/distribution/adapters/osgi/features/pom.xml
index 24e77d4661..7bd2236ac6 100755
--- a/distribution/adapters/osgi/features/pom.xml
+++ b/distribution/adapters/osgi/features/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
Keycloak OSGI Features
diff --git a/distribution/adapters/osgi/jaas/pom.xml b/distribution/adapters/osgi/jaas/pom.xml
index 093a760eb8..bbb0a3a8a1 100755
--- a/distribution/adapters/osgi/jaas/pom.xml
+++ b/distribution/adapters/osgi/jaas/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
Keycloak OSGI JAAS Realm Configuration
diff --git a/distribution/adapters/osgi/pom.xml b/distribution/adapters/osgi/pom.xml
index 74c30aa57e..28299093be 100755
--- a/distribution/adapters/osgi/pom.xml
+++ b/distribution/adapters/osgi/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
Keycloak OSGI Integration
diff --git a/distribution/adapters/osgi/thirdparty/pom.xml b/distribution/adapters/osgi/thirdparty/pom.xml
index d796451996..23e2ab2ac7 100755
--- a/distribution/adapters/osgi/thirdparty/pom.xml
+++ b/distribution/adapters/osgi/thirdparty/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
diff --git a/distribution/adapters/pom.xml b/distribution/adapters/pom.xml
index 29dfce28af..91e512ffb3 100755
--- a/distribution/adapters/pom.xml
+++ b/distribution/adapters/pom.xml
@@ -20,7 +20,7 @@
keycloak-distribution-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Adapters Distribution Parent
diff --git a/distribution/adapters/tomcat6-adapter-zip/pom.xml b/distribution/adapters/tomcat6-adapter-zip/pom.xml
index 1d797e8a08..9f9101391d 100755
--- a/distribution/adapters/tomcat6-adapter-zip/pom.xml
+++ b/distribution/adapters/tomcat6-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
diff --git a/distribution/adapters/tomcat7-adapter-zip/pom.xml b/distribution/adapters/tomcat7-adapter-zip/pom.xml
index b6005afd8b..b74a7d714c 100755
--- a/distribution/adapters/tomcat7-adapter-zip/pom.xml
+++ b/distribution/adapters/tomcat7-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
diff --git a/distribution/adapters/tomcat8-adapter-zip/pom.xml b/distribution/adapters/tomcat8-adapter-zip/pom.xml
index 36dfa05fd8..2aba29c136 100755
--- a/distribution/adapters/tomcat8-adapter-zip/pom.xml
+++ b/distribution/adapters/tomcat8-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
diff --git a/distribution/adapters/wf8-adapter/pom.xml b/distribution/adapters/wf8-adapter/pom.xml
index 59cef87a8a..aa6a3d7e67 100644
--- a/distribution/adapters/wf8-adapter/pom.xml
+++ b/distribution/adapters/wf8-adapter/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
Keycloak Wildfly 8 Adapter
diff --git a/distribution/adapters/wf8-adapter/wf8-adapter-zip/pom.xml b/distribution/adapters/wf8-adapter/wf8-adapter-zip/pom.xml
index 48d91fa8a8..98d4d2d543 100755
--- a/distribution/adapters/wf8-adapter/wf8-adapter-zip/pom.xml
+++ b/distribution/adapters/wf8-adapter/wf8-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/pom.xml b/distribution/adapters/wf8-adapter/wf8-modules/pom.xml
index 1fba204728..72a2727b76 100755
--- a/distribution/adapters/wf8-adapter/wf8-modules/pom.xml
+++ b/distribution/adapters/wf8-adapter/wf8-modules/pom.xml
@@ -25,7 +25,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
diff --git a/distribution/adapters/wildfly-adapter/pom.xml b/distribution/adapters/wildfly-adapter/pom.xml
index f36704cbc1..4084253f58 100644
--- a/distribution/adapters/wildfly-adapter/pom.xml
+++ b/distribution/adapters/wildfly-adapter/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
Keycloak Wildfly Adapter
diff --git a/distribution/adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml b/distribution/adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml
index 855cd8b6d0..34c0a42fb7 100755
--- a/distribution/adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml
+++ b/distribution/adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
diff --git a/distribution/adapters/wildfly-adapter/wildfly-modules/pom.xml b/distribution/adapters/wildfly-adapter/wildfly-modules/pom.xml
index e817ec067e..4fb605e3a9 100755
--- a/distribution/adapters/wildfly-adapter/wildfly-modules/pom.xml
+++ b/distribution/adapters/wildfly-adapter/wildfly-modules/pom.xml
@@ -25,7 +25,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
diff --git a/distribution/demo-dist/pom.xml b/distribution/demo-dist/pom.xml
index 3da79e9c9c..76ec68281c 100755
--- a/distribution/demo-dist/pom.xml
+++ b/distribution/demo-dist/pom.xml
@@ -21,7 +21,7 @@
keycloak-distribution-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
keycloak-demo-dist
diff --git a/distribution/docs-dist/pom.xml b/distribution/docs-dist/pom.xml
index 44d1f743cc..a25e72dc9c 100755
--- a/distribution/docs-dist/pom.xml
+++ b/distribution/docs-dist/pom.xml
@@ -21,7 +21,7 @@
keycloak-distribution-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
keycloak-docs-dist
diff --git a/distribution/downloads/pom.xml b/distribution/downloads/pom.xml
index e2d67d760a..3e1e7f1f64 100755
--- a/distribution/downloads/pom.xml
+++ b/distribution/downloads/pom.xml
@@ -21,7 +21,7 @@
keycloak-distribution-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
keycloak-dist-downloads
diff --git a/distribution/examples-dist/pom.xml b/distribution/examples-dist/pom.xml
index f30995b72d..eb1b1ef986 100755
--- a/distribution/examples-dist/pom.xml
+++ b/distribution/examples-dist/pom.xml
@@ -21,7 +21,7 @@
keycloak-distribution-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
keycloak-examples-dist
diff --git a/distribution/feature-packs/adapter-feature-pack/pom.xml b/distribution/feature-packs/adapter-feature-pack/pom.xml
index a7c8035b01..3bced41882 100755
--- a/distribution/feature-packs/adapter-feature-pack/pom.xml
+++ b/distribution/feature-packs/adapter-feature-pack/pom.xml
@@ -19,7 +19,7 @@
org.keycloak
feature-packs-parent
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/distribution/feature-packs/pom.xml b/distribution/feature-packs/pom.xml
index 1e01087bf5..0e1c075b28 100644
--- a/distribution/feature-packs/pom.xml
+++ b/distribution/feature-packs/pom.xml
@@ -20,7 +20,7 @@
keycloak-distribution-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Feature Pack Builds
diff --git a/distribution/feature-packs/server-feature-pack/pom.xml b/distribution/feature-packs/server-feature-pack/pom.xml
index 0ddd945f5d..82f2f74b54 100644
--- a/distribution/feature-packs/server-feature-pack/pom.xml
+++ b/distribution/feature-packs/server-feature-pack/pom.xml
@@ -19,7 +19,7 @@
org.keycloak
feature-packs-parent
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/distribution/pom.xml b/distribution/pom.xml
index c4a4d6a457..707733c829 100755
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
diff --git a/distribution/proxy-dist/pom.xml b/distribution/proxy-dist/pom.xml
index 0d89c454c2..379fee3c42 100755
--- a/distribution/proxy-dist/pom.xml
+++ b/distribution/proxy-dist/pom.xml
@@ -21,7 +21,7 @@
keycloak-distribution-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
keycloak-proxy-dist
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml
index 72be75fd85..0600717437 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml
index a7a6c5b70f..4206a4ce38 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml
@@ -25,7 +25,7 @@
keycloak-saml-as7-eap6-adapter-dist-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
diff --git a/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml b/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml
index 286e25ac02..848ad41571 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-saml-as7-eap6-adapter-dist-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
diff --git a/distribution/saml-adapters/as7-eap6-adapter/pom.xml b/distribution/saml-adapters/as7-eap6-adapter/pom.xml
index 8513a5bfc1..fa1337d974 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/pom.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
Keycloak SAML AS7 / JBoss EAP 6 Adapter Distros
diff --git a/distribution/saml-adapters/jetty81-adapter-zip/pom.xml b/distribution/saml-adapters/jetty81-adapter-zip/pom.xml
index b945a2e012..33a6ab75ec 100755
--- a/distribution/saml-adapters/jetty81-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/jetty81-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
diff --git a/distribution/saml-adapters/jetty92-adapter-zip/pom.xml b/distribution/saml-adapters/jetty92-adapter-zip/pom.xml
index 0497458a14..0c790e3ba4 100755
--- a/distribution/saml-adapters/jetty92-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/jetty92-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
diff --git a/distribution/saml-adapters/pom.xml b/distribution/saml-adapters/pom.xml
index bfb566ba60..b66acfef6b 100755
--- a/distribution/saml-adapters/pom.xml
+++ b/distribution/saml-adapters/pom.xml
@@ -20,7 +20,7 @@
keycloak-distribution-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
SAML Adapters Distribution Parent
diff --git a/distribution/saml-adapters/tomcat6-adapter-zip/pom.xml b/distribution/saml-adapters/tomcat6-adapter-zip/pom.xml
index 3a44ec56cc..025a9019df 100755
--- a/distribution/saml-adapters/tomcat6-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/tomcat6-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
diff --git a/distribution/saml-adapters/tomcat7-adapter-zip/pom.xml b/distribution/saml-adapters/tomcat7-adapter-zip/pom.xml
index 607744a8e0..de004749e6 100755
--- a/distribution/saml-adapters/tomcat7-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/tomcat7-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
diff --git a/distribution/saml-adapters/tomcat8-adapter-zip/pom.xml b/distribution/saml-adapters/tomcat8-adapter-zip/pom.xml
index b9704e05c7..25f58b7ebf 100755
--- a/distribution/saml-adapters/tomcat8-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/tomcat8-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
diff --git a/distribution/saml-adapters/wildfly-adapter/pom.xml b/distribution/saml-adapters/wildfly-adapter/pom.xml
index 95efce0942..8f13b8ad07 100755
--- a/distribution/saml-adapters/wildfly-adapter/pom.xml
+++ b/distribution/saml-adapters/wildfly-adapter/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../pom.xml
Keycloak Wildfly SAML Adapter
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml
index 8edb2e9ad7..cf95ea22fc 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml
index cfa403150e..061928efe0 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml
@@ -25,7 +25,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../../../pom.xml
diff --git a/distribution/server-dist/pom.xml b/distribution/server-dist/pom.xml
index 698fbdeb68..ac12d0e6ec 100755
--- a/distribution/server-dist/pom.xml
+++ b/distribution/server-dist/pom.xml
@@ -21,7 +21,7 @@
keycloak-distribution-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
keycloak-server-dist
diff --git a/distribution/server-overlay/pom.xml b/distribution/server-overlay/pom.xml
index 3164f32070..d4a3bb535c 100755
--- a/distribution/server-overlay/pom.xml
+++ b/distribution/server-overlay/pom.xml
@@ -21,7 +21,7 @@
keycloak-distribution-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
keycloak-server-overlay
diff --git a/distribution/src-dist/pom.xml b/distribution/src-dist/pom.xml
index acfdcebef2..4057c7eb7e 100755
--- a/distribution/src-dist/pom.xml
+++ b/distribution/src-dist/pom.xml
@@ -21,7 +21,7 @@
keycloak-distribution-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
keycloak-src-dist
diff --git a/docbook/auth-server-docs/pom.xml b/docbook/auth-server-docs/pom.xml
index 0e352107b3..7365106aef 100755
--- a/docbook/auth-server-docs/pom.xml
+++ b/docbook/auth-server-docs/pom.xml
@@ -21,7 +21,7 @@
keycloak-docbook-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/adapter-context.xml b/docbook/auth-server-docs/reference/en/en-US/modules/adapter-context.xml
new file mode 100755
index 0000000000..cfcf18fe87
--- /dev/null
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/adapter-context.xml
@@ -0,0 +1,12 @@
+
+ KeycloakSecurityContext
+
+ The KeycloakSecurityContext interface is available if you need to look at the access token directly. This context is also useful if you need to
+ get the encoded access token so you can make additional REST invocations. In servlet environments it is available in secured invocations as an attribute in HttpServletRequest.
+ Or, it is available in secure and insecure requests in the HttpSession for browser apps.
+
+ httpServletRequest.getAttribute(KeycloakSecurityContext.class.getName());
+ httpServletRequest.getSession().getAttribute(KeycloakSecurityContext.class.getName());
+
+
+
\ No newline at end of file
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/cache.xml b/docbook/auth-server-docs/reference/en/en-US/modules/cache.xml
index 4d68eb4c4e..dec9ecfe42 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/cache.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/cache.xml
@@ -22,12 +22,22 @@
(realm, application, client, roles, etc...) and one for users. These caches greatly improves the performance of the server.
+
+ Eviction and Expiration
+
+
+ By default the user cache contains a maximum of 10000 entries. This is not 10000 users, but 10000 entries in the cache. You can change the maximum
+ number of entries by editing the server configuration standalone.xml or standalone-ha.xml.
+ Locate the element cache-container name="keycloak" and change the eviction policy for the users cache. For
+ more information see Infinispan Subsystem documentation.
+
+
+
Disabling Caches
- The realm and user caches can be cleared through the management console. To
- disable the realm or user cache, you must edit the keycloak-server.json file
- in your distribution. Here's what the config looks like initially.
+ To disable the realm or user cache, you must edit the keycloak-server.json file
+ in your distribution. Here's what the config looks like initially.
- You must then change it to:
+ To disable the cache set the enabled field to false for the cache you want to disable:
+
Clear Caches
- To clear the realm or user cache, go to the Keycloak admin console Realm Settings->Cache Config page. Disable the cache
- you want. This will cause the cache to be cleared.
+ To clear the realm or user cache, go to the Keycloak admin console Realm Settings->Cache Config page. On this page you can clear the realm cache
+ or the user cache. This will clear the caches for all realms and not only the selected realm.
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/clustering.xml b/docbook/auth-server-docs/reference/en/en-US/modules/clustering.xml
index 850326eb44..cb36fc4c09 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/clustering.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/clustering.xml
@@ -57,6 +57,29 @@
database. This can be a relational database or Mongo. To make sure your database doesn't become a single
point of failure you may also want to deploy your database to a cluster.
+
+ DB lock
+ Note that Keycloak supports concurrent startup by more cluster nodes at the same. This is ensured by DB lock, which prevents that some
+ startup actions (migrating database from previous version, importing realms at startup, initial bootstrap of admin user) are always executed just by one
+ cluster node at a time and other cluster nodes need to wait until the current node finishes startup actions and release the DB lock.
+
+
+ By default, the maximum timeout for lock is 900 seconds, so in case that second node is not able to acquire the lock within 900 seconds, it fails to start.
+ The lock checking is done every 2 seconds by default. Typically you won't need to increase/decrease the default value, but just in case
+ it's possible to configure it in standalone/configuration/keycloak-server.json:
+
+
+
+ or similarly if you're using Mongo (just by replace jpa with mongo)
+
+
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml b/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml
index 4090712bc1..74a3e27a0e 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml
@@ -30,7 +30,7 @@
To use this adapter, you must first configure an application (or client) through the Keycloak Admin Console.
- You should select public for the Client Type field. As public clients can't
+ You should select public for the Access Type field. As public clients can't
be verified with a client secret, you are required to configure one or more valid redirect uris.
Once you've configured the application, click on the Installation tab and download the keycloak.json
file. This file should be hosted on your web-server at the same root as your HTML pages. Alternatively, you can manually
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/kerberos.xml b/docbook/auth-server-docs/reference/en/en-US/modules/kerberos.xml
index e89df835ea..1d5a1c9beb 100644
--- a/docbook/auth-server-docs/reference/en/en-US/modules/kerberos.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/kerberos.xml
@@ -154,7 +154,7 @@ ktadd -k /tmp/http.keytab HTTP/www.mydomain.org@MYDOMAIN.ORG
- Finally run Keycloak server and configure SPNEGO/Kerberos authentication in Keycloak admin console. Keycloak supports Kerberos authentication
+ Run Keycloak server and configure SPNEGO/Kerberos authentication in Keycloak admin console. Keycloak supports Kerberos authentication
through Federation provider SPI . We have 2 federation providers with Kerberos authentication support:
@@ -185,6 +185,20 @@ ktadd -k /tmp/http.keytab HTTP/www.mydomain.org@MYDOMAIN.ORG
+
+
+ Finally you may need to check the Kerberos authenticator correctly configured. You can go to Authentication tab in
+ admin console and select Browser flow. Here you will see Kerberos authenticator, which is used by Keycloak for SPNEGO
+ handshake with client (exchange Negotiate header etc.). By default it's disabled, so Keycloak doesn't ask for Negotiate header, however once you
+ configured federation provider in previous step, it's automatically switched to ALTERNATIVE. So defacto you don't need to do anything, just
+ check that it's really switched to Alternative.
+
+
+ Alternative means that Keycloak tries to ask browser for Negotiate header, but if it's not available, it will continue on next authenticator (which usually means
+ displaying username/password form to user). You can switch to REQUIRED if you want to enforce login with
+ kerberos ticket and not allow fallback to username/password form.
+
+
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
index aceff9ba96..9d3237d50d 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
@@ -128,7 +128,13 @@ bin/add-user.[sh|bat] -r master -u -p
]]>
Then restart the server.
-
+
+ For keycloak-overlay, please make sure to use:
+ -p
+]]>
+
+
Relational Database Configuration
@@ -214,8 +220,8 @@ bin/add-user.[sh|bat] -r master -u -p
Tested databases
Here is list of RDBMS databases and corresponding JDBC drivers, which were tested with Keycloak. Note that Hibernate dialect
- is usually set automatically according to your database, but in some cases, you must manually set the proper dialect,
- as the default dialect may not work correctly. You can setup dialect by adding property driverDialect
+ is usually set automatically according to your database, but you have possibility to override if default dialect doesn't work correctly.
+ You can setup dialect by adding property driverDialect
to the keycloak-server.json into connectionsJpa section (see above).
Tested databases
@@ -255,7 +261,7 @@ bin/add-user.[sh|bat] -r master -u -p
Microsoft SQL Server 2012
Microsoft SQL Server JDBC Driver 4.0.2206.100
- org.hibernate.dialect.SQLServer2008Dialect
+ auto
IBM DB2 10.5
@@ -832,7 +838,7 @@ $ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificat
Themes sections for more information on how to do this.
-
+
Installing Keycloak Server as Root Context
@@ -855,11 +861,11 @@ $ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificat
If you have run your server before altering the root context, your database
will contain references to the old /auth context. Your clients may also have incorrect
- references. To fix this on the server side, you will need to export
- your database to json, make corrections, and then import. Client-side keycloak.json
+ references. To fix this on the server side, you will need to export
+ your database to json, make corrections, and then import. Client-side keycloak.json
files will need to be updated manually as well.
-
+
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/user-federation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/user-federation.xml
index f44e6ab7fe..7cbd9668f0 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/user-federation.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/user-federation.xml
@@ -294,8 +294,24 @@
We have an example, which is showing LDAP integration and set of base mappers and sample mappers (mappers for street and postalCode) . It's in examples/ldap
in the Keycloak example distribution or demo distribution download. You can also check the example sources directly here .
+
+ Writing your own LDAP Mapper
+
+ For the more advanced usecases, you have the possibility to create your own implementation of LDAP mapper or just subclass from
+ some already existing mapper implementation. You will need to implement UserFederationMapperFactory interface. In most cases, instead of
+ creating UserFederationMapperFactory from scratch, you can create subclasses of AbstractLDAPFederationMapperFactory, which itself
+ implements UserFederationMapperFactory. Then you need to create mapper implementation, which will be subclass of
+ AbstractLDAPFederationMapper (this mapper implementation will be returned by YourAbstractLDAPFederationMapperFactorySubclass.createMapper method).
+
+
+ After your code is written you must package up all your classes within a JAR file. This jar file must contain a file called
+ org.keycloak.mappers.UserFederationMapperFactory within the META-INF/services directory of the JAR. This file is a list of fully
+ qualified classnames of all implementations of UserFederationMapperFactory. For more details, look at section for
+ Write your own federation provider and at Providers and SPI section.
+
+
-
+
Writing your own User Federation Provider
The keycloak examples directory contains an example of a simple User Federation Provider backed by
diff --git a/docbook/pom.xml b/docbook/pom.xml
index a0c9c0aba2..fc9e55beca 100755
--- a/docbook/pom.xml
+++ b/docbook/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
Keycloak Documentation
diff --git a/docbook/saml-adapter-docs/pom.xml b/docbook/saml-adapter-docs/pom.xml
index 65411ec7d7..3d418b6ad0 100755
--- a/docbook/saml-adapter-docs/pom.xml
+++ b/docbook/saml-adapter-docs/pom.xml
@@ -21,7 +21,7 @@
keycloak-docbook-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
diff --git a/examples/admin-client/pom.xml b/examples/admin-client/pom.xml
index a93ce0a07a..f210c62f6f 100755
--- a/examples/admin-client/pom.xml
+++ b/examples/admin-client/pom.xml
@@ -22,7 +22,7 @@
keycloak-examples-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Keycloak Examples - Admin Client
diff --git a/examples/basic-auth/pom.xml b/examples/basic-auth/pom.xml
index 6e457ab480..df1a594257 100755
--- a/examples/basic-auth/pom.xml
+++ b/examples/basic-auth/pom.xml
@@ -23,7 +23,7 @@
keycloak-examples-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Keycloak Examples - Basic Auth
diff --git a/examples/broker/facebook-authentication/pom.xml b/examples/broker/facebook-authentication/pom.xml
index 975968bda0..cdd609c7f1 100755
--- a/examples/broker/facebook-authentication/pom.xml
+++ b/examples/broker/facebook-authentication/pom.xml
@@ -23,7 +23,7 @@
keycloak-examples-broker-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Keycloak Broker Examples - Facebook Authentication
diff --git a/examples/broker/google-authentication/pom.xml b/examples/broker/google-authentication/pom.xml
index be7106600f..a7dd2fad68 100755
--- a/examples/broker/google-authentication/pom.xml
+++ b/examples/broker/google-authentication/pom.xml
@@ -23,7 +23,7 @@
keycloak-examples-broker-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Keycloak Broker Examples - Google Authentication
diff --git a/examples/broker/pom.xml b/examples/broker/pom.xml
index b9aba5ae5c..b1049173d0 100755
--- a/examples/broker/pom.xml
+++ b/examples/broker/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Broker Examples
diff --git a/examples/broker/saml-broker-authentication/pom.xml b/examples/broker/saml-broker-authentication/pom.xml
index aa6ee4754b..c5640ae351 100755
--- a/examples/broker/saml-broker-authentication/pom.xml
+++ b/examples/broker/saml-broker-authentication/pom.xml
@@ -23,7 +23,7 @@
keycloak-examples-broker-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Keycloak Broker Examples - SAML Identity Provider Brokering
diff --git a/examples/broker/twitter-authentication/pom.xml b/examples/broker/twitter-authentication/pom.xml
index 372ce2b8bf..a4a6ac74a4 100755
--- a/examples/broker/twitter-authentication/pom.xml
+++ b/examples/broker/twitter-authentication/pom.xml
@@ -23,7 +23,7 @@
keycloak-examples-broker-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Keycloak Broker Examples - Twitter Authentication
diff --git a/examples/cors/angular-product-app/pom.xml b/examples/cors/angular-product-app/pom.xml
index b4dbf087c5..b67e4de8ea 100755
--- a/examples/cors/angular-product-app/pom.xml
+++ b/examples/cors/angular-product-app/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-cors-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/cors/database-service/pom.xml b/examples/cors/database-service/pom.xml
index df482becf6..6e4fc7d538 100755
--- a/examples/cors/database-service/pom.xml
+++ b/examples/cors/database-service/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-cors-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/cors/pom.xml b/examples/cors/pom.xml
index 6613cb76fe..f0a52d5400 100755
--- a/examples/cors/pom.xml
+++ b/examples/cors/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Keycloak Examples - CORS
diff --git a/examples/demo-template/admin-access-app/pom.xml b/examples/demo-template/admin-access-app/pom.xml
index 74f5ad3e13..1a5f4606d2 100755
--- a/examples/demo-template/admin-access-app/pom.xml
+++ b/examples/demo-template/admin-access-app/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-demo-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/demo-template/angular-product-app/pom.xml b/examples/demo-template/angular-product-app/pom.xml
index 14871df396..fa8d7988ce 100755
--- a/examples/demo-template/angular-product-app/pom.xml
+++ b/examples/demo-template/angular-product-app/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-demo-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/demo-template/customer-app-cli/pom.xml b/examples/demo-template/customer-app-cli/pom.xml
index b15672a245..b6091bc9d2 100755
--- a/examples/demo-template/customer-app-cli/pom.xml
+++ b/examples/demo-template/customer-app-cli/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-demo-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/demo-template/customer-app-filter/pom.xml b/examples/demo-template/customer-app-filter/pom.xml
index 6c141fa8d0..c28fb7ea1f 100755
--- a/examples/demo-template/customer-app-filter/pom.xml
+++ b/examples/demo-template/customer-app-filter/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-demo-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/demo-template/customer-app-js/pom.xml b/examples/demo-template/customer-app-js/pom.xml
index d0fd979ff4..7f5a1bb510 100755
--- a/examples/demo-template/customer-app-js/pom.xml
+++ b/examples/demo-template/customer-app-js/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-demo-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/demo-template/customer-app/pom.xml b/examples/demo-template/customer-app/pom.xml
index 4ad3339fdc..479e28258f 100755
--- a/examples/demo-template/customer-app/pom.xml
+++ b/examples/demo-template/customer-app/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-demo-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/demo-template/database-service/pom.xml b/examples/demo-template/database-service/pom.xml
index 298929fee4..e209f26a75 100755
--- a/examples/demo-template/database-service/pom.xml
+++ b/examples/demo-template/database-service/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-demo-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/demo-template/example-ear/pom.xml b/examples/demo-template/example-ear/pom.xml
index 6c03852ed4..4926f3d580 100755
--- a/examples/demo-template/example-ear/pom.xml
+++ b/examples/demo-template/example-ear/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-demo-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/demo-template/offline-access-app/pom.xml b/examples/demo-template/offline-access-app/pom.xml
index 990cea28d8..3965a25869 100644
--- a/examples/demo-template/offline-access-app/pom.xml
+++ b/examples/demo-template/offline-access-app/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-demo-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/demo-template/pom.xml b/examples/demo-template/pom.xml
index eb5b8c883a..2da8e1aebf 100755
--- a/examples/demo-template/pom.xml
+++ b/examples/demo-template/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Examples
diff --git a/examples/demo-template/product-app/pom.xml b/examples/demo-template/product-app/pom.xml
index c055d20493..736f0f9aa7 100755
--- a/examples/demo-template/product-app/pom.xml
+++ b/examples/demo-template/product-app/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-demo-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/demo-template/service-account/pom.xml b/examples/demo-template/service-account/pom.xml
index 4dbe8a4c98..a6a006c8e8 100644
--- a/examples/demo-template/service-account/pom.xml
+++ b/examples/demo-template/service-account/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-demo-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/demo-template/third-party-cdi/pom.xml b/examples/demo-template/third-party-cdi/pom.xml
index 8ee3f8614d..af43b9ca9d 100755
--- a/examples/demo-template/third-party-cdi/pom.xml
+++ b/examples/demo-template/third-party-cdi/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-demo-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/demo-template/third-party/pom.xml b/examples/demo-template/third-party/pom.xml
index f15a607280..8b824585ac 100755
--- a/examples/demo-template/third-party/pom.xml
+++ b/examples/demo-template/third-party/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-demo-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/fuse/camel/pom.xml b/examples/fuse/camel/pom.xml
index 5edfef7ef2..e70fb23fed 100755
--- a/examples/fuse/camel/pom.xml
+++ b/examples/fuse/camel/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-fuse-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/fuse/customer-app-fuse/pom.xml b/examples/fuse/customer-app-fuse/pom.xml
index 218535103b..64e4a71119 100755
--- a/examples/fuse/customer-app-fuse/pom.xml
+++ b/examples/fuse/customer-app-fuse/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-fuse-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/fuse/cxf-jaxrs/pom.xml b/examples/fuse/cxf-jaxrs/pom.xml
index 67462c5ce4..4600f11cb5 100755
--- a/examples/fuse/cxf-jaxrs/pom.xml
+++ b/examples/fuse/cxf-jaxrs/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-fuse-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/fuse/cxf-jaxws/pom.xml b/examples/fuse/cxf-jaxws/pom.xml
index 37e10b959d..494d7ef4cd 100755
--- a/examples/fuse/cxf-jaxws/pom.xml
+++ b/examples/fuse/cxf-jaxws/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-fuse-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/fuse/features/pom.xml b/examples/fuse/features/pom.xml
index a82fc2b0b8..22664f3d0f 100755
--- a/examples/fuse/features/pom.xml
+++ b/examples/fuse/features/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-fuse-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/fuse/pom.xml b/examples/fuse/pom.xml
index fa5b5644e9..1d89e0431e 100755
--- a/examples/fuse/pom.xml
+++ b/examples/fuse/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Fuse examples
diff --git a/examples/fuse/product-app-fuse/pom.xml b/examples/fuse/product-app-fuse/pom.xml
index ca7f49b780..4de29b374f 100755
--- a/examples/fuse/product-app-fuse/pom.xml
+++ b/examples/fuse/product-app-fuse/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-fuse-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/fuse/testrealm.json b/examples/fuse/testrealm.json
index de93f7d597..88afec8315 100644
--- a/examples/fuse/testrealm.json
+++ b/examples/fuse/testrealm.json
@@ -69,7 +69,8 @@
],
"realmRoles": [ "user","admin" ],
"clientRoles": {
- "realm-management": [ "realm-admin" ]
+ "realm-management": [ "realm-admin" ],
+ "account": [ "manage-account" ]
}
},
{
diff --git a/examples/js-console/pom.xml b/examples/js-console/pom.xml
index 8a6c157126..e9a784d7ee 100755
--- a/examples/js-console/pom.xml
+++ b/examples/js-console/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/kerberos/pom.xml b/examples/kerberos/pom.xml
index 226ea634be..ca1b196e2e 100755
--- a/examples/kerberos/pom.xml
+++ b/examples/kerberos/pom.xml
@@ -22,7 +22,7 @@
keycloak-examples-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Keycloak Examples - Kerberos Credential Delegation
diff --git a/examples/ldap/pom.xml b/examples/ldap/pom.xml
index 2a09808d99..f16e8b0fa2 100644
--- a/examples/ldap/pom.xml
+++ b/examples/ldap/pom.xml
@@ -22,7 +22,7 @@
keycloak-examples-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/examples/multi-tenant/pom.xml b/examples/multi-tenant/pom.xml
index 0d1e380591..d6271f0171 100755
--- a/examples/multi-tenant/pom.xml
+++ b/examples/multi-tenant/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Keycloak Examples - Multi Tenant
diff --git a/examples/pom.xml b/examples/pom.xml
index dd91c30dc1..3d55a42b5e 100755
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Examples
diff --git a/examples/providers/authenticator/pom.xml b/examples/providers/authenticator/pom.xml
index 35f30a0c17..0097cae445 100755
--- a/examples/providers/authenticator/pom.xml
+++ b/examples/providers/authenticator/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-providers-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Authenticator Example
diff --git a/examples/providers/event-listener-sysout/pom.xml b/examples/providers/event-listener-sysout/pom.xml
index 8050ef784c..81a5e501df 100755
--- a/examples/providers/event-listener-sysout/pom.xml
+++ b/examples/providers/event-listener-sysout/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-providers-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Event Listener System.out Example
diff --git a/examples/providers/event-store-mem/pom.xml b/examples/providers/event-store-mem/pom.xml
index be6d6365cf..189438e9b7 100755
--- a/examples/providers/event-store-mem/pom.xml
+++ b/examples/providers/event-store-mem/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-providers-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Event Store In-Mem Example
diff --git a/examples/providers/federation-provider/pom.xml b/examples/providers/federation-provider/pom.xml
index 8823071c78..78f7986703 100755
--- a/examples/providers/federation-provider/pom.xml
+++ b/examples/providers/federation-provider/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-providers-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Properties Authentication Provider Example
diff --git a/examples/providers/pom.xml b/examples/providers/pom.xml
index 1c3c4189b3..0735a2a074 100755
--- a/examples/providers/pom.xml
+++ b/examples/providers/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Provider Examples
diff --git a/examples/saml/pom.xml b/examples/saml/pom.xml
index 74c2ba5bd1..88b9ada642 100755
--- a/examples/saml/pom.xml
+++ b/examples/saml/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Provider Examples
diff --git a/examples/saml/post-with-encryption/pom.xml b/examples/saml/post-with-encryption/pom.xml
index 2f84cb59bf..73e5191e0a 100755
--- a/examples/saml/post-with-encryption/pom.xml
+++ b/examples/saml/post-with-encryption/pom.xml
@@ -22,7 +22,7 @@
keycloak-examples-saml-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
saml-post-encryption
diff --git a/examples/saml/post-with-signature/pom.xml b/examples/saml/post-with-signature/pom.xml
index 6f0740bcea..a1bd6117ed 100755
--- a/examples/saml/post-with-signature/pom.xml
+++ b/examples/saml/post-with-signature/pom.xml
@@ -22,7 +22,7 @@
keycloak-examples-saml-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
sales-post-sig
diff --git a/examples/saml/redirect-with-signature/pom.xml b/examples/saml/redirect-with-signature/pom.xml
index f069408f80..cf3be5ace2 100755
--- a/examples/saml/redirect-with-signature/pom.xml
+++ b/examples/saml/redirect-with-signature/pom.xml
@@ -22,7 +22,7 @@
keycloak-examples-saml-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
saml-redirect-signatures
diff --git a/examples/saml/servlet-filter/pom.xml b/examples/saml/servlet-filter/pom.xml
index c67f6d4ede..bf05a91dcf 100755
--- a/examples/saml/servlet-filter/pom.xml
+++ b/examples/saml/servlet-filter/pom.xml
@@ -22,7 +22,7 @@
keycloak-examples-saml-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
saml-servlet-filter
diff --git a/examples/themes/pom.xml b/examples/themes/pom.xml
index 6a5ce62fbf..b0ebc4d01b 100755
--- a/examples/themes/pom.xml
+++ b/examples/themes/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
Themes Examples
diff --git a/federation/kerberos/pom.xml b/federation/kerberos/pom.xml
index 8634b56dd1..cdb65d2c1c 100755
--- a/federation/kerberos/pom.xml
+++ b/federation/kerberos/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml
index a47374cadc..7fe4b65196 100755
--- a/federation/ldap/pom.xml
+++ b/federation/ldap/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java
index 15af1332a4..eb8eaaab27 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java
@@ -131,7 +131,12 @@ public class LDAPConfig {
public boolean isPagination() {
String pagination = config.get(LDAPConstants.PAGINATION);
- return pagination==null ? false : Boolean.parseBoolean(pagination);
+ return Boolean.parseBoolean(pagination);
+ }
+
+ public int getBatchSizeForSync() {
+ String pageSizeConfig = config.get(LDAPConstants.BATCH_SIZE_FOR_SYNC);
+ return pageSizeConfig!=null ? Integer.parseInt(pageSizeConfig) : LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC;
}
public String getUsernameLdapAttribute() {
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index e800bc3929..4ddd142f23 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -27,6 +27,7 @@ import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilde
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
+import org.keycloak.federation.ldap.mappers.LDAPMappersComparator;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
@@ -47,6 +48,7 @@ import org.keycloak.services.managers.UserManager;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -128,7 +130,8 @@ public class LDAPFederationProvider implements UserFederationProvider {
}
Set federationMappers = realm.getUserFederationMappersByFederationProvider(model.getId());
- for (UserFederationMapperModel mapperModel : federationMappers) {
+ List sortedMappers = sortMappersAsc(federationMappers);
+ for (UserFederationMapperModel mapperModel : sortedMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied, realm);
}
@@ -176,8 +179,8 @@ public class LDAPFederationProvider implements UserFederationProvider {
@Override
public boolean removeUser(RealmModel realm, UserModel user) {
if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) {
- logger.warnf("User '%s' can't be deleted in LDAP as editMode is '%s'", user.getUsername(), editMode.toString());
- return false;
+ logger.warnf("User '%s' can't be deleted in LDAP as editMode is '%s'. Deleting user just from Keycloak DB, but he will be re-imported from LDAP again once searched in Keycloak", user.getUsername(), editMode.toString());
+ return true;
}
LDAPObject ldapObject = loadAndValidateUser(realm, user);
@@ -313,7 +316,8 @@ public class LDAPFederationProvider implements UserFederationProvider {
imported.setEnabled(true);
Set federationMappers = realm.getUserFederationMappersByFederationProvider(getModel().getId());
- for (UserFederationMapperModel mapperModel : federationMappers) {
+ List sortedMappers = sortMappersDesc(federationMappers);
+ for (UserFederationMapperModel mapperModel : sortedMappers) {
if (logger.isTraceEnabled()) {
logger.tracef("Using mapper %s during import user from LDAP", mapperModel);
}
@@ -517,4 +521,13 @@ public class LDAPFederationProvider implements UserFederationProvider {
return ldapMapper;
}
+
+
+ public List sortMappersAsc(Collection mappers) {
+ return LDAPMappersComparator.sortAsc(getLdapIdentityStore().getConfig(), mappers);
+ }
+
+ protected List sortMappersDesc(Collection mappers) {
+ return LDAPMappersComparator.sortDesc(getLdapIdentityStore().getConfig(), mappers);
+ }
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
index f6e4026be7..51a3c8cb70 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
@@ -34,6 +34,7 @@ import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory;
import org.keycloak.federation.ldap.mappers.msad.MSADUserAccountControlMapperFactory;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -46,6 +47,7 @@ import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
+import org.keycloak.models.UserFederationValidatingProviderFactory;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -59,7 +61,7 @@ import java.util.Set;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class LDAPFederationProviderFactory extends UserFederationEventAwareProviderFactory {
+public class LDAPFederationProviderFactory extends UserFederationEventAwareProviderFactory implements UserFederationValidatingProviderFactory {
private static final Logger logger = Logger.getLogger(LDAPFederationProviderFactory.class);
public static final String PROVIDER_NAME = LDAPConstants.LDAP_PROVIDER;
@@ -76,6 +78,13 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
return new LDAPFederationProvider(this, session, model, ldapIdentityStore);
}
+ @Override
+ public void validateConfig(RealmModel realm, UserFederationProviderModel providerModel) throws FederationConfigValidationException {
+ LDAPConfig cfg = new LDAPConfig(providerModel.getConfig());
+ String customFilter = cfg.getCustomUserSearchFilter();
+ LDAPUtils.validateCustomLdapFilter(customFilter);
+ }
+
@Override
public void init(Config.Scope config) {
this.ldapStoreRegistry = new LDAPIdentityStoreRegistry();
@@ -156,7 +165,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
// For read-only LDAP, we map "cn" as full name
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", newProviderModel.getId(), FullNameLDAPFederationMapperFactory.PROVIDER_ID,
FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN,
- UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
+ FullNameLDAPFederationMapper.READ_ONLY, readOnly,
+ FullNameLDAPFederationMapper.WRITE_ONLY, "false");
realm.addUserFederationMapper(mapperModel);
}
}
@@ -274,11 +284,10 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
final UserFederationSyncResult syncResult = new UserFederationSyncResult();
- boolean pagination = Boolean.parseBoolean(fedModel.getConfig().get(LDAPConstants.PAGINATION));
+ LDAPConfig ldapConfig = new LDAPConfig(fedModel.getConfig());
+ boolean pagination = ldapConfig.isPagination();
if (pagination) {
-
- String pageSizeConfig = fedModel.getConfig().get(LDAPConstants.BATCH_SIZE_FOR_SYNC);
- int pageSize = pageSizeConfig!=null ? Integer.parseInt(pageSizeConfig) : LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC;
+ int pageSize = ldapConfig.getBatchSizeForSync();
boolean nextPage = true;
while (nextPage) {
@@ -354,7 +363,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
// Update keycloak user
Set federationMappers = currentRealm.getUserFederationMappersByFederationProvider(fedModel.getId());
- for (UserFederationMapperModel mapperModel : federationMappers) {
+ List sortedMappers = ldapFedProvider.sortMappersDesc(federationMappers);
+ for (UserFederationMapperModel mapperModel : sortedMappers) {
LDAPFederationMapper ldapMapper = ldapFedProvider.getMapper(mapperModel);
ldapMapper.onImportUserFromLDAP(mapperModel, ldapFedProvider, ldapUser, currentUser, currentRealm, false);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
index a37362532b..e406c11d3f 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
@@ -19,6 +19,8 @@ package org.keycloak.federation.ldap;
import java.util.Collection;
import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -30,6 +32,7 @@ import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilde
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
@@ -58,7 +61,8 @@ public class LDAPUtils {
ldapUser.setObjectClasses(ldapConfig.getUserObjectClasses());
Set federationMappers = realm.getUserFederationMappersByFederationProvider(ldapProvider.getModel().getId());
- for (UserFederationMapperModel mapperModel : federationMappers) {
+ List sortedMappers = ldapProvider.sortMappersAsc(federationMappers);
+ for (UserFederationMapperModel mapperModel : sortedMappers) {
LDAPFederationMapper ldapMapper = ldapProvider.getMapper(mapperModel);
ldapMapper.onRegisterUserToLDAP(mapperModel, ldapProvider, ldapUser, user, realm);
}
@@ -224,4 +228,58 @@ public class LDAPUtils {
public static String getMemberValueOfChildObject(LDAPObject ldapUser, MembershipType membershipType) {
return membershipType == MembershipType.DN ? ldapUser.getDn().toString() : ldapUser.getAttributeAsString(ldapUser.getRdnAttributeName());
}
+
+
+ /**
+ * Load all LDAP objects corresponding to given query. We will load them paginated, so we allow to bypass the limitation of 1000
+ * maximum loaded objects in single query in MSAD
+ *
+ * @param ldapQuery
+ * @param ldapProvider
+ * @return
+ */
+ public static List loadAllLDAPObjects(LDAPQuery ldapQuery, LDAPFederationProvider ldapProvider) {
+ LDAPConfig ldapConfig = ldapProvider.getLdapIdentityStore().getConfig();
+ boolean pagination = ldapConfig.isPagination();
+ if (pagination) {
+ // For now reuse globally configured batch size in LDAP provider page
+ int pageSize = ldapConfig.getBatchSizeForSync();
+
+ List result = new LinkedList<>();
+ boolean nextPage = true;
+
+ while (nextPage) {
+ ldapQuery.setLimit(pageSize);
+ final List currentPageGroups = ldapQuery.getResultList();
+ result.addAll(currentPageGroups);
+ nextPage = ldapQuery.getPaginationContext() != null;
+ }
+
+ return result;
+ } else {
+ // LDAP pagination not available. Do everything in single transaction
+ return ldapQuery.getResultList();
+ }
+ }
+
+
+ /**
+ * Validate configured customFilter matches the requested format
+ *
+ * @param customFilter
+ * @throws FederationConfigValidationException
+ */
+ public static void validateCustomLdapFilter(String customFilter) throws FederationConfigValidationException {
+ if (customFilter != null) {
+
+ customFilter = customFilter.trim();
+ if (customFilter.isEmpty()) {
+ return;
+ }
+
+ if (!customFilter.startsWith("(") || !customFilter.endsWith(")")) {
+ throw new FederationConfigValidationException("ldapErrorInvalidCustomFilter");
+ }
+ }
+ }
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java
index dea173f075..957ba33b3a 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java
@@ -153,7 +153,8 @@ public class LDAPQuery {
public List getResultList() {
// Apply mappers now
- for (UserFederationMapperModel mapperModel : mappers) {
+ List sortedMappers = ldapFedProvider.sortMappersAsc(mappers);
+ for (UserFederationMapperModel mapperModel : sortedMappers) {
LDAPFederationMapper fedMapper = ldapFedProvider.getMapper(mapperModel);
fedMapper.beforeLDAPQuery(mapperModel, this);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQueryConditionsBuilder.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQueryConditionsBuilder.java
index 0ba9a39094..4be4ba41c5 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQueryConditionsBuilder.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQueryConditionsBuilder.java
@@ -61,9 +61,6 @@ public class LDAPQueryConditionsBuilder {
public Condition addCustomLDAPFilter(String filter) {
filter = filter.trim();
- if (!filter.startsWith("(") || !filter.endsWith(")")) {
- throw new ModelException("Custom filter doesn't start with ( or doesn't end with ). ");
- }
return new CustomLDAPFilter(filter);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
index b8adbdd4ef..9a9b929839 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
@@ -220,6 +220,8 @@ public class LDAPIdentityStore implements IdentityStore {
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);
operationManager.modifyAttribute(userDN, mod0);
+ } catch (ModelException me) {
+ throw me;
} catch (Exception e) {
throw new ModelException("Error updating password.", e);
}
@@ -240,6 +242,8 @@ public class LDAPIdentityStore implements IdentityStore {
modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, unicodePwd));
operationManager.modifyAttributes(userDN, modItems.toArray(new ModificationItem[] {}));
+ } catch (ModelException me) {
+ throw me;
} catch (Exception e) {
throw new ModelException(e);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
index 933d614b32..d681125f90 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
@@ -20,7 +20,7 @@ package org.keycloak.federation.ldap.mappers;
import org.keycloak.Config;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.mappers.UserFederationMapperFactory;
import org.keycloak.models.KeycloakSession;
@@ -85,10 +85,10 @@ public abstract class AbstractLDAPFederationMapperFactory implements UserFederat
return configProperty;
}
- protected void checkMandatoryConfigAttribute(String name, String displayName, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ protected void checkMandatoryConfigAttribute(String name, String displayName, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
String attrConfigValue = mapperModel.getConfig().get(name);
if (attrConfigValue == null || attrConfigValue.trim().isEmpty()) {
- throw new MapperConfigValidationException("Missing configuration for '" + displayName + "'");
+ throw new FederationConfigValidationException("Missing configuration for '" + displayName + "'");
}
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java
index 113a57d325..b94b5b497c 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java
@@ -40,6 +40,8 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
public static final String LDAP_FULL_NAME_ATTRIBUTE = "ldap.full.name.attribute";
public static final String READ_ONLY = "read.only";
+ public static final String WRITE_ONLY = "write.only";
+
public FullNameLDAPFederationMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) {
super(mapperModel, ldapProvider, realm);
@@ -47,6 +49,10 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
@Override
public void onImportUserFromLDAP(LDAPObject ldapUser, UserModel user, boolean isCreate) {
+ if (isWriteOnly()) {
+ return;
+ }
+
String ldapFullNameAttrName = getLdapFullNameAttrName();
String fullName = ldapUser.getAttributeAsString(ldapFullNameAttrName);
if (fullName == null) {
@@ -117,6 +123,10 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
@Override
public void beforeLDAPQuery(LDAPQuery query) {
+ if (isWriteOnly()) {
+ return;
+ }
+
String ldapFullNameAttrName = getLdapFullNameAttrName();
query.addReturningLdapAttribute(ldapFullNameAttrName);
@@ -178,4 +188,8 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
private boolean isReadOnly() {
return parseBooleanParameter(mapperModel, READ_ONLY);
}
+
+ private boolean isWriteOnly() {
+ return parseBooleanParameter(mapperModel, WRITE_ONLY);
+ }
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java
index 32b0acd024..32826b2dda 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java
@@ -24,7 +24,7 @@ import java.util.Map;
import org.keycloak.federation.ldap.LDAPConfig;
import org.keycloak.federation.ldap.LDAPFederationProvider;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
@@ -43,12 +43,17 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM
static {
ProviderConfigProperty userModelAttribute = createConfigProperty(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute",
- "Name of LDAP attribute, which contains fullName of user. In most cases it will be 'cn' ", ProviderConfigProperty.STRING_TYPE, null);
+ "Name of LDAP attribute, which contains fullName of user. Usually it will be 'cn' ", ProviderConfigProperty.STRING_TYPE, null);
configProperties.add(userModelAttribute);
- ProviderConfigProperty readOnly = createConfigProperty(UserAttributeLDAPFederationMapper.READ_ONLY, "Read Only",
+ ProviderConfigProperty readOnly = createConfigProperty(FullNameLDAPFederationMapper.READ_ONLY, "Read Only",
"For Read-only is data imported from LDAP to Keycloak DB, but it's not saved back to LDAP when user is updated in Keycloak.", ProviderConfigProperty.BOOLEAN_TYPE, null);
configProperties.add(readOnly);
+
+ ProviderConfigProperty writeOnly = createConfigProperty(FullNameLDAPFederationMapper.WRITE_ONLY, "Write Only",
+ "For Write-only is data propagated to LDAP when user is created or updated in Keycloak. But this mapper is not used to propagate data from LDAP back into Keycloak. " +
+ "This setting is useful if you configured separate firstName and lastName attribute mappers and you want to use those to read attribute from LDAP into Keycloak", ProviderConfigProperty.BOOLEAN_TYPE, null);
+ configProperties.add(writeOnly);
}
@Override
@@ -78,8 +83,11 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM
defaultValues.put(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN);
- String readOnly = config.getEditMode() == UserFederationProvider.EditMode.WRITABLE ? "false" : "true";
- defaultValues.put(UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
+ boolean readOnly = config.getEditMode() != UserFederationProvider.EditMode.WRITABLE;
+ defaultValues.put(FullNameLDAPFederationMapper.READ_ONLY, String.valueOf(readOnly));
+
+ String writeOnly = String.valueOf(!readOnly);
+ defaultValues.put(FullNameLDAPFederationMapper.WRITE_ONLY, writeOnly);
return defaultValues;
}
@@ -90,8 +98,21 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM
}
@Override
- public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
checkMandatoryConfigAttribute(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute", mapperModel);
+
+ boolean readOnly = AbstractLDAPFederationMapper.parseBooleanParameter(mapperModel, FullNameLDAPFederationMapper.READ_ONLY);
+ boolean writeOnly = AbstractLDAPFederationMapper.parseBooleanParameter(mapperModel, FullNameLDAPFederationMapper.WRITE_ONLY);
+
+ LDAPConfig cfg = new LDAPConfig(fedProviderModel.getConfig());
+ UserFederationProvider.EditMode editMode = cfg.getEditMode();
+
+ if (writeOnly && cfg.getEditMode() != UserFederationProvider.EditMode.WRITABLE) {
+ throw new FederationConfigValidationException("ldapErrorCantWriteOnlyForReadOnlyLdap");
+ }
+ if (writeOnly && readOnly) {
+ throw new FederationConfigValidationException("ldapErrorCantWriteOnlyAndReadOnly");
+ }
}
@Override
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/HardcodedLDAPRoleMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/HardcodedLDAPRoleMapperFactory.java
index ae07c0f3dd..1ca93f5b2a 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/HardcodedLDAPRoleMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/HardcodedLDAPRoleMapperFactory.java
@@ -23,7 +23,7 @@ import java.util.List;
import java.util.Map;
import org.keycloak.federation.ldap.LDAPFederationProvider;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationMapperModel;
@@ -77,14 +77,14 @@ public class HardcodedLDAPRoleMapperFactory extends AbstractLDAPFederationMapper
}
@Override
- public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
String roleName = mapperModel.getConfig().get(HardcodedLDAPRoleMapper.ROLE);
if (roleName == null) {
- throw new MapperConfigValidationException("Role can't be null");
+ throw new FederationConfigValidationException("Role can't be null");
}
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) {
- throw new MapperConfigValidationException("There is no role corresponding to configured value");
+ throw new FederationConfigValidationException("There is no role corresponding to configured value");
}
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPMappersComparator.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPMappersComparator.java
new file mode 100644
index 0000000000..c5507e62c5
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPMappersComparator.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2016 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.federation.ldap.mappers;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.keycloak.federation.ldap.LDAPConfig;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * TODO: Possibly add "priority" to UserFederationMapper instead of hardcoding behaviour
+ *
+ * @author Marek Posolda
+ */
+public class LDAPMappersComparator {
+
+ public static List sortAsc(LDAPConfig ldapConfig, Collection mappers) {
+ Comparator comparator = new ImportantFirstComparator(ldapConfig);
+
+ List result = new ArrayList<>(mappers);
+ Collections.sort(result, comparator);
+ return result;
+ }
+
+ public static List sortDesc(LDAPConfig ldapConfig, Collection mappers) {
+ Comparator comparator = new ImportantFirstComparator(ldapConfig).reversed();
+
+ List result = new ArrayList<>(mappers);
+ Collections.sort(result, comparator);
+ return result;
+ }
+
+
+ private static class ImportantFirstComparator implements Comparator {
+
+ private final LDAPConfig ldapConfig;
+
+ public ImportantFirstComparator(LDAPConfig ldapConfig) {
+ this.ldapConfig = ldapConfig;
+ }
+
+ @Override
+ public int compare(UserFederationMapperModel o1, UserFederationMapperModel o2) {
+ // UserAttributeLDAPFederationMapper first
+ boolean isO1AttrMapper = o1.getFederationMapperType().equals(UserAttributeLDAPFederationMapperFactory.PROVIDER_ID);
+ boolean isO2AttrMapper = o2.getFederationMapperType().equals(UserAttributeLDAPFederationMapperFactory.PROVIDER_ID);
+ if (!isO1AttrMapper) {
+ if (isO2AttrMapper) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else if (!isO2AttrMapper) {
+ return -1;
+ }
+
+ // Mapper for "username" attribute first
+ String model1 = o1.getConfig().get(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE);
+ String model2 = o2.getConfig().get(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE);
+ boolean isO1UsernameMapper = model1 != null && model1.equalsIgnoreCase(UserModel.USERNAME);
+ boolean isO2UsernameMapper = model2 != null && model2.equalsIgnoreCase(UserModel.USERNAME);
+ if (!isO1UsernameMapper) {
+ if (isO2UsernameMapper) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else if (!isO2UsernameMapper) {
+ return -1;
+ }
+
+ // The username mapper corresponding to the same like configured username for federationProvider is first
+ String o1LdapAttr = o1.getConfig().get(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE);
+ String o2LdapAttr = o2.getConfig().get(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE);
+ boolean isO1LdapAttr = o1LdapAttr != null && ldapConfig.getUsernameLdapAttribute().equalsIgnoreCase(o1LdapAttr);
+ boolean isO2LdapAttr = o2LdapAttr != null && ldapConfig.getUsernameLdapAttribute().equalsIgnoreCase(o2LdapAttr);
+
+ if (!isO1LdapAttr) {
+ if (isO2LdapAttr) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else if (!isO2LdapAttr) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ }
+
+
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java
index 6a85623045..b0a7faae4a 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java
@@ -24,7 +24,7 @@ import java.util.Map;
import org.keycloak.federation.ldap.LDAPConfig;
import org.keycloak.federation.ldap.LDAPFederationProvider;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
@@ -101,7 +101,7 @@ public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFedera
}
@Override
- public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "User Model Attribute", mapperModel);
checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "LDAP Attribute", mapperModel);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java
index 2c1c048c08..cd220b292a 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java
@@ -27,6 +27,7 @@ import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
+import org.keycloak.federation.ldap.LDAPConfig;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.federation.ldap.idm.model.LDAPDn;
@@ -41,9 +42,11 @@ import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode;
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy;
import org.keycloak.models.GroupModel;
+import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -149,8 +152,7 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl
logger.debugf("Syncing groups from LDAP into Keycloak DB. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getDisplayName());
// Get all LDAP groups
- LDAPQuery ldapQuery = createGroupQuery();
- List ldapGroups = ldapQuery.getResultList();
+ List ldapGroups = getAllLDAPGroups();
// Convert to internal format
Map ldapGroupsMap = new HashMap<>();
@@ -286,29 +288,46 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl
}
}
- // Override if better effectivity or different algorithm is needed
+
protected GroupModel findKcGroupByLDAPGroup(LDAPObject ldapGroup) {
String groupNameAttr = config.getGroupNameLdapAttribute();
String groupName = ldapGroup.getAttributeAsString(groupNameAttr);
- List groups = realm.getGroups();
- for (GroupModel group : groups) {
- if (group.getName().equals(groupName)) {
- return group;
+ if (config.isPreserveGroupsInheritance()) {
+ // Override if better effectivity or different algorithm is needed
+ List groups = realm.getGroups();
+ for (GroupModel group : groups) {
+ if (group.getName().equals(groupName)) {
+ return group;
+ }
}
- }
- return null;
+ return null;
+ } else {
+ // Without preserved inheritance, it's always top-level group
+ return KeycloakModelUtils.findGroupByPath(realm, "/" + groupName);
+ }
}
protected GroupModel findKcGroupOrSyncFromLDAP(LDAPObject ldapGroup, UserModel user) {
GroupModel kcGroup = findKcGroupByLDAPGroup(ldapGroup);
if (kcGroup == null) {
- // Sync groups from LDAP
- if (!syncFromLDAPPerformedInThisTransaction) {
- syncDataFromFederationProviderToKeycloak();
- kcGroup = findKcGroupByLDAPGroup(ldapGroup);
+
+ if (config.isPreserveGroupsInheritance()) {
+
+ // Better to sync all groups from LDAP with preserved inheritance
+ if (!syncFromLDAPPerformedInThisTransaction) {
+ syncDataFromFederationProviderToKeycloak();
+ kcGroup = findKcGroupByLDAPGroup(ldapGroup);
+ }
+ } else {
+ String groupNameAttr = config.getGroupNameLdapAttribute();
+ String groupName = ldapGroup.getAttributeAsString(groupNameAttr);
+
+ kcGroup = realm.createGroup(groupName);
+ updateAttributesOfKCGroup(kcGroup, ldapGroup);
+ realm.moveGroup(kcGroup, null);
}
// Could theoretically happen on some LDAP servers if 'memberof' style is used and 'memberof' attribute of user references non-existing group
@@ -321,6 +340,12 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl
return kcGroup;
}
+ // Send LDAP query to retrieve all groups
+ protected List getAllLDAPGroups() {
+ LDAPQuery ldapGroupQuery = createGroupQuery();
+ return LDAPUtils.loadAllLDAPObjects(ldapGroupQuery, ldapProvider);
+ }
+
// Sync from Keycloak to LDAP
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java
index 0304788550..169875d0a6 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java
@@ -26,6 +26,7 @@ import java.util.Map;
import org.keycloak.federation.ldap.LDAPConfig;
import org.keycloak.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory;
import org.keycloak.federation.ldap.mappers.membership.CommonLDAPGroupMapperConfig;
@@ -33,7 +34,7 @@ import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode;
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy;
import org.keycloak.federation.ldap.mappers.membership.role.RoleMapperConfig;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
@@ -87,9 +88,9 @@ public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapp
for (MembershipType membershipType : MembershipType.values()) {
membershipTypes.add(membershipType.toString());
}
- ProviderConfigProperty membershipType = createConfigProperty(RoleMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE, "Membership Attribute Type",
- "DN means that LDAP role has it's members declared in form of their full DN. For example 'member: uid=john,ou=users,dc=example,dc=com' . " +
- "UID means that LDAP role has it's members declared in form of pure user uids. For example 'memberUid: john' .",
+ ProviderConfigProperty membershipType = createConfigProperty(GroupMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE, "Membership Attribute Type",
+ "DN means that LDAP group has it's members declared in form of their full DN. For example 'member: uid=john,ou=users,dc=example,dc=com' . " +
+ "UID means that LDAP group has it's members declared in form of pure user uids. For example 'memberUid: john' .",
ProviderConfigProperty.LIST_TYPE, membershipTypes);
configProperties.add(membershipType);
@@ -164,6 +165,7 @@ public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapp
defaultValues.put(GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "true");
defaultValues.put(GroupMapperConfig.MEMBERSHIP_LDAP_ATTRIBUTE, LDAPConstants.MEMBER);
+ defaultValues.put(GroupMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE, MembershipType.DN.toString());
String mode = config.getEditMode() == UserFederationProvider.EditMode.WRITABLE ? LDAPGroupMapperMode.LDAP_ONLY.toString() : LDAPGroupMapperMode.READ_ONLY.toString();
defaultValues.put(GroupMapperConfig.MODE, mode);
@@ -185,7 +187,7 @@ public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapp
}
@Override
- public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
checkMandatoryConfigAttribute(GroupMapperConfig.GROUPS_DN, "LDAP Groups DN", mapperModel);
checkMandatoryConfigAttribute(GroupMapperConfig.MODE, "Mode", mapperModel);
@@ -193,8 +195,10 @@ public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapp
MembershipType membershipType = mt==null ? MembershipType.DN : Enum.valueOf(MembershipType.class, mt);
boolean preserveGroupInheritance = Boolean.parseBoolean(mapperModel.getConfig().get(GroupMapperConfig.PRESERVE_GROUP_INHERITANCE));
if (preserveGroupInheritance && membershipType != MembershipType.DN) {
- throw new MapperConfigValidationException("Not possible to preserve group inheritance and use UID membership type together");
+ throw new FederationConfigValidationException("ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType");
}
+
+ LDAPUtils.validateCustomLdapFilter(mapperModel.getConfig().get(GroupMapperConfig.GROUPS_LDAP_FILTER));
}
@Override
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java
index cbafcc4cee..9dbeec95cc 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java
@@ -122,9 +122,9 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper imple
logger.debugf("Syncing roles from LDAP into Keycloak DB. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getDisplayName());
- // Send LDAP query
- LDAPQuery ldapQuery = createRoleQuery();
- List ldapRoles = ldapQuery.getResultList();
+ // Send LDAP query to load all roles
+ LDAPQuery ldapRoleQuery = createRoleQuery();
+ List ldapRoles = LDAPUtils.loadAllLDAPObjects(ldapRoleQuery, ldapProvider);
RoleContainerModel roleContainer = getTargetRoleContainer();
String rolesRdnAttr = config.getRoleNameLdapAttribute();
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java
index ee5140b6e8..25a7ad1bc8 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java
@@ -26,12 +26,14 @@ import java.util.Map;
import org.keycloak.federation.ldap.LDAPConfig;
import org.keycloak.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory;
import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode;
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.federation.ldap.mappers.membership.group.GroupMapperConfig;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
@@ -178,7 +180,7 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
}
@Override
- public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
checkMandatoryConfigAttribute(RoleMapperConfig.ROLES_DN, "LDAP Roles DN", mapperModel);
checkMandatoryConfigAttribute(RoleMapperConfig.MODE, "Mode", mapperModel);
@@ -187,14 +189,11 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
if (!useRealmMappings) {
String clientId = mapperModel.getConfig().get(RoleMapperConfig.CLIENT_ID);
if (clientId == null || clientId.trim().isEmpty()) {
- throw new MapperConfigValidationException("Client ID needs to be provided in config when Realm Roles Mapping is not used");
+ throw new FederationConfigValidationException("ldapErrorMissingClientId");
}
}
- String customLdapFilter = mapperModel.getConfig().get(RoleMapperConfig.ROLES_LDAP_FILTER);
- if ((customLdapFilter != null && customLdapFilter.trim().length() > 0) && (!customLdapFilter.startsWith("(") || !customLdapFilter.endsWith(")"))) {
- throw new MapperConfigValidationException("Custom Roles LDAP filter must starts with '(' and ends with ')'");
- }
+ LDAPUtils.validateCustomLdapFilter(mapperModel.getConfig().get(RoleMapperConfig.ROLES_LDAP_FILTER));
}
@Override
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapper.java
index 88f12fbd1e..033b3f40f8 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapper.java
@@ -30,6 +30,7 @@ import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper;
import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationMapperModel;
@@ -48,6 +49,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
private static final Logger logger = Logger.getLogger(MSADUserAccountControlMapper.class);
private static final Pattern AUTH_EXCEPTION_REGEX = Pattern.compile(".*AcceptSecurityContext error, data ([0-9a-f]*), v.*");
+ private static final Pattern AUTH_INVALID_NEW_PASSWORD = Pattern.compile(".*error code ([0-9a-f]+) .*WILL_NOT_PERFORM.*");
public MSADUserAccountControlMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) {
super(mapperModel, ldapProvider, realm);
@@ -94,7 +96,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
}
protected boolean processAuthErrorCode(String errorCode, UserModel user) {
- logger.debugf("MSAD Error code is '%s' after failed LDAP login of user", errorCode, user.getUsername());
+ logger.debugf("MSAD Error code is '%s' after failed LDAP login of user '%s'", errorCode, user.getUsername());
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE) {
if (errorCode.equals("532") || errorCode.equals("773")) {
@@ -105,6 +107,8 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
// User is disabled in MSAD. Set him to disabled in KC as well
user.setEnabled(false);
return true;
+ } else if (errorCode.equals("775")) {
+ logger.warnf("Locked user '%s' attempt to login", user.getUsername());
}
}
@@ -112,6 +116,22 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
}
+ protected ModelException processFailedPasswordUpdateException(ModelException e) {
+ String exceptionMessage = e.getCause().getMessage().replace('\n', ' ');
+ Matcher m = AUTH_INVALID_NEW_PASSWORD.matcher(exceptionMessage);
+ if (m.matches()) {
+ String errorCode = m.group(1);
+ if (errorCode.equals("53")) {
+ ModelException me = new ModelException("invalidPasswordRegexPatternMessage", e);
+ me.setParameters(new Object[]{"passwordConstraintViolation"});
+ return me;
+ }
+ }
+
+ return e;
+ }
+
+
public class MSADUserModelDelegate extends UserModelDelegate {
private final LDAPObject ldapUser;
@@ -156,7 +176,12 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
@Override
public void updateCredential(UserCredentialModel cred) {
// Update LDAP password first
- super.updateCredential(cred);
+ try {
+ super.updateCredential(cred);
+ } catch (ModelException me) {
+ me = processFailedPasswordUpdateException(me);
+ throw me;
+ }
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && cred.getType().equals(UserCredentialModel.PASSWORD)) {
logger.debugf("Going to update userAccountControl for ldap user '%s' after successful password update", ldapUser.getDn().toString());
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapperFactory.java
index 2ccc96e1ff..36c494dcdd 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapperFactory.java
@@ -25,7 +25,7 @@ import java.util.Map;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
@@ -75,7 +75,7 @@ public class MSADUserAccountControlMapperFactory extends AbstractLDAPFederationM
}
@Override
- public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
}
@Override
diff --git a/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPMappersComparatorTest.java b/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPMappersComparatorTest.java
new file mode 100644
index 0000000000..7ca7ff2a88
--- /dev/null
+++ b/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPMappersComparatorTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2016 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.federation.ldap.idm.model;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.federation.ldap.LDAPConfig;
+import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
+import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory;
+import org.keycloak.federation.ldap.mappers.LDAPMappersComparator;
+import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper;
+import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+/**
+ * @author Marek Posolda
+ */
+public class LDAPMappersComparatorTest {
+
+
+
+ @Test
+ public void testCompareWithCNUsername() {
+ Map cfg = new HashMap<>();
+ cfg.put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, LDAPConstants.CN);
+ LDAPConfig config = new LDAPConfig(cfg);
+
+ List sorted = LDAPMappersComparator.sortAsc(config, getMappers());
+ assertOrder(sorted, "username-cn", "sAMAccountName", "first name", "full name");
+
+ sorted = LDAPMappersComparator.sortDesc(config, getMappers());
+ assertOrder(sorted, "full name", "first name", "sAMAccountName", "username-cn");
+ }
+
+ @Test
+ public void testCompareWithSAMAccountNameUsername() {
+ Map cfg = new HashMap<>();
+ cfg.put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, LDAPConstants.SAM_ACCOUNT_NAME);
+ LDAPConfig config = new LDAPConfig(cfg);
+
+ List sorted = LDAPMappersComparator.sortAsc(config, getMappers());
+ assertOrder(sorted, "sAMAccountName", "username-cn", "first name", "full name");
+
+ sorted = LDAPMappersComparator.sortDesc(config, getMappers());
+ assertOrder(sorted, "full name", "first name", "username-cn", "sAMAccountName");
+ }
+
+ private void assertOrder(List result, String... names) {
+ Assert.assertEquals(result.size(), names.length);
+ for (int i=0 ; i getMappers() {
+ Set result = new HashSet<>();
+
+ UserFederationMapperModel mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", "fed-provider", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
+ UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
+ UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
+ UserAttributeLDAPFederationMapper.READ_ONLY, "true",
+ UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "true",
+ UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
+ mapperModel.setId("idd1");
+ result.add(mapperModel);
+
+ mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username-cn", "fed-provider", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
+ UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
+ UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
+ UserAttributeLDAPFederationMapper.READ_ONLY, "true",
+ UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
+ UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
+ mapperModel.setId("idd2");
+ result.add(mapperModel);
+
+ mapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", "fed-provider", FullNameLDAPFederationMapperFactory.PROVIDER_ID,
+ FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN,
+ UserAttributeLDAPFederationMapper.READ_ONLY, "true");
+ mapperModel.setId("idd3");
+ result.add(mapperModel);
+
+ mapperModel = KeycloakModelUtils.createUserFederationMapperModel("sAMAccountName", "fed-provider", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
+ UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
+ UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SAM_ACCOUNT_NAME,
+ UserAttributeLDAPFederationMapper.READ_ONLY, "false",
+ UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
+ UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
+ mapperModel.setId("idd4");
+ result.add(mapperModel);
+
+ return result;
+ }
+}
diff --git a/federation/pom.xml b/federation/pom.xml
index c60cd1c52d..14d3532f01 100755
--- a/federation/pom.xml
+++ b/federation/pom.xml
@@ -22,7 +22,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/integration/admin-client/pom.xml b/integration/admin-client/pom.xml
index 00b5c27082..3a360639cd 100755
--- a/integration/admin-client/pom.xml
+++ b/integration/admin-client/pom.xml
@@ -22,7 +22,7 @@
keycloak-integration-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
@@ -55,6 +55,11 @@
resteasy-client
provided
+
+ org.jboss.resteasy
+ resteasy-multipart-provider
+ provided
+
org.jboss.resteasy
resteasy-jackson2-provider
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientAttributeCertificateResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientAttributeCertificateResource.java
new file mode 100644
index 0000000000..30d3bfb469
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientAttributeCertificateResource.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.admin.client.resource;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
+import org.keycloak.representations.KeyStoreConfig;
+import org.keycloak.representations.idm.CertificateRepresentation;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public interface ClientAttributeCertificateResource {
+
+ /**
+ * Get key info
+ *
+ * @return
+ */
+ @GET
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public CertificateRepresentation getKeyInfo();
+
+ /**
+ * Generate a new certificate with new key pair
+ *
+ * @return
+ */
+ @POST
+ @NoCache
+ @Path("generate")
+ @Produces(MediaType.APPLICATION_JSON)
+ public CertificateRepresentation generate();
+
+ /**
+ * Upload certificate and eventually private key
+ *
+ * @param uriInfo
+ * @param input
+ * @return
+ */
+ @POST
+ @Path("upload")
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
+ @Produces(MediaType.APPLICATION_JSON)
+ public CertificateRepresentation uploadJks(@Context final UriInfo uriInfo, MultipartFormDataInput input);
+
+ /**
+ * Upload only certificate, not private key
+ *
+ * @param uriInfo
+ * @param input
+ * @return
+ */
+ @POST
+ @Path("upload-certificate")
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
+ @Produces(MediaType.APPLICATION_JSON)
+ public CertificateRepresentation uploadJksCertificate(@Context final UriInfo uriInfo, MultipartFormDataInput input);
+
+ /**
+ * Get a keystore file for the client, containing private key and public certificate
+ *
+ * @param config Keystore configuration as JSON
+ * @return
+ */
+ @POST
+ @NoCache
+ @Path("/download")
+ @Produces(MediaType.APPLICATION_OCTET_STREAM)
+ @Consumes(MediaType.APPLICATION_JSON)
+ public byte[] getKeystore(final KeyStoreConfig config);
+
+ /**
+ * Generate a new keypair and certificate, and get the private key file
+ *
+ * Generates a keypair and certificate and serves the private key in a specified keystore format.
+ * Only generated public certificate is saved in Keycloak DB - the private key is not.
+ *
+ * @param config Keystore configuration as JSON
+ * @return
+ */
+ @POST
+ @NoCache
+ @Path("/generate-and-download")
+ @Produces(MediaType.APPLICATION_OCTET_STREAM)
+ @Consumes(MediaType.APPLICATION_JSON)
+ public byte[] generateAndGetKeystore(final KeyStoreConfig config);
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientResource.java
index ee1c69ad26..644cc7a8e6 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientResource.java
@@ -34,7 +34,6 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.util.List;
import java.util.Map;
-import java.util.Set;
/**
* @author rodrigo.sasaki@icarros.com.br
@@ -55,21 +54,6 @@ public interface ClientResource {
@DELETE
public void remove();
- @GET
- @Path("allowed-origins")
- @Produces(MediaType.APPLICATION_JSON)
- public Set getAllowedOrigins();
-
- @PUT
- @Path("allowed-origins")
- @Consumes(MediaType.APPLICATION_JSON)
- public void updateAllowedOrigins(Set allowedOrigins);
-
- @DELETE
- @Path("allowed-origins")
- @Consumes(MediaType.APPLICATION_JSON)
- public void removeAllowedOrigins(Set originsToRemove);
-
@POST
@Path("client-secret")
@Produces(MediaType.APPLICATION_JSON)
@@ -80,19 +64,31 @@ public interface ClientResource {
@Produces(MediaType.APPLICATION_JSON)
public CredentialRepresentation getSecret();
+ /**
+ * Generate a new registration access token for the client
+ *
+ * @return
+ */
+ @Path("registration-access-token")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.APPLICATION_JSON)
+ public ClientRepresentation regenerateRegistrationAccessToken();
+
+ /**
+ * Get representation of certificate resource
+ *
+ * @param attributePrefix
+ * @return
+ */
+ @Path("certificates/{attr}")
+ public ClientAttributeCertificateResource getCertficateResource(@PathParam("attr") String attributePrefix);
+
@GET
@NoCache
@Path("installation/providers/{providerId}")
public String getInstallationProvider(@PathParam("providerId") String providerId);
- @POST
- @Path("logout-all")
- public void logoutAllUsers();
-
- @POST
- @Path("logout-user/{username}")
- public void logoutUser(@PathParam("username") String username);
-
@Path("session-count")
@GET
@Produces(MediaType.APPLICATION_JSON)
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
index 15ca073e04..86548ecf84 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
@@ -149,7 +149,7 @@ public interface RealmResource {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response partialImport(PartialImportRepresentation rep);
-
+
@Path("authentication")
@Consumes(MediaType.APPLICATION_JSON)
AuthenticationManagementResource flows();
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java
index ede14795db..02056a7e2c 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java
@@ -22,6 +22,7 @@ import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
import org.keycloak.admin.client.Config;
import org.keycloak.admin.client.resource.BasicAuthFilter;
+import org.keycloak.common.util.Time;
import org.keycloak.representations.AccessTokenResponse;
import javax.ws.rs.BadRequestException;
@@ -34,8 +35,11 @@ import java.util.Date;
*/
public class TokenManager {
+ private static final long DEFAULT_MIN_VALIDITY = 30;
+
private AccessTokenResponse currentToken;
- private Date expirationTime;
+ private long expirationTime;
+ private long minTokenValidity = DEFAULT_MIN_VALIDITY;
private final Config config;
private final ResteasyClient client;
@@ -73,10 +77,11 @@ public class TokenManager {
TokenService tokenService = target.proxy(TokenService.class);
- AccessTokenResponse response = tokenService.grantToken(config.getRealm(), form.asMap());
+ int requestTime = Time.currentTime();
+ currentToken = tokenService.grantToken(config.getRealm(), form.asMap());
+ expirationTime = requestTime + currentToken.getExpiresIn();
- defineCurrentToken(response);
- return response;
+ return currentToken;
}
public AccessTokenResponse refreshToken(){
@@ -95,27 +100,22 @@ public class TokenManager {
TokenService tokenService = target.proxy(TokenService.class);
try {
- AccessTokenResponse response = tokenService.refreshToken(config.getRealm(), form.asMap());
- defineCurrentToken(response);
- return response;
+ int requestTime = Time.currentTime();
+ currentToken = tokenService.refreshToken(config.getRealm(), form.asMap());
+ expirationTime = requestTime + currentToken.getExpiresIn();
+
+ return currentToken;
} catch (BadRequestException e) {
return grantToken();
}
}
- private void setExpirationTime() {
- Calendar cal = Calendar.getInstance();
- cal.add(Calendar.SECOND, (int) currentToken.getExpiresIn());
- expirationTime = cal.getTime();
+ public void setMinTokenValidity(long minTokenValidity) {
+ this.minTokenValidity = minTokenValidity;
}
private boolean tokenExpired() {
- return new Date().after(expirationTime);
- }
-
- private void defineCurrentToken(AccessTokenResponse accessTokenResponse){
- currentToken = accessTokenResponse;
- setExpirationTime();
+ return (Time.currentTime() + minTokenValidity) >= expirationTime;
}
}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenService.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenService.java
index 6acdc83d6a..0cedec6b01 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenService.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenService.java
@@ -36,10 +36,10 @@ public interface TokenService {
@POST
@Path("/realms/{realm}/protocol/openid-connect/token")
- public AccessTokenResponse grantToken(@PathParam("realm") String realm, MultivaluedMap map);
+ AccessTokenResponse grantToken(@PathParam("realm") String realm, MultivaluedMap map);
@POST
@Path("/realms/{realm}/protocol/openid-connect/token")
- public AccessTokenResponse refreshToken(@PathParam("realm") String realm, MultivaluedMap map);
+ AccessTokenResponse refreshToken(@PathParam("realm") String realm, MultivaluedMap map);
}
diff --git a/integration/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/Context.java b/integration/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/Context.java
index 2d5dfe8d1c..6f60c04e0c 100644
--- a/integration/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/Context.java
+++ b/integration/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/Context.java
@@ -1,8 +1,8 @@
package org.keycloak.client.registration.cli;
+import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
-import org.codehaus.jackson.map.SerializationConfig;
-import org.codehaus.jackson.map.annotate.JsonSerialize;
+import com.fasterxml.jackson.databind.SerializationFeature;
import org.keycloak.client.registration.ClientRegistration;
import org.keycloak.util.SystemPropertiesJsonParserFactory;
@@ -16,8 +16,8 @@ public class Context {
private static final ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
static {
- mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
- mapper.enable(SerializationConfig.Feature.INDENT_OUTPUT);
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
private ClientRegistration reg;
diff --git a/integration/client-registration/pom.xml b/integration/client-registration/pom.xml
index b0d72f6312..5baf10262e 100755
--- a/integration/client-registration/pom.xml
+++ b/integration/client-registration/pom.xml
@@ -21,7 +21,7 @@
keycloak-integration-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/integration/pom.xml b/integration/pom.xml
index adf1219b38..2d1c939855 100755
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
Keycloak Integration
diff --git a/model/infinispan/pom.xml b/model/infinispan/pom.xml
index 87f1711f39..24634e6902 100755
--- a/model/infinispan/pom.xml
+++ b/model/infinispan/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
new file mode 100755
index 0000000000..e41913d4f6
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
@@ -0,0 +1,217 @@
+package org.keycloak.models.cache.infinispan;
+
+import org.infinispan.Cache;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
+import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
+import org.jboss.logging.Logger;
+import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public abstract class CacheManager {
+ protected static final Logger logger = Logger.getLogger(CacheManager.class);
+ protected final Cache revisions;
+ protected final Cache cache;
+ protected final UpdateCounter counter = new UpdateCounter();
+
+ public CacheManager(Cache cache, Cache revisions) {
+ this.cache = cache;
+ this.revisions = revisions;
+ this.cache.addListener(this);
+ }
+
+ public Cache getCache() {
+ return cache;
+ }
+
+ public long getCurrentCounter() {
+ return counter.current();
+ }
+
+ public Long getCurrentRevision(String id) {
+ Long revision = revisions.get(id);
+ if (revision == null) {
+ revision = counter.current();
+ }
+ // if you do cache.remove() on node 1 and the entry doesn't exist on node 2, node 2 never receives a invalidation event
+ // so, we do this to force this.
+ String invalidationKey = "invalidation.key" + id;
+ cache.putForExternalRead(invalidationKey, new AbstractRevisioned(-1L, invalidationKey));
+ return revision;
+ }
+
+ public void endRevisionBatch() {
+ try {
+ revisions.endBatch(true);
+ } catch (Exception e) {
+ }
+
+ }
+
+ public T get(String id, Class type) {
+ Revisioned o = (Revisioned)cache.get(id);
+ if (o == null) {
+ return null;
+ }
+ Long rev = revisions.get(id);
+ if (rev == null) {
+ RealmCacheManager.logger.tracev("get() missing rev");
+ return null;
+ }
+ long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
+ if (rev > oRev) {
+ RealmCacheManager.logger.tracev("get() rev: {0} o.rev: {1}", rev.longValue(), oRev);
+ return null;
+ }
+ return o != null && type.isInstance(o) ? type.cast(o) : null;
+ }
+
+ public Object invalidateObject(String id) {
+ Revisioned removed = (Revisioned)cache.remove(id);
+ // if you do cache.remove() on node 1 and the entry doesn't exist on node 2, node 2 never receives a invalidation event
+ // so, we do this to force the event.
+ cache.remove("invalidation.key" + id);
+ bumpVersion(id);
+ return removed;
+ }
+
+ protected void bumpVersion(String id) {
+ long next = counter.next();
+ Object rev = revisions.put(id, next);
+ }
+
+ public void addRevisioned(Revisioned object, long startupRevision) {
+ //startRevisionBatch();
+ String id = object.getId();
+ try {
+ //revisions.getAdvancedCache().lock(id);
+ Long rev = revisions.get(id);
+ if (rev == null) {
+ if (id.endsWith("realm.clients")) RealmCacheManager.logger.trace("addRevisioned rev == null realm.clients");
+ rev = counter.current();
+ revisions.put(id, rev);
+ }
+ revisions.startBatch();
+ if (!revisions.getAdvancedCache().lock(id)) {
+ RealmCacheManager.logger.trace("Could not obtain version lock");
+ return;
+ }
+ rev = revisions.get(id);
+ if (rev == null) {
+ if (id.endsWith("realm.clients")) RealmCacheManager.logger.trace("addRevisioned rev2 == null realm.clients");
+ return;
+ }
+ if (rev > startupRevision) { // revision is ahead transaction start. Other transaction updated in the meantime. Don't cache
+ if (RealmCacheManager.logger.isTraceEnabled()) {
+ RealmCacheManager.logger.tracev("Skipped cache. Current revision {0}, Transaction start revision {1}", object.getRevision(), startupRevision);
+ }
+ return;
+ }
+ if (rev.equals(object.getRevision())) {
+ if (id.endsWith("realm.clients")) RealmCacheManager.logger.tracev("adding Object.revision {0} rev {1}", object.getRevision(), rev);
+ cache.putForExternalRead(id, object);
+ return;
+ }
+ if (rev > object.getRevision()) { // revision is ahead, don't cache
+ if (id.endsWith("realm.clients")) RealmCacheManager.logger.trace("addRevisioned revision is ahead realm.clients");
+ return;
+ }
+ // revisions cache has a lower value than the object.revision, so update revision and add it to cache
+ if (id.endsWith("realm.clients")) RealmCacheManager.logger.tracev("adding Object.revision {0} rev {1}", object.getRevision(), rev);
+ revisions.put(id, object.getRevision());
+ cache.putForExternalRead(id, object);
+ } finally {
+ endRevisionBatch();
+ }
+
+ }
+
+ public void clear() {
+ cache.clear();
+ }
+
+ public void addInvalidations(Predicate> predicate, Set invalidations) {
+ Iterator> it = getEntryIterator(predicate);
+ while (it.hasNext()) {
+ invalidations.add(it.next().getKey());
+ }
+ }
+
+ private Iterator> getEntryIterator(Predicate> predicate) {
+ return cache
+ .entrySet()
+ .stream()
+ .filter(predicate).iterator();
+ }
+
+ @CacheEntryInvalidated
+ public void cacheInvalidated(CacheEntryInvalidatedEvent event) {
+ if (event.isPre()) {
+ String key = event.getKey();
+ if (key.startsWith("invalidation.key")) {
+ // if you do cache.remove() on node 1 and the entry doesn't exist on node 2, node 2 never receives a invalidation event
+ // so, we do this to force this.
+ String bump = key.substring("invalidation.key".length());
+ RealmCacheManager.logger.tracev("bumping invalidation key {0}", bump);
+ bumpVersion(bump);
+ return;
+ }
+
+ } else {
+ //if (!event.isPre()) {
+ String key = event.getKey();
+ if (key.startsWith("invalidation.key")) {
+ // if you do cache.remove() on node 1 and the entry doesn't exist on node 2, node 2 never receives a invalidation event
+ // so, we do this to force this.
+ String bump = key.substring("invalidation.key".length());
+ bumpVersion(bump);
+ RealmCacheManager.logger.tracev("bumping invalidation key {0}", bump);
+ return;
+ }
+ bumpVersion(key);
+ Object object = event.getValue();
+ if (object != null) {
+ bumpVersion(key);
+ Predicate> predicate = getInvalidationPredicate(object);
+ if (predicate != null) runEvictions(predicate);
+ RealmCacheManager.logger.tracev("invalidating: {0}" + object.getClass().getName());
+ }
+ }
+ }
+
+ @CacheEntriesEvicted
+ public void cacheEvicted(CacheEntriesEvictedEvent event) {
+ if (!event.isPre())
+ for (Map.Entry entry : event.getEntries().entrySet()) {
+ Object object = entry.getValue();
+ bumpVersion(entry.getKey());
+ if (object == null) continue;
+ RealmCacheManager.logger.tracev("evicting: {0}" + object.getClass().getName());
+ Predicate> predicate = getInvalidationPredicate(object);
+ if (predicate != null) runEvictions(predicate);
+ }
+ }
+
+ public void runEvictions(Predicate> current) {
+ Set evictions = new HashSet<>();
+ addInvalidations(current, evictions);
+ RealmCacheManager.logger.tracev("running evictions size: {0}", evictions.size());
+ for (String key : evictions) {
+ cache.evict(key);
+ bumpVersion(key);
+ }
+ }
+
+ protected abstract Predicate> getInvalidationPredicate(Object object);
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
index fb9946b28e..f32ac69369 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
@@ -35,12 +35,12 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
private static final Logger log = Logger.getLogger(InfinispanCacheRealmProviderFactory.class);
- protected volatile StreamRealmCache realmCache;
+ protected volatile RealmCacheManager realmCache;
@Override
public CacheRealmProvider create(KeycloakSession session) {
lazyInit(session);
- return new StreamCacheRealmProvider(realmCache, session);
+ return new RealmCacheSession(realmCache, session);
}
private void lazyInit(KeycloakSession session) {
@@ -49,7 +49,7 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
if (realmCache == null) {
Cache cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
Cache revisions = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.VERSION_CACHE_NAME);
- realmCache = new StreamRealmCache(cache, revisions);
+ realmCache = new RealmCacheManager(cache, revisions);
}
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
index e8657ff579..9e93d55141 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
@@ -29,6 +29,7 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.models.cache.CacheUserProviderFactory;
import org.keycloak.models.cache.infinispan.entities.CachedUser;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
import java.util.concurrent.ConcurrentHashMap;
@@ -39,25 +40,23 @@ public class InfinispanCacheUserProviderFactory implements CacheUserProviderFact
private static final Logger log = Logger.getLogger(InfinispanCacheUserProviderFactory.class);
- protected volatile InfinispanUserCache userCache;
+ protected volatile UserCacheManager userCache;
- protected final RealmLookup usernameLookup = new RealmLookup();
- protected final RealmLookup emailLookup = new RealmLookup();
@Override
public CacheUserProvider create(KeycloakSession session) {
lazyInit(session);
- return new DefaultCacheUserProvider(userCache, session);
+ return new UserCacheSession(userCache, session);
}
private void lazyInit(KeycloakSession session) {
if (userCache == null) {
synchronized (this) {
if (userCache == null) {
- Cache cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.USER_CACHE_NAME);
- cache.addListener(new CacheListener());
- userCache = new InfinispanUserCache(cache, usernameLookup, emailLookup);
+ Cache cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.USER_CACHE_NAME);
+ Cache revisions = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.VERSION_CACHE_NAME);
+ userCache = new UserCacheManager(cache, revisions);
}
}
}
@@ -81,100 +80,5 @@ public class InfinispanCacheUserProviderFactory implements CacheUserProviderFact
return "default";
}
- @Listener
- public class CacheListener {
-
- @CacheEntryCreated
- public void userCreated(CacheEntryCreatedEvent event) {
- if (!event.isPre()) {
- CachedUser user = event.getValue();
- if (user != null) {
- String realm = user.getRealm();
-
- usernameLookup.put(realm, user.getUsername(), user.getId());
- if (user.getEmail() != null) {
- emailLookup.put(realm, user.getEmail(), user.getId());
- }
-
- log.tracev("User added realm={0}, id={1}, username={2}", realm, user.getId(), user.getUsername());
- }
- }
- }
-
- @CacheEntryRemoved
- public void userRemoved(CacheEntryRemovedEvent event) {
- if (event.isPre()) {
- CachedUser user = event.getValue();
- if (user != null) {
- removeUser(user);
-
- log.tracev("User invalidated realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername());
- }
- }
- }
-
- @CacheEntryInvalidated
- public void userInvalidated(CacheEntryInvalidatedEvent event) {
- if (event.isPre()) {
- CachedUser user = event.getValue();
- if (user != null) {
- removeUser(user);
-
- log.tracev("User invalidated realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername());
- }
- }
- }
-
- @CacheEntriesEvicted
- public void userEvicted(CacheEntriesEvictedEvent event) {
- for (CachedUser user : event.getEntries().values()) {
- removeUser(user);
-
- log.tracev("User evicted realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername());
- }
- }
-
- private void removeUser(CachedUser cachedUser) {
- String realm = cachedUser.getRealm();
- usernameLookup.remove(realm, cachedUser.getUsername());
- if (cachedUser.getEmail() != null) {
- emailLookup.remove(realm, cachedUser.getEmail());
- }
- }
-
- }
-
- static class RealmLookup {
-
- protected final ConcurrentHashMap> lookup = new ConcurrentHashMap<>();
-
- public void put(String realm, String key, String value) {
- ConcurrentHashMap map = lookup.get(realm);
- if(map == null) {
- map = new ConcurrentHashMap<>();
- ConcurrentHashMap p = lookup.putIfAbsent(realm, map);
- if (p != null) {
- map = p;
- }
- }
- map.put(key, value);
- }
-
- public String get(String realm, String key) {
- ConcurrentHashMap map = lookup.get(realm);
- return map != null ? map.get(key) : null;
- }
-
- public void remove(String realm, String key) {
- ConcurrentHashMap map = lookup.get(realm);
- if (map != null) {
- map.remove(key);
- if (map.isEmpty()) {
- lookup.remove(realm);
- }
- }
- }
-
- }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCache.java
deleted file mode 100755
index bb7196e779..0000000000
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCache.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2016 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.models.cache.infinispan;
-
-import org.infinispan.Cache;
-import org.jboss.logging.Logger;
-import org.keycloak.models.cache.infinispan.entities.CachedUser;
-
-/**
- * @author Stian Thorgersen
- */
-public class InfinispanUserCache implements UserCache {
-
- protected static final Logger logger = Logger.getLogger(InfinispanUserCache.class);
-
- protected volatile boolean enabled = true;
-
- protected final Cache cache;
-
- protected final InfinispanCacheUserProviderFactory.RealmLookup usernameLookup;
-
- protected final InfinispanCacheUserProviderFactory.RealmLookup emailLookup;
-
- public InfinispanUserCache(Cache cache, InfinispanCacheUserProviderFactory.RealmLookup usernameLookup, InfinispanCacheUserProviderFactory.RealmLookup emailLookup) {
- this.cache = cache;
- this.usernameLookup = usernameLookup;
- this.emailLookup = emailLookup;
- }
-
- @Override
- public CachedUser getCachedUser(String realmId, String id) {
- if (realmId == null || id == null) return null;
- CachedUser user = cache.get(id);
- return user != null && realmId.equals(user.getRealm()) ? user : null;
- }
-
- @Override
- public void invalidateCachedUserById(String realmId, String id) {
- logger.tracev("Invalidating user {0}", id);
- cache.remove(id);
- }
-
- @Override
- public void addCachedUser(String realmId, CachedUser user) {
- logger.tracev("Adding user {0}", user.getId());
- cache.putForExternalRead(user.getId(), user);
- }
-
- @Override
- public CachedUser getCachedUserByUsername(String realmId, String name) {
- String id = usernameLookup.get(realmId, name);
- return id != null ? getCachedUser(realmId, id) : null;
- }
-
- @Override
- public CachedUser getCachedUserByEmail(String realmId, String email) {
- String id = emailLookup.get(realmId, email);
- return id != null ? getCachedUser(realmId, id) : null;
- }
-
- @Override
- public void invalidateRealmUsers(String realmId) {
- logger.tracev("Invalidating users for realm {0}", realmId);
-
- cache.clear();
- }
-
- @Override
- public void clear() {
- cache.clear();
- }
-
-}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/StreamRealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheManager.java
similarity index 53%
rename from model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/StreamRealmCache.java
rename to model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheManager.java
index 452eee489d..55e3b38777 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/StreamRealmCache.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheManager.java
@@ -19,10 +19,6 @@ package org.keycloak.models.cache.infinispan;
import org.infinispan.Cache;
import org.infinispan.notifications.Listener;
-import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
-import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
-import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
-import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
import org.jboss.logging.Logger;
import org.keycloak.models.cache.infinispan.entities.CachedClient;
import org.keycloak.models.cache.infinispan.entities.CachedClientTemplate;
@@ -37,10 +33,7 @@ import org.keycloak.models.cache.infinispan.stream.HasRolePredicate;
import org.keycloak.models.cache.infinispan.stream.InClientPredicate;
import org.keycloak.models.cache.infinispan.stream.InRealmPredicate;
import org.keycloak.models.cache.infinispan.stream.RealmQueryPredicate;
-import org.keycloak.models.cache.infinispan.stream.RoleQueryPredicate;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
@@ -49,110 +42,14 @@ import java.util.function.Predicate;
* @author Stian Thorgersen
*/
@Listener
-public class StreamRealmCache {
+public class RealmCacheManager extends CacheManager {
- protected static final Logger logger = Logger.getLogger(StreamRealmCache.class);
+ protected static final Logger logger = Logger.getLogger(RealmCacheManager.class);
- protected final Cache revisions;
- protected final Cache cache;
-
- public StreamRealmCache(Cache cache, Cache revisions) {
- this.cache = cache;
- this.cache.addListener(this);
- this.revisions = revisions;
+ public RealmCacheManager(Cache cache, Cache revisions) {
+ super(cache, revisions);
}
- public Cache getCache() {
- return cache;
- }
-
- public Cache getRevisions() {
- return revisions;
- }
-
- public Long getCurrentRevision(String id) {
- Long revision = revisions.get(id);
- if (revision == null) return UpdateCounter.current();
- return revision;
- }
-
- public void endRevisionBatch() {
- try {
- revisions.endBatch(true);
- } catch (Exception e) {
- }
-
- }
-
- public T get(String id, Class type) {
- Revisioned o = (Revisioned)cache.get(id);
- if (o == null) {
- return null;
- }
- Long rev = revisions.get(id);
- if (rev == null) {
- logger.tracev("get() missing rev");
- return null;
- }
- long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
- if (rev > oRev) {
- logger.tracev("get() rev: {0} o.rev: {1}", rev.longValue(), oRev);
- return null;
- }
- return o != null && type.isInstance(o) ? type.cast(o) : null;
- }
-
- public Object invalidateObject(String id) {
- Revisioned removed = (Revisioned)cache.remove(id);
- long next = UpdateCounter.next();
- Object rev = revisions.put(id, next);
- return removed;
- }
-
- public void addRevisioned(Revisioned object) {
- //startRevisionBatch();
- String id = object.getId();
- try {
- //revisions.getAdvancedCache().lock(id);
- Long rev = revisions.get(id);
- if (rev == null) {
- if (id.endsWith("realm.clients")) logger.trace("addRevisioned rev == null realm.clients");
- rev = UpdateCounter.current();
- revisions.put(id, rev);
- }
- revisions.startBatch();
- if (!revisions.getAdvancedCache().lock(id)) {
- logger.trace("Could not obtain version lock");
- }
- rev = revisions.get(id);
- if (rev == null) {
- if (id.endsWith("realm.clients")) logger.trace("addRevisioned rev2 == null realm.clients");
- return;
- }
- if (rev.equals(object.getRevision())) {
- if (id.endsWith("realm.clients")) logger.tracev("adding Object.revision {0} rev {1}", object.getRevision(), rev);
- cache.putForExternalRead(id, object);
- return;
- }
- if (rev > object.getRevision()) { // revision is ahead, don't cache
- if (id.endsWith("realm.clients")) logger.trace("addRevisioned revision is ahead realm.clients");
- return;
- }
- // revisions cache has a lower value than the object.revision, so update revision and add it to cache
- if (id.endsWith("realm.clients")) logger.tracev("adding Object.revision {0} rev {1}", object.getRevision(), rev);
- revisions.put(id, object.getRevision());
- cache.putForExternalRead(id, object);
- } finally {
- endRevisionBatch();
- }
-
- }
-
-
-
- public void clear() {
- cache.clear();
- }
public void realmInvalidation(String id, Set invalidations) {
Predicate> predicate = getRealmInvalidationPredicate(id);
@@ -241,46 +138,7 @@ public class StreamRealmCache {
return getRoleInvalidationPredicate(id);
}
- public void addInvalidations(Predicate> predicate, Set invalidations) {
- Iterator> it = getEntryIterator(predicate);
- while (it.hasNext()) {
- invalidations.add(it.next().getKey());
- }
- }
-
- private Iterator> getEntryIterator(Predicate> predicate) {
- return cache
- .entrySet()
- .stream()
- .filter(predicate).iterator();
- }
-
- @CacheEntryInvalidated
- public void cacheInvalidated(CacheEntryInvalidatedEvent event) {
- if (!event.isPre()) {
- Object object = event.getValue();
- if (object != null) {
- Predicate> predicate = getInvalidationPredicate(object);
- if (predicate != null) runEvictions(predicate);
- }
- }
- }
-
- @CacheEntriesEvicted
- public void cacheEvicted(CacheEntriesEvictedEvent event) {
- if (!event.isPre())
- for (Object object : event.getEntries().values()) {
- Predicate> predicate = getInvalidationPredicate(object);
- if (predicate != null) runEvictions(predicate);
- }
- }
-
- public void runEvictions(Predicate> current) {
- Set evictions = new HashSet<>();
- addInvalidations(current, evictions);
- for (String key : evictions) cache.evict(key);
- }
-
+ @Override
protected Predicate> getInvalidationPredicate(Object object) {
if (object instanceof CachedRealm) {
CachedRealm cached = (CachedRealm)object;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/StreamCacheRealmProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
similarity index 93%
rename from model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/StreamCacheRealmProvider.java
rename to model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
index 2432cc6965..4a149ba65e 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/StreamCacheRealmProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
@@ -40,7 +40,6 @@ import org.keycloak.models.cache.infinispan.entities.RealmListQuery;
import org.keycloak.models.cache.infinispan.entities.RoleListQuery;
import org.keycloak.models.utils.KeycloakModelUtils;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
@@ -66,8 +65,6 @@ import java.util.Set;
* it is added to the cache. So, we keep the version number around for this.
* - In a transaction, objects are registered to be invalidated. If an object is marked for invalidation within a transaction
* a cached object should never be returned. An DB adapter should always be returned.
- * - At prepare phase of the transaction, a local lock on the revision cache will be obtained for each object marked for invalidation
- * we sort the list of these keys to order local acquisition and avoid deadlocks.
* - After DB commits, the objects marked for invalidation are invalidated, or rather removed from the cache. At this time
* the revision cache entry for this object has its version number bumped.
* - Whenever an object is marked for invalidation, the cache is also searched for any objects that are related to this object
@@ -88,13 +85,20 @@ import java.util.Set;
* - There is a Infinispan @Listener registered. If an invalidation event happens, this is treated like
* the object was removed from the database and will perform evictions based on that assumption.
* - Eviction events will also cascade other evictions, but not assume this is a db removal.
+ * - With an invalidation cache, if you remove an entry on node 1 and this entry does not exist on node 2, node 2 will not receive a @Listener invalidation event.
+ * so, hat we have to put a marker entry in the invalidation cache before we read from the DB, so if the DB changes in between reading and adding a cache entry, the cache will be notified and bump
+ * the version information.
+ *
+ * DBs with Repeatable Read:
+ * - DBs like MySQL are Repeatable Read by default. So, if you query a Client for instance, it will always return the same result in the same transaction even if the DB
+ * was updated in between these queries. This makes it possible to store stale cache entries. To avoid this problem, this class stores the current local version counter
+ * at the beginningof the transaction. Whenever an entry is added to the cache, the current coutner is compared against the counter at the beginning of the tx. If the current
+ * is greater, then don't cache.
*
* Groups and Roles:
* - roles are tricky because of composites. Composite lists are cached too. So, when a role is removed
* we also iterate and invalidate any role or group that contains that role being removed.
*
- *
- *
* - any relationship should be resolved from session.realms(). For example if JPA.getClientByClientId() is invoked,
* JPA should find the id of the client and then call session.realms().getClientById(). THis is to ensure that the cached
* object is invoked and all proper invalidation are being invoked.
@@ -102,12 +106,12 @@ import java.util.Set;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class StreamCacheRealmProvider implements CacheRealmProvider {
- protected static final Logger logger = Logger.getLogger(StreamCacheRealmProvider.class);
+public class RealmCacheSession implements CacheRealmProvider {
+ protected static final Logger logger = Logger.getLogger(RealmCacheSession.class);
public static final String REALM_CLIENTS_QUERY_SUFFIX = ".realm.clients";
public static final String ROLES_QUERY_SUFFIX = ".roles";
public static final String ROLE_BY_NAME_QUERY_SUFFIX = ".role.by-name";
- protected StreamRealmCache cache;
+ protected RealmCacheManager cache;
protected KeycloakSession session;
protected RealmProvider delegate;
protected boolean transactionActive;
@@ -122,14 +126,20 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
protected Set invalidations = new HashSet<>();
protected boolean clearAll;
+ protected final long startupRevision;
- public StreamCacheRealmProvider(StreamRealmCache cache, KeycloakSession session) {
+ public RealmCacheSession(RealmCacheManager cache, KeycloakSession session) {
this.cache = cache;
this.session = session;
+ this.startupRevision = cache.getCurrentCounter();
session.getTransaction().enlistPrepare(getPrepareTransaction());
session.getTransaction().enlistAfterCompletion(getAfterTransaction());
}
+ public long getStartupRevision() {
+ return startupRevision;
+ }
+
@Override
public void clear() {
cache.clear();
@@ -192,16 +202,16 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
@Override
public void commit() {
+ /* THIS WAS CAUSING DEADLOCK IN A CLUSTER
if (delegate == null) return;
List locks = new LinkedList<>();
locks.addAll(invalidations);
Collections.sort(locks); // lock ordering
cache.getRevisions().startBatch();
- //if (!invalidates.isEmpty()) cache.getRevisions().getAdvancedCache().lock(invalidates);
- for (String lock : locks) {
- boolean success = cache.getRevisions().getAdvancedCache().lock(lock);
- }
+
+ if (!locks.isEmpty()) cache.getRevisions().getAdvancedCache().lock(locks);
+ */
}
@@ -303,7 +313,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
if (model == null) return null;
if (invalidations.contains(id)) return model;
cached = new CachedRealm(loaded, model);
- cache.addRevisioned(cached);
+ cache.addRevisioned(cached, startupRevision);
} else if (invalidations.contains(id)) {
return getDelegate().getRealm(id);
} else if (managedRealms.containsKey(id)) {
@@ -327,7 +337,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
if (model == null) return null;
if (invalidations.contains(model.getId())) return model;
query = new RealmListQuery(loaded, cacheKey, model.getId());
- cache.addRevisioned(query);
+ cache.addRevisioned(query, startupRevision);
return model;
} else if (invalidations.contains(cacheKey)) {
return getDelegate().getRealmByName(name);
@@ -370,6 +380,14 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
return getDelegate().removeRealm(id);
}
+ protected void invalidateClient(RealmModel realm, ClientModel client) {
+ invalidations.add(client.getId());
+ invalidations.add(getRealmClientsQueryCacheKey(realm.getId()));
+ invalidations.add(getClientByClientIdCacheKey(client.getClientId(), realm));
+ listInvalidations.add(realm.getId());
+ }
+
+
@Override
public ClientModel addClient(RealmModel realm, String clientId) {
ClientModel client = getDelegate().addClient(realm, clientId);
@@ -385,8 +403,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
private ClientModel addedClient(RealmModel realm, ClientModel client) {
logger.trace("added Client.....");
// need to invalidate realm client query cache every time as it may not be loaded on this node, but loaded on another
- invalidations.add(getRealmClientsQueryCacheKey(realm.getId()));
- invalidations.add(client.getId());
+ invalidateClient(realm, client);
cache.clientAdded(realm.getId(), client.getId(), invalidations);
// this is needed so that a new client that hasn't been committed isn't cached in a query
listInvalidations.add(realm.getId());
@@ -433,7 +450,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
for (ClientModel client : model) ids.add(client.getId());
query = new ClientListQuery(loaded, cacheKey, realm, ids);
logger.tracev("adding realm clients cache miss: realm {0} key {1}", realm.getName(), cacheKey);
- cache.addRevisioned(query);
+ cache.addRevisioned(query, startupRevision);
return model;
}
List list = new LinkedList<>();
@@ -454,10 +471,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
ClientModel client = getClientById(id, realm);
if (client == null) return false;
// need to invalidate realm client query cache every time client list is changed
- invalidations.add(getRealmClientsQueryCacheKey(realm.getId()));
- invalidations.add(getClientByClientIdCacheKey(client.getClientId(), realm));
- listInvalidations.add(realm.getId());
- registerClientInvalidation(id);
+ invalidateClient(realm, client);
cache.clientRemoval(realm.getId(), id, invalidations);
for (RoleModel role : client.getRoles()) {
cache.roleInvalidation(role.getId(), invalidations);
@@ -506,7 +520,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
for (RoleModel role : model) ids.add(role.getId());
query = new RoleListQuery(loaded, cacheKey, realm, ids);
logger.tracev("adding realm roles cache miss: realm {0} key {1}", realm.getName(), cacheKey);
- cache.addRevisioned(query);
+ cache.addRevisioned(query, startupRevision);
return model;
}
Set list = new HashSet<>();
@@ -542,7 +556,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
for (RoleModel role : model) ids.add(role.getId());
query = new RoleListQuery(loaded, cacheKey, realm, ids, client.getClientId());
logger.tracev("adding client roles cache miss: client {0} key {1}", client.getClientId(), cacheKey);
- cache.addRevisioned(query);
+ cache.addRevisioned(query, startupRevision);
return model;
}
Set list = new HashSet<>();
@@ -591,7 +605,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
if (model == null) return null;
query = new RoleListQuery(loaded, cacheKey, realm, model.getId());
logger.tracev("adding realm role cache miss: client {0} key {1}", realm.getName(), cacheKey);
- cache.addRevisioned(query);
+ cache.addRevisioned(query, startupRevision);
return model;
}
RoleModel role = getRoleById(query.getRoles().iterator().next(), realm);
@@ -621,7 +635,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
if (model == null) return null;
query = new RoleListQuery(loaded, cacheKey, realm, model.getId(), client.getClientId());
logger.tracev("adding client role cache miss: client {0} key {1}", client.getClientId(), cacheKey);
- cache.addRevisioned(query);
+ cache.addRevisioned(query, startupRevision);
return model;
}
RoleModel role = getRoleById(query.getRoles().iterator().next(), realm);
@@ -658,7 +672,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
} else {
cached = new CachedRealmRole(loaded, model, realm);
}
- cache.addRevisioned(cached);
+ cache.addRevisioned(cached, startupRevision);
} else if (invalidations.contains(id)) {
return getDelegate().getRoleById(id, realm);
@@ -683,7 +697,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
if (model == null) return null;
if (invalidations.contains(id)) return model;
cached = new CachedGroup(loaded, realm, model);
- cache.addRevisioned(cached);
+ cache.addRevisioned(cached, startupRevision);
} else if (invalidations.contains(id)) {
return getDelegate().getGroupById(id, realm);
@@ -723,7 +737,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
for (GroupModel client : model) ids.add(client.getId());
query = new GroupListQuery(loaded, cacheKey, realm, ids);
logger.tracev("adding realm getGroups cache miss: realm {0} key {1}", realm.getName(), cacheKey);
- cache.addRevisioned(query);
+ cache.addRevisioned(query, startupRevision);
return model;
}
List list = new LinkedList<>();
@@ -759,7 +773,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
for (GroupModel client : model) ids.add(client.getId());
query = new GroupListQuery(loaded, cacheKey, realm, ids);
logger.tracev("adding realm getTopLevelGroups cache miss: realm {0} key {1}", realm.getName(), cacheKey);
- cache.addRevisioned(query);
+ cache.addRevisioned(query, startupRevision);
return model;
}
List list = new LinkedList<>();
@@ -835,7 +849,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
if (invalidations.contains(id)) return model;
cached = new CachedClient(loaded, realm, model);
logger.tracev("adding client by id cache miss: {0}", cached.getClientId());
- cache.addRevisioned(cached);
+ cache.addRevisioned(cached, startupRevision);
} else if (invalidations.contains(id)) {
return getDelegate().getClientById(id, realm);
} else if (managedApplications.containsKey(id)) {
@@ -864,7 +878,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
id = model.getId();
query = new ClientListQuery(loaded, cacheKey, realm, id);
logger.tracev("adding client by name cache miss: {0}", clientId);
- cache.addRevisioned(query);
+ cache.addRevisioned(query, startupRevision);
} else if (invalidations.contains(cacheKey)) {
return getDelegate().getClientByClientId(clientId, realm);
} else {
@@ -893,7 +907,7 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
if (model == null) return null;
if (invalidations.contains(id)) return model;
cached = new CachedClientTemplate(loaded, realm, model);
- cache.addRevisioned(cached);
+ cache.addRevisioned(cached, startupRevision);
} else if (invalidations.contains(id)) {
return getDelegate().getClientTemplateById(id, realm);
} else if (managedClientTemplates.containsKey(id)) {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UpdateCounter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UpdateCounter.java
index 0c83dc8286..966b1f4430 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UpdateCounter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UpdateCounter.java
@@ -3,17 +3,19 @@ package org.keycloak.models.cache.infinispan;
import java.util.concurrent.atomic.AtomicLong;
/**
+ * Used to track cache revisions
+ *
* @author Stian Thorgersen
*/
public class UpdateCounter {
- private static final AtomicLong counter = new AtomicLong();
+ private final AtomicLong counter = new AtomicLong();
- public static long current() {
+ public long current() {
return counter.get();
}
- public static long next() {
+ public long next() {
return counter.incrementAndGet();
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
index 6421ca2fb5..6e53626592 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
@@ -31,11 +31,11 @@ import java.util.*;
public class UserAdapter implements UserModel {
protected UserModel updated;
protected CachedUser cached;
- protected CacheUserProvider userProviderCache;
+ protected UserCacheSession userProviderCache;
protected KeycloakSession keycloakSession;
protected RealmModel realm;
- public UserAdapter(CachedUser cached, CacheUserProvider userProvider, KeycloakSession keycloakSession, RealmModel realm) {
+ public UserAdapter(CachedUser cached, UserCacheSession userProvider, KeycloakSession keycloakSession, RealmModel realm) {
this.cached = cached;
this.userProviderCache = userProvider;
this.keycloakSession = keycloakSession;
@@ -44,7 +44,7 @@ public class UserAdapter implements UserModel {
protected void getDelegateForUpdate() {
if (updated == null) {
- userProviderCache.registerUserInvalidation(realm, getId());
+ userProviderCache.registerUserInvalidation(realm, cached);
updated = userProviderCache.getDelegate().getUserById(getId(), realm);
if (updated == null) throw new IllegalStateException("Not found in database");
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheManager.java
new file mode 100755
index 0000000000..e1fb79cbdc
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheManager.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 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.models.cache.infinispan;
+
+import org.infinispan.Cache;
+import org.infinispan.notifications.Listener;
+import org.jboss.logging.Logger;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.cache.infinispan.entities.CachedUser;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+import org.keycloak.models.cache.infinispan.stream.InRealmPredicate;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * @author Stian Thorgersen
+ */
+@Listener
+public class UserCacheManager extends CacheManager {
+
+ protected static final Logger logger = Logger.getLogger(UserCacheManager.class);
+
+ protected volatile boolean enabled = true;
+ public UserCacheManager(Cache cache, Cache revisions) {
+ super(cache, revisions);
+ }
+
+ @Override
+ public void clear() {
+ cache.clear();
+ }
+
+ @Override
+ protected Predicate> getInvalidationPredicate(Object object) {
+ return null;
+ }
+
+ public void invalidateRealmUsers(String realm, Set invalidations) {
+ addInvalidations(InRealmPredicate.create().realm(realm), invalidations);
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
similarity index 62%
rename from model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
rename to model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
index e8f74bc9b8..c611bd37e4 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
@@ -17,9 +17,11 @@
package org.keycloak.models.cache.infinispan;
+import org.jboss.logging.Logger;
import org.keycloak.models.*;
import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.models.cache.infinispan.entities.CachedUser;
+import org.keycloak.models.cache.infinispan.entities.UserListQuery;
import java.util.*;
@@ -27,21 +29,24 @@ import java.util.*;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class DefaultCacheUserProvider implements CacheUserProvider {
- protected UserCache cache;
+public class UserCacheSession implements CacheUserProvider {
+ protected static final Logger logger = Logger.getLogger(UserCacheSession.class);
+ protected UserCacheManager cache;
protected KeycloakSession session;
protected UserProvider delegate;
protected boolean transactionActive;
protected boolean setRollbackOnly;
+ protected final long startupRevision;
- protected Map userInvalidations = new HashMap<>();
+
+ protected Set invalidations = new HashSet<>();
protected Set realmInvalidations = new HashSet<>();
protected Map managedUsers = new HashMap<>();
- public DefaultCacheUserProvider(UserCache cache, KeycloakSession session) {
+ public UserCacheSession(UserCacheManager cache, KeycloakSession session) {
this.cache = cache;
this.session = session;
-
+ this.startupRevision = cache.getCurrentCounter();
session.getTransaction().enlistAfterCompletion(getTransaction());
}
@@ -55,20 +60,22 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
if (delegate != null) return delegate;
delegate = session.getProvider(UserProvider.class);
+
return delegate;
}
- @Override
- public void registerUserInvalidation(RealmModel realm, String id) {
- userInvalidations.put(id, realm.getId());
+ public void registerUserInvalidation(RealmModel realm,CachedUser user) {
+ invalidations.add(user.getId());
+ if (user.getEmail() != null) invalidations.add(getUserByEmailCacheKey(realm.getId(), user.getEmail()));
+ invalidations.add(getUserByUsernameCacheKey(realm.getId(), user.getUsername()));
}
protected void runInvalidations() {
- for (Map.Entry invalidation : userInvalidations.entrySet()) {
- cache.invalidateCachedUserById(invalidation.getValue(), invalidation.getKey());
- }
for (String realmId : realmInvalidations) {
- cache.invalidateRealmUsers(realmId);
+ cache.invalidateRealmUsers(realmId, invalidations);
+ }
+ for (String invalidation : invalidations) {
+ cache.invalidateObject(invalidation);
}
}
@@ -111,81 +118,145 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
}
private boolean isRegisteredForInvalidation(RealmModel realm, String userId) {
- return realmInvalidations.contains(realm.getId()) || userInvalidations.containsKey(userId);
+ return realmInvalidations.contains(realm.getId()) || invalidations.contains(userId);
}
@Override
public UserModel getUserById(String id, RealmModel realm) {
+ logger.tracev("getuserById {0}", id);
if (isRegisteredForInvalidation(realm, id)) {
+ logger.trace("registered for invalidation return delegate");
return getDelegate().getUserById(id, realm);
}
- CachedUser cached = cache.getCachedUser(realm.getId(), id);
+ CachedUser cached = cache.get(id, CachedUser.class);
if (cached == null) {
+ logger.trace("not cached");
+ Long loaded = cache.getCurrentRevision(id);
UserModel model = getDelegate().getUserById(id, realm);
- if (model == null) return null;
- if (managedUsers.containsKey(id)) return managedUsers.get(id);
- if (userInvalidations.containsKey(id)) return model;
- cached = new CachedUser(realm, model);
- cache.addCachedUser(realm.getId(), cached);
+ if (model == null) {
+ logger.trace("delegate returning null");
+ return null;
+ }
+ if (managedUsers.containsKey(id)) {
+ logger.trace("return managedusers");
+ return managedUsers.get(id);
+ }
+ if (invalidations.contains(id)) return model;
+ cached = new CachedUser(loaded, realm, model);
+ cache.addRevisioned(cached, startupRevision);
} else if (managedUsers.containsKey(id)) {
+ logger.trace("return managedusers");
return managedUsers.get(id);
}
+ logger.trace("returning new cache adapter");
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
managedUsers.put(id, adapter);
return adapter;
}
+ public String getUserByUsernameCacheKey(String realmId, String username) {
+ return realmId + ".username." + username;
+ }
+
+ public String getUserByEmailCacheKey(String realmId, String email) {
+ return realmId + ".email." + email;
+ }
+
@Override
public UserModel getUserByUsername(String username, RealmModel realm) {
-
+ logger.tracev("getUserByUsername: {0}", username);
username = username.toLowerCase();
-
if (realmInvalidations.contains(realm.getId())) {
+ logger.tracev("realmInvalidations");
return getDelegate().getUserByUsername(username, realm);
}
- CachedUser cached = cache.getCachedUserByUsername(realm.getId(), username);
- if (cached == null) {
- UserModel model = getDelegate().getUserByUsername(username, realm);
- if (model == null) return null;
- if (managedUsers.containsKey(model.getId())) return managedUsers.get(model.getId());
- if (userInvalidations.containsKey(model.getId())) return model;
- cached = new CachedUser(realm, model);
- cache.addCachedUser(realm.getId(), cached);
- } else if (userInvalidations.containsKey(cached.getId())) {
- return getDelegate().getUserById(cached.getId(), realm);
- } else if (managedUsers.containsKey(cached.getId())) {
- return managedUsers.get(cached.getId());
+ String cacheKey = getUserByUsernameCacheKey(realm.getId(), username);
+ if (invalidations.contains(cacheKey)) {
+ logger.tracev("invalidations");
+ return getDelegate().getUserByUsername(username, realm);
+ }
+ UserListQuery query = cache.get(cacheKey, UserListQuery.class);
+
+ String userId = null;
+ if (query == null) {
+ logger.tracev("query null");
+ Long loaded = cache.getCurrentRevision(cacheKey);
+ UserModel model = getDelegate().getUserByUsername(username, realm);
+ if (model == null) {
+ logger.tracev("model from delegate null");
+ return null;
+ }
+ userId = model.getId();
+ query = new UserListQuery(loaded, cacheKey, realm, model.getId());
+ cache.addRevisioned(query, startupRevision);
+ if (invalidations.contains(userId)) return model;
+ if (managedUsers.containsKey(userId)) {
+ logger.tracev("return managed user");
+ return managedUsers.get(userId);
+ }
+
+ CachedUser cached = cache.get(userId, CachedUser.class);
+ if (cached == null) {
+ cached = new CachedUser(loaded, realm, model);
+ cache.addRevisioned(cached, startupRevision);
+ }
+ logger.trace("return new cache adapter");
+ UserAdapter adapter = new UserAdapter(cached, this, session, realm);
+ managedUsers.put(userId, adapter);
+ return adapter;
+ } else {
+ userId = query.getUsers().iterator().next();
+ if (invalidations.contains(userId)) {
+ logger.tracev("invalidated cache return delegate");
+ return getDelegate().getUserByUsername(username, realm);
+
+ }
+ logger.trace("return getUserById");
+ return getUserById(userId, realm);
}
- UserAdapter adapter = new UserAdapter(cached, this, session, realm);
- managedUsers.put(cached.getId(), adapter);
- return adapter;
}
@Override
public UserModel getUserByEmail(String email, RealmModel realm) {
if (email == null) return null;
-
email = email.toLowerCase();
-
if (realmInvalidations.contains(realm.getId())) {
return getDelegate().getUserByEmail(email, realm);
}
- CachedUser cached = cache.getCachedUserByEmail(realm.getId(), email);
- if (cached == null) {
+ String cacheKey = getUserByEmailCacheKey(realm.getId(), email);
+ if (invalidations.contains(cacheKey)) {
+ return getDelegate().getUserByEmail(email, realm);
+ }
+ UserListQuery query = cache.get(cacheKey, UserListQuery.class);
+
+ String userId = null;
+ if (query == null) {
+ Long loaded = cache.getCurrentRevision(cacheKey);
UserModel model = getDelegate().getUserByEmail(email, realm);
if (model == null) return null;
- if (userInvalidations.containsKey(model.getId())) return model;
- cached = new CachedUser(realm, model);
- cache.addCachedUser(realm.getId(), cached);
- } else if (userInvalidations.containsKey(cached.getId())) {
- return getDelegate().getUserByEmail(email, realm);
- } else if (managedUsers.containsKey(cached.getId())) {
- return managedUsers.get(cached.getId());
+ userId = model.getId();
+ query = new UserListQuery(loaded, cacheKey, realm, model.getId());
+ cache.addRevisioned(query, startupRevision);
+ if (invalidations.contains(userId)) return model;
+ if (managedUsers.containsKey(userId)) return managedUsers.get(userId);
+
+ CachedUser cached = cache.get(userId, CachedUser.class);
+ if (cached == null) {
+ cached = new CachedUser(loaded, realm, model);
+ cache.addRevisioned(cached, startupRevision);
+ }
+ UserAdapter adapter = new UserAdapter(cached, this, session, realm);
+ managedUsers.put(userId, adapter);
+ return adapter;
+ } else {
+ userId = query.getUsers().iterator().next();
+ if (invalidations.contains(userId)) {
+ return getDelegate().getUserByEmail(email, realm);
+
+ }
+ return getUserById(userId, realm);
}
- UserAdapter adapter = new UserAdapter(cached, this, session, realm);
- managedUsers.put(cached.getId(), adapter);
- return adapter;
}
@Override
@@ -266,6 +337,8 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
@Override
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
UserModel user = getDelegate().addUser(realm, id, username, addDefaultRoles, addDefaultRoles);
+ // just in case the transaction is rolled back you need to invalidate the user and all cache queries for that user
+ invalidateUser(realm, user);
managedUsers.put(user.getId(), user);
return user;
}
@@ -273,13 +346,23 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
@Override
public UserModel addUser(RealmModel realm, String username) {
UserModel user = getDelegate().addUser(realm, username);
+ // just in case the transaction is rolled back you need to invalidate the user and all cache queries for that user
+ invalidateUser(realm, user);
managedUsers.put(user.getId(), user);
return user;
}
+ protected void invalidateUser(RealmModel realm, UserModel user) {
+ // just in case the transaction is rolled back you need to invalidate the user and all cache queries for that user
+ invalidations.add(user.getId());
+ if (user.getEmail() != null) invalidations.add(getUserByEmailCacheKey(realm.getId(), user.getEmail()));
+ invalidations.add(getUserByUsernameCacheKey(realm.getId(), user.getUsername()));
+
+ }
+
@Override
public boolean removeUser(RealmModel realm, UserModel user) {
- registerUserInvalidation(realm, user.getId());
+ invalidateUser(realm, user);
return getDelegate().removeUser(realm, user);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
index c1bf4cd56a..af300001a6 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
@@ -34,8 +34,7 @@ import java.util.Set;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class CachedUser implements Serializable {
- private String id;
+public class CachedUser extends AbstractRevisioned implements InRealm {
private String realm;
private String username;
private Long createdTimestamp;
@@ -53,8 +52,10 @@ public class CachedUser implements Serializable {
private Set roleMappings = new HashSet<>();
private Set groups = new HashSet<>();
- public CachedUser(RealmModel realm, UserModel user) {
- this.id = user.getId();
+
+
+ public CachedUser(Long revision, RealmModel realm, UserModel user) {
+ super(revision, user.getId());
this.realm = realm.getId();
this.username = user.getUsername();
this.createdTimestamp = user.getCreatedTimestamp();
@@ -80,10 +81,6 @@ public class CachedUser implements Serializable {
}
}
- public String getId() {
- return id;
- }
-
public String getRealm() {
return realm;
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/UserListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/UserListQuery.java
new file mode 100755
index 0000000000..c19e7aada1
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/UserListQuery.java
@@ -0,0 +1,49 @@
+package org.keycloak.models.cache.infinispan.entities;
+
+import org.keycloak.models.RealmModel;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class UserListQuery extends AbstractRevisioned implements UserQuery {
+ private final Set users;
+ private final String realm;
+ private final String realmName;
+
+ public UserListQuery(Long revisioned, String id, RealmModel realm, Set users) {
+ super(revisioned, id);
+ this.realm = realm.getId();
+ this.realmName = realm.getName();
+ this.users = users;
+ }
+
+ public UserListQuery(Long revisioned, String id, RealmModel realm, String user) {
+ super(revisioned, id);
+ this.realm = realm.getId();
+ this.realmName = realm.getName();
+ this.users = new HashSet<>();
+ this.users.add(user);
+ }
+
+ @Override
+ public Set getUsers() {
+ return users;
+ }
+
+ @Override
+ public String getRealm() {
+ return realm;
+ }
+
+ @Override
+ public String toString() {
+ return "UserListQuery{" +
+ "id='" + getId() + "'" +
+ "realmName='" + realmName + '\'' +
+ '}';
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/UserQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/UserQuery.java
new file mode 100755
index 0000000000..5f890a2d3d
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/UserQuery.java
@@ -0,0 +1,11 @@
+package org.keycloak.models.cache.infinispan.entities;
+
+import java.util.Set;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public interface UserQuery extends InRealm {
+ Set getUsers();
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 7630225384..10d6205367 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -270,7 +270,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public void removeUserSession(RealmModel realm, UserSessionModel session) {
UserSessionEntity entity = getUserSessionEntity(session, false);
- removeUserSession(realm, entity, false);
+ if (entity != null) {
+ removeUserSession(realm, entity, false);
+ }
}
@Override
@@ -553,7 +555,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return ((UserSessionAdapter) userSession).getEntity();
} else {
Cache cache = getCache(offline);
- return (UserSessionEntity) cache.get(userSession.getId());
+ return cache != null ? (UserSessionEntity) cache.get(userSession.getId()) : null;
}
}
@@ -578,7 +580,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public void removeOfflineUserSession(RealmModel realm, UserSessionModel userSession) {
UserSessionEntity userSessionEntity = getUserSessionEntity(userSession, true);
- removeUserSession(realm, userSessionEntity, true);
+ if (userSessionEntity != null) {
+ removeUserSession(realm, userSessionEntity, true);
+ }
}
@Override
diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
index bc8b4a3fdd..3dcc913f9b 100755
--- a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
+++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
@@ -9,9 +9,11 @@ import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
import org.junit.Ignore;
@@ -61,6 +63,11 @@ public class ClusteredCacheBehaviorTest {
}
+ @CacheEntryCreated
+ public void created(CacheEntryCreatedEvent event) {
+
+ System.out.println("Listener '" + name + "' entry created " + event.getKey() + " isPre: " + event.isPre());
+ }
@CacheEntryRemoved
public void removed(CacheEntryRemovedEvent event) {
@@ -91,6 +98,8 @@ public class ClusteredCacheBehaviorTest {
System.out.println("node1 create entry");
node1Cache.put("key", "node1");
+ System.out.println("node1 create entry");
+ node1Cache.put("key", "node111");
System.out.println("node2 create entry");
node2Cache.put("key", "node2");
System.out.println("node1 remove entry");
diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml
index 57baeea326..c1176f3a36 100755
--- a/model/jpa/pom.xml
+++ b/model/jpa/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
index 144df70ec5..444e891031 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
@@ -121,12 +121,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
}
}
- String driverDialect = config.get("driverDialect");
- if (driverDialect != null && driverDialect.length() > 0) {
- properties.put("hibernate.dialect", driverDialect);
- }
-
- String schema = config.get("schema");
+ String schema = getSchema();
if (schema != null) {
properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
}
@@ -147,6 +142,11 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
connection = getConnection();
try{
prepareOperationalInfo(connection);
+
+ String driverDialect = detectDialect(connection);
+ if (driverDialect != null) {
+ properties.put("hibernate.dialect", driverDialect);
+ }
if (databaseSchema != null) {
logger.trace("Updating database");
@@ -167,7 +167,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
}
if (currentVersion == null || !JpaUpdaterProvider.LAST_VERSION.equals(currentVersion)) {
- updater.update(session, connection, schema);
+ updater.update(connection, schema);
} else {
logger.debug("Database is up to date");
}
@@ -184,13 +184,24 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
emf = Persistence.createEntityManagerFactory(unitName, properties);
logger.trace("EntityManagerFactory created");
+ } catch (Exception e) {
+ // Safe rollback
+ if (connection != null) {
+ try {
+ connection.rollback();
+ } catch (SQLException e2) {
+ logger.warn("Can't rollback connection", e2);
+ }
+ }
+
+ throw e;
} finally {
// Close after creating EntityManagerFactory to prevent in-mem databases from closing
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
- logger.warn(e);
+ logger.warn("Can't close connection", e);
}
}
}
@@ -198,7 +209,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
}
}
}
-
+
protected void prepareOperationalInfo(Connection connection) {
try {
operationalInfo = new LinkedHashMap<>();
@@ -207,12 +218,51 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
operationalInfo.put("databaseUser", md.getUserName());
operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion());
operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion());
+
+ logger.debugf("Database info: %s", operationalInfo.toString());
} catch (SQLException e) {
logger.warn("Unable to prepare operational info due database exception: " + e.getMessage());
}
}
- private Connection getConnection() {
+
+ protected String detectDialect(Connection connection) {
+ String driverDialect = config.get("driverDialect");
+ if (driverDialect != null && driverDialect.length() > 0) {
+ return driverDialect;
+ } else {
+ try {
+ String dbProductName = connection.getMetaData().getDatabaseProductName();
+ String dbProductVersion = connection.getMetaData().getDatabaseProductVersion();
+
+ // For MSSQL2014, we may need to fix the autodetected dialect by hibernate
+ if (dbProductName.equals("Microsoft SQL Server")) {
+ String topVersionStr = dbProductVersion.split("\\.")[0];
+ boolean shouldSet2012Dialect = true;
+ try {
+ int topVersion = Integer.parseInt(topVersionStr);
+ if (topVersion < 12) {
+ shouldSet2012Dialect = false;
+ }
+ } catch (NumberFormatException nfe) {
+ }
+ if (shouldSet2012Dialect) {
+ String sql2012Dialect = "org.hibernate.dialect.SQLServer2012Dialect";
+ logger.debugf("Manually override hibernate dialect to %s", sql2012Dialect);
+ return sql2012Dialect;
+ }
+ }
+ } catch (SQLException e) {
+ logger.warnf("Unable to detect hibernate dialect due database exception : %s", e.getMessage());
+ }
+
+ return null;
+ }
+ }
+
+
+ @Override
+ public Connection getConnection() {
try {
String dataSourceLookup = config.get("dataSource");
if (dataSourceLookup != null) {
@@ -226,6 +276,11 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
throw new RuntimeException("Failed to connect to database", e);
}
}
+
+ @Override
+ public String getSchema() {
+ return config.get("schema");
+ }
@Override
public Map getOperationalInfo() {
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java
index edede60965..8d687bcef9 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java
@@ -17,6 +17,8 @@
package org.keycloak.connections.jpa;
+import java.sql.Connection;
+
import org.keycloak.provider.ProviderFactory;
/**
@@ -24,4 +26,9 @@ import org.keycloak.provider.ProviderFactory;
*/
public interface JpaConnectionProviderFactory extends ProviderFactory {
+ // Caller is responsible for closing connection
+ Connection getConnection();
+
+ String getSchema();
+
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
index 2e2c5d1f29..0e0dea2e7e 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
@@ -17,7 +17,6 @@
package org.keycloak.connections.jpa.updater;
-import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.Provider;
import java.sql.Connection;
@@ -33,7 +32,7 @@ public interface JpaUpdaterProvider extends Provider {
public String getCurrentVersionSql(String defaultSchema);
- public void update(KeycloakSession session, Connection connection, String defaultSchema);
+ public void update(Connection connection, String defaultSchema);
public void validate(Connection connection, String defaultSchema);
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java
index 80bb46710a..61075c7d3c 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java
@@ -20,18 +20,10 @@ package org.keycloak.connections.jpa.updater.liquibase;
import liquibase.Contexts;
import liquibase.Liquibase;
import liquibase.changelog.ChangeSet;
-import liquibase.changelog.DatabaseChangeLog;
import liquibase.changelog.RanChangeSet;
-import liquibase.database.Database;
-import liquibase.database.DatabaseFactory;
-import liquibase.database.core.DB2Database;
-import liquibase.database.jvm.JdbcConnection;
-import liquibase.logging.LogFactory;
-import liquibase.logging.LogLevel;
-import liquibase.resource.ClassLoaderResourceAccessor;
-import liquibase.servicelocator.ServiceLocator;
import org.jboss.logging.Logger;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
+import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
import org.keycloak.models.KeycloakSession;
import java.sql.Connection;
@@ -46,8 +38,14 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
private static final Logger logger = Logger.getLogger(LiquibaseJpaUpdaterProvider.class);
- private static final String CHANGELOG = "META-INF/jpa-changelog-master.xml";
- private static final String DB2_CHANGELOG = "META-INF/db2-jpa-changelog-master.xml";
+ public static final String CHANGELOG = "META-INF/jpa-changelog-master.xml";
+ public static final String DB2_CHANGELOG = "META-INF/db2-jpa-changelog-master.xml";
+
+ private final KeycloakSession session;
+
+ public LiquibaseJpaUpdaterProvider(KeycloakSession session) {
+ this.session = session;
+ }
@Override
public String getCurrentVersionSql(String defaultSchema) {
@@ -55,7 +53,7 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
}
@Override
- public void update(KeycloakSession session, Connection connection, String defaultSchema) {
+ public void update(Connection connection, String defaultSchema) {
logger.debug("Starting database update");
// Need ThreadLocal as liquibase doesn't seem to have API to inject custom objects into tasks
@@ -108,145 +106,14 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
}
private Liquibase getLiquibase(Connection connection, String defaultSchema) throws Exception {
- ServiceLocator sl = ServiceLocator.getInstance();
-
- if (!System.getProperties().containsKey("liquibase.scan.packages")) {
- if (sl.getPackages().remove("liquibase.core")) {
- sl.addPackageToScan("liquibase.core.xml");
- }
-
- if (sl.getPackages().remove("liquibase.parser")) {
- sl.addPackageToScan("liquibase.parser.core.xml");
- }
-
- if (sl.getPackages().remove("liquibase.serializer")) {
- sl.addPackageToScan("liquibase.serializer.core.xml");
- }
-
- sl.getPackages().remove("liquibase.ext");
- sl.getPackages().remove("liquibase.sdk");
- }
-
- LogFactory.setInstance(new LogWrapper());
-
- // Adding PostgresPlus support to liquibase
- DatabaseFactory.getInstance().register(new PostgresPlusDatabase());
-
- Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
- if (defaultSchema != null) {
- database.setDefaultSchemaName(defaultSchema);
- }
-
- String changelog = (database instanceof DB2Database) ? DB2_CHANGELOG : CHANGELOG;
- logger.debugf("Using changelog file: %s", changelog);
- return new Liquibase(changelog, new ClassLoaderResourceAccessor(getClass().getClassLoader()), database);
+ LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
+ return liquibaseProvider.getLiquibase(connection, defaultSchema);
}
@Override
public void close() {
}
- private static class LogWrapper extends LogFactory {
-
- private liquibase.logging.Logger logger = new liquibase.logging.Logger() {
- @Override
- public void setName(String name) {
- }
-
- @Override
- public void setLogLevel(String level) {
- }
-
- @Override
- public void setLogLevel(LogLevel level) {
- }
-
- @Override
- public void setLogLevel(String logLevel, String logFile) {
- }
-
- @Override
- public void severe(String message) {
- LiquibaseJpaUpdaterProvider.logger.error(message);
- }
-
- @Override
- public void severe(String message, Throwable e) {
- LiquibaseJpaUpdaterProvider.logger.error(message, e);
- }
-
- @Override
- public void warning(String message) {
- // Ignore this warning as cascaded drops doesn't work anyway with all DBs, which we need to support
- if ("Database does not support drop with cascade".equals(message)) {
- LiquibaseJpaUpdaterProvider.logger.debug(message);
- } else {
- LiquibaseJpaUpdaterProvider.logger.warn(message);
- }
- }
-
- @Override
- public void warning(String message, Throwable e) {
- LiquibaseJpaUpdaterProvider.logger.warn(message, e);
- }
-
- @Override
- public void info(String message) {
- LiquibaseJpaUpdaterProvider.logger.debug(message);
- }
-
- @Override
- public void info(String message, Throwable e) {
- LiquibaseJpaUpdaterProvider.logger.debug(message, e);
- }
-
- @Override
- public void debug(String message) {
- LiquibaseJpaUpdaterProvider.logger.trace(message);
- }
-
- @Override
- public LogLevel getLogLevel() {
- if (LiquibaseJpaUpdaterProvider.logger.isTraceEnabled()) {
- return LogLevel.DEBUG;
- } else if (LiquibaseJpaUpdaterProvider.logger.isDebugEnabled()) {
- return LogLevel.INFO;
- } else {
- return LogLevel.WARNING;
- }
- }
-
- @Override
- public void debug(String message, Throwable e) {
- LiquibaseJpaUpdaterProvider.logger.trace(message, e);
- }
-
- @Override
- public void setChangeLog(DatabaseChangeLog databaseChangeLog) {
- }
-
- @Override
- public void setChangeSet(ChangeSet changeSet) {
- }
-
- @Override
- public int getPriority() {
- return 0;
- }
- };
-
- @Override
- public liquibase.logging.Logger getLog(String name) {
- return logger;
- }
-
- @Override
- public liquibase.logging.Logger getLog() {
- return logger;
- }
-
- }
-
public static String getTable(String table, String defaultSchema) {
return defaultSchema != null ? defaultSchema + "." + table : table;
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java
index 28982b9333..26eac9a646 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java
@@ -30,7 +30,7 @@ public class LiquibaseJpaUpdaterProviderFactory implements JpaUpdaterProviderFac
@Override
public JpaUpdaterProvider create(KeycloakSession session) {
- return new LiquibaseJpaUpdaterProvider();
+ return new LiquibaseJpaUpdaterProvider(session);
}
@Override
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java
new file mode 100644
index 0000000000..a011b4153a
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2016 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.connections.jpa.updater.liquibase.conn;
+
+import java.sql.Connection;
+
+import liquibase.Liquibase;
+import liquibase.changelog.ChangeSet;
+import liquibase.changelog.DatabaseChangeLog;
+import liquibase.database.Database;
+import liquibase.database.DatabaseFactory;
+import liquibase.database.core.DB2Database;
+import liquibase.database.jvm.JdbcConnection;
+import liquibase.exception.LiquibaseException;
+import liquibase.lockservice.LockService;
+import liquibase.lockservice.LockServiceFactory;
+import liquibase.logging.LogFactory;
+import liquibase.logging.LogLevel;
+import liquibase.resource.ClassLoaderResourceAccessor;
+import liquibase.servicelocator.ServiceLocator;
+import liquibase.sqlgenerator.SqlGeneratorFactory;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider;
+import org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase;
+import org.keycloak.connections.jpa.updater.liquibase.lock.CustomInsertLockRecordGenerator;
+import org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockService;
+import org.keycloak.connections.jpa.updater.liquibase.lock.DummyLockService;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author Marek Posolda
+ */
+public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionProviderFactory, LiquibaseConnectionProvider {
+
+ private static final Logger logger = Logger.getLogger(DefaultLiquibaseConnectionProvider.class);
+
+ private volatile boolean initialized = false;
+
+ @Override
+ public LiquibaseConnectionProvider create(KeycloakSession session) {
+ if (!initialized) {
+ synchronized (this) {
+ if (!initialized) {
+ baseLiquibaseInitialization();
+ initialized = true;
+ }
+ }
+ }
+ return this;
+ }
+
+ protected void baseLiquibaseInitialization() {
+ ServiceLocator sl = ServiceLocator.getInstance();
+
+ if (!System.getProperties().containsKey("liquibase.scan.packages")) {
+ if (sl.getPackages().remove("liquibase.core")) {
+ sl.addPackageToScan("liquibase.core.xml");
+ }
+
+ if (sl.getPackages().remove("liquibase.parser")) {
+ sl.addPackageToScan("liquibase.parser.core.xml");
+ }
+
+ if (sl.getPackages().remove("liquibase.serializer")) {
+ sl.addPackageToScan("liquibase.serializer.core.xml");
+ }
+
+ sl.getPackages().remove("liquibase.ext");
+ sl.getPackages().remove("liquibase.sdk");
+ }
+
+ LogFactory.setInstance(new LogWrapper());
+
+ // Adding PostgresPlus support to liquibase
+ DatabaseFactory.getInstance().register(new PostgresPlusDatabase());
+
+ // Change command for creating lock and drop DELETE lock record from it
+ SqlGeneratorFactory.getInstance().register(new CustomInsertLockRecordGenerator());
+ }
+
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public String getId() {
+ return "default";
+ }
+
+ @Override
+ public Liquibase getLiquibase(Connection connection, String defaultSchema) throws LiquibaseException {
+ Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
+ if (defaultSchema != null) {
+ database.setDefaultSchemaName(defaultSchema);
+ }
+
+ String changelog = (database instanceof DB2Database) ? LiquibaseJpaUpdaterProvider.DB2_CHANGELOG : LiquibaseJpaUpdaterProvider.CHANGELOG;
+ logger.debugf("Using changelog file: %s", changelog);
+
+ // We wrap liquibase update in CustomLockService provided by DBLockProvider. No need to lock inside liquibase itself.
+ // NOTE: This can't be done in baseLiquibaseInitialization() as liquibase always restarts lock service
+ LockServiceFactory.getInstance().register(new DummyLockService());
+
+ return new Liquibase(changelog, new ClassLoaderResourceAccessor(getClass().getClassLoader()), database);
+ }
+
+ private static class LogWrapper extends LogFactory {
+
+ private liquibase.logging.Logger logger = new liquibase.logging.Logger() {
+ @Override
+ public void setName(String name) {
+ }
+
+ @Override
+ public void setLogLevel(String level) {
+ }
+
+ @Override
+ public void setLogLevel(LogLevel level) {
+ }
+
+ @Override
+ public void setLogLevel(String logLevel, String logFile) {
+ }
+
+ @Override
+ public void severe(String message) {
+ DefaultLiquibaseConnectionProvider.logger.error(message);
+ }
+
+ @Override
+ public void severe(String message, Throwable e) {
+ DefaultLiquibaseConnectionProvider.logger.error(message, e);
+ }
+
+ @Override
+ public void warning(String message) {
+ // Ignore this warning as cascaded drops doesn't work anyway with all DBs, which we need to support
+ if ("Database does not support drop with cascade".equals(message)) {
+ DefaultLiquibaseConnectionProvider.logger.debug(message);
+ } else {
+ DefaultLiquibaseConnectionProvider.logger.warn(message);
+ }
+ }
+
+ @Override
+ public void warning(String message, Throwable e) {
+ DefaultLiquibaseConnectionProvider.logger.warn(message, e);
+ }
+
+ @Override
+ public void info(String message) {
+ DefaultLiquibaseConnectionProvider.logger.debug(message);
+ }
+
+ @Override
+ public void info(String message, Throwable e) {
+ DefaultLiquibaseConnectionProvider.logger.debug(message, e);
+ }
+
+ @Override
+ public void debug(String message) {
+ if (DefaultLiquibaseConnectionProvider.logger.isTraceEnabled()) {
+ DefaultLiquibaseConnectionProvider.logger.trace(message);
+ }
+ }
+
+ @Override
+ public LogLevel getLogLevel() {
+ if (DefaultLiquibaseConnectionProvider.logger.isTraceEnabled()) {
+ return LogLevel.DEBUG;
+ } else if (DefaultLiquibaseConnectionProvider.logger.isDebugEnabled()) {
+ return LogLevel.INFO;
+ } else {
+ return LogLevel.WARNING;
+ }
+ }
+
+ @Override
+ public void debug(String message, Throwable e) {
+ DefaultLiquibaseConnectionProvider.logger.trace(message, e);
+ }
+
+ @Override
+ public void setChangeLog(DatabaseChangeLog databaseChangeLog) {
+ }
+
+ @Override
+ public void setChangeSet(ChangeSet changeSet) {
+ }
+
+ @Override
+ public int getPriority() {
+ return 0;
+ }
+ };
+
+ @Override
+ public liquibase.logging.Logger getLog(String name) {
+ return logger;
+ }
+
+ @Override
+ public liquibase.logging.Logger getLog() {
+ return logger;
+ }
+
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProvider.java
new file mode 100644
index 0000000000..5aa81cc84e
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016 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.connections.jpa.updater.liquibase.conn;
+
+import java.sql.Connection;
+
+import liquibase.Liquibase;
+import liquibase.exception.LiquibaseException;
+import org.keycloak.provider.Provider;
+
+/**
+ * @author Marek Posolda
+ */
+public interface LiquibaseConnectionProvider extends Provider {
+
+ Liquibase getLiquibase(Connection connection, String defaultSchema) throws LiquibaseException;
+
+}
diff --git a/services/src/main/java/org/keycloak/messages/MessagesProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProviderFactory.java
similarity index 75%
rename from services/src/main/java/org/keycloak/messages/MessagesProviderFactory.java
rename to model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProviderFactory.java
index 10d2a2e1f0..7dfe5f9d55 100644
--- a/services/src/main/java/org/keycloak/messages/MessagesProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProviderFactory.java
@@ -15,13 +15,12 @@
* limitations under the License.
*/
-package org.keycloak.messages;
+package org.keycloak.connections.jpa.updater.liquibase.conn;
import org.keycloak.provider.ProviderFactory;
/**
- * @author Leonardo Zanivan
+ * @author Marek Posolda
*/
-public interface MessagesProviderFactory extends ProviderFactory {
-
+public interface LiquibaseConnectionProviderFactory extends ProviderFactory {
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionSpi.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionSpi.java
new file mode 100644
index 0000000000..7aeda323b3
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionSpi.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 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.connections.jpa.updater.liquibase.conn;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author Marek Posolda
+ */
+public class LiquibaseConnectionSpi implements Spi {
+
+ @Override
+ public boolean isInternal() {
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "connectionsLiquibase";
+ }
+
+ @Override
+ public Class extends Provider> getProviderClass() {
+ return LiquibaseConnectionProvider.class;
+ }
+
+ @Override
+ public Class extends ProviderFactory> getProviderFactoryClass() {
+ return LiquibaseConnectionProviderFactory.class;
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomInsertLockRecordGenerator.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomInsertLockRecordGenerator.java
new file mode 100644
index 0000000000..3b1e2f9571
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomInsertLockRecordGenerator.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016 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.connections.jpa.updater.liquibase.lock;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import liquibase.database.Database;
+import liquibase.exception.ValidationErrors;
+import liquibase.sql.Sql;
+import liquibase.sqlgenerator.SqlGeneratorChain;
+import liquibase.sqlgenerator.core.AbstractSqlGenerator;
+import liquibase.statement.core.DeleteStatement;
+import liquibase.statement.core.InitializeDatabaseChangeLogLockTableStatement;
+
+/**
+ * We need to remove DELETE SQL command, which liquibase adds by default when inserting record to table lock. This is causing buggy behaviour
+ *
+ * @author Marek Posolda
+ */
+public class CustomInsertLockRecordGenerator extends AbstractSqlGenerator {
+
+ @Override
+ public int getPriority() {
+ return super.getPriority() + 1; // Ensure bigger priority than InitializeDatabaseChangeLogLockTableGenerator
+ }
+
+ @Override
+ public ValidationErrors validate(InitializeDatabaseChangeLogLockTableStatement initializeDatabaseChangeLogLockTableStatement, Database database, SqlGeneratorChain sqlGeneratorChain) {
+ return new ValidationErrors();
+ }
+
+ @Override
+ public Sql[] generateSql(InitializeDatabaseChangeLogLockTableStatement statement, Database database, SqlGeneratorChain sqlGeneratorChain) {
+ // Generated by InitializeDatabaseChangeLogLockTableGenerator
+ Sql[] sqls = sqlGeneratorChain.generateSql(statement, database);
+
+ // Removing delete statement
+ List result = new ArrayList<>();
+ for (Sql sql : sqls) {
+ String sqlCommand = sql.toSql();
+ if (!sqlCommand.toUpperCase().contains("DELETE")) {
+ result.add(sql);
+ }
+ }
+
+ return result.toArray(new Sql[result.size()]);
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockService.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockService.java
new file mode 100644
index 0000000000..0c246ca9a3
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockService.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2016 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.connections.jpa.updater.liquibase.lock;
+
+import java.lang.reflect.Field;
+import java.text.DateFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+
+import liquibase.database.Database;
+import liquibase.database.core.DerbyDatabase;
+import liquibase.exception.DatabaseException;
+import liquibase.exception.LockException;
+import liquibase.executor.Executor;
+import liquibase.executor.ExecutorService;
+import liquibase.lockservice.DatabaseChangeLogLock;
+import liquibase.lockservice.StandardLockService;
+import liquibase.logging.LogFactory;
+import liquibase.sql.visitor.AbstractSqlVisitor;
+import liquibase.sql.visitor.SqlVisitor;
+import liquibase.statement.core.CreateDatabaseChangeLogLockTableStatement;
+import liquibase.statement.core.DropTableStatement;
+import liquibase.statement.core.InitializeDatabaseChangeLogLockTableStatement;
+import liquibase.statement.core.RawSqlStatement;
+import org.jboss.logging.Logger;
+import org.keycloak.common.util.Time;
+import org.keycloak.common.util.reflections.Reflections;
+
+/**
+ * Liquibase lock service, which has some bugfixes and assumes timeouts to be configured in milliseconds
+ *
+ * @author Marek Posolda
+ */
+public class CustomLockService extends StandardLockService {
+
+ private static final Logger log = Logger.getLogger(CustomLockService.class);
+
+ private long changeLogLocRecheckTimeMillis = -1;
+
+ @Override
+ public void setChangeLogLockRecheckTime(long changeLogLocRecheckTime) {
+ super.setChangeLogLockRecheckTime(changeLogLocRecheckTime);
+ this.changeLogLocRecheckTimeMillis = changeLogLocRecheckTime;
+ }
+
+ // Bug in StandardLockService.getChangeLogLockRecheckTime()
+ @Override
+ public Long getChangeLogLockRecheckTime() {
+ if (changeLogLocRecheckTimeMillis == -1) {
+ return super.getChangeLogLockRecheckTime();
+ } else {
+ return changeLogLocRecheckTimeMillis;
+ }
+ }
+
+ @Override
+ public void init() throws DatabaseException {
+ boolean createdTable = false;
+ Executor executor = ExecutorService.getInstance().getExecutor(database);
+
+ if (!hasDatabaseChangeLogLockTable()) {
+
+ try {
+ if (log.isTraceEnabled()) {
+ log.trace("Create Database Lock Table");
+ }
+ executor.execute(new CreateDatabaseChangeLogLockTableStatement());
+ database.commit();
+ } catch (DatabaseException de) {
+ log.warn("Failed to create lock table. Maybe other transaction created in the meantime. Retrying...");
+ if (log.isDebugEnabled()) {
+ log.debug(de.getMessage(), de); //Log details at debug level
+ }
+ database.rollback();
+ throw new LockRetryException(de);
+ }
+
+ log.debugf("Created database lock table with name: %s", database.escapeTableName(database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName()));
+
+ try {
+ Field field = Reflections.findDeclaredField(StandardLockService.class, "hasDatabaseChangeLogLockTable");
+ Reflections.setAccessible(field);
+ field.set(CustomLockService.this, true);
+ } catch (IllegalAccessException iae) {
+ throw new RuntimeException(iae);
+ }
+
+ createdTable = true;
+ }
+
+
+ if (!isDatabaseChangeLogLockTableInitialized(createdTable)) {
+ try {
+ if (log.isTraceEnabled()) {
+ log.trace("Initialize Database Lock Table");
+ }
+ executor.execute(new InitializeDatabaseChangeLogLockTableStatement());
+ database.commit();
+
+ } catch (DatabaseException de) {
+ log.warn("Failed to insert first record to the lock table. Maybe other transaction inserted in the meantime. Retrying...");
+ if (log.isDebugEnabled()) {
+ log.debug(de.getMessage(), de); // Log details at debug level
+ }
+ database.rollback();
+ throw new LockRetryException(de);
+ }
+
+ log.debug("Initialized record in the database lock table");
+ }
+
+
+ // Keycloak doesn't support Derby, but keep it for sure...
+ if (executor.updatesDatabase() && database instanceof DerbyDatabase && ((DerbyDatabase) database).supportsBooleanDataType()) { //check if the changelog table is of an old smallint vs. boolean format
+ String lockTable = database.escapeTableName(database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName());
+ Object obj = executor.queryForObject(new RawSqlStatement("select min(locked) as test from " + lockTable + " fetch first row only"), Object.class);
+ if (!(obj instanceof Boolean)) { //wrong type, need to recreate table
+ executor.execute(new DropTableStatement(database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName(), false));
+ executor.execute(new CreateDatabaseChangeLogLockTableStatement());
+ executor.execute(new InitializeDatabaseChangeLogLockTableStatement());
+ }
+ }
+
+ }
+
+ @Override
+ public void waitForLock() throws LockException {
+ boolean locked = false;
+ long startTime = Time.toMillis(Time.currentTime());
+ long timeToGiveUp = startTime + (getChangeLogLockWaitTime());
+
+ while (!locked && Time.toMillis(Time.currentTime()) < timeToGiveUp) {
+ locked = acquireLock();
+ if (!locked) {
+ int remainingTime = ((int)(timeToGiveUp / 1000)) - Time.currentTime();
+ log.debugf("Waiting for changelog lock... Remaining time: %d seconds", remainingTime);
+ try {
+ Thread.sleep(getChangeLogLockRecheckTime());
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ if (!locked) {
+ DatabaseChangeLogLock[] locks = listLocks();
+ String lockedBy;
+ if (locks.length > 0) {
+ DatabaseChangeLogLock lock = locks[0];
+ lockedBy = lock.getLockedBy() + " since " + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(lock.getLockGranted());
+ } else {
+ lockedBy = "UNKNOWN";
+ }
+ throw new LockException("Could not acquire change log lock. Currently locked by " + lockedBy);
+ }
+ }
+
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/DummyLockService.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/DummyLockService.java
new file mode 100644
index 0000000000..61ef19fdfa
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/DummyLockService.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 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.connections.jpa.updater.liquibase.lock;
+
+import liquibase.exception.LockException;
+import liquibase.lockservice.StandardLockService;
+
+/**
+ * Dummy lock service injected to Liquibase. Doesn't need to do anything as we already have a lock when Liquibase update is called.
+ *
+ * @author Marek Posolda
+ */
+public class DummyLockService extends StandardLockService {
+
+ @Override
+ public void waitForLock() throws LockException {
+ }
+
+ @Override
+ public void releaseLock() throws LockException {
+ }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java
new file mode 100644
index 0000000000..8769053102
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2016 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.connections.jpa.updater.liquibase.lock;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import liquibase.Liquibase;
+import liquibase.exception.DatabaseException;
+import liquibase.exception.LiquibaseException;
+import liquibase.exception.LockException;
+import liquibase.lockservice.LockService;
+import org.jboss.logging.Logger;
+import org.keycloak.connections.jpa.JpaConnectionProvider;
+import org.keycloak.connections.jpa.JpaConnectionProviderFactory;
+import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.dblock.DBLockProvider;
+
+/**
+ * @author Marek Posolda
+ */
+public class LiquibaseDBLockProvider implements DBLockProvider {
+
+ private static final Logger logger = Logger.getLogger(LiquibaseDBLockProvider.class);
+
+ // 3 should be sufficient (Potentially one failure for createTable and one for insert record)
+ private int DEFAULT_MAX_ATTEMPTS = 3;
+
+
+ private final LiquibaseDBLockProviderFactory factory;
+ private final KeycloakSession session;
+
+ private LockService lockService;
+ private Connection dbConnection;
+
+ private int maxAttempts = DEFAULT_MAX_ATTEMPTS;
+
+ public LiquibaseDBLockProvider(LiquibaseDBLockProviderFactory factory, KeycloakSession session) {
+ this.factory = factory;
+ this.session = session;
+ init();
+ }
+
+ private void init() {
+ LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
+ JpaConnectionProviderFactory jpaProviderFactory = (JpaConnectionProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(JpaConnectionProvider.class);
+
+ this.dbConnection = jpaProviderFactory.getConnection();
+ String defaultSchema = jpaProviderFactory.getSchema();
+
+ try {
+ Liquibase liquibase = liquibaseProvider.getLiquibase(dbConnection, defaultSchema);
+
+ this.lockService = new CustomLockService();
+ lockService.setChangeLogLockWaitTime(factory.getLockWaitTimeoutMillis());
+ lockService.setChangeLogLockRecheckTime(factory.getLockRecheckTimeMillis());
+ lockService.setDatabase(liquibase.getDatabase());
+ } catch (LiquibaseException exception) {
+ safeRollbackConnection();
+ safeCloseConnection();
+ throw new IllegalStateException(exception);
+ }
+ }
+
+ // Assumed transaction was rolled-back and we want to start with new DB connection
+ private void restart() {
+ safeCloseConnection();
+ this.dbConnection = null;
+ this.lockService = null;
+ init();
+ }
+
+
+ @Override
+ public void waitForLock() {
+ while (maxAttempts > 0) {
+ try {
+ lockService.waitForLock();
+ this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
+ return;
+ } catch (LockException le) {
+ if (le.getCause() != null && le.getCause() instanceof LockRetryException) {
+ // Indicates we should try to acquire lock again in different transaction
+ restart();
+ maxAttempts--;
+ } else {
+ throw new IllegalStateException("Failed to retrieve lock", le);
+
+ // TODO: Possibility to forcefully retrieve lock after timeout instead of just give-up?
+ }
+ }
+ }
+ }
+
+
+ @Override
+ public void releaseLock() {
+ try {
+ lockService.releaseLock();
+ } catch (LockException e) {
+ logger.error("Could not release lock", e);
+ }
+ lockService.reset();
+ }
+
+ @Override
+ public void destroyLockInfo() {
+ try {
+ this.lockService.destroy();
+ dbConnection.commit();
+ logger.debug("Destroyed lock table");
+ } catch (DatabaseException | SQLException de) {
+ logger.error("Failed to destroy lock table");
+ safeRollbackConnection();
+ }
+ }
+
+ @Override
+ public void close() {
+ safeCloseConnection();
+ }
+
+ private void safeRollbackConnection() {
+ if (dbConnection != null) {
+ try {
+ this.dbConnection.rollback();
+ } catch (SQLException se) {
+ logger.warn("Failed to rollback connection after error", se);
+ }
+ }
+ }
+
+ private void safeCloseConnection() {
+ // Close after creating EntityManagerFactory to prevent in-mem databases from closing
+ if (dbConnection != null) {
+ try {
+ dbConnection.close();
+ } catch (SQLException e) {
+ logger.warn("Failed to close connection", e);
+ }
+ }
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProviderFactory.java
new file mode 100644
index 0000000000..5ce8be3f47
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProviderFactory.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016 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.connections.jpa.updater.liquibase.lock;
+
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.common.util.Time;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.dblock.DBLockProviderFactory;
+
+/**
+ * @author Marek Posolda
+ */
+public class LiquibaseDBLockProviderFactory implements DBLockProviderFactory {
+
+ private static final Logger logger = Logger.getLogger(LiquibaseDBLockProviderFactory.class);
+
+ private long lockRecheckTimeMillis;
+ private long lockWaitTimeoutMillis;
+
+ protected long getLockRecheckTimeMillis() {
+ return lockRecheckTimeMillis;
+ }
+
+ protected long getLockWaitTimeoutMillis() {
+ return lockWaitTimeoutMillis;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ int lockRecheckTime = config.getInt("lockRecheckTime", 2);
+ int lockWaitTimeout = config.getInt("lockWaitTimeout", 900);
+ this.lockRecheckTimeMillis = Time.toMillis(lockRecheckTime);
+ this.lockWaitTimeoutMillis = Time.toMillis(lockWaitTimeout);
+ logger.debugf("Liquibase lock provider configured with lockWaitTime: %d seconds, lockRecheckTime: %d seconds", lockWaitTimeout, lockRecheckTime);
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public LiquibaseDBLockProvider create(KeycloakSession session) {
+ return new LiquibaseDBLockProvider(this, session);
+ }
+
+ @Override
+ public void setTimeouts(long lockRecheckTimeMillis, long lockWaitTimeoutMillis) {
+ this.lockRecheckTimeMillis = lockRecheckTimeMillis;
+ this.lockWaitTimeoutMillis = lockWaitTimeoutMillis;
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String getId() {
+ return "jpa";
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LockRetryException.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LockRetryException.java
new file mode 100644
index 0000000000..e86e2b5dda
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LockRetryException.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 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.connections.jpa.updater.liquibase.lock;
+
+/**
+ * Indicates that retrieve lock wasn't successful, but it worth to retry it in different transaction (For example if we were trying to create LOCK table, but other transaction
+ * created the table in the meantime etc)
+ *
+ * @author Marek Posolda
+ */
+public class LockRetryException extends RuntimeException {
+
+ public LockRetryException(String message) {
+ super(message);
+ }
+
+ public LockRetryException(Throwable cause) {
+ super(cause);
+ }
+
+ public LockRetryException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index 115d4dd839..adde0e436d 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -47,7 +47,7 @@ import java.util.Set;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class ClientAdapter implements ClientModel {
+public class ClientAdapter implements ClientModel, JpaModel {
protected KeycloakSession session;
protected RealmModel realm;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientTemplateAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientTemplateAdapter.java
index 826ed8448b..e38ab7361f 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientTemplateAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientTemplateAdapter.java
@@ -24,6 +24,7 @@ import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
+import org.keycloak.models.jpa.entities.ClientEntity;
import org.keycloak.models.jpa.entities.ClientTemplateEntity;
import org.keycloak.models.jpa.entities.ProtocolMapperEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
@@ -42,7 +43,7 @@ import java.util.Set;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class ClientTemplateAdapter implements ClientTemplateModel {
+public class ClientTemplateAdapter implements ClientTemplateModel , JpaModel {
protected KeycloakSession session;
protected RealmModel realm;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java
index a2d0fece4b..6ea87c5587 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java
@@ -41,7 +41,7 @@ import java.util.Set;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class GroupAdapter implements GroupModel {
+public class GroupAdapter implements GroupModel , JpaModel {
protected GroupEntity group;
protected EntityManager em;
@@ -53,7 +53,7 @@ public class GroupAdapter implements GroupModel {
this.realm = realm;
}
- public GroupEntity getGroup() {
+ public GroupEntity getEntity() {
return group;
}
@@ -88,7 +88,7 @@ public class GroupAdapter implements GroupModel {
public static GroupEntity toEntity(GroupModel model, EntityManager em) {
if (model instanceof GroupAdapter) {
- return ((GroupAdapter)model).getGroup();
+ return ((GroupAdapter)model).getEntity();
}
return em.getReference(GroupEntity.class, model.getId());
}
@@ -233,7 +233,7 @@ public class GroupAdapter implements GroupModel {
protected TypedQuery getGroupRoleMappingEntityTypedQuery(RoleModel role) {
TypedQuery query = em.createNamedQuery("groupHasRole", GroupRoleMappingEntity.class);
- query.setParameter("group", getGroup());
+ query.setParameter("group", getEntity());
query.setParameter("roleId", role.getId());
return query;
}
@@ -242,7 +242,7 @@ public class GroupAdapter implements GroupModel {
public void grantRole(RoleModel role) {
if (hasRole(role)) return;
GroupRoleMappingEntity entity = new GroupRoleMappingEntity();
- entity.setGroup(getGroup());
+ entity.setGroup(getEntity());
entity.setRoleId(role.getId());
em.persist(entity);
em.flush();
@@ -269,7 +269,7 @@ public class GroupAdapter implements GroupModel {
// we query ids only as the role might be cached and following the @ManyToOne will result in a load
// even if we're getting just the id.
TypedQuery query = em.createNamedQuery("groupRoleMappingIds", String.class);
- query.setParameter("group", getGroup());
+ query.setParameter("group", getEntity());
List ids = query.getResultList();
Set roles = new HashSet();
for (String roleId : ids) {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaModel.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaModel.java
new file mode 100755
index 0000000000..7cc915f325
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaModel.java
@@ -0,0 +1,9 @@
+package org.keycloak.models.jpa;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public interface JpaModel {
+ T getEntity();
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index 635521e157..8ed10be0bb 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
@@ -39,9 +39,11 @@ import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -53,7 +55,6 @@ public class JpaRealmProvider implements RealmProvider {
private final KeycloakSession session;
protected EntityManager em;
-
public JpaRealmProvider(KeycloakSession session, EntityManager em) {
this.session = session;
this.em = em;
@@ -76,21 +77,22 @@ public class JpaRealmProvider implements RealmProvider {
realm.setId(id);
em.persist(realm);
em.flush();
- final RealmModel model = new RealmAdapter(session, em, realm);
+ final RealmModel adapter = new RealmAdapter(session, em, realm);
session.getKeycloakSessionFactory().publish(new RealmModel.RealmCreationEvent() {
@Override
public RealmModel getCreatedRealm() {
- return model;
+ return adapter;
}
});
- return model;
+ return adapter;
}
@Override
public RealmModel getRealm(String id) {
RealmEntity realm = em.find(RealmEntity.class, id);
if (realm == null) return null;
- return new RealmAdapter(session, em, realm);
+ RealmAdapter adapter = new RealmAdapter(session, em, realm);
+ return adapter;
}
@Override
@@ -124,6 +126,7 @@ public class JpaRealmProvider implements RealmProvider {
if (realm == null) {
return false;
}
+ em.refresh(realm);
RealmAdapter adapter = new RealmAdapter(session, em, realm);
session.users().preRemove(adapter);
int num = em.createNamedQuery("deleteGroupRoleMappingsByRealm")
@@ -174,7 +177,8 @@ public class JpaRealmProvider implements RealmProvider {
entity.setRealmId(realm.getId());
em.persist(entity);
em.flush();
- return new RoleAdapter(session, realm, em, entity);
+ RoleAdapter adapter = new RoleAdapter(session, realm, em, entity);
+ return adapter;
}
@@ -203,7 +207,8 @@ public class JpaRealmProvider implements RealmProvider {
roleEntity.setRealmId(realm.getId());
em.persist(roleEntity);
em.flush();
- return new RoleAdapter(session, realm, em, roleEntity);
+ RoleAdapter adapter = new RoleAdapter(session, realm, em, roleEntity);
+ return adapter;
}
@Override
@@ -247,11 +252,11 @@ public class JpaRealmProvider implements RealmProvider {
@Override
public boolean removeRole(RealmModel realm, RoleModel role) {
session.users().preRemove(realm, role);
- RoleEntity roleEntity = em.getReference(RoleEntity.class, role.getId());
RoleContainerModel container = role.getContainer();
if (container.getDefaultRoles().contains(role.getName())) {
container.removeDefaultRoles(role.getName());
}
+ RoleEntity roleEntity = em.getReference(RoleEntity.class, role.getId());
String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em);
em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", roleEntity).executeUpdate();
em.createNamedQuery("deleteScopeMappingByRole").setParameter("role", roleEntity).executeUpdate();
@@ -260,7 +265,6 @@ public class JpaRealmProvider implements RealmProvider {
em.remove(roleEntity);
em.flush();
-
return true;
}
@@ -270,7 +274,8 @@ public class JpaRealmProvider implements RealmProvider {
RoleEntity entity = em.find(RoleEntity.class, id);
if (entity == null) return null;
if (!realm.getId().equals(entity.getRealmId())) return null;
- return new RoleAdapter(session, realm, em, entity);
+ RoleAdapter adapter = new RoleAdapter(session, realm, em, entity);
+ return adapter;
}
@Override
@@ -278,7 +283,8 @@ public class JpaRealmProvider implements RealmProvider {
GroupEntity groupEntity = em.find(GroupEntity.class, id);
if (groupEntity == null) return null;
if (!groupEntity.getRealm().getId().equals(realm.getId())) return null;
- return new GroupAdapter(realm, em, groupEntity);
+ GroupAdapter adapter = new GroupAdapter(realm, em, groupEntity);
+ return adapter;
}
@Override
@@ -361,7 +367,8 @@ public class JpaRealmProvider implements RealmProvider {
groupEntity.setRealm(realmEntity);
em.persist(groupEntity);
- return new GroupAdapter(realm, em, groupEntity);
+ GroupAdapter adapter = new GroupAdapter(realm, em, groupEntity);
+ return adapter;
}
@Override
@@ -391,6 +398,7 @@ public class JpaRealmProvider implements RealmProvider {
em.persist(entity);
em.flush();
final ClientModel resource = new ClientAdapter(realm, em, session, entity);
+
em.flush();
session.getKeycloakSessionFactory().publish(new RealmModel.ClientCreationEvent() {
@Override
@@ -416,14 +424,14 @@ public class JpaRealmProvider implements RealmProvider {
}
-
@Override
public ClientModel getClientById(String id, RealmModel realm) {
ClientEntity app = em.find(ClientEntity.class, id);
-
// Check if application belongs to this realm
if (app == null || !realm.getId().equals(app.getRealm().getId())) return null;
- return new ClientAdapter(realm, em, session, app);
+ ClientAdapter client = new ClientAdapter(realm, em, session, app);
+ return client;
+
}
@Override
@@ -468,6 +476,7 @@ public class JpaRealmProvider implements RealmProvider {
// Check if application belongs to this realm
if (app == null || !realm.getId().equals(app.getRealm().getId())) return null;
- return new ClientTemplateAdapter(realm, em, session, app);
+ ClientTemplateAdapter adapter = new ClientTemplateAdapter(realm, em, session, app);
+ return adapter;
}
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 3dd9de7e0f..f517782360 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -65,7 +65,7 @@ import java.util.Set;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class RealmAdapter implements RealmModel {
+public class RealmAdapter implements RealmModel, JpaModel {
protected static final Logger logger = Logger.getLogger(RealmAdapter.class);
protected RealmEntity realm;
protected EntityManager em;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
index 0ac1be9e8d..61cf70c4fb 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
@@ -21,6 +21,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
+import org.keycloak.models.jpa.entities.RealmEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -33,7 +34,7 @@ import java.util.Set;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class RoleAdapter implements RoleModel {
+public class RoleAdapter implements RoleModel, JpaModel {
protected RoleEntity role;
protected EntityManager em;
protected RealmModel realm;
@@ -46,7 +47,7 @@ public class RoleAdapter implements RoleModel {
this.session = session;
}
- public RoleEntity getRole() {
+ public RoleEntity getEntity() {
return role;
}
@@ -97,17 +98,17 @@ public class RoleAdapter implements RoleModel {
@Override
public void addCompositeRole(RoleModel role) {
RoleEntity entity = RoleAdapter.toRoleEntity(role, em);
- for (RoleEntity composite : getRole().getCompositeRoles()) {
+ for (RoleEntity composite : getEntity().getCompositeRoles()) {
if (composite.equals(entity)) return;
}
- getRole().getCompositeRoles().add(entity);
+ getEntity().getCompositeRoles().add(entity);
em.flush();
}
@Override
public void removeCompositeRole(RoleModel role) {
RoleEntity entity = RoleAdapter.toRoleEntity(role, em);
- Iterator it = getRole().getCompositeRoles().iterator();
+ Iterator it = getEntity().getCompositeRoles().iterator();
while (it.hasNext()) {
if (it.next().equals(entity)) it.remove();
}
@@ -117,7 +118,7 @@ public class RoleAdapter implements RoleModel {
public Set getComposites() {
Set set = new HashSet();
- for (RoleEntity composite : getRole().getCompositeRoles()) {
+ for (RoleEntity composite : getEntity().getCompositeRoles()) {
set.add(new RoleAdapter(session, realm, em, composite));
// todo I want to do this, but can't as you get stack overflow
@@ -161,7 +162,7 @@ public class RoleAdapter implements RoleModel {
public static RoleEntity toRoleEntity(RoleModel model, EntityManager em) {
if (model instanceof RoleAdapter) {
- return ((RoleAdapter)model).getRole();
+ return ((RoleAdapter)model).getEntity();
}
return em.getReference(RoleEntity.class, model.getId());
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index 9114af1e69..ef24368113 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -63,7 +63,7 @@ import java.util.Set;
* @author Bill Burke
* @version $Revision: 1 $
*/
-public class UserAdapter implements UserModel {
+public class UserAdapter implements UserModel, JpaModel {
protected UserEntity user;
protected EntityManager em;
@@ -77,7 +77,7 @@ public class UserAdapter implements UserModel {
this.session = session;
}
- public UserEntity getUser() {
+ public UserEntity getEntity() {
return user;
}
@@ -503,7 +503,7 @@ public class UserAdapter implements UserModel {
// we query ids only as the group might be cached and following the @ManyToOne will result in a load
// even if we're getting just the id.
TypedQuery query = em.createNamedQuery("userGroupIds", String.class);
- query.setParameter("user", getUser());
+ query.setParameter("user", getEntity());
List ids = query.getResultList();
Set groups = new HashSet<>();
for (String groupId : ids) {
@@ -518,7 +518,7 @@ public class UserAdapter implements UserModel {
public void joinGroup(GroupModel group) {
if (isMemberOf(group)) return;
UserGroupMembershipEntity entity = new UserGroupMembershipEntity();
- entity.setUser(getUser());
+ entity.setUser(getEntity());
entity.setGroupId(group.getId());
em.persist(entity);
em.flush();
@@ -548,7 +548,7 @@ public class UserAdapter implements UserModel {
protected TypedQuery getUserGroupMappingQuery(GroupModel group) {
TypedQuery query = em.createNamedQuery("userMemberOf", UserGroupMembershipEntity.class);
- query.setParameter("user", getUser());
+ query.setParameter("user", getEntity());
query.setParameter("groupId", group.getId());
return query;
}
@@ -562,7 +562,7 @@ public class UserAdapter implements UserModel {
protected TypedQuery getUserRoleMappingEntityTypedQuery(RoleModel role) {
TypedQuery query = em.createNamedQuery("userHasRole", UserRoleMappingEntity.class);
- query.setParameter("user", getUser());
+ query.setParameter("user", getEntity());
query.setParameter("roleId", role.getId());
return query;
}
@@ -571,7 +571,7 @@ public class UserAdapter implements UserModel {
public void grantRole(RoleModel role) {
if (hasRole(role)) return;
UserRoleMappingEntity entity = new UserRoleMappingEntity();
- entity.setUser(getUser());
+ entity.setUser(getEntity());
entity.setRoleId(role.getId());
em.persist(entity);
em.flush();
@@ -598,7 +598,7 @@ public class UserAdapter implements UserModel {
// we query ids only as the role might be cached and following the @ManyToOne will result in a load
// even if we're getting just the id.
TypedQuery query = em.createNamedQuery("userRoleMappingIds", String.class);
- query.setParameter("user", getUser());
+ query.setParameter("user", getEntity());
List ids = query.getResultList();
Set roles = new HashSet();
for (String roleId : ids) {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index fc10217127..efbd154f71 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -50,6 +50,7 @@ import java.util.Set;
@Table(name="CLIENT", uniqueConstraints = {@UniqueConstraint(columnNames = {"REALM_ID", "CLIENT_ID"})})
@NamedQueries({
@NamedQuery(name="getClientsByRealm", query="select client from ClientEntity client where client.realm = :realm"),
+ @NamedQuery(name="getClientById", query="select client from ClientEntity client where client.id = :id and client.realm.id = :realm"),
@NamedQuery(name="getClientIdsByRealm", query="select client.id from ClientEntity client where client.realm.id = :realm"),
@NamedQuery(name="findClientIdByClientId", query="select client.id from ClientEntity client where client.clientId = :clientId and client.realm.id = :realm"),
@NamedQuery(name="findClientByClientId", query="select client from ClientEntity client where client.clientId = :clientId and client.realm.id = :realm"),
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 052ce6e558..5ad023afc4 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -117,11 +117,11 @@ public class RealmEntity {
@Column(name="NOT_BEFORE")
protected int notBefore;
- @Column(name="PUBLIC_KEY", length = 2048)
+ @Column(name="PUBLIC_KEY", length = 4000)
protected String publicKeyPem;
- @Column(name="PRIVATE_KEY", length = 2048)
+ @Column(name="PRIVATE_KEY", length = 4000)
protected String privateKeyPem;
- @Column(name="CERTIFICATE", length = 2048)
+ @Column(name="CERTIFICATE", length = 4000)
protected String certificatePem;
@Column(name="CODE_SECRET", length = 255)
protected String codeSecret;
diff --git a/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-1.9.1.xml b/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-1.9.1.xml
new file mode 100644
index 0000000000..d0b387dab2
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-1.9.1.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml
index ae4592d575..a69e443710 100644
--- a/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml
@@ -30,4 +30,5 @@
+
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.1.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.1.xml
new file mode 100755
index 0000000000..f801ddc19e
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.1.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
index 6a4d1dcbbf..c107b8ef58 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -30,4 +30,5 @@
+
diff --git a/model/jpa/src/main/resources/META-INF/services/org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProviderFactory b/model/jpa/src/main/resources/META-INF/services/org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProviderFactory
new file mode 100644
index 0000000000..f7e18f0ce0
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/services/org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProviderFactory
@@ -0,0 +1,18 @@
+#
+# Copyright 2016 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.
+#
+
+org.keycloak.connections.jpa.updater.liquibase.conn.DefaultLiquibaseConnectionProvider
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.messages.MessagesProviderFactory b/model/jpa/src/main/resources/META-INF/services/org.keycloak.models.dblock.DBLockProviderFactory
similarity index 88%
rename from services/src/main/resources/META-INF/services/org.keycloak.messages.MessagesProviderFactory
rename to model/jpa/src/main/resources/META-INF/services/org.keycloak.models.dblock.DBLockProviderFactory
index 617bd16097..2b2ac0234b 100644
--- a/services/src/main/resources/META-INF/services/org.keycloak.messages.MessagesProviderFactory
+++ b/model/jpa/src/main/resources/META-INF/services/org.keycloak.models.dblock.DBLockProviderFactory
@@ -15,4 +15,4 @@
# limitations under the License.
#
-org.keycloak.services.messages.AdminMessagesProviderFactory
\ No newline at end of file
+org.keycloak.connections.jpa.updater.liquibase.lock.LiquibaseDBLockProviderFactory
\ No newline at end of file
diff --git a/model/jpa/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/model/jpa/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 16b42ba3ee..94c651287a 100644
--- a/model/jpa/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/model/jpa/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -16,4 +16,5 @@
#
org.keycloak.connections.jpa.JpaConnectionSpi
-org.keycloak.connections.jpa.updater.JpaUpdaterSpi
\ No newline at end of file
+org.keycloak.connections.jpa.updater.JpaUpdaterSpi
+org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionSpi
\ No newline at end of file
diff --git a/model/mongo/pom.xml b/model/mongo/pom.xml
index be0bd9b5a0..db244732ee 100755
--- a/model/mongo/pom.xml
+++ b/model/mongo/pom.xml
@@ -22,7 +22,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
index 744d14e02e..73f4128da2 100755
--- a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
@@ -78,7 +78,13 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class);
- private volatile MongoClient client;
+ private static final int STATE_BEFORE_INIT = 0; // Even before MongoClient is created
+ private static final int STATE_BEFORE_UPDATE = 1; // Mongo client was created, but DB is not yet updated to last version
+ private static final int STATE_AFTER_UPDATE = 2; // Mongo client was created and DB updated. DB is fully initialized now
+
+ private volatile int state = STATE_BEFORE_INIT;
+
+ private MongoClient client;
private MongoStore mongoStore;
private DB db;
@@ -86,15 +92,6 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
private Map operationalInfo;
- @Override
- public MongoConnectionProvider create(KeycloakSession session) {
- lazyInit(session);
-
- TransactionMongoStoreInvocationContext invocationContext = new TransactionMongoStoreInvocationContext(mongoStore);
- session.getTransaction().enlist(new MongoKeycloakTransaction(invocationContext));
- return new DefaultMongoConnectionProvider(db, mongoStore, invocationContext);
- }
-
@Override
public void init(Config.Scope config) {
this.config = config;
@@ -105,33 +102,22 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
}
+ @Override
+ public DB getDBBeforeUpdate() {
+ lazyInitBeforeUpdate();
+ return db;
+ }
- private void lazyInit(KeycloakSession session) {
- if (client == null) {
+ private void lazyInitBeforeUpdate() {
+ if (state == STATE_BEFORE_INIT) {
synchronized (this) {
- if (client == null) {
+ if (state == STATE_BEFORE_INIT) {
try {
this.client = createMongoClient();
-
String dbName = config.get("db", "keycloak");
this.db = client.getDB(dbName);
- String databaseSchema = config.get("databaseSchema");
- if (databaseSchema != null) {
- if (databaseSchema.equals("update")) {
- MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class);
-
- if (mongoUpdater == null) {
- throw new RuntimeException("Can't update database: Mongo updater provider not found");
- }
-
- mongoUpdater.update(session, db);
- } else {
- throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
- }
- }
-
- this.mongoStore = new MongoStoreImpl(db, getManagedEntities());
+ state = STATE_BEFORE_UPDATE;
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -140,6 +126,53 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
}
}
+
+ @Override
+ public MongoConnectionProvider create(KeycloakSession session) {
+ lazyInit(session);
+
+ TransactionMongoStoreInvocationContext invocationContext = new TransactionMongoStoreInvocationContext(mongoStore);
+ session.getTransaction().enlist(new MongoKeycloakTransaction(invocationContext));
+ return new DefaultMongoConnectionProvider(db, mongoStore, invocationContext);
+ }
+
+ private void lazyInit(KeycloakSession session) {
+ lazyInitBeforeUpdate();
+
+ if (state == STATE_BEFORE_UPDATE) {
+ synchronized (this) {
+ if (state == STATE_BEFORE_UPDATE) {
+ try {
+ update(session);
+ this.mongoStore = new MongoStoreImpl(db, getManagedEntities());
+
+ state = STATE_AFTER_UPDATE;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ }
+
+ private void update(KeycloakSession session) {
+ String databaseSchema = config.get("databaseSchema");
+ if (databaseSchema != null) {
+ if (databaseSchema.equals("update")) {
+ MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class);
+
+ if (mongoUpdater == null) {
+ throw new RuntimeException("Can't update database: Mongo updater provider not found");
+ }
+
+ mongoUpdater.update(session, db);
+ } else {
+ throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
+ }
+ }
+ }
+
+
private Class[] getManagedEntities() throws ClassNotFoundException {
Class[] entityClasses = new Class[entities.length];
for (int i = 0; i < entities.length; i++) {
@@ -160,6 +193,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
return "default";
}
+
/**
* Override this method if you want more possibility to configure Mongo client. It can be also used to inject mongo client
* from different source.
@@ -205,7 +239,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
MongoClient client;
if (user != null && password != null) {
- MongoCredential credential = MongoCredential.createMongoCRCredential(user, dbName, password.toCharArray());
+ MongoCredential credential = MongoCredential.createCredential(user, dbName, password.toCharArray());
client = new MongoClient(new ServerAddress(host, port), Collections.singletonList(credential), clientOptions);
} else {
client = new MongoClient(new ServerAddress(host, port), clientOptions);
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProvider.java
index 0e9f84a7a9..f13eaf7a4e 100644
--- a/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProvider.java
@@ -27,6 +27,9 @@ import org.keycloak.provider.Provider;
*/
public interface MongoConnectionProvider extends Provider {
+ /**
+ * @return Fully updated and initialized DB
+ */
DB getDB();
MongoStore getMongoStore();
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java
index 2d5d4ca149..42a18533d9 100644
--- a/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java
@@ -17,10 +17,17 @@
package org.keycloak.connections.mongo;
+import com.mongodb.DB;
import org.keycloak.provider.ProviderFactory;
/**
* @author Stian Thorgersen
*/
public interface MongoConnectionProviderFactory extends ProviderFactory {
+
+ /**
+ * @return DB object, which may not be yet updated to current Keycloak version. Useful just if something needs to be done even before DB update (for example acquire DB lock)
+ */
+ DB getDBBeforeUpdate();
+
}
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProvider.java
new file mode 100644
index 0000000000..e3383fece0
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProvider.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2016 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.connections.mongo.lock;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DB;
+import com.mongodb.DBCursor;
+import com.mongodb.DBObject;
+import com.mongodb.DuplicateKeyException;
+import com.mongodb.WriteResult;
+import org.jboss.logging.Logger;
+import org.keycloak.common.util.HostUtils;
+import org.keycloak.common.util.Time;
+import org.keycloak.models.dblock.DBLockProvider;
+
+/**
+ * @author Marek Posolda
+ */
+public class MongoDBLockProvider implements DBLockProvider {
+
+ private static final String DB_LOCK_COLLECTION = "dblock";
+ private static final Logger logger = Logger.getLogger(MongoDBLockProvider .class);
+
+ private final MongoDBLockProviderFactory factory;
+ private final DB db;
+
+ public MongoDBLockProvider(MongoDBLockProviderFactory factory, DB db) {
+ this.factory = factory;
+ this.db = db;
+ }
+
+
+ @Override
+ public void waitForLock() {
+ boolean locked = false;
+ long startTime = Time.toMillis(Time.currentTime());
+ long timeToGiveUp = startTime + (factory.getLockWaitTimeoutMillis());
+
+ while (!locked && Time.toMillis(Time.currentTime()) < timeToGiveUp) {
+ locked = acquireLock();
+ if (!locked) {
+ int remainingTime = ((int)(timeToGiveUp / 1000)) - Time.currentTime();
+ logger.debugf("Waiting for changelog lock... Remaining time: %d seconds", remainingTime);
+ try {
+ Thread.sleep(factory.getLockRecheckTimeMillis());
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ if (!locked) {
+ DBObject query = new BasicDBObject("_id", 1);
+ DBCursor cursor = db.getCollection(DB_LOCK_COLLECTION).find(query);
+ String lockedBy;
+ if (cursor.hasNext()) {
+ DBObject dbObj = cursor.next();
+ lockedBy = dbObj.get("lockedBy") + " since " + Time.toDate(((int)((long) dbObj.get("lockedSince") / 1000)));
+ } else {
+ lockedBy = "UNKNOWN";
+ }
+ throw new IllegalStateException("Could not acquire change log lock. Currently locked by " + lockedBy);
+ }
+ }
+
+
+ private boolean acquireLock() {
+ DBObject query = new BasicDBObject("locked", false);
+
+ BasicDBObject update = new BasicDBObject("locked", true);
+ update.append("_id", 1);
+ update.append("lockedSince", Time.toMillis(Time.currentTime()));
+ update.append("lockedBy", HostUtils.getHostName()); // Maybe replace with something better, but doesn't matter for now
+
+ try {
+ WriteResult wr = db.getCollection(DB_LOCK_COLLECTION).update(query, update, true, false);
+ if (wr.getN() == 1) {
+ logger.debugf("Successfully acquired DB lock");
+ return true;
+ } else {
+ return false;
+ }
+ } catch (DuplicateKeyException dke) {
+ logger.debugf("Failed acquire lock. Reason: %s", dke.getMessage());
+ }
+
+ return false;
+ }
+
+
+ @Override
+ public void releaseLock() {
+ DBObject query = new BasicDBObject("locked", true);
+
+ BasicDBObject update = new BasicDBObject("locked", false);
+ update.append("_id", 1);
+ update.append("lockedBy", null);
+ update.append("lockedSince", null);
+
+ try {
+ WriteResult wr = db.getCollection(DB_LOCK_COLLECTION).update(query, update, true, false);
+ if (wr.getN() > 0) {
+ logger.debugf("Successfully released DB lock");
+ } else {
+ logger.warnf("Attempt to release DB lock, but nothing was released");
+ }
+ } catch (DuplicateKeyException dke) {
+ logger.debugf("Failed release lock. Reason: %s", dke.getMessage());
+ }
+ }
+
+ @Override
+ public void destroyLockInfo() {
+ db.getCollection(DB_LOCK_COLLECTION).remove(new BasicDBObject());
+ logger.debugf("Destroyed lock collection");
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProviderFactory.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProviderFactory.java
new file mode 100644
index 0000000000..7bd6e02181
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProviderFactory.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2016 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.connections.mongo.lock;
+
+import com.mongodb.DB;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.common.util.Time;
+import org.keycloak.connections.mongo.MongoConnectionProvider;
+import org.keycloak.connections.mongo.MongoConnectionProviderFactory;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.dblock.DBLockProviderFactory;
+
+/**
+ * @author Marek Posolda
+ */
+public class MongoDBLockProviderFactory implements DBLockProviderFactory {
+
+ private static final Logger logger = Logger.getLogger(MongoDBLockProviderFactory.class);
+
+ private long lockRecheckTimeMillis;
+ private long lockWaitTimeoutMillis;
+
+ protected long getLockRecheckTimeMillis() {
+ return lockRecheckTimeMillis;
+ }
+
+ protected long getLockWaitTimeoutMillis() {
+ return lockWaitTimeoutMillis;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ int lockRecheckTime = config.getInt("lockRecheckTime", 2);
+ int lockWaitTimeout = config.getInt("lockWaitTimeout", 900);
+ this.lockRecheckTimeMillis = Time.toMillis(lockRecheckTime);
+ this.lockWaitTimeoutMillis = Time.toMillis(lockWaitTimeout);
+ logger.debugf("Mongo lock provider configured with lockWaitTime: %d seconds, lockRecheckTime: %d seconds", lockWaitTimeout, lockRecheckTime);
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public MongoDBLockProvider create(KeycloakSession session) {
+ MongoConnectionProviderFactory mongoConnectionFactory = (MongoConnectionProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(MongoConnectionProvider.class);
+ DB db = mongoConnectionFactory.getDBBeforeUpdate();
+ return new MongoDBLockProvider(this, db);
+ }
+
+ @Override
+ public void setTimeouts(long lockRecheckTimeMillis, long lockWaitTimeoutMillis) {
+ this.lockRecheckTimeMillis = lockRecheckTimeMillis;
+ this.lockWaitTimeoutMillis = lockWaitTimeoutMillis;
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String getId() {
+ return "mongo";
+ }
+}
diff --git a/model/mongo/src/main/resources/META-INF/services/org.keycloak.models.dblock.DBLockProviderFactory b/model/mongo/src/main/resources/META-INF/services/org.keycloak.models.dblock.DBLockProviderFactory
new file mode 100644
index 0000000000..4c6c4aa4c8
--- /dev/null
+++ b/model/mongo/src/main/resources/META-INF/services/org.keycloak.models.dblock.DBLockProviderFactory
@@ -0,0 +1,35 @@
+#
+# Copyright 2016 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.
+#
+
+#
+# Copyright 2016 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.
+#
+
+org.keycloak.connections.mongo.lock.MongoDBLockProviderFactory
\ No newline at end of file
diff --git a/model/pom.xml b/model/pom.xml
index 3aeb364245..f33f76fe93 100755
--- a/model/pom.xml
+++ b/model/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
Keycloak Model Parent
diff --git a/pom.xml b/pom.xml
index a736a452e9..f10c0b4c57 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,7 +31,7 @@
org.keycloak
keycloak-parent
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
pom
diff --git a/proxy/launcher/pom.xml b/proxy/launcher/pom.xml
index e1876eb9cd..d584353f0d 100755
--- a/proxy/launcher/pom.xml
+++ b/proxy/launcher/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/proxy/pom.xml b/proxy/pom.xml
index cee6f9252d..e27a3bffb8 100755
--- a/proxy/pom.xml
+++ b/proxy/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
Model Parent
diff --git a/proxy/proxy-server/pom.xml b/proxy/proxy-server/pom.xml
index 4d3227f6b4..41967de3e8 100755
--- a/proxy/proxy-server/pom.xml
+++ b/proxy/proxy-server/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/saml-core/pom.xml b/saml-core/pom.xml
index 365c7aa095..4323a13212 100755
--- a/saml-core/pom.xml
+++ b/saml-core/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java b/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
index 612ead9b34..5cb19deb04 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.saml.common.parsers;
+import org.keycloak.common.util.Environment;
import org.keycloak.saml.common.PicketLinkLogger;
import org.keycloak.saml.common.PicketLinkLoggerFactory;
import org.keycloak.saml.common.constants.GeneralConstants;
@@ -29,6 +30,8 @@ import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent;
+import javax.xml.stream.util.EventReaderDelegate;
+
import java.io.InputStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
@@ -83,28 +86,10 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
if (configStream == null)
throw logger.nullArgumentError("InputStream");
- XMLInputFactory xmlInputFactory = getXMLInputFactory();
-
XMLEventReader xmlEventReader = StaxParserUtil.getXMLEventReader(configStream);
try {
- xmlEventReader = xmlInputFactory.createFilteredReader(xmlEventReader, new EventFilter() {
- public boolean accept(XMLEvent xmlEvent) {
- // We are going to disregard characters that are new line and whitespace
- if (xmlEvent.isCharacters()) {
- Characters chars = xmlEvent.asCharacters();
- String data = chars.getData();
- data = valid(data) ? data.trim() : null;
- return valid(data);
- } else {
- return xmlEvent.isStartElement() || xmlEvent.isEndElement();
- }
- }
-
- private boolean valid(String str) {
- return str != null && str.length() > 0;
- }
- });
+ xmlEventReader = filterWhitespaces(xmlEventReader);
} catch (XMLStreamException e) {
throw logger.parserException(e);
}
@@ -137,4 +122,48 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
}
}
+ protected XMLEventReader filterWhitespaces(XMLEventReader xmlEventReader) throws XMLStreamException {
+ XMLInputFactory xmlInputFactory = getXMLInputFactory();
+
+ xmlEventReader = xmlInputFactory.createFilteredReader(xmlEventReader, new EventFilter() {
+ public boolean accept(XMLEvent xmlEvent) {
+ // We are going to disregard characters that are new line and whitespace
+ if (xmlEvent.isCharacters()) {
+ Characters chars = xmlEvent.asCharacters();
+ String data = chars.getData();
+ data = valid(data) ? data.trim() : null;
+ return valid(data);
+ } else {
+ return xmlEvent.isStartElement() || xmlEvent.isEndElement();
+ }
+ }
+
+ private boolean valid(String str) {
+ return str != null && str.length() > 0;
+ }
+
+ });
+
+ // Handle IBM JDK bug with Stax parsing when EventReader presented
+ if (Environment.IS_IBM_JAVA) {
+ final XMLEventReader origReader = xmlEventReader;
+
+ xmlEventReader = new EventReaderDelegate(origReader) {
+
+ @Override
+ public boolean hasNext() {
+ boolean hasNext = super.hasNext();
+ try {
+ return hasNext && (origReader.peek() != null);
+ } catch (XMLStreamException xse) {
+ throw new IllegalStateException(xse);
+ }
+ }
+
+ };
+ }
+
+ return xmlEventReader;
+ }
+
}
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/util/TransformerUtil.java b/saml-core/src/main/java/org/keycloak/saml/common/util/TransformerUtil.java
index c8026fd524..4e43c3d333 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/util/TransformerUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/util/TransformerUtil.java
@@ -27,6 +27,7 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
+import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.util.JAXBSource;
@@ -108,6 +109,21 @@ public class TransformerUtil {
SecurityActions.setTCCL(TransformerUtil.class.getClassLoader());
}
transformerFactory = TransformerFactory.newInstance();
+ try {
+ transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ } catch (TransformerConfigurationException ignored) {
+ // some platforms don't support this. For example our testsuite pulls Selenium which requires Xalan 2.7.1
+ logger.warn("XML External Entity switches are not supported. You may get XML injection vulnerabilities.");
+ }
+ try {
+ transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
+
+ transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
+ } catch (Exception ignored) {
+ // some platforms don't support this. For example our testsuite pulls Selenium which requires Xalan 2.7.1
+ logger.warn("XML External Entity switches are not supported. You may get XML injection vulnerabilities.");
+ }
+
} finally {
if (tccl_jaxp) {
SecurityActions.setTCCL(prevTCCL);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/AbstractDescriptorParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/AbstractDescriptorParser.java
index 6b9db93812..a58006ce66 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/AbstractDescriptorParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/AbstractDescriptorParser.java
@@ -35,28 +35,8 @@ import javax.xml.stream.events.XMLEvent;
public abstract class AbstractDescriptorParser extends AbstractParser {
protected XMLEventReader filterWhiteSpaceCharacters(XMLEventReader xmlEventReader) throws ParsingException {
-
- XMLInputFactory xmlInputFactory = getXMLInputFactory();
-
try {
- xmlEventReader = xmlInputFactory.createFilteredReader(xmlEventReader, new EventFilter() {
- public boolean accept(XMLEvent xmlEvent) {
- // We are going to disregard characters that are new line and whitespace
- if (xmlEvent.isCharacters()) {
- Characters chars = xmlEvent.asCharacters();
- String data = chars.getData();
- data = valid(data) ? data.trim() : null;
- return valid(data);
- } else {
- return xmlEvent.isStartElement() || xmlEvent.isEndElement();
- }
- }
-
- private boolean valid(String str) {
- return str != null && str.length() > 0;
- }
- });
- return xmlEventReader;
+ return filterWhitespaces(xmlEventReader);
} catch (XMLStreamException e) {
throw new ParsingException(e);
}
diff --git a/server-spi/pom.xml b/server-spi/pom.xml
index 69f1c3c506..047a065cd3 100755
--- a/server-spi/pom.xml
+++ b/server-spi/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/server-spi/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java b/server-spi/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java
index 7c1a3dbd65..3a25c5c87d 100644
--- a/server-spi/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java
+++ b/server-spi/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java
@@ -86,7 +86,7 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory,
byte[] key = getSecretKeyFactory().generateSecret(spec).getEncoded();
return Base64.encodeBytes(key);
} catch (InvalidKeySpecException e) {
- throw new RuntimeException("Credential could not be encoded");
+ throw new RuntimeException("Credential could not be encoded", e);
}
}
@@ -101,7 +101,7 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory,
try {
return SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("PBKDF2 algorithm not found");
+ throw new RuntimeException("PBKDF2 algorithm not found", e);
}
}
diff --git a/server-spi/src/main/java/org/keycloak/mappers/FederationConfigValidationException.java b/server-spi/src/main/java/org/keycloak/mappers/FederationConfigValidationException.java
new file mode 100644
index 0000000000..cf58b0bdd3
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/mappers/FederationConfigValidationException.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016 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.mappers;
+
+/**
+ * @author Marek Posolda
+ */
+public class FederationConfigValidationException extends Exception {
+
+ private Object[] parameters;
+
+ public FederationConfigValidationException(String message) {
+ super(message);
+ }
+
+ public FederationConfigValidationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public FederationConfigValidationException(String message, Object ... parameters) {
+ super(message);
+ this.parameters = parameters;
+ }
+
+ public Object[] getParameters() {
+ return parameters;
+ }
+
+ public void setParameters(Object[] parameters) {
+ this.parameters = parameters;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
index 80eec6a597..661462cc1f 100644
--- a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
+++ b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
@@ -52,10 +52,12 @@ public interface UserFederationMapperFactory extends ProviderFactoryBill Burke
- * @version $Revision: 1 $
- */
-public interface UserCache {
+public class MigrateTo1_9_2 {
- void clear();
+ public static final ModelVersion VERSION = new ModelVersion("1.9.2");
- CachedUser getCachedUser(String realmId, String id);
-
- void addCachedUser(String realmId, CachedUser user);
-
- CachedUser getCachedUserByUsername(String realmId, String name);
-
- CachedUser getCachedUserByEmail(String realmId, String name);
-
- void invalidateCachedUserById(String realmId, String id);
-
- void invalidateRealmUsers(String realmId);
+ public void migrate(KeycloakSession session) {
+ for (RealmModel realm : session.realms().getRealms()) {
+ if (realm.getBrowserSecurityHeaders() != null) {
+ realm.getBrowserSecurityHeaders().put("xFrameOptions", "nosniff");
+ }
+ }
+ }
}
diff --git a/server-spi/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java b/server-spi/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java
index 4abc494eae..10560271b0 100755
--- a/server-spi/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java
+++ b/server-spi/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java
@@ -30,13 +30,15 @@ public class BrowserSecurityHeaders {
public static final Map defaultHeaders;
static {
- Map headerMap = new HashMap();
+ Map headerMap = new HashMap<>();
headerMap.put("xFrameOptions", "X-Frame-Options");
headerMap.put("contentSecurityPolicy", "Content-Security-Policy");
+ headerMap.put("xContentTypeOptions", "X-Content-Type-Options");
- Map dh = new HashMap();
+ Map dh = new HashMap<>();
dh.put("xFrameOptions", "SAMEORIGIN");
dh.put("contentSecurityPolicy", "frame-src 'self'");
+ dh.put("xContentTypeOptions", "nosniff");
defaultHeaders = Collections.unmodifiableMap(dh);
headerAttributeMap = Collections.unmodifiableMap(headerMap);
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
index 45077e236a..ef8d182534 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -55,9 +55,7 @@ public class UserFederationManager implements UserProvider {
}
protected UserFederationProvider getFederationProvider(UserFederationProviderModel model) {
- UserFederationProviderFactory factory = (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, model.getProviderName());
- return factory.getInstance(session, model);
-
+ return KeycloakModelUtils.getFederationProviderInstance(session, model);
}
@Override
@@ -98,7 +96,7 @@ public class UserFederationManager implements UserProvider {
boolean localRemoved = session.userStorage().removeUser(realm, user);
managedUsers.remove(user.getId());
if (!localRemoved) {
- logger.warn("User removed from federation provider, but failed to remove him from keycloak model");
+ logger.warn("User possibly removed from federation provider, but failed to remove him from keycloak model");
}
return localRemoved;
} else {
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationSyncResult.java b/server-spi/src/main/java/org/keycloak/models/UserFederationSyncResult.java
index a9a32f9216..380c654659 100644
--- a/server-spi/src/main/java/org/keycloak/models/UserFederationSyncResult.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationSyncResult.java
@@ -96,7 +96,10 @@ public class UserFederationSyncResult {
if (ignored) {
return "Synchronization ignored as it's already in progress";
} else {
- String status = String.format("%d imported users, %d updated users, %d removed users", added, updated, removed);
+ String status = String.format("%d imported users, %d updated users", added, updated);
+ if (removed > 0) {
+ status += String.format(", %d removed users", removed);
+ }
if (failed != 0) {
status += String.format(", %d users failed sync! See server log for more details", failed);
}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationValidatingProviderFactory.java b/server-spi/src/main/java/org/keycloak/models/UserFederationValidatingProviderFactory.java
new file mode 100644
index 0000000000..9624690236
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationValidatingProviderFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016 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.models;
+
+import org.keycloak.mappers.FederationConfigValidationException;
+
+/**
+ * TODO: Merge with UserFederationProviderFactory and add "default" method validateConfig with empty body once we move to source level 1.8
+ *
+ * @author Marek Posolda
+ */
+public interface UserFederationValidatingProviderFactory extends UserFederationProviderFactory {
+
+ /**
+ * Called when instance of mapperModel is created for this factory through admin endpoint
+ *
+ * @param realm
+ * @param providerModel
+ * @throws FederationConfigValidationException if configuration provided in mapperModel is not valid
+ */
+ void validateConfig(RealmModel realm, UserFederationProviderModel providerModel) throws FederationConfigValidationException;
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java b/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java
index d9c9e566e1..63c97061d6 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java
@@ -27,5 +27,4 @@ import org.keycloak.models.UserProvider;
public interface CacheUserProvider extends UserProvider {
void clear();
UserProvider getDelegate();
- void registerUserInvalidation(RealmModel realm, String id);
}
diff --git a/server-spi/src/main/java/org/keycloak/models/dblock/DBLockProvider.java b/server-spi/src/main/java/org/keycloak/models/dblock/DBLockProvider.java
new file mode 100644
index 0000000000..b5fb417aca
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/dblock/DBLockProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016 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.models.dblock;
+
+import org.keycloak.provider.Provider;
+
+/**
+ * Global database lock to ensure that some actions in DB can be done just be one cluster node at a time.
+ *
+ *
+ * @author Marek Posolda
+ */
+public interface DBLockProvider extends Provider {
+
+
+ /**
+ * Try to retrieve DB lock or wait if retrieve was unsuccessful. Throw exception if lock can't be retrieved within specified timeout (900 seconds by default)
+ */
+ void waitForLock();
+
+
+ void releaseLock();
+
+
+ /**
+ * Will destroy whole state of DB lock (drop table/collection to track locking).
+ * */
+ void destroyLockInfo();
+}
diff --git a/server-spi/src/main/java/org/keycloak/mappers/MapperConfigValidationException.java b/server-spi/src/main/java/org/keycloak/models/dblock/DBLockProviderFactory.java
similarity index 70%
rename from server-spi/src/main/java/org/keycloak/mappers/MapperConfigValidationException.java
rename to server-spi/src/main/java/org/keycloak/models/dblock/DBLockProviderFactory.java
index cc4ebd921a..747a168126 100644
--- a/server-spi/src/main/java/org/keycloak/mappers/MapperConfigValidationException.java
+++ b/server-spi/src/main/java/org/keycloak/models/dblock/DBLockProviderFactory.java
@@ -15,18 +15,17 @@
* limitations under the License.
*/
-package org.keycloak.mappers;
+package org.keycloak.models.dblock;
+
+import org.keycloak.provider.ProviderFactory;
/**
* @author Marek Posolda
*/
-public class MapperConfigValidationException extends Exception {
+public interface DBLockProviderFactory extends ProviderFactory {
- public MapperConfigValidationException(String message) {
- super(message);
- }
-
- public MapperConfigValidationException(String message, Throwable cause) {
- super(message, cause);
- }
+ /**
+ * Useful for testing to override provided configuration
+ */
+ void setTimeouts(long lockRecheckTimeMillis, long lockWaitTimeoutMillis);
}
diff --git a/services/src/main/java/org/keycloak/messages/MessagesSpi.java b/server-spi/src/main/java/org/keycloak/models/dblock/DBLockSpi.java
similarity index 80%
rename from services/src/main/java/org/keycloak/messages/MessagesSpi.java
rename to server-spi/src/main/java/org/keycloak/models/dblock/DBLockSpi.java
index f9d58ff5e4..cd78f0f0e7 100644
--- a/services/src/main/java/org/keycloak/messages/MessagesSpi.java
+++ b/server-spi/src/main/java/org/keycloak/models/dblock/DBLockSpi.java
@@ -15,16 +15,16 @@
* limitations under the License.
*/
-package org.keycloak.messages;
+package org.keycloak.models.dblock;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
- * @author Leonardo Zanivan
+ * @author Marek Posolda
*/
-public class MessagesSpi implements Spi {
+public class DBLockSpi implements Spi {
@Override
public boolean isInternal() {
@@ -33,17 +33,16 @@ public class MessagesSpi implements Spi {
@Override
public String getName() {
- return "messages";
+ return "dblock";
}
@Override
public Class extends Provider> getProviderClass() {
- return MessagesProvider.class;
+ return DBLockProvider.class;
}
@Override
public Class extends ProviderFactory> getProviderFactoryClass() {
- return MessagesProviderFactory.class;
+ return DBLockProviderFactory.class;
}
-
}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
index 654c7644d3..074949f4ad 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
@@ -71,7 +71,7 @@ public class CredentialValidation {
public static boolean validateHashedCredential(KeycloakSession session, RealmModel realm, UserModel user, String unhashedCredValue, UserCredentialValueModel credential) {
- if(unhashedCredValue == null){
+ if (unhashedCredValue == null || unhashedCredValue.isEmpty()) {
return false;
}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index ff5c38e1d6..f75bf11027 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -35,6 +35,8 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.ScopeContainerModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.CertificateRepresentation;
@@ -252,18 +254,21 @@ public final class KeycloakModelUtils {
}
/**
- * Try to find user by given username. If it fails, then fallback to find him by email
+ * Try to find user by username or email
*
* @param realm realm
* @param username username or email of user
* @return found user
*/
public static UserModel findUserByNameOrEmail(KeycloakSession session, RealmModel realm, String username) {
- UserModel user = session.users().getUserByUsername(username, realm);
- if (user == null && username.contains("@")) {
- user = session.users().getUserByEmail(username, realm);
+ if (username.indexOf('@') != -1) {
+ UserModel user = session.users().getUserByEmail(username, realm);
+ if (user != null) {
+ return user;
+ }
}
- return user;
+
+ return session.users().getUserByUsername(username, realm);
}
/**
@@ -406,6 +411,16 @@ public final class KeycloakModelUtils {
return mapperModel;
}
+ public static UserFederationProviderFactory getFederationProviderFactory(KeycloakSession session, UserFederationProviderModel model) {
+ return (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, model.getProviderName());
+ }
+
+ public static UserFederationProvider getFederationProviderInstance(KeycloakSession session, UserFederationProviderModel model) {
+ UserFederationProviderFactory factory = getFederationProviderFactory(session, model);
+ return factory.getInstance(session, model);
+
+ }
+
// END USER FEDERATION RELATED STUFF
public static String toLowerCaseSafe(String str) {
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index d349f4e02e..970fd7eede 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -695,6 +695,16 @@ public class RepresentationToModel {
if ("GENERATE".equals(rep.getPublicKey())) {
KeycloakModelUtils.generateRealmKeys(realm);
+ } else {
+ if (rep.getPrivateKey() != null && rep.getPublicKey() != null) {
+ realm.setPrivateKeyPem(rep.getPrivateKey());
+ realm.setPublicKeyPem(rep.getPublicKey());
+ realm.setCodeSecret(KeycloakModelUtils.generateCodeSecret());
+ }
+
+ if (rep.getCertificate() != null) {
+ realm.setCertificatePem(rep.getCertificate());
+ }
}
if(rep.isInternationalizationEnabled() != null){
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 7bb58fc556..ffbb0ac35a 100755
--- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -21,6 +21,7 @@ org.keycloak.models.RealmSpi
org.keycloak.models.UserSessionSpi
org.keycloak.models.UserSpi
org.keycloak.models.session.UserSessionPersisterSpi
+org.keycloak.models.dblock.DBLockSpi
org.keycloak.migration.MigrationSpi
org.keycloak.hash.PasswordHashSpi
org.keycloak.events.EventListenerSpi
diff --git a/services/pom.xml b/services/pom.xml
index ff4377adac..2c98d39b82 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
4.0.0
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index fd6cd5a4d3..68c0620fb3 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -569,7 +569,7 @@ public class AuthenticationProcessor {
} else if (e.getError() == AuthenticationFlowError.USER_TEMPORARILY_DISABLED) {
logger.failedAuthentication(e);
event.error(Errors.USER_TEMPORARILY_DISABLED);
- return ErrorPage.error(session, Messages.ACCOUNT_TEMPORARILY_DISABLED);
+ return ErrorPage.error(session, Messages.INVALID_USER);
} else if (e.getError() == AuthenticationFlowError.INVALID_CLIENT_SESSION) {
logger.failedAuthentication(e);
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
index e16d759d87..6971ab5dd9 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
@@ -88,7 +88,7 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator
federatedUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
}
- // TODO: Event
+ userRegisteredSuccess(context, federatedUser, serializedCtx, brokerContext);
context.setUser(federatedUser);
context.getClientSession().setNote(BROKER_REGISTERED_NEW_USER, "true");
@@ -140,6 +140,12 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator
}
+ // Empty method by default. This exists, so subclass can override and add callback after new user is registered through social
+ protected void userRegisteredSuccess(AuthenticationFlowContext context, UserModel registeredUser, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+
+ }
+
+
@Override
public boolean requiresUser() {
return false;
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
index 77d004d064..137e37086a 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
@@ -65,7 +65,7 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
protected Response temporarilyDisabledUser(AuthenticationFlowContext context) {
return context.form()
- .setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).createLogin();
+ .setError(Messages.INVALID_USER).createLogin();
}
protected Response invalidCredentials(AuthenticationFlowContext context) {
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index 11eba29b99..32d64260cf 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -425,7 +425,6 @@ public class SAMLEndpoint {
@Override
protected SAMLDocumentHolder extractResponseDocument(String response) {
byte[] samlBytes = PostBindingUtil.base64Decode(response);
- String xml = new String(samlBytes);
return SAMLRequestParser.parseResponseDocument(samlBytes);
}
diff --git a/services/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java b/services/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java
index dd2ee07565..329b7297c3 100755
--- a/services/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java
+++ b/services/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java
@@ -35,6 +35,7 @@ import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author Marek Posolda
@@ -118,26 +119,29 @@ public class DirImportProvider implements ImportProvider {
// Import realm first
FileInputStream is = new FileInputStream(realmFile);
final RealmRepresentation realmRep = JsonSerialization.readValue(is, RealmRepresentation.class);
+ final AtomicBoolean realmImported = new AtomicBoolean();
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
public void runExportImportTask(KeycloakSession session) throws IOException {
- ImportUtils.importRealm(session, realmRep, strategy);
+ boolean imported = ImportUtils.importRealm(session, realmRep, strategy);
+ realmImported.set(imported);
}
});
- // Import users
- for (File userFile : userFiles) {
- final FileInputStream fis = new FileInputStream(userFile);
- KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
-
- @Override
- protected void runExportImportTask(KeycloakSession session) throws IOException {
- ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, fis);
- }
- });
+ if (realmImported.get()) {
+ // Import users
+ for (File userFile : userFiles) {
+ final FileInputStream fis = new FileInputStream(userFile);
+ KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
+ @Override
+ protected void runExportImportTask(KeycloakSession session) throws IOException {
+ ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, fis);
+ }
+ });
+ }
}
}
diff --git a/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
index 06f6e54998..2ee2e11b3f 100755
--- a/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
+++ b/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
@@ -68,7 +68,7 @@ public class ImportUtils {
* @param strategy specifies whether to overwrite or ignore existing realm or user entries
* @return newly imported realm (or existing realm if ignoreExisting is true and realm of this name already exists)
*/
- public static void importRealm(KeycloakSession session, RealmRepresentation rep, Strategy strategy) {
+ public static boolean importRealm(KeycloakSession session, RealmRepresentation rep, Strategy strategy) {
String realmName = rep.getRealm();
RealmProvider model = session.realms();
RealmModel realm = model.getRealmByName(realmName);
@@ -76,7 +76,7 @@ public class ImportUtils {
if (realm != null) {
if (strategy == Strategy.IGNORE_EXISTING) {
logger.infof("Realm '%s' already exists. Import skipped", realmName);
- return;
+ return false;
} else {
logger.infof("Realm '%s' already exists. Removing it before import", realmName);
if (Config.getAdminRealm().equals(realm.getId())) {
@@ -91,13 +91,13 @@ public class ImportUtils {
}
RealmImporter realmManager = session.getContext().getRealmManager();
- realm = realmManager.importRealm(rep);
+ realmManager.importRealm(rep);
if (System.getProperty(ExportImportConfig.ACTION) != null) {
logger.infof("Realm '%s' imported", realmName);
}
- return;
+ return true;
}
/**
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java b/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
index fd701245af..893e816592 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
@@ -27,7 +27,6 @@ import java.util.Map;
import java.util.Properties;
import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
@@ -64,6 +63,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.FormMessage;
+import org.keycloak.utils.MediaType;
/**
* @author Stian Thorgersen
@@ -209,7 +209,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
try {
String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
- Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
+ Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML_UTF_8_TYPE).language(locale).entity(result);
BrowserSecurityHeaderSetup.headers(builder, realm);
return builder.build();
} catch (FreeMarkerException e) {
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java
index ec02ebafa3..a20810e8a0 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java
@@ -79,7 +79,7 @@ public class AccountFederatedIdentityBean {
this.identities = new LinkedList(orderedSet);
// Removing last social provider is not possible if you don't have other possibility to authenticate
- this.removeLinkPossible = availableIdentities > 1 || user.getFederationLink() != null || AccountService.isPasswordSet(user);
+ this.removeLinkPossible = availableIdentities > 1 || user.getFederationLink() != null || AccountService.isPasswordSet(session, realm, user);
}
private FederatedIdentityModel getIdentity(Set identities, String providerId) {
diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
index bb2e518d9c..1a81870ee0 100755
--- a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -61,8 +61,8 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.services.Urls;
import org.keycloak.services.messages.Messages;
+import org.keycloak.utils.MediaType;
-import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
@@ -312,7 +312,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
try {
String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
- Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
+ Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML_UTF_8).entity(result);
BrowserSecurityHeaderSetup.headers(builder, realm);
for (Map.Entry entry : httpResponseHeaders.entrySet()) {
builder.header(entry.getKey(), entry.getValue());
@@ -413,7 +413,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
}
try {
String result = freeMarker.processTemplate(attributes, form, theme);
- Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
+ Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML_UTF_8_TYPE).language(locale).entity(result);
BrowserSecurityHeaderSetup.headers(builder, realm);
for (Map.Entry entry : httpResponseHeaders.entrySet()) {
builder.header(entry.getKey(), entry.getValue());
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index e49665cabc..1ff3da9d01 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -222,6 +222,22 @@ public class TokenEndpoint {
accessCode.setAction(null);
UserSessionModel userSession = clientSession.getUserSession();
+
+ if (userSession == null) {
+ event.error(Errors.USER_SESSION_NOT_FOUND);
+ throw new ErrorResponseException("invalid_grant", "User session not found", Response.Status.BAD_REQUEST);
+ }
+
+ UserModel user = userSession.getUser();
+ if (user == null) {
+ event.error(Errors.USER_NOT_FOUND);
+ throw new ErrorResponseException("invalid_grant", "User not found", Response.Status.BAD_REQUEST);
+ }
+ if (!user.isEnabled()) {
+ event.error(Errors.USER_DISABLED);
+ throw new ErrorResponseException("invalid_grant", "User disabled", Response.Status.BAD_REQUEST);
+ }
+
event.user(userSession.getUser());
event.session(userSession.getId());
@@ -241,17 +257,6 @@ public class TokenEndpoint {
throw new ErrorResponseException("invalid_grant", "Client not allowed to exchange code", Response.Status.BAD_REQUEST);
}
- UserModel user = session.users().getUserById(userSession.getUser().getId(), realm);
- if (user == null) {
- event.error(Errors.USER_NOT_FOUND);
- throw new ErrorResponseException("invalid_grant", "User not found", Response.Status.BAD_REQUEST);
- }
-
- if (!user.isEnabled()) {
- event.error(Errors.USER_DISABLED);
- throw new ErrorResponseException("invalid_grant", "User disabled", Response.Status.BAD_REQUEST);
- }
-
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
event.error(Errors.USER_SESSION_NOT_FOUND);
throw new ErrorResponseException("invalid_grant", "Session not active", Response.Status.BAD_REQUEST);
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
index fca6a9ed9d..53f92f4fb8 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
@@ -18,7 +18,6 @@ package org.keycloak.services;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.KeycloakTransactionManager;
-import org.keycloak.services.ServicesLogger;
import java.util.LinkedList;
import java.util.List;
diff --git a/services/src/main/java/org/keycloak/services/ServicesLogger.java b/services/src/main/java/org/keycloak/services/ServicesLogger.java
index 0927f38592..8af67e5d7e 100644
--- a/services/src/main/java/org/keycloak/services/ServicesLogger.java
+++ b/services/src/main/java/org/keycloak/services/ServicesLogger.java
@@ -401,4 +401,8 @@ public interface ServicesLogger extends BasicLogger {
@LogMessage(level = ERROR)
@Message(id=90, value="Failed to close ProviderSession")
void failedToCloseProviderSession(@Cause Throwable t);
+
+ @LogMessage(level = WARN)
+ @Message(id=91, value="Forced release of DB lock at startup requested by System property. Make sure to not use this in production environment! And especially when more cluster nodes are started concurrently.")
+ void forcedReleaseDBLock();
}
diff --git a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java
index 530447ea87..d6a3f13dc9 100755
--- a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java
+++ b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java
@@ -77,6 +77,12 @@ public class KeycloakSessionServletFilter implements Filter {
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
+ // KeycloakTransactionCommitter is responsible for committing the transaction, but if an exception is thrown it's not invoked and transaction
+ // should be rolled back
+ if (session.getTransaction() != null && session.getTransaction().isActive()) {
+ session.getTransaction().rollback();
+ }
+
session.close();
ResteasyProviderFactory.clearContextData();
}
diff --git a/services/src/main/java/org/keycloak/services/managers/DBLockManager.java b/services/src/main/java/org/keycloak/services/managers/DBLockManager.java
new file mode 100644
index 0000000000..1909c36f41
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/managers/DBLockManager.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2016 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.services.managers;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.RealmProviderFactory;
+import org.keycloak.models.dblock.DBLockProvider;
+import org.keycloak.models.dblock.DBLockProviderFactory;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.services.ServicesLogger;
+
+/**
+ * @author Marek Posolda
+ */
+public class DBLockManager {
+
+ protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+
+ public void waitForLock(KeycloakSessionFactory sessionFactory) {
+ KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ DBLockProvider lock = getDBLock(session);
+ lock.waitForLock();
+ }
+
+ });
+ }
+
+
+ public void releaseLock(KeycloakSessionFactory sessionFactory) {
+ KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ DBLockProvider lock = getDBLock(session);
+ lock.releaseLock();
+ }
+
+ });
+ }
+
+
+ public void checkForcedUnlock(KeycloakSessionFactory sessionFactory) {
+ if (Boolean.getBoolean("keycloak.dblock.forceUnlock")) {
+ logger.forcedReleaseDBLock();
+ releaseLock(sessionFactory);
+ }
+ }
+
+
+ // Try to detect ID from realmProvider
+ public DBLockProvider getDBLock(KeycloakSession session) {
+ String realmProviderId = getRealmProviderId(session);
+ return session.getProvider(DBLockProvider.class, realmProviderId);
+ }
+
+ public DBLockProviderFactory getDBLockFactory(KeycloakSession session) {
+ String realmProviderId = getRealmProviderId(session);
+ return (DBLockProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(DBLockProvider.class, realmProviderId);
+ }
+
+ private String getRealmProviderId(KeycloakSession session) {
+ RealmProviderFactory realmProviderFactory = (RealmProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(RealmProvider.class);
+ return realmProviderFactory.getId();
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/messages/AdminMessagesProvider.java b/services/src/main/java/org/keycloak/services/messages/AdminMessagesProvider.java
deleted file mode 100644
index a37c850858..0000000000
--- a/services/src/main/java/org/keycloak/services/messages/AdminMessagesProvider.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2016 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.services.messages;
-
-import java.io.IOException;
-import java.net.URL;
-import java.text.MessageFormat;
-import java.util.Locale;
-import java.util.Properties;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.messages.MessagesProvider;
-import org.keycloak.services.ServicesLogger;
-
-/**
- * @author Leonardo Zanivan
- */
-public class AdminMessagesProvider implements MessagesProvider {
-
- private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
-
- private KeycloakSession session;
- private Locale locale;
- private Properties messagesBundle;
-
- public AdminMessagesProvider(KeycloakSession session, Locale locale) {
- this.session = session;
- this.locale = locale;
- this.messagesBundle = getMessagesBundle(locale);
- }
-
- @Override
- public String getMessage(String messageKey, Object... parameters) {
- String message = messagesBundle.getProperty(messageKey, messageKey);
-
- try {
- return new MessageFormat(message, locale).format(parameters);
- } catch (Exception e) {
- logger.failedToFormatMessage(e.getMessage());
- return message;
- }
- }
-
- @Override
- public void close() {
- }
-
- private Properties getMessagesBundle(Locale locale) {
- Properties properties = new Properties();
-
- if (locale == null) {
- return properties;
- }
-
- URL url = getClass().getClassLoader().getResource(
- "theme/base/admin/messages/messages_" + locale.toString() + ".properties");
- if (url != null) {
- try {
- properties.load(url.openStream());
- } catch (IOException ex) {
- logger.failedToloadMessages(ex);
- }
- }
-
- return properties;
- }
-
-}
diff --git a/services/src/main/java/org/keycloak/services/messages/AdminMessagesProviderFactory.java b/services/src/main/java/org/keycloak/services/messages/AdminMessagesProviderFactory.java
deleted file mode 100644
index 205d437b8f..0000000000
--- a/services/src/main/java/org/keycloak/services/messages/AdminMessagesProviderFactory.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2016 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.services.messages;
-
-import java.util.Locale;
-import org.keycloak.Config;
-import org.keycloak.messages.MessagesProvider;
-import org.keycloak.messages.MessagesProviderFactory;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakSessionFactory;
-
-/**
- * @author Leonardo Zanivan
- */
-public class AdminMessagesProviderFactory implements MessagesProviderFactory {
-
- @Override
- public MessagesProvider create(KeycloakSession session) {
- return new AdminMessagesProvider(session, Locale.ENGLISH);
- }
-
- @Override
- public void init(Config.Scope config) {
- }
-
- @Override
- public void postInit(KeycloakSessionFactory factory) {
- }
-
- @Override
- public void close() {
- }
-
- @Override
- public String getId() {
- return "admin";
- }
-
-}
diff --git a/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java b/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
index 45df9825b0..547424cfc4 100755
--- a/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
@@ -177,6 +177,8 @@ public abstract class AbstractSecuredLocalService {
oauth.setClientId(client.getClientId());
+ oauth.setSecure(realm.getSslRequired().isRequired(clientConnection));
+
UriBuilder uriBuilder = UriBuilder.fromUri(getBaseRedirectUri()).path("login-redirect");
if (path != null) {
@@ -247,8 +249,7 @@ public abstract class AbstractSecuredLocalService {
URI url = uriBuilder.build();
- // todo httpOnly!
- NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure);
+ NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure, true);
logger.debug("NewCookie: " + cookie.toString());
logger.debug("Oauth Redirect to: " + url);
return Response.status(302)
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 653e266614..4907707d90 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -30,16 +30,20 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.CredentialValidation;
import org.keycloak.models.utils.FormMessage;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
@@ -298,7 +302,7 @@ public class AccountService extends AbstractSecuredLocalService {
@GET
public Response passwordPage() {
if (auth != null) {
- account.setPasswordSet(isPasswordSet(auth.getUser()));
+ account.setPasswordSet(isPasswordSet(session, realm, auth.getUser()));
}
return forwardToPage("password", AccountPages.PASSWORD);
@@ -601,7 +605,7 @@ public class AccountService extends AbstractSecuredLocalService {
csrfCheck(formData);
UserModel user = auth.getUser();
- boolean requireCurrent = isPasswordSet(user);
+ boolean requireCurrent = isPasswordSet(session, realm, user);
account.setPasswordSet(requireCurrent);
String password = formData.getFirst("password");
@@ -621,7 +625,7 @@ public class AccountService extends AbstractSecuredLocalService {
}
}
- if (Validation.isEmpty(passwordNew)) {
+ if (Validation.isBlank(passwordNew)) {
setReferrerOnPage();
return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
}
@@ -723,7 +727,7 @@ public class AccountService extends AbstractSecuredLocalService {
if (link != null) {
// Removing last social provider is not possible if you don't have other possibility to authenticate
- if (session.users().getFederatedIdentities(user, realm).size() > 1 || user.getFederationLink() != null || isPasswordSet(user)) {
+ if (session.users().getFederatedIdentities(user, realm).size() > 1 || user.getFederationLink() != null || isPasswordSet(session, realm, user)) {
session.users().removeFederatedIdentity(realm, user, providerId);
logger.debugv("Social provider {0} removed successfully from user {1}", providerId, user.getUsername());
@@ -758,11 +762,25 @@ public class AccountService extends AbstractSecuredLocalService {
return Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName());
}
- public static boolean isPasswordSet(UserModel user) {
+ public static boolean isPasswordSet(KeycloakSession session, RealmModel realm, UserModel user) {
boolean passwordSet = false;
+ // See if password is set for user on linked UserFederationProvider
if (user.getFederationLink() != null) {
- passwordSet = true;
+
+ UserFederationProvider federationProvider = null;
+ for (UserFederationProviderModel fedProviderModel : realm.getUserFederationProviders()) {
+ if (fedProviderModel.getId().equals(user.getFederationLink())) {
+ federationProvider = KeycloakModelUtils.getFederationProviderInstance(session, fedProviderModel);
+ }
+ }
+
+ if (federationProvider != null) {
+ Set supportedCreds = federationProvider.getSupportedCredentialTypes(user);
+ if (supportedCreds.contains(UserCredentialModel.PASSWORD)) {
+ passwordSet = true;
+ }
+ }
}
for (UserCredentialValueModel c : user.getCredentialsDirectly()) {
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 0ef806bfe7..4de67ed3ad 100644
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -25,6 +25,7 @@ import org.keycloak.Config;
import org.keycloak.exportimport.ExportImportManager;
import org.keycloak.migration.MigrationModelManager;
import org.keycloak.models.*;
+import org.keycloak.services.managers.DBLockManager;
import org.keycloak.models.utils.PostMigrationEvent;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.RealmRepresentation;
@@ -39,7 +40,6 @@ import org.keycloak.services.resources.admin.AdminRoot;
import org.keycloak.services.scheduled.ClearExpiredEvents;
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
import org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner;
-import org.keycloak.services.scheduled.ScheduledTaskRunner;
import org.keycloak.services.util.JsonConfigProvider;
import org.keycloak.services.util.ObjectMapperResolver;
import org.keycloak.timer.TimerProvider;
@@ -82,7 +82,6 @@ public class KeycloakApplication extends Application {
singletons.add(new ServerVersionResource());
singletons.add(new RealmsResource());
singletons.add(new AdminRoot());
- singletons.add(new ModelExceptionMapper());
classes.add(QRCodeResource.class);
classes.add(ThemeResource.class);
classes.add(JsResource.class);
@@ -91,44 +90,56 @@ public class KeycloakApplication extends Application {
singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false"))));
- migrateModel();
-
- boolean bootstrapAdminUser = false;
-
- KeycloakSession session = sessionFactory.create();
ExportImportManager exportImportManager;
+
+ DBLockManager dbLockManager = new DBLockManager();
+ dbLockManager.checkForcedUnlock(sessionFactory);
+ dbLockManager.waitForLock(sessionFactory);
try {
- session.getTransaction().begin();
+ migrateModel();
- ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
- exportImportManager = new ExportImportManager(session);
+ KeycloakSession session = sessionFactory.create();
+ try {
+ session.getTransaction().begin();
- boolean createMasterRealm = applianceBootstrap.isNewInstall();
- if (exportImportManager.isRunImport() && exportImportManager.isImportMasterIncluded()) {
- createMasterRealm = false;
+ ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
+ exportImportManager = new ExportImportManager(session);
+
+ boolean createMasterRealm = applianceBootstrap.isNewInstall();
+ if (exportImportManager.isRunImport() && exportImportManager.isImportMasterIncluded()) {
+ createMasterRealm = false;
+ }
+
+ if (createMasterRealm) {
+ applianceBootstrap.createMasterRealm(contextPath);
+ }
+ session.getTransaction().commit();
+ } catch (RuntimeException re) {
+ if (session.getTransaction().isActive()) {
+ session.getTransaction().rollback();
+ }
+ throw re;
+ } finally {
+ session.close();
}
- if (createMasterRealm) {
- applianceBootstrap.createMasterRealm(contextPath);
+ if (exportImportManager.isRunImport()) {
+ exportImportManager.runImport();
+ } else {
+ importRealms();
}
- session.getTransaction().commit();
+
+ importAddUser();
} finally {
- session.close();
+ dbLockManager.releaseLock(sessionFactory);
}
- if (exportImportManager.isRunImport()) {
- exportImportManager.runImport();
- } else {
- importRealms();
- }
-
- importAddUser();
-
if (exportImportManager.isRunExport()) {
exportImportManager.runExport();
}
- session = sessionFactory.create();
+ boolean bootstrapAdminUser = false;
+ KeycloakSession session = sessionFactory.create();
try {
session.getTransaction().begin();
bootstrapAdminUser = new ApplianceBootstrap(session).isNoMasterUser();
@@ -154,6 +165,7 @@ public class KeycloakApplication extends Application {
} catch (Exception e) {
session.getTransaction().rollback();
logger.migrationFailure(e);
+ throw e;
} finally {
session.close();
}
diff --git a/services/src/main/java/org/keycloak/services/resources/ModelExceptionMapper.java b/services/src/main/java/org/keycloak/services/resources/ModelExceptionMapper.java
deleted file mode 100644
index e71fa670d2..0000000000
--- a/services/src/main/java/org/keycloak/services/resources/ModelExceptionMapper.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2016 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.services.resources;
-
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.ext.ExceptionMapper;
-import javax.ws.rs.ext.Provider;
-
-import org.keycloak.messages.MessagesProvider;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelException;
-import org.keycloak.services.ErrorResponse;
-import org.keycloak.services.ServicesLogger;
-
-/**
- * @author Leonardo Zanivan
- */
-@Provider
-public class ModelExceptionMapper implements ExceptionMapper {
-
- private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
-
- @Context
- private KeycloakSession session;
-
- @Override
- public Response toResponse(ModelException ex) {
- String message = session.getProvider(MessagesProvider.class, "admin")
- .getMessage(ex.getMessage(), ex.getParameters());
-
- logger.error(message, ex);
- return ErrorResponse.error(message, Response.Status.BAD_REQUEST);
- }
-}
diff --git a/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java b/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java
index e2ee4025f1..23e9baa636 100755
--- a/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java
@@ -17,17 +17,27 @@
package org.keycloak.services.resources;
import org.keycloak.Config;
-import org.keycloak.theme.FreeMarkerUtil;
-import org.keycloak.theme.Theme;
-import org.keycloak.theme.ThemeProvider;
-import org.keycloak.models.KeycloakSession;
import org.keycloak.common.util.MimeTypeUtil;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.util.CacheControlUtil;
+import org.keycloak.theme.FreeMarkerUtil;
+import org.keycloak.theme.Theme;
+import org.keycloak.theme.ThemeProvider;
+import org.keycloak.utils.MediaType;
-import javax.ws.rs.*;
-import javax.ws.rs.core.*;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
@@ -64,7 +74,7 @@ public class WelcomeResource {
* @throws URISyntaxException
*/
@GET
- @Produces("text/html")
+ @Produces(MediaType.TEXT_HTML_UTF_8)
public Response getWelcomePage() throws URISyntaxException {
checkBootstrap();
@@ -127,7 +137,7 @@ public class WelcomeResource {
*/
@GET
@Path("/welcome-content/{path}")
- @Produces("text/html")
+ @Produces(MediaType.TEXT_HTML_UTF_8)
public Response getResource(@PathParam("path") String path) {
try {
InputStream resource = getTheme().getResourceAsStream(path);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java
index 044c7d506c..9ed474b5c7 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java
@@ -27,7 +27,6 @@ import org.keycloak.theme.BrowserSecurityHeaderSetup;
import org.keycloak.theme.FreeMarkerException;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme;
-import org.keycloak.theme.ThemeProvider;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
@@ -43,14 +42,13 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.Urls;
+import org.keycloak.utils.MediaType;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
-import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Providers;
@@ -281,7 +279,7 @@ public class AdminConsole {
if (!uriInfo.getRequestUri().getPath().endsWith("/")) {
return Response.status(302).location(uriInfo.getRequestUriBuilder().path("/").build()).build();
} else {
- Theme theme = getTheme();
+ Theme theme = AdminRoot.getTheme(session, realm);
Map map = new HashMap<>();
@@ -297,17 +295,12 @@ public class AdminConsole {
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil();
String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme);
- Response.ResponseBuilder builder = Response.status(Response.Status.OK).type(MediaType.TEXT_HTML).entity(result);
+ Response.ResponseBuilder builder = Response.status(Response.Status.OK).type(MediaType.TEXT_HTML_UTF_8).language(Locale.ENGLISH).entity(result);
BrowserSecurityHeaderSetup.headers(builder, realm);
return builder.build();
}
}
- private Theme getTheme() throws IOException {
- ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
- return themeProvider.getTheme(realm.getAdminTheme(), Theme.Type.ADMIN);
- }
-
@GET
@Path("{indexhtml: index.html}") // this expression is a hack to get around jaxdoclet generation bug. Doesn't like index.html
public Response getIndexHtmlRedirect() {
@@ -318,11 +311,7 @@ public class AdminConsole {
@Path("messages.json")
@Produces(MediaType.APPLICATION_JSON)
public Properties getMessages(@QueryParam("lang") String lang) {
- try {
- Locale locale = lang != null ? Locale.forLanguageTag(lang) : Locale.ENGLISH;
- return getTheme().getMessages("admin-messages", locale);
- } catch (IOException e) {
- throw new WebApplicationException("Failed to load message bundle", e);
- }
+ return AdminRoot.getMessages(session, realm, "admin-messages", lang);
}
+
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
index 10b636b2db..0649b25fff 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
@@ -38,6 +38,8 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.Cors;
import org.keycloak.services.resources.admin.info.ServerInfoAdminResource;
+import org.keycloak.theme.Theme;
+import org.keycloak.theme.ThemeProvider;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@@ -47,6 +49,9 @@ import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Properties;
/**
* Root resource for admin console and admin REST API
@@ -265,4 +270,31 @@ public class AdminRoot {
}
}
+ public static Theme getTheme(KeycloakSession session, RealmModel realm) throws IOException {
+ ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
+ return themeProvider.getTheme(realm.getAdminTheme(), Theme.Type.ADMIN);
+ }
+
+ public static Properties getMessages(KeycloakSession session, RealmModel realm, String lang) {
+ try {
+ Theme theme = getTheme(session, realm);
+ Locale locale = lang != null ? Locale.forLanguageTag(lang) : Locale.ENGLISH;
+ return theme.getMessages(locale);
+ } catch (IOException e) {
+ logger.error("Failed to load messages from theme", e);
+ return new Properties();
+ }
+ }
+
+ public static Properties getMessages(KeycloakSession session, RealmModel realm, String bundle, String lang) {
+ try {
+ Theme theme = getTheme(session, realm);
+ Locale locale = lang != null ? Locale.forLanguageTag(lang) : Locale.ENGLISH;
+ return theme.getMessages(bundle, locale);
+ } catch (IOException e) {
+ logger.error("Failed to load messages from theme", e);
+ return new Properties();
+ }
+ }
+
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
index 6c9bba991c..54378bf181 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
@@ -581,7 +581,7 @@ public class AuthenticationManagementResource {
}
public List getSortedExecutions(AuthenticationFlowModel parentFlow) {
- List executions = realm.getAuthenticationExecutions(parentFlow.getId());
+ List executions = new LinkedList<>(realm.getAuthenticationExecutions(parentFlow.getId()));
Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
return executions;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
index 6633a68f20..bea79a018a 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
@@ -28,6 +28,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.KeyStoreConfig;
import org.keycloak.representations.idm.CertificateRepresentation;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.common.util.PemUtils;
@@ -57,7 +58,7 @@ import java.util.Map;
* @version $Revision: 1 $
*/
public class ClientAttributeCertificateResource {
-
+
public static final String PRIVATE_KEY = "private.key";
public static final String X509CERTIFICATE = "certificate";
@@ -112,7 +113,7 @@ public class ClientAttributeCertificateResource {
client.setAttribute(privateAttribute, info.getPrivateKey());
client.setAttribute(certificateAttribute, info.getCertificate());
-
+
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(info).success();
return info;
@@ -225,64 +226,6 @@ public class ClientAttributeCertificateResource {
return info;
}
-
- public static class KeyStoreConfig {
- protected Boolean realmCertificate;
- protected String storePassword;
- protected String keyPassword;
- protected String keyAlias;
- protected String realmAlias;
- protected String format;
-
- public Boolean isRealmCertificate() {
- return realmCertificate;
- }
-
- public void setRealmCertificate(Boolean realmCertificate) {
- this.realmCertificate = realmCertificate;
- }
-
- public String getStorePassword() {
- return storePassword;
- }
-
- public void setStorePassword(String storePassword) {
- this.storePassword = storePassword;
- }
-
- public String getKeyPassword() {
- return keyPassword;
- }
-
- public void setKeyPassword(String keyPassword) {
- this.keyPassword = keyPassword;
- }
-
- public String getKeyAlias() {
- return keyAlias;
- }
-
- public void setKeyAlias(String keyAlias) {
- this.keyAlias = keyAlias;
- }
-
- public String getRealmAlias() {
- return realmAlias;
- }
-
- public void setRealmAlias(String realmAlias) {
- this.realmAlias = realmAlias;
- }
-
- public String getFormat() {
- return format;
- }
-
- public void setFormat(String format) {
- this.format = format;
- }
- }
-
/**
* Get a keystore file for the client, containing private key and public certificate
*
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index 790dd2b8f6..09c3deec81 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -45,7 +45,6 @@ import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.ErrorResponse;
-import org.keycloak.util.JsonSerialization;
import org.keycloak.common.util.Time;
import javax.ws.rs.Consumes;
@@ -62,12 +61,11 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
+
import static java.lang.Boolean.TRUE;
@@ -172,54 +170,6 @@ public class ClientResource {
return provider.generateInstallation(session, realm, client, keycloak.getBaseUri(uriInfo));
}
-
- /**
- * Get keycloak.json file
- *
- * this method is deprecated, see getInstallationProvider
- *
- * Returns a keycloak.json file to be used to configure the adapter of the specified client.
- *
- * @return
- * @throws IOException
- */
- @Deprecated
- @GET
- @NoCache
- @Path("installation/json")
- @Produces(MediaType.APPLICATION_JSON)
- public String getInstallation() throws IOException {
- auth.requireView();
-
- ClientManager clientManager = new ClientManager(new RealmManager(session));
- Object rep = clientManager.toInstallationRepresentation(realm, client, getKeycloakApplication().getBaseUri(uriInfo));
-
- // TODO Temporary solution to pretty-print
- return JsonSerialization.mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rep);
- }
-
- /**
- * Get adapter configuration XML for JBoss / Wildfly Keycloak subsystem
- *
- * this method is deprecated, see getInstallationProvider
- *
- * Returns XML that can be included in the JBoss / Wildfly Keycloak subsystem to configure the adapter of that client.
- *
- * @return
- * @throws IOException
- */
- @Deprecated
- @GET
- @NoCache
- @Path("installation/jboss")
- @Produces(MediaType.TEXT_PLAIN)
- public String getJBossInstallation() throws IOException {
- auth.requireView();
-
- ClientManager clientManager = new ClientManager(new RealmManager(session));
- return clientManager.toJBossSubsystemConfig(realm, client, getKeycloakApplication().getBaseUri(uriInfo));
- }
-
/**
* Delete the client
*
@@ -306,64 +256,6 @@ public class ClientResource {
return new RoleContainerResource(uriInfo, realm, auth, client, adminEvent);
}
- /**
- * Get allowed origins
- *
- * This is used for CORS requests. Access tokens will have
- * their allowedOrigins claim set to this value for tokens created for this client.
- *
- * @return
- */
- @Path("allowed-origins")
- @GET
- @NoCache
- @Produces(MediaType.APPLICATION_JSON)
- public Set getAllowedOrigins()
- {
- auth.requireView();
- return client.getWebOrigins();
- }
-
- /**
- * Update allowed origins
- *
- * This is used for CORS requests. Access tokens will have
- * their allowedOrigins claim set to this value for tokens created for this client.
- *
- * @param allowedOrigins
- */
- @Path("allowed-origins")
- @PUT
- @Consumes(MediaType.APPLICATION_JSON)
- public void updateAllowedOrigins(Set allowedOrigins)
- {
- auth.requireManage();
-
- client.setWebOrigins(allowedOrigins);
- adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(client).success();
- }
-
- /**
- * Delete the specified origins from current allowed origins
- *
- * This is used for CORS requests. Access tokens will have
- * their allowedOrigins claim set to this value for tokens created for this client.
- *
- * @param allowedOrigins List of origins to delete
- */
- @Path("allowed-origins")
- @DELETE
- @Consumes(MediaType.APPLICATION_JSON)
- public void deleteAllowedOrigins(Set allowedOrigins)
- {
- auth.requireManage();
-
- for (String origin : allowedOrigins) {
- client.removeWebOrigin(origin);
- }
- adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
- }
-
/**
* Get a user dedicated to the service account
*
@@ -507,41 +399,6 @@ public class ClientResource {
return sessions;
}
-
- /**
- * Logout all sessions
- *
- * If the client has an admin URL, invalidate all sessions associated with that client directly.
- *
- */
- @Path("logout-all")
- @POST
- public GlobalRequestResult logoutAll() {
- auth.requireManage();
- adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
- return new ResourceAdminManager(session).logoutClient(uriInfo.getRequestUri(), realm, client);
-
- }
-
- /**
- * Logout the user by username
- *
- * If the client has an admin URL, invalidate the sessions for a particular user directly.
- *
- */
- @Path("logout-user/{username}")
- @POST
- public void logout(final @PathParam("username") String username) {
- auth.requireManage();
- UserModel user = session.users().getUserByUsername(username, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
- new ResourceAdminManager(session).logoutUserFromClient(uriInfo.getRequestUri(), realm, client, user);
-
- }
-
/**
* Register a cluster node with the client
*
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 0c9f4ce341..b434cdfe04 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -20,7 +20,10 @@ import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.BadRequestException;
import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.KeyPairVerifier;
import org.keycloak.common.ClientConnection;
+import org.keycloak.common.VerificationException;
+import org.keycloak.common.util.PemUtils;
import org.keycloak.events.Event;
import org.keycloak.events.EventQuery;
import org.keycloak.events.EventStoreProvider;
@@ -30,6 +33,8 @@ import org.keycloak.events.admin.AdminEventQuery;
import org.keycloak.events.admin.OperationType;
import org.keycloak.exportimport.ClientDescriptionConverter;
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
+import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
@@ -74,8 +79,11 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
+import java.security.PrivateKey;
+import java.security.PublicKey;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -236,6 +244,14 @@ public class RealmAdminResource {
logger.debug("updating realm: " + realm.getName());
try {
+ if (!"GENERATE".equals(rep.getPublicKey()) && (rep.getPrivateKey() != null && rep.getPublicKey() != null)) {
+ try {
+ KeyPairVerifier.verify(rep.getPrivateKey(), rep.getPublicKey());
+ } catch (VerificationException e) {
+ return ErrorResponse.error(e.getMessage(), Status.BAD_REQUEST);
+ }
+ }
+
RepresentationToModel.updateRealm(rep, realm);
// Refresh periodic sync tasks for configured federationProviders
@@ -253,7 +269,7 @@ public class RealmAdminResource {
throw e;
} catch (Exception e) {
logger.error(e.getMessage(), e);
- return ErrorResponse.error("Failed to update " + rep.getRealm() + " Realm.", Response.Status.INTERNAL_SERVER_ERROR);
+ return ErrorResponse.error("Failed to update realm", Response.Status.INTERNAL_SERVER_ERROR);
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
index 0eb1748e09..29b4ae6715 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
@@ -16,12 +16,14 @@
*/
package org.keycloak.services.resources.admin;
+import java.text.MessageFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Properties;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -40,7 +42,7 @@ import javax.ws.rs.core.UriInfo;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.events.admin.OperationType;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.mappers.UserFederationMapperFactory;
import org.keycloak.models.KeycloakSession;
@@ -63,7 +65,6 @@ import org.keycloak.representations.idm.UserFederationProviderRepresentation;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.UsersSyncManager;
-import org.keycloak.timer.TimerProvider;
/**
* @author Marek Posolda
@@ -105,6 +106,9 @@ public class UserFederationProviderResource {
}
UserFederationProviderModel model = new UserFederationProviderModel(rep.getId(), rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
+
+ UserFederationProvidersResource.validateFederationProviderConfig(session, auth, realm, model);
+
realm.updateUserFederationProvider(model);
new UsersSyncManager().notifyToRefreshPeriodicSync(session, realm, model, false);
boolean kerberosCredsAdded = UserFederationProvidersResource.checkKerberosCredential(session, realm, model);
@@ -369,9 +373,12 @@ public class UserFederationProviderResource {
private void validateModel(UserFederationMapperModel model) {
try {
UserFederationMapperFactory mapperFactory = (UserFederationMapperFactory) session.getKeycloakSessionFactory().getProviderFactory(UserFederationMapper.class, model.getFederationMapperType());
- mapperFactory.validateConfig(realm, model);
- } catch (MapperConfigValidationException ex) {
- throw new ErrorResponseException("Validation error", ex.getMessage(), Response.Status.BAD_REQUEST);
+ mapperFactory.validateConfig(realm, federationProviderModel, model);
+ } catch (FederationConfigValidationException ex) {
+ logger.error(ex.getMessage());
+ Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
+ throw new ErrorResponseException(ex.getMessage(), MessageFormat.format(messages.getProperty(ex.getMessage(), ex.getMessage()), ex.getParameters()),
+ Response.Status.BAD_REQUEST);
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java
index 995cda46af..9c9ac5143f 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java
@@ -21,11 +21,13 @@ import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.common.constants.KerberosConstants;
import org.keycloak.events.admin.OperationType;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationValidatingProviderFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.provider.ConfiguredProvider;
@@ -35,6 +37,8 @@ import org.keycloak.representations.idm.ConfigPropertyRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation;
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
+import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.UsersSyncManager;
import org.keycloak.timer.TimerProvider;
@@ -51,9 +55,11 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
+import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
+import java.util.Properties;
/**
* Base resource for managing users
@@ -101,6 +107,20 @@ public class UserFederationProvidersResource {
return false;
}
+ public static void validateFederationProviderConfig(KeycloakSession session, RealmAuth auth, RealmModel realm, UserFederationProviderModel model) {
+ UserFederationProviderFactory providerFactory = KeycloakModelUtils.getFederationProviderFactory(session, model);
+ if (providerFactory instanceof UserFederationValidatingProviderFactory) {
+ try {
+ ((UserFederationValidatingProviderFactory) providerFactory).validateConfig(realm, model);
+ } catch (FederationConfigValidationException fcve) {
+ logger.error(fcve.getMessage());
+ Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
+ throw new ErrorResponseException(fcve.getMessage(), MessageFormat.format(messages.getProperty(fcve.getMessage(), fcve.getMessage()), fcve.getParameters()),
+ Response.Status.BAD_REQUEST);
+ }
+ }
+ }
+
/**
* Get available provider factories
*
@@ -176,6 +196,10 @@ public class UserFederationProvidersResource {
if (displayName != null && displayName.trim().equals("")) {
displayName = null;
}
+
+ UserFederationProviderModel tempModel = new UserFederationProviderModel(null, rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName, rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
+ validateFederationProviderConfig(session, auth, realm, tempModel);
+
UserFederationProviderModel model = realm.addUserFederationProvider(rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
new UsersSyncManager().notifyToRefreshPeriodicSync(session, realm, model, false);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index e8e38ba491..9b035356ed 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -36,6 +36,7 @@ import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserConsentModel;
@@ -54,6 +55,7 @@ import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.UserConsentRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
+import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.UserManager;
@@ -74,11 +76,14 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.WebApplicationException;
+import java.io.IOException;
import java.net.URI;
+import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -86,6 +91,7 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.keycloak.models.UsernameLoginFailureModel;
@@ -93,6 +99,10 @@ import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.resources.AccountService;
import org.keycloak.common.util.Time;
+import org.keycloak.services.validation.Validation;
+import org.keycloak.theme.Theme;
+import org.keycloak.theme.Theme.Type;
+import org.keycloak.theme.ThemeProvider;
/**
* Base resource for managing users
@@ -707,6 +717,9 @@ public class UsersResource {
if (pass == null || pass.getValue() == null || !CredentialRepresentation.PASSWORD.equals(pass.getType())) {
throw new BadRequestException("No password provided");
}
+ if (Validation.isBlank(pass.getValue())) {
+ throw new BadRequestException("Empty password not allowed");
+ }
UserCredentialModel cred = RepresentationToModel.convertCredential(pass);
try {
@@ -715,6 +728,10 @@ public class UsersResource {
throw new BadRequestException("Resetting to N old passwords is not allowed.");
} catch (ModelReadOnlyException mre) {
throw new BadRequestException("Can't reset password as account is read only");
+ } catch (ModelException e) {
+ Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
+ throw new ErrorResponseException(e.getMessage(), MessageFormat.format(messages.getProperty(e.getMessage(), e.getMessage()), e.getParameters()),
+ Status.BAD_REQUEST);
}
if (pass.isTemporary() != null && pass.isTemporary()) user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
index 419ea1652b..4454ccbfe6 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
@@ -51,8 +51,14 @@ import org.keycloak.provider.Spi;
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
-import org.keycloak.representations.info.*;
import org.keycloak.broker.social.SocialIdentityProvider;
+import org.keycloak.representations.info.ClientInstallationRepresentation;
+import org.keycloak.representations.info.MemoryInfoRepresentation;
+import org.keycloak.representations.info.ProviderRepresentation;
+import org.keycloak.representations.info.ServerInfoRepresentation;
+import org.keycloak.representations.info.SpiInfoRepresentation;
+import org.keycloak.representations.info.SystemInfoRepresentation;
+import org.keycloak.representations.info.ThemeInfoRepresentation;
/**
* @author Stian Thorgersen
diff --git a/services/src/main/java/org/keycloak/services/util/LocaleHelper.java b/services/src/main/java/org/keycloak/services/util/LocaleHelper.java
index 63f38d0514..18711931c6 100755
--- a/services/src/main/java/org/keycloak/services/util/LocaleHelper.java
+++ b/services/src/main/java/org/keycloak/services/util/LocaleHelper.java
@@ -61,16 +61,6 @@ public class LocaleHelper {
}
}
- // User profile
- if (user != null && user.getAttributes().containsKey(UserModel.LOCALE)) {
- String localeString = user.getFirstAttribute(UserModel.LOCALE);
- Locale locale = findLocale(realm.getSupportedLocales(), localeString);
- if (locale != null) {
- updateLocaleCookie(session, realm, localeString);
- return locale;
- }
- }
-
// Locale cookie
if (httpHeaders != null && httpHeaders.getCookies().containsKey(LOCALE_COOKIE)) {
String localeString = httpHeaders.getCookies().get(LOCALE_COOKIE).getValue();
@@ -83,6 +73,16 @@ public class LocaleHelper {
}
}
+ // User profile
+ if (user != null && user.getAttributes().containsKey(UserModel.LOCALE)) {
+ String localeString = user.getFirstAttribute(UserModel.LOCALE);
+ Locale locale = findLocale(realm.getSupportedLocales(), localeString);
+ if (locale != null) {
+ updateLocaleCookie(session, realm, localeString);
+ return locale;
+ }
+ }
+
// ui_locales query parameter
if (uriInfo != null && uriInfo.getQueryParameters().containsKey(UI_LOCALES_PARAM)) {
String localeString = uriInfo.getQueryParameters().getFirst(UI_LOCALES_PARAM);
diff --git a/services/src/main/java/org/keycloak/theme/BrowserSecurityHeaderSetup.java b/services/src/main/java/org/keycloak/theme/BrowserSecurityHeaderSetup.java
index 41fc584529..dfcbf50e1d 100755
--- a/services/src/main/java/org/keycloak/theme/BrowserSecurityHeaderSetup.java
+++ b/services/src/main/java/org/keycloak/theme/BrowserSecurityHeaderSetup.java
@@ -32,8 +32,9 @@ public class BrowserSecurityHeaderSetup {
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, RealmModel realm) {
for (Map.Entry entry : realm.getBrowserSecurityHeaders().entrySet()) {
String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
- if (headerName == null) continue;
- builder.header(headerName, entry.getValue());
+ if (headerName != null && entry.getValue() != null && entry.getValue().length() > 0) {
+ builder.header(headerName, entry.getValue());
+ }
}
return builder;
}
diff --git a/services/src/main/java/org/keycloak/utils/MediaType.java b/services/src/main/java/org/keycloak/utils/MediaType.java
new file mode 100644
index 0000000000..31ab972392
--- /dev/null
+++ b/services/src/main/java/org/keycloak/utils/MediaType.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016 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.utils;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class MediaType {
+
+ public static final String TEXT_HTML_UTF_8 = "text/html; charset=utf-8";
+ public static final javax.ws.rs.core.MediaType TEXT_HTML_UTF_8_TYPE = new javax.ws.rs.core.MediaType("text", "html", "utf-8");
+
+ public static final String APPLICATION_JSON = javax.ws.rs.core.MediaType.APPLICATION_JSON;
+ public static final javax.ws.rs.core.MediaType APPLICATION_JSON_TYPE = javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
+
+ public static final String APPLICATION_FORM_URLENCODED = javax.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED;
+ public static final javax.ws.rs.core.MediaType APPLICATION_FORM_URLENCODED_TYPE = javax.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED_TYPE;
+
+}
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 7143e6c20b..50bb346ff5 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -17,5 +17,4 @@
org.keycloak.exportimport.ClientDescriptionConverterSpi
org.keycloak.wellknown.WellKnownSpi
-org.keycloak.messages.MessagesSpi
org.keycloak.services.clientregistration.ClientRegistrationSpi
diff --git a/services/src/test/java/org/keycloak/test/broker/saml/SAMLParsingTest.java b/services/src/test/java/org/keycloak/test/broker/saml/SAMLParsingTest.java
new file mode 100644
index 0000000000..d26895ae4a
--- /dev/null
+++ b/services/src/test/java/org/keycloak/test/broker/saml/SAMLParsingTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016 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.test.broker.saml;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.saml.SAMLRequestParser;
+import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
+import org.keycloak.saml.processing.web.util.PostBindingUtil;
+
+/**
+ * This was failing on IBM JDK
+ *
+ * @author Marek Posolda
+ */
+public class SAMLParsingTest {
+
+ private static final String SAML_RESPONSE = "PHNhbWxwOkxvZ291dFJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBEZXN0aW5hdGlvbj0iaHR0cDovL2xvY2FsaG9zdDo4MDgxL2F1dGgvcmVhbG1zL3JlYWxtLXdpdGgtYnJva2VyL2Jyb2tlci9rYy1zYW1sLWlkcC1iYXNpYy9lbmRwb2ludCIgSUQ9IklEXzlhMTcxZDIzLWM0MTctNDJmNS05YmNhLWMwOTMxMjNmZDY4YyIgSW5SZXNwb25zZVRvPSJJRF9iYzczMDcxMS0yMDM3LTQzZjMtYWQ3Ni03YmMzMzg0MmZiODciIElzc3VlSW5zdGFudD0iMjAxNi0wMi0yOVQxMjowMDoxNC4wNDRaIiBWZXJzaW9uPSIyLjAiPjxzYW1sOklzc3VlciB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5odHRwOi8vbG9jYWxob3N0OjgwODIvYXV0aC9yZWFsbXMvcmVhbG0td2l0aC1zYW1sLWlkcC1iYXNpYzwvc2FtbDpJc3N1ZXI+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PC9zYW1scDpMb2dvdXRSZXNwb25zZT4=";
+
+ @Test
+ public void parseTest() throws Exception {
+ byte[] samlBytes = PostBindingUtil.base64Decode(SAML_RESPONSE);
+ SAMLDocumentHolder holder = SAMLRequestParser.parseResponseDocument(samlBytes);
+ Assert.assertNotNull(holder);
+ }
+}
diff --git a/testsuite/docker-cluster/pom.xml b/testsuite/docker-cluster/pom.xml
index 30a6a56472..1f67e1234c 100755
--- a/testsuite/docker-cluster/pom.xml
+++ b/testsuite/docker-cluster/pom.xml
@@ -21,7 +21,7 @@
keycloak-testsuite-pom
org.keycloak
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
diff --git a/testsuite/integration-arquillian/README.md b/testsuite/integration-arquillian/README.md
index 39168de056..1adc0547bc 100644
--- a/testsuite/integration-arquillian/README.md
+++ b/testsuite/integration-arquillian/README.md
@@ -152,6 +152,7 @@ integration-arquillian
│
├──console (activated by -Pconsole-ui-tests)
├──mod_auth_mellon (activated by -Pmod_auth_mellon)
+ ├──console_no_users (activated by -Pconsole-ui-no-users-tests)
└──...
```
diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml
index ffc8bfcb7f..e008b7400f 100644
--- a/testsuite/integration-arquillian/pom.xml
+++ b/testsuite/integration-arquillian/pom.xml
@@ -24,7 +24,7 @@
org.keycloak
keycloak-testsuite-pom
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
../pom.xml
diff --git a/testsuite/integration-arquillian/servers/eap7/pom.xml b/testsuite/integration-arquillian/servers/eap7/pom.xml
index 47bec1b991..a02b37d994 100644
--- a/testsuite/integration-arquillian/servers/eap7/pom.xml
+++ b/testsuite/integration-arquillian/servers/eap7/pom.xml
@@ -1,27 +1,27 @@
+~ Copyright 2016 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.
+-->
org.keycloak.testsuite
integration-arquillian-servers
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
@@ -117,6 +117,68 @@
+
+ ssl
+
+
+ auth.server.ssl.required
+
+
+
+
+
+ org.codehaus.mojo
+ xml-maven-plugin
+
+
+ configure-adapter-subsystem-security
+ process-resources
+
+ transform
+
+
+
+
+ ${keycloak.server.home}/standalone/configuration
+
+ standalone.xml
+
+ src/main/xslt/security.xsl
+ ${keycloak.server.home}/standalone/configuration
+
+
+
+
+
+
+
+ maven-resources-plugin
+ 2.7
+
+
+ copy-keystore
+ process-resources
+
+ copy-resources
+
+
+ ${keycloak.server.home}/standalone/configuration
+
+
+ src/main/keystore
+
+ keycloak.jks
+ keycloak.truststore
+
+
+
+
+
+
+
+
+
+
jpa
@@ -255,13 +317,14 @@
+
- ssl
-
-
- auth.server.ssl.required
-
-
+ auth-server-eap7-cluster
+
+ 1
+ 1
+ 1
+
@@ -269,53 +332,62 @@
xml-maven-plugin
- configure-adapter-subsystem-security
+ configure-wildfly-datasource
process-resources
transform
+
${keycloak.server.home}/standalone/configuration
- standalone.xml
+ standalone-ha.xml
- src/main/xslt/security.xsl
+ src/main/xslt/datasource-jdbc-url.xsl
${keycloak.server.home}/standalone/configuration
+
+
+ pool.name
+ KeycloakDS
+
+
+ jdbc.url
+ jdbc:h2:tcp://${jboss.bind.address:localhost}:9092/mem:keycloak;DB_CLOSE_DELAY=-1
+
+
+
+
+ ${keycloak.server.home}/standalone/configuration
+
+ standalone-ha.xml
+
+ src/main/xslt/ispn-cache-owners.xsl
+ ${keycloak.server.home}/standalone/configuration
+
+
+ sessionCacheOwners
+ ${session.cache.owners}
+
+
+ offlineSessionCacheOwners
+ ${offline.session.cache.owners}
+
+
+ loginFailureCacheOwners
+ ${login.failure.cache.owners}
+
+
-
- maven-resources-plugin
- 2.7
-
-
- copy-keystore
- process-resources
-
- copy-resources
-
-
- ${keycloak.server.home}/standalone/configuration
-
-
- src/main/keystore
-
- keycloak.jks
- keycloak.truststore
-
-
-
-
-
-
-
+
diff --git a/testsuite/integration-arquillian/servers/eap7/src/main/xslt/datasource-jdbc-url.xsl b/testsuite/integration-arquillian/servers/eap7/src/main/xslt/datasource-jdbc-url.xsl
new file mode 100644
index 0000000000..589ee4cc1d
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/eap7/src/main/xslt/datasource-jdbc-url.xsl
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/eap7/src/main/xslt/ispn-cache-owners.xsl b/testsuite/integration-arquillian/servers/eap7/src/main/xslt/ispn-cache-owners.xsl
new file mode 100644
index 0000000000..7237d89868
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/eap7/src/main/xslt/ispn-cache-owners.xsl
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/pom.xml b/testsuite/integration-arquillian/servers/pom.xml
index 1546e35f96..81affa2c9e 100644
--- a/testsuite/integration-arquillian/servers/pom.xml
+++ b/testsuite/integration-arquillian/servers/pom.xml
@@ -21,7 +21,7 @@
org.keycloak.testsuite
integration-arquillian
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
@@ -54,6 +54,13 @@
eap7
+
+ auth-server-eap7-cluster
+
+ eap7
+ wildfly-balancer
+
+
diff --git a/testsuite/integration-arquillian/servers/wildfly-balancer/pom.xml b/testsuite/integration-arquillian/servers/wildfly-balancer/pom.xml
index 4a033649bb..84d6638b38 100644
--- a/testsuite/integration-arquillian/servers/wildfly-balancer/pom.xml
+++ b/testsuite/integration-arquillian/servers/wildfly-balancer/pom.xml
@@ -21,7 +21,7 @@
org.keycloak.testsuite
integration-arquillian-servers
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/testsuite/integration-arquillian/servers/wildfly/pom.xml b/testsuite/integration-arquillian/servers/wildfly/pom.xml
index bd822a150e..5607d94cd2 100644
--- a/testsuite/integration-arquillian/servers/wildfly/pom.xml
+++ b/testsuite/integration-arquillian/servers/wildfly/pom.xml
@@ -21,7 +21,7 @@
org.keycloak.testsuite
integration-arquillian-servers
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml
index 61b360484c..70bf0df018 100644
--- a/testsuite/integration-arquillian/tests/base/pom.xml
+++ b/testsuite/integration-arquillian/tests/base/pom.xml
@@ -21,7 +21,7 @@
org.keycloak.testsuite
integration-arquillian-tests
- 1.9.1.Final-SNAPSHOT
+ 2.0.0.CR1-SNAPSHOT
4.0.0
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java
index 275fd9ca63..99fda364fe 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java
@@ -30,6 +30,8 @@ import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.Response.StatusType;
import org.apache.commons.lang.builder.EqualsBuilder;
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
@@ -44,6 +46,11 @@ public class ApiUtil {
public static String getCreatedId(Response response) {
URI location = response.getLocation();
+ if (!response.getStatusInfo().equals(Status.CREATED)) {
+ StatusType statusInfo = response.getStatusInfo();
+ throw new RuntimeException("Create method returned status " +
+ statusInfo.getReasonPhrase() + " (Code: " + statusInfo.getStatusCode() + "); expected status: Created (201)");
+ }
if (location == null) {
return null;
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
index 275974f842..63f5436d39 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
@@ -23,6 +23,7 @@ import org.jboss.arquillian.container.test.impl.enricher.resource.URLResourcePro
import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor;
import org.jboss.arquillian.container.test.spi.client.deployment.DeploymentScenarioGenerator;
import org.jboss.arquillian.core.spi.LoadableExtension;
+import org.jboss.arquillian.graphene.location.ContainerCustomizableURLResourceProvider;
import org.jboss.arquillian.graphene.location.CustomizableURLResourceProvider;
import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
import org.jboss.arquillian.test.spi.execution.TestExecutionDecider;
@@ -60,7 +61,8 @@ public class KeycloakArquillianExtension implements LoadableExtension {
builder
.override(ResourceProvider.class, URLResourceProvider.class, URLProvider.class)
- .override(ResourceProvider.class, CustomizableURLResourceProvider.class, URLProvider.class);
+ .override(ResourceProvider.class, CustomizableURLResourceProvider.class, URLProvider.class)
+ .override(ResourceProvider.class, ContainerCustomizableURLResourceProvider.class, URLProvider.class);
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java
index 673bc7c4fd..fc63b2291b 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java
@@ -17,7 +17,9 @@
package org.keycloak.testsuite.arquillian;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
*
@@ -33,6 +35,8 @@ public final class TestContext {
private final List appServerBackendsInfo = new ArrayList<>();
private boolean adminLoggedIn;
+
+ private final Map customContext = new HashMap<>();
public TestContext(SuiteContext suiteContext, Class testClass) {
this.suiteContext = suiteContext;
@@ -88,4 +92,12 @@ public final class TestContext {
+ (isAdapterTest() ? "App server container: " + getAppServerInfo() + "\n" : "");
}
+ public Object getCustomValue(Object key) {
+ return customContext.get(key);
+ }
+
+ public void setCustomValue(Object key, Object value) {
+ customContext.put(key, value);
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java
index 72d389c556..9f34a40bb8 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java
@@ -17,6 +17,7 @@
package org.keycloak.testsuite.auth.page;
+import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -38,7 +39,8 @@ public class WelcomePage extends AuthServer {
private WebElement createButton;
public boolean isPasswordSet() {
- return !driver.getPageSource().contains("Please create an initial admin user to get started.");
+ return !(driver.getPageSource().contains("Please create an initial admin user to get started.") ||
+ driver.getPageSource().contains("You need local access to create the initial admin user."));
}
public void setPassword(String username, String password) {
@@ -58,4 +60,8 @@ public class WelcomePage extends AuthServer {
}
}
+ public void navigateToAdminConsole() {
+ driver.findElement(By.linkText("Administration Console")).click();
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/AccountManagement.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/AccountManagement.java
index ba45448473..0de5299820 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/AccountManagement.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/AccountManagement.java
@@ -18,8 +18,8 @@ package org.keycloak.testsuite.auth.page.account;
import javax.ws.rs.core.UriBuilder;
import org.jboss.arquillian.graphene.findby.FindByJQuery;
-import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.testsuite.auth.page.AuthRealm;
+import org.keycloak.testsuite.page.PageWithLogOutAction;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -29,7 +29,7 @@ import org.openqa.selenium.support.FindBy;
* @author Petr Mensik
* @author tkyjovsk
*/
-public class AccountManagement extends AuthRealm {
+public class AccountManagement extends AuthRealm implements PageWithLogOutAction {
@Override
public UriBuilder createUriBuilder() {
@@ -76,7 +76,12 @@ public class AccountManagement extends AuthRealm {
public void signOut() {
signOutLink.click();
}
-
+
+ @Override
+ public void logOut() {
+ signOut();
+ }
+
public void account() {
accountLink.click();
}
@@ -108,4 +113,5 @@ public class AccountManagement extends AuthRealm {
public void waitForAccountLinkPresent() {
waitUntilElement(accountLink, "account link should be present").is().present();
}
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/Login.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/Login.java
index f489f0087d..dcee089960 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/Login.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/Login.java
@@ -42,7 +42,7 @@ public abstract class Login extends AuthRealm {
@Override
public UriBuilder createUriBuilder() {
return super.createUriBuilder()
- .path((getProtocol().equals(OIDC) || getProtocol().equals(SAML)) ? "protocol/" : "" + "{" + PROTOCOL + "}" + (getProtocol().equals(OIDC) ? "/auth" : ""));
+ .path(((getProtocol().equals(OIDC) || getProtocol().equals(SAML)) ? "protocol/" : "") + "{" + PROTOCOL + "}" + (getProtocol().equals(OIDC) ? "/auth" : ""));
}
public void setProtocol(String protocol) {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/SAMLIDPInitiatedLogin.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/SAMLIDPInitiatedLogin.java
new file mode 100644
index 0000000000..66cf0758c0
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/SAMLIDPInitiatedLogin.java
@@ -0,0 +1,19 @@
+package org.keycloak.testsuite.auth.page.login;
+
+import javax.ws.rs.core.UriBuilder;
+
+/**
+ * @author mhajas
+ */
+public class SAMLIDPInitiatedLogin extends SAMLRedirectLogin {
+
+ public void setUrlName(String urlName) {
+ setUriParameter("clientUrlName", urlName);
+ }
+
+ @Override
+ public UriBuilder createUriBuilder() {
+ return super.createUriBuilder().path("clients/{clientUrlName}");
+ }
+}
+
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/TermsAndConditions.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/TermsAndConditions.java
new file mode 100644
index 0000000000..a4c425eac2
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/TermsAndConditions.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016 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.testsuite.auth.page.login;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class TermsAndConditions extends LoginActions {
+
+ @FindBy(id = "kc-accept")
+ private WebElement acceptButton;
+
+ @FindBy(id = "kc-decline")
+ private WebElement declineButton;
+
+ @FindBy(id = "kc-terms-text")
+ private WebElement textElem;
+
+ @Override
+ public boolean isCurrent() {
+ return driver.getTitle().equals("Terms and Conditions");
+ }
+
+ public void acceptTerms() {
+ acceptButton.click();
+ }
+ public void declineTerms() {
+ declineButton.click();
+ }
+
+ public String getAcceptButtonText() {
+ return acceptButton.getAttribute("value");
+ }
+
+ public String getDeclineButtonText() {
+ return declineButton.getAttribute("value");
+ }
+
+ public String getText() {
+ return textElem.getText();
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsole.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsole.java
index 18a535281e..c46b59fb30 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsole.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsole.java
@@ -25,6 +25,7 @@ import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
import org.keycloak.testsuite.auth.page.login.PageWithLoginUrl;
import org.keycloak.testsuite.console.page.fragment.Menu;
import org.keycloak.testsuite.console.page.fragment.ModalDialog;
+import org.keycloak.testsuite.page.PageWithLogOutAction;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -32,8 +33,8 @@ import org.openqa.selenium.support.FindBy;
*
* @author Petr Mensik
*/
-public class AdminConsole extends AuthServer implements PageWithLoginUrl {
-
+public class AdminConsole extends AuthServer implements PageWithLoginUrl, PageWithLogOutAction {
+
public static final String ADMIN_REALM = "adminRealm";
public AdminConsole() {
@@ -56,7 +57,7 @@ public class AdminConsole extends AuthServer implements PageWithLoginUrl {
@Page
private Menu menu;
-
+
@FindBy(xpath = "//div[@class='modal-dialog']")
protected ModalDialog modalDialog;
@@ -79,7 +80,8 @@ public class AdminConsole extends AuthServer implements PageWithLoginUrl {
@FindBy(css = "navbar-brand")
protected WebElement brandLink;
-
+
+ @Override
public void logOut() {
menu.logOut();
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/PageWithLogOutAction.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/PageWithLogOutAction.java
new file mode 100644
index 0000000000..33fa4fcce7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/PageWithLogOutAction.java
@@ -0,0 +1,11 @@
+package org.keycloak.testsuite.page;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public interface PageWithLogOutAction {
+
+ public void logOut();
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java
index 23103d3339..d8729149ac 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java
@@ -43,6 +43,8 @@ public class IOUtil {
private static final Logger log = Logger.getLogger(IOUtil.class);
+ public static final File PROJECT_BUILD_DIRECTORY = new File(System.getProperty("project.build.directory", "target"));
+
public static T loadJson(InputStream is, Class type) {
try {
return JsonSerialization.readValue(is, type);
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LogChecker.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LogChecker.java
index 8e16b3492e..ac1b059eb9 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LogChecker.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LogChecker.java
@@ -1,9 +1,10 @@
package org.keycloak.testsuite.util;
+import org.apache.commons.io.FileUtils;
+import org.jboss.logging.Logger;
+
import java.io.File;
import java.io.IOException;
-import org.apache.commons.io.FileUtils;
-import org.jboss.logging.Logger;
/**
*
@@ -14,20 +15,30 @@ public class LogChecker {
private static final Logger log = Logger.getLogger(LogChecker.class);
+ private static final String[] IGNORED = new String[] { ".*Jetty ALPN support not found.*" };
+
public static void checkServerLog(File logFile) throws IOException {
log.info(String.format("Checking server log: '%s'", logFile.getAbsolutePath()));
- String logContent = FileUtils.readFileToString(logFile);
+ String[] logContent = FileUtils.readFileToString(logFile).split("\n");
- boolean containsError
- = logContent.contains("ERROR")
- || logContent.contains("SEVERE")
- || logContent.contains("Exception ");
-
- //There is expected string "Exception" in server log: Adding provider
- //singleton org.keycloak.services.resources.ModelExceptionMapper
- if (containsError) {
- throw new RuntimeException(String.format("Server log file contains ERROR: '%s'", logFile.getPath()));
+ for (String log : logContent) {
+ boolean containsError = log.contains("ERROR") || log.contains("SEVERE") || log.contains("Exception ");
+ //There is expected string "Exception" in server log: Adding provider
+ //singleton org.keycloak.services.resources.ModelExceptionMapper
+ if (containsError) {
+ boolean ignore = false;
+ for (String i : IGNORED) {
+ if (log.matches(i)) {
+ ignore = true;
+ break;
+ }
+ }
+ if (!ignore) {
+ throw new RuntimeException(String.format("Server log file contains ERROR: '%s'", log));
+ }
+ }
}
+
}
public static void checkJBossServerLog(String jbossHome) throws IOException {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Timer.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Timer.java
index 1b2ec652b1..082a2a400b 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Timer.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Timer.java
@@ -14,15 +14,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.keycloak.testsuite.util;
-import java.text.MessageFormat;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.TreeMap;
+import org.apache.commons.io.IOUtils;
+import org.jboss.logging.Logger;
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartUtilities;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import static org.keycloak.testsuite.util.IOUtil.PROJECT_BUILD_DIRECTORY;
+import static org.jgroups.util.Util.assertTrue;
/**
*
@@ -30,45 +43,129 @@ import java.util.Map;
*/
public class Timer {
- private static Long time;
+ public static final Timer DEFAULT = new Timer();
- private static final Map> stats = new HashMap<>();
+ protected final Logger log = Logger.getLogger(Timer.class);
- public static void time() {
- time = new Date().getTime();
+ protected static final File DATA_DIR = new File(PROJECT_BUILD_DIRECTORY, "stats/data");
+ protected static final File CHARTS_DIR = new File(PROJECT_BUILD_DIRECTORY, "stats/charts");
+
+ public static final String DEFAULT_OPERATION = "DEFAULT_OPERATION";
+
+ private Long time;
+ private String operation = DEFAULT_OPERATION;
+ private final Map> stats = new TreeMap<>();
+
+ public long elapsedTime() {
+ long elapsedTime = 0;
+ if (time == null) {
+ } else {
+ elapsedTime = new Date().getTime() - time;
+ }
+ return elapsedTime;
}
- public static void time(String operation) {
- if (time == null) {
- System.out.println(MessageFormat.format("Starting timer for operation {0}", operation));
- time();
- } else {
- long timeOrig = time;
- time();
- logOperation(operation, time - timeOrig);
- System.out.println(MessageFormat.format("Operation {0} took {1} ms", operation, time - timeOrig));
+ public void reset() {
+ reset(operation); // log last operation
+ }
+
+ public void reset(String operation) {
+ reset(operation, true);
+ }
+
+ public void reset(String newOperation, boolean logOperationOnChange) {
+ if (time != null) {
+ if (operation.equals(newOperation) || logOperationOnChange) {
+ logOperation(operation, elapsedTime());
+ }
+ }
+ time = new Date().getTime();
+ if (!operation.equals(newOperation)) {
+ operation = newOperation;
+ log.info(String.format("Operation '%s' started.", newOperation));
}
}
- private static void logOperation(String operation, long delta) {
+ private void logOperation(String operation, long duration) {
if (!stats.containsKey(operation)) {
stats.put(operation, new ArrayList());
}
- stats.get(operation).add(delta);
+ stats.get(operation).add(duration);
+ log.info(String.format("Operation '%s' took: %s ms", operation, duration));
}
- public static void printStats() {
- if (!stats.isEmpty()) {
- System.out.println("OPERATION STATS:");
- }
- for (String op : stats.keySet()) {
- long sum = 0;
- for (Long t : stats.get(op)) {
- sum += t;
+ public void clearStats() {
+ clearStats(true, true, true);
+ }
+
+ public void clearStats(boolean logStats, boolean saveData, boolean saveCharts) {
+ if (logStats) {
+ log.info("Timer Statistics:");
+ for (String op : stats.keySet()) {
+ long sum = 0;
+ for (Long duration : stats.get(op)) {
+ sum += duration;
+ }
+ log.info(String.format("Operation '%s' average: %s ms", op, sum / stats.get(op).size()));
+ }
+ }
+ if (PROJECT_BUILD_DIRECTORY.exists()) {
+ DATA_DIR.mkdirs();
+ CHARTS_DIR.mkdirs();
+ for (String op : stats.keySet()) {
+ if (saveData) {
+ saveData(op);
+ }
+ if (saveCharts) {
+ saveChart(op);
+ }
}
- System.out.println(MessageFormat.format("Operation {0} average time: {1,number,#} ms", op, sum / stats.get(op).size()));
}
stats.clear();
}
+ private void saveData(String op) {
+ try {
+ File f = new File(DATA_DIR, op.replace(" ", "_") + ".txt");
+ if (!f.createNewFile()) {
+ throw new IOException("Couldn't create file: " + f);
+ }
+ OutputStream stream = new BufferedOutputStream(new FileOutputStream(f));
+ for (Long duration : stats.get(op)) {
+ IOUtils.write(duration.toString(), stream);
+ IOUtils.write("\n", stream);
+ }
+ stream.flush();
+ IOUtils.closeQuietly(stream);
+ } catch (IOException ex) {
+ log.error("Unable to save data for operation '" + op + "'", ex);
+ }
+ }
+
+ private void saveChart(String op) {
+ XYSeries series = new XYSeries(op);
+ int i = 0;
+ for (Long duration : stats.get(op)) {
+ series.add(++i, duration);
+ }
+ final XYSeriesCollection data = new XYSeriesCollection(series);
+ final JFreeChart chart = ChartFactory.createXYLineChart(
+ op,
+ "Operations",
+ "Duration (ms)",
+ data,
+ PlotOrientation.VERTICAL,
+ true,
+ true,
+ false
+ );
+ try {
+ ChartUtilities.saveChartAsPNG(
+ new File(CHARTS_DIR, op.replace(" ", "_") + ".png"),
+ chart, 640, 480);
+ } catch (IOException ex) {
+ log.warn("Unable to save chart for operation '" + op + "'.");
+ }
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
index 989b105fb6..5c3a906798 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
@@ -20,6 +20,7 @@ import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.keycloak.testsuite.arquillian.TestContext;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.ws.rs.NotFoundException;
@@ -33,12 +34,16 @@ import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.AuthenticationManagementResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RealmsResource;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.models.Constants;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
-import static org.keycloak.testsuite.admin.Users.setPasswordFor;
+import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.arquillian.SuiteContext;
import org.keycloak.testsuite.auth.page.WelcomePage;
@@ -52,8 +57,8 @@ import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
import org.keycloak.testsuite.auth.page.account.Account;
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
import org.keycloak.testsuite.auth.page.login.UpdatePassword;
-import org.keycloak.testsuite.util.Timer;
import org.keycloak.testsuite.util.WaitUtils;
+import static org.keycloak.testsuite.admin.Users.setPasswordFor;
/**
*
@@ -128,7 +133,6 @@ public abstract class AbstractKeycloakTest {
public void afterAbstractKeycloakTest() {
// removeTestRealms(); // keeping test realms after test to be able to inspect failures, instead deleting existing realms before import
// keycloak.close(); // keeping admin connection open
- Timer.printStats();
}
private void updateMasterAdminPassword() {
@@ -209,6 +213,65 @@ public abstract class AbstractKeycloakTest {
return adminClient.realms();
}
+ public void createRealm(String realm) {
+ try {
+ RealmResource realmResource = adminClient.realms().realm(realm);
+ // Throws NotFoundException in case the realm does not exist! Ugly but there
+ // does not seem to be a way to this just by asking.
+ RealmRepresentation realmRepresentation = realmResource.toRepresentation();
+ } catch (NotFoundException ex) {
+ RealmRepresentation realmRepresentation = new RealmRepresentation();
+ realmRepresentation.setRealm(realm);
+ realmRepresentation.setEnabled(true);
+ realmRepresentation.setRegistrationAllowed(true);
+ adminClient.realms().create(realmRepresentation);
+
+// List requiredActions = adminClient.realm(realm).flows().getRequiredActions();
+// for (RequiredActionProviderRepresentation a : requiredActions) {
+// a.setEnabled(false);
+// a.setDefaultAction(false);
+// adminClient.realm(realm).flows().updateRequiredAction(a.getAlias(), a);
+// }
+ }
+ }
+
+ public String createUser(String realm, String username, String password, String ... requiredActions) {
+ List requiredUserActions = Arrays.asList(requiredActions);
+
+ UserRepresentation homer = new UserRepresentation();
+ homer.setEnabled(true);
+ homer.setUsername(username);
+ homer.setRequiredActions(requiredUserActions);
+
+ return ApiUtil.createUserAndResetPasswordWithAdminClient(adminClient.realm(realm), homer, password);
+ }
+
+ public void setRequiredActionEnabled(String realm, String requiredAction, boolean enabled, boolean defaultAction) {
+ AuthenticationManagementResource managementResource = adminClient.realm(realm).flows();
+
+ RequiredActionProviderRepresentation action = managementResource.getRequiredAction(requiredAction);
+ action.setEnabled(enabled);
+ action.setDefaultAction(defaultAction);
+
+ managementResource.updateRequiredAction(requiredAction, action);
+ }
+
+ public void setRequiredActionEnabled(String realm, String userId, String requiredAction, boolean enabled) {
+ UsersResource usersResource = adminClient.realm(realm).users();
+
+ UserResource userResource = usersResource.get(userId);
+ UserRepresentation userRepresentation = userResource.toRepresentation();
+
+ List requiredActions = userRepresentation.getRequiredActions();
+ if (enabled && !requiredActions.contains(requiredAction)) {
+ requiredActions.add(requiredAction);
+ } else if (!enabled && requiredActions.contains(requiredAction)) {
+ requiredActions.remove(requiredAction);
+ }
+
+ userResource.update(userRepresentation);
+ }
+
private void loadConstantsProperties() throws ConfigurationException {
constantsProperties = new PropertiesConfiguration("test-constants.properties");
constantsProperties.setThrowExceptionOnMissing(true);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ChangePasswordTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ChangePasswordTest.java
index 8e85307f0a..ce1900d01f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ChangePasswordTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ChangePasswordTest.java
@@ -62,6 +62,9 @@ public class ChangePasswordTest extends AbstractAccountManagementTest {
testRealmChangePasswordPage.changePasswords(correctPassword, NEW_PASSWORD, NEW_PASSWORD + "-mismatch");
assertAlertError();
+
+ testRealmChangePasswordPage.changePasswords(correctPassword, " ", " ");
+ assertAlertError();
}
@Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/RegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/RegistrationTest.java
index a49d823202..1e3e916638 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/RegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/RegistrationTest.java
@@ -17,10 +17,13 @@
package org.keycloak.testsuite.account;
import org.jboss.arquillian.graphene.page.Page;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.keycloak.testsuite.auth.page.login.Registration;
-import static org.junit.Assert.*;
import org.junit.Before;
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
import org.keycloak.representations.idm.RealmRepresentation;
@@ -58,6 +61,7 @@ public class RegistrationTest extends AbstractAccountManagementTest {
setPasswordFor(newUser, PASSWORD);
testRealmAccountManagementPage.navigateTo();
+ assertTrue("Registration should be allowed.", testRealmResource().toRepresentation().isRegistrationAllowed());
testRealmLoginPage.form().register();
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java
old mode 100755
new mode 100644
index 00c0549bcb..83a4b0c223
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java
@@ -19,6 +19,7 @@ package org.keycloak.testsuite.account;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.AfterClass;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.representations.idm.RealmRepresentation;
@@ -61,6 +62,7 @@ public class ResetCredentialsTest extends AbstractAccountManagementTest {
}
testRealmAccountManagementPage.navigateTo();
+ assertTrue("Reset password should be allowed.", testRealmResource().toRepresentation().isResetPasswordAllowed());
testRealmLoginPage.form().forgotPassword();
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
index 5619885d85..fafbf2942b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
@@ -260,7 +260,7 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
resultList.get(0).findElement(By.xpath(".//td[text()='REVOKE_GRANT']"));
resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='account']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']"));
resultList.get(0).findElement(By.xpath(".//td[text()='revoked_client']/../td[text()='customer-portal']"));
loginEventsPage.table().reset();
@@ -272,7 +272,7 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']"));
resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']"));
resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='bburke@redhat.com']"));
resultList.get(0).findElement(By.xpath(".//td[text()='consent']/../td[text()='consent_granted']"));
}
@@ -317,7 +317,7 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
resultList.get(0).findElement(By.xpath(".//td[text()='LOGOUT']"));
resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']"));
loginEventsPage.table().reset();
loginEventsPage.table().filterForm().addEventType("LOGIN");
@@ -328,7 +328,7 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']"));
resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']"));
resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='bburke@redhat.com']"));
loginEventsPage.table().reset();
@@ -339,7 +339,7 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
assertEquals(1, resultList.size());
resultList.get(0).findElement(By.xpath(".//td[text()='CODE_TO_TOKEN']"));
resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']"));
resultList.get(0).findElement(By.xpath(".//td[text()='refresh_token_type']/../td[text()='Refresh']"));
String serverLogPath = null;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
old mode 100755
new mode 100644
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
index e07b02f213..62ac22681d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
@@ -27,6 +27,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
import org.keycloak.testsuite.adapter.page.*;
import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.auth.page.login.SAMLIDPInitiatedLogin;
import org.keycloak.testsuite.util.IOUtil;
import org.w3c.dom.Document;
@@ -81,6 +82,9 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
@Page
private SalesPostSigTransientServlet salesPostSigTransientServletPage;
+ @Page
+ private SAMLIDPInitiatedLogin samlidpInitiatedLogin;
+
@Deployment(name = BadClientSalesPostSigServlet.DEPLOYMENT_NAME)
protected static WebArchive badClientSalesPostSig() {
return samlServletDeployment(BadClientSalesPostSigServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
@@ -220,7 +224,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
assertCurrentUrlStartsWith(testRealmSAMLRedirectLoginPage);
salesPostPassiveServletPage.navigateTo();
- assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(""));
+ assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("") || driver.getPageSource().contains(""));
salesPostSigEmailServletPage.navigateTo();
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
@@ -367,7 +371,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
public void salesPostPassiveTest() {
salesPostPassiveServletPage.navigateTo();
//Different 403 status page on EAP and Wildfly
- assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(""));
+ assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("") || driver.getPageSource().contains(""));
salesPostServletPage.navigateTo();
testRealmSAMLRedirectLoginPage.form().login(bburkeUser);
@@ -378,7 +382,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
salesPostPassiveServletPage.logout();
salesPostPassiveServletPage.navigateTo();
//Different 403 status page on EAP and Wildfly
- assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(""));
+ assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("") || driver.getPageSource().contains(""));
salesPostServletPage.navigateTo();
testRealmSAMLRedirectLoginPage.form().login("unauthorized", "password");
@@ -458,4 +462,37 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
salesPostSigTransientServletPage.logout();
}
+
+ @Test
+ public void idpInitiatedLogin() {
+ samlidpInitiatedLogin.setAuthRealm(SAMLSERVLETDEMO);
+ samlidpInitiatedLogin.setUrlName("employee2");
+ samlidpInitiatedLogin.navigateTo();
+ samlidpInitiatedLogin.form().login(bburkeUser);
+
+ employee2ServletPage.navigateTo();
+ assertTrue(driver.getPageSource().contains("principal=bburke"));
+
+ salesPostSigServletPage.navigateTo();
+ assertTrue(driver.getPageSource().contains("principal=bburke"));
+
+ employee2ServletPage.logout();
+ }
+
+ @Test
+ public void idpInitiatedUnauthorizedLoginTest() {
+ samlidpInitiatedLogin.setAuthRealm(SAMLSERVLETDEMO);
+ samlidpInitiatedLogin.setUrlName("employee2");
+ samlidpInitiatedLogin.navigateTo();
+ samlidpInitiatedLogin.form().login("unauthorized","password");
+
+ assertFalse(driver.getPageSource().contains("principal="));
+ assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
+
+ employee2ServletPage.navigateTo();
+ assertFalse(driver.getPageSource().contains("principal="));
+ assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
+
+ employee2ServletPage.logout();
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
index a027234c2b..0bce8a6780 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
@@ -23,22 +23,29 @@ import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.After;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.adapter.page.SessionPortal;
+
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
+
import org.keycloak.testsuite.auth.page.account.Sessions;
import org.keycloak.testsuite.auth.page.login.Login;
+
import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
+
import org.keycloak.testsuite.util.SecondBrowser;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
@@ -156,9 +163,11 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
public void testAdminApplicationLogout() {
// login as bburke
loginAndCheckSession(driver, testRealmLoginPage);
+
// logout mposolda with admin client
- findClientResourceByClientId(testRealmResource(), "session-portal")
- .logoutUser("mposolda");
+ UserRepresentation mposolda = testRealmResource().users().search("mposolda", null, null, null, null, null).get(0);
+ testRealmResource().users().get(mposolda.getId()).logout();
+
// bburke should be still logged with original httpSession in our browser window
sessionPortalPage.navigateTo();
assertEquals(driver.getCurrentUrl(), sessionPortalPage.toString());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java
index 70013c57a8..fb7e966156 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java
@@ -21,6 +21,7 @@ import org.junit.Assert;
import org.junit.Before;
import org.keycloak.admin.client.resource.AuthenticationManagementResource;
import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
@@ -92,14 +93,41 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
Assert.assertEquals("Execution requirement choices - " + actual.getProviderId(), expected.getRequirementChoices(), actual.getRequirementChoices());
}
+ void compareExecution(AuthenticationExecutionExportRepresentation expected, AuthenticationExecutionExportRepresentation actual) {
+ Assert.assertEquals("Execution flowAlias - " + actual.getAuthenticator(), expected.getFlowAlias(), actual.getFlowAlias());
+ Assert.assertEquals("Execution authenticator - " + actual.getAuthenticator(), expected.getAuthenticator(), actual.getAuthenticator());
+ Assert.assertEquals("Execution userSetupAllowed - " + actual.getAuthenticator(), expected.isUserSetupAllowed(), actual.isUserSetupAllowed());
+ Assert.assertEquals("Execution authenticatorFlow - " + actual.getAuthenticator(), expected.isAutheticatorFlow(), actual.isAutheticatorFlow());
+ Assert.assertEquals("Execution authenticatorConfig - " + actual.getAuthenticator(), expected.getAuthenticatorConfig(), actual.getAuthenticatorConfig());
+ Assert.assertEquals("Execution priority - " + actual.getAuthenticator(), expected.getPriority(), actual.getPriority());
+ Assert.assertEquals("Execution requirement - " + actual.getAuthenticator(), expected.getRequirement(), actual.getRequirement());
+ }
+
+ void compareExecutions(List expected, List actual) {
+ Assert.assertNotNull("Executions should not be null", actual);
+ Assert.assertEquals("Size", expected.size(), actual.size());
+
+ for (int i = 0; i < expected.size(); i++) {
+ compareExecution(expected.get(i), actual.get(i));
+ }
+ }
+
void compareFlows(AuthenticationFlowRepresentation expected, AuthenticationFlowRepresentation actual) {
Assert.assertEquals("Flow alias", expected.getAlias(), actual.getAlias());
Assert.assertEquals("Flow description", expected.getDescription(), actual.getDescription());
Assert.assertEquals("Flow providerId", expected.getProviderId(), actual.getProviderId());
Assert.assertEquals("Flow top level", expected.isTopLevel(), actual.isTopLevel());
Assert.assertEquals("Flow built-in", expected.isBuiltIn(), actual.isBuiltIn());
- }
+ List expectedExecs = expected.getAuthenticationExecutions();
+ List actualExecs = actual.getAuthenticationExecutions();
+
+ if (expectedExecs == null) {
+ Assert.assertTrue("Executions should be null or empty", actualExecs == null || actualExecs.size() == 0);
+ } else {
+ compareExecutions(expectedExecs, actualExecs);
+ }
+ }
AuthenticationFlowRepresentation newFlow(String alias, String description,
String providerId, boolean topLevel, boolean builtIn) {
@@ -112,8 +140,8 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
return flow;
}
- AuthenticationExecutionInfoRepresentation newExecution(String displayName, String providerId, Boolean configurable,
- int level, int index, String requirement, Boolean authFlow, String[] choices) {
+ AuthenticationExecutionInfoRepresentation newExecInfo(String displayName, String providerId, Boolean configurable,
+ int level, int index, String requirement, Boolean authFlow, String[] choices) {
AuthenticationExecutionInfoRepresentation execution = new AuthenticationExecutionInfoRepresentation();
execution.setRequirement(requirement);
@@ -129,6 +157,12 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
return execution;
}
+ void addExecInfo(List target, String displayName, String providerId, Boolean configurable,
+ int level, int index, String requirement, Boolean authFlow, String[] choices) {
+
+ AuthenticationExecutionInfoRepresentation exec = newExecInfo(displayName, providerId, configurable, level, index, requirement, authFlow, choices);
+ target.add(exec);
+ }
AuthenticatorConfigRepresentation newConfig(String alias, String[] keyvalues) {
AuthenticatorConfigRepresentation config = new AuthenticatorConfigRepresentation();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java
index f9c0bba714..afe702f033 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java
@@ -83,7 +83,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
response.close();
}
- compareExecution(newExecution("Review Profile", "idp-review-profile", true, 0, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec);
+ compareExecution(newExecInfo("Review Profile", "idp-review-profile", true, 0, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec);
// remove execution
authMgmtResource.removeExecution(exec.getId());
@@ -143,7 +143,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
// Note: there is no checking in addExecution if requirement is one of requirementChoices
// Thus we can have OPTIONAL which is neither ALTERNATIVE, nor DISABLED
- compareExecution(newExecution("Cookie", "auth-cookie", false, 0, 2, OPTIONAL, null, new String[]{ALTERNATIVE, DISABLED}), exec);
+ compareExecution(newExecInfo("Cookie", "auth-cookie", false, 0, 2, OPTIONAL, null, new String[]{ALTERNATIVE, DISABLED}), exec);
}
@Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java
index 63c262ab0e..d381beca67 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java
@@ -19,12 +19,15 @@ package org.keycloak.testsuite.admin.authentication;
import org.junit.Assert;
import org.junit.Test;
+import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
+import org.keycloak.testsuite.admin.ApiUtil;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.core.Response;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* @author Marko Strukelj
@@ -62,13 +65,52 @@ public class FlowTest extends AbstractAuthenticationTest {
response.close();
}
- // check that new flow is returned
+ // check that new flow is returned in a children list
flows = authMgmtResource.getFlows();
AuthenticationFlowRepresentation found = findFlowByAlias("browser-2", flows);
- Assert.assertNotNull("created flow visible", found);
+ Assert.assertNotNull("created flow visible in parent", found);
compareFlows(newFlow, found);
+ // check that new flow is returned individually
+ AuthenticationFlowRepresentation found2 = authMgmtResource.getFlow(found.getId());
+ Assert.assertNotNull("created flow visible directly", found2);
+ compareFlows(newFlow, found2);
+
+
+ // add execution flow using a different method
+ Map data = new HashMap<>();
+ data.put("alias", "SomeFlow");
+ data.put("type", "basic-flow");
+ data.put("description", "Test flow");
+ data.put("provider", "registration-page-form");
+
+ try {
+ authMgmtResource.addExecutionFlow("inexistent-parent-flow-alias", data);
+ Assert.fail("addExecutionFlow for inexistent parent should have failed");
+ } catch (Exception expected) {
+ }
+
+ authMgmtResource.addExecutionFlow("browser-2", data);
+
+ // check that new flow is returned in a children list
+ flows = authMgmtResource.getFlows();
+ found2 = findFlowByAlias("browser-2", flows);
+ Assert.assertNotNull("created flow visible in parent", found2);
+
+ List execs = found2.getAuthenticationExecutions();
+ Assert.assertNotNull(execs);
+ Assert.assertEquals("Size one", 1, execs.size());
+
+ AuthenticationExecutionExportRepresentation expected = new AuthenticationExecutionExportRepresentation();
+ expected.setFlowAlias("SomeFlow");
+ expected.setUserSetupAllowed(false);
+ expected.setAuthenticator("registration-page-form");
+ expected.setAutheticatorFlow(true);
+ expected.setRequirement("DISABLED");
+ expected.setPriority(0);
+ compareExecution(expected, execs.get(0));
+
// delete non-built-in flow
authMgmtResource.deleteFlow(found.getId());
@@ -122,7 +164,31 @@ public class FlowTest extends AbstractAuthenticationTest {
// adjust expected values before comparing
browser.setAlias("Copy of browser");
browser.setBuiltIn(false);
+ browser.getAuthenticationExecutions().get(2).setFlowAlias("Copy of browser forms");
+ compareFlows(browser, copyOfBrowser);
+
+ // get new flow directly and compare
+ copyOfBrowser = authMgmtResource.getFlow(copyOfBrowser.getId());
+ Assert.assertNotNull(copyOfBrowser);
compareFlows(browser, copyOfBrowser);
}
+ @Test
+ // KEYCLOAK-2580
+ public void addExecutionFlow() {
+ HashMap params = new HashMap<>();
+ params.put("newName", "parent");
+ Response response = authMgmtResource.copy("browser", params);
+ Assert.assertEquals(201, response.getStatus());
+ response.close();
+
+ params = new HashMap<>();
+ params.put("alias", "child");
+ params.put("description", "Description");
+ params.put("provider", "registration-page-form");
+ params.put("type", "basic-flow");
+
+ authMgmtResource.addExecutionFlow("parent", params);
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java
index 1f42069adf..45f538fbd3 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java
@@ -19,6 +19,7 @@ package org.keycloak.testsuite.admin.authentication;
import org.junit.Assert;
import org.junit.Test;
+import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
@@ -81,12 +82,12 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
FlowExecutions fe2 = it2.next();
compareFlows(fe1.flow, fe2.flow);
- compareExecutions(fe1.executions, fe2.executions);
+ compareExecutionsInfo(fe1.executions, fe2.executions);
}
}
- private void compareExecutions(List expected, List actual) {
+ private void compareExecutionsInfo(List expected, List actual) {
Assert.assertEquals("Executions count", expected.size(), actual.size());
Iterator it1 = expected.iterator();
Iterator it2 = actual.iterator();
@@ -124,66 +125,117 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
LinkedList expected = new LinkedList<>();
AuthenticationFlowRepresentation flow = newFlow("browser", "browser based authentication", "basic-flow", true, true);
- List executions = new LinkedList<>();
- executions.add(newExecution("Cookie", "auth-cookie", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}));
- executions.add(newExecution("Kerberos", "auth-spnego", false, 0, 1, DISABLED, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
- executions.add(newExecution("forms", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
- executions.add(newExecution("Username Password Form", "auth-username-password-form", false, 1, 0, REQUIRED, null, new String[]{REQUIRED}));
- executions.add(newExecution("OTP Form", "auth-otp-form", false, 1, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
- expected.add(new FlowExecutions(flow, executions));
+ addExecExport(flow, null, false, "auth-cookie", false, null, ALTERNATIVE, 10);
+ addExecExport(flow, null, false, "auth-spnego", false, null, DISABLED, 20);
+ addExecExport(flow, "forms", false, null, true, null, ALTERNATIVE, 30);
+
+ List execs = new LinkedList<>();
+ addExecInfo(execs, "Cookie", "auth-cookie", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
+ addExecInfo(execs, "Kerberos", "auth-spnego", false, 0, 1, DISABLED, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
+ addExecInfo(execs, "forms", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
+ addExecInfo(execs, "Username Password Form", "auth-username-password-form", false, 1, 0, REQUIRED, null, new String[]{REQUIRED});
+ addExecInfo(execs, "OTP Form", "auth-otp-form", false, 1, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
+ expected.add(new FlowExecutions(flow, execs));
flow = newFlow("clients", "Base authentication for clients", "client-flow", true, true);
- executions = new LinkedList<>();
- executions.add(newExecution("Client Id and Secret", "client-secret", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}));
- executions.add(newExecution("Signed Jwt", "client-jwt", false, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}));
- expected.add(new FlowExecutions(flow, executions));
+ addExecExport(flow, null, false, "client-secret", false, null, ALTERNATIVE, 10);
+ addExecExport(flow, null, false, "client-jwt", false, null, ALTERNATIVE, 20);
+
+ execs = new LinkedList<>();
+ addExecInfo(execs, "Client Id and Secret", "client-secret", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
+ addExecInfo(execs, "Signed Jwt", "client-jwt", false, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
+ expected.add(new FlowExecutions(flow, execs));
flow = newFlow("direct grant", "OpenID Connect Resource Owner Grant", "basic-flow", true, true);
- executions = new LinkedList<>();
- executions.add(newExecution("Username Validation", "direct-grant-validate-username", false, 0, 0, REQUIRED, null, new String[]{REQUIRED}));
- executions.add(newExecution("Password", "direct-grant-validate-password", false, 0, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
- executions.add(newExecution("OTP", "direct-grant-validate-otp", false, 0, 2, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
- expected.add(new FlowExecutions(flow, executions));
+ addExecExport(flow, null, false, "direct-grant-validate-username", false, null, REQUIRED, 10);
+ addExecExport(flow, null, false, "direct-grant-validate-password", false, null, REQUIRED, 20);
+ addExecExport(flow, null, false, "direct-grant-validate-otp", false, null, OPTIONAL, 30);
+
+ execs = new LinkedList<>();
+ addExecInfo(execs, "Username Validation", "direct-grant-validate-username", false, 0, 0, REQUIRED, null, new String[]{REQUIRED});
+ addExecInfo(execs, "Password", "direct-grant-validate-password", false, 0, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED});
+ addExecInfo(execs, "OTP", "direct-grant-validate-otp", false, 0, 2, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
+ expected.add(new FlowExecutions(flow, execs));
flow = newFlow("first broker login", "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
"basic-flow", true, true);
- executions = new LinkedList<>();
- executions.add(newExecution("Review Profile", "idp-review-profile", true, 0, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
- executions.add(newExecution("Create User If Unique", "idp-create-user-if-unique", true, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
- executions.add(newExecution("Handle Existing Account", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
- executions.add(newExecution("Confirm link existing account", "idp-confirm-link", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
- executions.add(newExecution("Verify existing account by Email", "idp-email-verification", false, 1, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
- executions.add(newExecution("Verify Existing Account by Re-authentication", null, false, 1, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
- executions.add(newExecution("Username Password Form for identity provider reauthentication", "idp-username-password-form", false, 2, 0, REQUIRED, null, new String[]{REQUIRED}));
- executions.add(newExecution("OTP Form", "auth-otp-form", false, 2, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
- expected.add(new FlowExecutions(flow, executions));
+ addExecExport(flow, null, false, "idp-review-profile", false, "review profile config", REQUIRED, 10);
+ addExecExport(flow, null, false, "idp-create-user-if-unique", false, "create unique user config", ALTERNATIVE, 20);
+ addExecExport(flow, "Handle Existing Account", false, null, true, null, ALTERNATIVE, 30);
+
+ execs = new LinkedList<>();
+ addExecInfo(execs, "Review Profile", "idp-review-profile", true, 0, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED});
+ addExecInfo(execs, "Create User If Unique", "idp-create-user-if-unique", true, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
+ addExecInfo(execs, "Handle Existing Account", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
+ addExecInfo(execs, "Confirm link existing account", "idp-confirm-link", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED});
+ addExecInfo(execs, "Verify existing account by Email", "idp-email-verification", false, 1, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
+ addExecInfo(execs, "Verify Existing Account by Re-authentication", null, false, 1, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
+ addExecInfo(execs, "Username Password Form for identity provider reauthentication", "idp-username-password-form", false, 2, 0, REQUIRED, null, new String[]{REQUIRED});
+ addExecInfo(execs, "OTP Form", "auth-otp-form", false, 2, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
+ expected.add(new FlowExecutions(flow, execs));
flow = newFlow("registration", "registration flow", "basic-flow", true, true);
- executions = new LinkedList<>();
- executions.add(newExecution("registration form", "registration-page-form", false, 0, 0, REQUIRED, true, new String[]{REQUIRED, DISABLED}));
- executions.add(newExecution("Registration User Creation", "registration-user-creation", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
- executions.add(newExecution("Profile Validation", "registration-profile-action", false, 1, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
- executions.add(newExecution("Password Validation", "registration-password-action", false, 1, 2, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
- executions.add(newExecution("Recaptcha", "registration-recaptcha-action", true, 1, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}));
- expected.add(new FlowExecutions(flow, executions));
+ addExecExport(flow, "registration form", false, "registration-page-form", true, null, REQUIRED, 10);
+
+ execs = new LinkedList<>();
+ addExecInfo(execs, "registration form", "registration-page-form", false, 0, 0, REQUIRED, true, new String[]{REQUIRED, DISABLED});
+ addExecInfo(execs, "Registration User Creation", "registration-user-creation", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED});
+ addExecInfo(execs, "Profile Validation", "registration-profile-action", false, 1, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED});
+ addExecInfo(execs, "Password Validation", "registration-password-action", false, 1, 2, REQUIRED, null, new String[]{REQUIRED, DISABLED});
+ addExecInfo(execs, "Recaptcha", "registration-recaptcha-action", true, 1, 3, DISABLED, null, new String[]{REQUIRED, DISABLED});
+ expected.add(new FlowExecutions(flow, execs));
flow = newFlow("reset credentials", "Reset credentials for a user if they forgot their password or something", "basic-flow", true, true);
- executions = new LinkedList<>();
- executions.add(newExecution("Choose User", "reset-credentials-choose-user", false, 0, 0, REQUIRED, null, new String[]{REQUIRED}));
- executions.add(newExecution("Send Reset Email", "reset-credential-email", false, 0, 1, REQUIRED, null, new String[]{REQUIRED}));
- executions.add(newExecution("Reset Password", "reset-password", false, 0, 2, REQUIRED, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
- executions.add(newExecution("Reset OTP", "reset-otp", false, 0, 3, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
- expected.add(new FlowExecutions(flow, executions));
+ addExecExport(flow, null, false, "reset-credentials-choose-user", false, null, REQUIRED, 10);
+ addExecExport(flow, null, false, "reset-credential-email", false, null, REQUIRED, 20);
+ addExecExport(flow, null, false, "reset-password", false, null, REQUIRED, 30);
+ addExecExport(flow, null, false, "reset-otp", false, null, OPTIONAL, 40);
+
+ execs = new LinkedList<>();
+ addExecInfo(execs, "Choose User", "reset-credentials-choose-user", false, 0, 0, REQUIRED, null, new String[]{REQUIRED});
+ addExecInfo(execs, "Send Reset Email", "reset-credential-email", false, 0, 1, REQUIRED, null, new String[]{REQUIRED});
+ addExecInfo(execs, "Reset Password", "reset-password", false, 0, 2, REQUIRED, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
+ addExecInfo(execs, "Reset OTP", "reset-otp", false, 0, 3, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
+ expected.add(new FlowExecutions(flow, execs));
flow = newFlow("saml ecp", "SAML ECP Profile Authentication Flow", "basic-flow", true, true);
- executions = new LinkedList<>();
- executions.add(newExecution(null, "http-basic-authenticator", false, 0, 0, REQUIRED, null, new String[]{}));
- expected.add(new FlowExecutions(flow, executions));
+ addExecExport(flow, null, false, "http-basic-authenticator", false, null, REQUIRED, 10);
+
+ execs = new LinkedList<>();
+ addExecInfo(execs, null, "http-basic-authenticator", false, 0, 0, REQUIRED, null, new String[]{});
+ expected.add(new FlowExecutions(flow, execs));
return expected;
}
- static class FlowExecutions implements Comparable {
+ private void addExecExport(AuthenticationFlowRepresentation flow, String flowAlias, boolean userSetupAllowed,
+ String authenticator, boolean authenticatorFlow, String authenticatorConfig,
+ String requirement, int priority) {
+
+ AuthenticationExecutionExportRepresentation rep = newExecutionExportRepresentation(flowAlias, userSetupAllowed,
+ authenticator, authenticatorFlow, authenticatorConfig, requirement, priority);
+
+ List execs = flow.getAuthenticationExecutions();
+ if (execs == null) {
+ execs = new ArrayList<>();
+ flow.setAuthenticationExecutions(execs);
+ }
+ execs.add(rep);
+ }
+
+ private AuthenticationExecutionExportRepresentation newExecutionExportRepresentation(String flowAlias, boolean userSetupAllowed, String authenticator, boolean authenticatorFlow, String authenticatorConfig, String requirement, int priority) {
+ AuthenticationExecutionExportRepresentation rep = new AuthenticationExecutionExportRepresentation();
+ rep.setFlowAlias(flowAlias);
+ rep.setUserSetupAllowed(userSetupAllowed);
+ rep.setAuthenticator(authenticator);
+ rep.setAutheticatorFlow(authenticatorFlow);
+ rep.setAuthenticatorConfig(authenticatorConfig);
+ rep.setRequirement(requirement);
+ rep.setPriority(priority);
+ return rep;
+ }
+
+ private static class FlowExecutions implements Comparable {
AuthenticationFlowRepresentation flow;
List executions;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialProvidersTest.java
deleted file mode 100644
index 2291bb9a80..0000000000
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialProvidersTest.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2016 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.testsuite.admin.authentication;
-
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-/**
- * @author Marko Strukelj
- */
-public class InitialProvidersTest extends AbstractAuthenticationTest {
-
- @Test
- public void testAuthenticationProvidersList() {
-
- List