From 526a114cafec20f5d00b154eaa60ae94217d605a Mon Sep 17 00:00:00 2001 From: Martin Vysny Date: Thu, 1 Aug 2024 10:27:40 +0300 Subject: [PATCH] #161: bypass ErrorStateHandler$ExceptionsTrace and throw an informative AccessDeniedException instead. Fixes #161 --- .../mvysny/kaributesting/v10/NavigatorTest.kt | 13 +++++++ .../mvysny/kaributesting/v10/RoutesTest.kt | 2 +- .../github/mvysny/kaributesting/v10/Routes.kt | 34 +++++++++++++++++++ .../github/mvysny/kaributesting/v10/Utils.kt | 3 ++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/karibu-testing-v10/kt10-tests/src/main/kotlin/com/github/mvysny/kaributesting/v10/NavigatorTest.kt b/karibu-testing-v10/kt10-tests/src/main/kotlin/com/github/mvysny/kaributesting/v10/NavigatorTest.kt index b825de78..241fbb7e 100644 --- a/karibu-testing-v10/kt10-tests/src/main/kotlin/com/github/mvysny/kaributesting/v10/NavigatorTest.kt +++ b/karibu-testing-v10/kt10-tests/src/main/kotlin/com/github/mvysny/kaributesting/v10/NavigatorTest.kt @@ -13,10 +13,12 @@ import com.vaadin.flow.component.button.Button import com.vaadin.flow.component.dialog.Dialog import com.vaadin.flow.component.notification.Notification import com.vaadin.flow.component.orderedlayout.VerticalLayout +import com.vaadin.flow.router.AccessDeniedException import com.vaadin.flow.router.BeforeLeaveEvent import com.vaadin.flow.router.BeforeLeaveObserver import com.vaadin.flow.router.NotFoundException import com.vaadin.flow.router.Route +import com.vaadin.flow.router.RouteNotFoundError import com.vaadin.flow.server.VaadinRequest import com.vaadin.flow.server.auth.NavigationAccessControl import java.io.Serializable @@ -234,11 +236,22 @@ internal fun DynaNodeGroup.navigatorTest() { } group("user logged in") { test("when access is rejected, default handler redirects to MockRouteNotFoundError") { + MockVaadin.tearDown() + val routes = Routes().autoDiscoverViews("com.github") + routes.errorRoutes.remove(MockRouteAccessDeniedError::class.java) + MockVaadin.setup(routes) + UI.getCurrent().addBeforeEnterListener(SimpleNavigationAccessControl("admin")) expectThrows("No route found for 'testing': Consider adding one of the following annotations to make the view accessible: @AnonymousAllowed, @PermitAll, @RolesAllowed.") { navigateTo() } } + test("when access is rejected, Karibu's MockRouteAccessDeniedError throws AccessDeniedException") { + UI.getCurrent().addBeforeEnterListener(SimpleNavigationAccessControl("admin")) + expectThrows("Consider adding one of the following annotations to make the view accessible: @AnonymousAllowed, @PermitAll, @RolesAllowed") { + navigateTo() + } + } } } } diff --git a/karibu-testing-v10/kt10-tests/src/main/kotlin/com/github/mvysny/kaributesting/v10/RoutesTest.kt b/karibu-testing-v10/kt10-tests/src/main/kotlin/com/github/mvysny/kaributesting/v10/RoutesTest.kt index 365cf80a..cc2a5d35 100644 --- a/karibu-testing-v10/kt10-tests/src/main/kotlin/com/github/mvysny/kaributesting/v10/RoutesTest.kt +++ b/karibu-testing-v10/kt10-tests/src/main/kotlin/com/github/mvysny/kaributesting/v10/RoutesTest.kt @@ -28,7 +28,7 @@ val allViews: Set> = setOf>( NavigationPostponeView::class.java, PreserveOnRefreshView::class.java ) -val allErrorRoutes: Set>> = setOf(ErrorView::class.java, MockRouteNotFoundError::class.java) +val allErrorRoutes: Set>> = setOf(ErrorView::class.java, MockRouteNotFoundError::class.java, MockRouteAccessDeniedError::class.java) @DynaTestDsl fun DynaNodeGroup.routesTestBatch() { diff --git a/karibu-testing-v10/src/main/kotlin/com/github/mvysny/kaributesting/v10/Routes.kt b/karibu-testing-v10/src/main/kotlin/com/github/mvysny/kaributesting/v10/Routes.kt index 2b1b95d0..a69daa59 100644 --- a/karibu-testing-v10/src/main/kotlin/com/github/mvysny/kaributesting/v10/Routes.kt +++ b/karibu-testing-v10/src/main/kotlin/com/github/mvysny/kaributesting/v10/Routes.kt @@ -11,6 +11,7 @@ import com.vaadin.flow.server.startup.RouteRegistryInitializer import io.github.classgraph.ClassGraph import io.github.classgraph.ClassInfo import io.github.classgraph.ScanResult +import org.slf4j.LoggerFactory import java.io.Serializable import java.lang.reflect.Field import java.util.concurrent.atomic.AtomicReference @@ -87,6 +88,9 @@ public data class Routes( if (errorRoutes.any { it != MockRouteNotFoundError::class.java && it.isRouteNotFound }) { errorRoutes.remove(MockRouteNotFoundError::class.java) } + if (errorRoutes.any { it != MockRouteAccessDeniedError::class.java && it.isAccessDenied }) { + errorRoutes.remove(MockRouteAccessDeniedError::class.java) + } println("Auto-discovered views: $this") } @@ -129,6 +133,36 @@ public open class MockRouteNotFoundError: Component(), HasErrorParameter { + override fun setErrorParameter(event: BeforeEnterEvent, parameter: ErrorParameter): Int { + // don't re-throw caughtException - the stacktrace won't point here. + // try our best to preserve the stacktrace, but bail out for custom exceptions + if (parameter.exception.javaClass == AccessDeniedException::class.java || parameter.exception.javaClass == MockAccessDeniedException::class.java) { + throw MockAccessDeniedException(parameter) + } + log.error("!!!! Karibu-Testing: MockRouteAccessDeniedError caught an exception ${parameter.caughtException}: ${parameter.customMessage}") + throw parameter.caughtException!! + } + public companion object { + @JvmStatic + private val log = LoggerFactory.getLogger(MockRouteAccessDeniedError::class.java) + } +} + +public class MockAccessDeniedException(override val message: String, cause: Throwable?) : AccessDeniedException() { + public constructor(param: ErrorParameter) : this(param.customMessage, param.caughtException) + init { + initCause(cause) + } +} + internal fun RouteData.toPrettyString(): String { val template = template val path: String = if (template.isNullOrBlank()) "" else "/$template" diff --git a/karibu-testing-v10/src/main/kotlin/com/github/mvysny/kaributesting/v10/Utils.kt b/karibu-testing-v10/src/main/kotlin/com/github/mvysny/kaributesting/v10/Utils.kt index 7a0db481..a6026b50 100644 --- a/karibu-testing-v10/src/main/kotlin/com/github/mvysny/kaributesting/v10/Utils.kt +++ b/karibu-testing-v10/src/main/kotlin/com/github/mvysny/kaributesting/v10/Utils.kt @@ -5,6 +5,7 @@ import com.github.mvysny.fakeservlet.FakeRequest import com.github.mvysny.fakeservlet.FakeResponse import com.vaadin.flow.component.UI import com.vaadin.flow.internal.ReflectTools +import com.vaadin.flow.router.AccessDeniedException import com.vaadin.flow.router.HasErrorParameter import com.vaadin.flow.router.NotFoundException import com.vaadin.flow.server.* @@ -113,6 +114,8 @@ internal fun Class<*>.getErrorParameterType(): Class<*>? = internal val Class<*>.isRouteNotFound: Boolean get() = getErrorParameterType() == NotFoundException::class.java +internal val Class<*>.isAccessDenied: Boolean + get() = getErrorParameterType() == AccessDeniedException::class.java public val currentRequest: VaadinRequest get() = VaadinService.getCurrentRequest()