karibu-testing icon indicating copy to clipboard operation
karibu-testing copied to clipboard

Problem with OAuth authentication

Open simasch opened this issue 1 year ago • 5 comments

I use Keycloak for authentication but I get this exception when trying to test with Karibu testing.

All my routes are annotated with @PermitAll. So there is no route "" available without authentication and also no LoginView.

If replace @PermitAll with @AnonymousAllowed on the "" route then it works.

com.vaadin.flow.router.NotFoundException: No route found for '': No route found for ''
Available routes: [ClubContestCreateView at '/clubcontest', EventView at '/events', StatusView at '/status']
If you'd like to revert back to the original Vaadin RouteNotFoundError, please remove the class com.github.mvysny.kaributesting.v10.MockRouteNotFoundError from Routes.errorRoutes
Available routes: [ClubContestCreateView at '/clubcontest', EventView at '/events', StatusView at '/status']
If you'd like to revert back to the original Vaadin RouteNotFoundError, please remove the class com.github.mvysny.kaributesting.v10.MockRouteNotFoundError from Routes.errorRoutes

	at com.github.mvysny.kaributesting.v10.MockRouteNotFoundError.setErrorParameter(Routes.kt:126)
	at com.vaadin.flow.router.internal.ErrorStateRenderer.notifyNavigationTarget(ErrorStateRenderer.java:121)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.notifyNavigationTarget(AbstractNavigationStateRenderer.java:623)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.sendBeforeEnterEvent(AbstractNavigationStateRenderer.java:600)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.sendBeforeEnterEventAndPopulateChain(AbstractNavigationStateRenderer.java:501)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.createChainIfEmptyAndExecuteBeforeEnterNavigation(AbstractNavigationStateRenderer.java:473)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.handle(AbstractNavigationStateRenderer.java:211)
	at com.vaadin.flow.router.internal.ErrorStateRenderer.handle(ErrorStateRenderer.java:106)
	at com.vaadin.flow.component.UI.handleExceptionNavigation(UI.java:1818)
	at com.vaadin.flow.component.UI.handleNavigation(UI.java:1793)
	at com.vaadin.flow.component.UI.renderViewForRoute(UI.java:1748)
	at com.vaadin.flow.component.UI.navigate(UI.java:1148)
	at com.vaadin.flow.component.UI.navigate(UI.java:1118)
	at com.github.mvysny.kaributesting.v10.MockVaadin.createUI$karibu_testing_v10(MockVaadin.kt:245)
	at com.github.mvysny.kaributesting.v10.MockVaadin.createSession(MockVaadin.kt:213)
	at com.github.mvysny.kaributesting.v10.MockVaadin.setup(MockVaadin.kt:95)
	at ch.pinnatec.stv.contest20.ui.KaribuTest.setup(KaribuTest.java:47)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptLifecycleMethod(TimeoutExtension.java:128)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptBeforeEachMethod(TimeoutExtension.java:78)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeMethodInExtensionContext(ClassBasedTestDescriptor.java:520)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$synthesizeBeforeEachMethodAdapter$23(ClassBasedTestDescriptor.java:505)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeEachMethods$3(TestMethodTestDescriptor.java:174)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeMethodsOrCallbacksUntilExceptionOccurs$6(TestMethodTestDescriptor.java:202)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(TestMethodTestDescriptor.java:202)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeEachMethods(TestMethodTestDescriptor.java:171)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:134)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: com.vaadin.flow.router.NotFoundException: No route found for ''
Available routes: [ClubContestCreateView at '/clubcontest', EventView at '/events', StatusView at '/status']
If you'd like to revert back to the original Vaadin RouteNotFoundError, please remove the class com.github.mvysny.kaributesting.v10.MockRouteNotFoundError from Routes.errorRoutes
	at com.github.mvysny.kaributesting.v10.MockRouteNotFoundError.setErrorParameter(Routes.kt:126)
	at com.vaadin.flow.router.internal.ErrorStateRenderer.notifyNavigationTarget(ErrorStateRenderer.java:121)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.notifyNavigationTarget(AbstractNavigationStateRenderer.java:623)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.sendBeforeEnterEvent(AbstractNavigationStateRenderer.java:600)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.sendBeforeEnterEventAndPopulateChain(AbstractNavigationStateRenderer.java:501)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.createChainIfEmptyAndExecuteBeforeEnterNavigation(AbstractNavigationStateRenderer.java:473)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.handle(AbstractNavigationStateRenderer.java:211)
	at com.vaadin.flow.router.internal.ErrorStateRenderer.handle(ErrorStateRenderer.java:106)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.reroute(AbstractNavigationStateRenderer.java:724)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.handleTriggeredBeforeEvent(AbstractNavigationStateRenderer.java:682)
	at com.vaadin.flow.component.internal.JavaScriptNavigationStateRenderer.handleTriggeredBeforeEvent(JavaScriptNavigationStateRenderer.java:99)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.sendBeforeEnterEvent(AbstractNavigationStateRenderer.java:615)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.sendBeforeEnterEvent(AbstractNavigationStateRenderer.java:590)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.createChainIfEmptyAndExecuteBeforeEnterNavigation(AbstractNavigationStateRenderer.java:466)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.handle(AbstractNavigationStateRenderer.java:211)
	at com.vaadin.flow.component.internal.JavaScriptNavigationStateRenderer.handle(JavaScriptNavigationStateRenderer.java:78)
	at com.vaadin.flow.component.UI.handleNavigation(UI.java:1785)
	... 81 more
