Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* limitations under the License.
*
* Portions copyright 2013-2015 ForgeRock AS.
* Portions Copyrighted 2024-2025 3A Systems LLC.
* Portions Copyrighted 2024-2026 3A Systems LLC.
*/
package org.forgerock.openidm.ui.internal.service;

Expand All @@ -25,6 +25,7 @@
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Map;
Expand All @@ -38,6 +39,7 @@
import org.forgerock.openidm.config.enhanced.EnhancedConfig;
import org.forgerock.openidm.core.IdentityServer;
import org.forgerock.openidm.core.PropertyUtil;
import org.forgerock.openidm.core.ServerConstants;
import org.forgerock.openidm.servletregistration.ServletRegistration;
import org.ops4j.pax.web.service.WebContainer;
import org.osgi.service.component.ComponentContext;
Expand Down Expand Up @@ -129,6 +131,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res)
}

// Never cache index.html to ensure we always have the current product version for asset requests
// (Cache-Control is also set in handleIndexHtml for the index.html special case)
if (target.equals("/index.html")) {
res.setHeader("Cache-Control", "no-cache");
}
Expand Down Expand Up @@ -157,12 +160,53 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res)

if (url == null) {
res.sendError(HttpServletResponse.SC_NOT_FOUND);
} else if (target.equals("/index.html")) {
handleIndexHtml(res, url);
} else {
handle(req, res, url, target);
}
}
}

/**
* Serves index.html with the openidm.context.path injected as a global JS variable.
* Replaces {@code </head>} with a small inline script that sets
* {@code window.__openidm_context_path} before RequireJS boots.
*/
private void handleIndexHtml(HttpServletResponse res, URL url) throws IOException {
res.setContentType("text/html");
res.setHeader("Cache-Control", "no-cache");

String contextPath = System.getProperty(
ServerConstants.OPENIDM_CONTEXT_PATH_PROPERTY,
ServerConstants.OPENIDM_CONTEXT_PATH_DEFAULT);
// Strip leading slash — the UI Constants.context value does not use it
String contextValue = contextPath.startsWith("/") ? contextPath.substring(1) : contextPath;

byte[] raw;
try (InputStream is = url.openStream()) {
raw = is.readAllBytes();
}
String html = new String(raw, StandardCharsets.UTF_8);

// Inject a tiny script right before </head> so it is available before RequireJS loads.
// Escape characters that could break out of the JS string or the script tag.
String safeContextValue = contextValue
.replace("&", "&amp;")
.replace("<", "\\u003c")
.replace(">", "\\u003e")
.replace("\"", "\\\"")
.replace("'", "\\'");
String injection = "<script>window.__openidm_context_path=\""
+ safeContextValue + "\";</script>\n</head>";
// replaceFirst to guard against malformed HTML with multiple </head> tags
html = html.replaceFirst("</head>", injection);

byte[] out = html.getBytes(StandardCharsets.UTF_8);
res.setContentLength(out.length);
res.getOutputStream().write(out);
}

/**
* Initializes the servlet and registers it with the WebContainer.
*
Expand Down
3 changes: 2 additions & 1 deletion openidm-ui/openidm-ui-api/src/main/resources/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
url = decodeURIComponent(url[1]);
} else {
// default Swagger JSON URL
url = "/openidm/?_api";
var ctx = (window.__openidm_context_path || "openidm");
url = "/" + ctx + "/?_api";
}

if (window.SwaggerTranslator) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2016 ForgeRock AS.
* Portions Copyright 2026 3A Systems, LLC.
*/

define([
"org/forgerock/commons/ui/common/util/Constants"
], function (commonConstants) {
commonConstants.context = "openidm";
commonConstants.context = (typeof window !== "undefined" && window.__openidm_context_path)
? window.__openidm_context_path
: "openidm";

commonConstants.HEADER_PARAM_PASSWORD = "X-OpenIDM-Password";
commonConstants.HEADER_PARAM_USERNAME = "X-OpenIDM-Username";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,28 @@ define([
"Custom context should produce the correct REST API path prefix");
Constants.context = originalContext;
});
});

QUnit.test("window.__openidm_context_path overrides the default context", function (assert) {
var originalValue = window.__openidm_context_path;
window.__openidm_context_path = "myidm";
var contextValue = (typeof window !== "undefined" && window.__openidm_context_path)
? window.__openidm_context_path
: "openidm";
assert.equal(contextValue, "myidm",
"window.__openidm_context_path should override the default 'openidm' context");
window.__openidm_context_path = originalValue;
});

QUnit.test("window.__openidm_context_path falls back to 'openidm' when unset", function (assert) {
var originalValue = window.__openidm_context_path;
delete window.__openidm_context_path;
var contextValue = (typeof window !== "undefined" && window.__openidm_context_path)
? window.__openidm_context_path
: "openidm";
assert.equal(contextValue, "openidm",
"Should fall back to 'openidm' when window.__openidm_context_path is not set");
if (originalValue !== undefined) {
window.__openidm_context_path = originalValue;
}
});
});