KEYCLOAK-6866 Error 404 after changing locale while authenticating using X.509

This commit is contained in:
vramik 2018-07-20 13:54:45 +02:00 committed by Marek Posolda
parent ea8eaaff07
commit 524ab44160
10 changed files with 123 additions and 26 deletions

View file

@ -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;
}

View file

@ -80,7 +80,9 @@ public interface LoginFormsProvider extends Provider {
Response createOAuthGrant();
Response createCode();
Response createX509ConfirmPage();
LoginFormsProvider setAuthenticationSession(AuthenticationSessionModel authenticationSession);
LoginFormsProvider setClientSessionCode(String accessCode);

View file

@ -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 <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
@ -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<String,String> 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) {

View file

@ -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<ClientScopeModel> clientScopesRequested;
protected Map<String, String> httpResponseHeaders = new HashMap<String, String>();
protected Map<String, String> httpResponseHeaders = new HashMap<>();
protected URI actionUri;
protected String execution;
@ -95,12 +97,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
protected UserModel user;
protected final Map<String, Object> attributes = new HashMap<String, Object>();
protected final Map<String, Object> attributes = new HashMap<>();
public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
this.session = session;
this.freeMarker = freeMarker;
this.attributes.put("scripts", new LinkedList<String>());
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;

View file

@ -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();
}

View file

@ -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<String, String> formData = new HashMap<>();
public X509ConfirmBean(MultivaluedMap<String, String> formData) {
this.formData = new HashMap<>();
if (formData != null) {
formData.keySet().stream().forEach((key) -> this.formData.put(key, formData.getFirst(key)));
}
}
public Map<String, String> getFormData() {
return formData;
}
}

View file

@ -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;

View file

@ -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());

View file

@ -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 <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
@ -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)));
}
}

View file

@ -10,9 +10,9 @@
<div class="${properties.kcLabelWrapperClass!}">
<label for="certificate_subjectDN" class="${properties.kcLabelClass!}">${msg("clientCertificate")}</label>
</div>
<#if subjectDN??>
<#if x509.formData.subjectDN??>
<div class="${properties.kcLabelWrapperClass!}">
<label id="certificate_subjectDN" class="${properties.kcLabelClass!}">${(subjectDN!"")}</label>
<label id="certificate_subjectDN" class="${properties.kcLabelClass!}">${(x509.formData.subjectDN!"")}</label>
</div>
<#else>
<div class="${properties.kcLabelWrapperClass!}">
@ -23,12 +23,12 @@
<div class="${properties.kcFormGroupClass!}">
<#if isUserEnabled>
<#if x509.formData.isUserEnabled??>
<div class="${properties.kcLabelWrapperClass!}">
<label for="username" class="${properties.kcLabelClass!}">${msg("doX509Login")}</label>
</div>
<div class="${properties.kcLabelWrapperClass!}">
<label id="username" class="${properties.kcLabelClass!}">${(username!'')}</label>
<label id="username" class="${properties.kcLabelClass!}">${(x509.formData.username!'')}</label>
</div>
</#if>
@ -43,7 +43,7 @@
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
<div class="${properties.kcFormButtonsWrapperClass!}">
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" type="submit" value="${msg("doContinue")}"/>
<#if isUserEnabled>
<#if x509.formData.isUserEnabled??>
<input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-cancel" type="submit" value="${msg("doIgnore")}"/>
</#if>
</div>