Merge pull request #4241 from mposolda/master

KEYCLOAK-4327 Switching language on User consent gives error
This commit is contained in:
Marek Posolda 2017-06-20 14:01:09 +02:00 committed by GitHub
commit 88d9411f4d
13 changed files with 180 additions and 28 deletions

View file

@ -120,4 +120,6 @@ public interface LoginFormsProvider extends Provider {
public LoginFormsProvider setStatus(Response.Status status); public LoginFormsProvider setStatus(Response.Status status);
LoginFormsProvider setActionUri(URI requestUri); LoginFormsProvider setActionUri(URI requestUri);
LoginFormsProvider setExecution(String execution);
} }

View file

@ -471,6 +471,7 @@ public class AuthenticationProcessor {
LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class) LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
.setUser(getUser()) .setUser(getUser())
.setActionUri(action) .setActionUri(action)
.setExecution(getExecution().getId())
.setFormData(request.getDecodedFormParameters()) .setFormData(request.getDecodedFormParameters())
.setClientSessionCode(accessCode); .setClientSessionCode(accessCode);
if (getForwardedErrorMessage() != null) { if (getForwardedErrorMessage() != null) {

View file

@ -270,6 +270,7 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
URI actionUrl = getActionUrl(executionId, code); URI actionUrl = getActionUrl(executionId, code);
LoginFormsProvider form = processor.getSession().getProvider(LoginFormsProvider.class) LoginFormsProvider form = processor.getSession().getProvider(LoginFormsProvider.class)
.setActionUri(actionUrl) .setActionUri(actionUrl)
.setExecution(executionId)
.setClientSessionCode(code) .setClientSessionCode(code)
.setFormData(formData) .setFormData(formData)
.setErrors(errors); .setErrors(errors);

View file

@ -137,11 +137,15 @@ public class RequiredActionContextResult implements RequiredActionContext {
ClientModel client = authenticationSession.getClient(); ClientModel client = authenticationSession.getClient();
return LoginActionsService.requiredActionProcessor(getUriInfo()) return LoginActionsService.requiredActionProcessor(getUriInfo())
.queryParam(OAuth2Constants.CODE, code) .queryParam(OAuth2Constants.CODE, code)
.queryParam(Constants.EXECUTION, factory.getId()) .queryParam(Constants.EXECUTION, getExecution())
.queryParam(Constants.CLIENT_ID, client.getClientId()) .queryParam(Constants.CLIENT_ID, client.getClientId())
.build(getRealm().getName()); .build(getRealm().getName());
} }
private String getExecution() {
return factory.getId();
}
@Override @Override
public String generateCode() { public String generateCode() {
ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, getRealm(), getAuthenticationSession()); ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, getRealm(), getAuthenticationSession());
@ -164,6 +168,7 @@ public class RequiredActionContextResult implements RequiredActionContext {
LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class) LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
.setUser(getUser()) .setUser(getUser())
.setActionUri(action) .setActionUri(action)
.setExecution(getExecution())
.setClientSessionCode(accessCode); .setClientSessionCode(accessCode);
return provider; return provider;
} }

View file

@ -169,6 +169,7 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator
.setStatus(Response.Status.OK) .setStatus(Response.Status.OK)
.setAttribute(LoginFormsProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext) .setAttribute(LoginFormsProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
.setActionUri(action) .setActionUri(action)
.setExecution(context.getExecution().getId())
.createIdpLinkEmailPage(); .createIdpLinkEmailPage();
context.forceChallenge(challenge); context.forceChallenge(challenge);
} }

View file

