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 = ServerConstants.OPENIDM_CONTEXT_PATH_PROPERTY;

/** Default REST context path. */
static final String OPENIDM_CONTEXT_PATH_DEFAULT = ServerConstants.OPENIDM_CONTEXT_PATH_DEFAULT;

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`. 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 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.
====


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

Expand Down
7 changes: 6 additions & 1 deletion openidm-servlet-registrator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
Expand All @@ -37,6 +37,11 @@
<description>This bundle is duplicates of OpenIDM HTTP context bundle</description>

<dependencies>
<dependency>
<groupId>org.openidentityplatform.openidm</groupId>
<artifactId>openidm-system</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openidentityplatform.openidm</groupId>
<artifactId>openidm-enhanced-config</artifactId>
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 @@ -55,6 +55,7 @@
import org.forgerock.openidm.servletregistration.RegisteredFilter;
import org.forgerock.openidm.servletregistration.ServletRegistration;
import org.forgerock.openidm.servletregistration.ServletFilterRegistrator;
import org.forgerock.openidm.core.ServerConstants;
import org.forgerock.util.Function;
import org.ops4j.pax.web.service.WebContainer;
import org.osgi.framework.BundleContext;
Expand Down Expand Up @@ -90,7 +91,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 = ServerConstants.OPENIDM_CONTEXT_PATH_PROPERTY;

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

/**
* 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 +232,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 @@ -43,6 +44,7 @@
import org.forgerock.json.resource.UpdateRequest;
import org.forgerock.openidm.config.persistence.ConfigBootstrapHelper;
import org.forgerock.openidm.core.IdentityServer;
import org.forgerock.openidm.core.ServerConstants;
import org.forgerock.openidm.shell.CustomCommandScope;
import org.forgerock.openidm.shell.felixgogo.MetaVar;
import org.forgerock.services.context.RootContext;
Expand All @@ -57,10 +59,29 @@ 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";

/** 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.
* This is only called when no {@code --url} argument was explicitly provided.
*/
private static String getEffectiveIdmUrl() {
String contextPath = System.getProperty(ServerConstants.OPENIDM_CONTEXT_PATH_PROPERTY,
ServerConstants.OPENIDM_CONTEXT_PATH_DEFAULT);
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 @@ -139,14 +160,17 @@ private static String getPassword(final String userPass) {
/**
* Gets OpenIDM URL configuration parameter.
*
* @param url OpenIDM URL
* @param url OpenIDM URL explicitly provided by the user, or blank if not provided
* @return URL
*/
private static String getUrl(final String url) {
if (isNotBlank(url)) {
// User explicitly provided --url; use it as-is (just normalize trailing slash).
// The openidm.context.path system property does NOT override an explicit --url.
return url.endsWith("/") ? url : url + "/";
}
throw new IllegalArgumentException("URL required");
// --url was not provided; derive the default URL from the openidm.context.path system property
return getEffectiveIdmUrl();
}

/**
Expand Down Expand Up @@ -185,7 +209,7 @@ public void update(CommandSession session,

@Descriptor(IDM_URL_DESC)
@MetaVar(IDM_URL_METAVAR)
@Parameter(names = {"--url"}, absentValue = IDM_URL_DEFAULT)
@Parameter(names = {"--url"}, absentValue = "")
final String idmUrl,

@Descriptor(IDM_PORT_DESC)
Expand Down Expand Up @@ -271,7 +295,7 @@ public void configimport(

@Descriptor(IDM_URL_DESC)
@MetaVar(IDM_URL_METAVAR)
@Parameter(names = { "--url" }, absentValue = IDM_URL_DEFAULT)
@Parameter(names = { "--url" }, absentValue = "")
final String idmUrl,

@Descriptor(IDM_PORT_DESC)
Expand Down Expand Up @@ -314,7 +338,7 @@ public void configimport(

@Descriptor(IDM_URL_DESC)
@MetaVar(IDM_URL_METAVAR)
@Parameter(names = { "--url" }, absentValue = IDM_URL_DEFAULT)
@Parameter(names = { "--url" }, absentValue = "")
final String idmUrl,

@Descriptor(IDM_PORT_DESC)
Expand Down Expand Up @@ -488,7 +512,7 @@ public void configexport(

@Descriptor(IDM_URL_DESC)
@MetaVar(IDM_URL_METAVAR)
@Parameter(names = { "--url" }, absentValue = IDM_URL_DEFAULT)
@Parameter(names = { "--url" }, absentValue = "")
final String idmUrl,

@Descriptor(IDM_PORT_DESC)
Expand Down Expand Up @@ -519,7 +543,7 @@ public void configexport(

@Descriptor(IDM_URL_DESC)
@MetaVar(IDM_URL_METAVAR)
@Parameter(names = { "--url" }, absentValue = IDM_URL_DEFAULT)
@Parameter(names = { "--url" }, absentValue = "")
final String idmUrl,

@Descriptor(IDM_PORT_DESC)
Expand Down Expand Up @@ -592,7 +616,7 @@ public void configureconnector(

@Descriptor(IDM_URL_DESC)
@MetaVar(IDM_URL_METAVAR)
@Parameter(names = { "--url" }, absentValue = IDM_URL_DEFAULT)
@Parameter(names = { "--url" }, absentValue = "")
final String idmUrl,

@Descriptor(IDM_PORT_DESC)
Expand Down
Loading
Loading