keycloak-aplcache

Details

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 ac435e3..cb203c1 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
@@ -120,4 +120,6 @@ public interface LoginFormsProvider extends Provider {
     public LoginFormsProvider setStatus(Response.Status status);
 
     LoginFormsProvider setActionUri(URI requestUri);
+
+    LoginFormsProvider setExecution(String execution);
 }
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 23d06e3..af7d2f7 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -471,6 +471,7 @@ public class AuthenticationProcessor {
             LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
                     .setUser(getUser())
                     .setActionUri(action)
+                    .setExecution(getExecution().getId())
                     .setFormData(request.getDecodedFormParameters())
                     .setClientSessionCode(accessCode);
             if (getForwardedErrorMessage() != null) {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
index 7189b95..ca841d0 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
@@ -169,6 +169,7 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator 
                 .setStatus(Response.Status.OK)
                 .setAttribute(LoginFormsProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
                 .setActionUri(action)
+                .setExecution(context.getExecution().getId())
                 .createIdpLinkEmailPage();
         context.forceChallenge(challenge);
     }
diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
index 82c12ec..575677d 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
@@ -270,6 +270,7 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
         URI actionUrl = getActionUrl(executionId, code);
         LoginFormsProvider form = processor.getSession().getProvider(LoginFormsProvider.class)
                 .setActionUri(actionUrl)
+                .setExecution(executionId)
                 .setClientSessionCode(code)
                 .setFormData(formData)
                 .setErrors(errors);
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
index 1d9475a..3afb34c 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
@@ -137,11 +137,15 @@ public class RequiredActionContextResult implements RequiredActionContext {
         ClientModel client = authenticationSession.getClient();
         return LoginActionsService.requiredActionProcessor(getUriInfo())
                 .queryParam(OAuth2Constants.CODE, code)
-                .queryParam(Constants.EXECUTION, factory.getId())
+                .queryParam(Constants.EXECUTION, getExecution())
                 .queryParam(Constants.CLIENT_ID, client.getClientId())
                 .build(getRealm().getName());
     }
 
+    private String getExecution() {
+        return factory.getId();
+    }
+
     @Override
     public String generateCode() {
         ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, getRealm(), getAuthenticationSession());
@@ -164,6 +168,7 @@ public class RequiredActionContextResult implements RequiredActionContext {
         LoginFormsProvider provider = getSession().getProvider(LoginFormsProvider.class)
                 .setUser(getUser())
                 .setActionUri(action)
+                .setExecution(getExecution())
                 .setClientSessionCode(accessCode);
         return provider;
     }
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 affaf20..d7eb01c 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
@@ -76,6 +76,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     private Map<String, String> httpResponseHeaders = new HashMap<String, String>();
     private String accessRequestMessage;
     private URI actionUri;
+    private String execution;
 
     private List<FormMessage> messages = null;
     private MessageType messageType = MessageType.ERROR;
@@ -230,6 +231,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
                         b = UriBuilder.fromUri(baseUri).path(uriInfo.getPath());
                         break;
                 }
+
+                if (execution != null) {
+                    b.queryParam(Constants.EXECUTION, execution);
+                }
+
                 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));
 
             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));
             }
         }
@@ -591,6 +603,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     }
 
     @Override
