From f10c47955f416c8adac4cf08e67ec4b5d0e6c70e Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Tue, 26 Jun 2018 16:55:14 -0300 Subject: [PATCH] [KEYCLOAK-7427] - Fix to support writing to response when doing programmatic logouts --- adapters/oidc/wildfly-elytron/pom.xml | 5 ++ .../adapters/elytron/ElytronAccount.java | 12 ++--- .../elytron/ElytronCookieTokenStore.java | 2 +- .../adapters/elytron/ElytronHttpFacade.java | 14 +++++ .../elytron/ElytronSessionTokenStore.java | 2 +- .../elytron/KeycloakServletExtension.java | 42 +++++++++++++++ .../elytron/ProtectedHttpServerExchange.java | 42 +++++++++++++++ .../io.undertow.servlet.ServletExtension | 52 +++++++++++++++++++ .../KeycloakDependencyProcessorWildFly.java | 2 +- .../servlet/DemoServletsAdapterTest.java | 18 ------- 10 files changed, 161 insertions(+), 30 deletions(-) create mode 100644 adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakServletExtension.java create mode 100644 adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ProtectedHttpServerExchange.java create mode 100644 adapters/oidc/wildfly-elytron/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension diff --git a/adapters/oidc/wildfly-elytron/pom.xml b/adapters/oidc/wildfly-elytron/pom.xml index ceb7f537e5..310d9f6ddf 100755 --- a/adapters/oidc/wildfly-elytron/pom.xml +++ b/adapters/oidc/wildfly-elytron/pom.xml @@ -95,5 +95,10 @@ jboss-servlet-api_3.0_spec provided + + io.undertow + undertow-servlet + provided + \ No newline at end of file diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronAccount.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronAccount.java index c8db009779..d11f1a96ba 100644 --- a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronAccount.java +++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronAccount.java @@ -78,7 +78,7 @@ public class ElytronAccount implements OidcKeycloakAccount { return false; } - boolean tryRefresh(CallbackHandler callbackHandler) { + boolean tryRefresh() { log.debug("Trying to refresh"); RefreshableKeycloakSecurityContext securityContext = getKeycloakSecurityContext(); @@ -88,14 +88,8 @@ public class ElytronAccount implements OidcKeycloakAccount { } if (securityContext.refreshExpiredToken(false)) { - SecurityIdentity securityIdentity = SecurityIdentityUtil.authorize(callbackHandler, principal); - - if (securityIdentity != null) { - log.debug("refresh succeeded"); - return true; - } - - return false; + log.debug("refresh succeeded"); + return true; } return checkActive(); diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronCookieTokenStore.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronCookieTokenStore.java index eda7d17231..bbe1046a14 100644 --- a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronCookieTokenStore.java +++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronCookieTokenStore.java @@ -88,7 +88,7 @@ public class ElytronCookieTokenStore implements ElytronTokeStore { boolean active = account.checkActive(); if (!active) { - active = account.tryRefresh(this.callbackHandler); + active = account.tryRefresh(); } if (active) { diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java index bda24bba03..9004bda266 100644 --- a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java +++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java @@ -18,6 +18,7 @@ package org.keycloak.adapters.elytron; +import io.undertow.server.handlers.CookieImpl; import org.bouncycastle.asn1.cmp.Challenge; import org.keycloak.KeycloakSecurityContext; import org.keycloak.adapters.AdapterDeploymentContext; @@ -62,6 +63,8 @@ import java.util.function.Consumer; */ class ElytronHttpFacade implements OIDCHttpFacade { + static final String UNDERTOW_EXCHANGE = ElytronHttpFacade.class.getName() + ".undertow.exchange"; + private final HttpServerRequest request; private final CallbackHandler callbackHandler; private final AdapterTokenStore tokenStore; @@ -312,6 +315,17 @@ class ElytronHttpFacade implements OIDCHttpFacade { @Override public void resetCookie(final String name, final String path) { responseConsumer = responseConsumer.andThen(response -> setCookie(name, "", path, null, 0, false, false, response)); + HttpScope exchangeScope = getScope(Scope.EXCHANGE); + ProtectedHttpServerExchange undertowExchange = ProtectedHttpServerExchange.class.cast(exchangeScope.getAttachment(UNDERTOW_EXCHANGE)); + + if (undertowExchange != null) { + CookieImpl cookie = new CookieImpl(name, ""); + + cookie.setMaxAge(0); + cookie.setPath(path); + + undertowExchange.getExchange().setResponseCookie(cookie); + } } @Override diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java index d4946418c2..1a81637f31 100644 --- a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java +++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java @@ -104,7 +104,7 @@ public class ElytronSessionTokenStore implements ElytronTokeStore { boolean active = account.checkActive(); if (!active) { - active = account.tryRefresh(this.callbackHandler); + active = account.tryRefresh(); } if (active) { diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakServletExtension.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakServletExtension.java new file mode 100644 index 0000000000..5adf6e98f2 --- /dev/null +++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakServletExtension.java @@ -0,0 +1,42 @@ +/* + * Copyright 2018 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.adapters.elytron; + +import javax.servlet.ServletContext; +import javax.servlet.ServletRequest; + +import io.undertow.server.HttpHandler; +import io.undertow.servlet.ServletExtension; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.handlers.ServletRequestContext; + +/** + * @author Pedro Igor + */ +public class KeycloakServletExtension implements ServletExtension { + + @Override + public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { + deploymentInfo.addOuterHandlerChainWrapper(handler -> (HttpHandler) exchange -> { + ServletRequest servletRequest = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletRequest(); + + servletRequest.setAttribute(ElytronHttpFacade.UNDERTOW_EXCHANGE, new ProtectedHttpServerExchange(exchange)); + + handler.handleRequest(exchange); + }); + } +} diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ProtectedHttpServerExchange.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ProtectedHttpServerExchange.java new file mode 100644 index 0000000000..682feb34aa --- /dev/null +++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ProtectedHttpServerExchange.java @@ -0,0 +1,42 @@ +/* + * Copyright 2018 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.adapters.elytron; + +import io.undertow.server.HttpServerExchange; + +/** + *

A wrapper for {@code {@link HttpServerExchange}} accessible only from classes in the same package. + * + *

This class is used to provide to the elytron mechanism access to the current exchange in order to allow making + * changes to the exchange (e.g. response) during the evaluation of requests. By default, changes to the exchange are only + * propagated after the execution of the mechanism. But in certain situations, such as when making a programmatic logout (HttpServletRequest.logout()) from + * within application code, any change made to the exchange is not propagated. + * + * @author Pedro Igor + */ +class ProtectedHttpServerExchange { + + private final HttpServerExchange exchange; + + public ProtectedHttpServerExchange(HttpServerExchange exchange) { + this.exchange = exchange; + } + + HttpServerExchange getExchange() { + return exchange; + } +} diff --git a/adapters/oidc/wildfly-elytron/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension b/adapters/oidc/wildfly-elytron/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension new file mode 100644 index 0000000000..965ca75b8f --- /dev/null +++ b/adapters/oidc/wildfly-elytron/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension @@ -0,0 +1,52 @@ +# +# * Copyright 2018 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. +# + +# +# * Copyright 2018 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. +# + +# +# Copyright 2016 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. +# + +org.keycloak.adapters.elytron.KeycloakServletExtension diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessorWildFly.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessorWildFly.java index c43aee00e7..07625d53be 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessorWildFly.java +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessorWildFly.java @@ -39,7 +39,7 @@ public class KeycloakDependencyProcessorWildFly extends KeycloakDependencyProces @Override protected void addPlatformSpecificModules(DeploymentPhaseContext phaseContext, ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) { if (isElytronEnabled(phaseContext)) { - moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_ELYTRON_ADAPTER, true, false, false, false)); + moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_ELYTRON_ADAPTER, true, false, true, false)); } else { moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_WILDFLY_ADAPTER, false, false, true, false)); moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_UNDERTOW_ADAPTER, false, false, false, false)); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java index 65bc5fa2d2..baff8982da 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java @@ -21,7 +21,6 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; -import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -49,7 +48,6 @@ import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.arquillian.graphene.page.Page; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.Assert; -import org.junit.Assume; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -272,21 +270,9 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest { driver.manage().deleteAllCookies(); } - private void assumeNotElytronAdapter() { - if (!AppServerTestEnricher.isUndertowAppServer()) { - try { - Assume.assumeFalse(FileUtils.readFileToString(Paths.get(System.getProperty("app.server.home"), "standalone", "configuration", "standalone.xml").toFile(), "UTF-8").contains("