Skip to content
Open
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 @@ -12,7 +12,7 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2013-2016 ForgeRock AS.
* Portions Copyrighted 2024-2025 3A Systems LLC.
* Portions Copyrighted 2024-2026 3A Systems LLC.
*/
package org.forgerock.openidm.servlet.internal;

Expand Down Expand Up @@ -74,7 +74,7 @@
/**
* A component to create and register the "API" Servlet; that is, the CHF Servlet that
*
* 1) listens on /openidm,
* 1) listens on /openidm (or the path configured via openidm.context.path system property),
* 2) dispatches to the HttpApplication, that is composed of
* a) the auth filter
* b) the JSON resource HTTP Handler, that
Expand All @@ -93,7 +93,11 @@ public class ServletComponent implements EventHandler {

static final String PID = "org.forgerock.openidm.api-servlet";

private static final String SERVLET_ALIAS = "/openidm";
/** System property name for the configurable REST context path. */
static final String OPENIDM_CONTEXT_PATH_PROPERTY = "openidm.context.path";

/** Default REST context path. */
static final String OPENIDM_CONTEXT_PATH_DEFAULT = "/openidm";
Comment on lines +97 to +100
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The openidm.context.path property name/default and the path-normalization logic are now duplicated in multiple modules/classes (e.g., this component, ServletRegistrationSingleton, and RemoteCommandScope). To avoid drift (e.g., different trimming/validation rules over time), consider centralizing the constant + normalization in a shared location (such as a core utility or ServerConstants/IdentityServer) and reusing it from all call sites.

Suggested change
static final String OPENIDM_CONTEXT_PATH_PROPERTY = "openidm.context.path";
/** Default REST context path. */
static final String OPENIDM_CONTEXT_PATH_DEFAULT = "/openidm";
static final String OPENIDM_CONTEXT_PATH_PROPERTY = ServerConstants.OPENIDM_CONTEXT_PATH_PROPERTY;
/** Default REST context path. */
static final String OPENIDM_CONTEXT_PATH_DEFAULT = ServerConstants.OPENIDM_CONTEXT_PATH_DEFAULT;

Copilot uses AI. Check for mistakes.

private static final String API_ID = "frapi:openidm";