@ -76,6 +76,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
private Map<String, String> httpResponseHeaders = new HashMap<String, String>(); private Map<String, String> httpResponseHeaders = new HashMap<String, String>();
private String accessRequestMessage; private String accessRequestMessage;
private URI actionUri; private URI actionUri;
private String execution;
private List<FormMessage> messages = null; private List<FormMessage> messages = null;
private MessageType messageType = MessageType.ERROR; private MessageType messageType = MessageType.ERROR;
@ -230,6 +231,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
b = UriBuilder.fromUri(baseUri).path(uriInfo.getPath()); b = UriBuilder.fromUri(baseUri).path(uriInfo.getPath());
break; break;
} }
if (execution != null) {
b.queryParam(Constants.EXECUTION, execution);
}
attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle)); attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle));
} }
} }
@ -366,7 +372,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri)); attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
if (realm.isInternationalizationEnabled()) { if (realm.isInternationalizationEnabled()) {
UriBuilder b = UriBuilder.fromUri(baseUri).path(uriInfo.getPath()); UriBuilder b = UriBuilder.fromUri(baseUri)
.path(uriInfo.getPath());
if (execution != null) {
b.queryParam(Constants.EXECUTION, execution);
}
attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle)); attributes.put("locale", new LocaleBean(realm, locale, b, messagesBundle));
} }
} }
@ -590,6 +602,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
return this; return this;
} }
@Override
public LoginFormsProvider setExecution(String execution) {
this.execution = execution;
return this;
}
@Override @Override
public LoginFormsProvider setResponseHeader(String headerName, String headerValue) { public LoginFormsProvider setResponseHeader(String headerName, String headerValue) {
this.httpResponseHeaders.put(headerName, headerValue); this.httpResponseHeaders.put(headerName, headerValue);

View file

@ -644,12 +644,15 @@ public class AuthenticationManager {
// Skip grant screen if everything was already approved by this user // Skip grant screen if everything was already approved by this user
if (realmRoles.size() > 0 || resourceRoles.size() > 0 || protocolMappers.size() > 0) { if (realmRoles.size() > 0 || resourceRoles.size() > 0 || protocolMappers.size() > 0) {
String execution = AuthenticatedClientSessionModel.Action.OAUTH_GRANT.name();
accessCode. accessCode.
setAction(AuthenticatedClientSessionModel.Action.REQUIRED_ACTIONS.name()); setAction(AuthenticatedClientSessionModel.Action.REQUIRED_ACTIONS.name());
authSession.setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, AuthenticatedClientSessionModel.Action.OAUTH_GRANT.name()); authSession.setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution);
return session.getProvider(LoginFormsProvider.class) return session.getProvider(LoginFormsProvider.class)
.setExecution(execution)
.setClientSessionCode(accessCode.getCode()) .setClientSessionCode(accessCode.getCode())
.setAccessRequest(realmRoles, resourceRoles, protocolMappers) .setAccessRequest(realmRoles, resourceRoles, protocolMappers)
.createOAuthGrant(); .createOAuthGrant();

View file

@ -58,6 +58,7 @@ public class AuthenticationFlowURLHelper {
return session.getProvider(LoginFormsProvider.class) return session.getProvider(LoginFormsProvider.class)
.setActionUri(lastStepUrl) .setActionUri(lastStepUrl)
.setExecution(getExecutionId(authSession))
.createLoginExpiredPage(); .createLoginExpiredPage();
} }
@ -76,7 +77,7 @@ public class AuthenticationFlowURLHelper {
public URI getLastExecutionUrl(AuthenticationSessionModel authSession) { public URI getLastExecutionUrl(AuthenticationSessionModel authSession) {
String executionId = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION); String executionId = getExecutionId(authSession);
String latestFlowPath = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH); String latestFlowPath = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
if (latestFlowPath == null) { if (latestFlowPath == null) {
@ -90,4 +91,8 @@ public class AuthenticationFlowURLHelper {
return getLastExecutionUrl(latestFlowPath, executionId, authSession.getClient().getClientId()); return getLastExecutionUrl(latestFlowPath, executionId, authSession.getClient().getClientId());
} }
private String getExecutionId(AuthenticationSessionModel authSession) {
return authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
}
} }

View file

@ -0,0 +1,44 @@
/*
* 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.testsuite.pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class LanguageComboboxAwarePage extends AbstractPage {
@FindBy(id = "kc-current-locale-link")
private WebElement languageText;
@FindBy(id = "kc-locale-dropdown")
private WebElement localeDropdown;
public String getLanguageDropdownText() {
return languageText.getText();
}
public void openLanguage(String language){
WebElement langLink = localeDropdown.findElement(By.xpath("//a[text()='" + language + "']"));
String url = langLink.getAttribute("href");
driver.navigate().to(url);
}
}

View file

@ -26,7 +26,7 @@ import org.openqa.selenium.support.FindBy;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class LoginPage extends AbstractPage { public class LoginPage extends LanguageComboboxAwarePage {
@ArquillianResource @ArquillianResource
protected OAuthClient oauth; protected OAuthClient oauth;
@ -75,12 +75,6 @@ public class LoginPage extends AbstractPage {
private WebElement instruction; private WebElement instruction;
@FindBy(id = "kc-current-locale-link")
private WebElement languageText;
@FindBy(id = "kc-locale-dropdown")
private WebElement localeDropdown;
public void login(String username, String password) { public void login(String username, String password) {
usernameInput.clear(); usernameInput.clear();
usernameInput.sendKeys(username); usernameInput.sendKeys(username);
@ -191,14 +185,4 @@ public class LoginPage extends AbstractPage {
assertCurrent(); assertCurrent();
} }
public String getLanguageDropdownText() {
return languageText.getText();
}
public void openLanguage(String language){
WebElement langLink = localeDropdown.findElement(By.xpath("//a[text()='" +language +"']"));
String url = langLink.getAttribute("href");
driver.navigate().to(url);
}
} }

View file

@ -22,7 +22,7 @@ import org.openqa.selenium.support.FindBy;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class LoginPasswordUpdatePage extends AbstractPage { public class LoginPasswordUpdatePage extends LanguageComboboxAwarePage {
@FindBy(id = "password-new") @FindBy(id = "password-new")
private WebElement newPasswordInput; private WebElement newPasswordInput;

View file

@ -22,7 +22,7 @@ import org.openqa.selenium.support.FindBy;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class OAuthGrantPage extends AbstractPage { public class OAuthGrantPage extends LanguageComboboxAwarePage {
@FindBy(css = "input[name=\"accept\"]") @FindBy(css = "input[name=\"accept\"]")
private WebElement acceptButton; private WebElement acceptButton;

View file

@ -16,19 +16,30 @@
*/ */
package org.keycloak.testsuite.i18n; package org.keycloak.testsuite.i18n;
import java.util.Arrays;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClient; import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine; import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.HttpClientBuilder; import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LanguageComboboxAwarePage;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.keycloak.testsuite.ProfileAssume; import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.util.IdentityProviderBuilder; import org.keycloak.testsuite.util.IdentityProviderBuilder;
/** /**
@ -37,9 +48,19 @@ import org.keycloak.testsuite.util.IdentityProviderBuilder;
*/ */
public class LoginPageTest extends AbstractI18NTest { public class LoginPageTest extends AbstractI18NTest {
@Page
protected AppPage appPage;
@Page @Page
protected LoginPage loginPage; protected LoginPage loginPage;
@Page
protected LoginPasswordUpdatePage changePasswordPage;
@Page
protected OAuthGrantPage grantPage;
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
testRealm.addIdentityProvider(IdentityProviderBuilder.create() testRealm.addIdentityProvider(IdentityProviderBuilder.create()
@ -63,11 +84,7 @@ public class LoginPageTest extends AbstractI18NTest {
loginPage.open(); loginPage.open();
Assert.assertEquals("English", loginPage.getLanguageDropdownText()); Assert.assertEquals("English", loginPage.getLanguageDropdownText());
loginPage.openLanguage("Deutsch"); switchLanguageToGermanAndBack("Username or email", "Benutzername oder E-Mail", loginPage);
Assert.assertEquals("Deutsch", loginPage.getLanguageDropdownText());
loginPage.openLanguage("English");
Assert.assertEquals("English", loginPage.getLanguageDropdownText());
} }
@Test @Test
@ -109,6 +126,8 @@ public class LoginPageTest extends AbstractI18NTest {
response = client.target(driver.getCurrentUrl()).request().acceptLanguage("en").get(); response = client.target(driver.getCurrentUrl()).request().acceptLanguage("en").get();
Assert.assertTrue(response.readEntity(String.class).contains("Log in to test")); Assert.assertTrue(response.readEntity(String.class).contains("Log in to test"));
client.close();
} }
@Test @Test
@ -119,4 +138,73 @@ public class LoginPageTest extends AbstractI18NTest {
Assert.assertEquals("MyOIDC", loginPage.findSocialButton("myoidc").getText()); Assert.assertEquals("MyOIDC", loginPage.findSocialButton("myoidc").getText());
} }
// KEYCLOAK-3887
@Test
public void languageChangeRequiredActions() {
UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost");
UserRepresentation userRep = user.toRepresentation();
userRep.setRequiredActions(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString()));
user.update(userRep);
loginPage.open();
loginPage.login("test-user@localhost", "password");
changePasswordPage.assertCurrent();
Assert.assertEquals("English", changePasswordPage.getLanguageDropdownText());
// Switch language
switchLanguageToGermanAndBack("Update password", "Passwort aktualisieren", changePasswordPage);
// Update password
changePasswordPage.changePassword("password", "password");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
}
// KEYCLOAK-3887
@Test
public void languageChangeConsentScreen() {
// Set client, which requires consent
oauth.clientId("third-party");
loginPage.open();
loginPage.login("test-user@localhost", "password");
grantPage.assertCurrent();
Assert.assertEquals("English", grantPage.getLanguageDropdownText());
// Switch language
switchLanguageToGermanAndBack("Do you grant these access privileges?", "Wollen Sie diese Zugriffsrechte", changePasswordPage);
// Confirm grant
grantPage.accept();
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
// Revert client
oauth.clientId("test-app");
}
private void switchLanguageToGermanAndBack(String expectedEnglishMessage, String expectedGermanMessage, LanguageComboboxAwarePage page) {
// Switch language to Deutsch
page.openLanguage("Deutsch");
Assert.assertEquals("Deutsch", page.getLanguageDropdownText());
String pageSource = driver.getPageSource();
Assert.assertFalse(pageSource.contains(expectedEnglishMessage));
Assert.assertTrue(pageSource.contains(expectedGermanMessage));
// Revert language
page.openLanguage("English");
Assert.assertEquals("English", page.getLanguageDropdownText());
pageSource = driver.getPageSource();
Assert.assertTrue(pageSource.contains(expectedEnglishMessage));
Assert.assertFalse(pageSource.contains(expectedGermanMessage));
}
} }