diff --git a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsPages.java b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsPages.java index 476e3aa8bf..b09340a4b1 100755 --- a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsPages.java +++ b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsPages.java @@ -24,6 +24,7 @@ public enum LoginFormsPages { LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL, LOGIN_IDP_LINK_CONFIRM, LOGIN_IDP_LINK_EMAIL, - OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, INFO, ERROR, LOGIN_UPDATE_PROFILE, LOGIN_PAGE_EXPIRED, CODE; + OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, INFO, ERROR, LOGIN_UPDATE_PROFILE, + LOGIN_PAGE_EXPIRED, CODE, X509_CONFIRM; } diff --git a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java index eb8013e835..011946fbfe 100755 --- a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java @@ -80,7 +80,9 @@ public interface LoginFormsProvider extends Provider { Response createOAuthGrant(); Response createCode(); - + + Response createX509ConfirmPage(); + LoginFormsProvider setAuthenticationSession(AuthenticationSessionModel authenticationSession); LoginFormsProvider setClientSessionCode(String accessCode); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509ClientCertificateAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509ClientCertificateAuthenticator.java index 01339f6369..5ba4e3c51d 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509ClientCertificateAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509ClientCertificateAuthenticator.java @@ -22,6 +22,7 @@ import java.security.cert.X509Certificate; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; +import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; @@ -30,11 +31,12 @@ import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator; import org.keycloak.events.Details; import org.keycloak.events.Errors; +import org.keycloak.forms.login.LoginFormsPages; import org.keycloak.forms.login.LoginFormsProvider; +import org.keycloak.forms.login.freemarker.Templates; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.UserModel; import org.keycloak.models.utils.FormMessage; -import org.keycloak.services.ServicesLogger; /** * @author Peter Nalyvayko @@ -43,8 +45,6 @@ import org.keycloak.services.ServicesLogger; */ public class X509ClientCertificateAuthenticator extends AbstractX509ClientCertificateAuthenticator { - protected static ServicesLogger logger = ServicesLogger.LOGGER; - @Override public void close() { @@ -216,11 +216,14 @@ public class X509ClientCertificateAuthenticator extends AbstractX509ClientCertif form.setErrors(errors); } - return form - .setAttribute("username", context.getUser() != null ? context.getUser().getUsername() : "unknown user") - .setAttribute("subjectDN", subjectDN) - .setAttribute("isUserEnabled", isUserEnabled) - .createForm("login-x509-info.ftl"); + MultivaluedMap formData = new MultivaluedHashMap<>(); + formData.add("username", context.getUser() != null ? context.getUser().getUsername() : "unknown user"); + formData.add("subjectDN", subjectDN); + formData.add("isUserEnabled", String.valueOf(isUserEnabled)); + + form.setFormData(formData); + + return form.createX509ConfirmPage(); } private void dumpContainerAttributes(AuthenticationFlowContext context) { diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java index 8b92110cd7..80cfe53a29 100755 --- a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java +++ b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java @@ -35,6 +35,7 @@ import org.keycloak.forms.login.freemarker.model.RegisterBean; import org.keycloak.forms.login.freemarker.model.RequiredActionUrlFormatterMethod; import org.keycloak.forms.login.freemarker.model.TotpBean; import org.keycloak.forms.login.freemarker.model.UrlBean; +import org.keycloak.forms.login.freemarker.model.X509ConfirmBean; import org.keycloak.models.*; import org.keycloak.models.utils.FormMessage; import org.keycloak.services.Urls; @@ -62,6 +63,7 @@ import java.net.URI; import java.text.MessageFormat; import java.util.*; + import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PASSWORD; /** @@ -75,7 +77,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { protected Response.Status status; protected javax.ws.rs.core.MediaType contentType; protected List clientScopesRequested; - protected Map httpResponseHeaders = new HashMap(); + protected Map httpResponseHeaders = new HashMap<>(); protected URI actionUri; protected String execution; @@ -95,12 +97,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { protected UserModel user; - protected final Map attributes = new HashMap(); + protected final Map attributes = new HashMap<>(); public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) { this.session = session; this.freeMarker = freeMarker; - this.attributes.put("scripts", new LinkedList()); + this.attributes.put("scripts", new LinkedList<>()); this.realm = session.getContext().getRealm(); this.client = session.getContext().getClient(); this.uriInfo = session.getContext().getUri(); @@ -204,6 +206,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { case CODE: attributes.put(OAuth2Constants.CODE, new CodeBean(accessCode, messageType == MessageType.ERROR ? getFirstMessageUnformatted() : null)); break; + case X509_CONFIRM: + attributes.put("x509", new X509ConfirmBean(formData)); + break; } return processTemplate(theme, Templates.getTemplate(page), locale); @@ -342,7 +347,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { Locale locale = session.getContext().resolveLocale(user); Properties messagesBundle = handleThemeResources(theme, locale); - FormMessage msg = new FormMessage(message, parameters); + FormMessage msg = new FormMessage(message, (Object[]) parameters); return formatMessage(msg, messagesBundle, locale); } @@ -385,6 +390,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { case LOGIN: b = UriBuilder.fromUri(Urls.realmLoginPage(baseUri, realm.getName())); break; + case X509_CONFIRM: + b = UriBuilder.fromUri(Urls.realmLoginPage(baseUri, realm.getName())); + break; case REGISTER: b = UriBuilder.fromUri(Urls.realmRegisterPage(baseUri, realm.getName())); break; @@ -508,6 +516,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { return createResponse(LoginFormsPages.CODE); } + @Override + public Response createX509ConfirmPage() { + return createResponse(LoginFormsPages.X509_CONFIRM); + } + protected void setMessage(MessageType type, String message, Object... parameters) { messageType = type; messages = new ArrayList<>(); @@ -629,15 +642,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { this.status = status; return this; } + @Override public LoginFormsProvider setMediaType(javax.ws.rs.core.MediaType type) { this.contentType = type; return this; } - - - @Override public LoginFormsProvider setActionUri(URI actionUri) { this.actionUri = actionUri; diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/Templates.java b/services/src/main/java/org/keycloak/forms/login/freemarker/Templates.java index f2a9d756a5..73921a595e 100755 --- a/services/src/main/java/org/keycloak/forms/login/freemarker/Templates.java +++ b/services/src/main/java/org/keycloak/forms/login/freemarker/Templates.java @@ -56,6 +56,8 @@ public class Templates { return "code.ftl"; case LOGIN_PAGE_EXPIRED: return "login-page-expired.ftl"; + case X509_CONFIRM: + return "login-x509-info.ftl"; default: throw new IllegalArgumentException(); } diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/model/X509ConfirmBean.java b/services/src/main/java/org/keycloak/forms/login/freemarker/model/X509ConfirmBean.java new file mode 100644 index 0000000000..20d87f4554 --- /dev/null +++ b/services/src/main/java/org/keycloak/forms/login/freemarker/model/X509ConfirmBean.java @@ -0,0 +1,42 @@ +/* + * Copyright 2018 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.forms.login.freemarker.model; + +import javax.ws.rs.core.MultivaluedMap; +import java.util.HashMap; +import java.util.Map; + +/** + * @author vramik + */ +public class X509ConfirmBean { + + private Map formData = new HashMap<>(); + + public X509ConfirmBean(MultivaluedMap formData) { + this.formData = new HashMap<>(); + + if (formData != null) { + formData.keySet().stream().forEach((key) -> this.formData.put(key, formData.getFirst(key))); + } + } + + public Map getFormData() { + return formData; + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/x509/X509IdentityConfirmationPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/x509/X509IdentityConfirmationPage.java index a656d50778..f229ca47b5 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/x509/X509IdentityConfirmationPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/x509/X509IdentityConfirmationPage.java @@ -20,6 +20,7 @@ package org.keycloak.testsuite.pages.x509; import org.jboss.arquillian.test.api.ArquillianResource; import org.keycloak.testsuite.pages.AbstractPage; +import org.keycloak.testsuite.pages.LanguageComboboxAwarePage; import org.keycloak.testsuite.util.OAuthClient; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; @@ -30,7 +31,7 @@ import org.openqa.selenium.support.FindBy; * @since 10/24/2016 */ -public class X509IdentityConfirmationPage extends AbstractPage { +public class X509IdentityConfirmationPage extends LanguageComboboxAwarePage { @ArquillianResource protected OAuthClient oauth; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java index 2218288228..4d1b22491a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java @@ -98,6 +98,7 @@ public abstract class AbstractX509AuthenticationTest extends AbstractTestRealmKe @Rule public AssertAdminEvents assertAdminEvents = new AssertAdminEvents(this); + @Override protected boolean isImportAfterEachMethod() { return true; } @@ -113,9 +114,9 @@ public abstract class AbstractX509AuthenticationTest extends AbstractTestRealmKe cliArgs.append("--ignore-ssl-errors=true "); cliArgs.append("--web-security=false "); - cliArgs.append("--ssl-certificates-path=" + authServerHome + "/ca.crt "); - cliArgs.append("--ssl-client-certificate-file=" + authServerHome + "/client.crt "); - cliArgs.append("--ssl-client-key-file=" + authServerHome + "/client.key "); + cliArgs.append("--ssl-certificates-path=").append(authServerHome).append("/ca.crt "); + cliArgs.append("--ssl-client-certificate-file=").append(authServerHome).append("/client.crt "); + cliArgs.append("--ssl-client-key-file=").append(authServerHome).append("/client.key "); cliArgs.append("--ssl-client-key-passphrase=secret "); System.setProperty("keycloak.phantomjs.cli.args", cliArgs.toString()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginTest.java index a714998a0e..51f44530ae 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserLoginTest.java @@ -33,11 +33,16 @@ import org.keycloak.testsuite.pages.x509.X509IdentityConfirmationPage; import javax.ws.rs.core.Response; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertEquals; import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.IdentityMapperType.USERNAME_EMAIL; import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.IdentityMapperType.USER_ATTRIBUTE; import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN; import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN_EMAIL; +import org.keycloak.testsuite.ProfileAssume; +import org.keycloak.testsuite.util.DroneUtils; /** * @author Peter Nalyvayko @@ -483,4 +488,33 @@ public class X509BrowserLoginTest extends AbstractX509AuthenticationTest { loginAsUserFromCertSubjectEmail(); } + // KEYCLOAK-6866 + @Test + public void changeLocaleOnX509InfoPage() { + ProfileAssume.assumeCommunity(); + + AuthenticatorConfigRepresentation cfg = newConfig("x509-browser-config", createLoginSubjectEmail2UsernameOrEmailConfig().getConfig()); + String cfgId = createConfig(browserExecution.getId(), cfg); + Assert.assertNotNull(cfgId); + + log.debug("Open confirm page"); + loginConfirmationPage.open(); + + log.debug("check if on confirm page"); + Assert.assertThat(loginConfirmationPage.getSubjectDistinguishedNameText(), startsWith("EMAILADDRESS=test-user@localhost")); + log.debug("check if locale is EN"); + Assert.assertThat(loginConfirmationPage.getLanguageDropdownText(), is(equalTo("English"))); + + log.debug("change locale to DE"); + loginConfirmationPage.openLanguage("Deutsch"); + log.debug("check if locale is DE"); + Assert.assertThat(loginConfirmationPage.getLanguageDropdownText(), is(equalTo("Deutsch"))); + Assert.assertThat(DroneUtils.getCurrentDriver().getPageSource(), containsString("X509 Client Zertifikat:")); + + log.debug("confirm cert"); + loginConfirmationPage.confirm(); + + log.debug("check if logged in"); + Assert.assertThat(appPage.getRequestType(), is(equalTo(AppPage.RequestType.AUTH_RESPONSE))); + } } diff --git a/themes/src/main/resources/theme/base/login/login-x509-info.ftl b/themes/src/main/resources/theme/base/login/login-x509-info.ftl index c3ff2fdd10..0228b06338 100644 --- a/themes/src/main/resources/theme/base/login/login-x509-info.ftl +++ b/themes/src/main/resources/theme/base/login/login-x509-info.ftl @@ -10,9 +10,9 @@ ${msg("clientCertificate")} - <#if subjectDN??> + <#if x509.formData.subjectDN??> - ${(subjectDN!"")} + ${(x509.formData.subjectDN!"")} <#else> @@ -23,12 +23,12 @@ - <#if isUserEnabled> + <#if x509.formData.isUserEnabled??> ${msg("doX509Login")} - ${(username!'')} + ${(x509.formData.username!'')} #if> @@ -43,7 +43,7 @@ - <#if isUserEnabled> + <#if x509.formData.isUserEnabled??> #if>