diff --git a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java index 5ef0770726..84d7552abf 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java @@ -127,7 +127,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider 0 && currentTime > exp) { - String response = refreshTokenForLogout(session, userSession); - AccessTokenResponse tokenResponse = null; - try { - tokenResponse = JsonSerialization.readValue(response, AccessTokenResponse.class); - } catch (IOException e) { - throw new RuntimeException(e); - } - return tokenResponse.getIdToken(); - } else { - return userSession.getNote(FEDERATED_ID_TOKEN); - - } - } - protected void processAccessTokenResponse(BrokeredIdentityContext context, AccessTokenResponse response) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractKcOidcBrokerLogoutTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractKcOidcBrokerLogoutTest.java new file mode 100644 index 0000000000..9c6a6a7e4d --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractKcOidcBrokerLogoutTest.java @@ -0,0 +1,41 @@ +package org.keycloak.testsuite.broker; + +import org.junit.Before; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.representations.idm.UserRepresentation; + +import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient; +import static org.keycloak.testsuite.admin.ApiUtil.resetUserPassword; + +public abstract class AbstractKcOidcBrokerLogoutTest extends AbstractBaseBrokerTest { + + @Before + public void createUser() { + log.debug("creating user for realm " + bc.providerRealmName()); + + final UserRepresentation user = new UserRepresentation(); + user.setUsername(bc.getUserLogin()); + user.setEmail(bc.getUserEmail()); + user.setEmailVerified(true); + user.setEnabled(true); + + final RealmResource realmResource = adminClient.realm(bc.providerRealmName()); + final String userId = createUserWithAdminClient(realmResource, user); + + resetUserPassword(realmResource.users().get(userId), bc.getUserPassword(), false); + } + + @Before + public void addIdentityProviderToProviderRealm() { + log.debug("adding identity provider to realm " + bc.consumerRealmName()); + + final RealmResource realm = adminClient.realm(bc.consumerRealmName()); + realm.identityProviders().create(bc.setUpIdentityProvider()).close(); + } + + @Before + public void addClients() { + addClientsToProviderAndConsumer(); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLogoutFrontChannelTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLogoutFrontChannelTest.java new file mode 100644 index 0000000000..b6ab93baf7 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLogoutFrontChannelTest.java @@ -0,0 +1,76 @@ +package org.keycloak.testsuite.broker; + +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.OAuth2Constants; +import org.keycloak.TokenVerifier; +import org.keycloak.common.VerificationException; +import org.keycloak.models.IdentityProviderSyncMode; +import org.keycloak.representations.IDToken; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.util.OAuthClient; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_CONS_NAME; +import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_PROV_NAME; +import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot; +import static org.keycloak.testsuite.broker.BrokerTestTools.getProviderRoot; +import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage; + +public class KcOidcBrokerLogoutFrontChannelTest extends AbstractKcOidcBrokerLogoutTest { + @Rule public AssertEvents events = new AssertEvents(this); + + @Override + protected BrokerConfiguration getBrokerConfiguration() { + return new KcOidcBrokerConfigurationIdpLogoutFrontChannel(); + } + + private static class KcOidcBrokerConfigurationIdpLogoutFrontChannel + extends KcOidcBrokerConfiguration { + + @Override + protected void applyDefaultConfiguration( + Map config, IdentityProviderSyncMode syncMode) { + super.applyDefaultConfiguration(config, syncMode); + config.put("backchannelSupported", "false"); + } + } + + @Test + public void logoutAfterIdpTokenExpired() throws VerificationException { + driver.navigate().to(getLoginUrl(getConsumerRoot(), bc.consumerRealmName(), "broker-app")); + logInWithBroker(bc); + updateAccountInformation(); + + // Exchange code from "broker-app" client of "consumer" realm for the tokens + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + OAuthClient.AccessTokenResponse response = + oauth + .realm(bc.consumerRealmName()) + .clientId("broker-app") + .redirectUri(getConsumerRoot() + "/auth/realms/" + REALM_CONS_NAME + "/app") + .doAccessTokenRequest(code, "broker-app-secret"); + assertEquals(200, response.getStatusCode()); + + String idTokenString = response.getIdToken(); + IDToken idToken = TokenVerifier.create(idTokenString, IDToken.class).getToken(); + int expiresInMs = (int) (idToken.getExp() - idToken.getIat()); + + // simulate token expiration + setTimeOffset(expiresInMs * 2); + + logoutFromRealm( + getConsumerRoot(), + bc.consumerRealmName(), + "something-else", + idTokenString, + "broker-app", + getConsumerRoot() + "/auth/realms/" + REALM_CONS_NAME + "/app"); + + // user should be logged out successfully from the IDP even though the id_token_hint is expired + driver.navigate().to(getAccountUrl(getProviderRoot(), REALM_PROV_NAME)); + waitForPage(driver, "sign in to provider", true); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLogoutTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLogoutTest.java index f1ea10f007..714480ee20 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLogoutTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLogoutTest.java @@ -1,26 +1,24 @@ package org.keycloak.testsuite.broker; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.keycloak.OAuth2Constants; -import org.keycloak.admin.client.resource.RealmResource; -import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.TokenVerifier; +import org.keycloak.common.VerificationException; +import org.keycloak.representations.IDToken; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.util.CookieHelper; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.util.OAuthClient; import static org.junit.Assert.assertEquals; -import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient; -import static org.keycloak.testsuite.admin.ApiUtil.resetUserPassword; import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_CONS_NAME; import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_PROV_NAME; -import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage; import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot; import static org.keycloak.testsuite.broker.BrokerTestTools.getProviderRoot; +import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage; -public class KcOidcBrokerLogoutTest extends AbstractBaseBrokerTest { +public class KcOidcBrokerLogoutTest extends AbstractKcOidcBrokerLogoutTest { @Rule public AssertEvents events = new AssertEvents(this); @@ -30,35 +28,6 @@ public class KcOidcBrokerLogoutTest extends AbstractBaseBrokerTest { return KcOidcBrokerConfiguration.INSTANCE; } - @Before - public void createUser() { - log.debug("creating user for realm " + bc.providerRealmName()); - - final UserRepresentation user = new UserRepresentation(); - user.setUsername(bc.getUserLogin()); - user.setEmail(bc.getUserEmail()); - user.setEmailVerified(true); - user.setEnabled(true); - - final RealmResource realmResource = adminClient.realm(bc.providerRealmName()); - final String userId = createUserWithAdminClient(realmResource, user); - - resetUserPassword(realmResource.users().get(userId), bc.getUserPassword(), false); - } - - @Before - public void addIdentityProviderToProviderRealm() { - log.debug("adding identity provider to realm " + bc.consumerRealmName()); - - final RealmResource realm = adminClient.realm(bc.consumerRealmName()); - realm.identityProviders().create(bc.setUpIdentityProvider()).close(); - } - - @Before - public void addClients() { - addClientsToProviderAndConsumer(); - } - @Test public void logoutWithoutInitiatingIdpLogsOutOfIdp() { logInAsUserInIDPForFirstTime(); @@ -117,4 +86,39 @@ public class KcOidcBrokerLogoutTest extends AbstractBaseBrokerTest { waitForPage(driver, "sign in to provider", true); } + + @Test + public void logoutAfterIdpTokenExpired() throws VerificationException { + driver.navigate().to(getLoginUrl(getConsumerRoot(), bc.consumerRealmName(), "broker-app")); + logInWithBroker(bc); + updateAccountInformation(); + + // Exchange code from "broker-app" client of "consumer" realm for the tokens + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + OAuthClient.AccessTokenResponse response = oauth.realm(bc.consumerRealmName()) + .clientId("broker-app") + .redirectUri(getConsumerRoot() + "/auth/realms/" + REALM_CONS_NAME + "/app") + .doAccessTokenRequest(code, "broker-app-secret"); + assertEquals(200, response.getStatusCode()); + + String idTokenString = response.getIdToken(); + IDToken idToken = TokenVerifier.create(idTokenString, IDToken.class).getToken(); + int expiresInMs = (int) (idToken.getExp() - idToken.getIat()); + + // simulate token expiration + setTimeOffset(expiresInMs * 2); + + logoutFromRealm( + getConsumerRoot(), + bc.consumerRealmName(), + "something-else", + idTokenString, + "broker-app", + getConsumerRoot() + "/auth/realms/" + REALM_CONS_NAME + "/app" + ); + + // user should be logged out successfully from the IDP even though the id_token_hint is expired + driver.navigate().to(getAccountUrl(getProviderRoot(), REALM_PROV_NAME)); + waitForPage(driver, "sign in to provider", true); + } }