keycloak-memoizeit
Changes
adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java 36(+36 -0)
adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java 2(+2 -0)
adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java 17(+15 -2)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java 4(+3 -1)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java 3(+3 -0)
adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java 36(+36 -0)
adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java 5(+3 -2)
distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml 1(+0 -1)
distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml 1(+0 -1)
distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml 1(+0 -1)
distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml 1(+0 -1)
distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml 1(+0 -1)
distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml 1(+0 -1)
distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-common/main/module.xml 1(+0 -1)
distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-core/main/module.xml 1(+0 -1)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml 1(+0 -1)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-core/main/module.xml 1(+0 -1)
distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml 1(+0 -1)
distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml 1(+0 -1)
federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java 2(+1 -1)
federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosServerSubjectAuthenticator.java 41(+20 -21)
federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java 21(+7 -14)
federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java 22(+20 -2)
integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java 34(+17 -17)
testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java 6(+6 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java 4(+4 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/InputServlet.java 13(+13 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java 6(+6 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java 12(+12 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/Login.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/SAMLIDPInitiatedLogin.java 19(+19 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java 8(+7 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java 2(+2 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Timer.java 147(+122 -25)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java 5(+3 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ChangePasswordTest.java 3(+3 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java 0(+0 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java 0(+0 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java 20(+20 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/AbstractUserTest.java 52(+52 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json 3(+2 -1)
testsuite/integration-arquillian/tests/other/console_no_users/src/test/java/org/keycloak/testsuite/console/pages/WelcomePageTest.java 112(+112 -0)
Details
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 f8d7278..56d8f51 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/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 8a3010d..70a67de 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/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 7734c2d..0a07e9e 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/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
index 63c27d7..5db3ead 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 de57268..e578f85 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;
@@ -101,6 +102,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 +113,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/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 07f2a40..eb17fee 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/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 51bdb4b..aa75439 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/common/src/main/java/org/keycloak/common/constants/KerberosConstants.java b/common/src/main/java/org/keycloak/common/constants/KerberosConstants.java
index 4089a0e..b644f16 100644
--- a/common/src/main/java/org/keycloak/common/constants/KerberosConstants.java
+++ b/common/src/main/java/org/keycloak/common/constants/KerberosConstants.java
@@ -17,6 +17,9 @@
package org.keycloak.common.constants;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.Oid;
+
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@@ -31,19 +34,33 @@ public class KerberosConstants {
/**
* OID of SPNEGO mechanism. See http://www.oid-info.com/get/1.3.6.1.5.5.2
*/
- public static final String SPNEGO_OID = "1.3.6.1.5.5.2";
+ private static final String SPNEGO_OID_STR = "1.3.6.1.5.5.2";
+ public static final Oid SPNEGO_OID;
/**
* OID of Kerberos v5 mechanism. See http://www.oid-info.com/get/1.2.840.113554.1.2.2
*/
- public static final String KRB5_OID = "1.2.840.113554.1.2.2";
+ private static final String KRB5_OID_STR = "1.2.840.113554.1.2.2";
+ public static final Oid KRB5_OID;
/**
* OID of Kerberos v5 name. See http://www.oid-info.com/get/1.2.840.113554.1.2.2.1
*/
- public static final String KRB5_NAME_OID = "1.2.840.113554.1.2.2.1";
+ private static final String KRB5_NAME_OID_STR = "1.2.840.113554.1.2.2.1";
+ public static final Oid KRB5_NAME_OID;
+
+
+ static {
+ try {
+ KRB5_OID = new Oid(KerberosConstants.KRB5_OID_STR);
+ KRB5_NAME_OID = new Oid(KerberosConstants.KRB5_NAME_OID_STR);
+ SPNEGO_OID = new Oid(KerberosConstants.SPNEGO_OID_STR);
+ } catch (GSSException e) {
+ throw new RuntimeException(e);
+ }
+ }
/**
diff --git a/common/src/main/java/org/keycloak/common/util/KerberosJdkProvider.java b/common/src/main/java/org/keycloak/common/util/KerberosJdkProvider.java
new file mode 100644
index 0000000..00a5f12
--- /dev/null
+++ b/common/src/main/java/org/keycloak/common/util/KerberosJdkProvider.java
@@ -0,0 +1,227 @@
+/*
+ * 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.common.util;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.kerberos.KerberosTicket;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.keycloak.common.constants.KerberosConstants;
+
+/**
+ * Provides abstraction to handle differences between various JDK vendors (Sun, IBM)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class KerberosJdkProvider {
+
+ public abstract Configuration createJaasConfigurationForServer(String keytab, String serverPrincipal, boolean debug);
+ public abstract Configuration createJaasConfigurationForUsernamePasswordLogin(boolean debug);
+
+ public abstract KerberosTicket gssCredentialToKerberosTicket(KerberosTicket kerberosTicket, GSSCredential gssCredential);
+
+
+
+ public GSSCredential kerberosTicketToGSSCredential(KerberosTicket kerberosTicket) {
+ return kerberosTicketToGSSCredential(kerberosTicket, GSSCredential.DEFAULT_LIFETIME, GSSCredential.INITIATE_ONLY);
+ }
+
+ // Actually same on both JDKs
+ public GSSCredential kerberosTicketToGSSCredential(KerberosTicket kerberosTicket, final int lifetime, final int usage) {
+ try {
+ final GSSManager gssManager = GSSManager.getInstance();
+
+ KerberosPrincipal kerberosPrincipal = kerberosTicket.getClient();
+ String krbPrincipalName = kerberosTicket.getClient().getName();
+ final GSSName gssName = gssManager.createName(krbPrincipalName, KerberosConstants.KRB5_NAME_OID);
+
+ Set<KerberosPrincipal> principals = Collections.singleton(kerberosPrincipal);
+ Set<GSSName> publicCreds = Collections.singleton(gssName);
+ Set<KerberosTicket> privateCreds = Collections.singleton(kerberosTicket);
+ Subject subject = new Subject(false, principals, publicCreds, privateCreds);
+
+ return Subject.doAs(subject, new PrivilegedExceptionAction<GSSCredential>() {
+
+ @Override
+ public GSSCredential run() throws Exception {
+ return gssManager.createCredential(gssName, lifetime, KerberosConstants.KRB5_OID, usage);
+ }
+
+ });
+ } catch (Exception e) {
+ throw new KerberosSerializationUtils.KerberosSerializationException("Unexpected exception during convert KerberosTicket to GSSCredential", e);
+ }
+ }
+
+
+ public static KerberosJdkProvider getProvider() {
+ if (KerberosSerializationUtils.JAVA_INFO.contains("IBM")) {
+ return new IBMJDKProvider();
+ } else {
+ return new SunJDKProvider();
+ }
+ }
+
+
+ // IMPL Subclasses
+
+
+ // Works for Oracle and OpenJDK
+ private static class SunJDKProvider extends KerberosJdkProvider {
+
+
+ @Override
+ public Configuration createJaasConfigurationForServer(final String keytab, final String serverPrincipal, final boolean debug) {
+ return new Configuration() {
+
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ Map<String, Object> options = new HashMap<>();
+ options.put("storeKey", "true");
+ options.put("doNotPrompt", "true");
+ options.put("isInitiator", "false");
+ options.put("useKeyTab", "true");
+
+ options.put("keyTab", keytab);
+ options.put("principal", serverPrincipal);
+ options.put("debug", String.valueOf(debug));
+ AppConfigurationEntry kerberosLMConfiguration = new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
+ return new AppConfigurationEntry[] { kerberosLMConfiguration };
+ }
+ };
+ }
+
+
+ @Override
+ public Configuration createJaasConfigurationForUsernamePasswordLogin(final boolean debug) {
+ return new Configuration() {
+
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ Map<String, Object> options = new HashMap<>();
+ options.put("storeKey", "true");
+ options.put("debug", String.valueOf(debug));
+ AppConfigurationEntry kerberosLMConfiguration = new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
+ return new AppConfigurationEntry[] { kerberosLMConfiguration };
+ }
+ };
+ }
+
+
+ // Note: input kerberosTicket is null for Sun based JDKs
+ @Override
+ public KerberosTicket gssCredentialToKerberosTicket(KerberosTicket kerberosTicket, GSSCredential gssCredential) {
+ try {
+ Class<?> gssUtil = Class.forName("com.sun.security.jgss.GSSUtil");
+ Method createSubject = gssUtil.getMethod("createSubject", GSSName.class, GSSCredential.class);
+ Subject subject = (Subject) createSubject.invoke(null, null, gssCredential);
+ Set<KerberosTicket> kerberosTickets = subject.getPrivateCredentials(KerberosTicket.class);
+ Iterator<KerberosTicket> iterator = kerberosTickets.iterator();
+ if (iterator.hasNext()) {
+ return iterator.next();
+ } else {
+ throw new KerberosSerializationUtils.KerberosSerializationException("Not available kerberosTicket in subject credentials. Subject was: " + subject.toString());
+ }
+ } catch (KerberosSerializationUtils.KerberosSerializationException ke) {
+ throw ke;
+ } catch (Exception e) {
+ throw new KerberosSerializationUtils.KerberosSerializationException("Unexpected error during convert GSSCredential to KerberosTicket", e);
+ }
+ }
+
+ }
+
+
+ // Works for IBM JDK
+ private static class IBMJDKProvider extends KerberosJdkProvider {
+
+ @Override
+ public Configuration createJaasConfigurationForServer(String keytab, final String serverPrincipal, final boolean debug) {
+ final String keytabUrl = getKeytabURL(keytab);
+
+ return new Configuration() {
+
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ Map<String, Object> options = new HashMap<>();
+ options.put("noAddress", "true");
+ options.put("credsType","acceptor");
+ options.put("useKeytab", keytabUrl);
+ options.put("principal", serverPrincipal);
+ options.put("debug", String.valueOf(debug));
+
+ AppConfigurationEntry kerberosLMConfiguration = new AppConfigurationEntry("com.ibm.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
+ return new AppConfigurationEntry[] { kerberosLMConfiguration };
+ }
+ };
+ }
+
+ private String getKeytabURL(String keytab) {
+ try {
+ return new File(keytab).toURI().toURL().toString();
+ } catch (MalformedURLException mfe) {
+ System.err.println("Invalid keytab location specified in configuration: " + keytab);
+ mfe.printStackTrace();
+ return keytab;
+ }
+ }
+
+
+ @Override
+ public Configuration createJaasConfigurationForUsernamePasswordLogin(final boolean debug) {
+ return new Configuration() {
+
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ Map<String, Object> options = new HashMap<>();
+ options.put("credsType","initiator");
+ options.put("noAddress", "true");
+ options.put("debug", String.valueOf(debug));
+ AppConfigurationEntry kerberosLMConfiguration = new AppConfigurationEntry("com.ibm.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
+ return new AppConfigurationEntry[] { kerberosLMConfiguration };
+ }
+ };
+ }
+
+
+ // For IBM, kerberosTicket was set on JAAS Subject, so we can just return it
+ @Override
+ public KerberosTicket gssCredentialToKerberosTicket(KerberosTicket kerberosTicket, GSSCredential gssCredential) {
+ if (kerberosTicket == null) {
+ throw new KerberosSerializationUtils.KerberosSerializationException("Not available kerberosTicket in subject credentials in IBM JDK");
+ } else {
+ return kerberosTicket;
+ }
+ }
+ }
+}
diff --git a/common/src/main/java/org/keycloak/common/util/KerberosSerializationUtils.java b/common/src/main/java/org/keycloak/common/util/KerberosSerializationUtils.java
index bc9d174..17a11e4 100644
--- a/common/src/main/java/org/keycloak/common/util/KerberosSerializationUtils.java
+++ b/common/src/main/java/org/keycloak/common/util/KerberosSerializationUtils.java
@@ -25,22 +25,13 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
-import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.Set;
+import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosTicket;
import org.ietf.jgss.GSSCredential;
-import org.ietf.jgss.GSSException;
-import org.ietf.jgss.GSSManager;
-import org.ietf.jgss.Oid;
-import org.keycloak.common.constants.KerberosConstants;
-import org.keycloak.common.util.reflections.Reflections;
-import sun.security.jgss.GSSCredentialImpl;
-import sun.security.jgss.GSSManagerImpl;
-import sun.security.jgss.krb5.Krb5InitCredential;
-import sun.security.jgss.krb5.Krb5NameElement;
-import sun.security.jgss.spi.GSSCredentialSpi;
-import sun.security.krb5.Credentials;
/**
* Provides serialization/deserialization of kerberos {@link org.ietf.jgss.GSSCredential}, so it can be transmitted from auth-server to the application
@@ -50,18 +41,9 @@ import sun.security.krb5.Credentials;
*/
public class KerberosSerializationUtils {
- public static final Oid KRB5_OID;
- public static final Oid KRB5_NAME_OID;
public static final String JAVA_INFO;
static {
- try {
- KRB5_OID = new Oid(KerberosConstants.KRB5_OID);
- KRB5_NAME_OID = new Oid(KerberosConstants.KRB5_NAME_OID);
- } catch (GSSException e) {
- throw new RuntimeException(e);
- }
-
String javaVersion = System.getProperty("java.version");
String javaRuntimeVersion = System.getProperty("java.runtime.version");
String javaVendor = System.getProperty("java.vendor");
@@ -72,50 +54,17 @@ public class KerberosSerializationUtils {
private KerberosSerializationUtils() {
}
- public static String serializeCredential(GSSCredential gssCredential) throws KerberosSerializationException {
+ public static String serializeCredential(KerberosTicket kerberosTicket, GSSCredential gssCredential) throws KerberosSerializationException {
try {
if (gssCredential == null) {
throw new KerberosSerializationException("Null credential given as input");
}
- if (!(gssCredential instanceof GSSCredentialImpl)) {
- throw new KerberosSerializationException("Unknown credential type: " + gssCredential.getClass());
- }
-
- GSSCredentialImpl gssCredImpl = (GSSCredentialImpl) gssCredential;
- Oid[] mechs = gssCredImpl.getMechs();
-
- for (Oid oid : mechs) {
- if (oid.equals(KRB5_OID)) {
- int usage = gssCredImpl.getUsage(oid);
- boolean initiate = (usage == GSSCredential.INITIATE_ONLY || usage == GSSCredential.INITIATE_AND_ACCEPT);
-
- GSSCredentialSpi credentialSpi = gssCredImpl.getElement(oid, initiate);
- if (credentialSpi instanceof Krb5InitCredential) {
- Krb5InitCredential credential = (Krb5InitCredential) credentialSpi;
- KerberosTicket kerberosTicket = new KerberosTicket(credential.getEncoded(),
- credential.getClient(),
- credential.getServer(),
- credential.getSessionKey().getEncoded(),
- credential.getSessionKeyType(),
- credential.getFlags(),
- credential.getAuthTime(),
- credential.getStartTime(),
- credential.getEndTime(),
- credential.getRenewTill(),
- credential.getClientAddresses());
- return serialize(kerberosTicket);
- } else {
- throw new KerberosSerializationException("Unsupported type of credentialSpi: " + credentialSpi.getClass());
- }
- }
- }
+ kerberosTicket = KerberosJdkProvider.getProvider().gssCredentialToKerberosTicket(kerberosTicket, gssCredential);
- throw new KerberosSerializationException("Kerberos credential not found. Available mechanisms: " + mechs);
+ return serialize(kerberosTicket);
} catch (IOException e) {
- throw new KerberosSerializationException("Exception occured", e);
- } catch (GSSException e) {
- throw new KerberosSerializationException("Exception occured", e);
+ throw new KerberosSerializationException("Unexpected exception when serialize GSSCredential", e);
}
}
@@ -132,32 +81,12 @@ public class KerberosSerializationUtils {
}
KerberosTicket ticket = (KerberosTicket) deserializedCred;
- String fullName = ticket.getClient().getName();
-
- Method getInstance = Reflections.findDeclaredMethod(Krb5NameElement.class, "getInstance", String.class, Oid.class);
- Krb5NameElement krb5Name = Reflections.invokeMethod(true, getInstance, Krb5NameElement.class, null, fullName, KRB5_NAME_OID);
-
- Credentials krb5CredsInternal = new Credentials(
- ticket.getEncoded(),
- ticket.getClient().getName(),
- ticket.getServer().getName(),
- ticket.getSessionKey().getEncoded(),
- ticket.getSessionKeyType(),
- ticket.getFlags(),
- ticket.getAuthTime(),
- ticket.getStartTime(),
- ticket.getEndTime(),
- ticket.getRenewTill(),
- ticket.getClientAddresses()
- );
-
- Method getInstance2 = Reflections.findDeclaredMethod(Krb5InitCredential.class, "getInstance", Krb5NameElement.class, Credentials.class);
- Krb5InitCredential initCredential = Reflections.invokeMethod(true, getInstance2, Krb5InitCredential.class, null, krb5Name, krb5CredsInternal);
-
- GSSManagerImpl manager = (GSSManagerImpl) GSSManager.getInstance();
- return new GSSCredentialImpl(manager, initCredential);
+
+ return KerberosJdkProvider.getProvider().kerberosTicketToGSSCredential(ticket);
+ } catch (KerberosSerializationException ke) {
+ throw ke;
} catch (Exception ioe) {
- throw new KerberosSerializationException("Exception occured", ioe);
+ throw new KerberosSerializationException("Unexpected exception when deserialize GSSCredential", ioe);
}
}
diff --git a/core/src/main/java/org/keycloak/KeycloakSecurityContext.java b/core/src/main/java/org/keycloak/KeycloakSecurityContext.java
index 3806ffe..118fd1b 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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
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 0000000..c8b6dce
--- /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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+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/test/java/org/keycloak/KeyPairVerifierTest.java b/core/src/test/java/org/keycloak/KeyPairVerifierTest.java
new file mode 100644
index 0000000..da76a61
--- /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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+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/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
index 0d05dc2..4afb80d 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
@@ -28,7 +28,6 @@
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="sun.jdk" optional="true" />
- <module name="sun.jdk.jgss" optional="true" />
</dependencies>
</module>
diff --git a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
index 7821396..1d1be60 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
@@ -33,7 +33,6 @@
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="sun.jdk" optional="true" />
- <module name="sun.jdk.jgss" optional="true" />
</dependencies>
</module>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
index 0d05dc2..4afb80d 100755
--- a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
@@ -28,7 +28,6 @@
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="sun.jdk" optional="true" />
- <module name="sun.jdk.jgss" optional="true" />
</dependencies>
</module>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
index ab0b69a..f40e1e3 100755
--- a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
@@ -33,7 +33,6 @@
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="sun.jdk" optional="true" />
- <module name="sun.jdk.jgss" optional="true" />
</dependencies>
</module>
diff --git a/distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml b/distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
index 0d05dc2..4afb80d 100755
--- a/distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
+++ b/distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
@@ -28,7 +28,6 @@
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="sun.jdk" optional="true" />
- <module name="sun.jdk.jgss" optional="true" />
</dependencies>
</module>
diff --git a/distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml b/distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
index ab0b69a..f40e1e3 100755
--- a/distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
+++ b/distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
@@ -33,7 +33,6 @@
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="sun.jdk" optional="true" />
- <module name="sun.jdk.jgss" optional="true" />
</dependencies>
</module>
diff --git a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-common/main/module.xml b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-common/main/module.xml
index 04f1e61..81578ae 100755
--- a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-common/main/module.xml
+++ b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-common/main/module.xml
@@ -28,7 +28,6 @@
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="sun.jdk" optional="true" />
- <module name="sun.jdk.jgss" optional="true" />
</dependencies>
</module>
diff --git a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-core/main/module.xml b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-core/main/module.xml
index 0ca522d..885b944 100755
--- a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-core/main/module.xml
+++ b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/add-ons/keycloak/org/keycloak/keycloak-core/main/module.xml
@@ -33,7 +33,6 @@
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="sun.jdk" optional="true" />
- <module name="sun.jdk.jgss" optional="true" />
</dependencies>
</module>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml
index 04f1e61..81578ae 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-common/main/module.xml
@@ -28,7 +28,6 @@
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="sun.jdk" optional="true" />
- <module name="sun.jdk.jgss" optional="true" />
</dependencies>
</module>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-core/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-core/main/module.xml
index ca35e20..40b0e69 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-core/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-core/main/module.xml
@@ -33,7 +33,6 @@
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="sun.jdk" optional="true" />
- <module name="sun.jdk.jgss" optional="true" />
</dependencies>
</module>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
index 0d05dc2..4afb80d 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
@@ -28,7 +28,6 @@
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="sun.jdk" optional="true" />
- <module name="sun.jdk.jgss" optional="true" />
</dependencies>
</module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
index 0d05dc2..4afb80d 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml
@@ -28,7 +28,6 @@
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="sun.jdk" optional="true" />
- <module name="sun.jdk.jgss" optional="true" />
</dependencies>
</module>
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 0000000..cfcf18f
--- /dev/null
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/adapter-context.xml
@@ -0,0 +1,12 @@
+<chapter>
+ <title>KeycloakSecurityContext</title>
+ <para>
+ The <literal>KeycloakSecurityContext</literal> 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.
+ <programlisting>
+ httpServletRequest.getAttribute(KeycloakSecurityContext.class.getName());
+ httpServletRequest.getSession().getAttribute(KeycloakSecurityContext.class.getName());
+ </programlisting>
+ </para>
+</chapter>
\ No newline at end of file
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml b/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml
index d60c1cd..d1d5838 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml
@@ -155,7 +155,7 @@ Authorization: basic BASE64(client-id + ':' + client-secret)
</para>
<para>
To retrieve the Adapter Configuration then do a HTTP GET to:
- <literal><KEYCLOAK URL>//realms/<realm>/clients-registrations/installation/<client id></literal>
+ <literal><KEYCLOAK URL>//realms/<realm>/clients-registrations/install/<client id></literal>
</para>
<para>
No authentication is required for public clients. This means that for the JavaScript adapter you can
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java
index c2eccde..4d3bf62 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/CommonKerberosConfig.java
@@ -52,7 +52,7 @@ public abstract class CommonKerberosConfig {
return getConfig().get(KerberosConstants.KEYTAB);
}
- public boolean getDebug() {
+ public boolean isDebug() {
return Boolean.valueOf(getConfig().get(KerberosConstants.DEBUG));
}
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosServerSubjectAuthenticator.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosServerSubjectAuthenticator.java
index 2a155ed..aeaa074 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosServerSubjectAuthenticator.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosServerSubjectAuthenticator.java
@@ -17,16 +17,18 @@
package org.keycloak.federation.kerberos.impl;
-import java.util.HashMap;
-import java.util.Map;
+import java.io.IOException;
import javax.security.auth.Subject;
-import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import org.jboss.logging.Logger;
+import org.keycloak.common.util.KerberosJdkProvider;
import org.keycloak.federation.kerberos.CommonKerberosConfig;
/**
@@ -36,20 +38,32 @@ public class KerberosServerSubjectAuthenticator {
private static final Logger logger = Logger.getLogger(KerberosServerSubjectAuthenticator.class);
+ private static final CallbackHandler NO_CALLBACK_HANDLER = new CallbackHandler() {
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ throw new UnsupportedCallbackException(callbacks[0]);
+ }
+ };
+
+
private final CommonKerberosConfig config;
private LoginContext loginContext;
+
public KerberosServerSubjectAuthenticator(CommonKerberosConfig config) {
this.config = config;
}
+
public Subject authenticateServerSubject() throws LoginException {
Configuration config = createJaasConfiguration();
- loginContext = new LoginContext("does-not-matter", null, null, config);
+ loginContext = new LoginContext("does-not-matter", null, NO_CALLBACK_HANDLER, config);
loginContext.login();
return loginContext.getSubject();
}
+
public void logoutServerSubject() {
if (loginContext != null) {
try {
@@ -60,24 +74,9 @@ public class KerberosServerSubjectAuthenticator {
}
}
+
protected Configuration createJaasConfiguration() {
- return new Configuration() {
-
- @Override
- public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
- Map<String, Object> options = new HashMap<String, Object>();
- options.put("storeKey", "true");
- options.put("doNotPrompt", "true");
- options.put("isInitiator", "false");
- options.put("useKeyTab", "true");
-
- options.put("keyTab", config.getKeyTab());
- options.put("principal", config.getServerPrincipal());
- options.put("debug", String.valueOf(config.getDebug()));
- AppConfigurationEntry kerberosLMConfiguration = new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
- return new AppConfigurationEntry[] { kerberosLMConfiguration };
- }
- };
+ return KerberosJdkProvider.getProvider().createJaasConfigurationForServer(config.getKeyTab(), config.getServerPrincipal(), config.isDebug());
}
}
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java
index f18cf2c..2254a6e 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/KerberosUsernamePasswordAuthenticator.java
@@ -33,6 +33,7 @@ import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import org.jboss.logging.Logger;
+import org.keycloak.common.util.KerberosJdkProvider;
import org.keycloak.federation.kerberos.CommonKerberosConfig;
import org.keycloak.models.ModelException;
@@ -58,7 +59,7 @@ public class KerberosUsernamePasswordAuthenticator {
* @return true if user available
*/
public boolean isUserAvailable(String username) {
- logger.debug("Checking existence of user: " + username);
+ logger.debugf("Checking existence of user: %s", username);
try {
String principal = getKerberosPrincipal(username);
loginContext = new LoginContext("does-not-matter", null,
@@ -70,7 +71,7 @@ public class KerberosUsernamePasswordAuthenticator {
throw new IllegalStateException("Didn't expect to end here");
} catch (LoginException le) {
String message = le.getMessage();
- logger.debug("Message from kerberos: " + message);
+ logger.debugf("Message from kerberos: %s", message);
checkKerberosServerAvailable(le);
@@ -128,6 +129,7 @@ public class KerberosUsernamePasswordAuthenticator {
return loginContext.getSubject();
}
+
public void logoutSubject() {
if (loginContext != null) {
try {
@@ -139,7 +141,6 @@ public class KerberosUsernamePasswordAuthenticator {
}
-
protected String getKerberosPrincipal(String username) throws LoginException {
if (username.contains("@")) {
String[] tokens = username.split("@");
@@ -156,6 +157,7 @@ public class KerberosUsernamePasswordAuthenticator {
return username + "@" + config.getKerberosRealm();
}
+
protected CallbackHandler createJaasCallbackHandler(final String principal, final String password) {
return new CallbackHandler() {
@@ -176,17 +178,8 @@ public class KerberosUsernamePasswordAuthenticator {
};
}
- protected Configuration createJaasConfiguration() {
- return new Configuration() {
- @Override
- public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
- Map<String, Object> options = new HashMap<String, Object>();
- options.put("storeKey", "true");
- options.put("debug", String.valueOf(config.getDebug()));
- AppConfigurationEntry kerberosLMConfiguration = new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
- return new AppConfigurationEntry[] { kerberosLMConfiguration };
- }
- };
+ protected Configuration createJaasConfiguration() {
+ return KerberosJdkProvider.getProvider().createJaasConfigurationForUsernamePasswordLogin(config.isDebug());
}
}
diff --git a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java
index f09ea6e..c2b928e 100644
--- a/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java
+++ b/federation/kerberos/src/main/java/org/keycloak/federation/kerberos/impl/SPNEGOAuthenticator.java
@@ -19,15 +19,21 @@ package org.keycloak.federation.kerberos.impl;
import java.io.IOException;
import java.security.PrivilegedExceptionAction;
+import java.util.Iterator;
+import java.util.Set;
import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosTicket;
+import org.ietf.jgss.Oid;
+import org.keycloak.common.constants.KerberosConstants;
import org.keycloak.common.util.Base64;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.jboss.logging.Logger;
+import org.keycloak.common.util.KerberosJdkProvider;
import org.keycloak.federation.kerberos.CommonKerberosConfig;
import org.keycloak.common.util.KerberosSerializationUtils;
@@ -45,6 +51,7 @@ public class SPNEGOAuthenticator {
private boolean authenticated = false;
private String authenticatedKerberosPrincipal = null;
private GSSCredential delegationCredential;
+ private KerberosTicket kerberosTicket;
private String responseToken = null;
public SPNEGOAuthenticator(CommonKerberosConfig kerberosConfig, KerberosServerSubjectAuthenticator kerberosSubjectAuthenticator, String spnegoToken) {
@@ -61,6 +68,14 @@ public class SPNEGOAuthenticator {
try {
Subject serverSubject = kerberosSubjectAuthenticator.authenticateServerSubject();
authenticated = Subject.doAs(serverSubject, new AcceptSecContext());
+
+ // kerberosTicketis available in IBM JDK in case that GSSContext supports delegated credentials
+ Set<KerberosTicket> kerberosTickets = serverSubject.getPrivateCredentials(KerberosTicket.class);
+ Iterator<KerberosTicket> iterator = kerberosTickets.iterator();
+ if (iterator.hasNext()) {
+ kerberosTicket = iterator.next();
+ }
+
} catch (Exception e) {
log.warn("SPNEGO login failed", e);
} finally {
@@ -89,7 +104,7 @@ public class SPNEGOAuthenticator {
if (log.isTraceEnabled()) {
log.trace("Serializing credential " + delegationCredential);
}
- return KerberosSerializationUtils.serializeCredential(delegationCredential);
+ return KerberosSerializationUtils.serializeCredential(kerberosTicket, delegationCredential);
} catch (KerberosSerializationUtils.KerberosSerializationException kse) {
log.warn("Couldn't serialize credential: " + delegationCredential, kse);
return null;
@@ -150,7 +165,10 @@ public class SPNEGOAuthenticator {
protected GSSContext establishContext() throws GSSException, IOException {
GSSManager manager = GSSManager.getInstance();
- GSSContext gssContext = manager.createContext((GSSCredential) null);
+
+ Oid[] supportedMechs = new Oid[] { KerberosConstants.KRB5_OID, KerberosConstants.SPNEGO_OID };
+ GSSCredential gssCredential = manager.createCredential(null, GSSCredential.INDEFINITE_LIFETIME, supportedMechs, GSSCredential.ACCEPT_ONLY);
+ GSSContext gssContext = manager.createContext(gssCredential);
byte[] inputToken = Base64.decode(spnegoToken);
byte[] respToken = gssContext.acceptSecContext(inputToken, 0, inputToken.length);
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 ede1479..02056a7 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 6acdc83..0cedec6 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<String, String> map);
+ AccessTokenResponse grantToken(@PathParam("realm") String realm, MultivaluedMap<String, String> map);
@POST
@Path("/realms/{realm}/protocol/openid-connect/token")
- public AccessTokenResponse refreshToken(@PathParam("realm") String realm, MultivaluedMap<String, String> map);
+ AccessTokenResponse refreshToken(@PathParam("realm") String realm, MultivaluedMap<String, String> map);
}
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 6a4d1dc..c107b8e 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 @@
<include file="META-INF/jpa-changelog-1.7.0.xml"/>
<include file="META-INF/jpa-changelog-1.8.0.xml"/>
<include file="META-INF/jpa-changelog-1.9.0.xml"/>
+ <include file="META-INF/jpa-changelog-1.9.1.xml"/>
</databaseChangeLog>
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 7c1a3db..3a25c5c 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/models/utils/CredentialValidation.java b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
index 654c764..074949f 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/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index d349f4e..970fd7e 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/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 653e266..3241363 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -621,7 +621,7 @@ public class AccountService extends AbstractSecuredLocalService {
}
}
- if (Validation.isEmpty(passwordNew)) {
+ if (Validation.isBlank(passwordNew)) {
setReferrerOnPage();
return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
}
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 0c9f4ce..b434cdf 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/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index e8e38ba..dea9daf 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
@@ -93,6 +93,7 @@ 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;
/**
* Base resource for managing users
@@ -707,6 +708,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 {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
index f03b55a..7b0d9d3 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
@@ -138,6 +138,12 @@ public class AdapterTestStrategy extends ExternalResource {
String pageSource = driver.getPageSource();
System.out.println(pageSource);
Assert.assertTrue(pageSource.contains("parameter=hello"));
+ // test that user principal and KeycloakSecurityContext available
+ driver.navigate().to(APP_SERVER_BASE_URL + "/input-portal/insecure");
+ System.out.println("insecure: ");
+ System.out.println(driver.getPageSource());
+ Assert.assertTrue(driver.getPageSource().contains("Insecure Page"));
+ if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertTrue(driver.getPageSource().contains("UserPrincipal"));
// test logout
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/FilterAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/FilterAdapterTest.java
index 9637617..a533133 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/FilterAdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/FilterAdapterTest.java
@@ -100,6 +100,7 @@ public class FilterAdapterTest {
@Test
public void testSavedPostRequest() throws Exception {
+ System.setProperty("insecure.user.principal.unsupported", "true");
testStrategy.testSavedPostRequest();
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputServlet.java
index c0135ef..b6a0bd5 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputServlet.java
@@ -17,6 +17,9 @@
package org.keycloak.testsuite.adapter;
+import org.junit.Assert;
+import org.keycloak.KeycloakSecurityContext;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -35,6 +38,17 @@ public class InputServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String appBase = System.getProperty("app.server.base.url", "http://localhost:8081");
+ if (req.getRequestURI().endsWith("insecure")) {
+ if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertNotNull(req.getUserPrincipal());
+ if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertNotNull(req.getAttribute(KeycloakSecurityContext.class.getName()));
+ resp.setContentType("text/html");
+ PrintWriter pw = resp.getWriter();
+ pw.printf("<html><head><title>%s</title></head><body>", "Insecure Page");
+ if (req.getUserPrincipal() != null) pw.printf("UserPrincipal: " + req.getUserPrincipal().getName());
+ pw.print("</body></html>");
+ pw.flush();
+ return;
+ }
String actionUrl = appBase + "/input-portal/secured/post";
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
index d47e78f..daaf5a7 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
@@ -48,8 +48,8 @@ public class ConcurrencyTest extends AbstractClientTest {
private static final Logger log = Logger.getLogger(ConcurrencyTest.class);
- private static final int DEFAULT_THREADS = 10;
- private static final int DEFAULT_ITERATIONS = 100;
+ private static final int DEFAULT_THREADS = 5;
+ private static final int DEFAULT_ITERATIONS = 20;
// If enabled only one request is allowed at the time. Useful for checking that test is working.
private static final boolean SYNCHRONIZED = false;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
index 156835a..511e6b5 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
@@ -25,6 +25,7 @@ import org.keycloak.admin.client.resource.ServerInfoResource;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
@@ -32,6 +33,7 @@ import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.KeycloakServer;
import org.keycloak.util.JsonSerialization;
+import javax.ws.rs.BadRequestException;
import javax.ws.rs.NotFoundException;
import java.io.IOException;
import java.util.Collections;
@@ -51,6 +53,10 @@ import static org.junit.Assert.fail;
*/
public class RealmTest extends AbstractClientTest {
+ public static final String PRIVATE_KEY = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=";
+ public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB";
+ public static final String CERTIFICATE = "MIICsTCCAZkCBgFTLB5bhDANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFhZG1pbi1jbGllbnQtdGVzdDAeFw0xNjAyMjkwODIwMDBaFw0yNjAyMjgwODIxNDBaMBwxGjAYBgNVBAMMEWFkbWluLWNsaWVudC10ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAquzJtpAlpTFnJzILjTOHW+SOWav1eIsCtlAqiFTvBskbod6b4BtVaR3FVrQm8rFiwDOIEWT3IG3ZIz0LKYxnqvuffyLHGHjiroqrR63kY9Wa9B790lSEWVaGeNOMnKleqKu5QUNfL3wVebUh/C/QfxZ29R1EIbxNe2ThN8yuIca8Ltn43D5VlyatptojffxpCYiYqAmIwQDaq1um2cQ+4rPBLxC5jM9UBvYOMUP4u0caNSaPI1o9lHVKgTtWcdQzUeMmAGsnLV26XGhA/OwRduUxksumR1kh/KSqowasjgSrpVqtF/uo5TY57s7drD+zKG58cdHLreclB9AQNvNwZwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBh4iwg8GnadeQP52pV5vKJ4Z8A1R2aYCzoW7Lc3FI/pXWX9Af5dKILX5O2j/daamPS+WtDWxIuwvZC5drrkvJn/r8e4KstnXQzPQggIJbI9v3wfIX3VlFvwvZVGiuE5PSLSWb0L57PEojZVpIU5bLchq4yRSD2zK4dWX8Y6I/D40a74KDvPOlEL8405/T1iW7ytKT9awNJW04N91owoI+kdUL+DMnnGzIxDAoYAeZI/1vcwoaH24zyTLGItkzpKxqLOdB05cnxn5jCWY2Hyd1zqtRkadhgZaqu4lcDHAHEMDp6dEjLZW8ym8bnlto+MD2y//CsyPCzyCLlA726vrli";
+
@Test
public void getRealms() {
List<RealmRepresentation> realms = keycloak.realms().findAll();
@@ -331,4 +337,80 @@ public class RealmTest extends AbstractClientTest {
}
+ @Test
+ public void uploadRealmKeys() throws Exception {
+ String originalPublicKey = realm.toRepresentation().getPublicKey();
+
+ RealmRepresentation rep = new RealmRepresentation();
+ rep.setPrivateKey("INVALID");
+ rep.setPublicKey(PUBLIC_KEY);
+
+ try {
+ realm.update(rep);
+ fail("Expected BadRequestException");
+ } catch (BadRequestException e) {
+ }
+
+ rep.setPrivateKey(PRIVATE_KEY);
+ rep.setPublicKey("INVALID");
+
+ try {
+ realm.update(rep);
+ fail("Expected BadRequestException");
+ } catch (BadRequestException e) {
+ }
+
+ assertEquals(originalPublicKey, realm.toRepresentation().getPublicKey());
+
+ rep.setPublicKey(PUBLIC_KEY);
+ realm.update(rep);
+
+ assertEquals(PUBLIC_KEY, rep.getPublicKey());
+
+ String privateKey2048 = IOUtils.toString(getClass().getResourceAsStream("/keys/private2048.pem"));
+ String publicKey2048 = IOUtils.toString(getClass().getResourceAsStream("/keys/public2048.pem"));
+
+ rep.setPrivateKey(privateKey2048);
+
+ try {
+ realm.update(rep);
+ fail("Expected BadRequestException");
+ } catch (BadRequestException e) {
+ }
+
+ assertEquals(PUBLIC_KEY, realm.toRepresentation().getPublicKey());
+
+ rep.setPublicKey(publicKey2048);
+
+ realm.update(rep);
+
+ assertEquals(publicKey2048, realm.toRepresentation().getPublicKey());
+
+ String privateKey4096 = IOUtils.toString(getClass().getResourceAsStream("/keys/private4096.pem"));
+ String publicKey4096 = IOUtils.toString(getClass().getResourceAsStream("/keys/public4096.pem"));
+ rep.setPrivateKey(privateKey4096);
+ rep.setPublicKey(publicKey4096);
+
+ realm.update(rep);
+
+ assertEquals(publicKey4096, realm.toRepresentation().getPublicKey());
+ }
+
+ @Test
+ public void uploadCertificate() throws IOException {
+ RealmRepresentation rep = new RealmRepresentation();
+ rep.setCertificate(CERTIFICATE);
+
+ realm.update(rep);
+
+ assertEquals(CERTIFICATE, rep.getCertificate());
+
+ String certificate = IOUtils.toString(getClass().getResourceAsStream("/keys/certificate.pem"));
+ rep.setCertificate(certificate);
+
+ realm.update(rep);
+
+ assertEquals(certificate, rep.getCertificate());
+ }
+
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
index 07ed389..b38b362 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
@@ -646,6 +646,23 @@ public class UserTest extends AbstractClientTest {
assertEquals("Keycloak Account Management", driver.getTitle());
}
+ @Test
+ public void resetUserInvalidPassword() {
+ String userId = createUser("user1", "user1@localhost");
+
+ try {
+ CredentialRepresentation cred = new CredentialRepresentation();
+ cred.setType(CredentialRepresentation.PASSWORD);
+ cred.setValue(" ");
+ cred.setTemporary(false);
+ realm.users().get(userId).resetPassword(cred);
+ fail("Expected failure");
+ } catch (ClientErrorException e) {
+ assertEquals(400, e.getResponse().getStatus());
+ e.getResponse().close();
+ }
+ }
+
private void switchEditUsernameAllowedOn() {
RealmRepresentation rep = realm.toRepresentation();
rep.setEditUsernameAllowed(true);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
index 16e302e..12f0f5e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java
@@ -192,6 +192,10 @@ public abstract class AbstractKerberosTest {
loginPage.login("jduke", "theduke");
changePasswordPage.assertCurrent();
+ // Bad existing password
+ changePasswordPage.changePassword("theduke-invalid", "newPass", "newPass");
+ Assert.assertTrue(driver.getPageSource().contains("Invalid existing password."));
+
// Change password is not possible as editMode is READ_ONLY
changePasswordPage.changePassword("theduke", "newPass", "newPass");
Assert.assertTrue(driver.getPageSource().contains("You can't update your password as your account is read only"));
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/InputServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/InputServlet.java
index b6eb54a..57c8e48 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/InputServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/InputServlet.java
@@ -17,6 +17,9 @@
package org.keycloak.testsuite.keycloaksaml;
+import org.junit.Assert;
+import org.keycloak.KeycloakSecurityContext;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -34,6 +37,16 @@ public class InputServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String appBase = System.getProperty("app.server.base.url", "http://localhost:8081");
+ if (req.getRequestURI().endsWith("insecure")) {
+ if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertNotNull(req.getUserPrincipal());
+ resp.setContentType("text/html");
+ PrintWriter pw = resp.getWriter();
+ pw.printf("<html><head><title>%s</title></head><body>", "Insecure Page");
+ if (req.getUserPrincipal() != null) pw.printf("UserPrincipal: " + req.getUserPrincipal().getName());
+ pw.print("</body></html>");
+ pw.flush();
+ return;
+ }
String actionUrl = appBase + "/input-portal/secured/post";
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java
index 948d9ca..a9e95bb 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java
@@ -152,6 +152,12 @@ public class SamlAdapterTestStrategy extends ExternalResource {
String pageSource = driver.getPageSource();
System.out.println(pageSource);
Assert.assertTrue(pageSource.contains("parameter=hello"));
+ // test that user principal and KeycloakSecurityContext available
+ driver.navigate().to(APP_SERVER_BASE_URL + "/input-portal/insecure");
+ System.out.println("insecure: ");
+ System.out.println(driver.getPageSource());
+ Assert.assertTrue(driver.getPageSource().contains("Insecure Page"));
+ if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertTrue(driver.getPageSource().contains("UserPrincipal"));
// test logout
diff --git a/testsuite/integration/src/test/resources/kerberos/kerberos-ldap-connection.properties b/testsuite/integration/src/test/resources/kerberos/kerberos-ldap-connection.properties
index a0ea537..3170d6c 100644
--- a/testsuite/integration/src/test/resources/kerberos/kerberos-ldap-connection.properties
+++ b/testsuite/integration/src/test/resources/kerberos/kerberos-ldap-connection.properties
@@ -31,4 +31,4 @@ idm.test.kerberos.allow.kerberos.authentication=true
idm.test.kerberos.realm=KEYCLOAK.ORG
idm.test.kerberos.server.principal=HTTP/localhost@KEYCLOAK.ORG
idm.test.kerberos.debug=false
-idm.test.kerberos.use.kerberos.for.password.authentication=false
\ No newline at end of file
+idm.test.kerberos.use.kerberos.for.password.authentication=true
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/kerberos/test-krb5.conf b/testsuite/integration/src/test/resources/kerberos/test-krb5.conf
index a775b47..6989b7f 100644
--- a/testsuite/integration/src/test/resources/kerberos/test-krb5.conf
+++ b/testsuite/integration/src/test/resources/kerberos/test-krb5.conf
@@ -1,8 +1,8 @@
[libdefaults]
default_realm = KEYCLOAK.ORG
- default_tgs_enctypes = des3-cbc-sha1-kd aes256-cts-hmac-sha1-96 rc4-hmac
- default_tkt_enctypes = des3-cbc-sha1-kd aes256-cts-hmac-sha1-96 rc4-hmac
- permitted_enctypes = des3-cbc-sha1-kd aes256-cts-hmac-sha1-96 rc4-hmac
+ default_tgs_enctypes = des3-cbc-sha1-kd aes256-cts-hmac-sha1-96 rc4-hmac aes128-cts-hmac-sha1-96
+ default_tkt_enctypes = des3-cbc-sha1-kd aes256-cts-hmac-sha1-96 rc4-hmac aes128-cts-hmac-sha1-96
+ permitted_enctypes = des3-cbc-sha1-kd aes256-cts-hmac-sha1-96 rc4-hmac aes128-cts-hmac-sha1-96
kdc_timeout = 30000
dns_lookup_realm = false
dns_lookup_kdc = false
diff --git a/testsuite/integration/src/test/resources/keys/certificate.pem b/testsuite/integration/src/test/resources/keys/certificate.pem
new file mode 100644
index 0000000..a526c93
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keys/certificate.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDXTCCAkWgAwIBAgIJAIzE3vQp7EQWMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTYwMjI5MDgzMDU0WhcNNDMwNzE2MDgzMDU0WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAp1+GzdEkt2FZbISXYO12503FL6Oh8s4+tJ2fE66N8IezhugP8xiySDfW
+TEMaO5Z2TaTnQQoF9SSZ9Edq1GPxpBX0cdkCOBopEGdlb3hUYDeMaDMs18KGemUc
+Fj+CWB5VVcbmWMJ36WCz7FC+Oe38tmujR1AJpJL3pwqazyWIZzPqX8rW+rrNPGKP
+C96oBPZMb4RJWivLBJi/o5MGSpo1sJNtxyF4zUUI00LX0wZAV1HH1XErd1Vz41on
+nmB+tj9nevVRR4rDV280IELp9Ud0PIb3w843uJtwfSAwVG0pT6hv1VBDrBxTS08N
+dPU8CtkQAXzCCr8nqfAbUFOhcWRQgQIDAQABo1AwTjAdBgNVHQ4EFgQUFE+uUZAI
+n57ArEylqhCmHkAenTEwHwYDVR0jBBgwFoAUFE+uUZAIn57ArEylqhCmHkAenTEw
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEApkgD3OCtw3+sk7GR1YaJ
+xNd8HT+fxXmnLqGnCQWX8lRIg5vj1PDMRev6vlIK3JfQV3zajcpKFfpy96klWsJy
+ZLYBVW2QOtMzDdQ9I8dS4Pn/SJ/Vo/M/ucfY4ttcuUL3oQCrI/c/u9tcamGMfbwd
+658MlXrUvt4B6qXY5AbgUvYR25P86uw7hSFMq5tQftNQsLbOh2FEeIiKhpgI7w8S
+SPajaWjUXsfHc5H7f9MciE2NS1Vd3AViGrVWP1rgQ1Iv0UyQVQrnjmIs12ENJmTd
+5lDqra5FJhaO7+RUG6er8n8HwXzhHkPmezGqtxWKikjitqvDY9prB3omJSa4Led+
+AQ==
+-----END CERTIFICATE-----
diff --git a/testsuite/integration/src/test/resources/keys/private2048.pem b/testsuite/integration/src/test/resources/keys/private2048.pem
new file mode 100644
index 0000000..e2baad3
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keys/private2048.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEApnZ/E2BUULHjsRiSnEgZ4vGe15BRqZPdkHR+NcvVYpThc7Jq
+Y6nZrdrwO9sOjlMC5e2Q18Fypi4KbJpGSe9r0DPgcbPsHSoe2xFO3M8XBE0DyoRb
+laQFhe6p/sj3ak32k2zn+fMZUmlx/MTNQh1ICki7So0NDCBXt8XGZNnEyvKeXOUZ
+P5qicP9KxVAQiWJvlkaTjc8rrRTmf+HWw/QfgQC0tzBRpa7T+RpW9O+rnWfOaNfT
+kTb9itIc+ZOa2Z4iidZ7+ifMOp9cNT641Wb6iYqJ2ufqY+msxI54tYM1tPgGS7r4
+SnCwmnqTaO383wXUl8TQ7qStmAWIepV3nNyuAQIDAQABAoIBAEj90rDrX21W43Fn
+RfpTP06dBjqdpMFH/jJ2clUigPnOMKGrzSzQcIvkYczNPC+6RJ4PsqB4yc4GiDmg
+2EtZOZw88yDIdTNAofELQNpf0Ebpgk0OBp6yIl3dDhuTgbHSZ9mzOnEGYMcbR4k/
+voVME6e2xrFk8iCsGeqSRXE5cCpQzXLFQGRrGGoJH8hggteyQhei39pxo8IM87A0
+ptdZj8YJ0YUUpztyshR5KAzZeJPjKRXE1wPiZoqDzQ0OCC+k9IGCeJIAAGVB1Zs7
+4ga4gEkczYfSbhRRGl7Uls4XJHL1pNsU75sRpQZGuGm9as6PprYJiDH5Eoz+EdzO
++oxs0FUCgYEA1NFbnyjlTEB9liVAMSd+BNRL7c78+Tsbfn/y6b1pEcSZ5+R79tNw
+NTAmk0GSrW64MRQS2gYe871w9QBwpSlNoGMN5Zjzl8WIZ+AFzTCVnEyBt0DOTj/w
+ZU2/o56C+/CYj6kUjP88HRIX/Grr7rY20uGcDKVLFVeYCBGUI/m0WOsCgYEAyD1F
+TLZea00ZBl0xb/ju0KKGHxw63BldfoKCXjTarz1M7Nl9vBdGuOBH+H3a2wSPhJfb
+7Le9LY+tU53aCJql5xshsDNtduTMdHPamKXkHH038Tbn1vb8HWPzAzYcAY3oAPVJ
+OPPWtTmVeitl0Lo5kPYFbx4yBykOOAokLE/qGcMCgYBZu4CnRkYQday+TyyWzTEM
+djshpUHzEGISX36b4ZpYvI2sQiGmvBY2xvus4VwoNmQBhZZBSY1pdjoXg7z7VsP9
+WWa1pV0oZEiUi9fGYbLjeTrEetXCFqGVBUhFhAN0mUiqYj9hCAlftI5ahva96ySI
+nEoA5v0WnZ1j4Y2V8aaCSwKBgA2bBCtrNM0rpuikymgmTOvGL0DL5T/xRUYETiFi
+i/1eN+zb9kwidL65ForO2mEJVUsYGmxiK6t92LQWxKrS/zTNxiM3y1dJwo6jFJZM
+p0w8QeHU7jnP/F9u3CM6uPpuDvaJtBj3kH4t1HdBnaBqFuE/pizfq1yLMJkkL0MH
+hwuLAoGAEwOTV+mkM6Fv+JGDwRkfFlz95XfQsTXT3xBgGy9sGmtn2b4tPCzO/Fxd
+eLD9dMeI8M3yB4Vc1gRhK8jmmgvcYhsyLn+DreLGWnwQjQn+vAMCpkcPMGJjp3bz
+UooIHTCHq7aNirxPrzgi+F5PcGR074qPFrdVaL031CPTAGmMqzE=
+-----END RSA PRIVATE KEY-----
diff --git a/testsuite/integration/src/test/resources/keys/private4096.pem b/testsuite/integration/src/test/resources/keys/private4096.pem
new file mode 100644
index 0000000..e854f45
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keys/private4096.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAni4pAISeCrzLSEuTJPx51Uwh0LIQuNHRTah1UuWk5NP4W1ct
+l1PuCoddtZNiPL2tnEw0Js1m7vlI3+miynNy8ze2LCtaA98oPUVsD9C13hKH3nQ1
+Zf/IYCzHMC7BAJ7pFCgo2TM0S2ic2eHyr+/vQd2ha8mnj6hzwJ3O9KaIINDLA1Eu
+cJNZDxmlbzJfap5mFH1Uj2RhDiYM4m8445fR7QDv3Zhiv0HMP0dCaVnPot+jT6ar
+195XDDLSIrK+sc8SFLdlc4BlGitDyZviKVzI5E4nn4hkHLBK6jHzNd+afJLPyZIm
+t0N9NPj6y3h3gaGAVMWQpK2bgKEdF6417CPGbcMuGsBbpmDQzl9T4EtcN/Pq741t
+yCEXUWORHOEU+bBiEO1RAWsah13LxhYQ38YclTkr7BGZfQ4sfEZDKEknr7UuQw1e
+E7eZLmiAb+N72kxO5u50OtAmqUhw8LN5iQ/gD5dcItsdi643nKTFUB+bEIqqWaXB
+vRsIn3BmNJiPrgUFJoMm94vrAC1WKjKU4QfERSD/SUuFL3FisP3c33VP1VX33rfM
+6PIWyffIx43XoSp7MwFL3zPvTNLBP33LIBPD83ZT1Af46ky8TIA98CEkNNQdBOQE
+trSsm0snYuku1xNQrdccw3jFvroVQGGPS4fLb0ZveUzvWlTXfPwcF/I+UX8CAwEA
+AQKCAgAtBL4uo4/HRowkez3ZnPGfvxcwqj0QHMcQ7wzVFv6jEaDxfhI/q2doH7aP
+4u5mkyVCAn4lPPg6s0AGWhw2ujzQ894pG/12dKCjL4rdxWy9U1DQAwrfiLiihpv6
+HpRXdOKpFfTqPCR6AcACUsgbQpI5kLZMUwwZWrv26fLotiywCjx9KTGqYIVMQuRO
+jWuSkJpE4paaaNsmh7XLDBSI0cCxTz71NlWEUVmr+L/x89mgZT25gZoOyfzEQAKG
+VZDoUiK3OqrpAHXFyOJ2EqN/WiTitUuZn9u+Pn5TwPuTLZxuL+prV+kk51RPKz1V
+g2efwTMg7UmJVM+ZAEGjZ7V5C4WBbR4kxpCVe2vs2x6OUkhu84MFrB3WQ5oHbR7C
+EZc15FVZNux7mHokwBYsosgtNUtdRtWN3mIIjZKQhbQfw5U8LwLGf94Gc5sxAu8O
+E/KQ2kynqs6OU0PixVMRxkLnVJeT3x4lS8VIuiEx6Wa8+R5774tMmVPPghr2Y2Fx
+IY/nF0fwU+5wn5Bu2Td5m/+g/lFfWZslTdTRNPtAPHfEuQCVxgtolpS2is9rqJw7
+0ymmck+XdM18IGVf1e+AH01w8FMlngU7JqO2jkqNE0B+s+FRkYkg/ldTJjI3e8HT
+kFBIynUfgzNgvulL06Re9TSq5QG9pahpT3FyUILbe808fV+jgQKCAQEAz4bBUHSH
+TAsgUSvAQ/KDJAaoaEFLAoy1B/u2qk/4e01b1FrEVGO4tPhGR5fsWYew7ktTMHPp
+ywra1sTdUUKEVm51YHiymT1T5w5S1gYJn6eElhfcf3tTk1zDW+WBQ0lrn5FVe4Kt
+guFledpGqssGymqrxwKqKnGY5viI+jUmBZATIo6yeNDJuBd2W2kXO40wUn7M5/yE
+a1jUxc6SPC2Z4CsAZyL5YuwbwW/K2dP97MX2snyPlnLzm9etBsMa52N/ODBThgxE
++pwST+0DMbi9RtSCEQfeTEmJvuXkANhLnQZq7oW+WwEpWTjNQMzdaOYyIBk4vlmP
+8mjI+KdplCtNcQKCAQEAwyC1OH1iASdJ7ihvSx8+nM6vk835Am2xERvcduAh7yX6
+0S8Uq0AYlFz1qg/7T0HK1+6kWsCSxgaXRnlU6Mit62sEQ89HEs6kPCkjGvT0BLF1
+dTU1f8xbWG/Ra7ejjuVAV4K2ZhpI2TwHChQt94mkNmeW99SHTVZmsKEo6igfstRx
+Xk5PwK69YVbR4NEGRWcKjeeokfNML6WKa+G0zzaP6mRpdcIvsHbZJNX9NKAU1IvC
++QrrgZNnR0gjFrb77QVK/yED8MidYz1VSOPP1TtozKfyqc7flC28wTd1U1c+Sh+9
+02nkh2xEKwBRnf3+3qdGYZnZPJTHOnLuOegv84LV7wKCAQEApaE1lNMMUOLobiBv
+GUvq4sv1iQ/joCtRKQf7KD8fYLnDOt8epwPYHYex/93/Iw2rZuTzhk5dIFKPiq4g
+vYRLPvh18gMi+C78UgMalfrHn4cByRFOSOjTMV/uA+BOpLdqkDZcdXE8rqLabMCL
+ejEEQHWWmAVGbw2vLVjbamcU64er3f7p7oclGCqRqi9b/YmYMZ9GRzlBLrP9Tcqq
+6CC1GTb44VgGlq8/D8n7qpMJrPnrBVVo/HjOeWlPjYAWbur4VI4te2U7gJEkBGp0
+DDEXz/o3vQP8pgJjT9sHeK0o3DCNE8XmwZdRuwYcu0VGyTxAcWHv6exteNms3Ngw
+6bMN4QKCAQABFK5MSM2BKiGLsyeip+Kl5bMtQ2fMrqTbbmcTNXyaoYA5JmSb6jf4
+omct8Pa4Yqnn9kdsxUJK3IB8AHIK9AmakzYr1fsTzJc2ShgKry6m1ADNjGTmd1BO
+NhhX22WJhhWMJooyGJUstttnH+N9SoLhVkOMzd2N/RuGgO4EFgLO78RM/GwOqikc
+X+m7sAyz17VEQfM6E7npTaZtoItq1meHqdS3tUKkXJQpUxIa94QGBVwoGvpg9lsN
+FwYyuwK8NlpK/XjTHZlZkl5lj/V3veN/trJuZFnyrSote8wnkQUkTfa0NBLy+ROL
+lW3eTSjbPNvz8HE2l7Bez4IoSfPyClh7AoIBAAyWkCZU/57BWfWQVM+74Czjfh9l
+fYrri16POhlDe7oZn/nAaKxWGoNTOEFkKeM0uag+mDUspxXn/ZREV+xb3WXTNTjV
+hLM7HiKMx9rMOIQedO4heyJWT5fIzGfxmPVok9cO8XMG+Ox3SLpn1S0N5+5fkvQj
+S4OX8v4U0AAhfwyxd1zvvsMWj9lPEOpFIWpjhFtnxvevCYCu62JKYKwI0kSqFeWr
+nm1EgcpZVKvLZjScWiW+f20KOBeg/WFBeqh0BFEM7eCOg1EyGJnHLegYabcpWIYH
+hLJWQdXkUbXkJBql3Sbd3o3ZTiknahO4bc1Kcxm29Kns8Y02cEd+Ahwuhh4=
+-----END RSA PRIVATE KEY-----
diff --git a/testsuite/integration/src/test/resources/keys/public2048.pem b/testsuite/integration/src/test/resources/keys/public2048.pem
new file mode 100644
index 0000000..565df1e
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keys/public2048.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApnZ/E2BUULHjsRiSnEgZ
+4vGe15BRqZPdkHR+NcvVYpThc7JqY6nZrdrwO9sOjlMC5e2Q18Fypi4KbJpGSe9r
+0DPgcbPsHSoe2xFO3M8XBE0DyoRblaQFhe6p/sj3ak32k2zn+fMZUmlx/MTNQh1I
+Cki7So0NDCBXt8XGZNnEyvKeXOUZP5qicP9KxVAQiWJvlkaTjc8rrRTmf+HWw/Qf
+gQC0tzBRpa7T+RpW9O+rnWfOaNfTkTb9itIc+ZOa2Z4iidZ7+ifMOp9cNT641Wb6
+iYqJ2ufqY+msxI54tYM1tPgGS7r4SnCwmnqTaO383wXUl8TQ7qStmAWIepV3nNyu
+AQIDAQAB
+-----END PUBLIC KEY-----
diff --git a/testsuite/integration/src/test/resources/keys/public4096.pem b/testsuite/integration/src/test/resources/keys/public4096.pem
new file mode 100644
index 0000000..1d1fa1b
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keys/public4096.pem
@@ -0,0 +1,14 @@
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAni4pAISeCrzLSEuTJPx5
+1Uwh0LIQuNHRTah1UuWk5NP4W1ctl1PuCoddtZNiPL2tnEw0Js1m7vlI3+miynNy
+8ze2LCtaA98oPUVsD9C13hKH3nQ1Zf/IYCzHMC7BAJ7pFCgo2TM0S2ic2eHyr+/v
+Qd2ha8mnj6hzwJ3O9KaIINDLA1EucJNZDxmlbzJfap5mFH1Uj2RhDiYM4m8445fR
+7QDv3Zhiv0HMP0dCaVnPot+jT6ar195XDDLSIrK+sc8SFLdlc4BlGitDyZviKVzI
+5E4nn4hkHLBK6jHzNd+afJLPyZImt0N9NPj6y3h3gaGAVMWQpK2bgKEdF6417CPG
+bcMuGsBbpmDQzl9T4EtcN/Pq741tyCEXUWORHOEU+bBiEO1RAWsah13LxhYQ38Yc
+lTkr7BGZfQ4sfEZDKEknr7UuQw1eE7eZLmiAb+N72kxO5u50OtAmqUhw8LN5iQ/g
+D5dcItsdi643nKTFUB+bEIqqWaXBvRsIn3BmNJiPrgUFJoMm94vrAC1WKjKU4QfE
+RSD/SUuFL3FisP3c33VP1VX33rfM6PIWyffIx43XoSp7MwFL3zPvTNLBP33LIBPD
+83ZT1Af46ky8TIA98CEkNNQdBOQEtrSsm0snYuku1xNQrdccw3jFvroVQGGPS4fL
+b0ZveUzvWlTXfPwcF/I+UX8CAwEAAQ==
+-----END PUBLIC KEY-----
diff --git a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
index 950fd22..e9bb97f 100755
--- a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
@@ -86,7 +86,7 @@
"connectionsInfinispan": {
"default": {
"clustered": "${keycloak.connectionsInfinispan.clustered:false}",
- "async": "${keycloak.connectionsInfinispan.async:true}",
+ "async": "${keycloak.connectionsInfinispan.async:false}",
"sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}"
}
}
diff --git a/testsuite/integration-arquillian/README.md b/testsuite/integration-arquillian/README.md
index 39168de..1adc054 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/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 673bc7c..fc63b22 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<ContainerInfo> 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/login/Login.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/Login.java
index 67c145b..875306f 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
@@ -40,7 +40,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 0000000..66cf075
--- /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/WelcomePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java
index 72d389c..9f34a40 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/util/IOUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java
index 23103d3..d872914 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> T loadJson(InputStream is, Class<T> type) {
try {
return JsonSerialization.readValue(is, type);
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 1b2ec65..082a2a4 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<String, List<Long>> 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";
- public static void time(String operation) {
+ private Long time;
+ private String operation = DEFAULT_OPERATION;
+ private final Map<String, List<Long>> stats = new TreeMap<>();
+
+ public long elapsedTime() {
+ long elapsedTime = 0;
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));
+ elapsedTime = new Date().getTime() - time;
}
+ return elapsedTime;
+ }
+
+ public void reset() {
+ reset(operation); // log last operation
}
- private static void logOperation(String operation, long delta) {
+ 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 void logOperation(String operation, long duration) {
if (!stats.containsKey(operation)) {
stats.put(operation, new ArrayList<Long>());
}
- stats.get(operation).add(delta);
+ stats.get(operation).add(duration);
+ log.info(String.format("Operation '%s' took: %s ms", operation, duration));
+ }
+
+ public void clearStats() {
+ clearStats(true, true, true);
}
- public static void printStats() {
- if (!stats.isEmpty()) {
- System.out.println("OPERATION STATS:");
+ 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()));
+ }
}
- for (String op : stats.keySet()) {
- long sum = 0;
- for (Long t : stats.get(op)) {
- sum += t;
+ 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 92dc9c5..1e5145c 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
@@ -16,6 +16,7 @@
*/
package org.keycloak.testsuite;
+import java.io.File;
import org.keycloak.testsuite.arquillian.TestContext;
import java.util.ArrayList;
import java.util.List;
@@ -36,7 +37,6 @@ import org.keycloak.admin.client.resource.RealmsResource;
import org.keycloak.models.Constants;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
-import static org.keycloak.testsuite.admin.Users.setPasswordFor;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.arquillian.SuiteContext;
import org.keycloak.testsuite.auth.page.WelcomePage;
@@ -52,6 +52,8 @@ 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;
+import static org.keycloak.testsuite.admin.Users.setPasswordFor;
/**
*
@@ -124,7 +126,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() {
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 8e85307..ce1900d 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/ResetCredentialsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java
old mode 100755
new mode 100644
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 e07b02f..2d5645a 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);
@@ -458,4 +462,20 @@ 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();
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/AbstractUserTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/AbstractUserTest.java
new file mode 100644
index 0000000..d7dbbdc
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/AbstractUserTest.java
@@ -0,0 +1,52 @@
+package org.keycloak.testsuite.user;
+
+import javax.ws.rs.core.Response;
+import static javax.ws.rs.core.Response.Status.CREATED;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.representations.idm.UserRepresentation;
+import static org.keycloak.testsuite.admin.ApiUtil.getCreatedId;
+import static org.junit.Assert.assertEquals;
+import org.keycloak.testsuite.AbstractAuthTest;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public abstract class AbstractUserTest extends AbstractAuthTest {
+
+ protected UsersResource users() {
+ return testRealmResource().users();
+ }
+
+ protected UserResource user(UserRepresentation user) {
+ if (user.getId()==null) {
+ throw new IllegalStateException("User id cannot be null.");
+ }
+ return user(user.getId());
+ }
+
+ protected UserResource user(String id) {
+ return users().get(id);
+ }
+
+ public static UserRepresentation createUserRep(String username) {
+ UserRepresentation user = new UserRepresentation();
+ user.setUsername(username);
+ user.setEmail(username + "@email.test");
+ return user;
+ }
+
+ public UserRepresentation createUser(UserRepresentation user) {
+ return createUser(users(), user);
+ }
+
+ public UserRepresentation createUser(UsersResource users, UserRepresentation user) {
+ Response response = users.create(user);
+ assertEquals(CREATED.getStatusCode(), response.getStatus());
+ user.setId(getCreatedId(response));
+ response.close();
+ return user;
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
index 031edc7..5a1d2c6 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
@@ -299,7 +299,8 @@
],
"adminUrl": "http://localhost:8080/employee2",
"attributes": {
- "saml.authnstatement": "true"
+ "saml.authnstatement": "true",
+ "saml_idp_initiated_sso_url_name" : "employee2"
},
"protocolMappers": [
{
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
index d267f06..e0cfefd 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
@@ -60,7 +60,7 @@
<property name="enabled">${auth.server.wildfly}</property>
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
<property name="jbossHome">${keycloak.home}</property>
- <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props}</property>
+ <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Djboss.bind.address=0.0.0.0 -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props}</property>
<property name="managementPort">${auth.server.management.port}</property>
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
</configuration>
@@ -131,7 +131,7 @@
<property name="enabled">${auth.server.eap7}</property>
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
<property name="jbossHome">${keycloak.home}</property>
- <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props}</property>
+ <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Djboss.bind.address=0.0.0.0 -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props}</property>
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
<property name="managementPort">${auth.server.management.port}</property>
</configuration>
diff --git a/testsuite/integration-arquillian/tests/other/console_no_users/pom.xml b/testsuite/integration-arquillian/tests/other/console_no_users/pom.xml
new file mode 100644
index 0000000..220a10b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/console_no_users/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.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.
+ -->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-tests-other</artifactId>
+ <version>1.9.1.Final-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>integration-arquillian-tests-console-no-users</artifactId>
+
+ <name>Admin Console UI Tests - Without pre-configured accounts</name>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-admin-user-json-file</id>
+ <phase>none</phase>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/testsuite/integration-arquillian/tests/other/console_no_users/src/test/java/org/keycloak/testsuite/console/pages/WelcomePageTest.java b/testsuite/integration-arquillian/tests/other/console_no_users/src/test/java/org/keycloak/testsuite/console/pages/WelcomePageTest.java
new file mode 100644
index 0000000..1051bb1
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/console_no_users/src/test/java/org/keycloak/testsuite/console/pages/WelcomePageTest.java
@@ -0,0 +1,112 @@
+package org.keycloak.testsuite.console.pages;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.auth.page.WelcomePage;
+import org.keycloak.testsuite.auth.page.login.OIDCLogin;
+
+/**
+ *
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class WelcomePageTest extends AbstractKeycloakTest {
+
+ @Page
+ private WelcomePage welcomePage;
+
+ @Page
+ protected OIDCLogin loginPage;
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ // no operation
+ }
+
+ /*
+ * Leave out client initialization and creation of a user account. We
+ * don't need those.
+ */
+ @Before
+ @Override
+ public void beforeAbstractKeycloakTest() {
+ setDefaultPageUriParameters();
+ driverSettings();
+ }
+
+ /**
+ * Attempt to resolve the floating IP address. This is where EAP/WildFly
+ * will be accessible. See "-Djboss.bind.address=0.0.0.0".
+ *
+ * @return
+ * @throws Exception
+ */
+ private String getFloatingIpAddress() throws Exception {
+ Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
+ for (NetworkInterface ni : Collections.list(netInterfaces)) {
+ Enumeration<InetAddress> inetAddresses = ni.getInetAddresses();
+ for (InetAddress a : Collections.list(inetAddresses)) {
+ if (!a.isLoopbackAddress() && a.isSiteLocalAddress()) {
+ return a.getHostAddress();
+ }
+ }
+ }
+ return null;
+ }
+
+ private URL getPublicServerUrl() throws Exception {
+ String floatingIp = getFloatingIpAddress();
+ if (floatingIp == null) {
+ throw new RuntimeException("Could not determine floating IP address.");
+ }
+ return new URL("http", floatingIp, welcomePage.getInjectedUrl().getPort(), "");
+ }
+
+ @Test
+ public void test_1_LocalAccessNoAdmin() throws Exception {
+ welcomePage.navigateTo();
+ Assert.assertFalse("Welcome page did not ask to create a new admin user.", welcomePage.isPasswordSet());
+ }
+
+ @Test
+ public void test_2_RemoteAccessNoAdmin() throws Exception {
+ driver.navigate().to(getPublicServerUrl());
+ Assert.assertFalse("Welcome page did not ask to create a new admin user.", welcomePage.isPasswordSet());
+ }
+
+ @Test
+ public void test_3_LocalAccessWithAdmin() throws Exception {
+ welcomePage.navigateTo();
+ welcomePage.setPassword("admin", "admin");
+ Assert.assertTrue(driver.getPageSource().contains("User created"));
+
+ welcomePage.navigateTo();
+ Assert.assertTrue("Welcome page asked to set admin password.", welcomePage.isPasswordSet());
+ }
+
+ @Test
+ public void test_4_RemoteAccessWithAdmin() throws Exception {
+ driver.navigate().to(getPublicServerUrl());
+ Assert.assertTrue("Welcome page asked to set admin password.", welcomePage.isPasswordSet());
+ }
+
+ @Test
+ public void test_5_AccessCreatedAdminAccount() throws Exception {
+ welcomePage.navigateToAdminConsole();
+ loginPage.form().login("admin", "admin");
+ Assert.assertFalse("Login with 'admin:admin' failed",
+ driver.getPageSource().contains("Invalid username or password."));
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml b/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml
new file mode 100644
index 0000000..11ec6e7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.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.
+ -->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-tests-other</artifactId>
+ <version>1.9.1.Final-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>integration-arquillian-tests-jpa-performance</artifactId>
+
+ <name>Keycloak JPA Performance Tests</name>
+
+</project>
diff --git a/testsuite/integration-arquillian/tests/other/jpa-performance/README.md b/testsuite/integration-arquillian/tests/other/jpa-performance/README.md
new file mode 100644
index 0000000..d389616
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/jpa-performance/README.md
@@ -0,0 +1,48 @@
+# Keycloak JPA Performance Tests
+
+## How to run
+
+1. Build the Arquilian Base Testsuite module: `/testsuite/integration-arquillian/base`
+2. Run the test from this module using `mvn test` or `mvn clean test`.
+
+Optional parameters:
+```
+-Dmany.users.count=10000
+-Dmany.users.batch=1000
+```
+
+### With MySQL
+
+Start dockerized MySQL:
+```
+docker run --name mysql-keycloak -e MYSQL_ROOT_PASSWORD=keycloak -e MYSQL_DATABASE=keycloak -e MYSQL_USER=keycloak -e MYSQL_PASSWORD=keycloak -d -p 3306:3306 mysql
+```
+
+Additional test parameters:
+```
+-Pclean-jpa
+-Dkeycloak.connectionsJpa.url=jdbc:mysql://localhost/keycloak
+-Dkeycloak.connectionsJpa.driver=com.mysql.jdbc.Driver
+-Dkeycloak.connectionsJpa.user=keycloak
+-Dkeycloak.connectionsJpa.password=keycloak
+```
+
+### With PostgreSQL
+
+Start dockerized PostgreSQL:
+```
+docker run --name postgres-keycloak -e POSTGRES_PASSWORD=keycloak -d -p 5432:5432 postgres
+```
+
+Additional test parameters:
+```
+-Pclean-jpa
+-Dkeycloak.connectionsJpa.url=jdbc:postgresql://localhost/postgres
+-Dkeycloak.connectionsJpa.driver=org.postgresql.Driver
+-Dkeycloak.connectionsJpa.user=postgres
+-Dkeycloak.connectionsJpa.password=keycloak
+```
+
+## Reports
+
+Test creates reports in `target/stats`.
diff --git a/testsuite/integration-arquillian/tests/other/jpa-performance/src/test/java/org/keycloak/testsuite/user/ManyUsersTest.java b/testsuite/integration-arquillian/tests/other/jpa-performance/src/test/java/org/keycloak/testsuite/user/ManyUsersTest.java
new file mode 100644
index 0000000..4916393
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/jpa-performance/src/test/java/org/keycloak/testsuite/user/ManyUsersTest.java
@@ -0,0 +1,119 @@
+package org.keycloak.testsuite.user;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.util.Timer;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.util.JsonSerialization;
+import static org.junit.Assert.fail;
+import org.keycloak.admin.client.resource.RealmResource;
+import static org.keycloak.testsuite.util.IOUtil.PROJECT_BUILD_DIRECTORY;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class ManyUsersTest extends AbstractUserTest {
+
+ private static final int COUNT = Integer.parseInt(System.getProperty("many.users.count", "10000"));
+ private static final int BATCH = Integer.parseInt(System.getProperty("many.users.batch", "1000"));
+
+ private static final String REALM = "realm_with_many_users";
+
+ private List<UserRepresentation> users;
+
+ private final Timer realmTimer = new Timer();
+ private final Timer usersTimer = new Timer();
+
+ protected RealmResource realmResource() {
+ return realmsResouce().realm(REALM);
+ }
+
+ @Before
+ public void before() {
+ users = new LinkedList<>();
+ for (int i = 0; i < COUNT; i++) {
+ users.add(createUserRep("user" + i));
+ }
+
+ realmTimer.reset("create realm before test");
+ RealmRepresentation realm = new RealmRepresentation();
+ realm.setRealm(REALM);
+ realmsResouce().create(realm);
+ }
+
+ @After
+ public void after() {
+ realmTimer.clearStats(true, true, false);
+ usersTimer.clearStats();
+ }
+
+ @Test
+ public void manyUsers() throws IOException {
+ RealmRepresentation realm = realmResource().toRepresentation();
+ realm.setUsers(users);
+
+ // CREATE
+ realmTimer.reset("create " + users.size() + " users");
+ usersTimer.reset("create " + BATCH + " users");
+ int i = 0;
+ for (UserRepresentation user : users) {
+ createUser(realmResource().users(), user);
+ if (++i % BATCH == 0) {
+ usersTimer.reset();
+ log.info("Created users: " + i + " / " + users.size());
+ }
+ }
+ if (i % BATCH != 0) {
+ usersTimer.reset();
+ log.info("Created users: " + i + " / " + users.size());
+ }
+
+ // SAVE REALM
+ realmTimer.reset("save realm with " + users.size() + " users");
+ File realmFile = new File(PROJECT_BUILD_DIRECTORY, REALM + ".json");
+ JsonSerialization.writeValueToStream(new BufferedOutputStream(new FileOutputStream(realmFile)), realm);
+
+ // DELETE REALM
+ realmTimer.reset("delete realm with " + users.size() + " users");
+ realmResource().remove();
+ try {
+ realmResource().toRepresentation();
+ fail("realm not deleted");
+ } catch (Exception ex) {
+ log.debug("realm deleted");
+ }
+
+ // RE-IMPORT SAVED REALM
+ realmTimer.reset("re-import realm with " + realm.getUsers().size() + " users");
+ realmsResouce().create(realm);
+ realmTimer.reset("load " + realm.getUsers().size() + " users");
+ users = realmResource().users().search("", 0, Integer.MAX_VALUE);
+
+ // DELETE INDIVIDUAL USERS
+ realmTimer.reset("delete " + users.size() + " users");
+ usersTimer.reset("delete " + BATCH + " users", false);
+ i = 0;
+ for (UserRepresentation user : users) {
+ realmResource().users().get(user.getId()).remove();
+ if (++i % BATCH == 0) {
+ usersTimer.reset();
+ log.info("Deleted users: " + i + " / " + users.size());
+ }
+ }
+ if (i % BATCH != 0) {
+ usersTimer.reset();
+ log.info("Deleted users: " + i + " / " + users.size());
+ }
+ realmTimer.reset();
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/pom.xml b/testsuite/integration-arquillian/tests/other/pom.xml
index 19648b7..ec894b4 100644
--- a/testsuite/integration-arquillian/tests/other/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/pom.xml
@@ -135,11 +135,23 @@
</modules>
</profile>
<profile>
+ <id>console-ui-no-users-tests</id>
+ <modules>
+ <module>console_no_users</module>
+ </modules>
+ </profile>
+ <profile>
<id>mod_auth_mellon</id>
<modules>
<module>mod_auth_mellon</module>
</modules>
</profile>
+ <profile>
+ <id>jpa-performance</id>
+ <modules>
+ <module>jpa-performance</module>
+ </modules>
+ </profile>
</profiles>
</project>
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index b2438dd..7addcc0 100644
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -108,6 +108,7 @@
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
+ <project.build.directory>${project.build.directory}</project.build.directory>
<browser>${browser}</browser>
<firefox_binary>${firefox_binary}</firefox_binary>
<shouldDeploy>false</shouldDeploy>
@@ -223,6 +224,12 @@
<version>2.1.0.Alpha3</version><!-- TODO upgrade <arquillian-graphene.version> and use ${arquillian-graphene.version} -->
</dependency>
+ <dependency>
+ <groupId>jfree</groupId>
+ <artifactId>jfreechart</artifactId>
+ <version>1.0.13</version>
+ </dependency>
+
<!-- <dependency>
<groupId>org.arquillian.extension</groupId>
<artifactId>arquillian-recorder-reporter-impl</artifactId>
@@ -411,10 +418,83 @@
<groupId>org.codehaus.mojo</groupId>
<artifactId>xml-maven-plugin</artifactId>
</plugin>
+ <plugin>
+ <groupId>org.liquibase</groupId>
+ <artifactId>liquibase-maven-plugin</artifactId>
+ </plugin>
</plugins>
</build>
</profile>
+ <!-- MySQL -->
+ <profile>
+ <activation>
+ <property>
+ <name>keycloak.connectionsJpa.driver</name>
+ <value>com.mysql.jdbc.Driver</value>
+ </property>
+ </activation>
+ <id>mysql</id>
+ <dependencies>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ </dependency>
+ </dependencies>
+ </profile>
+
+ <!-- PostgreSQL -->
+ <profile>
+ <activation>
+ <property>
+ <name>keycloak.connectionsJpa.driver</name>
+ <value>org.postgresql.Driver</value>
+ </property>
+ </activation>
+ <id>postgresql</id>
+ <dependencies>
+ <dependency>
+ <groupId>org.postgresql</groupId>
+ <artifactId>postgresql</artifactId>
+ <version>${postgresql.version}</version>
+ </dependency>
+ </dependencies>
+ </profile>
+
+ <profile>
+ <id>clean-jpa</id>
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.liquibase</groupId>
+ <artifactId>liquibase-maven-plugin</artifactId>
+ <configuration>
+ <changeLogFile>META-INF/jpa-changelog-master.xml</changeLogFile>
+
+ <url>${keycloak.connectionsJpa.url}</url>
+ <driver>${keycloak.connectionsJpa.driver}</driver>
+ <username>${keycloak.connectionsJpa.user}</username>
+ <password>${keycloak.connectionsJpa.password}</password>
+
+ <promptOnNonLocalDatabase>false</promptOnNonLocalDatabase>
+ <databaseClass>${keycloak.connectionsJpa.liquibaseDatabaseClass}</databaseClass>
+ </configuration>
+ <executions>
+ <execution>
+ <id>clean-jpa</id>
+ <phase>clean</phase>
+ <goals>
+ <goal>dropAll</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ </profile>
+
<profile>
<id>auth-server-wildfly</id>
<properties>
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 57836f5..3b4a974 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -38,6 +38,7 @@ sslRequired.option.external=external requests
sslRequired.option.none=none
sslRequired.tooltip=Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses.
publicKey=Public key
+privateKey=Private key
gen-new-keys=Generate new keys
certificate=Certificate
host=Host
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 150f57f..cf5c8e7 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -1171,17 +1171,8 @@ module.controller('CreateClientCtrl', function($scope, realm, client, templates,
$scope.save = function() {
-
$scope.client.protocol = $scope.protocol;
- if ($scope.client.protocol == 'openid-connect' && !$scope.client.rootUrl) {
- Notifications.error("You must specify the root URL of application");
- }
-
- if ($scope.client.protocol == 'saml' && !$scope.client.adminUrl) {
- Notifications.error("You must specify the SAML Endpoint URL");
- }
-
Client.save({
realm: realm.realm,
client: ''
@@ -1599,8 +1590,8 @@ module.controller('ClientProtocolMapperListCtrl', function($scope, realm, client
});
module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo, client, mapper, ClientProtocolMapper, Notifications, Dialog, $location) {
- /*
$scope.realm = realm;
+ /*
$scope.client = client;
$scope.create = false;
$scope.protocol = client.protocol;
@@ -1674,12 +1665,13 @@ module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo
});
module.controller('ClientProtocolMapperCreateCtrl', function($scope, realm, serverInfo, client, ClientProtocolMapper, Notifications, Dialog, $location) {
+ $scope.realm = realm;
+
if (client.protocol == null) {
client.protocol = 'openid-connect';
}
var protocol = client.protocol;
/*
- $scope.realm = realm;
$scope.client = client;
$scope.create = true;
$scope.protocol = protocol;
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index c93e8c6..94b550d 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -990,8 +990,21 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
};
});
-module.controller('RealmKeysDetailCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications) {
- $scope.realm = realm;
+module.controller('RealmKeysDetailCtrl', function($scope, Realm, realm, $http, $route, $location, Dialog, Notifications) {
+ $scope.realm = angular.copy(realm);
+ $scope.enableUpload = false;
+
+ $scope.$watch('realm', function () {
+ if (!angular.equals($scope.realm, realm)) {
+ if ($scope.realm.privateKey && $scope.realm.publicKey != realm.publicKey) {
+ $scope.enableUpload = true;
+ } else if ($scope.realm.certificate != realm.certificate) {
+ $scope.enableUpload = true;
+ } else {
+ $scope.enableUpload = false;
+ }
+ }
+ }, true);
$scope.generate = function() {
Dialog.confirmGenerateKeys($scope.realm.realm, 'realm', function() {
@@ -1003,6 +1016,34 @@ module.controller('RealmKeysDetailCtrl', function($scope, Realm, realm, $http, $
});
});
};
+
+ $scope.cancel = function() {
+ $route.reload();
+ }
+
+ $scope.save = function() {
+ var title = 'Upload keys for realm';
+ var msg = 'Are you sure you want to upload keys for ' + $scope.realm.realm + '?';
+ var btns = {
+ ok: {
+ label: 'Upload Keys',
+ cssClass: 'btn btn-danger'
+ },
+ cancel: {
+ label: 'Cancel',
+ cssClass: 'btn btn-default'
+ }
+ };
+
+ Dialog.open(title, msg, btns, function() {
+ Realm.update($scope.realm, function () {
+ Notifications.success('Keys uploaded for realm.');
+ Realm.get({ id : realm.realm }, function(updated) {
+ $scope.realm = updated;
+ })
+ });
+ });
+ };
});
module.controller('RealmSessionStatsCtrl', function($scope, realm, stats, RealmClientSessionStats, RealmLogoutAll, Notifications) {
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index 77810f5..bbbb843 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -220,14 +220,14 @@
<div class="input-group" ng-repeat="(i, redirectUri) in client.redirectUris track by $index">
<input class="form-control" ng-model="client.redirectUris[i]">
<div class="input-group-addon">
- <i class="pficon pficon-remove" style="width: 10px;" data-ng-click="deleteRedirectUri($index)"></i>
+ <i class="fa fa-minus" style="width: 10px;" data-ng-click="deleteRedirectUri($index)"></i>
</div>
</div>
<div class="input-group">
<input class="form-control" ng-model="newRedirectUri" id="newRedirectUri">
<div class="input-group-addon">
- <i class="pficon pficon-add" style="width: 10px;" data-ng-click="newRedirectUri.length > 0 && addRedirectUri()"></i>
+ <i class="fa fa-plus" style="width: 10px;" data-ng-click="newRedirectUri.length > 0 && addRedirectUri()"></i>
</div>
</div>
</div>
@@ -279,14 +279,14 @@
<div class="input-group" ng-repeat="(i, webOrigin) in client.webOrigins track by $index">
<input class="form-control" ng-model="client.webOrigins[i]">
<div class="input-group-addon">
- <i class="pficon pficon-remove" style="width: 10px;" data-ng-click="deleteWebOrigin($index)"></i>
+ <i class="fa fa-minus" style="width: 10px;" data-ng-click="deleteWebOrigin($index)"></i>
</div>
</div>
<div class="input-group">
<input class="form-control" ng-model="newWebOrigin" id="newWebOrigin">
<div class="input-group-addon">
- <i class="pficon pficon-add" style="width: 10px;" data-ng-click="newWebOrigin.length > 0 && addWebOrigin()"></i>
+ <i class="fa fa-plus" style="width: 10px;" data-ng-click="newWebOrigin.length > 0 && addWebOrigin()"></i>
</div>
</div>
</div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/create-client.html b/themes/src/main/resources/theme/base/admin/resources/partials/create-client.html
index 757f077..98609c8 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/create-client.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/create-client.html
@@ -18,8 +18,8 @@
</div>
<div class="col-md-6" data-ng-show="importing">
- <button class="btn btn-default" data-ng-click="viewImportDetails()">{{:: 'view-details' | translate}}</button>
- <button class="btn btn-default" data-ng-click="reset()">{{:: 'clear-import' | translate}}</button>
+ <button class="btn btn-default" type="button" data-ng-click="viewImportDetails()">{{:: 'view-details' | translate}}</button>
+ <button class="btn btn-default" type="button" data-ng-click="reset()">{{:: 'clear-import' | translate}}</button>
</div>
</div>
@@ -58,14 +58,14 @@
<kc-tooltip>{{:: 'client-template.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-hide="protocol == 'saml'">
- <label class="col-md-2 control-label" for="rootUrl">{{:: 'root-url' | translate}} <span class="required">*</span></label>
+ <label class="col-md-2 control-label" for="rootUrl">{{:: 'root-url' | translate}}</label>
<div class="col-sm-6">
<input class="form-control" type="text" name="rootUrl" id="rootUrl" data-ng-model="client.rootUrl">
</div>
<kc-tooltip>{{:: 'root-url.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-show="protocol == 'saml'">
- <label class="col-md-2 control-label" for="masterSamlUrl">{{:: 'client-saml-endpoint' | translate}} <span class="required">*</span></label>
+ <label class="col-md-2 control-label" for="masterSamlUrl">{{:: 'client-saml-endpoint' | translate}}</label>
<div class="col-sm-6">
<input class="form-control" type="text" name="masterSamlUrl" id="masterSamlUrl"
data-ng-model="client.adminUrl">
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html
index 93301af..b8a16c9 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html
@@ -4,25 +4,38 @@
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<fieldset class="border-top">
<div class="form-group">
+ <label class="col-md-2 control-label" for="privateKey">{{:: 'privateKey' | translate}}</label>
+
+ <div class="col-md-10">
+ <textarea type="password" id="privateKey" name="privateKey" class="form-control" rows="{{!realm.privateKey ? 1 : 8}}" data-ng-model="realm.privateKey"></textarea>
+ </div>
+ </div>
+ <div class="form-group">
<label class="col-md-2 control-label" for="publicKey">{{:: 'publicKey' | translate}}</label>
<div class="col-md-10">
<textarea type="text" id="publicKey" name="publicKey" class="form-control" rows="4"
- kc-select-action="click" readonly>{{realm.publicKey}}</textarea>
+ kc-select-action="click" data-ng-model="realm.publicKey"></textarea>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="certificate">{{:: 'certificate' | translate}}</label>
<div class="col-md-10">
- <textarea type="text" id="certificate" name="certificate" class="form-control" rows="8" kc-select-action="click" readonly>{{realm.certificate}}</textarea>
+ <textarea type="text" id="certificate" name="certificate" class="form-control" rows="8" kc-select-action="click" data-ng-model="realm.certificate"></textarea>
</div>
</div>
</fieldset>
<div class="form-group" data-ng-show="access.manageRealm">
<div class="col-md-10 col-md-offset-2">
- <button class="btn btn-danger" type="submit" data-ng-click="generate()">{{:: 'gen-new-keys' | translate}}</button>
+ <button class="btn btn-danger" type="button" data-ng-click="generate()" data-ng-disabled="enableUpload">{{:: 'gen-new-keys' | translate}}</button>
+ </div>
+ </div>
+ <div class="form-group" data-ng-show="access.manageRealm">
+ <div class="col-md-10 col-md-offset-2">
+ <button class="btn btn-danger" type="button" data-ng-click="save()" data-ng-disabled="!enableUpload">{{:: 'upload-keys' | translate}}</button>
+ <button class="btn btn-default" type="button" data-ng-click="cancel()" data-ng-disabled="!enableUpload">{{:: 'cancel' | translate}}</button>
</div>
</div>
</form>
diff --git a/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/KerberosEmbeddedServer.java b/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/KerberosEmbeddedServer.java
index 1f8fe07..d7f868f 100644
--- a/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/KerberosEmbeddedServer.java
+++ b/util/embedded-ldap/src/main/java/org/keycloak/util/ldap/KerberosEmbeddedServer.java
@@ -19,8 +19,11 @@ package org.keycloak.util.ldap;
import java.io.IOException;
import java.lang.reflect.Field;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashSet;
+import java.util.Locale;
import java.util.Properties;
import java.util.Set;
@@ -43,13 +46,7 @@ import org.apache.directory.server.protocol.shared.transport.UdpTransport;
import org.apache.directory.shared.kerberos.KerberosTime;
import org.apache.directory.shared.kerberos.KerberosUtils;
import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
-import org.ietf.jgss.GSSException;
-import org.ietf.jgss.GSSManager;
-import org.ietf.jgss.GSSName;
import org.jboss.logging.Logger;
-import org.keycloak.common.util.KerberosSerializationUtils;
-import sun.security.jgss.GSSNameImpl;
-import sun.security.jgss.krb5.Krb5NameElement;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -113,15 +110,8 @@ public class KerberosEmbeddedServer extends LDAPEmbeddedServer {
this.kdcEncryptionTypes = readProperty(PROPERTY_KDC_ENCTYPES, DEFAULT_KDC_ENCRYPTION_TYPES);
if (ldapSaslPrincipal == null || ldapSaslPrincipal.isEmpty()) {
- try {
- // Same algorithm like sun.security.krb5.PrincipalName constructor
- GSSName gssName = GSSManager.getInstance().createName("ldap@" + bindHost, GSSName.NT_HOSTBASED_SERVICE);
- GSSNameImpl gssName1 = (GSSNameImpl) gssName;
- Krb5NameElement krb5NameElement = (Krb5NameElement) gssName1.getElement(KerberosSerializationUtils.KRB5_OID);
- this.ldapSaslPrincipal = krb5NameElement.getKrb5PrincipalName().toString();
- } catch (GSSException uhe) {
- throw new RuntimeException(uhe);
- }
+ String hostname = getHostnameForSASLPrincipal(bindHost);
+ this.ldapSaslPrincipal = "ldap/" + hostname + "@" + this.kerberosRealm;
}
}
@@ -219,6 +209,31 @@ public class KerberosEmbeddedServer extends LDAPEmbeddedServer {
}
+ // Forked from sun.security.krb5.PrincipalName constructor
+ private String getHostnameForSASLPrincipal(String hostName) {
+ try {
+ // RFC4120 does not recommend canonicalizing a hostname.
+ // However, for compatibility reason, we will try
+ // canonicalize it and see if the output looks better.
+
+ String canonicalized = (InetAddress.getByName(hostName)).
+ getCanonicalHostName();
+
+ // Looks if canonicalized is a longer format of hostName,
+ // we accept cases like
+ // bunny -> bunny.rabbit.hole
+ if (canonicalized.toLowerCase(Locale.ENGLISH).startsWith(
+ hostName.toLowerCase(Locale.ENGLISH)+".")) {
+ hostName = canonicalized;
+ }
+ } catch (UnknownHostException | SecurityException e) {
+ // not canonicalized or no permission to do so, use old
+ }
+ return hostName.toLowerCase(Locale.ENGLISH);
+ }
+
+
+
/**
* Replacement of apacheDS KdcServer class with disabled ticket replay cache.
*