This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a commit to branch tomee-10.x
in repository https://gitbox.apache.org/repos/asf/tomee.git

commit 72306c04bef8a4cd901aaa86744dfbcddd9b1896
Author: Richard Zowalla <[email protected]>
AuthorDate: Tue Oct 13 08:49:50 2026 +0200

    Clear the WS-Security caller identity after the JAX-WS request (#3084)
    
    Every UsernameToken login of a message is undone on the thread which
    did it, also when the chain fails or resumes on another thread.
    
    (cherry picked from commit 9246fa872edf323caaa9cb2b890fc63b7ac39947)
---
 .../openejb/server/cxf/ConfigureCxfSecurity.java   |   3 +
 .../apache/openejb/server/cxf/CxfWsContainer.java  |  31 +-
 .../openejb/server/cxf/OpenEJBLoginValidator.java  |  15 +
 .../server/cxf/WSSLoginCleanupInterceptor.java     | 161 ++++++++
 .../cxf/CxfWsContainerSecurityStateTest.java       | 426 +++++++++++++++++++++
 5 files changed, 634 insertions(+), 2 deletions(-)

diff --git 
a/server/openejb-cxf/src/main/java/org/apache/openejb/server/cxf/ConfigureCxfSecurity.java
 
b/server/openejb-cxf/src/main/java/org/apache/openejb/server/cxf/ConfigureCxfSecurity.java
index 599de67b1f..cbc895c08e 100644
--- 
a/server/openejb-cxf/src/main/java/org/apache/openejb/server/cxf/ConfigureCxfSecurity.java
+++ 
b/server/openejb-cxf/src/main/java/org/apache/openejb/server/cxf/ConfigureCxfSecurity.java
@@ -109,6 +109,9 @@ public class ConfigureCxfSecurity {
             // if WS Security is used with a JAX-WS handler (See 
EjbInterceptor), we have to deal with mustUnderstand flag
             // in WS Security headers. So, let's add an interceptor
             endpoint.getInInterceptors().add(new WSSPassThroughInterceptor());
+
+            // undo the logins of the message even when it fails
+            
endpoint.getOutFaultInterceptors().add(WSSLoginCleanupInterceptor.OUT_FAULT);
         }
 
         if (null != outProps && !outProps.isEmpty()) {
diff --git 
a/server/openejb-cxf/src/main/java/org/apache/openejb/server/cxf/CxfWsContainer.java
 
b/server/openejb-cxf/src/main/java/org/apache/openejb/server/cxf/CxfWsContainer.java
index 08e94ac542..44a3979bf1 100644
--- 
a/server/openejb-cxf/src/main/java/org/apache/openejb/server/cxf/CxfWsContainer.java
+++ 
b/server/openejb-cxf/src/main/java/org/apache/openejb/server/cxf/CxfWsContainer.java
@@ -22,11 +22,13 @@ import org.apache.cxf.transport.DestinationFactory;
 import org.apache.cxf.transport.http.AbstractHTTPDestination;
 import org.apache.openejb.assembler.classic.util.ServiceConfiguration;
 import org.apache.openejb.core.webservices.PortData;
+import org.apache.openejb.loader.SystemInstance;
 import org.apache.openejb.monitoring.LocalMBeanServer;
 import org.apache.openejb.server.cxf.transport.util.CxfUtil;
 import org.apache.openejb.server.httpd.HttpListener;
 import org.apache.openejb.server.httpd.HttpRequest;
 import org.apache.openejb.server.httpd.HttpResponse;
+import org.apache.openejb.spi.SecurityService;
 
 import javax.management.ObjectName;
 
@@ -81,11 +83,36 @@ public abstract class CxfWsContainer implements 
HttpListener {
     public void onMessage(final HttpRequest request, final HttpResponse 
response) throws Exception {
         final ClassLoader oldLoader = 
Thread.currentThread().getContextClassLoader();
         Thread.currentThread().setContextClassLoader(CxfUtil.initBusLoader());
+        final SecurityService securityService = 
SystemInstance.get().getComponent(SecurityService.class);
+        final Object securityState = securityService == null ? null : 
securityService.currentState();
         try {
             destination.invoke(null, request.getServletContext(), request, 
response);
         } finally {
-            if (oldLoader != null) {
-                CxfUtil.clearBusLoader(oldLoader);
+            try {
+                if (securityService != null) {
+                    restoreSecurityState(securityService, securityState, 
request);
+                }
+            } finally {
+                if (oldLoader != null) {
+                    CxfUtil.clearBusLoader(oldLoader);
+                }
+            }
+        }
+    }
+
+    // a ws-security login associates the caller with the current (pooled) 
thread,
+    // undo it once the request is done so it can't leak into the next one
+    private static void restoreSecurityState(final SecurityService 
securityService, final Object securityState,
+                                             final HttpRequest request) {
+        if (securityService.currentState() != securityState) {
+            securityService.setState(securityState);
+        }
+
+        // logins done by this thread, the ones of a resumed chain are 
released by WSSLoginCleanupInterceptor
+        if (request.getAttribute(OpenEJBLoginValidator.LOGINS) instanceof 
WSSLoginCleanupInterceptor.Logins logins) {
+            logins.release(true);
+            if (logins.isEmpty()) {
+                request.removeAttribute(OpenEJBLoginValidator.LOGINS);
             }
         }
     }
diff --git 
a/server/openejb-cxf/src/main/java/org/apache/openejb/server/cxf/OpenEJBLoginValidator.java
 
b/server/openejb-cxf/src/main/java/org/apache/openejb/server/cxf/OpenEJBLoginValidator.java
index 1c6fc77e17..854bdaa0fa 100644
--- 
a/server/openejb-cxf/src/main/java/org/apache/openejb/server/cxf/OpenEJBLoginValidator.java
+++ 
b/server/openejb-cxf/src/main/java/org/apache/openejb/server/cxf/OpenEJBLoginValidator.java
@@ -16,6 +16,8 @@
  */
 package org.apache.openejb.server.cxf;
 
+import org.apache.cxf.message.Message;
+import org.apache.cxf.transport.http.AbstractHTTPDestination;
 import org.apache.openejb.core.security.AbstractSecurityService;
 import org.apache.openejb.loader.SystemInstance;
 import org.apache.openejb.spi.SecurityService;
@@ -25,10 +27,14 @@ import org.apache.wss4j.dom.handler.RequestData;
 import org.apache.wss4j.dom.message.token.UsernameToken;
 import org.apache.wss4j.dom.validate.UsernameTokenValidator;
 
+import jakarta.servlet.http.HttpServletRequest;
 import javax.security.auth.callback.Callback;
 import javax.security.auth.login.LoginException;
 
 public class OpenEJBLoginValidator extends UsernameTokenValidator {
+    // request attribute holding the logins of the request, CxfWsContainer 
releases the ones of its thread
+    public static final String LOGINS = WSSLoginCleanupInterceptor.Logins.KEY;
+
     @Override
     protected void verifyDigestPassword(final UsernameToken usernameToken,
                                         final RequestData data) throws 
WSSecurityException {
@@ -47,6 +53,7 @@ public class OpenEJBLoginValidator extends 
UsernameTokenValidator {
         final String user = usernameToken.getName();
         final String password = pwCb.getPassword();
         final SecurityService securityService = 
SystemInstance.get().getComponent(SecurityService.class);
+        final Object previousState = securityService.currentState();
         final Object token;
         try {
             securityService.disassociate();
@@ -58,5 +65,13 @@ public class OpenEJBLoginValidator extends 
UsernameTokenValidator {
         } catch (final LoginException e) {
             throw new SecurityException("cannot log user " + user, e);
         }
+
+        // undo the login once the message is processed, a message can carry 
several tokens
+        if (data.getMsgContext() instanceof Message message) {
+            WSSLoginCleanupInterceptor.register(message, token, previousState, 
securityService.currentState());
+            if (message.get(AbstractHTTPDestination.HTTP_REQUEST) instanceof 
HttpServletRequest request) {
+                request.setAttribute(LOGINS, 
WSSLoginCleanupInterceptor.Logins.of(message, false));
+            }
+        }
     }
 }
diff --git 
a/server/openejb-cxf/src/main/java/org/apache/openejb/server/cxf/WSSLoginCleanupInterceptor.java
 
b/server/openejb-cxf/src/main/java/org/apache/openejb/server/cxf/WSSLoginCleanupInterceptor.java
new file mode 100644
index 0000000000..ae31f3f33a
--- /dev/null
+++ 
b/server/openejb-cxf/src/main/java/org/apache/openejb/server/cxf/WSSLoginCleanupInterceptor.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.openejb.server.cxf;
+
+import org.apache.cxf.interceptor.InterceptorChain;
+import org.apache.cxf.interceptor.OutgoingChainInterceptor;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+import org.apache.cxf.phase.Phase;
+import org.apache.openejb.loader.SystemInstance;
+import org.apache.openejb.spi.SecurityService;
+
+import javax.security.auth.login.LoginException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Undoes the logins done by {@link OpenEJBLoginValidator} for a message once 
it has been processed:
+ * the caller identity is removed from the thread which did the login and 
every login token is logged out.
+ * It runs at the end of the in chain (after the response has been sent) and 
at the end of the out fault chain,
+ * so it doesn't depend on the thread which received the request 
(continuations can resume the chain elsewhere).
+ */
+public class WSSLoginCleanupInterceptor extends 
AbstractPhaseInterceptor<Message> {
+    public static final WSSLoginCleanupInterceptor IN = new 
WSSLoginCleanupInterceptor(Phase.POST_INVOKE);
+    public static final WSSLoginCleanupInterceptor OUT_FAULT = new 
WSSLoginCleanupInterceptor(Phase.SETUP_ENDING);
+
+    public WSSLoginCleanupInterceptor(final String phase) {
+        super(WSSLoginCleanupInterceptor.class.getName() + "." + phase, phase);
+        if (Phase.POST_INVOKE.equals(phase)) {
+            addAfter(OutgoingChainInterceptor.class.getName());
+        }
+    }
+
+    @Override
+    public void handleMessage(final Message message) {
+        final Logins logins = Logins.of(message, false);
+        if (logins != null) {
+            logins.release(false);
+        }
+    }
+
+    @Override
+    public void handleFault(final Message message) {
+        handleMessage(message);
+    }
+
+    static void register(final Message message, final Object token, final 
Object previousState, final Object state) {
+        Logins.of(message, true).add(token, previousState, state);
+
+        final InterceptorChain chain = message.getInterceptorChain();
+        if (chain != null && !contains(chain)) {
+            chain.add(IN);
+        }
+    }
+
+    private static boolean contains(final InterceptorChain chain) {
+        for (final Object interceptor : chain) {
+            if (interceptor == IN) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Logins done for a message, shared through the exchange so the fault 
chain sees them too.
+     */
+    public static final class Logins {
+        public static final String KEY = Logins.class.getName();
+
+        private final List<Login> logins = new ArrayList<>();
+
+        static Logins of(final Message message, final boolean create) {
+            final Exchange exchange = message.getExchange();
+            final Object existing = exchange != null ? exchange.get(KEY) : 
message.get(KEY);
+            if (existing instanceof Logins found) {
+                return found;
+            }
+            if (!create) {
+                return null;
+            }
+            final Logins logins = new Logins();
+            if (exchange != null) {
+                exchange.put(KEY, logins);
+            } else {
+                message.put(KEY, logins);
+            }
+            return logins;
+        }
+
+        synchronized void add(final Object token, final Object previousState, 
final Object state) {
+            logins.add(new Login(token, Thread.currentThread(), previousState, 
state));
+        }
+
+        public synchronized boolean isEmpty() {
+            return logins.isEmpty();
+        }
+
+        /**
+         * Restores the caller of the current thread and logs out the tokens.
+         *
+         * @param currentThreadOnly only release the logins done by the 
current thread,
+         *                          the other ones are released by their own 
thread or at the end of the chain
+         */
+        public void release(final boolean currentThreadOnly) {
+            final List<Login> released = new ArrayList<>();
+            final Thread current = Thread.currentThread();
+            synchronized (this) {
+                for (final Iterator<Login> it = logins.iterator(); 
it.hasNext(); ) {
+                    final Login login = it.next();
+                    if (!currentThreadOnly || login.thread == current) {
+                        released.add(login);
+                        it.remove();
+                    }
+                }
+            }
+            if (released.isEmpty()) {
+                return;
+            }
+
+            final SecurityService securityService = 
SystemInstance.get().getComponent(SecurityService.class);
+            if (securityService == null) {
+                return;
+            }
+
+            // latest first so the state before the first login wins
+            for (int i = released.size() - 1; i >= 0; i--) {
+                final Login login = released.get(i);
+                if (login.thread == current && securityService.currentState() 
== login.state) {
+                    securityService.setState(login.previousState);
+                }
+            }
+            for (final Login login : released) {
+                try {
+                    securityService.logout(login.token);
+                } catch (final LoginException e) {
+                    // already logged out
+                }
+            }
+        }
+    }
+
+    private record Login(Object token, Thread thread, Object previousState, 
Object state) {
+    }
+}
diff --git 
a/server/openejb-cxf/src/test/java/org/apache/openejb/server/cxf/CxfWsContainerSecurityStateTest.java
 
b/server/openejb-cxf/src/test/java/org/apache/openejb/server/cxf/CxfWsContainerSecurityStateTest.java
new file mode 100644
index 0000000000..272cfbdf4e
--- /dev/null
+++ 
b/server/openejb-cxf/src/test/java/org/apache/openejb/server/cxf/CxfWsContainerSecurityStateTest.java
@@ -0,0 +1,426 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.openejb.server.cxf;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.bus.extension.ExtensionManagerBus;
+import org.apache.cxf.bus.managers.PhaseManagerImpl;
+import org.apache.cxf.continuations.SuspendedInvocationException;
+import org.apache.cxf.interceptor.AbstractAttributedInterceptorProvider;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.interceptor.InterceptorChain;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.ExchangeImpl;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageImpl;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+import org.apache.cxf.phase.Phase;
+import org.apache.cxf.phase.PhaseInterceptorChain;
+import org.apache.cxf.service.model.EndpointInfo;
+import org.apache.cxf.transport.http.AbstractHTTPDestination;
+import org.apache.cxf.transport.http.DestinationRegistryImpl;
+import org.apache.openejb.core.security.AbstractSecurityService;
+import org.apache.openejb.loader.SystemInstance;
+import org.apache.openejb.server.cxf.transport.HttpDestination;
+import org.apache.openejb.server.httpd.HttpRequest;
+import org.apache.openejb.server.httpd.HttpResponse;
+import org.apache.openejb.spi.SecurityService;
+import org.apache.wss4j.common.ext.WSPasswordCallback;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.dom.WSConstants;
+import org.apache.wss4j.dom.handler.RequestData;
+import org.apache.wss4j.dom.message.token.UsernameToken;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.w3c.dom.Document;
+
+import jakarta.servlet.ServletConfig;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import javax.management.ObjectName;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.login.LoginException;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.IOException;
+import java.lang.reflect.Proxy;
+import java.security.Principal;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class CxfWsContainerSecurityStateTest {
+    private TestSecurityService securityService;
+    private Bus bus;
+
+    @Before
+    public void setUp() {
+        securityService = new TestSecurityService();
+        SystemInstance.get().setComponent(SecurityService.class, 
securityService);
+        bus = new ExtensionManagerBus();
+    }
+
+    @After
+    public void tearDown() {
+        securityService.disassociate();
+        securityService.destroyResource();
+        bus.shutdown(true);
+        SystemInstance.reset();
+    }
+
+    @Test
+    public void callerIsClearedAfterRequest() throws Exception {
+        final AtomicReference<Principal> caller = new AtomicReference<>();
+        final HttpRequest request = request();
+
+        container(new Invocation() {
+            @Override
+            public void invoke(final HttpServletRequest req) throws Exception {
+                login(req);
+                caller.set(securityService.getCallerPrincipal());
+            }
+        }).onMessage(request, null);
+
+        assertNotNull("caller should be associated during the invocation", 
caller.get());
+        assertEquals("jonathan", caller.get().getName());
+        assertNull(securityService.currentState());
+        assertNull(request.getAttribute(OpenEJBLoginValidator.LOGINS));
+        assertEquals(1, securityService.loggedOut.size());
+    }
+
+    @Test
+    public void callerIsClearedWhenInvocationFails() throws Exception {
+        try {
+            container(new Invocation() {
+                @Override
+                public void invoke(final HttpServletRequest req) throws 
Exception {
+                    login(req);
+                    throw new IOException("broken");
+                }
+            }).onMessage(request(), null);
+            fail();
+        } catch (final IOException expected) {
+            // ok
+        }
+
+        assertNull(securityService.currentState());
+        assertEquals(1, securityService.loggedOut.size());
+    }
+
+    @Test
+    public void previousStateIsRestored() throws Exception {
+        final UUID outer = securityService.login("outer", "outer");
+        securityService.associate(outer);
+        final Object outerState = securityService.currentState();
+
+        container(new Invocation() {
+            @Override
+            public void invoke(final HttpServletRequest req) throws Exception {
+                login(req);
+                assertEquals("jonathan", 
securityService.getCallerPrincipal().getName());
+            }
+        }).onMessage(request(), null);
+
+        assertSame(outerState, securityService.currentState());
+        assertEquals(1, securityService.loggedOut.size());
+    }
+
+    @Test
+    public void everyTokenOfTheRequestIsLoggedOut() throws Exception {
+        final HttpRequest request = request();
+
+        container(new Invocation() {
+            @Override
+            public void invoke(final HttpServletRequest req) throws Exception {
+                final Message message = message(req);
+                login(message);
+                login(message);
+                login(message);
+            }
+        }).onMessage(request, null);
+
+        assertNull(securityService.currentState());
+        assertEquals(3, securityService.loggedIn.size());
+        assertEquals(securityService.loggedIn, securityService.loggedOut);
+        assertNull(request.getAttribute(OpenEJBLoginValidator.LOGINS));
+    }
+
+    @Test
+    public void chainReleasesItsLoginsWithoutContainer() {
+        final AtomicReference<Principal> caller = new AtomicReference<>();
+        final PhaseInterceptorChain chain = inChain(
+                new Step(Phase.PRE_PROTOCOL, m -> {
+                    login(m);
+                    login(m);
+                }),
+                new Step(Phase.INVOKE, m -> 
caller.set(securityService.getCallerPrincipal())));
+
+        chain.doIntercept(message(null, chain));
+
+        assertEquals("jonathan", caller.get().getName());
+        assertNull(securityService.currentState());
+        assertEquals(2, securityService.loggedOut.size());
+        assertEquals(securityService.loggedIn, securityService.loggedOut);
+    }
+
+    @Test
+    public void resumedChainReleasesLoginsOnItsOwnThread() throws Exception {
+        final AtomicReference<Principal> caller = new AtomicReference<>();
+        final AtomicReference<Object> stateAfterChain = new 
AtomicReference<>();
+        final AtomicReference<Throwable> error = new AtomicReference<>();
+        final boolean[] suspended = {false};
+        final PhaseInterceptorChain chain = inChain(
+                new Step(Phase.RECEIVE, m -> {
+                    if (!suspended[0]) {
+                        suspended[0] = true;
+                        m.getInterceptorChain().suspend();
+                    }
+                }),
+                new Step(Phase.PRE_PROTOCOL, m -> login(m)),
+                new Step(Phase.INVOKE, m -> 
caller.set(securityService.getCallerPrincipal())));
+
+        try {
+            chain.doIntercept(message(null, chain));
+            fail();
+        } catch (final SuspendedInvocationException expected) {
+            // the destination keeps the message for the continuation
+        }
+        assertEquals(InterceptorChain.State.PAUSED, chain.getState());
+        assertNull(caller.get());
+
+        // the continuation resumes the chain on another thread, outside of 
CxfWsContainer.onMessage
+        final Thread resumer = new Thread(() -> {
+            try {
+                chain.resume();
+                stateAfterChain.set(securityService.currentState());
+            } catch (final Throwable t) {
+                error.set(t);
+            }
+        });
+        resumer.start();
+        resumer.join(TimeUnit.MINUTES.toMillis(1));
+
+        assertNull(error.get());
+        assertEquals("jonathan", caller.get().getName());
+        assertNull("resuming thread must not keep the caller", 
stateAfterChain.get());
+        assertEquals(1, securityService.loggedOut.size());
+        assertEquals(securityService.loggedIn, securityService.loggedOut);
+    }
+
+    @Test
+    public void faultChainReleasesLogins() {
+        final PhaseInterceptorChain outFault = new PhaseInterceptorChain(new 
PhaseManagerImpl().getOutPhases());
+        outFault.add(WSSLoginCleanupInterceptor.OUT_FAULT);
+
+        final AtomicReference<Object> stateAfterFault = new 
AtomicReference<>();
+        final PhaseInterceptorChain chain = inChain(
+                new Step(Phase.PRE_PROTOCOL, m -> login(m)),
+                new Step(Phase.INVOKE, m -> {
+                    throw new Fault(new IllegalStateException("broken"));
+                }));
+        chain.setFaultObserver(m -> {
+            final Message fault = new MessageImpl();
+            fault.setExchange(m.getExchange());
+            outFault.doIntercept(fault);
+            stateAfterFault.set(securityService.currentState());
+        });
+
+        chain.doIntercept(message(null, chain));
+
+        assertNull(stateAfterFault.get());
+        assertEquals(1, securityService.loggedOut.size());
+        assertEquals(securityService.loggedIn, securityService.loggedOut);
+    }
+
+    @Test
+    public void securedEndpointGetsFaultCleanup() {
+        final AbstractAttributedInterceptorProvider endpoint = new 
AbstractAttributedInterceptorProvider() {
+        };
+        final Map<String, Object> in = new HashMap<>();
+        in.put("action", "UsernameToken");
+        ConfigureCxfSecurity.setupWSS4JChain(endpoint, in, null);
+
+        
assertTrue(endpoint.getOutFaultInterceptors().contains(WSSLoginCleanupInterceptor.OUT_FAULT));
+    }
+
+    private PhaseInterceptorChain inChain(final Step... steps) {
+        final PhaseInterceptorChain chain = new PhaseInterceptorChain(new 
PhaseManagerImpl().getInPhases());
+        for (final Step step : steps) {
+            chain.add(step);
+        }
+        return chain;
+    }
+
+    private static Message message(final HttpServletRequest req) {
+        return message(req, null);
+    }
+
+    private static Message message(final HttpServletRequest req, final 
InterceptorChain chain) {
+        final Message message = new MessageImpl();
+        message.setInterceptorChain(chain);
+        final Exchange exchange = new ExchangeImpl();
+        exchange.setInMessage(message);
+        if (req != null) {
+            message.put(AbstractHTTPDestination.HTTP_REQUEST, req);
+        }
+        return message;
+    }
+
+    private void login(final HttpServletRequest req) throws Exception {
+        login(message(req));
+    }
+
+    private void login(final Message message) {
+        final Document doc;
+        try {
+            doc = 
DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+        } catch (final ParserConfigurationException e) {
+            throw new IllegalStateException(e);
+        }
+        final UsernameToken token = new UsernameToken(false, doc, 
WSConstants.PASSWORD_DIGEST);
+        token.setName("jonathan");
+        token.addNonce(doc);
+        token.addCreated(false, doc);
+        token.setPassword("secret");
+
+        final RequestData data = new RequestData();
+        data.setMsgContext(message);
+        data.setCallbackHandler(callbacks -> {
+            for (final Callback callback : callbacks) {
+                if (callback instanceof WSPasswordCallback pwd && 
"jonathan".equals(pwd.getIdentifier())) {
+                    pwd.setPassword("secret");
+                }
+            }
+        });
+
+        try {
+            new OpenEJBLoginValidator().verifyDigestPassword(token, data);
+        } catch (final WSSecurityException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private CxfWsContainer container(final Invocation invocation) throws 
IOException {
+        final EndpointInfo endpointInfo = new EndpointInfo();
+        endpointInfo.setAddress("http://localhost/ws";);
+
+        final CxfWsContainer container = new CxfWsContainer(bus, null, null, 
null) {
+            @Override
+            protected CxfEndpoint createEndpoint() {
+                return null;
+            }
+
+            @Override
+            protected ObjectName registerMBean() {
+                return null;
+            }
+
+            @Override
+            protected void setWsldUrl(final String wsdl) {
+                // no-op
+            }
+        };
+        container.destination = new HttpDestination(bus, new 
DestinationRegistryImpl(), endpointInfo, "/ws") {
+            @Override
+            public void invoke(final ServletConfig config, final 
ServletContext context,
+                               final HttpServletRequest req, final 
HttpServletResponse resp) throws IOException {
+                try {
+                    invocation.invoke(req);
+                } catch (final IOException | RuntimeException e) {
+                    throw e;
+                } catch (final Exception e) {
+                    throw new IllegalStateException(e);
+                }
+            }
+        };
+        return container;
+    }
+
+    private static HttpRequest request() {
+        final Map<String, Object> attributes = new HashMap<>();
+        return (HttpRequest) 
Proxy.newProxyInstance(CxfWsContainerSecurityStateTest.class.getClassLoader(),
+                new Class<?>[]{HttpRequest.class}, (proxy, method, args) -> {
+                    switch (method.getName()) {
+                        case "getAttribute":
+                            return attributes.get(args[0]);
+                        case "setAttribute":
+                            attributes.put((String) args[0], args[1]);
+                            return null;
+                        case "removeAttribute":
+                            attributes.remove(args[0]);
+                            return null;
+                        default:
+                            return null;
+                    }
+                });
+    }
+
+    private static final class Step extends AbstractPhaseInterceptor<Message> {
+        private final Consumer<Message> action;
+
+        private Step(final String phase, final Consumer<Message> action) {
+            super(UUID.randomUUID().toString(), phase);
+            this.action = action;
+        }
+
+        @Override
+        public void handleMessage(final Message message) {
+            action.accept(message);
+        }
+    }
+
+    private interface Invocation {
+        void invoke(HttpServletRequest req) throws Exception;
+    }
+
+    private static class TestSecurityService extends AbstractSecurityService {
+        private final List<UUID> loggedIn = new CopyOnWriteArrayList<>();
+        private final List<UUID> loggedOut = new CopyOnWriteArrayList<>();
+
+        @Override
+        public UUID login(final String securityRealm, final String user, final 
String pass) throws LoginException {
+            if (!user.equals(pass) && !"secret".equals(pass)) {
+                throw new LoginException("bad password");
+            }
+            final UUID token = registerSubject(createSubject(user, user));
+            if (!"outer".equals(user)) {
+                loggedIn.add(token);
+            }
+            return token;
+        }
+
+        @Override
+        public void logout(final UUID securityIdentity) throws LoginException {
+            super.logout(securityIdentity);
+            loggedOut.add(securityIdentity);
+        }
+    }
+}

Reply via email to