From b0cb13ac3705a6bf84633023cfe15070d1e4da6c Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Thu, 23 Sep 2021 09:26:37 +0300 Subject: [PATCH 1/6] Always use the security context from VaadinSession when one is available. This ensures that the security context is the expected (the one from the UI you run access() on) if you run UI.access from a request to another VaadinSession. Practical use case is e.g. sending a global 'refresh' event and the receipient updating the UI as a result. Fixes #906 --- vaadin-spring-tests/pom.xml | 10 +++ .../test-spring-security-flow/pom.xml | 12 +++ .../spring/flowsecurity/SecurityUtils.java | 6 ++ .../flowsecurity/service/BankService.java | 15 +++- .../flowsecurity/views/Broadcaster.java | 31 +++++++ .../flowsecurity/views/PrivateView.java | 68 +++++++++++++++- .../flow/spring/flowsecurity/AbstractIT.java | 81 +++++++++++++++++++ .../flow/spring/flowsecurity/AppViewIT.java | 68 +--------------- .../flowsecurity/UIAccessContextIT.java | 71 ++++++++++++++++ ...dinAwareSecurityContextHolderStrategy.java | 69 ++++++++++++++++ .../VaadinWebSecurityConfigurerAdapter.java | 6 ++ .../spring/SpringClassesSerializableTest.java | 1 + 12 files changed, 368 insertions(+), 70 deletions(-) create mode 100644 vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/Broadcaster.java create mode 100644 vaadin-spring-tests/test-spring-security-flow/src/test/java/com/vaadin/flow/spring/flowsecurity/AbstractIT.java create mode 100644 vaadin-spring-tests/test-spring-security-flow/src/test/java/com/vaadin/flow/spring/flowsecurity/UIAccessContextIT.java create mode 100644 vaadin-spring/src/main/java/com/vaadin/flow/spring/security/VaadinAwareSecurityContextHolderStrategy.java diff --git a/vaadin-spring-tests/pom.xml b/vaadin-spring-tests/pom.xml index 3bd05397a..321fe86ae 100644 --- a/vaadin-spring-tests/pom.xml +++ b/vaadin-spring-tests/pom.xml @@ -56,6 +56,16 @@ vaadin-upload-flow ${vaadin.version} + + com.vaadin + vaadin-dialog-flow + ${vaadin.version} + + + com.vaadin + vaadin-notification-flow + ${vaadin.version} + com.vaadin vaadin-ordered-layout-flow diff --git a/vaadin-spring-tests/test-spring-security-flow/pom.xml b/vaadin-spring-tests/test-spring-security-flow/pom.xml index dda6c2210..720ab713e 100644 --- a/vaadin-spring-tests/test-spring-security-flow/pom.xml +++ b/vaadin-spring-tests/test-spring-security-flow/pom.xml @@ -36,10 +36,22 @@ com.vaadin vaadin-tabs-flow + + com.vaadin + vaadin-text-field-flow + com.vaadin vaadin-upload-flow + + com.vaadin + vaadin-dialog-flow + + + com.vaadin + vaadin-notification-flow + com.vaadin vaadin-login-flow diff --git a/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityUtils.java b/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityUtils.java index 3b4b7013f..30150f4de 100644 --- a/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityUtils.java +++ b/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/SecurityUtils.java @@ -22,6 +22,12 @@ public class SecurityUtils { public UserDetails getAuthenticatedUser() { SecurityContext context = SecurityContextHolder.getContext(); + if (context == null) { + throw new IllegalStateException("No security context available"); + } + if (context.getAuthentication() == null) { + return null; + } Object principal = context.getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { UserDetails userDetails = (UserDetails) context.getAuthentication() diff --git a/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/service/BankService.java b/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/service/BankService.java index 634cc634e..9d1f7895a 100644 --- a/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/service/BankService.java +++ b/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/service/BankService.java @@ -19,13 +19,25 @@ public class BankService { private SecurityUtils utils; public void applyForLoan() { + applyForLoan(10000); + } + + public void applyForHugeLoan() { + applyForLoan(1000000); + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + } + } + + private void applyForLoan(int amount) { String name = utils.getAuthenticatedUser().getUsername(); Optional acc = accountRepository.findByOwner(name); if (!acc.isPresent()) { return; } Account account = acc.get(); - account.setBalance(account.getBalance().add(new BigDecimal("10000"))); + account.setBalance(account.getBalance().add(new BigDecimal(amount))); accountRepository.save(account); } @@ -34,4 +46,5 @@ public BigDecimal getBalance() { return accountRepository.findByOwner(name).map(Account::getBalance) .orElse(null); } + } diff --git a/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/Broadcaster.java b/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/Broadcaster.java new file mode 100644 index 000000000..482b4c642 --- /dev/null +++ b/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/Broadcaster.java @@ -0,0 +1,31 @@ +package com.vaadin.flow.spring.flowsecurity.views; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.ComponentEventBus; +import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.shared.Registration; + +public class Broadcaster { + + private static Broadcaster instance = new Broadcaster(); + + private ComponentEventBus router = new ComponentEventBus(new Div()); + + public static class RefreshEvent extends ComponentEvent { + public RefreshEvent() { + super(new Div(), false); + } + } + + public static void sendMessage() { + instance.router.fireEvent(new RefreshEvent()); + } + + public static Registration addMessageListener( + ComponentEventListener listener) { + return instance.router.addListener(RefreshEvent.class, listener); + } + +} \ No newline at end of file diff --git a/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/PrivateView.java b/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/PrivateView.java index a835d1927..3c5ed6f81 100644 --- a/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/PrivateView.java +++ b/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/PrivateView.java @@ -3,23 +3,34 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.concurrent.Executor; import javax.annotation.security.PermitAll; +import com.vaadin.flow.component.AttachEvent; import com.vaadin.flow.component.ClickEvent; +import com.vaadin.flow.component.DetachEvent; +import com.vaadin.flow.component.Text; +import com.vaadin.flow.component.UI; import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.dialog.Dialog; import com.vaadin.flow.component.html.H4; import com.vaadin.flow.component.html.Image; import com.vaadin.flow.component.html.Paragraph; import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.notification.Notification; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.upload.Upload; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import com.vaadin.flow.server.StreamResource; +import com.vaadin.flow.shared.Registration; import com.vaadin.flow.spring.flowsecurity.SecurityUtils; import com.vaadin.flow.spring.flowsecurity.service.BankService; +import org.springframework.security.concurrent.DelegatingSecurityContextExecutor; + @Route(value = "private", layout = MainView.class) @PageTitle("Private View") @PermitAll @@ -28,15 +39,27 @@ public class PrivateView extends VerticalLayout { private BankService bankService; private Span balanceSpan = new Span(); private SecurityUtils utils; + private DelegatingSecurityContextExecutor executor; + private Registration registration; - public PrivateView(BankService bankService, SecurityUtils utils) { + public PrivateView(BankService bankService, SecurityUtils utils, + Executor executor) { this.bankService = bankService; this.utils = utils; + this.executor = new DelegatingSecurityContextExecutor(executor); updateBalanceText(); balanceSpan.setId("balanceText"); add(balanceSpan); add(new Button("Apply for a loan", this::applyForLoan)); + add(new Button("Apply for a huge loan", + this::applyForHugeLoanUsingExecutor)); + + Button globalRefresh = new Button("Send global refresh event", + e -> Broadcaster.sendMessage()); + globalRefresh.setId("sendRefresh"); + add(globalRefresh); + Upload upload = new Upload(); ByteArrayOutputStream imageStream = new ByteArrayOutputStream(); upload.setReceiver((filename, mimeType) -> { @@ -57,16 +80,57 @@ public PrivateView(BankService bankService, SecurityUtils utils) { add(upload); } + @Override + protected void onAttach(AttachEvent attachEvent) { + super.onAttach(attachEvent); + attachEvent.getUI().setPollInterval(1000); + registration = Broadcaster.addMessageListener(e -> { + getUI().get().access(() -> this.updateBalanceText()); + }); + } + + @Override + protected void onDetach(DetachEvent detachEvent) { + super.onDetach(detachEvent); + detachEvent.getUI().setPollInterval(-1); + registration.remove(); + } + private void updateBalanceText() { String name = utils.getAuthenticatedUserInfo().getFullName(); BigDecimal balance = bankService.getBalance(); this.balanceSpan.setText(String.format( "Hello %s, your bank account balance is $%s.", name, balance)); - } private void applyForLoan(ClickEvent