KEYCLOAK-6286 Adding 'Exclude Session State From Authentication Response' switch to fix backwards compatibility with Keycloak 2.X adapters

This commit is contained in:
mposolda 2018-01-22 09:57:50 +01:00 committed by Marek Posolda
parent 71e0b00600
commit 6369c26671
6 changed files with 168 additions and 2 deletions

View file

@ -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);

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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

View file

@ -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;

View file

@ -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>