From f8d1816ec995a5fa8331c1007420338de988178a Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Tue, 8 Sep 2026 16:10:04 +0200 Subject: [PATCH 01/12] Migrating property 'plugin.restapi.enabled' --- plugin.xml | 1 + src/i18n/restapi_i18n.properties | 1 + src/i18n/restapi_i18n_nl.properties | 1 + .../openfire/plugin/rest/AuthFilter.java | 2 +- .../plugin/rest/RESTServicePlugin.java | 55 ++++++------------- .../rest/service/UserServiceLegacy.java | 2 +- src/web/rest-api.jsp | 4 +- 7 files changed, 24 insertions(+), 42 deletions(-) diff --git a/plugin.xml b/plugin.xml index cf76295bb..e9866a36e 100644 --- a/plugin.xml +++ b/plugin.xml @@ -8,6 +8,7 @@ ${project.version} 2025-10-02 5.0.0 + 5.2.0 diff --git a/src/i18n/restapi_i18n.properties b/src/i18n/restapi_i18n.properties index cdce3d0c0..e31d94c6b 100644 --- a/src/i18n/restapi_i18n.properties +++ b/src/i18n/restapi_i18n.properties @@ -1,5 +1,6 @@ system_property.plugin.restapi.muc.case-insensitive-lookup.enabled=Names of MUC rooms should be node-prepped. This, however, was not guaranteed the case in some versions of Openfire and this plugin. Earlier versions of this plugin used a case-insensitive lookup to work around this. As this should be unneeded, and is quite resource intensive, this behavior has been made configurable (disabled by default). system_property.plugin.restapi.muc.room-mutex.enabled=Controls if a mutual exclusion lock is used when an API interacts with a room. +system_property.plugin.restapi.secret=The value that is used to authenticate requests when using 'shared secret' authentication. stat.restapi_responses.informational.name=REST API 1xx responses stat.restapi_responses.informational.desc=The amount of HTTP responses that had an 'Informational' status (a code in the 1xx range). diff --git a/src/i18n/restapi_i18n_nl.properties b/src/i18n/restapi_i18n_nl.properties index 18cbc6656..21d43cd48 100644 --- a/src/i18n/restapi_i18n_nl.properties +++ b/src/i18n/restapi_i18n_nl.properties @@ -1,5 +1,6 @@ system_property.plugin.restapi.muc.case-insensitive-lookup.enabled=Namen van MUC-kamers zouden genode-prepped moeten zijn. Dit werd echter niet gegarandeerd in sommige versies van Openfire en deze plugin. Oudere versies van deze plugin gebruikten een hoofdletter-ongevoelige zoekopdracht om hier omheen te werken. Dit vergt behoorlijk wat rekenkracht en zou onnodig moeten zijn. Hierom is dit gedrag configureerbaar gemaakt (standaard-instelling: uit) system_property.plugin.restapi.muc.room-mutex.enabled=Bepaald of een MUC-kamer-specifieke mutex wordt gebruikt wanneer de API interacteert met een MUC-kamer. +system_property.plugin.restapi.secret=De waarde die wordt gebruikt om verzoeken te authenticeren wanneer authenticatie met een 'gedeeld geheim' wordt gebruikt. stat.restapi_responses.informational.name=REST API 1xx antwoorden stat.restapi_responses.informational.desc=Het aantal HTTP antwoorden met een 'Informational' status (een code in de 1xx reeks). diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java b/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java index 2848e52f8..5375be5cf 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java @@ -138,7 +138,7 @@ public void filter(ContainerRequestContext containerRequest) throws IOException throw new WebApplicationException(Status.UNAUTHORIZED); } } else { - if (!auth.equals(plugin.getSecret())) { + if (!auth.equals(RESTServicePlugin.SECRET.getValue())) { LOG.warn("Wrong secret key authorization. Provided key: " + auth); throw new WebApplicationException(Status.UNAUTHORIZED); } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java b/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java index 2f61a5692..e1e527f8e 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java @@ -21,10 +21,7 @@ import org.jivesoftware.openfire.container.PluginManager; import org.jivesoftware.openfire.plugin.rest.service.JerseyWrapper; import org.jivesoftware.openfire.stats.StatisticsManager; -import org.jivesoftware.util.JiveGlobals; -import org.jivesoftware.util.PropertyEventDispatcher; -import org.jivesoftware.util.PropertyEventListener; -import org.jivesoftware.util.StringUtils; +import org.jivesoftware.util.*; import java.io.File; import java.util.*; @@ -34,12 +31,19 @@ */ public class RESTServicePlugin implements Plugin, PropertyEventListener { + /** + * The value that is used to authenticate requests when using 'shared secret' authentication. + */ + public static final SystemProperty SECRET = SystemProperty.Builder.ofType(String.class) + .setPlugin("REST API") + .setKey("plugin.restapi.secret") + .setDynamic(true) + .setEncrypted(true) + .build(); + private static final String CUSTOM_AUTH_FILTER_PROPERTY_NAME = "plugin.restapi.customAuthFilter"; public static final String SERVICE_LOGGING_ENABLED = "plugin.restapi.serviceLoggingEnabled"; - /** The secret. */ - private String secret; - /** The allowed i ps. */ private Collection allowedIPs; @@ -68,12 +72,11 @@ public void setServiceLoggingEnabled(boolean serviceLoggingEnabled) { /* (non-Javadoc) * @see org.jivesoftware.openfire.container.Plugin#initializePlugin(org.jivesoftware.openfire.container.PluginManager, java.io.File) */ - public void initializePlugin(PluginManager manager, File pluginDirectory) { - secret = JiveGlobals.getProperty("plugin.restapi.secret", ""); + public void initializePlugin(PluginManager manager, File pluginDirectory) + { // If no secret key has been assigned, assign a random one. - if ("".equals(secret)) { - secret = StringUtils.randomString(16); - setSecret(secret); + if (SECRET.getValue() == null || SECRET.getValue().isEmpty()) { + SECRET.setValue(StringUtils.randomString(16)); } // See if Custom authentication filter has been defined @@ -135,26 +138,6 @@ public String getLoadingStatusMessage() { public String loadAuthenticationFilter(String customAuthFilterClassName) { return JerseyWrapper.tryLoadingAuthenticationFilter(customAuthFilterClassName); } - - /** - * Returns the secret key that only valid requests should know. - * - * @return the secret key. - */ - public String getSecret() { - return secret; - } - - /** - * Sets the secret key that grants permission to use the userservice. - * - * @param secret - * the secret key. - */ - public void setSecret(String secret) { - JiveGlobals.setProperty("plugin.restapi.secret", secret); - this.secret = secret; - } /** * Returns the custom authentication filter class name used in place of the basic ones to grant permission to use the Rest services. @@ -240,9 +223,7 @@ public void setHttpAuth(String httpAuth) { * @see org.jivesoftware.util.PropertyEventListener#propertySet(java.lang.String, java.util.Map) */ public void propertySet(String property, Map params) { - if (property.equals("plugin.restapi.secret")) { - this.secret = (String) params.get("value"); - } else if (property.equals("plugin.restapi.enabled")) { + if (property.equals("plugin.restapi.enabled")) { this.enabled = Boolean.parseBoolean((String) params.get("value")); } else if (property.equals("plugin.restapi.allowedIPs")) { this.allowedIPs = StringUtils.stringToCollection((String) params.get("value")); @@ -257,9 +238,7 @@ public void propertySet(String property, Map params) { * @see org.jivesoftware.util.PropertyEventListener#propertyDeleted(java.lang.String, java.util.Map) */ public void propertyDeleted(String property, Map params) { - if (property.equals("plugin.restapi.secret")) { - this.secret = ""; - } else if (property.equals("plugin.restapi.enabled")) { + if (property.equals("plugin.restapi.enabled")) { this.enabled = false; } else if (property.equals("plugin.restapi.allowedIPs")) { this.allowedIPs = Collections.emptyList(); diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/service/UserServiceLegacy.java b/src/java/org/jivesoftware/openfire/plugin/rest/service/UserServiceLegacy.java index 8be8fa197..ea2eccf31 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/service/UserServiceLegacy.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/service/UserServiceLegacy.java @@ -112,7 +112,7 @@ public Response userSerivceRequest() throws IOException { } // Check this request is authorised - if (secret == null || !secret.equals(plugin.getSecret())) { + if (secret == null || secret.isEmpty() || !secret.equals(RESTServicePlugin.SECRET.getValue())) { LOG.warn("An unauthorised user service request was received: " + request.getQueryString()); replyError("RequestNotAuthorised", response, out); return Response.status(200).build(); diff --git a/src/web/rest-api.jsp b/src/web/rest-api.jsp index a4bfccce1..1a8ea6e97 100644 --- a/src/web/rest-api.jsp +++ b/src/web/rest-api.jsp @@ -68,7 +68,7 @@ boolean is2Reload = "custom".equals(httpAuth) || "custom".equals(plugin.getHttpAuth()); plugin.setEnabled(enabled); - plugin.setSecret(secret); + RESTServicePlugin.SECRET.setValue(secret == null || secret.isEmpty() ? StringUtils.randomString(16) : secret); plugin.setHttpAuth(httpAuth); plugin.setAllowedIPs(StringUtils.stringToCollection(allowedIPs)); plugin.setCustomAuthFiIterClassName(customAuthFilterClassName); @@ -88,7 +88,7 @@ } } - secret = plugin.getSecret(); + secret = RESTServicePlugin.SECRET.getValue(); enabled = plugin.isEnabled(); httpAuth = plugin.getHttpAuth(); allowedIPs = StringUtils.collectionToString(plugin.getAllowedIPs()); From b1fac1edbda51e9c5b602410d92d06abd5ae6528 Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Tue, 8 Sep 2026 16:28:19 +0200 Subject: [PATCH 02/12] Migrating property 'plugin.restapi.serviceLoggingEnabled' --- src/i18n/restapi_i18n.properties | 1 + src/i18n/restapi_i18n_nl.properties | 1 + .../plugin/rest/RESTServicePlugin.java | 25 ++++++++----------- .../rest/controller/ClusteringController.java | 5 ++-- .../rest/controller/MUCRoomController.java | 7 +++--- .../rest/controller/MUCServiceController.java | 5 ++-- .../rest/controller/SystemController.java | 4 +-- .../controller/UserServiceController.java | 5 ++-- src/web/rest-api.jsp | 4 +-- 9 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/i18n/restapi_i18n.properties b/src/i18n/restapi_i18n.properties index e31d94c6b..b454eeb06 100644 --- a/src/i18n/restapi_i18n.properties +++ b/src/i18n/restapi_i18n.properties @@ -1,6 +1,7 @@ system_property.plugin.restapi.muc.case-insensitive-lookup.enabled=Names of MUC rooms should be node-prepped. This, however, was not guaranteed the case in some versions of Openfire and this plugin. Earlier versions of this plugin used a case-insensitive lookup to work around this. As this should be unneeded, and is quite resource intensive, this behavior has been made configurable (disabled by default). system_property.plugin.restapi.muc.room-mutex.enabled=Controls if a mutual exclusion lock is used when an API interacts with a room. system_property.plugin.restapi.secret=The value that is used to authenticate requests when using 'shared secret' authentication. +system_property.plugin.restapi.serviceLoggingEnabled=Enables or disables additional logging of REST API service calls. stat.restapi_responses.informational.name=REST API 1xx responses stat.restapi_responses.informational.desc=The amount of HTTP responses that had an 'Informational' status (a code in the 1xx range). diff --git a/src/i18n/restapi_i18n_nl.properties b/src/i18n/restapi_i18n_nl.properties index 21d43cd48..e2231969c 100644 --- a/src/i18n/restapi_i18n_nl.properties +++ b/src/i18n/restapi_i18n_nl.properties @@ -1,6 +1,7 @@ system_property.plugin.restapi.muc.case-insensitive-lookup.enabled=Namen van MUC-kamers zouden genode-prepped moeten zijn. Dit werd echter niet gegarandeerd in sommige versies van Openfire en deze plugin. Oudere versies van deze plugin gebruikten een hoofdletter-ongevoelige zoekopdracht om hier omheen te werken. Dit vergt behoorlijk wat rekenkracht en zou onnodig moeten zijn. Hierom is dit gedrag configureerbaar gemaakt (standaard-instelling: uit) system_property.plugin.restapi.muc.room-mutex.enabled=Bepaald of een MUC-kamer-specifieke mutex wordt gebruikt wanneer de API interacteert met een MUC-kamer. system_property.plugin.restapi.secret=De waarde die wordt gebruikt om verzoeken te authenticeren wanneer authenticatie met een 'gedeeld geheim' wordt gebruikt. +system_property.plugin.restapi.serviceLoggingEnabled=Aanvullende logging van REST API-serviceaanroepen in- of uitschakelen. stat.restapi_responses.informational.name=REST API 1xx antwoorden stat.restapi_responses.informational.desc=Het aantal HTTP antwoorden met een 'Informational' status (een code in de 1xx reeks). diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java b/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java index e1e527f8e..c5d65db2f 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2008 Jive Software, 2022 Ignite Realtime Foundation. All rights reserved. + * Copyright (C) 2005-2008 Jive Software, 2022-2026 Ignite Realtime Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,8 +41,17 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener { .setEncrypted(true) .build(); + /** + * Enables or disables additional logging of REST API service calls. + */ + public static final SystemProperty SERVICE_LOGGING_ENABLED = SystemProperty.Builder.ofType(Boolean.class) + .setPlugin("REST API") + .setKey("plugin.restapi.serviceLoggingEnabled") + .setDynamic(true) + .setDefaultValue(false) + .build(); + private static final String CUSTOM_AUTH_FILTER_PROPERTY_NAME = "plugin.restapi.customAuthFilter"; - public static final String SERVICE_LOGGING_ENABLED = "plugin.restapi.serviceLoggingEnabled"; /** The allowed i ps. */ private Collection allowedIPs; @@ -50,17 +59,6 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener { /** The enabled. */ private boolean enabled; - public boolean isServiceLoggingEnabled() { - return serviceLoggingEnabled; - } - - public void setServiceLoggingEnabled(boolean serviceLoggingEnabled) { - JiveGlobals.setProperty(SERVICE_LOGGING_ENABLED, Boolean.toString(serviceLoggingEnabled)); - this.serviceLoggingEnabled = serviceLoggingEnabled; - } - - private boolean serviceLoggingEnabled; - /** The http auth. */ private String httpAuth; @@ -98,7 +96,6 @@ public void initializePlugin(PluginManager manager, File pluginDirectory) // means that this filter is disabled. allowedIPs = StringUtils.stringToCollection(JiveGlobals.getProperty("plugin.restapi.allowedIPs", "")); - setServiceLoggingEnabled(JiveGlobals.getBooleanProperty(SERVICE_LOGGING_ENABLED, false)); // Listen to system property events PropertyEventDispatcher.addListener(this); diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/controller/ClusteringController.java b/src/java/org/jivesoftware/openfire/plugin/rest/controller/ClusteringController.java index 75a9ef062..4cc9ac7ce 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/controller/ClusteringController.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/controller/ClusteringController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022. + * Copyright (c) 2022-2026 Ignite Realtime Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ import org.jivesoftware.openfire.plugin.rest.RESTServicePlugin; import org.jivesoftware.openfire.plugin.rest.entity.ClusterNodeEntities; import org.jivesoftware.openfire.plugin.rest.entity.ClusterNodeEntity; -import org.jivesoftware.util.JiveGlobals; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,7 +56,7 @@ public static void setInstance(final ClusteringController instance) { } public static void log(String logMessage) { - if (JiveGlobals.getBooleanProperty(RESTServicePlugin.SERVICE_LOGGING_ENABLED, false)) { + if (RESTServicePlugin.SERVICE_LOGGING_ENABLED.getValue()) { LOG.info(logMessage); } } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/controller/MUCRoomController.java b/src/java/org/jivesoftware/openfire/plugin/rest/controller/MUCRoomController.java index f04283bf6..2053a55b1 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/controller/MUCRoomController.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/controller/MUCRoomController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2025 Ignite Realtime Foundation + * Copyright (c) 2022-2026 Ignite Realtime Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,6 @@ import org.jivesoftware.openfire.plugin.rest.utils.MUCRoomUtils; import org.jivesoftware.openfire.plugin.rest.utils.UserUtils; import org.jivesoftware.util.AlreadyExistsException; -import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.StringUtils; import org.jivesoftware.util.SystemProperty; import org.slf4j.Logger; @@ -102,13 +101,13 @@ public static void setInstance(final MUCRoomController instance) { } public static void log(String logMessage) { - if (JiveGlobals.getBooleanProperty(RESTServicePlugin.SERVICE_LOGGING_ENABLED, false)) { + if (RESTServicePlugin.SERVICE_LOGGING_ENABLED.getValue()) { LOG.info(logMessage); } } public static void log(String logMessage, Throwable t) { - if (JiveGlobals.getBooleanProperty(RESTServicePlugin.SERVICE_LOGGING_ENABLED, false)) { + if (RESTServicePlugin.SERVICE_LOGGING_ENABLED.getValue()) { LOG.info(logMessage, t); } } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/controller/MUCServiceController.java b/src/java/org/jivesoftware/openfire/plugin/rest/controller/MUCServiceController.java index 1c5e5e44d..d73a4ec0c 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/controller/MUCServiceController.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/controller/MUCServiceController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022. + * Copyright (c) 2022-2026 Ignite Realtime Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,6 @@ import org.jivesoftware.openfire.plugin.rest.exceptions.ExceptionType; import org.jivesoftware.openfire.plugin.rest.exceptions.ServiceException; import org.jivesoftware.util.AlreadyExistsException; -import org.jivesoftware.util.JiveGlobals; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,7 +58,7 @@ public static MUCServiceController getInstance() { } public static void log(String logMessage) { - if (JiveGlobals.getBooleanProperty(RESTServicePlugin.SERVICE_LOGGING_ENABLED, false)) { + if (RESTServicePlugin.SERVICE_LOGGING_ENABLED.getValue()) { LOG.info(logMessage); } } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/controller/SystemController.java b/src/java/org/jivesoftware/openfire/plugin/rest/controller/SystemController.java index 28f9d22f5..24049ea36 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/controller/SystemController.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/controller/SystemController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022. + * Copyright (c) 2022-2026 Ignite Realtime Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,7 +64,7 @@ public static void setInstance(final SystemController instance) { } public static void log(String logMessage) { - if (JiveGlobals.getBooleanProperty(RESTServicePlugin.SERVICE_LOGGING_ENABLED, false)) { + if (RESTServicePlugin.SERVICE_LOGGING_ENABLED.getValue()) { LOG.info(logMessage); } } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/controller/UserServiceController.java b/src/java/org/jivesoftware/openfire/plugin/rest/controller/UserServiceController.java index 54fd6a239..9322b9b29 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/controller/UserServiceController.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/controller/UserServiceController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022. + * Copyright (c) 2022-2026 Ignite Realtime Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,6 @@ import org.jivesoftware.openfire.user.UserManager; import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.openfire.vcard.VCardManager; -import org.jivesoftware.util.JiveGlobals; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmpp.packet.JID; @@ -109,7 +108,7 @@ private UserServiceController() { } public static void log(String logMessage) { - if (JiveGlobals.getBooleanProperty(RESTServicePlugin.SERVICE_LOGGING_ENABLED, false)) { + if (RESTServicePlugin.SERVICE_LOGGING_ENABLED.getValue()) { LOG.info(logMessage); } } diff --git a/src/web/rest-api.jsp b/src/web/rest-api.jsp index 1a8ea6e97..5fa20c449 100644 --- a/src/web/rest-api.jsp +++ b/src/web/rest-api.jsp @@ -72,7 +72,7 @@ plugin.setHttpAuth(httpAuth); plugin.setAllowedIPs(StringUtils.stringToCollection(allowedIPs)); plugin.setCustomAuthFiIterClassName(customAuthFilterClassName); - plugin.setServiceLoggingEnabled(loggingEnabled); + RESTServicePlugin.SERVICE_LOGGING_ENABLED.setValue(loggingEnabled); if(is2Reload) { String pluginName = PluginMetadataHelper.getName(plugin); @@ -93,7 +93,7 @@ httpAuth = plugin.getHttpAuth(); allowedIPs = StringUtils.collectionToString(plugin.getAllowedIPs()); customAuthFilterClassName = plugin.getCustomAuthFilterClassName(); - loggingEnabled = plugin.isServiceLoggingEnabled(); + loggingEnabled = RESTServicePlugin.SERVICE_LOGGING_ENABLED.getValue(); %> From 09e8d03db1e4fc1c6e3eb0ab7235e2580e0ec859 Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Tue, 8 Sep 2026 16:38:17 +0200 Subject: [PATCH 03/12] Migrating property 'plugin.restapi.customAuthFilter' --- src/i18n/restapi_i18n.properties | 1 + src/i18n/restapi_i18n_nl.properties | 1 + .../plugin/rest/RESTServicePlugin.java | 39 ++++--------------- .../plugin/rest/service/JerseyWrapper.java | 8 +--- src/web/rest-api.jsp | 5 +-- 5 files changed, 14 insertions(+), 40 deletions(-) diff --git a/src/i18n/restapi_i18n.properties b/src/i18n/restapi_i18n.properties index b454eeb06..79946c9b2 100644 --- a/src/i18n/restapi_i18n.properties +++ b/src/i18n/restapi_i18n.properties @@ -1,3 +1,4 @@ +system_property.plugin.restapi.customAuthFilter=The class name of a custom authentication filter implementation. system_property.plugin.restapi.muc.case-insensitive-lookup.enabled=Names of MUC rooms should be node-prepped. This, however, was not guaranteed the case in some versions of Openfire and this plugin. Earlier versions of this plugin used a case-insensitive lookup to work around this. As this should be unneeded, and is quite resource intensive, this behavior has been made configurable (disabled by default). system_property.plugin.restapi.muc.room-mutex.enabled=Controls if a mutual exclusion lock is used when an API interacts with a room. system_property.plugin.restapi.secret=The value that is used to authenticate requests when using 'shared secret' authentication. diff --git a/src/i18n/restapi_i18n_nl.properties b/src/i18n/restapi_i18n_nl.properties index e2231969c..82afe5c0c 100644 --- a/src/i18n/restapi_i18n_nl.properties +++ b/src/i18n/restapi_i18n_nl.properties @@ -1,3 +1,4 @@ +system_property.plugin.restapi.customAuthFilter=De klassenaam van een authenticatie-filter implementatie. system_property.plugin.restapi.muc.case-insensitive-lookup.enabled=Namen van MUC-kamers zouden genode-prepped moeten zijn. Dit werd echter niet gegarandeerd in sommige versies van Openfire en deze plugin. Oudere versies van deze plugin gebruikten een hoofdletter-ongevoelige zoekopdracht om hier omheen te werken. Dit vergt behoorlijk wat rekenkracht en zou onnodig moeten zijn. Hierom is dit gedrag configureerbaar gemaakt (standaard-instelling: uit) system_property.plugin.restapi.muc.room-mutex.enabled=Bepaald of een MUC-kamer-specifieke mutex wordt gebruikt wanneer de API interacteert met een MUC-kamer. system_property.plugin.restapi.secret=De waarde die wordt gebruikt om verzoeken te authenticeren wanneer authenticatie met een 'gedeeld geheim' wordt gebruikt. diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java b/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java index c5d65db2f..b3aadb2f3 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java @@ -51,7 +51,14 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener { .setDefaultValue(false) .build(); - private static final String CUSTOM_AUTH_FILTER_PROPERTY_NAME = "plugin.restapi.customAuthFilter"; + /** + * The class name of a custom authentication filter implementation. + */ + public static final SystemProperty CUSTOM_AUTH_FILTER = SystemProperty.Builder.ofType(String.class) + .setPlugin("REST API") + .setKey("plugin.restapi.customAuthFilter") + .setDynamic(true) + .build(); /** The allowed i ps. */ private Collection allowedIPs; @@ -62,9 +69,6 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener { /** The http auth. */ private String httpAuth; - /** The custom authentication filter */ - private String customAuthFilterClassName; - private final Set registeredStatisticKeys = new HashSet<>(); /* (non-Javadoc) @@ -77,9 +81,6 @@ public void initializePlugin(PluginManager manager, File pluginDirectory) SECRET.setValue(StringUtils.randomString(16)); } - // See if Custom authentication filter has been defined - customAuthFilterClassName = JiveGlobals.getProperty("plugin.restapi.customAuthFilter", ""); - // Start collecting statistics. for (StatisticsFilter.RestResponseFamilyStatistic statistic : StatisticsFilter.generateAllFamilyStatisticInstances()) { StatisticsManager.getInstance().addStatistic(statistic.getKeyName(), statistic); @@ -135,26 +136,6 @@ public String getLoadingStatusMessage() { public String loadAuthenticationFilter(String customAuthFilterClassName) { return JerseyWrapper.tryLoadingAuthenticationFilter(customAuthFilterClassName); } - - /** - * Returns the custom authentication filter class name used in place of the basic ones to grant permission to use the Rest services. - * - * @return custom authentication filter class name . - */ - public String getCustomAuthFilterClassName() { - return customAuthFilterClassName; - } - - /** - * Sets the customAuthFIlterClassName used to grant permission to use the Rest services. - * - * @param customAuthFilterClassName - * custom authentication filter class name. - */ - public void setCustomAuthFiIterClassName(String customAuthFilterClassName) { - JiveGlobals.setProperty(CUSTOM_AUTH_FILTER_PROPERTY_NAME, customAuthFilterClassName); - this.customAuthFilterClassName = customAuthFilterClassName; - } /** * Gets the allowed i ps. @@ -226,8 +207,6 @@ public void propertySet(String property, Map params) { this.allowedIPs = StringUtils.stringToCollection((String) params.get("value")); } else if (property.equals("plugin.restapi.httpAuth")) { this.httpAuth = (String) params.get("value"); - } else if(property.equals(CUSTOM_AUTH_FILTER_PROPERTY_NAME)) { - this.customAuthFilterClassName = (String) params.get("value"); } } @@ -241,8 +220,6 @@ public void propertyDeleted(String property, Map params) { this.allowedIPs = Collections.emptyList(); } else if (property.equals("plugin.restapi.httpAuth")) { this.httpAuth = "basic"; - } else if(property.equals(CUSTOM_AUTH_FILTER_PROPERTY_NAME)) { - this.customAuthFilterClassName = null; } } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/service/JerseyWrapper.java b/src/java/org/jivesoftware/openfire/plugin/rest/service/JerseyWrapper.java index 1fa6a3314..e8600c385 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/service/JerseyWrapper.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/service/JerseyWrapper.java @@ -22,7 +22,6 @@ import org.jivesoftware.openfire.plugin.rest.CustomJacksonMapperProvider; import org.jivesoftware.openfire.plugin.rest.StatisticsFilter; import org.jivesoftware.openfire.plugin.rest.exceptions.RESTExceptionMapper; -import org.jivesoftware.util.JiveGlobals; import javax.servlet.ServletConfig; import javax.ws.rs.core.Context; @@ -34,9 +33,6 @@ */ public class JerseyWrapper extends ResourceConfig { - /** The Constant CUSTOM_AUTH_PROPERTY_NAME */ - private static final String CUSTOM_AUTH_PROPERTY_NAME = "plugin.restapi.customAuthFilter"; - /** The Constant REST_AUTH_TYPE */ private static final String REST_AUTH_TYPE = "plugin.restapi.httpAuth"; @@ -69,9 +65,9 @@ public static String tryLoadingAuthenticationFilter(String customAuthFilterClass } public String loadAuthenticationFilter() { - + // Check if custom AuthFilter is available - String customAuthFilterClassName = JiveGlobals.getProperty(CUSTOM_AUTH_PROPERTY_NAME); + String customAuthFilterClassName = RESTServicePlugin.CUSTOM_AUTH_FILTER.getValue(); String restAuthType = JiveGlobals.getProperty(REST_AUTH_TYPE); Class pickedAuthFilter = AuthFilter.class; diff --git a/src/web/rest-api.jsp b/src/web/rest-api.jsp index 5fa20c449..9427664d8 100644 --- a/src/web/rest-api.jsp +++ b/src/web/rest-api.jsp @@ -21,7 +21,6 @@ import="java.util.*, org.jivesoftware.openfire.XMPPServer, org.jivesoftware.util.*,org.jivesoftware.openfire.plugin.rest.RESTServicePlugin, - org.jivesoftware.openfire.container.Plugin, org.jivesoftware.openfire.container.PluginManager" errorPage="error.jsp"%> <%@ page import="org.jivesoftware.openfire.container.PluginMetadataHelper" %> @@ -71,7 +70,7 @@ RESTServicePlugin.SECRET.setValue(secret == null || secret.isEmpty() ? StringUtils.randomString(16) : secret); plugin.setHttpAuth(httpAuth); plugin.setAllowedIPs(StringUtils.stringToCollection(allowedIPs)); - plugin.setCustomAuthFiIterClassName(customAuthFilterClassName); + RESTServicePlugin.CUSTOM_AUTH_FILTER.setValue(customAuthFilterClassName); RESTServicePlugin.SERVICE_LOGGING_ENABLED.setValue(loggingEnabled); if(is2Reload) { @@ -92,7 +91,7 @@ enabled = plugin.isEnabled(); httpAuth = plugin.getHttpAuth(); allowedIPs = StringUtils.collectionToString(plugin.getAllowedIPs()); - customAuthFilterClassName = plugin.getCustomAuthFilterClassName(); + customAuthFilterClassName = RESTServicePlugin.CUSTOM_AUTH_FILTER.getValue(); loggingEnabled = RESTServicePlugin.SERVICE_LOGGING_ENABLED.getValue(); %> From 18a80fc9e6ef625f1b0cd01b0fa44d9291dbd79b Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Tue, 8 Sep 2026 16:45:54 +0200 Subject: [PATCH 04/12] Migrating property 'plugin.restapi.enabled' --- src/i18n/restapi_i18n.properties | 1 + src/i18n/restapi_i18n_nl.properties | 1 + .../openfire/plugin/rest/AuthFilter.java | 4 +- .../plugin/rest/RESTServicePlugin.java | 46 +++++-------------- .../rest/service/UserServiceLegacy.java | 4 +- src/web/rest-api.jsp | 4 +- 6 files changed, 20 insertions(+), 40 deletions(-) diff --git a/src/i18n/restapi_i18n.properties b/src/i18n/restapi_i18n.properties index 79946c9b2..6cf8aa5c8 100644 --- a/src/i18n/restapi_i18n.properties +++ b/src/i18n/restapi_i18n.properties @@ -1,4 +1,5 @@ system_property.plugin.restapi.customAuthFilter=The class name of a custom authentication filter implementation. +system_property.plugin.restapi.enabled=Enables or disables the processing of REST API service requests. system_property.plugin.restapi.muc.case-insensitive-lookup.enabled=Names of MUC rooms should be node-prepped. This, however, was not guaranteed the case in some versions of Openfire and this plugin. Earlier versions of this plugin used a case-insensitive lookup to work around this. As this should be unneeded, and is quite resource intensive, this behavior has been made configurable (disabled by default). system_property.plugin.restapi.muc.room-mutex.enabled=Controls if a mutual exclusion lock is used when an API interacts with a room. system_property.plugin.restapi.secret=The value that is used to authenticate requests when using 'shared secret' authentication. diff --git a/src/i18n/restapi_i18n_nl.properties b/src/i18n/restapi_i18n_nl.properties index 82afe5c0c..f47f8c058 100644 --- a/src/i18n/restapi_i18n_nl.properties +++ b/src/i18n/restapi_i18n_nl.properties @@ -1,4 +1,5 @@ system_property.plugin.restapi.customAuthFilter=De klassenaam van een authenticatie-filter implementatie. +system_property.plugin.restapi.enabled=Schakelt de verwerking van REST API-serviceverzoeken in of uit. system_property.plugin.restapi.muc.case-insensitive-lookup.enabled=Namen van MUC-kamers zouden genode-prepped moeten zijn. Dit werd echter niet gegarandeerd in sommige versies van Openfire en deze plugin. Oudere versies van deze plugin gebruikten een hoofdletter-ongevoelige zoekopdracht om hier omheen te werken. Dit vergt behoorlijk wat rekenkracht en zou onnodig moeten zijn. Hierom is dit gedrag configureerbaar gemaakt (standaard-instelling: uit) system_property.plugin.restapi.muc.room-mutex.enabled=Bepaald of een MUC-kamer-specifieke mutex wordt gebruikt wanneer de API interacteert met een MUC-kamer. system_property.plugin.restapi.secret=De waarde die wordt gebruikt om verzoeken te authenticeren wanneer authenticatie met een 'gedeeld geheim' wordt gebruikt. diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java b/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java index 5375be5cf..a6454539b 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022. + * Copyright (C) 2022-2026 Ignite Realtime Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,7 +66,7 @@ public void filter(ContainerRequestContext containerRequest) throws IOException return; } - if (!plugin.isEnabled()) { + if (!RESTServicePlugin.ENABLED.getValue()) { LOG.debug("REST API Plugin is not enabled"); throw new WebApplicationException(Status.FORBIDDEN); } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java b/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java index b3aadb2f3..eb57a4726 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java @@ -60,11 +60,18 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener { .setDynamic(true) .build(); + /** + * Enables or disables the processing of REST API service requests. + */ + public static final SystemProperty ENABLED = SystemProperty.Builder.ofType(Boolean.class) + .setPlugin("REST API") + .setKey("plugin.restapi.enabled") + .setDynamic(true) + .setDefaultValue(false) + .build(); + /** The allowed i ps. */ private Collection allowedIPs; - - /** The enabled. */ - private boolean enabled; /** The http auth. */ private String httpAuth; @@ -87,9 +94,6 @@ public void initializePlugin(PluginManager manager, File pluginDirectory) registeredStatisticKeys.add(statistic.getKeyName()); } - // See if the service is enabled or not. - enabled = JiveGlobals.getBooleanProperty("plugin.restapi.enabled", false); - // See if the HTTP Basic Auth is enabled or not. httpAuth = JiveGlobals.getProperty("plugin.restapi.httpAuth", "basic"); @@ -156,28 +160,6 @@ public void setAllowedIPs(Collection allowedIPs) { this.allowedIPs = allowedIPs; } - /** - * Returns true if the user service is enabled. If not enabled, it will not - * accept requests to create new accounts. - * - * @return true if the user service is enabled. - */ - public boolean isEnabled() { - return enabled; - } - - /** - * Enables or disables the user service. If not enabled, it will not accept - * requests to create new accounts. - * - * @param enabled - * true if the user service should be enabled. - */ - public void setEnabled(boolean enabled) { - this.enabled = enabled; - JiveGlobals.setProperty("plugin.restapi.enabled", enabled ? "true" : "false"); - } - /** * Gets the http authentication mechanism. * @@ -201,9 +183,7 @@ public void setHttpAuth(String httpAuth) { * @see org.jivesoftware.util.PropertyEventListener#propertySet(java.lang.String, java.util.Map) */ public void propertySet(String property, Map params) { - if (property.equals("plugin.restapi.enabled")) { - this.enabled = Boolean.parseBoolean((String) params.get("value")); - } else if (property.equals("plugin.restapi.allowedIPs")) { + if (property.equals("plugin.restapi.allowedIPs")) { this.allowedIPs = StringUtils.stringToCollection((String) params.get("value")); } else if (property.equals("plugin.restapi.httpAuth")) { this.httpAuth = (String) params.get("value"); @@ -214,9 +194,7 @@ public void propertySet(String property, Map params) { * @see org.jivesoftware.util.PropertyEventListener#propertyDeleted(java.lang.String, java.util.Map) */ public void propertyDeleted(String property, Map params) { - if (property.equals("plugin.restapi.enabled")) { - this.enabled = false; - } else if (property.equals("plugin.restapi.allowedIPs")) { + if (property.equals("plugin.restapi.allowedIPs")) { this.allowedIPs = Collections.emptyList(); } else if (property.equals("plugin.restapi.httpAuth")) { this.httpAuth = "basic"; diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/service/UserServiceLegacy.java b/src/java/org/jivesoftware/openfire/plugin/rest/service/UserServiceLegacy.java index ea2eccf31..9fd2b55dc 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/service/UserServiceLegacy.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/service/UserServiceLegacy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022. + * Copyright (C) 2022-2026 Ignite Realtime Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -105,7 +105,7 @@ public Response userSerivceRequest() throws IOException { // type = type == null ? "image" : type; // Check that our plugin is enabled. - if (!plugin.isEnabled()) { + if (!RESTServicePlugin.ENABLED.getValue()) { LOG.warn("User service plugin is disabled: " + request.getQueryString()); replyError("UserServiceDisabled", response, out); return Response.status(200).build(); diff --git a/src/web/rest-api.jsp b/src/web/rest-api.jsp index 9427664d8..675e163ac 100644 --- a/src/web/rest-api.jsp +++ b/src/web/rest-api.jsp @@ -66,7 +66,7 @@ if (errors.size() == 0) { boolean is2Reload = "custom".equals(httpAuth) || "custom".equals(plugin.getHttpAuth()); - plugin.setEnabled(enabled); + RESTServicePlugin.ENABLED.setValue(enabled); RESTServicePlugin.SECRET.setValue(secret == null || secret.isEmpty() ? StringUtils.randomString(16) : secret); plugin.setHttpAuth(httpAuth); plugin.setAllowedIPs(StringUtils.stringToCollection(allowedIPs)); @@ -88,7 +88,7 @@ } secret = RESTServicePlugin.SECRET.getValue(); - enabled = plugin.isEnabled(); + enabled = RESTServicePlugin.ENABLED.getValue(); httpAuth = plugin.getHttpAuth(); allowedIPs = StringUtils.collectionToString(plugin.getAllowedIPs()); customAuthFilterClassName = RESTServicePlugin.CUSTOM_AUTH_FILTER.getValue(); From 1b772994681cad80e98096ead814dfe919cea6e2 Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Tue, 8 Sep 2026 17:25:42 +0200 Subject: [PATCH 05/12] Migrating property 'plugin.restapi.httpAuth' --- src/i18n/restapi_i18n.properties | 1 + src/i18n/restapi_i18n_nl.properties | 1 + .../openfire/plugin/rest/AuthFilter.java | 2 +- .../plugin/rest/RESTServicePlugin.java | 60 ++++++++++--------- .../rest/service/CustomOpenApiResource.java | 4 +- .../plugin/rest/service/JerseyWrapper.java | 14 ++--- src/web/rest-api.jsp | 55 +++++++++++++---- 7 files changed, 82 insertions(+), 55 deletions(-) diff --git a/src/i18n/restapi_i18n.properties b/src/i18n/restapi_i18n.properties index 6cf8aa5c8..8cf943630 100644 --- a/src/i18n/restapi_i18n.properties +++ b/src/i18n/restapi_i18n.properties @@ -1,5 +1,6 @@ system_property.plugin.restapi.customAuthFilter=The class name of a custom authentication filter implementation. system_property.plugin.restapi.enabled=Enables or disables the processing of REST API service requests. +system_property.plugin.restapi.httpAuth=The authentication mechanism used to authenticate REST API service requests. system_property.plugin.restapi.muc.case-insensitive-lookup.enabled=Names of MUC rooms should be node-prepped. This, however, was not guaranteed the case in some versions of Openfire and this plugin. Earlier versions of this plugin used a case-insensitive lookup to work around this. As this should be unneeded, and is quite resource intensive, this behavior has been made configurable (disabled by default). system_property.plugin.restapi.muc.room-mutex.enabled=Controls if a mutual exclusion lock is used when an API interacts with a room. system_property.plugin.restapi.secret=The value that is used to authenticate requests when using 'shared secret' authentication. diff --git a/src/i18n/restapi_i18n_nl.properties b/src/i18n/restapi_i18n_nl.properties index f47f8c058..f43c6c890 100644 --- a/src/i18n/restapi_i18n_nl.properties +++ b/src/i18n/restapi_i18n_nl.properties @@ -1,5 +1,6 @@ system_property.plugin.restapi.customAuthFilter=De klassenaam van een authenticatie-filter implementatie. system_property.plugin.restapi.enabled=Schakelt de verwerking van REST API-serviceverzoeken in of uit. +system_property.plugin.restapi.httpAuth=Het authenticatiemechanisme dat wordt gebruikt om REST API-serviceverzoeken te authenticeren. system_property.plugin.restapi.muc.case-insensitive-lookup.enabled=Namen van MUC-kamers zouden genode-prepped moeten zijn. Dit werd echter niet gegarandeerd in sommige versies van Openfire en deze plugin. Oudere versies van deze plugin gebruikten een hoofdletter-ongevoelige zoekopdracht om hier omheen te werken. Dit vergt behoorlijk wat rekenkracht en zou onnodig moeten zijn. Hierom is dit gedrag configureerbaar gemaakt (standaard-instelling: uit) system_property.plugin.restapi.muc.room-mutex.enabled=Bepaald of een MUC-kamer-specifieke mutex wordt gebruikt wanneer de API interacteert met een MUC-kamer. system_property.plugin.restapi.secret=De waarde die wordt gebruikt om verzoeken te authenticeren wanneer authenticatie met een 'gedeeld geheim' wordt gebruikt. diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java b/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java index a6454539b..a6fcdd437 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java @@ -109,7 +109,7 @@ public void filter(ContainerRequestContext containerRequest) throws IOException } // HTTP Basic Auth or Shared Secret key - if ("basic".equals(plugin.getHttpAuth())) { + if (RESTServicePlugin.AuthType.basic.equals(RESTServicePlugin.AUTH_TYPE.getValue())) { String[] usernameAndPassword = BasicAuth.decode(auth); // If username or password fail diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java b/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java index eb57a4726..7aac0c8e1 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java @@ -70,11 +70,39 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener { .setDefaultValue(false) .build(); + /** + * The authentication mechanism used to authenticate REST API service requests. + */ + public static final SystemProperty AUTH_TYPE = SystemProperty.Builder.ofType(AuthType.class) + .setPlugin("REST API") + .setKey("plugin.restapi.httpAuth") + .setDynamic(true) + .setDefaultValue(AuthType.basic) + .build(); + + /** + * The types of authentication mechanisms for REST service calls. + */ + public enum AuthType + { + /** + * Use HTTP Basic Authentication. + */ + basic, + + /** + * Use a Shared Secret. + */ + secret, + + /** + * Use a custom authentication implementation. + */ + custom + } + /** The allowed i ps. */ private Collection allowedIPs; - - /** The http auth. */ - private String httpAuth; private final Set registeredStatisticKeys = new HashSet<>(); @@ -94,9 +122,6 @@ public void initializePlugin(PluginManager manager, File pluginDirectory) registeredStatisticKeys.add(statistic.getKeyName()); } - // See if the HTTP Basic Auth is enabled or not. - httpAuth = JiveGlobals.getProperty("plugin.restapi.httpAuth", "basic"); - // Get the list of IP addresses that can use this service. An empty list // means that this filter is disabled. allowedIPs = StringUtils.stringToCollection(JiveGlobals.getProperty("plugin.restapi.allowedIPs", "")); @@ -160,33 +185,12 @@ public void setAllowedIPs(Collection allowedIPs) { this.allowedIPs = allowedIPs; } - /** - * Gets the http authentication mechanism. - * - * @return the http authentication mechanism - */ - public String getHttpAuth() { - return httpAuth; - } - - /** - * Sets the http auth. - * - * @param httpAuth the new http auth - */ - public void setHttpAuth(String httpAuth) { - this.httpAuth = httpAuth; - JiveGlobals.setProperty("plugin.restapi.httpAuth", httpAuth); - } - /* (non-Javadoc) * @see org.jivesoftware.util.PropertyEventListener#propertySet(java.lang.String, java.util.Map) */ public void propertySet(String property, Map params) { if (property.equals("plugin.restapi.allowedIPs")) { this.allowedIPs = StringUtils.stringToCollection((String) params.get("value")); - } else if (property.equals("plugin.restapi.httpAuth")) { - this.httpAuth = (String) params.get("value"); } } @@ -196,8 +200,6 @@ public void propertySet(String property, Map params) { public void propertyDeleted(String property, Map params) { if (property.equals("plugin.restapi.allowedIPs")) { this.allowedIPs = Collections.emptyList(); - } else if (property.equals("plugin.restapi.httpAuth")) { - this.httpAuth = "basic"; } } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/service/CustomOpenApiResource.java b/src/java/org/jivesoftware/openfire/plugin/rest/service/CustomOpenApiResource.java index a180d2560..a7ed30a30 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/service/CustomOpenApiResource.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/service/CustomOpenApiResource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Ignite Realtime Foundation. All rights reserved. + * Copyright (C) 2022-2026 Ignite Realtime Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,7 +76,7 @@ public CustomOpenApiResource() { openAPI.components(new Components()); final String key; - if (plugin == null || !"basic".equals(plugin.getHttpAuth())) { + if (plugin == null || !RESTServicePlugin.AuthType.basic.equals(RESTServicePlugin.AUTH_TYPE.getValue())) { key = "Secret key auth"; final SecurityScheme apiKeyScheme = new SecurityScheme(); apiKeyScheme.setDescription("Authenticate using the Secret Key as configured in the Openfire admin console."); diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/service/JerseyWrapper.java b/src/java/org/jivesoftware/openfire/plugin/rest/service/JerseyWrapper.java index e8600c385..84b3dda38 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/service/JerseyWrapper.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/service/JerseyWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022. + * Copyright (C) 2022-2026 Ignite Realtime Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,7 @@ package org.jivesoftware.openfire.plugin.rest.service; import org.glassfish.jersey.server.ResourceConfig; -import org.jivesoftware.openfire.plugin.rest.AuthFilter; -import org.jivesoftware.openfire.plugin.rest.CORSFilter; -import org.jivesoftware.openfire.plugin.rest.CustomJacksonMapperProvider; -import org.jivesoftware.openfire.plugin.rest.StatisticsFilter; +import org.jivesoftware.openfire.plugin.rest.*; import org.jivesoftware.openfire.plugin.rest.exceptions.RESTExceptionMapper; import javax.servlet.ServletConfig; @@ -33,9 +30,6 @@ */ public class JerseyWrapper extends ResourceConfig { - /** The Constant REST_AUTH_TYPE */ - private static final String REST_AUTH_TYPE = "plugin.restapi.httpAuth"; - /** The Constant SERVLET_URL. */ public static final String SERVLET_URL = "restapi/*"; @@ -68,11 +62,11 @@ public String loadAuthenticationFilter() { // Check if custom AuthFilter is available String customAuthFilterClassName = RESTServicePlugin.CUSTOM_AUTH_FILTER.getValue(); - String restAuthType = JiveGlobals.getProperty(REST_AUTH_TYPE); + RESTServicePlugin.AuthType restAuthType = RESTServicePlugin.AUTH_TYPE.getValue(); Class pickedAuthFilter = AuthFilter.class; try { - if(customAuthFilterClassName != null && "custom".equals(restAuthType)) { + if(customAuthFilterClassName != null && RESTServicePlugin.AuthType.custom.equals(restAuthType)) { pickedAuthFilter = Class.forName(customAuthFilterClassName, false, JerseyWrapper.class.getClassLoader()); loadingStatusMessage = null; } diff --git a/src/web/rest-api.jsp b/src/web/rest-api.jsp index 675e163ac..9b787fac3 100644 --- a/src/web/rest-api.jsp +++ b/src/web/rest-api.jsp @@ -1,6 +1,6 @@ <%-- /* - * Copyright (c) 2022. + * Copyright (C) 2022-2026 Ignite Realtime Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ boolean success = request.getParameter("success") != null; String secret = ParamUtils.getParameter(request, "secret"); boolean enabled = ParamUtils.getBooleanParameter(request, "enabled"); - String httpAuth = ParamUtils.getParameter(request, "authtype"); + String authTypeString = ParamUtils.getParameter(request, "authtype"); String allowedIPs = ParamUtils.getParameter(request, "allowedIPs"); String customAuthFilterClassName = ParamUtils.getParameter(request, "customAuthFilterClassName"); boolean loggingEnabled = ParamUtils.getBooleanParameter(request, "loggingEnabled"); @@ -54,21 +54,29 @@ .getPluginByName("REST API").orElse(null); // Handle a save - Map errors = new HashMap(); + Map errors = new HashMap<>(); + + RESTServicePlugin.AuthType authType = null; if (save) { - if("custom".equals(httpAuth)) { + try { + authType = RESTServicePlugin.AuthType.valueOf(authTypeString); + } catch (Exception e) { + errors.put("authtype", "invalid value"); + } + + if (RESTServicePlugin.AuthType.custom.equals(authType)) { loadingStatus = plugin.loadAuthenticationFilter(customAuthFilterClassName); } if (loadingStatus != null) { errors.put("loadingStatus", loadingStatus); } - - if (errors.size() == 0) { - - boolean is2Reload = "custom".equals(httpAuth) || "custom".equals(plugin.getHttpAuth()); + + if (errors.isEmpty()) + { + boolean is2Reload = RESTServicePlugin.AuthType.custom.equals(authType) || RESTServicePlugin.AuthType.custom.equals(RESTServicePlugin.AUTH_TYPE.getValue()); RESTServicePlugin.ENABLED.setValue(enabled); RESTServicePlugin.SECRET.setValue(secret == null || secret.isEmpty() ? StringUtils.randomString(16) : secret); - plugin.setHttpAuth(httpAuth); + RESTServicePlugin.AUTH_TYPE.setValue(authType); plugin.setAllowedIPs(StringUtils.stringToCollection(allowedIPs)); RESTServicePlugin.CUSTOM_AUTH_FILTER.setValue(customAuthFilterClassName); RESTServicePlugin.SERVICE_LOGGING_ENABLED.setValue(loggingEnabled); @@ -89,7 +97,7 @@ secret = RESTServicePlugin.SECRET.getValue(); enabled = RESTServicePlugin.ENABLED.getValue(); - httpAuth = plugin.getHttpAuth(); + authType = RESTServicePlugin.AUTH_TYPE.getValue(); allowedIPs = StringUtils.collectionToString(plugin.getAllowedIPs()); customAuthFilterClassName = RESTServicePlugin.CUSTOM_AUTH_FILTER.getValue(); loggingEnabled = RESTServicePlugin.SERVICE_LOGGING_ENABLED.getValue(); @@ -144,6 +152,27 @@ <% } %> + <% + if (errors.get("authtype") != null) { + %> +
+ + + + + + + +
Unrecognized authentication type. + +
+
+
+ <% + } + %> +
@@ -170,12 +199,12 @@
> + id="http_basic_auth" <%=(RESTServicePlugin.AuthType.basic.equals(authType) ? "checked" : "")%>>
> + id="secretKeyAuth" <%=(RESTServicePlugin.AuthType.secret.equals(authType) ? "checked" : "")%>>
@@ -185,7 +214,7 @@ id="text_secret">
> + id="customFilterAuth" <%=(RESTServicePlugin.AuthType.custom.equals(authType) ? "checked" : "")%>> From aab5935211df790a5710d0613631998dc04e7ee1 Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Tue, 8 Sep 2026 19:27:25 +0200 Subject: [PATCH 06/12] Migrating property 'plugin.restapi.allowedIPs' --- src/i18n/restapi_i18n.properties | 1 + src/i18n/restapi_i18n_nl.properties | 1 + .../openfire/plugin/rest/AuthFilter.java | 4 +- .../plugin/rest/RESTServicePlugin.java | 42 +++++-------------- .../rest/service/UserServiceLegacy.java | 4 +- src/web/rest-api.jsp | 4 +- 6 files changed, 18 insertions(+), 38 deletions(-) diff --git a/src/i18n/restapi_i18n.properties b/src/i18n/restapi_i18n.properties index 8cf943630..2e84601d5 100644 --- a/src/i18n/restapi_i18n.properties +++ b/src/i18n/restapi_i18n.properties @@ -1,3 +1,4 @@ +system_property.plugin.restapi.allowedIPs=List of IP addresses that are allowed to access the REST API services. An empty list will allow all IP addresses. system_property.plugin.restapi.customAuthFilter=The class name of a custom authentication filter implementation. system_property.plugin.restapi.enabled=Enables or disables the processing of REST API service requests. system_property.plugin.restapi.httpAuth=The authentication mechanism used to authenticate REST API service requests. diff --git a/src/i18n/restapi_i18n_nl.properties b/src/i18n/restapi_i18n_nl.properties index f43c6c890..ea8c0d304 100644 --- a/src/i18n/restapi_i18n_nl.properties +++ b/src/i18n/restapi_i18n_nl.properties @@ -1,3 +1,4 @@ +system_property.plugin.restapi.allowedIPs=Een lijst van IP-adressen die toegang hebben tot de REST API-services. Een lege lijst staat alle IP-adressen toe. system_property.plugin.restapi.customAuthFilter=De klassenaam van een authenticatie-filter implementatie. system_property.plugin.restapi.enabled=Schakelt de verwerking van REST API-serviceverzoeken in of uit. system_property.plugin.restapi.httpAuth=Het authenticatiemechanisme dat wordt gebruikt om REST API-serviceverzoeken te authenticeren. diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java b/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java index a6fcdd437..a3a9084f0 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java @@ -83,7 +83,7 @@ public void filter(ContainerRequestContext containerRequest) throws IOException return; } - if (!plugin.getAllowedIPs().isEmpty()) { + if (!RESTServicePlugin.ALLOWED_IPS.getValue().isEmpty()) { // Get client's IP address String ipAddress = httpRequest.getHeader("x-forwarded-for"); if (ipAddress == null) { @@ -95,7 +95,7 @@ public void filter(ContainerRequestContext containerRequest) throws IOException } } } - if (!plugin.getAllowedIPs().contains(ipAddress)) { + if (!RESTServicePlugin.ALLOWED_IPS.getValue().contains(ipAddress)) { LOG.warn("REST API rejected service for IP address: " + ipAddress); throw new WebApplicationException(Status.UNAUTHORIZED); } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java b/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java index 7aac0c8e1..4ece28a35 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java @@ -80,6 +80,16 @@ public class RESTServicePlugin implements Plugin, PropertyEventListener { .setDefaultValue(AuthType.basic) .build(); + /** + * List of IP addresses that are allowed to access the REST API services. + */ + public static final SystemProperty> ALLOWED_IPS = SystemProperty.Builder.ofType(Set.class) + .setPlugin("REST API") + .setKey("plugin.restapi.allowedIPs") + .setDefaultValue(Collections.emptySet()) + .setDynamic(true) + .buildSet(String.class); + /** * The types of authentication mechanisms for REST service calls. */ @@ -100,9 +110,6 @@ public enum AuthType */ custom } - - /** The allowed i ps. */ - private Collection allowedIPs; private final Set registeredStatisticKeys = new HashSet<>(); @@ -122,10 +129,6 @@ public void initializePlugin(PluginManager manager, File pluginDirectory) registeredStatisticKeys.add(statistic.getKeyName()); } - // Get the list of IP addresses that can use this service. An empty list - // means that this filter is disabled. - allowedIPs = StringUtils.stringToCollection(JiveGlobals.getProperty("plugin.restapi.allowedIPs", "")); - // Listen to system property events PropertyEventDispatcher.addListener(this); @@ -165,42 +168,17 @@ public String getLoadingStatusMessage() { public String loadAuthenticationFilter(String customAuthFilterClassName) { return JerseyWrapper.tryLoadingAuthenticationFilter(customAuthFilterClassName); } - - /** - * Gets the allowed i ps. - * - * @return the allowed i ps - */ - public Collection getAllowedIPs() { - return allowedIPs; - } - - /** - * Sets the allowed i ps. - * - * @param allowedIPs the new allowed i ps - */ - public void setAllowedIPs(Collection allowedIPs) { - JiveGlobals.setProperty("plugin.restapi.allowedIPs", StringUtils.collectionToString(allowedIPs)); - this.allowedIPs = allowedIPs; - } /* (non-Javadoc) * @see org.jivesoftware.util.PropertyEventListener#propertySet(java.lang.String, java.util.Map) */ public void propertySet(String property, Map params) { - if (property.equals("plugin.restapi.allowedIPs")) { - this.allowedIPs = StringUtils.stringToCollection((String) params.get("value")); - } } /* (non-Javadoc) * @see org.jivesoftware.util.PropertyEventListener#propertyDeleted(java.lang.String, java.util.Map) */ public void propertyDeleted(String property, Map params) { - if (property.equals("plugin.restapi.allowedIPs")) { - this.allowedIPs = Collections.emptyList(); - } } /* (non-Javadoc) diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/service/UserServiceLegacy.java b/src/java/org/jivesoftware/openfire/plugin/rest/service/UserServiceLegacy.java index 9fd2b55dc..4ce5db4e5 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/service/UserServiceLegacy.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/service/UserServiceLegacy.java @@ -73,7 +73,7 @@ public Response userSerivceRequest() throws IOException { // Printwriter for writing out responses to browser PrintWriter out = response.getWriter(); - if (!plugin.getAllowedIPs().isEmpty()) { + if (!RESTServicePlugin.ALLOWED_IPS.getValue().isEmpty()) { // Get client's IP address String ipAddress = request.getHeader("x-forwarded-for"); if (ipAddress == null) { @@ -85,7 +85,7 @@ public Response userSerivceRequest() throws IOException { } } } - if (!plugin.getAllowedIPs().contains(ipAddress)) { + if (!RESTServicePlugin.ALLOWED_IPS.getValue().contains(ipAddress)) { LOG.warn("User service rejected service to IP address: " + ipAddress); replyError("RequestNotAuthorised", response, out); return Response.status(200).build(); diff --git a/src/web/rest-api.jsp b/src/web/rest-api.jsp index 9b787fac3..4826dfd41 100644 --- a/src/web/rest-api.jsp +++ b/src/web/rest-api.jsp @@ -77,7 +77,7 @@ RESTServicePlugin.ENABLED.setValue(enabled); RESTServicePlugin.SECRET.setValue(secret == null || secret.isEmpty() ? StringUtils.randomString(16) : secret); RESTServicePlugin.AUTH_TYPE.setValue(authType); - plugin.setAllowedIPs(StringUtils.stringToCollection(allowedIPs)); + RESTServicePlugin.ALLOWED_IPS.setValue(new HashSet<>(StringUtils.stringToCollection(allowedIPs))); RESTServicePlugin.CUSTOM_AUTH_FILTER.setValue(customAuthFilterClassName); RESTServicePlugin.SERVICE_LOGGING_ENABLED.setValue(loggingEnabled); @@ -98,7 +98,7 @@ secret = RESTServicePlugin.SECRET.getValue(); enabled = RESTServicePlugin.ENABLED.getValue(); authType = RESTServicePlugin.AUTH_TYPE.getValue(); - allowedIPs = StringUtils.collectionToString(plugin.getAllowedIPs()); + allowedIPs = StringUtils.collectionToString(RESTServicePlugin.ALLOWED_IPS.getValue()); customAuthFilterClassName = RESTServicePlugin.CUSTOM_AUTH_FILTER.getValue(); loggingEnabled = RESTServicePlugin.SERVICE_LOGGING_ENABLED.getValue(); %> From cd8b58e45bfc5bd7d41b31badae39f7261c16afd Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Tue, 8 Sep 2026 19:28:29 +0200 Subject: [PATCH 07/12] Removed unused PropertyEventListener definition --- .../plugin/rest/RESTServicePlugin.java | 33 +------------------ 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java b/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java index 4ece28a35..8dced564d 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/RESTServicePlugin.java @@ -29,7 +29,7 @@ /** * The Class RESTServicePlugin. */ -public class RESTServicePlugin implements Plugin, PropertyEventListener { +public class RESTServicePlugin implements Plugin { /** * The value that is used to authenticate requests when using 'shared secret' authentication. @@ -129,9 +129,6 @@ public void initializePlugin(PluginManager manager, File pluginDirectory) registeredStatisticKeys.add(statistic.getKeyName()); } - // Listen to system property events - PropertyEventDispatcher.addListener(this); - // Exclude this servlet from requering the user to login AuthCheckFilter.addExclude(JerseyWrapper.SERVLET_URL); } @@ -149,8 +146,6 @@ public void destroyPlugin() { // Release the excluded URL AuthCheckFilter.removeExclude(JerseyWrapper.SERVLET_URL); - // Stop listening to system property events - PropertyEventDispatcher.removeListener(this); } /** @@ -168,30 +163,4 @@ public String getLoadingStatusMessage() { public String loadAuthenticationFilter(String customAuthFilterClassName) { return JerseyWrapper.tryLoadingAuthenticationFilter(customAuthFilterClassName); } - - /* (non-Javadoc) - * @see org.jivesoftware.util.PropertyEventListener#propertySet(java.lang.String, java.util.Map) - */ - public void propertySet(String property, Map params) { - } - - /* (non-Javadoc) - * @see org.jivesoftware.util.PropertyEventListener#propertyDeleted(java.lang.String, java.util.Map) - */ - public void propertyDeleted(String property, Map params) { - } - - /* (non-Javadoc) - * @see org.jivesoftware.util.PropertyEventListener#xmlPropertySet(java.lang.String, java.util.Map) - */ - public void xmlPropertySet(String property, Map params) { - // Do nothing - } - - /* (non-Javadoc) - * @see org.jivesoftware.util.PropertyEventListener#xmlPropertyDeleted(java.lang.String, java.util.Map) - */ - public void xmlPropertyDeleted(String property, Map params) { - // Do nothing - } } From 1798a93347a01635c18cb4a17c74f33f832312ec Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Tue, 8 Sep 2026 22:20:57 +0200 Subject: [PATCH 08/12] fixes #244: Prevent REST API plugin from exposing its own configuration The REST API plugin's system properties endpoints (GET/POST/PUT/DELETE /plugins/restapi/v1/system/properties) allowed reading and writing any Openfire system property, including the properties that control the REST API plugin's own behavior. This allowed a caller with REST API access to durably reconfigure the plugin's own authentication, amongst others. --- changelog.html | 1 + .../rest/controller/SystemController.java | 53 ++++++- .../plugin/rest/service/SystemService.java | 5 +- test/system.hurl | 143 ++++++++++++++++++ 4 files changed, 198 insertions(+), 4 deletions(-) diff --git a/changelog.html b/changelog.html index 697074cfc..09b741d68 100644 --- a/changelog.html +++ b/changelog.html @@ -47,6 +47,7 @@

1.12.1 (to be determined)

  • [#251] - Enable JUnit 5 tests
  • +
  • [#244] - Prevent REST API plugin from exposing its own configuration (including authentication) via its own endpoints
  • [#242] - Fix individual System Property GETs returning HTTP/404
  • [#213] - Improve setting a subject in a chat room
  • [#217] - Add Hurl e2e tests, and CI to run them
  • diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/controller/SystemController.java b/src/java/org/jivesoftware/openfire/plugin/rest/controller/SystemController.java index 24049ea36..944900fc9 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/controller/SystemController.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/controller/SystemController.java @@ -81,6 +81,11 @@ public SystemProperties getSystemProperties() { final List compoundProperties = systemProperties.stream().map(p -> new org.jivesoftware.openfire.plugin.rest.entity.SystemProperty(p.getKey(), p.getValueAsSaved())).collect(Collectors.toList()); // Now add any missing JiveGlobals properties JiveGlobals.getPropertyNames().stream().filter(key -> !systemPropertyKeys.contains(key)).forEach(key -> compoundProperties.add(new org.jivesoftware.openfire.plugin.rest.entity.SystemProperty(key, JiveGlobals.getProperty(key)))); + + // Ensure that we're not exposing any 'forbidden' properties. + final Set forbiddenPropertyKeys = getForbiddenPropertyKeys(); + compoundProperties.removeIf(systemProperty -> forbiddenPropertyKeys.contains(systemProperty.getKey())); + // And sort by key compoundProperties.sort(Comparator.comparing(org.jivesoftware.openfire.plugin.rest.entity.SystemProperty::getKey)); @@ -97,19 +102,30 @@ public SystemProperties getSystemProperties() { * @throws ServiceException the service exception */ public org.jivesoftware.openfire.plugin.rest.entity.SystemProperty getSystemProperty(String propertyKey) throws ServiceException { + // Ensure that we're not exposing any 'forbidden' properties. + final Set forbiddenPropertyKeys = getForbiddenPropertyKeys(); + final Optional systemProperty = SystemProperty.getProperty(propertyKey); if (systemProperty.isPresent()) { + if (forbiddenPropertyKeys.contains(systemProperty.get().getKey())) { + // Ensure that we're not exposing any 'forbidden' properties. + throw new ServiceException("Access to property is forbidden", propertyKey, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN); + } // There's guaranteed to be a system property - return a value (even null), no matter what. return new org.jivesoftware.openfire.plugin.rest.entity.SystemProperty(propertyKey, systemProperty.get().getValueAsSaved()); } // No system property found. Check JiveGlobals. This cannot distinguish between a property that is not set and a property that is set to null. + if (forbiddenPropertyKeys.contains(propertyKey)) { + // Ensure that we're not exposing any 'forbidden' properties. + throw new ServiceException("Access to property is forbidden", propertyKey, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN); + } + final String propertyValue = JiveGlobals.getProperty(propertyKey); if (propertyValue != null) { return new org.jivesoftware.openfire.plugin.rest.entity.SystemProperty(propertyKey, propertyValue); } else { - throw new ServiceException("Could not find property", propertyKey, ExceptionType.PROPERTY_NOT_FOUND, - Response.Status.NOT_FOUND); + throw new ServiceException("Could not find property", propertyKey, ExceptionType.PROPERTY_NOT_FOUND, Response.Status.NOT_FOUND); } } @@ -118,7 +134,12 @@ public org.jivesoftware.openfire.plugin.rest.entity.SystemProperty getSystemProp * * @param systemProperty the system property */ - public void createSystemProperty(org.jivesoftware.openfire.plugin.rest.entity.SystemProperty systemProperty) { + public void createSystemProperty(org.jivesoftware.openfire.plugin.rest.entity.SystemProperty systemProperty) throws ServiceException + { + // Ensure that we're not exposing any 'forbidden' properties. + if (getForbiddenPropertyKeys().contains(systemProperty.getKey())) { + throw new ServiceException("Could not create property", systemProperty.getKey(), ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN); + } JiveGlobals.setProperty(systemProperty.getKey(), systemProperty.getValue()); } @@ -129,6 +150,11 @@ public void createSystemProperty(org.jivesoftware.openfire.plugin.rest.entity.Sy * @throws ServiceException the service exception */ public void deleteSystemProperty(String propertyKey) throws ServiceException { + // Ensure that we're not exposing any 'forbidden' properties. + if (getForbiddenPropertyKeys().contains(propertyKey)) { + throw new ServiceException("Could not delete property", propertyKey, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN); + } + if(JiveGlobals.getProperty(propertyKey) != null) { JiveGlobals.deleteProperty(propertyKey); } else { @@ -145,6 +171,10 @@ public void deleteSystemProperty(String propertyKey) throws ServiceException { * @throws ServiceException the service exception */ public void updateSystemProperty(String propertyKey, org.jivesoftware.openfire.plugin.rest.entity.SystemProperty systemProperty) throws ServiceException { + // Ensure that we're not exposing any 'forbidden' properties. + if (getForbiddenPropertyKeys().contains(propertyKey)) { + throw new ServiceException("Could not update property", propertyKey, ExceptionType.NOT_ALLOWED, Response.Status.FORBIDDEN); + } if(JiveGlobals.getProperty(propertyKey) != null) { if(systemProperty.getKey().equals(propertyKey)) { JiveGlobals.setProperty(propertyKey, systemProperty.getValue()); @@ -281,4 +311,21 @@ public boolean areConnectionListenersStarted() { return true; } + + /** + * Returns a set of system property keys that are not allowed to be modified via the REST API. + * + * @return a set of system property keys (never null, possibly empty). + */ + public static Set getForbiddenPropertyKeys() + { + final String pluginName = RESTServicePlugin.ENABLED.getPlugin(); + return org.jivesoftware.util.SystemProperty.getProperties().stream() + + // Do not allow modifications of the configuration of this plugin itself. See https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 + .filter(p -> pluginName.equals(p.getPlugin())) // This works only because all properties used by the plugin are SystemProperty instances (as opposed to using JiveGlobals directly). + + .map(org.jivesoftware.util.SystemProperty::getKey) + .collect(Collectors.toSet()); + } } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/service/SystemService.java b/src/java/org/jivesoftware/openfire/plugin/rest/service/SystemService.java index 2851f89a5..cc4d65788 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/service/SystemService.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/service/SystemService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022. + * Copyright (c) 2022-2026 Ignite Realtime Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,6 +71,7 @@ public SystemProperty getSystemProperty( description = "Create a new Openfire system property. Will overwrite a pre-existing system property that uses the same name.", responses = { @ApiResponse(responseCode = "201", description = "The system property is created."), + @ApiResponse(responseCode = "403", description = "Prohibited to create this system property."), }) @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response createSystemProperty( @@ -88,6 +89,7 @@ public Response createSystemProperty( responses = { @ApiResponse(responseCode = "200", description = "The system property is updated."), @ApiResponse(responseCode = "400", description = "The provided system property does not match the name in the URL."), + @ApiResponse(responseCode = "403", description = "Prohibited to update this system property."), @ApiResponse(responseCode = "404", description = "The system property could not be found.") }) @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) @@ -106,6 +108,7 @@ public Response updateSystemProperty( description = "Removes an existing Openfire system property.", responses = { @ApiResponse(responseCode = "200", description = "The system property is deleted."), + @ApiResponse(responseCode = "403", description = "Prohibited to delete this system property."), @ApiResponse(responseCode = "404", description = "The system property could not be found.") }) public Response deleteSystemProperty( diff --git a/test/system.hurl b/test/system.hurl index 96354db1a..7ea2ba637 100644 --- a/test/system.hurl +++ b/test/system.hurl @@ -103,3 +103,146 @@ HTTP 400 DELETE http://localhost:9090/plugins/restapi/v1/system/properties/test.key Authorization: {{authkey}} HTTP 200 + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +GET http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.enabled +Authorization: {{authkey}} +HTTP 403 + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +PUT http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.enabled +Authorization: {{authkey}} +Content-Type: application/xml +``` + + +``` +HTTP 403 + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +DELETE http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.enabled +Authorization: {{authkey}} +HTTP 403 + + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +GET http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.httpAuth +Authorization: {{authkey}} +HTTP 403 + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +PUT http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.httpAuth +Authorization: {{authkey}} +Content-Type: application/xml +``` + + +``` +HTTP 403 + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +DELETE http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.httpAuth +Authorization: {{authkey}} +HTTP 403 + + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +GET http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.customAuthFilter +Authorization: {{authkey}} +HTTP 403 + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +PUT http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.customAuthFilter +Authorization: {{authkey}} +Content-Type: application/xml +``` + + +``` +HTTP 403 + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +DELETE http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.customAuthFilter +Authorization: {{authkey}} +HTTP 403 + + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +GET http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.secret +Authorization: {{authkey}} +HTTP 403 + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +PUT http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.secret +Authorization: {{authkey}} +Content-Type: application/xml +``` + + +``` +HTTP 403 + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +DELETE http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.secret +Authorization: {{authkey}} +HTTP 403 + + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +GET http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.serviceLoggingEnabled +Authorization: {{authkey}} +HTTP 403 + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +PUT http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.serviceLoggingEnabled +Authorization: {{authkey}} +Content-Type: application/xml +``` + + +``` +HTTP 403 + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +DELETE http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.serviceLoggingEnabled +Authorization: {{authkey}} +HTTP 403 + + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +GET http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.allowedIPs +Authorization: {{authkey}} +HTTP 403 + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +PUT http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.allowedIPs +Authorization: {{authkey}} +Content-Type: application/xml +``` + + +``` +HTTP 403 + +# https://github.com/igniterealtime/openfire-restAPI-plugin/issues/244 +# The REST API plugin should not allow users to access properties used to control the plugin's functionality. +DELETE http://localhost:9090/plugins/restapi/v1/system/properties/plugin.restapi.allowedIPs +Authorization: {{authkey}} +HTTP 403 From 952e73653f610f0922a7fd91d4c305b335f72d84 Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Wed, 9 Sep 2026 16:31:13 +0200 Subject: [PATCH 09/12] Fix HTML input label reference --- src/web/rest-api.jsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/rest-api.jsp b/src/web/rest-api.jsp index 4826dfd41..4d0ded260 100644 --- a/src/web/rest-api.jsp +++ b/src/web/rest-api.jsp @@ -215,7 +215,7 @@
    > -