Skip to content

Commit

Permalink
fix: Make all public views available without login (#858)
Browse files Browse the repository at this point in the history
Previously only a root mapped ("") view was available.

Fixes vaadin/flow#11355
  • Loading branch information
Artur- authored Jun 30, 2021
1 parent 9c8852d commit 9a62a4d
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.vaadin.flow.spring.flowsecurity.views;

import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.orderedlayout.FlexLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.auth.AnonymousAllowed;

@Route(value = "another", layout = MainView.class)
@PageTitle("Another Public View")
@AnonymousAllowed
public class AnotherPublicView extends FlexLayout {

public AnotherPublicView() {
setFlexDirection(FlexDirection.COLUMN);
setHeightFull();

H1 header = new H1("Another public view for testing");
header.setId("header");
header.getStyle().set("text-align", "center");
add(header);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
public class AppViewIT extends ChromeBrowserTest {

private static final String ROOT_PAGE_HEADER_TEXT = "Welcome to the Java Bank of Vaadin";
private static final String ANOTHER_PUBLIC_PAGE_HEADER_TEXT = "Another public view for testing";
private static final int SERVER_PORT = 8888;
private static final String USER_FULLNAME = "John the User";
private static final String ADMIN_FULLNAME = "Emma the Admin";
Expand Down Expand Up @@ -109,6 +110,12 @@ public void root_page_does_not_require_login() {
assertRootPageShown();
}

@Test
public void other_public_page_does_not_require_login() {
open("another");
assertAnotherPublicPageShown();
}

@Test
public void navigate_to_private_view_prevented() {
open("");
Expand Down Expand Up @@ -301,6 +308,12 @@ private void assertRootPageShown() {
Assert.assertEquals(ROOT_PAGE_HEADER_TEXT, headerText);
}

private void assertAnotherPublicPageShown() {
waitUntil(drive -> $("h1").attribute("id", "header").exists());
String headerText = $("h1").id("header").getText();
Assert.assertEquals(ANOTHER_PUBLIC_PAGE_HEADER_TEXT, headerText);
}

private void assertPrivatePageShown(String fullName) {
assertPathShown("private");
waitUntil(driver -> $("span").attribute("id", "balanceText").exists());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ public boolean isAnonymousEndpoint(HttpServletRequest request) {
return false;
}

/**
* Checks whether the request targets a Flow route that is public, i.e.
* marked as @{@link AnonymousAllowed}.
*
* @param request
* the servlet request
* @return {@code true} if the request is targeting an anonymous route,
* {@code false} otherwise
*/
public boolean isAnonymousRoute(HttpServletRequest request) {
String vaadinMapping = configurationProperties.getUrlMapping();
String requestedPath = getRequestPathInsideContext(request);
Expand All @@ -115,6 +124,12 @@ public boolean isAnonymousRoute(HttpServletRequest request) {
return false;
}
String path = maybePath.get();
if (path.startsWith("/")) {
// Requested path includes a beginning "/" but route mapping is done
// without one
path = path.substring(1);
}

SpringServlet servlet = springServletRegistration.getServlet();
VaadinService service = servlet.getService();
Router router = service.getRouter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,28 @@

import java.util.Collections;
import java.util.Map;
import java.util.Optional;

import javax.annotation.security.RolesAllowed;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.internal.AnnotationReader;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.Router;
import com.vaadin.flow.router.internal.NavigationRouteTarget;
import com.vaadin.flow.router.internal.RouteTarget;
import com.vaadin.flow.router.internal.RouteUtil;
import com.vaadin.flow.server.HandlerHelper.RequestType;
import com.vaadin.flow.server.RouteRegistry;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import com.vaadin.fusion.VaadinConnectControllerConfiguration;
import com.vaadin.fusion.VaadinEndpointProperties;
import com.vaadin.flow.shared.ApplicationConstants;
import com.vaadin.flow.spring.SpringBootAutoConfiguration;
import com.vaadin.flow.spring.SpringSecurityAutoConfiguration;
import com.vaadin.flow.spring.SpringServlet;
import com.vaadin.flow.spring.SpringVaadinServletService;
import com.vaadin.flow.spring.VaadinConfigurationProperties;
import com.vaadin.fusion.VaadinConnectControllerConfiguration;
import com.vaadin.fusion.VaadinEndpointProperties;

import org.junit.Assert;
import org.junit.Test;
Expand Down Expand Up @@ -132,14 +135,15 @@ public void testExternalRequest_other_customMapping() {
.isFrameworkInternalRequest(createRequest("/foo", null)));
}

@Test
public void testAnonymousRouteRequest_rootServlet() {
@Route("")
@AnonymousAllowed
public static class PublicRootView extends Component {

}

@Route("")
@Route("other")
@AnonymousAllowed
public static class PublicView extends Component {
public static class AnotherPublicView extends Component {

}

Expand All @@ -153,29 +157,41 @@ public static class AdminView extends Component {
public void testAnonymousRouteRequest_rootMappedServlet_publicView() {
Mockito.when(vaadinConfigurationProperties.getUrlMapping())
.thenReturn("/*");
setupMockServlet("/", PublicView.class);
SpringServlet servlet = setupMockServlet();
addRoute(servlet, PublicRootView.class);
addRoute(servlet, AnotherPublicView.class);

MockHttpServletRequest request = createRequest(null);
request.setServletPath("/");
Assert.assertTrue(requestUtil.isAnonymousRoute(request));

request = createRequest("other");
request.setServletPath("/");
Assert.assertTrue(requestUtil.isAnonymousRoute(request));
}

@Test
public void testAnonymousRouteRequest_rootMappedServlet_notAView() {
Mockito.when(vaadinConfigurationProperties.getUrlMapping())
.thenReturn("/*");
setupMockServlet("/", PublicView.class);
SpringServlet servlet = setupMockServlet();
addRoute(servlet, PublicRootView.class);
addRoute(servlet, AnotherPublicView.class);

MockHttpServletRequest request = createRequest(null);
request.setServletPath("/bar");
Assert.assertFalse(requestUtil.isAnonymousRoute(request));

request = createRequest("other");
request.setServletPath("/bar");
Assert.assertFalse(requestUtil.isAnonymousRoute(request));
}

@Test
public void testAnonymousRouteRequest_rootMappedServlet_privateView() {
Mockito.when(vaadinConfigurationProperties.getUrlMapping())
.thenReturn("/*");
setupMockServlet("/admin", AdminView.class);
addRoute(setupMockServlet(), AdminView.class);

MockHttpServletRequest request = createRequest(null);
request.setServletPath("/admin");
Expand All @@ -186,29 +202,41 @@ public void testAnonymousRouteRequest_rootMappedServlet_privateView() {
public void testAnonymousRouteRequest_fooMappedServlet_publicView() {
Mockito.when(vaadinConfigurationProperties.getUrlMapping())
.thenReturn("/foo/*");
setupMockServlet("", PublicView.class);
SpringServlet servlet = setupMockServlet();
addRoute(servlet, PublicRootView.class);
addRoute(servlet, AnotherPublicView.class);

MockHttpServletRequest request = createRequest(null);
request.setServletPath("/foo/");
Assert.assertTrue(requestUtil.isAnonymousRoute(request));

request = createRequest("other");
request.setServletPath("/foo/");
Assert.assertTrue(requestUtil.isAnonymousRoute(request));
}

@Test
public void testAnonymousRouteRequest_fooMappedServlet_notAView() {
Mockito.when(vaadinConfigurationProperties.getUrlMapping())
.thenReturn("/foo/*");
setupMockServlet("", PublicView.class);
SpringServlet servlet = setupMockServlet();
addRoute(servlet, PublicRootView.class);
addRoute(servlet, AnotherPublicView.class);

MockHttpServletRequest request = createRequest(null);
request.setServletPath("/foo/bar");
Assert.assertFalse(requestUtil.isAnonymousRoute(request));

request = createRequest("other");
request.setServletPath("/foo/bar");
Assert.assertFalse(requestUtil.isAnonymousRoute(request));
}

@Test
public void testAnonymousRouteRequest_fooMappedServlet_privateView() {
Mockito.when(vaadinConfigurationProperties.getUrlMapping())
.thenReturn("/foo/*");
setupMockServlet("admin", AdminView.class);
addRoute(setupMockServlet(), AdminView.class);

MockHttpServletRequest request = createRequest(null);
request.setServletPath("/foo/admin");
Expand All @@ -219,34 +247,57 @@ public void testAnonymousRouteRequest_fooMappedServlet_privateView() {
public void testAnonymousRouteRequest_fooMappedServlet_publicViewPathOutsideServlet() {
Mockito.when(vaadinConfigurationProperties.getUrlMapping())
.thenReturn("/foo/*");
setupMockServlet("", PublicView.class);
SpringServlet servlet = setupMockServlet();
addRoute(servlet, PublicRootView.class);
addRoute(servlet, AnotherPublicView.class);

MockHttpServletRequest request = createRequest(null);
request.setServletPath("/");
Assert.assertFalse(requestUtil.isAnonymousRoute(request));
request = createRequest("other");
request.setServletPath("/");
Assert.assertFalse(requestUtil.isAnonymousRoute(request));
}

private void setupMockServlet(String viewRoute,
Class<? extends Component> view) {
private SpringServlet setupMockServlet() {
SpringServlet servlet = Mockito.mock(SpringServlet.class);
SpringVaadinServletService service = Mockito
.mock(SpringVaadinServletService.class);
Router router = Mockito.mock(Router.class);
RouteRegistry routeRegistry = Mockito.mock(RouteRegistry.class);
NavigationRouteTarget publicNavigationTarget = Mockito
.mock(NavigationRouteTarget.class);
RouteTarget publicRouteTarget = Mockito.mock(RouteTarget.class);

Mockito.when(springServletRegistration.getServlet())
.thenReturn(servlet);
Mockito.when(servlet.getService()).thenReturn(service);
Mockito.when(service.getRouter()).thenReturn(router);
Mockito.when(router.getRegistry()).thenReturn(routeRegistry);
Mockito.when(routeRegistry.getNavigationRouteTarget(viewRoute))
.thenReturn(publicNavigationTarget);
Mockito.when(publicNavigationTarget.getRouteTarget())
return servlet;
}

private void addRoute(SpringServlet servlet,
Class<? extends Component> view) {

Optional<Route> route = AnnotationReader.getAnnotationFor(view,
Route.class);

if (!route.isPresent()) {
throw new IllegalArgumentException(
"Unable find a @Route annotation");
}

String path = RouteUtil.getRoutePath(view, route.get());
RouteRegistry routeRegistry = servlet.getService().getRouter()
.getRegistry();
RouteTarget publicRouteTarget = Mockito.mock(RouteTarget.class);
NavigationRouteTarget navigationTarget = Mockito
.mock(NavigationRouteTarget.class);

Mockito.when(routeRegistry.getNavigationRouteTarget(path))
.thenReturn(navigationTarget);
Mockito.when(navigationTarget.getRouteTarget())
.thenReturn(publicRouteTarget);
Mockito.when(publicRouteTarget.getTarget()).thenReturn((Class) view);

}

static MockHttpServletRequest createRequest(String pathInfo) {
Expand Down

0 comments on commit 9a62a4d

Please sign in to comment.