-
-
Notifications
You must be signed in to change notification settings - Fork 47
Make REST context path configurable via openidm.context.path system property
#142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
0a882f0
f935409
81b026f
5e4f102
9d963ed
fdd7eeb
c383648
c545b24
341483c
c06ea06
ee9dc32
8ff1486
dd454b8
e2e87ac
5f5835a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static final String API_ID = "frapi:openidm"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+162
to
+170
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | |
| * 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; | |
| } | |
| private static String normalizeContextPath(String path) { | |
| if (!path.startsWith("/")) { | |
| path = "/" + path; | |
| } | |
| if (path.endsWith("/") && path.length() > 1) { | |
| path = path.substring(0, path.length() - 1); | |
| } | |
| return path; | |
| } | |
| /** | |
| * Returns the servlet alias (REST context path) from the system property | |
| * {@code openidm.context.path}, defaulting to {@code /openidm}. | |
| */ | |
| static String getServletAlias() { | |
| return normalizeContextPath( | |
| System.getProperty(OPENIDM_CONTEXT_PATH_PROPERTY, OPENIDM_CONTEXT_PATH_DEFAULT)); | |
| } |
| 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 | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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!: | ||||||||||||||||||||||
|
|
@@ -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. | ||||||||||||||||||||||
|
||||||||||||||||||||||
| 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. |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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; | ||||||||||||
|
|
||||||||||||
|
|
@@ -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_DESC = "URL of OpenIDM REST service. Default " + IDM_URL_DEFAULT; | |
| private static final String IDM_URL_DESC = | |
| "URL of OpenIDM REST service. Default: http://localhost:8080 plus the effective " | |
| + "context path (derived from -Dopenidm.context.path; default context path: " | |
| + "/openidm)."; |
Copilot
AI
Mar 31, 2026
There was a problem hiding this comment.
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’).
| return getEffectiveIdmUrl(url.endsWith("/") ? url : url + "/"); | |
| return url.endsWith("/") ? url : url + "/"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
openidm.context.pathproperty name/default and the path-normalization logic are now duplicated in multiple modules/classes (e.g., this component,ServletRegistrationSingleton, andRemoteCommandScope). 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 orServerConstants/IdentityServer) and reusing it from all call sites.