EmbeddedWebHandler.java
/*
** Module : EmbeddedWebHandler.java
** Abstract : Define handlers to run the embedded web app from within FWD.
**
** Copyright (c) 2018-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------Description----------------------------------
** 001 CA 20180724 Created initial version
** 002 CA 20180815 adm_windows.json will be served from the embedded/adm_windows.json path.
** The DefaultHandler must not be registered here, otherwise the /gui path will
** not be found.
** 003 IAS 20210423 Make EmbeddedWebAppHandler.AuthWorker thread daemon.
** CA 20210816 Do not set the request.handled to false (and do not handle the request if already handled).
** 004 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 005 GBB 20230825 Complete rework of the class. Renamed from EmbeddedWebAppHandler to EmbeddedWebHandler.
** Remote spawn implementation removed.
** 006 GBB 20231027 Template placeholder constant for auto-login client url renamed to AUTO_LOGIN_DATA.
** 007 GBB 20240517 Resolving web pages template placeholder PLACEHOLDER_PROXY_PATH.
** 008 GBB 20240530 Configurable /embedded path.
** 009 TJD 20240415 Allow serving JS libraries from webjars
** 010 GBB 20240709 DriverType moved to outer class WebDriverType.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.main;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.security.SecurityManager;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.p2j.web.*;
import javax.servlet.http.*;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.*;
import org.eclipse.jetty.util.resource.*;
import java.lang.*;
import java.util.*;
/**
* Define handlers to run the embedded web app from within fwd. The handlers are run on the
* <code>/embedded</code>, where <code>/embedded/fwd</code> will serve content from the
* <code>com/goldencode/p2j/ui/client/gui/driver/web/res/</code> package and the
* <code>/embedded/custom</code> will server content from the <code>embedded/</code> sub-package
* of the application's package root (as retrieved via {@link SourceNameMapper#getPackageRoot}.
*/
public class EmbeddedWebHandler
extends WebDriverHandler
{
/** Logger. */
private static final CentralLogger LOG = CentralLogger.get(EmbeddedWebHandler.class);
/** The path to for the embedded configs. */
private static final String TARGET_EMBEDDED_CONFIGS = TARGET_EMBEDDED + "/adm_windows.json";
/** The path for the embedded custom resources. */
private static final String TARGET_EMBEDDED_CUSTOM = TARGET_EMBEDDED + "/custom";
/** The path for the embedded base web resources. */
private static final String TARGET_EMBEDDED_RES = TARGET_EMBEDDED + "/fwd";
/** The path for the embedded base web resources. */
private static final String EMBEDDED_RES_PATH = "com/goldencode/p2j/ui/client/gui/driver/web/res/";
/** The filename of the resource for the login page partial. */
private static final String HTML_PARTIAL_HTML_FILENAME = "web_partial_login_embedded.html";
/** Template placeholder for the embedded context path. See {@link #TARGET_EMBEDDED}. */
protected static final String PLACEHOLDER_EMBEDDED_PATH = "${embeddedPath}";
/**
* Template placeholder for the context path before /webres. Can be the proxy path or empty (root).
* Different from {@link #PLACEHOLDER_PROXY_PATH} that can be omitted with
* {@link #REMOVE_PROXY_PATH_IN_WEB_PAGES}.
*/
protected static final String PLACEHOLDER_WEBRES_PATH = "${webresPath}";
/**
* Private constructor for EmbeddedWebHandle. Initializes all the handlers for the embedded web app.
* Reads directory configs from the server context, because handle() runs without security context.
*/
EmbeddedWebHandler()
{
super("client:cmd-line-option:startup-procedure=emain.p", "client:web:embedded=true");
addHandler(new HandlerCollection(
createContextHandler(TARGET_EMBEDDED_RES, EMBEDDED_RES_PATH),
createContextHandler(TARGET_EMBEDDED_CUSTOM, PKG_ROOT + "embedded/"),
// in build.gradle, add a WARNING for each zip artifact which is hard-coded here.
// do not expose the "/" root resource folder - as this will allow access to any class path
// file, which includes jar'ed classes, configurations, etc
// this needs to be inline with build.gradle versions
new DojoToolkitHandler(TARGET_EMBEDDED),
new WebResourceHandler(TARGET_EMBEDDED, "/bootstrap", "META-INF/resources/webjars/bootstrap/3.4.1"),
new WebResourceHandler(TARGET_EMBEDDED, "/d3", "META-INF/resources/webjars/d3/4.13.0/build"),
new WebResourceHandler(TARGET_EMBEDDED, "/fonts", "fonts"),
new WebResourceHandler(TARGET_EMBEDDED, "/jquery", "META-INF/resources/webjars/jquery/3.7.1"),
new WebResourceHandler(TARGET_EMBEDDED, "/jquery-ui", "META-INF/resources/webjars/jquery-ui/1.13.2"),
new WebResourceHandler(TARGET_EMBEDDED, "/material-components-web", "material-components-web"),
new WebResourceHandler(TARGET_EMBEDDED, "/material-design-icons", "material-design-icons"),
new WebResourceHandler(TARGET_EMBEDDED, "/tabulator", "tabulator-master-2.12.0")
));
}
/**
* Creates a new context handler serving class path resources from the given path.
*
* @param context
* The context used to server the resources.
* @param resPath
* The path were the resources reside.
*
* @return A new handler.
*/
private static ContextHandler createContextHandler(String context, String resPath)
{
ResourceHandler res = new ResourceHandler();
res.setDirectoriesListed(false);
ContextHandler ctx = new ContextHandler(context);
ctx.setBaseResource(Resource.newClassPathResource(resPath));
ctx.setHandler(res);
return ctx;
}
/**
* Processes embedded web requests.
*
* @param target
* The target path.
* @param base
* The HTTP request, Jetty style.
* @param request
* The HTTP request.
* @param response
* The HTTP response.
*/
@Override
public void handle(String target, Request base, HttpServletRequest request, HttpServletResponse response)
{
try
{
super.handle(target, base, request, response);
if (base.isHandled())
{
return;
}
if (!target.equals(TARGET_EMBEDDED) &&
!target.equals(TARGET_EMBEDDED_CONFIGS))
{
// do not set 'handled' to false, as it may have to be processed by another handler.
return;
}
base.setHandled(true);
String method = request.getMethod().toUpperCase();
if (target.equals(TARGET_EMBEDDED) && method.equals("POST"))
{
handleStartClientRequest(base, request, response, true, WebDriverType.EMBEDDED);
return;
}
if (!method.equals("GET"))
{
response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
return;
}
String clientUrl = null;
// TODO: enable auto login (and logout page) when cross document msgs can handle it
/*if (target.equals(TARGET_EMBEDDED))
{
SpawnerResult spawnerResult = handleSsoAutoLogin(base, request, true, DriverType.EMBEDDED);
if (spawnerResult != null)
{
// on successful auto login, the new client url will be embedded in the login page
clientUrl = spawnerResult.uri;
}
}*/
handleStatic(target, request, response, clientUrl);
}
catch (Throwable t)
{
LOG.warning("Unexpected exception:", t);
sendError(response, SupportedHttpCode.UNEXPECTED_ERROR, null);
}
}
/**
* Handles HTTP requests for static resources, i.e. index / SSO login page and configs.
*
* @param target
* The target path.
* @param request
* The HTTP request.
* @param response
* The HTTP response.
* @param clientUrl
* The spawned client URL.
*/
private void handleStatic(String target,
HttpServletRequest request,
HttpServletResponse response,
String clientUrl)
{
if (target.equals(TARGET_EMBEDDED_CONFIGS))
{
serveStatic(response,
"application/json",
null,
PKG_ROOT + "embedded/adm_windows.json");
}
else if (target.equals(TARGET_EMBEDDED))
{
String serverProxyPath = getServerProxyPath(resolveForwardedHost(request));
String htmlContent = loadPartial(HTML_PARTIAL_HTML_FILENAME,
PKG_RES_DIR,
new HashMap<String, String>() {{
put(PLACEHOLDER_WEBRES_PATH, serverProxyPath);
put(PLACEHOLDER_PROXY_PATH,
REMOVE_PROXY_PATH_IN_WEB_PAGES ? "" : serverProxyPath);
put(PLACEHOLDER_EMBEDDED_PATH, TARGET_EMBEDDED);
}});
SsoAuthenticator ssoAuthenticator = SecurityManager.getInstance().ssoSm.getSsoAuthenticator();
if (ssoAuthenticator != null)
{
String ssoInflatedTemplate = ssoAuthenticator.getLoginPage(htmlContent);
if (ssoInflatedTemplate != null)
{
htmlContent = ssoInflatedTemplate;
}
}
if (htmlContent == null)
{
LOG.fine("File not found " + HTML_PARTIAL_HTML_FILENAME);
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
Map<String, String> placeholderValuePairs = new HashMap<>();
if (clientUrl != null)
{
placeholderValuePairs.put(AUTO_LOGIN_DATA, clientUrl);
}
placeholderValuePairs.put(PLACEHOLDER_HTML_TEMPLATE, htmlContent);
placeholderValuePairs.put(PLACEHOLDER_TITLE, PAGE_TITLE_LOGIN == null ?
"Login (Embedded Gui Web Client)" :
PAGE_TITLE_LOGIN);
placeholderValuePairs.put(PLACEHOLDER_PROXY_PATH, getServerProxyPath(request));
serveHtml(response, placeholderValuePairs, getResPathCurrentDir("web_landing.html"));
}
else
{
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
}