From 37584a24e06a80e6ffa9bb123d7c3c7a31901e50 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Sat, 27 Feb 2016 17:22:23 -0500 Subject: [PATCH] unsecure url has principal KEYCLOAK-2550 Typo in userguide KEYCLOAK-1928 Kerberos working with IBM JDK KEYCLOAK-1928 Remove sun.jdk.jgss module KEYCLOAK-1928 Fix kerberos with adapter on JDK7 KPR-147 - Initial login scenarios around admin password - test KEYCLOAK-2561 Fix issues with blank password KEYCLOAK-2559 Missing add/remove button for 'Valid Redirect URIs' in a client settings form Added simple test for JPA performance (with many users). Fixed "re-import" operation logging. Fixed for Timer.saveData() Fixed for Timer.saveData() ManyUsersTest: ArrayList --> LinkedList Fix AbstractUserTest Fix parentheses in login page object Add tests for IDP initiated login KEYCLOAK-1040 Allow import of realm keys (like we do for SAML) KEYCLOAK-2556 Remove required for client create root url and saml endpoint KEYCLOAK-2555 ForbiddenException when importing test realm or creating test user KEYCLOAK-2553 Unexpected form behavior while creating a client KEYCLOAK-2551 Broken navigation links while creating/editing a Client Mapper --- .../AbstractKeycloakJettyAuthenticator.java | 36 +++ .../servlet/OIDCFilterSessionStore.java | 2 + .../tomcat/CatalinaSessionTokenStore.java | 17 +- .../undertow/ServletSessionTokenStore.java | 4 +- .../undertow/UndertowSessionTokenStore.java | 3 + .../saml/jetty/AbstractSamlAuthenticator.java | 36 +++ .../saml/AbstractSamlAuthenticatorValve.java | 5 +- .../common/constants/KerberosConstants.java | 23 +- .../common/util/KerberosJdkProvider.java | 227 ++++++++++++++++++ .../util/KerberosSerializationUtils.java | 93 +------ .../java/org/keycloak/KeyPairVerifier.java | 59 +++++ .../org/keycloak/KeycloakSecurityContext.java | 3 + .../org/keycloak/KeyPairVerifierTest.java | 69 ++++++ .../keycloak/keycloak-common/main/module.xml | 1 - .../keycloak/keycloak-core/main/module.xml | 1 - .../keycloak/keycloak-common/main/module.xml | 1 - .../keycloak/keycloak-core/main/module.xml | 1 - .../keycloak/keycloak-common/main/module.xml | 1 - .../keycloak/keycloak-core/main/module.xml | 1 - .../keycloak/keycloak-common/main/module.xml | 1 - .../keycloak/keycloak-core/main/module.xml | 1 - .../keycloak/keycloak-common/main/module.xml | 1 - .../keycloak/keycloak-core/main/module.xml | 1 - .../keycloak/keycloak-common/main/module.xml | 1 - .../keycloak/keycloak-common/main/module.xml | 1 - .../en/en-US/modules/adapter-context.xml | 12 + .../en/en-US/modules/client-registration.xml | 2 +- .../kerberos/CommonKerberosConfig.java | 2 +- .../KerberosServerSubjectAuthenticator.java | 41 ++-- ...KerberosUsernamePasswordAuthenticator.java | 21 +- .../kerberos/impl/SPNEGOAuthenticator.java | 22 +- .../admin/client/token/TokenManager.java | 34 +-- .../admin/client/token/TokenService.java | 4 +- .../META-INF/jpa-changelog-1.9.1.xml | 24 +- .../META-INF/jpa-changelog-master.xml | 1 + .../hash/Pbkdf2PasswordHashProvider.java | 4 +- .../models/utils/CredentialValidation.java | 2 +- .../models/utils/RepresentationToModel.java | 10 + .../services/resources/AccountService.java | 2 +- .../resources/admin/RealmAdminResource.java | 18 +- .../resources/admin/UsersResource.java | 4 + testsuite/integration-arquillian/README.md | 1 + .../testsuite/arquillian/TestContext.java | 12 + .../testsuite/auth/page/WelcomePage.java | 8 +- .../testsuite/auth/page/login/Login.java | 2 +- .../page/login/SAMLIDPInitiatedLogin.java | 19 ++ .../org/keycloak/testsuite/util/IOUtil.java | 2 + .../org/keycloak/testsuite/util/Timer.java | 151 +++++++++--- .../testsuite/AbstractKeycloakTest.java | 5 +- .../testsuite/account/ChangePasswordTest.java | 3 + .../account/ResetCredentialsTest.java | 0 .../AbstractDemoServletsAdapterTest.java | 0 .../AbstractSAMLServletsAdapterTest.java | 20 ++ .../testsuite/user/AbstractUserTest.java | 52 ++++ .../adapter-test/keycloak-saml/testsaml.json | 3 +- .../base/src/test/resources/arquillian.xml | 4 +- .../tests/other/console_no_users/pom.xml | 49 ++++ .../console/pages/WelcomePageTest.java | 112 +++++++++ .../tests/other/jpa-performance/README.md | 48 ++++ .../tests/other/jpa-performance/pom.xml | 34 +++ .../testsuite/user/ManyUsersTest.java | 119 +++++++++ .../tests/other/pom.xml | 12 + .../integration-arquillian/tests/pom.xml | 80 ++++++ .../adapter/AdapterTestStrategy.java | 6 + .../testsuite/adapter/FilterAdapterTest.java | 1 + .../testsuite/adapter/InputServlet.java | 14 ++ .../testsuite/admin/ConcurrencyTest.java | 4 +- .../keycloak/testsuite/admin/RealmTest.java | 82 +++++++ .../keycloak/testsuite/admin/UserTest.java | 17 ++ .../federation/AbstractKerberosTest.java | 4 + .../testsuite/keycloaksaml/InputServlet.java | 13 + .../keycloaksaml/SamlAdapterTestStrategy.java | 6 + .../resources/META-INF/keycloak-server.json | 2 +- .../kerberos-ldap-connection.properties | 2 +- .../test/resources/kerberos/test-krb5.conf | 6 +- .../src/test/resources/keys/certificate.pem | 21 ++ .../src/test/resources/keys/private2048.pem | 27 +++ .../src/test/resources/keys/private4096.pem | 51 ++++ .../src/test/resources/keys/public2048.pem | 9 + .../src/test/resources/keys/public4096.pem | 14 ++ .../messages/admin-messages_en.properties | 1 + .../admin/resources/js/controllers/clients.js | 14 +- .../admin/resources/js/controllers/realm.js | 45 +++- .../resources/partials/client-detail.html | 8 +- .../resources/partials/create-client.html | 8 +- .../admin/resources/partials/realm-keys.html | 19 +- .../util/ldap/KerberosEmbeddedServer.java | 45 ++-- 87 files changed, 1684 insertions(+), 263 deletions(-) create mode 100644 common/src/main/java/org/keycloak/common/util/KerberosJdkProvider.java create mode 100644 core/src/main/java/org/keycloak/KeyPairVerifier.java create mode 100644 core/src/test/java/org/keycloak/KeyPairVerifierTest.java create mode 100755 docbook/auth-server-docs/reference/en/en-US/modules/adapter-context.xml rename distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/sun/jdk/jgss/main/module.xml => model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.1.xml (58%) mode change 100644 => 100755 create mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/SAMLIDPInitiatedLogin.java mode change 100755 => 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java mode change 100755 => 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/AbstractUserTest.java create mode 100644 testsuite/integration-arquillian/tests/other/console_no_users/pom.xml create mode 100644 testsuite/integration-arquillian/tests/other/console_no_users/src/test/java/org/keycloak/testsuite/console/pages/WelcomePageTest.java create mode 100644 testsuite/integration-arquillian/tests/other/jpa-performance/README.md create mode 100644 testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml create mode 100644 testsuite/integration-arquillian/tests/other/jpa-performance/src/test/java/org/keycloak/testsuite/user/ManyUsersTest.java create mode 100644 testsuite/integration/src/test/resources/keys/certificate.pem create mode 100644 testsuite/integration/src/test/resources/keys/private2048.pem create mode 100644 testsuite/integration/src/test/resources/keys/private4096.pem create mode 100644 testsuite/integration/src/test/resources/keys/public2048.pem create mode 100644 testsuite/integration/src/test/resources/keys/public4096.pem diff --git a/adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java b/adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java index f8d72783f6..56d8f51298 100755 --- a/adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java +++ b/adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java @@ -18,6 +18,8 @@ package org.keycloak.adapters.jetty.core; import org.eclipse.jetty.security.DefaultUserIdentity; +import org.eclipse.jetty.security.IdentityService; +import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.security.ServerAuthException; import org.eclipse.jetty.security.UserAuthentication; import org.eclipse.jetty.security.authentication.DeferredAuthentication; @@ -135,10 +137,44 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica return new DefaultUserIdentity(theSubject, principal, theRoles); } + private class DummyLoginService implements LoginService { + @Override + public String getName() { + return null; + } + + @Override + public UserIdentity login(String username, Object credentials) { + return null; + } + + @Override + public boolean validate(UserIdentity user) { + return false; + } + + @Override + public IdentityService getIdentityService() { + return null; + } + + @Override + public void setIdentityService(IdentityService service) { + + } + + @Override + public void logout(UserIdentity user) { + + } + } + @Override public void setConfiguration(AuthConfiguration configuration) { //super.setConfiguration(configuration); initializeKeycloak(); + // need this so that getUserPrincipal does not throw NPE + _loginService = new DummyLoginService(); String error = configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE); setErrorPage(error); } diff --git a/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java b/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java index 8a3010ddd9..70a67de8a5 100755 --- a/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java +++ b/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java @@ -89,6 +89,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte protected void cleanSession(HttpSession session) { session.removeAttribute(KeycloakAccount.class.getName()); + session.removeAttribute(KeycloakSecurityContext.class.getName()); clearSavedRequest(session); } @@ -160,6 +161,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte SerializableKeycloakAccount sAccount = new SerializableKeycloakAccount(roles, account.getPrincipal(), securityContext); HttpSession httpSession = request.getSession(); httpSession.setAttribute(KeycloakAccount.class.getName(), sAccount); + httpSession.setAttribute(KeycloakSecurityContext.class.getName(), sAccount.getKeycloakSecurityContext()); if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getClientSession(), account.getPrincipal().getName(), httpSession.getId()); //String username = securityContext.getToken().getSubject(); //log.fine("userSessionManagement.login: " + username); diff --git a/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java index 7734c2d5d5..0a07e9e6a3 100755 --- a/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java +++ b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java @@ -69,12 +69,22 @@ public class CatalinaSessionTokenStore extends CatalinaAdapterSessionStore imple // just in case session got serialized if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this); - if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return; + if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) { + request.setAttribute(KeycloakSecurityContext.class.getName(), session); + request.setUserPrincipal(account.getPrincipal()); + request.setAuthType("KEYCLOAK"); + return; + } // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will // not be updated boolean success = session.refreshExpiredToken(false); - if (success && session.isActive()) return; + if (success && session.isActive()) { + request.setAttribute(KeycloakSecurityContext.class.getName(), session); + request.setUserPrincipal(account.getPrincipal()); + request.setAuthType("KEYCLOAK"); + return; + } // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session log.fine("Cleanup and expire session " + catalinaSession.getId() + " after failed refresh"); @@ -85,6 +95,8 @@ public class CatalinaSessionTokenStore extends CatalinaAdapterSessionStore imple } protected void cleanSession(Session catalinaSession) { + catalinaSession.getSession().removeAttribute(KeycloakSecurityContext.class.getName()); + catalinaSession.getSession().removeAttribute(SerializableKeycloakAccount.class.getName()); catalinaSession.getSession().removeAttribute(OidcKeycloakAccount.class.getName()); catalinaSession.setPrincipal(null); catalinaSession.setAuthType(null); @@ -164,6 +176,7 @@ public class CatalinaSessionTokenStore extends CatalinaAdapterSessionStore imple session.setPrincipal(principal); session.setAuthType("KEYCLOAK"); session.getSession().setAttribute(SerializableKeycloakAccount.class.getName(), sAccount); + session.getSession().setAttribute(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext()); String username = securityContext.getToken().getSubject(); log.fine("userSessionManagement.login: " + username); this.sessionManagement.login(session); diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java index 63c27d78da..5db3eadca3 100755 --- a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java +++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java @@ -92,7 +92,8 @@ public class ServletSessionTokenStore implements AdapterTokenStore { } else { log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session"); try { - session.setAttribute(KeycloakUndertowAccount.class.getName(), null); + session.removeAttribute(KeycloakUndertowAccount.class.getName()); + session.removeAttribute(KeycloakSecurityContext.class.getName()); session.invalidate(); } catch (Exception e) { log.debug("Failed to invalidate session, might already be invalidated"); @@ -106,6 +107,7 @@ public class ServletSessionTokenStore implements AdapterTokenStore { final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); HttpSession session = getSession(true); session.setAttribute(KeycloakUndertowAccount.class.getName(), account); + session.setAttribute(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext()); sessionManagement.login(servletRequestContext.getDeployment().getSessionManager()); } diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java index de57268635..e578f85bbe 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 07f2a40f0e..eb17feed55 100755 --- a/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java +++ b/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java @@ -18,6 +18,8 @@ package org.keycloak.adapters.saml.jetty; import org.eclipse.jetty.security.DefaultUserIdentity; +import org.eclipse.jetty.security.IdentityService; +import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.security.ServerAuthException; import org.eclipse.jetty.security.UserAuthentication; import org.eclipse.jetty.security.authentication.DeferredAuthentication; @@ -135,12 +137,46 @@ public abstract class AbstractSamlAuthenticator extends LoginAuthenticator { } + private class DummyLoginService implements LoginService { + @Override + public String getName() { + return null; + } + + @Override + public UserIdentity login(String username, Object credentials) { + return null; + } + + @Override + public boolean validate(UserIdentity user) { + return false; + } + + @Override + public IdentityService getIdentityService() { + return null; + } + + @Override + public void setIdentityService(IdentityService service) { + + } + + @Override + public void logout(UserIdentity user) { + + } + } + @Override public void setConfiguration(AuthConfiguration configuration) { //super.setConfiguration(configuration); initializeKeycloak(); + // need this so that getUserPrincipal does not throw NPE + _loginService = new DummyLoginService(); String error = configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE); setErrorPage(error); } diff --git a/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java b/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java index 51bdb4b758..aa75439d11 100755 --- a/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java +++ b/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java @@ -167,9 +167,9 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i @Override public void invoke(Request request, Response response) throws IOException, ServletException { log.fine("*********************** SAML ************"); + CatalinaHttpFacade facade = new CatalinaHttpFacade(response, request); + SamlDeployment deployment = deploymentContext.resolveDeployment(facade); if (request.getRequestURI().substring(request.getContextPath().length()).endsWith("/saml")) { - CatalinaHttpFacade facade = new CatalinaHttpFacade(response, request); - SamlDeployment deployment = deploymentContext.resolveDeployment(facade); if (deployment != null && deployment.isConfigured()) { SamlSessionStore tokenStore = getSessionStore(request, facade, deployment); SamlAuthenticator authenticator = new CatalinaSamlEndpoint(facade, deployment, tokenStore); @@ -180,6 +180,7 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i } try { + getSessionStore(request, facade, deployment).isLoggedIn(); // sets request UserPrincipal if logged in. we do this so that the UserPrincipal is available on unsecured, unconstrainted URLs super.invoke(request, response); } finally { } diff --git a/common/src/main/java/org/keycloak/common/constants/KerberosConstants.java b/common/src/main/java/org/keycloak/common/constants/KerberosConstants.java index 4089a0e018..b644f167ae 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 Marek Posolda */ @@ -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 0000000000..00a5f12885 --- /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 Marek Posolda + */ +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 principals = Collections.singleton(kerberosPrincipal); + Set publicCreds = Collections.singleton(gssName); + Set privateCreds = Collections.singleton(kerberosTicket); + Subject subject = new Subject(false, principals, publicCreds, privateCreds); + + return Subject.doAs(subject, new PrivilegedExceptionAction() { + + @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 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 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 kerberosTickets = subject.getPrivateCredentials(KerberosTicket.class); + Iterator 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 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 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 bc9d174d84..17a11e492e 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()); - } + kerberosTicket = KerberosJdkProvider.getProvider().gssCredentialToKerberosTicket(kerberosTicket, gssCredential); - 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()); - } - } - } - - 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/KeyPairVerifier.java b/core/src/main/java/org/keycloak/KeyPairVerifier.java new file mode 100644 index 0000000000..c8b6dce612 --- /dev/null +++ b/core/src/main/java/org/keycloak/KeyPairVerifier.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak; + +import org.keycloak.common.VerificationException; +import org.keycloak.common.util.PemUtils; +import org.keycloak.jose.jws.JWSBuilder; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.crypto.RSAProvider; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * @author Stian Thorgersen + */ +public class KeyPairVerifier { + + public static void verify(String privateKeyPem, String publicKeyPem) throws VerificationException { + PrivateKey privateKey; + try { + privateKey = PemUtils.decodePrivateKey(privateKeyPem); + } catch (Exception e) { + throw new VerificationException("Failed to decode private key"); + } + + PublicKey publicKey; + try { + publicKey = PemUtils.decodePublicKey(publicKeyPem); + } catch (Exception e) { + throw new VerificationException("Failed to decode public key"); + } + + try { + String jws = new JWSBuilder().content("content".getBytes()).rsa256(privateKey); + if (!RSAProvider.verify(new JWSInput(jws), publicKey)) { + throw new VerificationException("Keys don't match"); + } + } catch (Exception e) { + throw new VerificationException("Keys don't match"); + } + } + +} diff --git a/core/src/main/java/org/keycloak/KeycloakSecurityContext.java b/core/src/main/java/org/keycloak/KeycloakSecurityContext.java index 3806ffeaab..118fd1be73 100755 --- a/core/src/main/java/org/keycloak/KeycloakSecurityContext.java +++ b/core/src/main/java/org/keycloak/KeycloakSecurityContext.java @@ -28,6 +28,9 @@ import java.io.ObjectOutputStream; import java.io.Serializable; /** + * Available in secured requests under HttpServlerRequest.getAttribute() + * Also available in HttpSession.getAttribute under the classname of this class + * * @author Bill Burke * @version $Revision: 1 $ */ diff --git a/core/src/test/java/org/keycloak/KeyPairVerifierTest.java b/core/src/test/java/org/keycloak/KeyPairVerifierTest.java new file mode 100644 index 0000000000..da76a611b9 --- /dev/null +++ b/core/src/test/java/org/keycloak/KeyPairVerifierTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.common.VerificationException; + +/** + * @author Stian Thorgersen + */ +public class KeyPairVerifierTest { + + String privateKey1 = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y="; + String publicKey1 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"; + + String privateKey2048 = "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEpQIBAAKCAQEA4V3MpOnuKsdBbR1UzNjK9o5meEMQ4s5Vpykhv1DpqTilKOiE\n" + + "H7VQ/XtjNxw0yjnFBilCnpK6yN9mDEHbBEzaRjtdrgVhkIejiaXFBP5MBhUQ5l9u\n" + "8E3IZC3E8pwDjVF0Z9u0R4lGeUg2k6O+NKumqIvxoLCTuG0zf53bctGsRd57LuFi\n" + + "pgCkNyxvscOhulsbEMYrLwlb5bMGgx9v+RCnwvunNEb7RK+5pzP+iH1MRejRsX+U\n" + "7h9zHRn2gQhIl7SzG9GXebuPWr4KKwfMHWy0PEuQrsfWRXm9/dTEavbfNkv5E53z\n" + + "WXjWyf93ezkVhBX0YoXmf6UO7PAlvsrjno3TuwIDAQABAoIBAQC5iCAOcCtLemhp\n" + "bOlADwXgPtErFoNTROyMxjbrKrCCSIjniawj8oAvfiHq38Sx6ydBcDxREZjF/+wi\n" + + "ESE+hAp6ISt5NSLh+lhu3FK7TqLFqxgTn+NT36Umm+t0k231LGa5jcz3y5KCDCoq\n" + "F3ZiJCH6xeLxGA00mmn4GLvt5aF+jiO80ICGs4iUg99IoXhc5u/VU0hB5J78BinW\n" + + "inkCABuBNkDLgIqc9BoH4L5MOx3zDqzmHffeq9+2V4X7NiD5QyiyWtABaQpEIY5k\n" + "R48RTno6xN3hvG48/DwkO2gABSLQ/OJd3Hupv4wlmmSc1xo93CaV44hq2i2GsU1i\n" + + "m6d3xDW5AoGBAPCfkvPkqr88xg+8Cu3G/3GFpUsQ0VEme+8dIjXMTJHa13K7xaRh\n" + "GHCVg4a8oHJ/P/vNSwvPyR71iRX4csqkKSaprvJk8vxbU539unmHWKkfUHrywQlz\n" + + "q4OuXOjOdvILLOTsu3/+k6vAIE6SZJiDmf2eGxi9Qbm5rlxE3h3HRAKfAoGBAO/E\n" + "ogHV86LmnJTJbx1hP3IfRHk0qaiSj35ljlAz+3v6GN/KSUYCWTtp2GjRIKY3qQ8I\n" + + "7l+PVTFg3SY7cPq2C9TE+6xroiWkUd2JldPLYSxpWpFNYlo709SzmLquDho+fwJC\n" + "nAxoxKghsXJarz7TRfNyFqDXscS6oQLurU9P5lVlAoGBAJh1QvLtS5Jnu0Z06qfF\n" + + "kkwnVZe+TCGStKvIVciobUts0V2Mw6lnK8kJspBIK5DgN3YfmREe0lufTwBwrqre\n" + "YIRytro2ZA6o/s332ZLuwqpFgQSlktGeTGnerFeFma+6jPNvW025y27jCJVABCTu\n" + + "HT+oUZrXLzGyCFvF9sX/X4QZAoGBAICap4r0h0nJCBOGN+M6Vh2QR9n7NUUF15Gk\n" + "R0EdoLZO3yiqB8NVXydPDpSqFykQkc1OrQz0hG2H1xa6q07OdmoZfiRtVvt5t69s\n" + + "LMD9RZHcsIdfSnG7xVNBQZpf4ZCSFO3RbIH7b//+kn8TxQudptd9SkXba65prBM2\n" + "kh8IbDNBAoGAVsKvkruH7RK7CimDSWcdAKvHARqkjs/PoeKEEY8Yu6zf0Z9TQM5l\n" + + "uC9EwBamYcSusWRcdcz+9HYG58XFnmXq+3EUuFbJ+Ljb8YWBgePjSHDoS/6+/+zq\n" + "B1b5uQp/jYFbYQl50UPRPTF+ul1eQoy7F43Ngj3/5cDRarFZe3ZTzZo=\n" + + "-----END RSA PRIVATE KEY-----"; + String publicKey2048 = "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4V3MpOnuKsdBbR1UzNjK\n" + + "9o5meEMQ4s5Vpykhv1DpqTilKOiEH7VQ/XtjNxw0yjnFBilCnpK6yN9mDEHbBEza\n" + "RjtdrgVhkIejiaXFBP5MBhUQ5l9u8E3IZC3E8pwDjVF0Z9u0R4lGeUg2k6O+NKum\n" + + "qIvxoLCTuG0zf53bctGsRd57LuFipgCkNyxvscOhulsbEMYrLwlb5bMGgx9v+RCn\n" + "wvunNEb7RK+5pzP+iH1MRejRsX+U7h9zHRn2gQhIl7SzG9GXebuPWr4KKwfMHWy0\n" + + "PEuQrsfWRXm9/dTEavbfNkv5E53zWXjWyf93ezkVhBX0YoXmf6UO7PAlvsrjno3T\n" + "uwIDAQAB\n" + "-----END PUBLIC KEY-----"; + + @Test + public void verify() throws Exception { + KeyPairVerifier.verify(privateKey1, publicKey1); + KeyPairVerifier.verify(privateKey2048, publicKey2048); + + try { + KeyPairVerifier.verify(privateKey1, publicKey2048); + Assert.fail("Expected VerificationException"); + } catch (VerificationException e) { + } + + try { + KeyPairVerifier.verify(privateKey2048, publicKey1); + Assert.fail("Expected VerificationException"); + } catch (VerificationException e) { + } + } + +} diff --git a/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 0d05dc2392..4afb80d502 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 @@ - 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 782139698c..1d1be60dec 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 @@ - 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 0d05dc2392..4afb80d502 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 @@ - 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 ab0b69a768..f40e1e3165 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 @@ - 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 0d05dc2392..4afb80d502 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 @@ - 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 ab0b69a768..f40e1e3165 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 @@ - 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 04f1e613b3..81578ae8d0 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 @@ - 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 0ca522d916..885b944df9 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 @@ - 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 04f1e613b3..81578ae8d0 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 @@ - 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 ca35e20698..40b0e69467 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 @@ - 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 0d05dc2392..4afb80d502 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 @@ - 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 0d05dc2392..4afb80d502 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 @@ - diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/adapter-context.xml b/docbook/auth-server-docs/reference/en/en-US/modules/adapter-context.xml new file mode 100755 index 0000000000..cfcf18fe87 --- /dev/null +++ b/docbook/auth-server-docs/reference/en/en-US/modules/adapter-context.xml @@ -0,0 +1,12 @@ + + KeycloakSecurityContext + + The KeycloakSecurityContext interface is available if you need to look at the access token directly. This context is also useful if you need to + get the encoded access token so you can make additional REST invocations. In servlet environments it is available in secured invocations as an attribute in HttpServletRequest. + Or, it is available in secure and insecure requests in the HttpSession for browser apps. + + httpServletRequest.getAttribute(KeycloakSecurityContext.class.getName()); + httpServletRequest.getSession().getAttribute(KeycloakSecurityContext.class.getName()); + + + \ No newline at end of file diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml b/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml index d60c1cd318..d1d5838f70 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) To retrieve the Adapter Configuration then do a HTTP GET to: - <KEYCLOAK URL>//realms/<realm>/clients-registrations/installation/<client id> + <KEYCLOAK URL>//realms/<realm>/clients-registrations/install/<client id> 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 c2eccde759..4d3bf62e12 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 2a155ed485..aeaa07414c 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 options = new HashMap(); - 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 f18cf2ceb1..2254a6e87f 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 options = new HashMap(); - 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 f09ea6eea6..c2b928ec0e 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 kerberosTickets = serverSubject.getPrivateCredentials(KerberosTicket.class); + Iterator 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 ede14795db..02056a7e2c 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java @@ -22,6 +22,7 @@ import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget; import org.keycloak.admin.client.Config; import org.keycloak.admin.client.resource.BasicAuthFilter; +import org.keycloak.common.util.Time; import org.keycloak.representations.AccessTokenResponse; import javax.ws.rs.BadRequestException; @@ -34,8 +35,11 @@ import java.util.Date; */ public class TokenManager { + private static final long DEFAULT_MIN_VALIDITY = 30; + private AccessTokenResponse currentToken; - private Date expirationTime; + private long expirationTime; + private long minTokenValidity = DEFAULT_MIN_VALIDITY; private final Config config; private final ResteasyClient client; @@ -73,10 +77,11 @@ public class TokenManager { TokenService tokenService = target.proxy(TokenService.class); - AccessTokenResponse response = tokenService.grantToken(config.getRealm(), form.asMap()); + int requestTime = Time.currentTime(); + currentToken = tokenService.grantToken(config.getRealm(), form.asMap()); + expirationTime = requestTime + currentToken.getExpiresIn(); - defineCurrentToken(response); - return response; + return currentToken; } public AccessTokenResponse refreshToken(){ @@ -95,27 +100,22 @@ public class TokenManager { TokenService tokenService = target.proxy(TokenService.class); try { - AccessTokenResponse response = tokenService.refreshToken(config.getRealm(), form.asMap()); - defineCurrentToken(response); - return response; + int requestTime = Time.currentTime(); + currentToken = tokenService.refreshToken(config.getRealm(), form.asMap()); + expirationTime = requestTime + currentToken.getExpiresIn(); + + return currentToken; } catch (BadRequestException e) { return grantToken(); } } - private void setExpirationTime() { - Calendar cal = Calendar.getInstance(); - cal.add(Calendar.SECOND, (int) currentToken.getExpiresIn()); - expirationTime = cal.getTime(); + public void setMinTokenValidity(long minTokenValidity) { + this.minTokenValidity = minTokenValidity; } private boolean tokenExpired() { - return new Date().after(expirationTime); - } - - private void defineCurrentToken(AccessTokenResponse accessTokenResponse){ - currentToken = accessTokenResponse; - setExpirationTime(); + return (Time.currentTime() + minTokenValidity) >= expirationTime; } } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenService.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenService.java index 6acdc83d6a..0cedec6b01 100755 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenService.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenService.java @@ -36,10 +36,10 @@ public interface TokenService { @POST @Path("/realms/{realm}/protocol/openid-connect/token") - public AccessTokenResponse grantToken(@PathParam("realm") String realm, MultivaluedMap map); + AccessTokenResponse grantToken(@PathParam("realm") String realm, MultivaluedMap map); @POST @Path("/realms/{realm}/protocol/openid-connect/token") - public AccessTokenResponse refreshToken(@PathParam("realm") String realm, MultivaluedMap map); + AccessTokenResponse refreshToken(@PathParam("realm") String realm, MultivaluedMap map); } diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/sun/jdk/jgss/main/module.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.1.xml old mode 100644 new mode 100755 similarity index 58% rename from distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/sun/jdk/jgss/main/module.xml rename to model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.1.xml index f14318a028..63474a1438 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/sun/jdk/jgss/main/module.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.1.xml @@ -1,7 +1,4 @@ - - - - + - - - - - - - - - - - - - - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml index 6a4d1dcbbf..c107b8ef58 100755 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml @@ -30,4 +30,5 @@ + diff --git a/server-spi/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java b/server-spi/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java index 7c1a3dbd65..3a25c5c87d 100644 --- a/server-spi/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java +++ b/server-spi/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java @@ -86,7 +86,7 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory, byte[] key = getSecretKeyFactory().generateSecret(spec).getEncoded(); return Base64.encodeBytes(key); } catch (InvalidKeySpecException e) { - throw new RuntimeException("Credential could not be encoded"); + throw new RuntimeException("Credential could not be encoded", e); } } @@ -101,7 +101,7 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory, try { return SecretKeyFactory.getInstance(PBKDF2_ALGORITHM); } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("PBKDF2 algorithm not found"); + throw new RuntimeException("PBKDF2 algorithm not found", e); } } diff --git a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java index 654c7644d3..074949f4ad 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java @@ -71,7 +71,7 @@ public class CredentialValidation { public static boolean validateHashedCredential(KeycloakSession session, RealmModel realm, UserModel user, String unhashedCredValue, UserCredentialValueModel credential) { - if(unhashedCredValue == null){ + if (unhashedCredValue == null || unhashedCredValue.isEmpty()) { return false; } diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index d349f4e02e..970fd7eede 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -695,6 +695,16 @@ public class RepresentationToModel { if ("GENERATE".equals(rep.getPublicKey())) { KeycloakModelUtils.generateRealmKeys(realm); + } else { + if (rep.getPrivateKey() != null && rep.getPublicKey() != null) { + realm.setPrivateKeyPem(rep.getPrivateKey()); + realm.setPublicKeyPem(rep.getPublicKey()); + realm.setCodeSecret(KeycloakModelUtils.generateCodeSecret()); + } + + if (rep.getCertificate() != null) { + realm.setCertificatePem(rep.getCertificate()); + } } if(rep.isInternationalizationEnabled() != null){ diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index 653e266614..324136361f 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 0c9f4ce341..b434cdfe04 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -20,7 +20,10 @@ import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.BadRequestException; import org.jboss.resteasy.spi.NotFoundException; import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.keycloak.KeyPairVerifier; import org.keycloak.common.ClientConnection; +import org.keycloak.common.VerificationException; +import org.keycloak.common.util.PemUtils; import org.keycloak.events.Event; import org.keycloak.events.EventQuery; import org.keycloak.events.EventStoreProvider; @@ -30,6 +33,8 @@ import org.keycloak.events.admin.AdminEventQuery; import org.keycloak.events.admin.OperationType; import org.keycloak.exportimport.ClientDescriptionConverter; import org.keycloak.exportimport.ClientDescriptionConverterFactory; +import org.keycloak.jose.jws.JWSBuilder; +import org.keycloak.jose.jws.JWSInput; import org.keycloak.models.ClientModel; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; @@ -74,8 +79,11 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; +import java.security.PrivateKey; +import java.security.PublicKey; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -236,6 +244,14 @@ public class RealmAdminResource { logger.debug("updating realm: " + realm.getName()); try { + if (!"GENERATE".equals(rep.getPublicKey()) && (rep.getPrivateKey() != null && rep.getPublicKey() != null)) { + try { + KeyPairVerifier.verify(rep.getPrivateKey(), rep.getPublicKey()); + } catch (VerificationException e) { + return ErrorResponse.error(e.getMessage(), Status.BAD_REQUEST); + } + } + RepresentationToModel.updateRealm(rep, realm); // Refresh periodic sync tasks for configured federationProviders @@ -253,7 +269,7 @@ public class RealmAdminResource { throw e; } catch (Exception e) { logger.error(e.getMessage(), e); - return ErrorResponse.error("Failed to update " + rep.getRealm() + " Realm.", Response.Status.INTERNAL_SERVER_ERROR); + return ErrorResponse.error("Failed to update realm", Response.Status.INTERNAL_SERVER_ERROR); } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index e8e38ba491..dea9daf87c 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-arquillian/README.md b/testsuite/integration-arquillian/README.md index 39168de056..1adc0547bc 100644 --- a/testsuite/integration-arquillian/README.md +++ b/testsuite/integration-arquillian/README.md @@ -152,6 +152,7 @@ integration-arquillian │ ├──console (activated by -Pconsole-ui-tests) ├──mod_auth_mellon (activated by -Pmod_auth_mellon) + ├──console_no_users (activated by -Pconsole-ui-no-users-tests) └──... ``` diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java index 673bc7c4fd..fc63b2291b 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java @@ -17,7 +17,9 @@ package org.keycloak.testsuite.arquillian; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * @@ -33,6 +35,8 @@ public final class TestContext { private final List appServerBackendsInfo = new ArrayList<>(); private boolean adminLoggedIn; + + private final Map customContext = new HashMap<>(); public TestContext(SuiteContext suiteContext, Class testClass) { this.suiteContext = suiteContext; @@ -88,4 +92,12 @@ public final class TestContext { + (isAdapterTest() ? "App server container: " + getAppServerInfo() + "\n" : ""); } + public Object getCustomValue(Object key) { + return customContext.get(key); + } + + public void setCustomValue(Object key, Object value) { + customContext.put(key, value); + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java index 72d389c556..9f34a40bb8 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java @@ -17,6 +17,7 @@ package org.keycloak.testsuite.auth.page; +import org.openqa.selenium.By; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; @@ -38,7 +39,8 @@ public class WelcomePage extends AuthServer { private WebElement createButton; public boolean isPasswordSet() { - return !driver.getPageSource().contains("Please create an initial admin user to get started."); + return !(driver.getPageSource().contains("Please create an initial admin user to get started.") || + driver.getPageSource().contains("You need local access to create the initial admin user.")); } public void setPassword(String username, String password) { @@ -58,4 +60,8 @@ public class WelcomePage extends AuthServer { } } + public void navigateToAdminConsole() { + driver.findElement(By.linkText("Administration Console")).click(); + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/Login.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/Login.java index 67c145b262..875306f345 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 0000000000..66cf0758c0 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/SAMLIDPInitiatedLogin.java @@ -0,0 +1,19 @@ +package org.keycloak.testsuite.auth.page.login; + +import javax.ws.rs.core.UriBuilder; + +/** + * @author mhajas + */ +public class SAMLIDPInitiatedLogin extends SAMLRedirectLogin { + + public void setUrlName(String urlName) { + setUriParameter("clientUrlName", urlName); + } + + @Override + public UriBuilder createUriBuilder() { + return super.createUriBuilder().path("clients/{clientUrlName}"); + } +} + diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java index 23103d3339..d8729149ac 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java @@ -43,6 +43,8 @@ public class IOUtil { private static final Logger log = Logger.getLogger(IOUtil.class); + public static final File PROJECT_BUILD_DIRECTORY = new File(System.getProperty("project.build.directory", "target")); + public static T loadJson(InputStream is, Class type) { try { return JsonSerialization.readValue(is, type); diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Timer.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Timer.java index 1b2ec652b1..082a2a400b 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Timer.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Timer.java @@ -14,15 +14,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.keycloak.testsuite.util; -import java.text.MessageFormat; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TreeMap; +import org.apache.commons.io.IOUtils; +import org.jboss.logging.Logger; +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartUtilities; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; +import static org.keycloak.testsuite.util.IOUtil.PROJECT_BUILD_DIRECTORY; +import static org.jgroups.util.Util.assertTrue; /** * @@ -30,45 +43,129 @@ import java.util.Map; */ public class Timer { - private static Long time; + public static final Timer DEFAULT = new Timer(); - private static final Map> stats = new HashMap<>(); + protected final Logger log = Logger.getLogger(Timer.class); - public static void time() { - time = new Date().getTime(); + protected static final File DATA_DIR = new File(PROJECT_BUILD_DIRECTORY, "stats/data"); + protected static final File CHARTS_DIR = new File(PROJECT_BUILD_DIRECTORY, "stats/charts"); + + public static final String DEFAULT_OPERATION = "DEFAULT_OPERATION"; + + private Long time; + private String operation = DEFAULT_OPERATION; + private final Map> stats = new TreeMap<>(); + + public long elapsedTime() { + long elapsedTime = 0; + if (time == null) { + } else { + elapsedTime = new Date().getTime() - time; + } + return elapsedTime; } - public static void time(String operation) { - if (time == null) { - System.out.println(MessageFormat.format("Starting timer for operation {0}", operation)); - time(); - } else { - long timeOrig = time; - time(); - logOperation(operation, time - timeOrig); - System.out.println(MessageFormat.format("Operation {0} took {1} ms", operation, time - timeOrig)); + public void reset() { + reset(operation); // log last operation + } + + public void reset(String operation) { + reset(operation, true); + } + + public void reset(String newOperation, boolean logOperationOnChange) { + if (time != null) { + if (operation.equals(newOperation) || logOperationOnChange) { + logOperation(operation, elapsedTime()); + } + } + time = new Date().getTime(); + if (!operation.equals(newOperation)) { + operation = newOperation; + log.info(String.format("Operation '%s' started.", newOperation)); } } - private static void logOperation(String operation, long delta) { + private void logOperation(String operation, long duration) { if (!stats.containsKey(operation)) { stats.put(operation, new ArrayList()); } - stats.get(operation).add(delta); + stats.get(operation).add(duration); + log.info(String.format("Operation '%s' took: %s ms", operation, duration)); } - public static void printStats() { - if (!stats.isEmpty()) { - System.out.println("OPERATION STATS:"); - } - for (String op : stats.keySet()) { - long sum = 0; - for (Long t : stats.get(op)) { - sum += t; + public void clearStats() { + clearStats(true, true, true); + } + + public void clearStats(boolean logStats, boolean saveData, boolean saveCharts) { + if (logStats) { + log.info("Timer Statistics:"); + for (String op : stats.keySet()) { + long sum = 0; + for (Long duration : stats.get(op)) { + sum += duration; + } + log.info(String.format("Operation '%s' average: %s ms", op, sum / stats.get(op).size())); + } + } + if (PROJECT_BUILD_DIRECTORY.exists()) { + DATA_DIR.mkdirs(); + CHARTS_DIR.mkdirs(); + for (String op : stats.keySet()) { + if (saveData) { + saveData(op); + } + if (saveCharts) { + saveChart(op); + } } - System.out.println(MessageFormat.format("Operation {0} average time: {1,number,#} ms", op, sum / stats.get(op).size())); } stats.clear(); } + private void saveData(String op) { + try { + File f = new File(DATA_DIR, op.replace(" ", "_") + ".txt"); + if (!f.createNewFile()) { + throw new IOException("Couldn't create file: " + f); + } + OutputStream stream = new BufferedOutputStream(new FileOutputStream(f)); + for (Long duration : stats.get(op)) { + IOUtils.write(duration.toString(), stream); + IOUtils.write("\n", stream); + } + stream.flush(); + IOUtils.closeQuietly(stream); + } catch (IOException ex) { + log.error("Unable to save data for operation '" + op + "'", ex); + } + } + + private void saveChart(String op) { + XYSeries series = new XYSeries(op); + int i = 0; + for (Long duration : stats.get(op)) { + series.add(++i, duration); + } + final XYSeriesCollection data = new XYSeriesCollection(series); + final JFreeChart chart = ChartFactory.createXYLineChart( + op, + "Operations", + "Duration (ms)", + data, + PlotOrientation.VERTICAL, + true, + true, + false + ); + try { + ChartUtilities.saveChartAsPNG( + new File(CHARTS_DIR, op.replace(" ", "_") + ".png"), + chart, 640, 480); + } catch (IOException ex) { + log.warn("Unable to save chart for operation '" + op + "'."); + } + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java index 92dc9c57e2..1e5145cc79 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 8e85307f0a..ce1900d01f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ChangePasswordTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ChangePasswordTest.java @@ -62,6 +62,9 @@ public class ChangePasswordTest extends AbstractAccountManagementTest { testRealmChangePasswordPage.changePasswords(correctPassword, NEW_PASSWORD, NEW_PASSWORD + "-mismatch"); assertAlertError(); + + testRealmChangePasswordPage.changePasswords(correctPassword, " ", " "); + assertAlertError(); } @Test diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/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 e07b02f213..2d5645a104 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 0000000000..d7dbbdc328 --- /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 031edc7018..5a1d2c6c20 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 d267f06a1f..e0cfefd5f5 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 @@ ${auth.server.wildfly} org.jboss.as.arquillian.container.managed.ManagedDeployableContainer ${keycloak.home} - -Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props} + -Djboss.socket.binding.port-offset=${auth.server.port.offset} -Djboss.bind.address=0.0.0.0 -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props} ${auth.server.management.port} ${startup.timeout.sec} @@ -131,7 +131,7 @@ ${auth.server.eap7} org.jboss.as.arquillian.container.managed.ManagedDeployableContainer ${keycloak.home} - -Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props} + -Djboss.socket.binding.port-offset=${auth.server.port.offset} -Djboss.bind.address=0.0.0.0 -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props} ${startup.timeout.sec} ${auth.server.management.port} 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 0000000000..220a10b510 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console_no_users/pom.xml @@ -0,0 +1,49 @@ + + + + + + 4.0.0 + + + org.keycloak.testsuite + integration-arquillian-tests-other + 1.9.1.Final-SNAPSHOT + + + integration-arquillian-tests-console-no-users + + Admin Console UI Tests - Without pre-configured accounts + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-admin-user-json-file + none + + + + + + + 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 0000000000..1051bb11cb --- /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 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 netInterfaces = NetworkInterface.getNetworkInterfaces(); + for (NetworkInterface ni : Collections.list(netInterfaces)) { + Enumeration 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/README.md b/testsuite/integration-arquillian/tests/other/jpa-performance/README.md new file mode 100644 index 0000000000..d38961620e --- /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/pom.xml b/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml new file mode 100644 index 0000000000..11ec6e7a45 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml @@ -0,0 +1,34 @@ + + + + + + 4.0.0 + + + org.keycloak.testsuite + integration-arquillian-tests-other + 1.9.1.Final-SNAPSHOT + + + integration-arquillian-tests-jpa-performance + + Keycloak JPA Performance Tests + + 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 0000000000..4916393bff --- /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 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 19648b7b45..ec894b44a9 100644 --- a/testsuite/integration-arquillian/tests/other/pom.xml +++ b/testsuite/integration-arquillian/tests/other/pom.xml @@ -134,12 +134,24 @@ console + + console-ui-no-users-tests + + console_no_users + + mod_auth_mellon mod_auth_mellon + + jpa-performance + + jpa-performance + + diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml index b2438dd4aa..7addcc0f05 100644 --- a/testsuite/integration-arquillian/tests/pom.xml +++ b/testsuite/integration-arquillian/tests/pom.xml @@ -108,6 +108,7 @@ maven-surefire-plugin + ${project.build.directory} ${browser} ${firefox_binary} false @@ -223,6 +224,12 @@ 2.1.0.Alpha3 + + jfree + jfreechart + 1.0.13 + + + + + + keycloak.connectionsJpa.driver + com.mysql.jdbc.Driver + + + mysql + + + mysql + mysql-connector-java + + + + + + + + + keycloak.connectionsJpa.driver + org.postgresql.Driver + + + postgresql + + + org.postgresql + postgresql + ${postgresql.version} + + + + + + clean-jpa + + + + + org.liquibase + liquibase-maven-plugin + + META-INF/jpa-changelog-master.xml + + ${keycloak.connectionsJpa.url} + ${keycloak.connectionsJpa.driver} + ${keycloak.connectionsJpa.user} + ${keycloak.connectionsJpa.password} + + false + ${keycloak.connectionsJpa.liquibaseDatabaseClass} + + + + clean-jpa + clean + + dropAll + + + + + + + + + auth-server-wildfly 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 f03b55acbb..7b0d9d3891 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 9637617a0e..a53313333b 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 c0135ef454..b6a0bd5b4e 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("%s", "Insecure Page"); + if (req.getUserPrincipal() != null) pw.printf("UserPrincipal: " + req.getUserPrincipal().getName()); + pw.print(""); + 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 d47e78f00b..daaf5a7cdd 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 156835adba..511e6b58e2 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 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 07ed3897e6..b38b362a14 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 16e302ee0e..12f0f5ed11 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 b6eb54a2de..57c8e48cbf 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("%s", "Insecure Page"); + if (req.getUserPrincipal() != null) pw.printf("UserPrincipal: " + req.getUserPrincipal().getName()); + pw.print(""); + 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 948d9cad0a..a9e95bba6b 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/META-INF/keycloak-server.json b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json index 950fd224ad..e9bb97f419 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/src/test/resources/kerberos/kerberos-ldap-connection.properties b/testsuite/integration/src/test/resources/kerberos/kerberos-ldap-connection.properties index a0ea53789f..3170d6ce9c 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 a775b47c86..6989b7f5e6 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 0000000000..a526c93da5 --- /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 0000000000..e2baad38cc --- /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 0000000000..e854f459c7 --- /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 0000000000..565df1e32f --- /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 0000000000..1d1fa1be0f --- /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/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 57836f55a2..3b4a9749fe 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 150f57fa76..cf5c8e7068 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 c93e8c6929..94b550d704 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 77810f50b5..bbbb843fcd 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 @@
- +
- +
@@ -279,14 +279,14 @@
- +
- +
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 757f07712c..98609c8ed5 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 @@
- - + +
@@ -58,14 +58,14 @@ {{:: 'client-template.tooltip' | translate}}
- +
{{:: 'root-url.tooltip' | translate}}
- +
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 93301af67a..b8a16c9e26 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 @@ -3,26 +3,39 @@
+
+ + +
+ +
+
+ kc-select-action="click" data-ng-model="realm.publicKey">
- +
- + +
+
+
+
+ +
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 1f8fe07154..d7f868f032 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 Marek Posolda @@ -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. *