Skip to content

Commit 059984d

Browse files
Copilotvharseko
andauthored
Make REST context path configurable via openidm.context.path system property (#142)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: vharseko <6818498+vharseko@users.noreply.github.com> Co-authored-by: Valery Kharseko <vharseko@3a-systems.ru>
1 parent 0130728 commit 059984d

File tree

49 files changed

+470
-99
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+470
-99
lines changed

.github/workflows/build.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,25 @@ jobs:
6767
npx playwright test --reporter=list
6868
env:
6969
OPENIDM_URL: http://localhost:8080
70+
- name: Start OpenIDM with custom context path /myidm
71+
if: runner.os == 'Linux'
72+
run: |
73+
openidm/shutdown.sh
74+
timeout 1m bash -c 'while [ -f openidm/.openidm.pid ]; do sleep 2; done' || true
75+
rm -rf openidm/logs/*
76+
OPENIDM_OPTS="-Dlogback.configurationFile=conf/logging-config.groovy -Dopenidm.context.path=/myidm" openidm/startup.sh &
77+
timeout 3m bash -c 'until grep -q "OpenIDM ready" openidm/logs/openidm0.log.0 ; do sleep 5; done' || cat openidm/logs/openidm0.log.0
78+
grep -q "OpenIDM ready" openidm/logs/openidm0.log.0
79+
! grep "ERROR" openidm/logs/openidm0.log.0
80+
! grep "SEVERE" openidm/logs/openidm0.log.0
81+
- name: UI Smoke Tests with /myidm context path (Playwright)
82+
if: runner.os == 'Linux'
83+
run: |
84+
cd e2e
85+
npx playwright test --reporter=list
86+
env:
87+
OPENIDM_URL: http://localhost:8080
88+
OPENIDM_CONTEXT_PATH: /myidm
7089
- name: Test on Windows
7190
if: runner.os == 'Windows'
7291
run: |

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ WORKDIR /opt
2424

2525
#COPY openidm-zip/target/openidm-*.zip ./
2626

27-
RUN apt-get update \
27+
RUN echo 'Acquire::Retries "3";' > /etc/apt/apt.conf.d/80-retries \
28+
&& apt-get update \
2829
&& apt-get install -y --no-install-recommends curl unzip \
2930
&& bash -c 'if [ ! -z "$VERSION" ] ; then rm -rf ./*.zip ; curl -L https://github.com/OpenIdentityPlatform/OpenIDM/releases/download/$VERSION/openidm-$VERSION.zip --output openidm-$VERSION.zip ; fi' \
3031
&& unzip openidm-*.zip && rm -rf *.zip \

e2e/ui-smoke-test.spec.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import { test, expect } from "@playwright/test";
1919

2020
const BASE_URL = process.env.OPENIDM_URL || "http://localhost:8080";
21+
const CONTEXT_PATH = process.env.OPENIDM_CONTEXT_PATH || "/openidm";
2122
const ADMIN_USER = process.env.OPENIDM_ADMIN_USER || "openidm-admin";
2223
const ADMIN_PASS = process.env.OPENIDM_ADMIN_PASS || "openidm-admin";
2324

@@ -119,7 +120,7 @@ test.describe("OpenIDM UI Smoke Tests", () => {
119120
});
120121

121122
test("REST API ping is accessible", async ({ request }) => {
122-
const response = await request.get(`${BASE_URL}/openidm/info/ping`, {
123+
const response = await request.get(`${BASE_URL}${CONTEXT_PATH}/info/ping`, {
123124
headers: {
124125
"X-OpenIDM-Username": ADMIN_USER,
125126
"X-OpenIDM-Password": ADMIN_PASS,
@@ -131,7 +132,7 @@ test.describe("OpenIDM UI Smoke Tests", () => {
131132
});
132133

133134
test("REST API config endpoint is accessible", async ({ request }) => {
134-
const response = await request.get(`${BASE_URL}/openidm/config/ui/configuration`, {
135+
const response = await request.get(`${BASE_URL}${CONTEXT_PATH}/config/ui/configuration`, {
135136
headers: {
136137
"X-OpenIDM-Username": ADMIN_USER,
137138
"X-OpenIDM-Password": ADMIN_PASS,

openidm-api-servlet/src/main/java/org/forgerock/openidm/servlet/internal/ServletComponent.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* information: "Portions copyright [year] [name of copyright owner]".
1313
*
1414
* Copyright 2013-2016 ForgeRock AS.
15-
* Portions Copyrighted 2024-2025 3A Systems LLC.
15+
* Portions Copyrighted 2024-2026 3A Systems LLC.
1616
*/
1717
package org.forgerock.openidm.servlet.internal;
1818

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

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

96-
private static final String SERVLET_ALIAS = "/openidm";
96+
/** System property name for the configurable REST context path. */
97+
static final String OPENIDM_CONTEXT_PATH_PROPERTY = ServerConstants.OPENIDM_CONTEXT_PATH_PROPERTY;
98+
99+
/** Default REST context path. */
100+
static final String OPENIDM_CONTEXT_PATH_DEFAULT = ServerConstants.OPENIDM_CONTEXT_PATH_DEFAULT;
97101

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

@@ -155,9 +159,19 @@ protected synchronized void unbindRegistrator(ServletFilterRegistrator registrat
155159

156160
private HttpServlet servlet;
157161

162+
/**
163+
* Returns the servlet alias (REST context path) from the system property
164+
* {@code openidm.context.path}, defaulting to {@code /openidm}.
165+
*/
166+
static String getServletAlias() {
167+
return ServerConstants.normalizeContextPath(
168+
System.getProperty(OPENIDM_CONTEXT_PATH_PROPERTY, OPENIDM_CONTEXT_PATH_DEFAULT));
169+
}
170+
158171
@Activate
159172
protected void activate(ComponentContext context) throws ServletException, NamespaceException {
160-
logger.debug("Registering servlet at {}", SERVLET_ALIAS);
173+
final String servletAlias = getServletAlias();
174+
logger.debug("Registering servlet at {}", servletAlias);
161175

162176
final Handler handler = CrestHttp.newHttpHandler(
163177
new CrestApplication() {
@@ -201,8 +215,8 @@ public void stop() {
201215

202216
@SuppressWarnings("rawtypes")
203217
final Dictionary params = new Hashtable();
204-
servletRegistration.registerServlet(SERVLET_ALIAS, servlet, params);
205-
logger.info("Registered servlet at {}", SERVLET_ALIAS);
218+
servletRegistration.registerServlet(servletAlias, servlet, params);
219+
logger.info("Registered servlet at {}", servletAlias);
206220
}
207221

208222
@Deactivate
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* The contents of this file are subject to the terms of the Common Development and
3+
* Distribution License (the License). You may not use this file except in compliance with the
4+
* License.
5+
*
6+
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7+
* specific language governing permission and limitations under the License.
8+
*
9+
* When distributing Covered Software, include this CDDL Header Notice in each file and include
10+
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11+
* Header, with the fields enclosed by brackets [] replaced by your own identifying
12+
* information: "Portions copyright [year] [name of copyright owner]".
13+
*
14+
* Copyright 2025-2026 3A Systems LLC.
15+
*/
16+
17+
package org.forgerock.openidm.servlet.internal;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
import org.testng.annotations.AfterMethod;
22+
import org.testng.annotations.Test;
23+
24+
/**
25+
* Unit tests for {@link ServletComponent} context path configuration.
26+
*/
27+
public class ServletComponentTest {
28+
29+
@AfterMethod
30+
public void clearSystemProperty() {
31+
System.clearProperty(ServletComponent.OPENIDM_CONTEXT_PATH_PROPERTY);
32+
}
33+
34+
@Test
35+
public void testDefaultServletAlias() {
36+
// When no system property is set, should return the default /openidm
37+
System.clearProperty(ServletComponent.OPENIDM_CONTEXT_PATH_PROPERTY);
38+
assertThat(ServletComponent.getServletAlias()).isEqualTo("/openidm");
39+
}
40+
41+
@Test
42+
public void testCustomServletAlias() {
43+
// When system property is set to /myidm, should return /myidm
44+
System.setProperty(ServletComponent.OPENIDM_CONTEXT_PATH_PROPERTY, "/myidm");
45+
assertThat(ServletComponent.getServletAlias()).isEqualTo("/myidm");
46+
}
47+
48+
@Test
49+
public void testServletAliasWithoutLeadingSlash() {
50+
// Should add leading slash if missing
51+
System.setProperty(ServletComponent.OPENIDM_CONTEXT_PATH_PROPERTY, "myidm");
52+
assertThat(ServletComponent.getServletAlias()).isEqualTo("/myidm");
53+
}
54+
55+
@Test
56+
public void testServletAliasWithTrailingSlash() {
57+
// Should remove trailing slash
58+
System.setProperty(ServletComponent.OPENIDM_CONTEXT_PATH_PROPERTY, "/myidm/");
59+
assertThat(ServletComponent.getServletAlias()).isEqualTo("/myidm");
60+
}
61+
62+
@Test
63+
public void testServletAliasConstants() {
64+
assertThat(ServletComponent.OPENIDM_CONTEXT_PATH_PROPERTY).isEqualTo("openidm.context.path");
65+
assertThat(ServletComponent.OPENIDM_CONTEXT_PATH_DEFAULT).isEqualTo("/openidm");
66+
}
67+
}

openidm-doc/src/main/asciidoc/integrators-guide/appendix-rest.adoc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
information: "Portions copyright [year] [name of copyright owner]".
1313

1414
Copyright 2017 ForgeRock AS.
15-
Portions Copyright 2024-2025 3A Systems LLC.
15+
Portions Copyright 2024-2026 3A Systems LLC.
1616
////
1717
1818
:figure-caption!:
@@ -82,6 +82,11 @@ Note that for LDAP resources, you should not map the LDAP `dn` to the OpenIDM `u
8282
...
8383
----
8484
85+
[NOTE]
86+
====
87+
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__.
88+
====
89+
8590
8691
[#rest-object-identifier]
8792
=== Object Identifiers

openidm-doc/src/main/asciidoc/integrators-guide/chap-configuration.adoc

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
information: "Portions copyright [year] [name of copyright owner]".
1313

1414
Copyright 2017 ForgeRock AS.
15-
Portions Copyright 2024-2025 3A Systems LLC.
15+
Portions Copyright 2024-2026 3A Systems LLC.
1616
////
1717
1818
:figure-caption!:
@@ -152,6 +152,37 @@ felix.fileinstall.enableConfigSave=false
152152
----
153153
154154
155+
[#configuring-rest-context-path]
156+
==== Configuring the REST Context Path
157+
158+
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.
159+
160+
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:
161+
162+
[source]
163+
----
164+
openidm.context.path=/openidm
165+
----
166+
167+
Alternatively, you can pass the property as a JVM argument when starting OpenIDM:
168+
169+
[source, console]
170+
----
171+
$ OPENIDM_OPTS="-Dopenidm.context.path=/myidm" ./startup.sh
172+
----
173+
174+
The path must begin with a `/` and must not end with `/`. If the value provided does not start with a `/`, one is added automatically.
175+
176+
After changing this property, the REST API will be accessible under the new path, for example `\https://localhost:8443/myidm/config`. When the Admin UI or Self-Service UI is served by OpenIDM, `ResourceServlet` injects the effective context path into `index.html` at runtime as `window.__openidm_context_path`. The UI reads that injected value for its REST calls, so in this deployment model the UIs automatically follow a custom `openidm.context.path` without requiring a reverse proxy or a rebuild.
177+
178+
If you host the UI separately instead of serving it through OpenIDM, this runtime injection does not occur. In that case, you must provide equivalent runtime configuration for the UI, or use another deployment mechanism such as a reverse proxy or a custom UI build that targets the desired context path.
179+
180+
[NOTE]
181+
====
182+
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.
183+
====
184+
185+
155186
[#configuring-proxy]
156187
==== Communicating Through a Proxy Server
157188

openidm-servlet-registrator/pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
~ your own identifying information:
2323
~ "Portions Copyrighted [year] [name of copyright owner]"
2424
~
25-
~ Portions Copyrighted 2024-2025 3A Systems LLC.
25+
~ Portions Copyrighted 2024-2026 3A Systems LLC.
2626
-->
2727
<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">
2828
<modelVersion>4.0.0</modelVersion>
@@ -37,6 +37,11 @@
3737
<description>This bundle is duplicates of OpenIDM HTTP context bundle</description>
3838

3939
<dependencies>
40+
<dependency>
41+
<groupId>org.openidentityplatform.openidm</groupId>
42+
<artifactId>openidm-system</artifactId>
43+
<version>${project.version}</version>
44+
</dependency>
4045
<dependency>
4146
<groupId>org.openidentityplatform.openidm</groupId>
4247
<artifactId>openidm-enhanced-config</artifactId>

openidm-servlet-registrator/src/main/java/org/forgerock/openidm/servletregistration/impl/ServletRegistrationSingleton.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* your own identifying information:
2222
* "Portions Copyrighted [year] [name of copyright owner]"
2323
*
24-
* Portions Copyrighted 2024-2025 3A Systems LLC.
24+
* Portions Copyrighted 2024-2026 3A Systems LLC.
2525
*/
2626

2727
package org.forgerock.openidm.servletregistration.impl;
@@ -55,6 +55,7 @@
5555
import org.forgerock.openidm.servletregistration.RegisteredFilter;
5656
import org.forgerock.openidm.servletregistration.ServletRegistration;
5757
import org.forgerock.openidm.servletregistration.ServletFilterRegistrator;
58+
import org.forgerock.openidm.core.ServerConstants;
5859
import org.forgerock.util.Function;
5960
import org.ops4j.pax.web.service.WebContainer;
6061
import org.osgi.framework.BundleContext;
@@ -90,7 +91,21 @@ public class ServletRegistrationSingleton implements ServletRegistration {
9091

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

93-
private static final String[] DEFAULT_SERVLET_URL_PATTERNS = new String[] { "/openidm/*", "/selfservice/*" };
94+
/** System property name for the configurable REST context path. */
95+
private static final String OPENIDM_CONTEXT_PATH_PROPERTY = ServerConstants.OPENIDM_CONTEXT_PATH_PROPERTY;
96+
97+
/** Default REST context path. */
98+
private static final String OPENIDM_CONTEXT_PATH_DEFAULT = ServerConstants.OPENIDM_CONTEXT_PATH_DEFAULT;
99+
100+
/**
101+
* Returns the default servlet URL patterns, using the configured context path
102+
* from the {@code openidm.context.path} system property (default: {@code /openidm}).
103+
*/
104+
private static String[] getDefaultServletUrlPatterns() {
105+
String contextPath = ServerConstants.normalizeContextPath(
106+
System.getProperty(OPENIDM_CONTEXT_PATH_PROPERTY, OPENIDM_CONTEXT_PATH_DEFAULT));
107+
return new String[] { contextPath + "/*", "/selfservice/*" };
108+
}
94109

95110
// Context of this scr component
96111
private BundleContext bundleContext;
@@ -212,7 +227,7 @@ public URL apply(JsonValue jsonValue) throws JsonValueException {
212227

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

218233
// Filter init params, a string to string map

0 commit comments

Comments
 (0)