diff --git a/i18n/src/main/resources/openfire_i18n.properties b/i18n/src/main/resources/openfire_i18n.properties index 50b560e5a3..fc35c855fc 100644 --- a/i18n/src/main/resources/openfire_i18n.properties +++ b/i18n/src/main/resources/openfire_i18n.properties @@ -1895,6 +1895,11 @@ session.details.hide-extended=Show Less Details session.details.show-extended=Show More Details session.details.security=Security session.details.features=Features & Functionality +session.details.bind-method=Bind Method +session.details.bind-method-bind2=Bind 2 (inline, XEP-0386) +session.details.bind-method-legacy=Resource Binding (legacy IQ) +session.details.bind2-inline-features=Bind 2 Inline Features +session.details.negotiated-via-bind2=negotiated via Bind 2 # Session row Page diff --git a/i18n/src/main/resources/openfire_i18n_nl.properties b/i18n/src/main/resources/openfire_i18n_nl.properties index 790fd97987..97fba92688 100644 --- a/i18n/src/main/resources/openfire_i18n_nl.properties +++ b/i18n/src/main/resources/openfire_i18n_nl.properties @@ -1756,6 +1756,11 @@ session.details.hide-extended=Toon Minder Details session.details.show-extended=Toon Meer Details session.details.security=Beveiliging session.details.features=Functies & Functionaliteit +session.details.bind-method=Bindmethode +session.details.bind-method-bind2=Bind 2 (inline, XEP-0386) +session.details.bind-method-legacy=Resource-binding (verouderde IQ) +session.details.bind2-inline-features=Bind 2 inline-functies +session.details.negotiated-via-bind2=onderhandeld via Bind 2 # Session row Page diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/csi/CsiModule.java b/xmppserver/src/main/java/org/jivesoftware/openfire/csi/CsiModule.java index ed7f52c232..227a1d4fb2 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/csi/CsiModule.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/csi/CsiModule.java @@ -21,6 +21,8 @@ import org.jivesoftware.openfire.net.Bind2InlineHandler; import org.jivesoftware.openfire.net.Bind2Request; import org.jivesoftware.openfire.session.LocalClientSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -67,17 +69,28 @@ public synchronized void stop() { */ static class Bind2CSIHandler implements Bind2InlineHandler { + private static final Logger Log = LoggerFactory.getLogger(Bind2CSIHandler.class); + @Override public String getNamespace() { return CsiManager.NAMESPACE; } @Override - public boolean handleElement(LocalClientSession session, Element bound, Element element) { + public String getDisplayName() { + return "Client State Indication"; + } + + @Override + public boolean handleElement(LocalClientSession session, Element bound, Element element) + { if (element.getName().equals("active")) { session.getCsiManager().activate(); } else if (element.getName().equals("inactive")) { session.getCsiManager().deactivate(); + } else { + Log.debug("Received unexpected element '{}'; ignoring.", element.getName()); + return false; } return true; } diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/handler/Bind2CarbonsHandler.java b/xmppserver/src/main/java/org/jivesoftware/openfire/handler/Bind2CarbonsHandler.java index b2242b333d..aa33d48ad0 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/handler/Bind2CarbonsHandler.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/handler/Bind2CarbonsHandler.java @@ -19,16 +19,35 @@ import org.dom4j.Element; import org.jivesoftware.openfire.net.Bind2InlineHandler; import org.jivesoftware.openfire.session.LocalClientSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles inline elements related to Message Carbons during Bind2 requests. + */ +public class Bind2CarbonsHandler implements Bind2InlineHandler +{ + private static final Logger Log = LoggerFactory.getLogger(Bind2CarbonsHandler.class); -public class Bind2CarbonsHandler implements Bind2InlineHandler { @Override public String getNamespace() { return "urn:xmpp:carbons:2"; } @Override - public boolean handleElement(LocalClientSession session, Element bound, Element element) { - session.setMessageCarbonsEnabled(element.getName().equals("enable")); + public String getDisplayName() { + return "Message Carbons"; + } + + @Override + public boolean handleElement(LocalClientSession session, Element bound, Element element) + { + final String name = element.getName(); + if (!"enable".equals(name) && !"disable".equals(name)) { + Log.debug("Received unexpected element '{}'; ignoring.", element.getName()); + return false; + } + session.setMessageCarbonsEnabled("enable".equals(name)); return true; } } diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/handler/Bind2StreamManagementHandler.java b/xmppserver/src/main/java/org/jivesoftware/openfire/handler/Bind2StreamManagementHandler.java index b55a83b2e2..8ec2bba69d 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/handler/Bind2StreamManagementHandler.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/handler/Bind2StreamManagementHandler.java @@ -48,6 +48,11 @@ public String getNamespace() { return StreamManager.NAMESPACE_V3; } + @Override + public String getDisplayName() { + return "Stream Management"; + } + @Override public boolean isEnabled() { return StreamManager.isStreamManagementActive(); diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/net/Bind2InlineHandler.java b/xmppserver/src/main/java/org/jivesoftware/openfire/net/Bind2InlineHandler.java index 850690f9da..8fa0f0552a 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/net/Bind2InlineHandler.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/net/Bind2InlineHandler.java @@ -31,6 +31,15 @@ public interface Bind2InlineHandler { */ String getNamespace(); + /** + * Gets a human-friendly name for this handler. + * + * @return a human-friendly name for this handler. + */ + default String getDisplayName() { + return getNamespace(); + } + /** * Process an inline element from a bind2 request. * diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/net/Bind2Request.java b/xmppserver/src/main/java/org/jivesoftware/openfire/net/Bind2Request.java index 7c6fd38bef..98623ee979 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/net/Bind2Request.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/net/Bind2Request.java @@ -44,6 +44,13 @@ public class Bind2Request { // Add a map to store registered handlers by namespace private static final Map elementHandlers = new ConcurrentHashMap<>(); + /** + * The namespaces of inline feature requests that were successfully processed + * during {@link #processFeatureRequests}, for observability purposes. Empty until that + * method has run. + */ + private Set negotiatedFeatureNamespaces = Collections.emptySet(); + /** * Registers a handler for processing inline elements with a specific namespace. * @@ -103,6 +110,7 @@ public static boolean unregisterElementHandler(@Nonnull final Bind2InlineHandler */ public Element processFeatureRequests(LocalClientSession clientSession, Element successElement) { Element bound = successElement.addElement(new QName("bound", new Namespace("", NAMESPACE))); + final Set succeeded = new LinkedHashSet<>(); for (Element element : featureRequests) { String namespace = element.getNamespaceURI(); @@ -110,20 +118,24 @@ public Element processFeatureRequests(LocalClientSession clientSession, Element if (handler != null && handler.isEnabled()) { try { - if (!handler.handleElement(clientSession, bound, element)) { - Log.info("Handler for namespace {} failed to process element", namespace); + if (handler.handleElement(clientSession, bound, element)) { + Log.trace("Handler for namespace {} successfully processed element for session: {}", namespace, clientSession); + succeeded.add(namespace); + } else { + Log.info("Handler for namespace {} failed to process element for session: {}", namespace, clientSession); invokeFailureHandler(clientSession, bound, element, null, handler, namespace); } } catch (Exception e) { - Log.warn("Error processing element with namespace: {}", namespace, e); + Log.warn("Error processing element with namespace: {} for session: {}", namespace, clientSession, e); invokeFailureHandler(clientSession, bound, element, e, handler, namespace); } } else { - Log.debug("No handler registered/enabled for namespace: {}", namespace); + Log.debug("No handler registered/enabled for namespace: {} for session: {}", namespace, clientSession); // We don't fail here because there's no obvious way we could fail. } } + negotiatedFeatureNamespaces = Collections.unmodifiableSet(succeeded); return bound; } @@ -147,6 +159,25 @@ private static void invokeFailureHandler(final LocalClientSession clientSession, } } + /** + * Returns the namespaces of inline feature requests that were successfully processed + * during {@link #processFeatureRequests}, for observability purposes. Empty until that + * method has run. + */ + public Set getNegotiatedFeatureNamespaces() { + return negotiatedFeatureNamespaces; + } + + /** + * Looks up the registered inline handler for a given namespace, if any. + * + * @param namespace The namespace to look up. + * @return An Optional containing the handler, or an empty Optional if no handler is registered for the namespace. + */ + public static Optional getHandler(@Nonnull final String namespace) { + return Optional.ofNullable(elementHandlers.get(namespace)); + } + public static Element featureElement() { Element bind2 = DocumentHelper.createElement(new QName("bind", new Namespace("", "urn:xmpp:bind:0"))); Element bind2inline = bind2.addElement("inline"); diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/net/SASLAuthentication.java b/xmppserver/src/main/java/org/jivesoftware/openfire/net/SASLAuthentication.java index 232e2c3cf7..756fe2f2ae 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/net/SASLAuthentication.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/net/SASLAuthentication.java @@ -237,6 +237,20 @@ public class SASLAuthentication { */ private static final String SASL2_RESUME_REQUEST = "Sasl2.resume-request"; + /** + * Session Data property name used to record that a session's resource was bound via an inline XEP-0386 Bind 2 + * request during SASL2 authentication, as opposed to legacy IQ-based binding. Set only once binding has actually + * completed successfully; never set for sessions that bind via legacy IQ, and never set when an inline XEP-0198 + * resume made the inlined bind2 request moot. + */ + public static final String BIND2_USED = "Bind2.used"; + + /** + * Session Data property name holding the set of XML namespaces of inline feature requests that + * were successfully negotiated via a XEP-0386 Bind 2 request, if any. + */ + public static final String BIND2_INLINE_FEATURES = "Bind2.inline-features"; + /** * Controls whether the SCRAM mechanisms that are advertised to a client are tailored to the user that is expected * to authenticate. @@ -1047,6 +1061,11 @@ private static void completeSasl2Bind2(@Nonnull final LocalClientSession clientS clientSession.deliverRawText(success.asXML()); successDelivered = true; + clientSession.setSessionData(BIND2_USED, Boolean.TRUE); + if (!bind2Request.getNegotiatedFeatureNamespaces().isEmpty()) { + clientSession.setSessionData(BIND2_INLINE_FEATURES, bind2Request.getNegotiatedFeatureNamespaces()); + } + SessionEventDispatcher.dispatchEvent(clientSession, SessionEventDispatcher.EventType.resource_bound); // Deliver stream features now that has been sent. diff --git a/xmppserver/src/main/webapp/session-details.jsp b/xmppserver/src/main/webapp/session-details.jsp index e452d0f772..50cde982df 100644 --- a/xmppserver/src/main/webapp/session-details.jsp +++ b/xmppserver/src/main/webapp/session-details.jsp @@ -40,6 +40,13 @@ <%@ page import="org.slf4j.LoggerFactory" %> <%@ page import="java.nio.charset.StandardCharsets" %> <%@ page import="org.jivesoftware.openfire.session.*" %> +<%@ page import="org.jivesoftware.openfire.net.SASLAuthentication" %> +<%@ page import="org.jivesoftware.openfire.net.Bind2Request" %> +<%@ page import="org.jivesoftware.openfire.net.Bind2InlineHandler" %> +<%@ page import="org.jivesoftware.openfire.streammanagement.StreamManager" %> +<%@ page import="org.jivesoftware.openfire.csi.CsiManager" %> +<%@ page import="java.util.Set" %> +<%@ page import="java.util.Optional" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -138,6 +145,11 @@ pageContext.setAttribute("address", address); pageContext.setAttribute("clusteringEnabled", ClusterManager.isClusteringStarted() || ClusterManager.isClusteringStarting() ); + + final boolean bind2Used = currentSess instanceof LocalSession && Boolean.TRUE.equals(((LocalSession) currentSess).getSessionData(SASLAuthentication.BIND2_USED)); + + @SuppressWarnings("unchecked") + final Set bind2FeatureNamespaces = currentSess instanceof LocalSession ? (Set) ((LocalSession) currentSess).getSessionData(SASLAuthentication.BIND2_INLINE_FEATURES) : null; %> @@ -460,6 +472,39 @@ <% } %> + + + : + + + <% if (bind2Used) { %> + + <% } else { %> + + <% } %> + + + <% if (bind2Used && bind2FeatureNamespaces != null && !bind2FeatureNamespaces.isEmpty()) { %> + + + : + + + <% + final StringBuilder bind2FeatureNames = new StringBuilder(); + for (final String featureNamespace : new TreeSet<>(bind2FeatureNamespaces)) { + final Optional featureHandler = Bind2Request.getHandler(featureNamespace); + final String featureDisplayName = featureHandler.map(Bind2InlineHandler::getDisplayName).orElse(featureNamespace); + if (!bind2FeatureNames.isEmpty()) { + bind2FeatureNames.append(", "); + } + bind2FeatureNames.append(featureDisplayName); + } + %> + <%= StringUtils.escapeHTMLTags(bind2FeatureNames.toString()) %> + + + <% } %> <% if (currentSess instanceof LocalSession && ((LocalSession) currentSess).getSessionData("ChannelBindingType") != null) { %> @@ -474,40 +519,6 @@ - <% // Show Software Version if there is : - if (!currentSess.getSoftwareVersion().isEmpty()) { - %> -
- -
- - - - - - - - <% - Map treeMap = new TreeMap<>(currentSess.getSoftwareVersion()); - for (Map.Entry entry : treeMap.entrySet()){ %> - - - - - <% - } - %> - -
- -
- <%= StringUtils.escapeHTMLTags(entry.getKey().substring(0, 1).toUpperCase()+""+entry.getKey().substring(1)) %>: - - <%= StringUtils.escapeHTMLTags(entry.getValue())%> -
-
- <% } %> -
@@ -539,7 +550,10 @@ } else { %><% } + if (bind2Used && bind2FeatureNamespaces != null && bind2FeatureNamespaces.contains(StreamManager.NAMESPACE_V3)) { %> + () + <% } %> @@ -553,6 +567,9 @@ (: <%= s.getCsiManager().getDelayQueueSize() %>) <% } %> + <% if (bind2Used && bind2FeatureNamespaces != null && bind2FeatureNamespaces.contains(CsiManager.NAMESPACE)) { %> + () + <% } %> <% } %> @@ -566,6 +583,9 @@ <% } else { %> <% } %> + <% if (bind2Used && bind2FeatureNamespaces != null && bind2FeatureNamespaces.contains("urn:xmpp:carbons:2")) { %> + () + <% } %> @@ -584,6 +604,40 @@
+ <% // Show Software Version if there is : + if (!currentSess.getSoftwareVersion().isEmpty()) { + %> +
+ +
+ + + + + + + + <% + Map treeMap = new TreeMap<>(currentSess.getSoftwareVersion()); + for (Map.Entry entry : treeMap.entrySet()){ %> + + + + + <% + } + %> + +
+ +
+ <%= StringUtils.escapeHTMLTags(entry.getKey().substring(0, 1).toUpperCase()+""+entry.getKey().substring(1)) %>: + + <%= StringUtils.escapeHTMLTags(entry.getValue())%> +
+
+ <% } %> + <% final EntityCapabilities caps = XMPPServer.getInstance().getEntityCapabilitiesManager().getEntityCapabilities(address); if (showCaps) {