+    public LoginFormsProvider setExecution(String execution) {
+        this.execution = execution;
+        return this;
+    }
+
+    @Override
     public LoginFormsProvider setResponseHeader(String headerName, String headerValue) {
         this.httpResponseHeaders.put(headerName, headerValue);
         return this;
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 07bd1f6..6c91759 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -644,12 +644,15 @@ public class AuthenticationManager {
 
             // Skip grant screen if everything was already approved by this user
             if (realmRoles.size() > 0 || resourceRoles.size() > 0 || protocolMappers.size() > 0) {
+                String execution = AuthenticatedClientSessionModel.Action.OAUTH_GRANT.name();
+
                 accessCode.
 
                         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)
+                        .setExecution(execution)
                         .setClientSessionCode(accessCode.getCode())
                         .setAccessRequest(realmRoles, resourceRoles, protocolMappers)
                         .createOAuthGrant();
diff --git a/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java b/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java
index 3726b99..489f73f 100644
--- a/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java
+++ b/services/src/main/java/org/keycloak/services/util/AuthenticationFlowURLHelper.java
@@ -58,6 +58,7 @@ public class AuthenticationFlowURLHelper {
 
         return session.getProvider(LoginFormsProvider.class)
                 .setActionUri(lastStepUrl)
+                .setExecution(getExecutionId(authSession))
                 .createLoginExpiredPage();
     }
 
@@ -76,7 +77,7 @@ public class AuthenticationFlowURLHelper {
 
 
     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);
 
         if (latestFlowPath == null) {
@@ -90,4 +91,8 @@ public class AuthenticationFlowURLHelper {
         return getLastExecutionUrl(latestFlowPath, executionId, authSession.getClient().getClientId());
     }
 
+    private String getExecutionId(AuthenticationSessionModel authSession) {
+        return authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
+    }
+
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LanguageComboboxAwarePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LanguageComboboxAwarePage.java
new file mode 100644
index 0000000..29d512e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LanguageComboboxAwarePage.java
@@ -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);
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java
index 11d8fb2..b025ec7 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java
@@ -26,7 +26,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class LoginPage extends AbstractPage {
+public class LoginPage extends LanguageComboboxAwarePage {
 
     @ArquillianResource
     protected OAuthClient oauth;
@@ -75,12 +75,6 @@ public class LoginPage extends AbstractPage {
     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) {
         usernameInput.clear();
         usernameInput.sendKeys(username);
@@ -191,14 +185,4 @@ public class LoginPage extends AbstractPage {
         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);
-    }
-
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java
index 93d203d..7a963e1 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java
@@ -22,7 +22,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class LoginPasswordUpdatePage extends AbstractPage {
+public class LoginPasswordUpdatePage extends LanguageComboboxAwarePage {
 
     @FindBy(id = "password-new")
     private WebElement newPasswordInput;
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java
index 1a550ec..cfb1f06 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/OAuthGrantPage.java
@@ -22,7 +22,7 @@ import org.openqa.selenium.support.FindBy;
 /**
  * @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\"]")
     private WebElement acceptButton;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java
index 5c9ff74..2d92719 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/LoginPageTest.java
@@ -16,19 +16,30 @@
  */
 package org.keycloak.testsuite.i18n;
 
+import java.util.Arrays;
+
 import org.apache.http.impl.client.DefaultHttpClient;
 import org.jboss.resteasy.client.jaxrs.ResteasyClient;
 import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
 import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine;
 import org.junit.Assert;
 import org.junit.Test;
+import org.keycloak.OAuth2Constants;
 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.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 javax.ws.rs.core.Response;
 import org.jboss.arquillian.graphene.page.Page;
 import org.keycloak.testsuite.ProfileAssume;
+import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
 import org.keycloak.testsuite.util.IdentityProviderBuilder;
 
 /**
@@ -38,8 +49,18 @@ import org.keycloak.testsuite.util.IdentityProviderBuilder;
 public class LoginPageTest extends AbstractI18NTest {
 
     @Page
+    protected AppPage appPage;
+
+    @Page
     protected LoginPage loginPage;
 
+    @Page
+    protected LoginPasswordUpdatePage changePasswordPage;
+
+    @Page
+    protected OAuthGrantPage grantPage;
+
+
     @Override
     public void configureTestRealm(RealmRepresentation testRealm) {
         testRealm.addIdentityProvider(IdentityProviderBuilder.create()
@@ -63,11 +84,7 @@ public class LoginPageTest extends AbstractI18NTest {
         loginPage.open();
         Assert.assertEquals("English", loginPage.getLanguageDropdownText());
 
-        loginPage.openLanguage("Deutsch");
-        Assert.assertEquals("Deutsch", loginPage.getLanguageDropdownText());
-
-        loginPage.openLanguage("English");
-        Assert.assertEquals("English", loginPage.getLanguageDropdownText());
+        switchLanguageToGermanAndBack("Username or email", "Benutzername oder E-Mail", loginPage);
     }
 
     @Test
@@ -109,6 +126,8 @@ public class LoginPageTest extends AbstractI18NTest {
 
         response = client.target(driver.getCurrentUrl()).request().acceptLanguage("en").get();
         Assert.assertTrue(response.readEntity(String.class).contains("Log in to test"));
+
+        client.close();
     }
 
     @Test
@@ -119,4 +138,73 @@ public class LoginPageTest extends AbstractI18NTest {
         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));
+    }
 }