keycloak-aplcache

KEYCLOAK-6286 Adding 'Exclude Session State From Authentication

1/22/2018 6:57:50 AM

Details

diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
index 588a06c..6f4ff75 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
@@ -36,6 +36,8 @@ public class OIDCAdvancedConfigWrapper {
 
     private static final String USE_JWKS_URL = "use.jwks.url";
 
+    private static final String EXCLUDE_SESSION_STATE_FROM_AUTH_RESPONSE = "exclude.session.state.from.auth.response";
+
     private final ClientModel clientModel;
     private final ClientRepresentation clientRep;
 
@@ -96,6 +98,16 @@ public class OIDCAdvancedConfigWrapper {
         setAttribute(JWKS_URL, jwksUrl);
     }
 
+    public boolean isExcludeSessionStateFromAuthResponse() {
+        String excludeSessionStateFromAuthResponse = getAttribute(EXCLUDE_SESSION_STATE_FROM_AUTH_RESPONSE);
+        return Boolean.parseBoolean(excludeSessionStateFromAuthResponse);
+    }
+
+    public void setExcludeSessionStateFromAuthResponse(boolean excludeSessionStateFromAuthResponse) {
+        String val = String.valueOf(excludeSessionStateFromAuthResponse);
+        setAttribute(EXCLUDE_SESSION_STATE_FROM_AUTH_RESPONSE, val);
+    }
+
     private String getAttribute(String attrKey) {
         if (clientModel != null) {
             return clientModel.getAttribute(attrKey);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index aa0377e..74ac6b2 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -189,7 +189,10 @@ public class OIDCLoginProtocol implements LoginProtocol {
         if (state != null)
             redirectUri.addParam(OAuth2Constants.STATE, state);
 
-        redirectUri.addParam(OAuth2Constants.SESSION_STATE, userSession.getId());
+        OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientModel(clientSession.getClient());
+        if (!clientConfig.isExcludeSessionStateFromAuthResponse()) {
+            redirectUri.addParam(OAuth2Constants.SESSION_STATE, userSession.getId());
+        }
 
         // Standard or hybrid flow
         String code = null;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCBackwardsCompatibilityTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCBackwardsCompatibilityTest.java
new file mode 100644
index 0000000..81a3444
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCBackwardsCompatibilityTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2017 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.oidc;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.events.Details;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.EventRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+import org.keycloak.testsuite.util.ClientManager;
+import org.keycloak.testsuite.util.OAuthClient;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class OIDCBackwardsCompatibilityTest extends AbstractTestRealmKeycloakTest {
+
+    @Rule
+    public AssertEvents events = new AssertEvents(this);
+
+    @Page
+    protected AppPage appPage;
+
+    @Page
+    protected LoginPage loginPage;
+
+    @Page
+    protected AccountUpdateProfilePage profilePage;
+
+    @Page
+    protected OAuthGrantPage grantPage;
+
+    @Page
+    protected ErrorPage errorPage;
+
+    @Deployment
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(OIDCBackwardsCompatibilityTest.class, AbstractTestRealmKeycloakTest.class);
+    }
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+    }
+
+    @Before
+    public void clientConfiguration() {
+        ClientManager.realm(adminClient.realm("test")).clientId("test-app").directAccessGrant(true);
+        /*
+         * Configure the default client ID. Seems like OAuthClient is keeping the state of clientID
+         * For example: If some test case configure oauth.clientId("sample-public-client"), other tests
+         * will faile and the clientID will always be "sample-public-client
+         * @see AccessTokenTest#testAuthorizationNegotiateHeaderIgnored()
+         */
+        oauth.clientId("test-app");
+        oauth.maxAge(null);
+    }
+
+
+    // KEYCLOAK-6286
+    @Test
+    public void testExcludeSessionStateParameter() {
+        // Open login form and login successfully. Assert session_state is present
+        OAuthClient.AuthorizationEndpointResponse authzResponse = oauth.doLogin("test-user@localhost", "password");
+        EventRepresentation loginEvent = events.expectLogin().assertEvent();
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(authzResponse.getSessionState());
+
+        // Switch "exclude session_state" to on
+        ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+        ClientRepresentation clientRep = client.toRepresentation();
+        OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
+        config.setExcludeSessionStateFromAuthResponse(true);
+        client.update(clientRep);
+
+        // Open login again and assert session_state not present
+        driver.navigate().to(oauth.getLoginFormUrl());
+        org.keycloak.testsuite.Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
+
+        authzResponse = new OAuthClient.AuthorizationEndpointResponse(oauth);
+        Assert.assertNull(authzResponse.getSessionState());
+
+        // Revert
+        config.setExcludeSessionStateFromAuthResponse(false);
+        client.update(clientRep);
+    }
+
+
+}
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 443a5c8..3595dd9 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -317,6 +317,10 @@ logout-service-redir-binding-url=Logout Service Redirect Binding URL
 logout-service-redir-binding-url.tooltip=SAML Redirect Binding URL for the client's single logout service. You can leave this blank if you are using a different binding.
 saml-signature-keyName-transformer=SAML Signature Key Name
 saml-signature-keyName-transformer.tooltip=Signed SAML documents contain identification of signing key in KeyName element. For Keycloak / RH-SSO counterparty, use KEY_ID, for MS AD FS use CERT_SUBJECT, for others check and use NONE if no other option works.
+oidc-compatibility-modes=OpenID Connect Compatibility Modes
+oidc-compatibility-modes.tooltip=Expand this section to configure settings for backwards compatibility with older OpenID Connect / OAuth2 adapters. It is useful especially if your client uses older version of Keycloak / RH-SSO adapter.
+exclude-session-state-from-auth-response=Exclude Session State From Authentication Response
+exclude-session-state-from-auth-response.tooltip=If this is on, the parameter 'session_state' will not be included in OpenID Connect Authentication Response. It is useful if your client uses older OIDC / OAuth2 adapter, which does not support 'session_state' parameter.
 
 # client import
 import-client=Import Client
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index ff64281..0fa5455 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -1002,7 +1002,15 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
         $scope.userInfoSignedResponseAlg = attrVal1==null ? 'unsigned' : attrVal1;
 
         var attrVal2 = $scope.client.attributes['request.object.signature.alg'];
-         $scope.requestObjectSignatureAlg = attrVal2==null ? 'any' : attrVal2;
+        $scope.requestObjectSignatureAlg = attrVal2==null ? 'any' : attrVal2;
+
+        if ($scope.client.attributes["exclude.session.state.from.auth.response"]) {
+            if ($scope.client.attributes["exclude.session.state.from.auth.response"] == "true") {
+                $scope.excludeSessionStateFromAuthResponse = true;
+            } else {
+                $scope.excludeSessionStateFromAuthResponse = false;
+            }
+        }
     }
 
     if (!$scope.create) {
@@ -1225,6 +1233,13 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
 
         }
 
+        if ($scope.excludeSessionStateFromAuthResponse == true) {
+            $scope.clientEdit.attributes["exclude.session.state.from.auth.response"] = "true";
+        } else {
+            $scope.clientEdit.attributes["exclude.session.state.from.auth.response"] = "false";
+
+        }
+
         $scope.clientEdit.protocol = $scope.protocol;
         $scope.clientEdit.attributes['saml.signature.algorithm'] = $scope.signatureAlgorithm;
         $scope.clientEdit.attributes['saml_name_id_format'] = $scope.nameIdFormat;
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index 6943b4e..c00601a 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -401,6 +401,17 @@
             </div>
         </fieldset>
 
+        <fieldset data-ng-show="protocol == 'openid-connect'">
+            <legend collapsed><span class="text">{{:: 'oidc-compatibility-modes' | translate}}</span>  <kc-tooltip>{{:: 'oidc-compatibility-modes.tooltip' | translate}}</kc-tooltip></legend>
+            <div class="form-group clearfix block" data-ng-show="protocol == 'openid-connect'">
+                <label class="col-md-2 control-label" for="excludeSessionStateFromAuthResponse">{{:: 'exclude-session-state-from-auth-response' | translate}}</label>
+                <div class="col-md-6">
+                    <input ng-model="excludeSessionStateFromAuthResponse" ng-click="switchChange()" name="excludeSessionStateFromAuthResponse" id="excludeSessionStateFromAuthResponse" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+                </div>
+                <kc-tooltip>{{:: 'exclude-session-state-from-auth-response.tooltip' | translate}}</kc-tooltip>
+            </div>
+        </fieldset>
+
         <div class="form-group">
             <div class="col-md-10 col-md-offset-2" data-ng-show="client.access.configure">
                 <button kc-save  data-ng-disabled="!changed">{{:: 'save' | translate}}</button>