diff --git a/changelog.html b/changelog.html index 82d83c4b4..915514591 100644 --- a/changelog.html +++ b/changelog.html @@ -50,6 +50,7 @@

  • [#259] - Prevent system property requests from affecting properties other than the one requested
  • [#256] - Record configuration changes in audit log
  • [#251] - Enable JUnit 5 tests
  • +
  • [#250] - Migrated the admin console page to JSTL, made its content translatable, and added CSRF protection
  • [#249] - Only trust forwarded headers (such as X-Forwarded-For) from trusted reverse proxies
  • [#248] - Do not expose properties that are encrypted or otherwise sensitive.
  • [#246] - Require custom authenticator plugin to be annotated as an authenticator
  • diff --git a/plugin.xml b/plugin.xml index 4200247df..57b145ec1 100644 --- a/plugin.xml +++ b/plugin.xml @@ -11,8 +11,8 @@ - + diff --git a/src/i18n/restapi_i18n.properties b/src/i18n/restapi_i18n.properties index 2e84601d5..844510f7b 100644 --- a/src/i18n/restapi_i18n.properties +++ b/src/i18n/restapi_i18n.properties @@ -25,3 +25,43 @@ stat.restapi_responses.server_error.units=Responses stat.restapi_responses.other.name=REST API unknown responses stat.restapi_responses.other.desc=The amount of HTTP responses that had an unrecognized status code. stat.restapi_responses.other.units=Responses + +restapi.admin.item.settings.name=REST API +restapi.admin.item.settings.description=Click to manage the service that allows to configure the Openfire over a RESTful API + +restapi.settings.title=REST API Properties +restapi.settings.info=Use the form below to enable or disable the REST API and configure the authentication. +restapi.settings.saved=REST API properties edited successfully. +restapi.settings.save=Save Settings +restapi.settings.documentation=You can find detailed documentation of the Openfire REST API here: {0}REST API Documentation (opens in new tab){1} +restapi.settings.error.csrf=The settings could not be saved, as the request could not be verified (CSRF failure). Please try again. +restapi.settings.error.authtype=Unrecognized authentication type. +restapi.settings.error.generic=An unexpected error occurred. +restapi.settings.warning.spoofable=Access is restricted to specific IP addresses, but the admin console is configured to determine the client address from forwarded HTTP headers (such as X-Forwarded-For), without a list of trusted proxies. Any client can bypass the IP address check by sending such a header. Configure the addresses of your reverse proxies as trusted proxies on the {0}Admin Console Access{1} page. +restapi.settings.service.title=REST API +restapi.settings.service.info=Enable or disable the processing of REST API requests. +restapi.settings.service.enabled=Enabled +restapi.settings.service.enabled.info=REST API requests will be processed. +restapi.settings.service.disabled=Disabled +restapi.settings.service.disabled.info=REST API requests will be ignored. +restapi.settings.auth.title=Authentication +restapi.settings.auth.info=The REST API can be secured with HTTP basic authentication, with a shared secret key defined below, or with a custom authentication filter. +restapi.settings.auth.basic=HTTP basic auth +restapi.settings.auth.basic.info=REST API authentication with Openfire admin account. +restapi.settings.auth.secret=Secret key auth +restapi.settings.auth.secret.info=REST API authentication over specified secret key. +restapi.settings.auth.secret.label=Secret key: +restapi.settings.auth.custom=Custom authentication filter classname +restapi.settings.auth.custom.info=REST API authentication delegates to a custom filter implemented in some other plugin. +restapi.settings.auth.custom.label=Filter classname: +restapi.settings.auth.custom.reload-note=Note: changing back and forth from custom authentication filter forces the REST API plugin reloading. +restapi.settings.allowedips.title=Allowed IP Addresses +restapi.settings.allowedips.info=For extra security you can specify the list of IP addresses that are allowed to use this service. An empty list means that the service can be accessed from any location. Addresses are delimited by commas. +restapi.settings.allowedips.label=Allowed IP Addresses: +restapi.settings.allowedips.proxy-note=Note: when the REST API is accessed through a reverse proxy, configure Openfire to use the forwarded client address, and to trust only your proxies, on the {0}Admin Console Access{1} page. +restapi.settings.logging.title=Additional Logging +restapi.settings.logging.info=When enabled, the operations that are performed through the REST API (such as the creation of a user or chat room) are recorded in the Openfire log file, at the INFO level. This can help to troubleshoot integrations, but increases the amount of logging. +restapi.settings.logging.enabled=Enabled +restapi.settings.logging.enabled.info=Logging enabled. +restapi.settings.logging.disabled=Disabled +restapi.settings.logging.disabled.info=Logging disabled. diff --git a/src/i18n/restapi_i18n_nl.properties b/src/i18n/restapi_i18n_nl.properties index ea8c0d304..f9cb74fe1 100644 --- a/src/i18n/restapi_i18n_nl.properties +++ b/src/i18n/restapi_i18n_nl.properties @@ -25,3 +25,43 @@ stat.restapi_responses.server_error.units=Antwoorden stat.restapi_responses.other.name=REST API onbekende antwoorden stat.restapi_responses.other.desc=Het aantal HTTP antwoorden met een onbekende status code. stat.restapi_responses.other.units=Antwoorden + +restapi.admin.item.settings.name=REST API +restapi.admin.item.settings.description=Klik om de dienst te beheren waarmee Openfire via een RESTful API geconfigureerd kan worden + +restapi.settings.title=REST API-eigenschappen +restapi.settings.info=Gebruik het onderstaande formulier om de REST API in of uit te schakelen en om de authenticatie te configureren. +restapi.settings.saved=De REST API-eigenschappen zijn succesvol gewijzigd. +restapi.settings.save=Instellingen opslaan +restapi.settings.documentation=Gedetailleerde documentatie van de Openfire REST API is hier te vinden: {0}REST API-documentatie (opent in een nieuw tabblad){1} +restapi.settings.error.csrf=De instellingen konden niet worden opgeslagen, omdat het verzoek niet geverifieerd kon worden (CSRF-fout). Probeer het opnieuw. +restapi.settings.error.authtype=Onbekend authenticatietype. +restapi.settings.error.generic=Er is een onverwachte fout opgetreden. +restapi.settings.warning.spoofable=De toegang is beperkt tot specifieke IP-adressen, maar de beheerconsole is zo geconfigureerd dat het adres van de client wordt bepaald aan de hand van doorgestuurde HTTP-headers (zoals X-Forwarded-For), zonder een lijst van vertrouwde proxies. Iedere client kan de controle op IP-adressen omzeilen door zo een header mee te sturen. Configureer de adressen van uw reverse proxies als vertrouwde proxies op de pagina {0}Toegang tot de beheerconsole{1}. +restapi.settings.service.title=REST API +restapi.settings.service.info=Schakel de verwerking van REST API-verzoeken in of uit. +restapi.settings.service.enabled=Ingeschakeld +restapi.settings.service.enabled.info=REST API-verzoeken worden verwerkt. +restapi.settings.service.disabled=Uitgeschakeld +restapi.settings.service.disabled.info=REST API-verzoeken worden genegeerd. +restapi.settings.auth.title=Authenticatie +restapi.settings.auth.info=De REST API kan worden beveiligd met HTTP basic-authenticatie, met een hieronder gedefinieerde gedeelde geheime sleutel, of met een eigen authenticatiefilter. +restapi.settings.auth.basic=HTTP basic-authenticatie +restapi.settings.auth.basic.info=REST API-authenticatie met een Openfire-beheerdersaccount. +restapi.settings.auth.secret=Authenticatie met geheime sleutel +restapi.settings.auth.secret.info=REST API-authenticatie met de opgegeven geheime sleutel. +restapi.settings.auth.secret.label=Geheime sleutel: +restapi.settings.auth.custom=Klassenaam van eigen authenticatiefilter +restapi.settings.auth.custom.info=REST API-authenticatie wordt overgelaten aan een eigen filter, dat in een andere plugin is ge\u00EFmplementeerd. +restapi.settings.auth.custom.label=Klassenaam van filter: +restapi.settings.auth.custom.reload-note=Let op: het overschakelen naar of van een eigen authenticatiefilter zorgt ervoor dat de REST API-plugin opnieuw wordt geladen. +restapi.settings.allowedips.title=Toegestane IP-adressen +restapi.settings.allowedips.info=Voor extra beveiliging kunt u een lijst opgeven van IP-adressen die deze dienst mogen gebruiken. Een lege lijst betekent dat de dienst vanaf elke locatie benaderd kan worden. Adressen worden gescheiden door komma's. +restapi.settings.allowedips.label=Toegestane IP-adressen: +restapi.settings.allowedips.proxy-note=Let op: wanneer de REST API via een reverse proxy wordt benaderd, configureer Openfire dan om het doorgestuurde adres van de client te gebruiken, en om alleen uw eigen proxies te vertrouwen, op de pagina {0}Toegang tot de beheerconsole{1}. +restapi.settings.logging.title=Aanvullende logging +restapi.settings.logging.info=Indien ingeschakeld worden de bewerkingen die via de REST API worden uitgevoerd (zoals het aanmaken van een gebruiker of chatruimte) vastgelegd in het logbestand van Openfire, op INFO-niveau. Dit kan helpen bij het oplossen van problemen met koppelingen, maar vergroot de hoeveelheid logging. +restapi.settings.logging.enabled=Ingeschakeld +restapi.settings.logging.enabled.info=Logging ingeschakeld. +restapi.settings.logging.disabled=Uitgeschakeld +restapi.settings.logging.disabled.info=Logging uitgeschakeld. diff --git a/src/web/images/error-16x16.gif b/src/web/images/error-16x16.gif deleted file mode 100644 index 379f50197..000000000 Binary files a/src/web/images/error-16x16.gif and /dev/null differ diff --git a/src/web/images/success-16x16.gif b/src/web/images/success-16x16.gif deleted file mode 100644 index 93e815328..000000000 Binary files a/src/web/images/success-16x16.gif and /dev/null differ diff --git a/src/web/images/warning-16x16.gif b/src/web/images/warning-16x16.gif deleted file mode 100644 index 15ba374c5..000000000 Binary files a/src/web/images/warning-16x16.gif and /dev/null differ diff --git a/src/web/rest-api.jsp b/src/web/rest-api.jsp index 9f0d09095..b8a6e4bcf 100644 --- a/src/web/rest-api.jsp +++ b/src/web/rest-api.jsp @@ -15,65 +15,62 @@ * limitations under the License. */ --%> - <%@ page contentType="text/html; charset=UTF-8" %> -<%@ page - import="java.util.*, - org.jivesoftware.openfire.XMPPServer, - org.jivesoftware.util.*,org.jivesoftware.openfire.plugin.rest.RESTServicePlugin, - org.jivesoftware.openfire.container.PluginManager" - errorPage="error.jsp"%> -<%@ page import="org.jivesoftware.openfire.container.PluginMetadataHelper" %> - -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@ page import="org.jivesoftware.openfire.container.PluginManager" %> +<%@ page import="org.jivesoftware.openfire.plugin.rest.RESTServicePlugin" %> +<%@ page import="org.jivesoftware.util.CookieUtils" %> +<%@ page import="org.jivesoftware.util.ParamUtils" %> +<%@ page import="org.jivesoftware.util.StringUtils" %> +<%@ page import="java.util.HashMap" %> +<%@ page import="java.util.HashSet" %> +<%@ page import="java.util.Map" %> +<%@ page errorPage="error.jsp" %> -<%-- Define Administration Bean --%> - - -<% - admin.init(request, response, session, application, out); -%> +<%@ taglib uri="admin" prefix="admin" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> + <% - // Get parameters - boolean save = request.getParameter("save") != null; - boolean success = request.getParameter("success") != null; - String secret = ParamUtils.getParameter(request, "secret"); - boolean enabled = ParamUtils.getBooleanParameter(request, "enabled"); - String authTypeString = ParamUtils.getParameter(request, "authtype"); - String allowedIPs = ParamUtils.getParameter(request, "allowedIPs"); - String customAuthFilterClassName = ParamUtils.getParameter(request, "customAuthFilterClassName"); - boolean loggingEnabled = ParamUtils.getBooleanParameter(request, "loggingEnabled"); + webManager.init(request, response, session, application, out); - String loadingStatus = null; - - final PluginManager pluginManager = admin.getXMPPServer().getPluginManager(); - - RESTServicePlugin plugin = (RESTServicePlugin) XMPPServer.getInstance().getPluginManager() - .getPluginByName("REST API").orElse(null); + final boolean save = request.getParameter("save") != null; + final Cookie csrfCookie = CookieUtils.getCookie(request, "csrf"); + final String csrfParam = ParamUtils.getParameter(request, "csrf"); - // Handle a save - Map errors = new HashMap<>(); + final Map errors = new HashMap<>(); - RESTServicePlugin.AuthType authType = null; if (save) { + if (csrfCookie == null || csrfParam == null || !csrfCookie.getValue().equals(csrfParam)) { + errors.put("csrf", ""); + } + + final String secret = ParamUtils.getParameter(request, "secret"); + final boolean enabled = ParamUtils.getBooleanParameter(request, "enabled"); + final String authTypeString = ParamUtils.getParameter(request, "authtype"); + final String allowedIPs = ParamUtils.getParameter(request, "allowedIPs"); + final String customAuthFilterClassName = ParamUtils.getParameter(request, "customAuthFilterClassName"); + final boolean loggingEnabled = ParamUtils.getBooleanParameter(request, "loggingEnabled"); + + RESTServicePlugin.AuthType authType = null; try { authType = RESTServicePlugin.AuthType.valueOf(authTypeString); } catch (Exception e) { - errors.put("authtype", "invalid value"); + errors.put("authtype", ""); } + final PluginManager pluginManager = webManager.getXMPPServer().getPluginManager(); + final RESTServicePlugin plugin = (RESTServicePlugin) pluginManager.getPluginByName("REST API").orElseThrow(); + if (RESTServicePlugin.AuthType.custom.equals(authType)) { - loadingStatus = plugin.validateCustomAuthenticationFilter(customAuthFilterClassName); - } - if (loadingStatus != null) { - errors.put("loadingStatus", loadingStatus); + final String loadingStatus = plugin.validateCustomAuthenticationFilter(customAuthFilterClassName); + if (loadingStatus != null) { + errors.put("loadingStatus", loadingStatus); + } } - if (errors.isEmpty()) - { - boolean is2Reload = RESTServicePlugin.AuthType.custom.equals(authType) || RESTServicePlugin.AuthType.custom.equals(RESTServicePlugin.AUTH_TYPE.getValue()); + if (errors.isEmpty()) { + final boolean requiresReload = 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); RESTServicePlugin.AUTH_TYPE.setValue(authType); @@ -82,203 +79,224 @@ RESTServicePlugin.SERVICE_LOGGING_ENABLED.setValue(loggingEnabled); // Log the event - admin.logEvent("Edited REST API properties", "enabled=" + enabled + "\nauthType=" + authType + "\nallowedIPs=" + allowedIPs + "\ncustomAuthFilterClassName=" + customAuthFilterClassName + "\nloggingEnabled=" + loggingEnabled); + webManager.logEvent("Edited REST API properties", "enabled=" + enabled + "\nauthType=" + authType + "\nallowedIPs=" + allowedIPs + "\ncustomAuthFilterClassName=" + customAuthFilterClassName + "\nloggingEnabled=" + loggingEnabled); - if(is2Reload) { - String pluginDir = pluginManager.getPluginPath(plugin).getFileName().toString(); + if (requiresReload) { + final String pluginDir = pluginManager.getPluginPath(plugin).getFileName().toString(); pluginManager.reloadPlugin(pluginDir); - + // Log the event - admin.logEvent("Reloaded plugin REST API (in response to authentication configuration change).", null); + webManager.logEvent("Reloaded plugin REST API (in response to authentication configuration change).", null); response.sendRedirect("/plugin-admin.jsp?reloadsuccess=true"); + return; } response.sendRedirect("rest-api.jsp?success=true"); return; } } - secret = RESTServicePlugin.SECRET.getValue(); - enabled = RESTServicePlugin.ENABLED.getValue(); - authType = RESTServicePlugin.AUTH_TYPE.getValue(); - allowedIPs = StringUtils.collectionToString(RESTServicePlugin.ALLOWED_IPS.getValue()); - customAuthFilterClassName = RESTServicePlugin.CUSTOM_AUTH_FILTER.getValue(); - loggingEnabled = RESTServicePlugin.SERVICE_LOGGING_ENABLED.getValue(); + final String csrf = StringUtils.randomString(15); + CookieUtils.setCookie(request, response, "csrf", csrf, -1); + pageContext.setAttribute("csrf", csrf); + pageContext.setAttribute("errors", errors); + + pageContext.setAttribute("secret", RESTServicePlugin.SECRET.getValue()); + pageContext.setAttribute("enabled", RESTServicePlugin.ENABLED.getValue()); + pageContext.setAttribute("authType", RESTServicePlugin.AUTH_TYPE.getValue().name()); + pageContext.setAttribute("allowedIPs", StringUtils.collectionToString(RESTServicePlugin.ALLOWED_IPS.getValue())); + pageContext.setAttribute("customAuthFilterClassName", RESTServicePlugin.CUSTOM_AUTH_FILTER.getValue()); + pageContext.setAttribute("loggingEnabled", RESTServicePlugin.SERVICE_LOGGING_ENABLED.getValue()); + pageContext.setAttribute("allowedIPsCheckSpoofable", RESTServicePlugin.isAllowedIPsCheckSpoofable()); %> -REST API Properties - + <fmt:message key="restapi.settings.title"/> + - <% - if (success) { - %> + + + + + + + + + + "/> + + + + + + + + + + + + + + + + + + + + + + + () + + + + + +

    + +

    -
    - + + "> + + + +

    + +

    +
    - - - - + + + + + + + +
    REST API properties edited - successfully.
    + + + +
    + + + +
    -
    -
    - <% - } - %> - - <% - if (RESTServicePlugin.isAllowedIPsCheckSpoofable()) { - %> -
    - + + + + +

    + +

    +
    - - + + + + + + + + +
    Access is restricted to specific IP addresses, but the admin console is - configured to determine the client address from forwarded HTTP headers (such as X-Forwarded-For), - without a list of trusted proxies. Any client can bypass the IP address check by sending such a header. - Configure the addresses of your reverse proxies as trusted proxies on the - Admin Console Access page. + + + + +
    + + + + + + + + +
    ">
    +
    + + + + + + + + +
    ">
    +

    -
    -
    - <% - } - %> - <% - if (errors.get("loadingStatus") != null) { - %> -
    - + + + + +

    + +

    +
    - - - - + + + +
    <%= loadingStatus %>
    + + + +
    -
    -
    - <% - } - %> - <% - if (errors.get("authtype") != null) { - %> -
    - +

    + + "/> + + +

    + + + + +

    + +

    +
    - - + + + + +
    Unrecognized authentication type. - + + + + +
    + + +
    -
    -
    - <% - } - %> - -

    Use the form below to enable or disable the REST API and - configure the authentication.

    - - - -
    - REST API -
    -

    - The REST API can be secured with a shared secret key defined below - or a with HTTP basic authentication.
    Moreover, for extra - security you can specify the list of IP addresses that are allowed - to use this service.
    An empty list means that the service can - be accessed from any location. Addresses are delimited by commas. -

    -
      - > - -
      - > - -
      -
      - - > - -
      - > - -
      - - " - id="text_secret"> -
      - > - -
      Note: changing back and forth from custom authentication filter forces the REST API plugin reloading
      - - " - id="custom_auth_filter_class_name" style="width:70%;padding:4px;"> -
      -
      - - - -
      Note: when the REST API is accessed through a reverse proxy, configure Openfire to use the forwarded client address, and to trust only your proxies, on the Admin Console Access page.
      -
      -
      - -

      Additional Logging

      - > - -
      - > - -
      - - -
    - -

    You can find here detailed documentation over the Openfire REST API: - REST API Documentation (opens in new tab) -

    -
    -
    + -

    - +

    + + "/> + + +

    + "> +