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>