Security issue in Bakery demo (Or at the least very unexpected behavior)?

While attempting to secure the application I’m building in Flow with Spring I looked at the method the Bakery demo was using for restricting access to views. The process of using a BeforeEnterEvent method in the MainView that checks if access is granted with SecurityUtils.isAccessGranted(event.getNavigationTarget()) appears to not work as I expected.

What I was looking for is to have a view fully restricted from a user that does not have the appropriate role assigned to them, however it appears that restriction can be easily bypassed by navigating to the target URL directly. This issue appears to be present even in the online demo.

Steps to reproduce:

  1. Go to https://bakery-flow.demo.vaadin.com/login
  2. Log in as the user with the Barista role.
  3. Navigate to https://bakery-flow.demo.vaadin.com/users

The app allows you to navigate to this view with no issues or complaints, despite the relevant view being annotated with @Secured(Role.ADMIN) and the button for navigating the view being correctly hidden. Is this what is supposed to happen? If it is, is there a way to prevent it in my own application?

Hi Patrick,

Thanks for reporting this, it seems to be a regression and it’s not supposed to happen, we will take a look at this with the team and fix it for the starter and will also share the fix here.

The issue is not happening when running with mvn spring-boot:run, only when running as a war in Tomcat. The problem is that the UI param is not being picked up by the servlet when running this way, so BakeryUI never gets used.

Removing BakeryUI and moving the code to a UIInitListener seems to fix the issue. So you can just:

  1. Remove BakeryUI
  2. Remove the server.servlet.context-parameters.UI line in application.properties
  3. Add the following class
package com.vaadin.starter.bakery.app.security;

import com.vaadin.flow.component.UI;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.server.ServiceInitEvent;
import com.vaadin.flow.server.VaadinServiceInitListener;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.starter.bakery.ui.components.OfflineBanner;
import com.vaadin.starter.bakery.ui.exceptions.AccessDeniedException;
import com.vaadin.starter.bakery.ui.views.login.LoginView;

/**
 * Adds before enter listener to check access to views.
 * Adds the Offline banner.
 * 
 */
@SpringComponent
public class ConfigureUIServiceInitListener implements VaadinServiceInitListener {

	@Override
	public void serviceInit(ServiceInitEvent event) {
		event.getSource().addUIInitListener(uiEvent -> {
			final UI ui = uiEvent.getUI();
			ui.add(new OfflineBanner());
			ui.addBeforeEnterListener(this::beforeEnter);
		});
	}

	/**
	 * Reroutes the user if she is not authorized to access the view. 
	 *
	 * @param event
	 *            before navigation event with event details
	 */
	private void beforeEnter(BeforeEnterEvent event) {
		final boolean accessGranted = SecurityUtils.isAccessGranted(event.getNavigationTarget());
		if (!accessGranted) {
			if (SecurityUtils.isUserLoggedIn()) {
				event.rerouteToError(AccessDeniedException.class);
			} else {
				event.rerouteTo(LoginView.class);
			}
		}
	}
}

Thank you, it took some fiddling but it all seems to be working now!