Expand Down Expand Up @@ -155,9 +159,25 @@ protected synchronized void unbindRegistrator(ServletFilterRegistrator registrat

private HttpServlet servlet;

/**
* Returns the servlet alias (REST context path) from the system property
* {@code openidm.context.path}, defaulting to {@code /openidm}.
*/
static String getServletAlias() {
String path = System.getProperty(OPENIDM_CONTEXT_PATH_PROPERTY, OPENIDM_CONTEXT_PATH_DEFAULT);
if (!path.startsWith("/")) {
path = "/" + path;
}
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
return path;
}

@Activate
protected void activate(ComponentContext context) throws ServletException, NamespaceException {
logger.debug("Registering servlet at {}", SERVLET_ALIAS);
final String servletAlias = getServletAlias();
logger.debug("Registering servlet at {}", servletAlias);

final Handler handler = CrestHttp.newHttpHandler(
new CrestApplication() {
Expand Down Expand Up @@ -201,8 +221,8 @@ public void stop() {

@SuppressWarnings("rawtypes")
final Dictionary params = new Hashtable();
servletRegistration.registerServlet(SERVLET_ALIAS, servlet, params);
logger.info("Registered servlet at {}", SERVLET_ALIAS);
servletRegistration.registerServlet(servletAlias, servlet, params);
logger.info("Registered servlet at {}", servletAlias);
}

@Deactivate
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2025-2026 3A Systems LLC.
*/

package org.forgerock.openidm.servlet.internal;

import static org.assertj.core.api.Assertions.assertThat;

import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;

/**
* Unit tests for {@link ServletComponent} context path configuration.
*/
public class ServletComponentTest {

@AfterMethod
public void clearSystemProperty() {
System.clearProperty(ServletComponent.OPENIDM_CONTEXT_PATH_PROPERTY);
}

@Test
public void testDefaultServletAlias() {
// When no system property is set, should return the default /openidm
System.clearProperty(ServletComponent.OPENIDM_CONTEXT_PATH_PROPERTY);
assertThat(ServletComponent.getServletAlias()).isEqualTo("/openidm");
}

@Test
public void testCustomServletAlias() {
// When system property is set to /myidm, should return /myidm
System.setProperty(ServletComponent.OPENIDM_CONTEXT_PATH_PROPERTY, "/myidm");
assertThat(ServletComponent.getServletAlias()).isEqualTo("/myidm");
}

@Test
public void testServletAliasWithoutLeadingSlash() {
// Should add leading slash if missing
System.setProperty(ServletComponent.OPENIDM_CONTEXT_PATH_PROPERTY, "myidm");
assertThat(ServletComponent.getServletAlias()).isEqualTo("/myidm");
}

@Test
public void testServletAliasWithTrailingSlash() {
// Should remove trailing slash
System.setProperty(ServletComponent.OPENIDM_CONTEXT_PATH_PROPERTY, "/myidm/");
assertThat(ServletComponent.getServletAlias()).isEqualTo("/myidm");
}

@Test
public void testServletAliasConstants() {
assertThat(ServletComponent.OPENIDM_CONTEXT_PATH_PROPERTY).isEqualTo("openidm.context.path");
assertThat(ServletComponent.OPENIDM_CONTEXT_PATH_DEFAULT).isEqualTo("/openidm");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
information: "Portions copyright [year] [name of copyright owner]".

Copyright 2017 ForgeRock AS.
Portions Copyright 2024-2025 3A Systems LLC.
Portions Copyright 2024-2026 3A Systems LLC.
////

:figure-caption!:
Expand Down Expand Up @@ -82,6 +82,11 @@ Note that for LDAP resources, you should not map the LDAP `dn` to the OpenIDM `u
...
----

[NOTE]
====
The `/openidm` context path shown in all URI examples throughout this guide is the default value. You can change it by setting the `openidm.context.path` system property in `conf/system.properties` or as a JVM argument (for example, `-Dopenidm.context.path=/myidm`). For more information, see xref:chap-configuration.adoc#configuring-rest-context-path["Configuring the REST Context Path"] in the __Integrator's Guide__.
====


[#rest-object-identifier]
=== Object Identifiers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
information: "Portions copyright [year] [name of copyright owner]".

Copyright 2017 ForgeRock AS.
Portions Copyright 2024-2025 3A Systems LLC.
Portions Copyright 2024-2026 3A Systems LLC.
////

:figure-caption!:
Expand Down Expand Up @@ -152,6 +152,35 @@ felix.fileinstall.enableConfigSave=false
----


[#configuring-rest-context-path]
==== Configuring the REST Context Path

By default, the OpenIDM REST API is available under the `/openidm` context path (for example, `\https://localhost:8443/openidm/`). You can change this base path by setting the `openidm.context.path` system property.

To set a custom REST context path, edit the `conf/system.properties` file and uncomment or add the following line, replacing `/openidm` with your preferred path:

[source]
----
openidm.context.path=/openidm
----

Alternatively, you can pass the property as a JVM argument when starting OpenIDM:

[source, console]
----
$ OPENIDM_OPTS="-Dopenidm.context.path=/myidm" ./startup.sh
----

The path must begin with a `/` and must not end with `/`. If the value provided does not start with a `/`, one is added automatically.

After changing this property, the REST API will be accessible under the new path, for example `\https://localhost:8443/myidm/config`, and the Admin UI and Self-Service UI will automatically use the configured path for all API calls.

[NOTE]
====
Changing the context path affects all REST API endpoints, the Admin UI, and the Self-Service UI. Ensure that any external integrations, load balancer rules, or documentation referring to the `/openidm` path are updated accordingly.
Comment on lines +176 to +180
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The statement that the Admin UI and Self-Service UI will “automatically use the configured path for all API calls” appears inaccurate with the current UI implementation: openidm-ui-common/.../util/Constants.js still hardcodes commonConstants.context = "openidm", so changing -Dopenidm.context.path on the JVM won’t update the UI’s REST base path unless additional wiring is added (for example, injecting the context into the UI at runtime or deriving it from window.location). Please either implement that wiring or adjust this documentation to describe the actual behavior/requirements for the UI.

Suggested change
After changing this property, the REST API will be accessible under the new path, for example `\https://localhost:8443/myidm/config`, and the Admin UI and Self-Service UI will automatically use the configured path for all API calls.
[NOTE]
====
Changing the context path affects all REST API endpoints, the Admin UI, and the Self-Service UI. Ensure that any external integrations, load balancer rules, or documentation referring to the `/openidm` path are updated accordingly.
After changing this property, the REST API will be accessible under the new path, for example `\https://localhost:8443/myidm/config`. The Admin UI and Self-Service UI, however, are implemented with a default context path of `/openidm` for their REST calls. To use a custom context path with the UIs, you must either deploy them behind a reverse proxy that maps the public path (for example `/myidm`) to `/openidm` on the OpenIDM server, or customize and rebuild the UI so that it derives the REST base path from the runtime context (for example, `window.location`) or from injected configuration that matches `openidm.context.path`.
[NOTE]
====
Changing the context path affects all REST API endpoints. If you expose the Admin UI or Self-Service UI under a custom path, ensure that any external integrations, load balancer rules, or documentation referring to the `/openidm` path are updated accordingly.

Copilot uses AI. Check for mistakes.
====


[#configuring-proxy]
==== Communicating Through a Proxy Server

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Portions Copyrighted 2024-2025 3A Systems LLC.
* Portions Copyrighted 2024-2026 3A Systems LLC.
*/

package org.forgerock.openidm.servletregistration.impl;
Expand Down Expand Up @@ -90,7 +90,26 @@ public class ServletRegistrationSingleton implements ServletRegistration {

private static final String[] DEFAULT_SERVLET_NAME = new String[] { "OpenIDM REST" };

private static final String[] DEFAULT_SERVLET_URL_PATTERNS = new String[] { "/openidm/*", "/selfservice/*" };
/** System property name for the configurable REST context path. */
private static final String OPENIDM_CONTEXT_PATH_PROPERTY = "openidm.context.path";

/** Default REST context path. */
private static final String OPENIDM_CONTEXT_PATH_DEFAULT = "/openidm";

/**
* Returns the default servlet URL patterns, using the configured context path
* from the {@code openidm.context.path} system property (default: {@code /openidm}).
*/
private static String[] getDefaultServletUrlPatterns() {
String contextPath = System.getProperty(OPENIDM_CONTEXT_PATH_PROPERTY, OPENIDM_CONTEXT_PATH_DEFAULT);
if (!contextPath.startsWith("/")) {
contextPath = "/" + contextPath;
}
if (contextPath.endsWith("/")) {
contextPath = contextPath.substring(0, contextPath.length() - 1);
}
return new String[] { contextPath + "/*", "/selfservice/*" };
}

// Context of this scr component
private BundleContext bundleContext;
Expand Down Expand Up @@ -212,7 +231,7 @@ public URL apply(JsonValue jsonValue) throws JsonValueException {

// URL patterns to apply the filter to, e.g. one could also add "/openidmui/*");
List<String> urlPatterns = config.get(SERVLET_FILTER_URL_PATTERNS)
.defaultTo(Arrays.asList(DEFAULT_SERVLET_URL_PATTERNS))
.defaultTo(Arrays.asList(getDefaultServletUrlPatterns()))
.asList(String.class);

// Filter init params, a string to string map
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2011-2016 ForgeRock AS.
* Portions copyright 2026 3A Systems LLC.
*/
package org.forgerock.openidm.shell.impl;

Expand Down Expand Up @@ -57,10 +58,33 @@ public class RemoteCommandScope extends CustomCommandScope {
private static final String IDM_PORT_DESC = "Port of OpenIDM REST service. This will override any port in --url.";
private static final String IDM_PORT_METAVAR = "PORT";

/** System property for configuring the REST context path. */
private static final String OPENIDM_CONTEXT_PATH_PROPERTY = "openidm.context.path";

/** Compile-time default URL used in CLI parameter annotations. */
private static final String IDM_URL_DEFAULT = "http://localhost:8080/openidm/";

private static final String IDM_URL_DESC = "URL of OpenIDM REST service. Default " + IDM_URL_DEFAULT;
private static final String IDM_URL_METAVAR = "URL";

/**
* Returns the effective default IDM URL, reading the {@code openidm.context.path} system
* property (default: {@code /openidm}) to construct the URL.
*/
private static String getEffectiveIdmUrl(String url) {
if (!IDM_URL_DEFAULT.equals(url)) {
return url;
}
String contextPath = System.getProperty(OPENIDM_CONTEXT_PATH_PROPERTY, "/openidm");
if (!contextPath.startsWith("/")) {
contextPath = "/" + contextPath;
}
if (contextPath.endsWith("/")) {
contextPath = contextPath.substring(0, contextPath.length() - 1);
}
return "http://localhost:8080" + contextPath + "/";
}

private static final String USER_PASS_DESC = "Server user and password";
private static final String USER_PASS_METAVAR = "USER[:PASSWORD]";
private static final String USER_PASS_DEFAULT = "";
Expand Down Expand Up @@ -144,7 +168,7 @@ private static String getPassword(final String userPass) {
*/
private static String getUrl(final String url) {
if (isNotBlank(url)) {
return url.endsWith("/") ? url : url + "/";
return getEffectiveIdmUrl(url.endsWith("/") ? url : url + "/");
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getUrl() now always runs the value through getEffectiveIdmUrl(...), which will replace the URL with one derived from openidm.context.path whenever the normalized URL equals IDM_URL_DEFAULT. This means a user explicitly passing --url http://localhost:8080/openidm/ (or .../openidm which gets normalized) can be unexpectedly overridden by the system property. --url should take precedence over the system property; consider only applying getEffectiveIdmUrl when the --url option was not explicitly provided (e.g., by using a sentinel absentValue and treating blank as ‘use default’).

Suggested change
return getEffectiveIdmUrl(url.endsWith("/") ? url : url + "/");
return url.endsWith("/") ? url : url + "/";

Copilot uses AI. Check for mistakes.
}
throw new IllegalArgumentException("URL required");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2015-2016 ForgeRock AS.
* Portions copyright 2026 3A Systems LLC.
*/

define([
"jquery",
"underscore"
], function ($, _) {
"underscore",
"org/forgerock/commons/ui/common/util/Constants"
], function ($, _, constants) {
var obj = {
"changed": {
"name": "Changed field",
Expand All @@ -39,8 +41,8 @@ define([
return;
}

if (v === "/openidm" || v === "/admin" || v === "/system") {
callback(["The URL cannot be one of the following reserved names: \"openidm\", \"admin\" or \"system\"."]);
if (v === "/" + constants.context || v === "/admin" || v === "/system") {
callback(["The URL cannot be one of the following reserved names: \"" + constants.context + "\", \"admin\" or \"system\"."]);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2015-2016 ForgeRock AS.
* Portions copyright 2026 3A Systems LLC.
*/

define([
Expand All @@ -20,7 +21,7 @@ define([
"org/forgerock/commons/ui/common/main/AbstractDelegate"
], function($, constants, AbstractDelegate) {

var obj = new AbstractDelegate(constants.host + "/openidm/audit/");
var obj = new AbstractDelegate(constants.host + "/" + constants.context + "/audit/");

obj.availableHandlers = function() {
return obj.serviceCall({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2015-2016 ForgeRock AS.
* Portions copyright 2026 3A Systems LLC.
*/

define([
Expand All @@ -20,7 +21,7 @@ define([
"org/forgerock/commons/ui/common/main/AbstractDelegate"
], function($, constants, AbstractDelegate) {

var obj = new AbstractDelegate(constants.host + "/openidm");
var obj = new AbstractDelegate(constants.host + "/" + constants.context);

obj.getNodes = function() {
return obj.serviceCall({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2014-2016 ForgeRock AS.
* Portions copyright 2026 3A Systems LLC.
*/

define([
Expand All @@ -22,7 +23,7 @@ define([
"org/forgerock/commons/ui/common/main/EventManager"
], function($, _, constants, AbstractDelegate, eventManager) {

var obj = new AbstractDelegate(constants.host + "/openidm/system");
var obj = new AbstractDelegate(constants.host + "/" + constants.context + "/system");

obj.connectorDelegateCache = {};

Expand Down
Loading
Loading