Caused by: com.vaadin.flow.router.NotFoundException
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at com.vaadin.flow.internal.ReflectTools.createProxyInstance(ReflectTools.java:484)
	at com.vaadin.flow.internal.ReflectTools.createInstance(ReflectTools.java:452)
	at com.vaadin.flow.router.BeforeEvent.rerouteToError(BeforeEvent.java:756)
	at com.vaadin.flow.router.BeforeEvent.rerouteToError(BeforeEvent.java:740)
	at com.vaadin.flow.server.auth.ViewAccessChecker.beforeEnter(ViewAccessChecker.java:203)
	at com.vaadin.flow.router.internal.AbstractNavigationStateRenderer.sendBeforeEnterEvent(AbstractNavigationStateRenderer.java:613)
	... 86 more

Any idea?

simasch avatar Mar 25 '23 10:03 simasch

Is the route detected properly in Routes.routes? I guess it is, but doesn't hurt to ask :-)

Could it be that the tests are running in production mode? If you try to access a route to which you don't have access to, Vaadin in production mode responds with 404 instead of 401.

The following lines are puzzling:

Available routes: [ClubContestCreateView at '/clubcontest', EventView at '/events', StatusView at '/status']

It comes from MockRouteNotFoundError but the list of routes comes from Vaadin registry rather than from Routes.routes. Perhaps Vaadin registry in production mode only returns routes that you have access to.

mvysny avatar Mar 25 '23 11:03 mvysny

It runs in development mode Vaadin is running in DEVELOPMENT mode - do not use for production deployments.

Routes are found: Routes(routes=ClubContestCreateView, EventView, StatusView, errorRoutes=MockRouteNotFoundError)

EventView is the view with the RouteAlias "".

I'm using Vaadin 24.

simasch avatar Mar 25 '23 13:03 simasch

at com.vaadin.flow.router.BeforeEvent.rerouteToError(BeforeEvent.java:756)
at com.vaadin.flow.router.BeforeEvent.rerouteToError(BeforeEvent.java:740)
at com.vaadin.flow.server.auth.ViewAccessChecker.beforeEnter(ViewAccessChecker.java:203)

This is probably where the answer lies. The ViewAccessChecker decides to call BeforeEvent.rerouteToError() for some reason. @AnonymousAllowed disables the security checks in ViewAccessChecker completely (since it allows anyone including when no-one is logged in), that's why replacing @PermitAll with @AnonymousAllowed works.

Probably Keycloak is not set up in your mock env properly; the servlet principal is therefore null and ViewAccessChecker thinks no-one is logged in. @PermitAll requires any user to be logged in, which thus fails with a null principal.

mvysny avatar Apr 03 '23 13:04 mvysny

The problem is, that this happens on startup of the test.

My test looks like this:

class EventViewIT extends KaribuTest {

  @BeforeEach
  void login() {
    login("_dz", "", List.of("ADMIN"));

    UI.getCurrent().navigate(EventView.class);
  }

  @Test
  void events_displayed() {
    _assert(Div.class, 2, s -> s.withClasses("contest-card"));
  }
}

And the failure happens here in my base test class

  @BeforeEach
  public void setup() {
    final Function0<UI> uiFactory = UI::new;
    final SpringServlet servlet = new MockSpringServlet(routes, ctx, uiFactory);
    MockVaadin.setup(uiFactory, servlet);
  }

And there is no login view. How should this work?

simasch avatar Apr 03 '23 13:04 simasch

At the moment, when Karibu is mocking the UI (during call to MockVaadin.setup()), it also navigates to the main view (""), which fails in your case. The reason for this navigation is to simulate the initial Vaadin state properly, which includes showing the root page. You can see the code in MockVaadin.createUI().

The navigation is only performed if the route exists:

            if (UI.getCurrent().internals.router.registry.getNavigationTarget("").isPresent) {
                UI.getCurrent().navigate("")
            }

So, apparently the route exists in your case, and thus the navigation is attempted, which then fails.

ViewAccessChecker contains the following lines:

            if (loginView != null) {
                beforeEnterEvent.forwardTo(loginView);
            } else {
                if (loginUrl != null) {
                    beforeEnterEvent.forwardToUrl(loginUrl);
                } else {
                    beforeEnterEvent.rerouteToError(NotFoundException.class);
                }
            }

Apparently in your case loginView and loginUrl is null (since Keycloak is expected to do the security handling), and thus rerouteToError(NotFoundException.class) is called. So, the route in fact exists.

The solution would be to initialize Keycloak before calling MockVaadin.setup(), so that Keycloak returns a non-null Principal in ViewAccessChecker.beforeEnter(). Could you please try out this solution and let me know whether it works?

One way could be to bypass Keycloak completely and use a fake/mock implementation of ViewAccessChecker which simply always provides a dummy non-null Principal. Maybe this could help: https://github.com/mvysny/vaadin-simple-security/blob/master/src/main/java/com/github/mvysny/vaadinsimplesecurity/SimpleViewAccessChecker.java .

mvysny avatar Apr 03 '23 15:04 mvysny