From d8ee3e0b1dabd3139b0239fbe84b73173bb2819e Mon Sep 17 00:00:00 2001 From: Joachim Ansorg Date: Mon, 29 Jan 2024 12:51:03 +0100 Subject: [PATCH] fix: add auth token query parameter to HTTP requests of webview assets --- .../java/appland/webviews/WebviewEditor.java | 4 +- .../webviews/webserver/AppMapWebview.java | 6 +- .../WebviewAuthTokenRequestHandler.java | 58 +++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 plugin-core/src/main/java/appland/webviews/webserver/WebviewAuthTokenRequestHandler.java diff --git a/plugin-core/src/main/java/appland/webviews/WebviewEditor.java b/plugin-core/src/main/java/appland/webviews/WebviewEditor.java index d4d326fd..aab88772 100644 --- a/plugin-core/src/main/java/appland/webviews/WebviewEditor.java +++ b/plugin-core/src/main/java/appland/webviews/WebviewEditor.java @@ -3,10 +3,10 @@ import appland.AppMapBundle; import appland.utils.GsonUtils; import appland.webviews.webserver.AppMapWebview; +import appland.webviews.webserver.WebviewAuthTokenRequestHandler; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.intellij.ide.BrowserUtil; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileEditor; @@ -178,6 +178,8 @@ protected boolean isWebViewReady() { private void setupJCEF() { // open links to https://appmap.io in the external browser contentPanel.getJBCefClient().addRequestHandler(new OpenExternalLinksHandler(), contentPanel.getCefBrowser()); + // add auth tokens to our localhost requests + contentPanel.getJBCefClient().addRequestHandler(new WebviewAuthTokenRequestHandler(), contentPanel.getCefBrowser()); contentPanel.setErrorPage(new DefaultWebviewErrorPage(navigating)); contentPanel.getJBCefClient().addDisplayHandler(new ConsoleInitMessageHandler(this::initWebviewApplication), contentPanel.getCefBrowser()); diff --git a/plugin-core/src/main/java/appland/webviews/webserver/AppMapWebview.java b/plugin-core/src/main/java/appland/webviews/webserver/AppMapWebview.java index d60c734a..c562e4db 100644 --- a/plugin-core/src/main/java/appland/webviews/webserver/AppMapWebview.java +++ b/plugin-core/src/main/java/appland/webviews/webserver/AppMapWebview.java @@ -26,6 +26,10 @@ public enum AppMapWebview { return "http://localhost:" + BuiltInServerOptions.getInstance().getEffectiveBuiltInServerPort(); } + public static @NotNull String getBaseUrlWithPath() { + return getBaseUrl() + APPMAP_SERVER_BASE_PATH; + } + private final @NotNull String webviewAssetsDirectoryName; /** @@ -39,6 +43,6 @@ public enum AppMapWebview { * @return HTTP URL of the IDE's built-in webserver for this webview's index.html file. */ public @NotNull String getIndexHtmlUrl() { - return getBaseUrl() + APPMAP_SERVER_BASE_PATH + "/" + webviewAssetsDirectoryName + "/index.html"; + return getBaseUrlWithPath() + "/" + webviewAssetsDirectoryName + "/index.html"; } } diff --git a/plugin-core/src/main/java/appland/webviews/webserver/WebviewAuthTokenRequestHandler.java b/plugin-core/src/main/java/appland/webviews/webserver/WebviewAuthTokenRequestHandler.java new file mode 100644 index 00000000..9e29c36f --- /dev/null +++ b/plugin-core/src/main/java/appland/webviews/webserver/WebviewAuthTokenRequestHandler.java @@ -0,0 +1,58 @@ +package appland.webviews.webserver; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.Urls; +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.handler.CefRequestHandlerAdapter; +import org.cef.handler.CefResourceRequestHandler; +import org.cef.handler.CefResourceRequestHandlerAdapter; +import org.cef.misc.BoolRef; +import org.cef.network.CefRequest; +import org.jetbrains.builtInWebServer.BuiltInWebServerKt; +import org.jetbrains.ide.BuiltInServerManager; + +/** + * Adds auth tokens to requests to webview resources to bypass any filtering of the built-in webserver. + * Without auth tokens the IDE's built-in webserver only accepts URLs prefixed with a project name. + */ +public final class WebviewAuthTokenRequestHandler extends CefRequestHandlerAdapter { + @Override + public CefResourceRequestHandler getResourceRequestHandler(CefBrowser browser, + CefFrame frame, + CefRequest request, + boolean isNavigation, + boolean isDownload, + String requestInitiator, + BoolRef disableDefaultHandling) { + + if (isUnsignedWebViewRequest(request)) { + return new CefResourceRequestHandlerAdapter() { + @Override + public boolean onBeforeResourceLoad(CefBrowser browser, CefFrame frame, CefRequest request) { + var url = Urls.parseEncoded(request.getURL()); + if (url != null) { + request.setURL(BuiltInServerManager.getInstance().addAuthToken(url).toExternalForm()); + } + + // false to continue with the request + return false; + } + }; + } + + // fallback to default handling of JCEF + return null; + } + + /** + * @return true if the request is for a webview asset, but not yet signed with an auth token + */ + private static boolean isUnsignedWebViewRequest(CefRequest request) { + var url = Urls.parseEncoded(request.getURL()); + var params = StringUtil.defaultIfEmpty(url != null ? url.getParameters() : null, ""); + return request.getURL().startsWith(AppMapWebview.getBaseUrlWithPath()) + && !params.contains("?" + BuiltInWebServerKt.TOKEN_PARAM_NAME + "=") + && !params.contains("&" + BuiltInWebServerKt.TOKEN_PARAM_NAME + "="); + } +}