Skip to content

Commit

Permalink
fix: handle logout from a background thread (#20688) (#20707)
Browse files Browse the repository at this point in the history
Allows AuthenticationContext.logout feature to be used from a background thread,
handling the missing request by forcing the client to make an additional request.
Applies the same logic used to support logout when using PUSH with websocket
transport.

References #11026

Co-authored-by: Marco Collovati <[email protected]>
  • Loading branch information
vaadin-bot and mcollovati authored Dec 16, 2024
1 parent 0112560 commit ccc8ebd
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package com.vaadin.flow.spring.flowsecurity.views;

import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import org.springframework.security.concurrent.DelegatingSecurityContextExecutor;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentUtil;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle;
import com.vaadin.flow.component.avatar.Avatar;
Expand Down Expand Up @@ -97,6 +102,18 @@ private Component createDrawerContent(Tabs menu) {
});
layout.add(logout);

Button logoutFromServer = new Button("Logout from server");
logoutFromServer.setId("logout-server");
logoutFromServer.addClickListener(e -> {
UI ui = UI.getCurrent();
Runnable action = ui.accessLater(() -> securityUtils.logout(),
null);
CompletableFuture.runAsync(action,
new DelegatingSecurityContextExecutor(CompletableFuture
.delayedExecutor(1, TimeUnit.SECONDS)));
});
layout.add(logoutFromServer);

Anchor logoutWithUrl = new Anchor("doLogout", "Logout with URL");
logoutWithUrl.getElement().setAttribute("router-ignore", true);
logoutWithUrl.setId("logout-anchor");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,16 @@ public void logout_via_doLogoutURL_redirects_to_logout() {
assertLogoutViewShown();
}

@Test
public void logout_server_initiated_redirects_to_logout() {
open(LOGIN_PATH);
loginAdmin();
navigateTo("admin");
assertAdminPageShown(ADMIN_FULLNAME);
getMainView().$(ButtonElement.class).id("logout-server").click();
assertRootPageShown();
}

@Test
public void client_menu_routes_correct_for_anonymous() {
navigateToClientMenuList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@
*/
package com.vaadin.flow.spring.flowsecurity;

import org.junit.Assert;
import org.junit.Test;
import org.openqa.selenium.WebDriver;

import com.vaadin.flow.component.button.testbench.ButtonElement;
import com.vaadin.flow.component.login.testbench.LoginFormElement;
import com.vaadin.flow.component.login.testbench.LoginOverlayElement;
import com.vaadin.testbench.HasElementQuery;
import com.vaadin.testbench.TestBenchElement;

import org.junit.Assert;
import org.junit.Test;
import org.openqa.selenium.WebDriver;

public class UIAccessContextIT extends AbstractIT {

@Test
Expand All @@ -37,14 +37,15 @@ public void securityContextSetForUIAccess() throws Exception {
super.setup();
open("private");
loginUser();
TestBenchElement balance = $("span").id("balanceText");
TestBenchElement balance = waitUntil(
d -> $("span").id("balanceText"));
Assert.assertEquals(expectedUserBalance, balance.getText());

open("private", adminBrowser);
HasElementQuery adminContext = () -> adminBrowser;
loginAdmin(adminContext);
TestBenchElement adminBalance = adminContext.$("span")
.id("balanceText");
TestBenchElement adminBalance = waitUntil(
d -> adminContext.$("span").id("balanceText"));
Assert.assertEquals(expectedAdminBalance, adminBalance.getText());

ButtonElement sendRefresh = $(ButtonElement.class)
Expand All @@ -70,6 +71,10 @@ private void loginAdmin(HasElementQuery adminContext) {
form.getUsernameField().setValue("emma");
form.getPasswordField().setValue("emma");
form.submit();
waitUntilNot(driver -> ((WebDriver) adminContext.getContext())
.getCurrentUrl().contains("my/login/page"));
waitUntilNot(
driver -> adminContext.$(LoginOverlayElement.class).exists());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.springframework.util.Assert;

import com.vaadin.flow.component.UI;
import com.vaadin.flow.server.VaadinRequest;
import com.vaadin.flow.server.VaadinServletRequest;
import com.vaadin.flow.server.VaadinServletResponse;
import com.vaadin.flow.shared.ui.Transport;
Expand Down Expand Up @@ -131,8 +132,10 @@ public boolean isAuthenticated() {
*/
public void logout() {
final UI ui = UI.getCurrent();
if (ui.getPushConfiguration().getTransport() == Transport.WEBSOCKET
&& ui.getInternals().getPushConnection().isConnected()) {
boolean pushWebsocketConnected = ui.getPushConfiguration()
.getTransport() == Transport.WEBSOCKET
&& ui.getInternals().getPushConnection().isConnected();
if (pushWebsocketConnected) {
// WEBSOCKET transport mode would not log out properly after session
// invalidation. Switching to WEBSOCKET_XHR for a single request
// to do the logout.
Expand All @@ -151,6 +154,11 @@ public void logout() {
ui.getPushConfiguration().setTransport(Transport.WEBSOCKET);
doLogout(ui);
});
} else if (VaadinRequest.getCurrent() == null) {
// Logout started from a background thread, force client to send
// a request
ui.getPage().executeJs("return true").then(ignored -> doLogout(ui),
error -> doLogout(ui));
} else {
doLogout(ui);
}
Expand Down Expand Up @@ -496,7 +504,7 @@ public void logout(HttpServletRequest request,
private boolean isContinueToNextHandler(HttpServletRequest request,
LogoutHandler handler) {
return handler instanceof SecurityContextLogoutHandler
&& (request.getSession() == null
&& (request.getSession(false) == null
|| !request.isRequestedSessionIdValid());
}
}
Expand Down

0 comments on commit ccc8ebd

Please sign in to comment.