keycloak-uncached

Merge pull request #2578 from mposolda/1.9.x KEYCLOAK-2769

4/11/2016 1:56:32 PM

Details

diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index d9a2fe3..c585c62 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -154,6 +154,8 @@ public class Messages {
 
     public static final String IDENTITY_PROVIDER_LINK_SUCCESS = "identityProviderLinkSuccess";
 
+    public static final String STALE_CODE = "staleCodeMessage";
+
     public static final String IDENTITY_PROVIDER_NOT_UNIQUE = "identityProviderNotUniqueMessage";
 
     public static final String REALM_SUPPORTS_NO_CREDENTIALS = "realmSupportsNoCredentialsMessage";
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index 4a9d993..9834ede 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -146,7 +146,12 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         }
 
         try {
-            ClientSessionCode clientSessionCode = parseClientSessionCode(code);
+            ParsedCodeContext parsedCode = parseClientSessionCode(code);
+            if (parsedCode.response != null) {
+                return parsedCode.response;
+            }
+
+            ClientSessionCode clientSessionCode = parsedCode.clientSessionCode;
             IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
             Response response = identityProvider.performLogin(createAuthenticationRequest(providerId, clientSessionCode));
 
@@ -245,14 +250,14 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
     }
 
     public Response authenticated(BrokeredIdentityContext context) {
-        ClientSessionCode clientCode = null;
         IdentityProviderModel identityProviderConfig = context.getIdpConfig();
-        try {
-            clientCode = parseClientSessionCode(context.getCode());
-        } catch (Exception e) {
-            return redirectToErrorPage(Messages.IDENTITY_PROVIDER_AUTHENTICATION_FAILED, e, identityProviderConfig.getProviderId());
 
+        ParsedCodeContext parsedCode = parseClientSessionCode(context.getCode());
+        if (parsedCode.response != null) {
+            return parsedCode.response;
         }
+        ClientSessionCode clientCode = parsedCode.clientSessionCode;
+
         String providerId = identityProviderConfig.getAlias();
         if (!identityProviderConfig.isStoreToken()) {
             if (isDebugEnabled()) {
@@ -329,8 +334,11 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
     @GET
     @Path("/after-first-broker-login")
     public Response afterFirstBrokerLogin(@QueryParam("code") String code) {
-        ClientSessionCode clientCode = parseClientSessionCode(code);
-        ClientSessionModel clientSession = clientCode.getClientSession();
+        ParsedCodeContext parsedCode = parseClientSessionCode(code);
+        if (parsedCode.response != null) {
+            return parsedCode.response;
+        }
+        ClientSessionModel clientSession = parsedCode.clientSessionCode.getClientSession();
 
         try {
             this.event.detail(Details.CODE_ID, clientSession.getId())
@@ -443,8 +451,11 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
     @GET
     @Path("/after-post-broker-login")
     public Response afterPostBrokerLoginFlow(@QueryParam("code") String code) {
-        ClientSessionCode clientCode = parseClientSessionCode(code);
-        ClientSessionModel clientSession = clientCode.getClientSession();
+        ParsedCodeContext parsedCode = parseClientSessionCode(code);
+        if (parsedCode.response != null) {
+            return parsedCode.response;
+        }
+        ClientSessionModel clientSession = parsedCode.clientSessionCode.getClientSession();
 
         try {
             SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(clientSession, PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT);
@@ -530,20 +541,23 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
 
     @Override
     public Response cancelled(String code) {
-        ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel);
-        if (clientCode == null || !clientCode.isValid(AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
-            return redirectToErrorPage(Messages.INVALID_CODE);
+        ParsedCodeContext parsedCode = parseClientSessionCode(code);
+        if (parsedCode.response != null) {
+            return parsedCode.response;
         }
+        ClientSessionCode clientCode = parsedCode.clientSessionCode;
 
         return browserAuthentication(clientCode.getClientSession(), null);
     }
 
     @Override
     public Response error(String code, String message) {
-        ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel);
-        if (clientCode == null || !clientCode.isValid(AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
-            return redirectToErrorPage(Messages.INVALID_CODE);
+        ParsedCodeContext parsedCode = parseClientSessionCode(code);
+        if (parsedCode.response != null) {
+            return parsedCode.response;
         }
+        ClientSessionCode clientCode = parsedCode.clientSessionCode;
+
         return browserAuthentication(clientCode.getClientSession(), message);
     }
 
@@ -604,36 +618,41 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
 
     }
 
-    private ClientSessionCode parseClientSessionCode(String code) {
+    private ParsedCodeContext parseClientSessionCode(String code) {
         ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel);
 
-        if (clientCode != null && clientCode.isValid(AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+        if (clientCode != null) {
             ClientSessionModel clientSession = clientCode.getClientSession();
 
-            if (clientSession != null) {
-                ClientModel client = clientSession.getClient();
+            if (clientSession.getUserSession() != null) {
+                this.event.session(clientSession.getUserSession());
+            }
 
-                if (client == null) {
-                    throw new IdentityBrokerException("Invalid client");
-                }
+            ClientModel client = clientSession.getClient();
+
+            if (client != null) {
 
                 logger.debugf("Got authorization code from client [%s].", client.getClientId());
                 this.event.client(client);
                 this.session.getContext().setClient(client);
 
-                if (clientSession.getUserSession() != null) {
-                    this.event.session(clientSession.getUserSession());
+                if (!clientCode.isValid(AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+                    logger.debugf("Authorization code is not valid. Client session ID: %s, Client session's action: %s", clientSession.getId(), clientSession.getAction());
+                    Response staleCodeError = redirectToErrorPage(Messages.STALE_CODE);
+                    return ParsedCodeContext.response(staleCodeError);
                 }
-            }
 
-            if (isDebugEnabled()) {
-                logger.debugf("Authorization code is valid.");
-            }
+                if (isDebugEnabled()) {
+                    logger.debugf("Authorization code is valid.");
+                }
 
-            return clientCode;
+                return ParsedCodeContext.clientSessionCode(clientCode);
+            }
         }
 
-        throw new IdentityBrokerException("Invalid code, please login again through your client.");
+        logger.debugf("Authorization code is not valid. Code: %s", code);
+        Response staleCodeError = redirectToErrorPage(Messages.STALE_CODE);
+        return ParsedCodeContext.response(staleCodeError);
     }
 
     private AuthenticationRequest createAuthenticationRequest(String providerId, ClientSessionCode clientSessionCode) {
@@ -808,4 +827,22 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
             this.session.getTransaction().rollback();
         }
     }
+
+
+    private static class ParsedCodeContext {
+        private ClientSessionCode clientSessionCode;
+        private Response response;
+
+        public static ParsedCodeContext clientSessionCode(ClientSessionCode clientSessionCode) {
+            ParsedCodeContext ctx = new ParsedCodeContext();
+            ctx.clientSessionCode = clientSessionCode;
+            return ctx;
+        }
+
+        public static ParsedCodeContext response(Response response) {
+            ParsedCodeContext ctx = new ParsedCodeContext();
+            ctx.response = response;
+            return ctx;
+        }
+    }
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
index 68d903c..e2fbfa2 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
@@ -44,6 +44,7 @@ import java.io.IOException;
 import java.util.List;
 
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 /**
@@ -142,7 +143,7 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP
 
 
     @Test
-    public void testAccessDeniedError() throws Exception {
+    public void testConsentDeniedWithExpiredClientSession() throws Exception {
         // Disable update profile
         setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
 
@@ -169,9 +170,26 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP
         // Login to broker
         loginIDP("test-user");
 
-        // Set time offset and manually enforce removeExpired sessions TODO: Will need custom REST endpoints for that on integration-arquillian
+        // Set time offset
         Time.setOffset(60);
         try {
+            // User rejected consent
+            grantPage.assertCurrent();
+            grantPage.cancel();
+
+            // Assert error page with backToApplication link displayed
+            errorPage.assertCurrent();
+            errorPage.clickBackToApplication();
+
+            assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+
+            // Login to broker again
+            loginIDP("test-user");
+
+            // Set time offset and manually remove expiredSessions TODO: Will require custom endpoint when migrate to integration-arquillian
+            Time.setOffset(120);
+
             brokerServerRule.stopSession(this.session, true);
             this.session = brokerServerRule.startSession();
 
@@ -184,8 +202,14 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP
             grantPage.assertCurrent();
             grantPage.cancel();
 
-            // Assert classic error page ( We're sorry ) displayed. TODO: Should be improved? See KEYCLOAK-2740
+            // Assert error page without backToApplication link (clientSession expired and was removed on the server)
             errorPage.assertCurrent();
+            try {
+                errorPage.clickBackToApplication();
+                fail("Not expected to have link backToApplication available");
+            } catch (NoSuchElementException nsee) {
+                // Expected;
+            }
 
         } finally {
             Time.setOffset(0);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java
index b821331..1cee306 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java
@@ -16,8 +16,10 @@
  */
 package org.keycloak.testsuite.pages;
 
+import org.junit.Assert;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.rule.WebResource;
+import org.openqa.selenium.By;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
 
@@ -32,10 +34,17 @@ public class ErrorPage extends AbstractPage {
     @FindBy(className = "instruction")
     private WebElement errorMessage;
 
+    @FindBy(id = "backToApplication")
+    private WebElement backToApplicationLink;
+
     public String getError() {
         return errorMessage.getText();
     }
 
+    public void clickBackToApplication() {
+        backToApplicationLink.click();
+    }
+
     public boolean isCurrent() {
         return driver.getTitle().equals("We're sorry...");
     }
diff --git a/themes/src/main/resources/theme/base/login/error.ftl b/themes/src/main/resources/theme/base/login/error.ftl
index 95de521..c069e26 100755
--- a/themes/src/main/resources/theme/base/login/error.ftl
+++ b/themes/src/main/resources/theme/base/login/error.ftl
@@ -8,7 +8,7 @@
         <div id="kc-error-message">
             <p class="instruction">${message.summary}</p>
             <#if client?? && client.baseUrl?has_content>
-                <p><a href="${client.baseUrl}">${msg("backToApplication")}</a></p>
+                <p><a id="backToApplication" href="${client.baseUrl}">${msg("backToApplication")}</a></p>
             </#if>
         </div>
     </#if>
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
index ee8a767..ec02b1c 100755
--- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -202,6 +202,7 @@ invalidCodeMessage=An error occurred, please login again through your applicatio
 identityProviderUnexpectedErrorMessage=Unexpected error when authenticating with identity provider
 identityProviderNotFoundMessage=Could not find an identity provider with the identifier.
 identityProviderLinkSuccess=Your account was successfully linked with {0} account {1} .
+staleCodeMessage=This page is no longer valid, please go back to your application and login again
 realmSupportsNoCredentialsMessage=Realm does not support any credential type.
 identityProviderNotUniqueMessage=Realm supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.
 emailVerifiedMessage=Your email address has been verified.