diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html index 6f08e96c90..85ad20bb88 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/otp-policy.html @@ -59,6 +59,15 @@ What should the initial counter value be? +
+ +
+ +
+ How many seconds should an OTP token be valid? Defaults to 30 seconds. +
+ +
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java index 6d6c7beff7..431a64664e 100755 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java @@ -9,6 +9,12 @@ import org.keycloak.admin.client.resource.RealmsResource; import org.keycloak.admin.client.token.TokenManager; /** + * Provides a Keycloak client. By default, this implementation uses a {@link ResteasyClient RESTEasy client} with the + * default {@link ResteasyClientBuilder} settings. To customize the underling client, use a {@link KeycloakBuilder} to + * create a Keycloak client. + * + * @see KeycloakBuilder + * * @author rodrigo.sasaki@icarros.com.br */ public class Keycloak { @@ -18,9 +24,9 @@ public class Keycloak { private final ResteasyWebTarget target; private final ResteasyClient client; - private Keycloak(String serverUrl, String realm, String username, String password, String clientId, String clientSecret){ + Keycloak(String serverUrl, String realm, String username, String password, String clientId, String clientSecret, ResteasyClient resteasyClient){ config = new Config(serverUrl, realm, username, password, clientId, clientSecret); - client = new ResteasyClientBuilder().build(); + client = resteasyClient != null ? resteasyClient : new ResteasyClientBuilder().build(); tokenManager = new TokenManager(config, client); @@ -30,11 +36,11 @@ public class Keycloak { } public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId, String clientSecret){ - return new Keycloak(serverUrl, realm, username, password, clientId, clientSecret); + return new Keycloak(serverUrl, realm, username, password, clientId, clientSecret, null); } public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId){ - return new Keycloak(serverUrl, realm, username, password, clientId, null); + return new Keycloak(serverUrl, realm, username, password, clientId, null, null); } public RealmsResource realms(){ @@ -49,6 +55,9 @@ public class Keycloak { return tokenManager; } + /** + * Closes the underlying client. After calling this method, this Keycloak instance cannot be reused. + */ public void close() { client.close(); } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/KeycloakBuilder.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/KeycloakBuilder.java new file mode 100644 index 0000000000..c4a1c3315d --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/KeycloakBuilder.java @@ -0,0 +1,107 @@ +package org.keycloak.admin.client; + +import org.jboss.resteasy.client.jaxrs.ResteasyClient; +import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; + +/** + * Provides a {@link Keycloak} client builder with the ability to customize the underlying + * {@link ResteasyClient RESTEasy client} used to communicate with the Keycloak server. + * + *

Example usage with a connection pool size of 20:

+ * + *
+ *   Keycloak keycloak = KeycloakBuilder.builder()
+ *     .serverUrl("https:/sso.example.com/auth")
+ *     .realm("realm")
+ *     .username("user")
+ *     .password("pass")
+ *     .clientId("client")
+ *     .clientSecret("secret")
+ *     .resteasyClient(new ResteasyClientBuilder().connectionPoolSize(20).build())
+ *     .build();
+ * 
+ * + * @author Scott Rossillo + * @see ResteasyClientBuilder + */ +public class KeycloakBuilder { + private String serverUrl; + private String realm; + private String username; + private String password; + private String clientId; + private String clientSecret; + private ResteasyClient resteasyClient; + + public KeycloakBuilder serverUrl(String serverUrl) { + this.serverUrl = serverUrl; + return this; + } + + public KeycloakBuilder realm(String realm) { + this.realm = realm; + return this; + } + + public KeycloakBuilder username(String username) { + this.username = username; + return this; + } + + public KeycloakBuilder password(String password) { + this.password = password; + return this; + } + + public KeycloakBuilder clientId(String clientId) { + this.clientId = clientId; + return this; + } + + public KeycloakBuilder clientSecret(String clientSecret) { + this.clientSecret = clientSecret; + return this; + } + + public KeycloakBuilder resteasyClient(ResteasyClient resteasyClient) { + this.resteasyClient = resteasyClient; + return this; + } + + /** + * Builds a new Keycloak client from this builder. + */ + public Keycloak build() { + if (serverUrl == null) { + throw new IllegalStateException("serverUrl required"); + } + + if (realm == null) { + throw new IllegalStateException("realm required"); + } + + if (username == null) { + throw new IllegalStateException("username required"); + } + + if (password == null) { + throw new IllegalStateException("password required"); + } + + if (clientId == null) { + throw new IllegalStateException("clientId required"); + } + + return new Keycloak(serverUrl, realm, username, password, clientId, clientSecret, resteasyClient); + } + + private KeycloakBuilder() { + } + + /** + * Returns a new Keycloak builder. + */ + public static KeycloakBuilder builder() { + return new KeycloakBuilder(); + } +} diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java index e2e5ba40f5..c72888fb0c 100755 --- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java +++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java @@ -64,6 +64,12 @@ class WrappedHttpServletRequest implements Request { @Override public Cookie getCookie(String cookieName) { + javax.servlet.http.Cookie[] cookies = request.getCookies(); + + if (cookies == null) { + return null; + } + for (javax.servlet.http.Cookie cookie : request.getCookies()) { if (cookie.getName().equals(cookieName)) { return new Cookie(cookie.getName(), cookie.getValue(), cookie.getVersion(), cookie.getDomain(), cookie.getPath()); diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequestTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequestTest.java index 91ec35bfa3..80d682e633 100644 --- a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequestTest.java +++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequestTest.java @@ -24,10 +24,11 @@ public class WrappedHttpServletRequestTest { private static final String QUERY_PARM_2 = "code2"; private WrappedHttpServletRequest request; + private MockHttpServletRequest mockHttpServletRequest; @Before public void setUp() throws Exception { - MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); + mockHttpServletRequest = new MockHttpServletRequest(); request = new WrappedHttpServletRequest(mockHttpServletRequest); mockHttpServletRequest.setMethod(REQUEST_METHOD); @@ -75,6 +76,13 @@ public class WrappedHttpServletRequestTest { assertNotNull(request.getCookie(COOKIE_NAME)); } + @Test + public void testGetCookieCookiesNull() throws Exception + { + mockHttpServletRequest.setCookies(null); + request.getCookie(COOKIE_NAME); + } + @Test public void testGetHeader() throws Exception { String header = request.getHeader(HEADER_SINGLE_VALUE); diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java index 6712c081b5..d668ae0444 100644 --- a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java +++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java @@ -1,8 +1,23 @@ +/* + * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ package org.keycloak.migration.migrators; import java.util.List; -import org.keycloak.Config; import org.keycloak.migration.MigrationProvider; import org.keycloak.migration.ModelVersion; import org.keycloak.models.*; @@ -52,10 +67,14 @@ public class MigrateTo1_6_0 { } ClientModel adminConsoleClient = realm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID); - if (adminConsoleClient != null) { + if ((adminConsoleClient != null) && !localeMapperAdded(adminConsoleClient)) { adminConsoleClient.addProtocolMapper(localeMapper); } } } + private boolean localeMapperAdded(ClientModel adminConsoleClient) { + return adminConsoleClient.getProtocolMapperByName("openid-connect", "locale") != null; + } + } diff --git a/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java b/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java index 829e8635e5..c1a9938547 100755 --- a/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java +++ b/services/src/main/java/org/keycloak/protocol/ProtocolMapperUtils.java @@ -1,12 +1,13 @@ package org.keycloak.protocol; -import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.UserModel; -import org.keycloak.representations.AccessToken; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory; +import org.keycloak.provider.ProviderFactory; import java.lang.reflect.Method; -import java.util.List; /** * @author Bill Burke @@ -59,4 +60,25 @@ public class ProtocolMapperUtils { } } + + /** + * Find the builtin locale mapper. + * + * @param session A KeycloakSession + * @return The builtin locale mapper. + */ + public static ProtocolMapperModel findLocaleMapper(KeycloakSession session) { + ProtocolMapperModel found = null; + for (ProviderFactory p : session.getKeycloakSessionFactory().getProviderFactories(LoginProtocol.class)) { + LoginProtocolFactory factory = (LoginProtocolFactory) p; + for (ProtocolMapperModel mapper : factory.getBuiltinMappers()) { + if (mapper.getName().equals(OIDCLoginProtocolFactory.LOCALE) && mapper.getProtocol().equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) { + found = mapper; + break; + } + } + if (found != null) break; + } + return found; + } } diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java index 958cd3e3aa..06c19ac369 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -1,3 +1,19 @@ +/* + * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ package org.keycloak.services.managers; import org.jboss.logging.Logger; @@ -33,6 +49,8 @@ import org.keycloak.timer.TimerProvider; import java.util.Collections; import java.util.HashSet; import java.util.List; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.protocol.ProtocolMapperUtils; /** * Per request object @@ -124,6 +142,9 @@ public class RealmManager implements RealmImporter { adminConsole.addRedirectUri(baseUrl + "/*"); adminConsole.setFullScopeAllowed(false); + ProtocolMapperModel localeMapper = ProtocolMapperUtils.findLocaleMapper(session); + if (localeMapper != null) adminConsole.addProtocolMapper(localeMapper); + RoleModel adminRole; if (realm.getName().equals(Config.getAdminRealm())) { adminRole = realm.getRole(AdminRoles.ADMIN); @@ -194,7 +215,7 @@ public class RealmManager implements RealmImporter { if(rep.getEnabledEventTypes() != null) { realm.setEnabledEventTypes(new HashSet(rep.getEnabledEventTypes())); } - + realm.setAdminEventsEnabled(rep.isAdminEventsEnabled()); realm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled()); } diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index ab55ef3e8d..28818d8d0b 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -83,12 +83,12 @@ public class KeycloakApplication extends Application { classes.add(JsResource.class); classes.add(WelcomeResource.class); - new ExportImportManager().checkExportImport(this.sessionFactory, context.getContextPath()); setupDefaultRealm(context.getContextPath()); - importRealms(context); migrateModel(); + new ExportImportManager().checkExportImport(this.sessionFactory, context.getContextPath()); + importRealms(context); AdminRecovery.recover(sessionFactory);