You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2024/11/21 08:05:37 UTC
(james-project) branch master updated (34ca224bad -> c505aabcac)
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
from 34ca224bad JAMES-4085 [S3 SSEC] SSEC basic implementation
new 92db805f33 JAMES-4090 Locally forced disconnection for IMAP / SMTP
new 696c62c0e4 JAMES-4090 Propose a generic framework to use disconnector outside of the protocol-api
new 4f75a6fc57 JAMES-4090 Disconectors for JMAP websocket and event source
new c2c89da9c6 JAMES-4090 Add an interface: DisconnectorNotifier
new 34e909b295 JAMES-4090 Endpoint to diconnect all users
new c4d7861ae0 JAMES-4090 Allow disconnecting several user at once
new c505aabcac JAMES-4090 Documentation forclosing users channels
The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
core/pom.xml | 4 ++
.../java/org/apache/james/core/Disconnector.java | 28 ++++++-----
.../modules/servers/partials/operate/webadmin.adoc | 54 ++++++++++++++++++++-
.../james/modules/protocols/IMAPServerModule.java | 3 ++
.../james/jmap/rfc8621/RFC8621MethodsModule.java | 5 ++
.../james/modules/protocols/SMTPServerModule.java | 2 +
.../james/modules/server/ServerRouteModule.java | 11 +++++
.../org/apache/james/DisconnectorNotifier.java} | 21 +++++++--
.../james/jmap/routes/EventSourceRoutes.scala | 20 +++++++-
.../apache/james/jmap/routes/WebSocketRoutes.scala | 23 ++++++++-
.../apache/james/imapserver/netty/IMAPServer.java | 23 ++++++++-
.../james/imapserver/netty/IMAPServerFactory.java | 13 ++++-
.../netty/ImapChannelUpstreamHandler.java | 15 +++++-
.../james/imapserver/netty/IMAPServerTest.java | 11 +++++
.../apache/james/lmtpserver/netty/LMTPServer.java | 10 +++-
.../netty/SMTPChannelInboundHandler.java | 12 ++---
.../apache/james/smtpserver/netty/SMTPServer.java | 28 ++++++++++-
.../james/smtpserver/netty/SMTPServerFactory.java | 12 ++++-
.../protocols/webadmin/webadmin-protocols/pom.xml | 4 ++
.../protocols/webadmin/ProtocolServerRoutes.java | 38 ++++++++++++++-
src/site/markdown/server/manage-webadmin.md | 55 +++++++++++++++++++++-
21 files changed, 354 insertions(+), 38 deletions(-)
copy server/container/guice/common/src/main/java/org/apache/james/ConfigurationSanitizingPerformer.java => core/src/main/java/org/apache/james/core/Disconnector.java (66%)
copy server/{protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/UserCondition.java => data/data-api/src/main/java/org/apache/james/DisconnectorNotifier.java} (69%)
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
(james-project) 01/07: JAMES-4090 Locally forced disconnection for IMAP / SMTP
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 92db805f3323022587acfde16500aa08c95790be
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Mon Nov 4 12:45:13 2024 +0100
JAMES-4090 Locally forced disconnection for IMAP / SMTP
---
.../apache/james/imapserver/netty/IMAPServer.java | 18 ++++++++++++++++
.../netty/ImapChannelUpstreamHandler.java | 15 ++++++++++++--
.../james/imapserver/netty/IMAPServerTest.java | 11 ++++++++++
.../protocols/lib/netty/CertificateReloadable.java | 6 ++++++
.../apache/james/lmtpserver/netty/LMTPServer.java | 10 ++++++++-
.../netty/SMTPChannelInboundHandler.java | 12 +++++------
.../apache/james/smtpserver/netty/SMTPServer.java | 24 +++++++++++++++++++++-
.../protocols/webadmin/ProtocolServerRoutes.java | 10 +++++++++
8 files changed, 96 insertions(+), 10 deletions(-)
diff --git a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
index f4b71e8679..b929758a56 100644
--- a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
+++ b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
@@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.james.core.Username;
import org.apache.james.imap.api.ConnectionCheck;
import org.apache.james.imap.api.ImapConfiguration;
import org.apache.james.imap.api.ImapConstants;
@@ -53,11 +54,15 @@ import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
+import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
+import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.haproxy.HAProxyMessageDecoder;
import io.netty.handler.stream.ChunkedWriteHandler;
+import io.netty.util.concurrent.GlobalEventExecutor;
/**
@@ -138,6 +143,7 @@ public class IMAPServer extends AbstractConfigurableAsyncServer implements ImapC
private final ImapMetrics imapMetrics;
private final GaugeRegistry gaugeRegistry;
private final Set<ConnectionCheck> connectionChecks;
+ private final DefaultChannelGroup imapChannelGroup;
private String hello;
private boolean compress;
@@ -152,6 +158,7 @@ public class IMAPServer extends AbstractConfigurableAsyncServer implements ImapC
private Duration heartbeatInterval;
private ReactiveThrottler reactiveThrottler;
+
public IMAPServer(ImapDecoder decoder, ImapEncoder encoder, ImapProcessor processor, ImapMetrics imapMetrics, GaugeRegistry gaugeRegistry, Set<ConnectionCheck> connectionChecks) {
this.processor = processor;
this.encoder = encoder;
@@ -159,6 +166,7 @@ public class IMAPServer extends AbstractConfigurableAsyncServer implements ImapC
this.imapMetrics = imapMetrics;
this.gaugeRegistry = gaugeRegistry;
this.connectionChecks = connectionChecks;
+ this.imapChannelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
}
@Override
@@ -189,6 +197,15 @@ public class IMAPServer extends AbstractConfigurableAsyncServer implements ImapC
processor.configure(imapConfiguration);
}
+ public void logout(Username user) {
+ imapChannelGroup.stream()
+ .filter(channel -> Optional.ofNullable(channel.attr(IMAP_SESSION_ATTRIBUTE_KEY).get())
+ .flatMap(session -> Optional.ofNullable(session.getUserName()))
+ .map(user::equals)
+ .orElse(false))
+ .forEach(channel -> channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE));
+ }
+
private static Integer parseLiteralSizeLimit(HierarchicalConfiguration<ImmutableNode> configuration) {
return Optional.ofNullable(configuration.getString("literalSizeLimit", null))
.map(Size::parse)
@@ -315,6 +332,7 @@ public class IMAPServer extends AbstractConfigurableAsyncServer implements ImapC
.heartbeatInterval(heartbeatInterval)
.ignoreIDLEUponProcessing(ignoreIDLEUponProcessing)
.proxyRequired(proxyRequired)
+ .imapChannelGroup(imapChannelGroup)
.build();
}
diff --git a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/ImapChannelUpstreamHandler.java b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/ImapChannelUpstreamHandler.java
index 1af387038f..4274e1cdab 100644
--- a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/ImapChannelUpstreamHandler.java
+++ b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/ImapChannelUpstreamHandler.java
@@ -65,6 +65,7 @@ import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.ssl.NotSslRecordException;
@@ -93,6 +94,7 @@ public class ImapChannelUpstreamHandler extends ChannelInboundHandlerAdapter imp
private ReactiveThrottler reactiveThrottler;
private Set<ConnectionCheck> connectionChecks;
private boolean proxyRequired;
+ private ChannelGroup imapChannelGroup;
public ImapChannelUpstreamHandlerBuilder reactiveThrottler(ReactiveThrottler reactiveThrottler) {
this.reactiveThrottler = reactiveThrottler;
@@ -154,8 +156,13 @@ public class ImapChannelUpstreamHandler extends ChannelInboundHandlerAdapter imp
return this;
}
+ public ImapChannelUpstreamHandlerBuilder imapChannelGroup(ChannelGroup imapChannelGroup) {
+ this.imapChannelGroup = imapChannelGroup;
+ return this;
+ }
+
public ImapChannelUpstreamHandler build() {
- return new ImapChannelUpstreamHandler(hello, processor, encoder, compress, secure, imapMetrics, authenticationConfiguration, ignoreIDLEUponProcessing, (int) heartbeatInterval.toSeconds(), reactiveThrottler, connectionChecks, proxyRequired);
+ return new ImapChannelUpstreamHandler(hello, processor, encoder, compress, secure, imapMetrics, authenticationConfiguration, ignoreIDLEUponProcessing, (int) heartbeatInterval.toSeconds(), reactiveThrottler, connectionChecks, proxyRequired, imapChannelGroup);
}
}
@@ -181,11 +188,12 @@ public class ImapChannelUpstreamHandler extends ChannelInboundHandlerAdapter imp
private final ReactiveThrottler reactiveThrottler;
private final Set<ConnectionCheck> connectionChecks;
private final boolean proxyRequired;
+ private final ChannelGroup imapChannelGroup;
public ImapChannelUpstreamHandler(String hello, ImapProcessor processor, ImapEncoder encoder, boolean compress,
Encryption secure, ImapMetrics imapMetrics, AuthenticationConfiguration authenticationConfiguration,
boolean ignoreIDLEUponProcessing, int heartbeatIntervalSeconds, ReactiveThrottler reactiveThrottler,
- Set<ConnectionCheck> connectionChecks, boolean proxyRequired) {
+ Set<ConnectionCheck> connectionChecks, boolean proxyRequired, ChannelGroup imapChannelGroup) {
this.hello = hello;
this.processor = processor;
this.encoder = encoder;
@@ -199,10 +207,12 @@ public class ImapChannelUpstreamHandler extends ChannelInboundHandlerAdapter imp
this.reactiveThrottler = reactiveThrottler;
this.connectionChecks = connectionChecks;
this.proxyRequired = proxyRequired;
+ this.imapChannelGroup = imapChannelGroup;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
+ imapChannelGroup.add(ctx.channel());
SessionId sessionId = SessionId.generate();
ImapSession imapsession = new NettyImapSession(ctx.channel(), secure, compress, authenticationConfiguration.isSSLRequired(),
authenticationConfiguration.isPlainAuthEnabled(), sessionId,
@@ -262,6 +272,7 @@ public class ImapChannelUpstreamHandler extends ChannelInboundHandlerAdapter imp
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// remove the stored attribute for the channel to free up resources
// See JAMES-1195
+ imapChannelGroup.remove(ctx.channel());
ImapSession imapSession = ctx.channel().attr(IMAP_SESSION_ATTRIBUTE_KEY).getAndSet(null);
try (Closeable closeable = mdc(imapSession).build()) {
InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index 300b19acc5..f2896fbaf2 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -248,6 +248,17 @@ class IMAPServerTest {
.append("INBOX", SMALL_MESSAGE));
}
+ @Test
+ void logoutShouldDisconnectUser() throws Exception {
+ testIMAPClient.connect("127.0.0.1", port)
+ .login(USER.asString(), USER_PASS);
+
+ imapServer.logout(USER);
+
+ assertThatThrownBy(() -> testIMAPClient
+ .append("INBOX", SMALL_MESSAGE));
+ }
+
@Test
void allowConnectWithUnbannedIp() throws IOException {
imapServer.getConnectionChecks().stream()
diff --git a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java
index 79656203f5..212cceedca 100644
--- a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java
+++ b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java
@@ -20,6 +20,8 @@ package org.apache.james.protocols.lib.netty;
import java.util.stream.Stream;
+import org.apache.james.core.Username;
+
public interface CertificateReloadable {
interface Factory {
Stream<? extends CertificateReloadable> certificatesReloadable();
@@ -28,4 +30,8 @@ public interface CertificateReloadable {
void reloadSSLCertificate() throws Exception;
int getPort();
+
+ default void logout(Username user) {
+
+ }
}
diff --git a/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java b/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java
index faa5751462..9eb8f12264 100644
--- a/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java
+++ b/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java
@@ -33,6 +33,7 @@ import org.apache.james.protocols.lib.netty.AbstractProtocolAsyncServer;
import org.apache.james.protocols.lmtp.LMTPConfiguration;
import org.apache.james.protocols.netty.AbstractChannelPipelineFactory;
import org.apache.james.protocols.netty.ChannelHandlerFactory;
+import org.apache.james.protocols.netty.Encryption;
import org.apache.james.protocols.netty.LineDelimiterBasedChannelHandlerFactory;
import org.apache.james.protocols.smtp.SMTPProtocol;
import org.apache.james.smtpserver.ExtendedSMTPSession;
@@ -41,6 +42,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.group.ChannelGroup;
+import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.util.concurrent.GlobalEventExecutor;
public class LMTPServer extends AbstractProtocolAsyncServer implements LMTPServerMBean {
private static final Logger LOGGER = LoggerFactory.getLogger(LMTPServer.class);
@@ -52,10 +56,12 @@ public class LMTPServer extends AbstractProtocolAsyncServer implements LMTPServe
private long maxMessageSize = 0;
private final LMTPConfigurationImpl lmtpConfig = new LMTPConfigurationImpl();
private final LMTPMetricsImpl lmtpMetrics;
+ private final ChannelGroup lmtpChannelGroup;
private String lmtpGreeting;
public LMTPServer(LMTPMetricsImpl lmtpMetrics) {
this.lmtpMetrics = lmtpMetrics;
+ this.lmtpChannelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
}
@Override
@@ -155,7 +161,9 @@ public class LMTPServer extends AbstractProtocolAsyncServer implements LMTPServe
return new ExtendedSMTPSession(lmtpConfig, transport);
}
};
- return new SMTPChannelInboundHandler(transport, lmtpMetrics);
+ boolean proxyRequired = true;
+ Encryption noEncryption = null;
+ return new SMTPChannelInboundHandler(transport, noEncryption, !proxyRequired, lmtpMetrics, lmtpChannelGroup);
}
@Override
diff --git a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPChannelInboundHandler.java b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPChannelInboundHandler.java
index d232a217e4..e8e37020bd 100644
--- a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPChannelInboundHandler.java
+++ b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPChannelInboundHandler.java
@@ -31,6 +31,7 @@ import org.apache.james.smtpserver.ExtendedSMTPSession;
import org.apache.james.smtpserver.SMTPConstants;
import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.group.ChannelGroup;
/**
* {@link BasicChannelInboundHandler} which is used by the SMTPServer
@@ -38,18 +39,15 @@ import io.netty.channel.ChannelHandlerContext;
public class SMTPChannelInboundHandler extends BasicChannelInboundHandler {
private final SmtpMetrics smtpMetrics;
+ private final ChannelGroup smtpChannelGroup;
- public SMTPChannelInboundHandler(Protocol protocol, Encryption encryption, boolean proxyRequired, SmtpMetrics smtpMetrics) {
+ public SMTPChannelInboundHandler(Protocol protocol, Encryption encryption, boolean proxyRequired, SmtpMetrics smtpMetrics, ChannelGroup smtpChannelGroup) {
super(new SMTPMDCContextFactory(), protocol, encryption, proxyRequired);
this.smtpMetrics = smtpMetrics;
+ this.smtpChannelGroup = smtpChannelGroup;
this.resultHandlers.add(recordCommandCount(smtpMetrics));
}
- public SMTPChannelInboundHandler(Protocol protocol, SmtpMetrics smtpMetrics) {
- super(new SMTPMDCContextFactory(), protocol);
- this.smtpMetrics = smtpMetrics;
- this.resultHandlers.add(recordCommandCount(smtpMetrics));
- }
private ProtocolHandlerResultHandler recordCommandCount(SmtpMetrics smtpMetrics) {
return (session, response, executionTime, handler) -> {
@@ -60,12 +58,14 @@ public class SMTPChannelInboundHandler extends BasicChannelInboundHandler {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
+ smtpChannelGroup.add(ctx.channel());
super.channelActive(ctx);
smtpMetrics.getConnectionMetric().increment();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+ smtpChannelGroup.remove(ctx.channel());
super.channelInactive(ctx);
smtpMetrics.getConnectionMetric().decrement();
}
diff --git a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
index 8dc271aaef..5ed535b7d3 100644
--- a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
+++ b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
@@ -18,6 +18,7 @@
****************************************************************/
package org.apache.james.smtpserver.netty;
+import static org.apache.james.protocols.netty.BasicChannelInboundHandler.SESSION_ATTRIBUTE_KEY;
import static org.apache.james.smtpserver.netty.SMTPServer.AuthenticationAnnounceMode.ALWAYS;
import static org.apache.james.smtpserver.netty.SMTPServer.AuthenticationAnnounceMode.NEVER;
@@ -32,6 +33,7 @@ import jakarta.inject.Inject;
import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.james.core.Username;
import org.apache.james.dnsservice.api.DNSService;
import org.apache.james.dnsservice.library.netmatcher.NetMatcher;
import org.apache.james.protocols.api.OidcSASLConfiguration;
@@ -44,6 +46,7 @@ import org.apache.james.protocols.netty.AllButStartTlsLineChannelHandlerFactory;
import org.apache.james.protocols.netty.ChannelHandlerFactory;
import org.apache.james.protocols.smtp.SMTPConfiguration;
import org.apache.james.protocols.smtp.SMTPProtocol;
+import org.apache.james.protocols.smtp.SMTPSession;
import org.apache.james.smtpserver.CoreCmdHandlerLoader;
import org.apache.james.smtpserver.ExtendedSMTPSession;
import org.apache.james.smtpserver.jmx.JMXHandlersLoader;
@@ -53,7 +56,12 @@ import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableSet;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.util.concurrent.GlobalEventExecutor;
+
/**
* NIO SMTPServer which use Netty
@@ -184,6 +192,7 @@ public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServe
*/
private final SMTPConfiguration theConfigData = new SMTPHandlerConfigurationDataImpl();
private final SmtpMetrics smtpMetrics;
+ private final DefaultChannelGroup smtpChannelGroup;
private Set<String> disabledFeatures = ImmutableSet.of();
private boolean addressBracketsEnforcement = true;
@@ -195,6 +204,7 @@ public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServe
public SMTPServer(SmtpMetrics smtpMetrics) {
this.smtpMetrics = smtpMetrics;
+ this.smtpChannelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
}
@Inject
@@ -392,7 +402,7 @@ public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServe
@Override
protected ChannelInboundHandlerAdapter createCoreHandler() {
- return new SMTPChannelInboundHandler(transport, getEncryption(), proxyRequired, smtpMetrics);
+ return new SMTPChannelInboundHandler(transport, getEncryption(), proxyRequired, smtpMetrics, smtpChannelGroup);
}
@Override
@@ -413,4 +423,16 @@ public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServe
public AuthenticationAnnounceMode getAuthRequired() {
return authenticationConfiguration.getAuthenticationAnnounceMode();
}
+
+ @Override
+ public void logout(Username user) {
+ smtpChannelGroup.stream()
+ .filter(channel -> {
+ if (channel.attr(SESSION_ATTRIBUTE_KEY).get() instanceof SMTPSession smtpSession) {
+ return user.equals(smtpSession.getUsername());
+ }
+ return false;
+ })
+ .forEach(channel -> channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE));
+ }
}
diff --git a/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java b/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
index ae6cde7188..00aebd1528 100644
--- a/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
+++ b/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
@@ -24,6 +24,7 @@ import java.util.Set;
import jakarta.inject.Inject;
+import org.apache.james.core.Username;
import org.apache.james.protocols.lib.netty.CertificateReloadable;
import org.apache.james.util.Port;
import org.apache.james.webadmin.Routes;
@@ -74,6 +75,15 @@ public class ProtocolServerRoutes implements Routes {
return Responses.returnNoContent(response);
});
+
+ service.delete(SERVERS + "/users/:user", (request, response) -> {
+ Username username = Username.of(request.params("user"));
+ servers.stream()
+ .flatMap(CertificateReloadable.Factory::certificatesReloadable)
+ .forEach(s -> s.logout(username));
+
+ return Responses.returnNoContent(response);
+ });
}
private Predicate<CertificateReloadable> filters(Request request) {
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
(james-project) 05/07: JAMES-4090 Endpoint to diconnect all users
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 34e909b29557a4b6698c99b46a5f3a3c44bab4c5
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Wed Nov 20 11:42:14 2024 +0100
JAMES-4090 Endpoint to diconnect all users
---
core/src/main/java/org/apache/james/core/Disconnector.java | 5 +++--
.../scala/org/apache/james/jmap/routes/EventSourceRoutes.scala | 5 +++--
.../scala/org/apache/james/jmap/routes/WebSocketRoutes.scala | 5 +++--
.../main/java/org/apache/james/imapserver/netty/IMAPServer.java | 5 +++--
.../org/apache/james/imapserver/netty/IMAPServerFactory.java | 3 ++-
.../java/org/apache/james/imapserver/netty/IMAPServerTest.java | 2 +-
.../main/java/org/apache/james/smtpserver/netty/SMTPServer.java | 5 +++--
.../org/apache/james/smtpserver/netty/SMTPServerFactory.java | 3 ++-
.../apache/james/protocols/webadmin/ProtocolServerRoutes.java | 9 ++++++++-
9 files changed, 28 insertions(+), 14 deletions(-)
diff --git a/core/src/main/java/org/apache/james/core/Disconnector.java b/core/src/main/java/org/apache/james/core/Disconnector.java
index cd22b01046..e736c243f8 100644
--- a/core/src/main/java/org/apache/james/core/Disconnector.java
+++ b/core/src/main/java/org/apache/james/core/Disconnector.java
@@ -20,6 +20,7 @@
package org.apache.james.core;
import java.util.Set;
+import java.util.function.Predicate;
import jakarta.inject.Inject;
@@ -28,7 +29,7 @@ public interface Disconnector {
};
- void disconnect(Username username);
+ void disconnect(Predicate<Username> username);
class CompositeDisconnector implements Disconnector {
private final Set<Disconnector> disconnectorSet;
@@ -39,7 +40,7 @@ public interface Disconnector {
}
@Override
- public void disconnect(Username username) {
+ public void disconnect(Predicate<Username> username) {
disconnectorSet.forEach(disconnector -> disconnector.disconnect(username));
}
}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/EventSourceRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/EventSourceRoutes.scala
index 35f0105504..0d530e2f3c 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/EventSourceRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/EventSourceRoutes.scala
@@ -52,6 +52,7 @@ import reactor.core.scala.publisher.{SFlux, SMono}
import reactor.core.scheduler.Schedulers
import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse}
+import java.util.function.Predicate
import scala.concurrent.duration._
import scala.jdk.CollectionConverters._
import scala.language.postfixOps
@@ -236,10 +237,10 @@ class EventSourceRoutes@Inject() (@Named(InjectionKeys.RFC_8621) val authenticat
StandardCharsets.UTF_8)
.`then`)
- override def disconnect(username: Username): Unit = {
+ override def disconnect(username: Predicate[Username]): Unit = {
val contexts = connectedUsers.values()
.stream()
- .filter(context => username.equals(context.session.getUser))
+ .filter(context => username.test(context.session.getUser))
.toList
contexts
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala
index bd35b6f7c3..f2c9e7ee9d 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala
@@ -51,6 +51,7 @@ import reactor.core.scheduler.Schedulers
import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse}
import reactor.netty.http.websocket.{WebsocketInbound, WebsocketOutbound}
+import java.util.function.Predicate
import scala.jdk.CollectionConverters._
object WebSocketRoutes {
@@ -195,10 +196,10 @@ class WebSocketRoutes @Inject() (@Named(InjectionKeys.RFC_8621) val authenticato
StandardCharsets.UTF_8)
.`then`)
- override def disconnect(username: Username): Unit = {
+ override def disconnect(username: Predicate[Username]): Unit = {
val contexts = connectedUsers.values()
.stream()
- .filter(context => username.equals(context.session.getUser))
+ .filter(context => username.test(context.session.getUser))
.toList
contexts
diff --git a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
index 01b0ec4b6f..f2e08114a6 100644
--- a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
+++ b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
@@ -25,6 +25,7 @@ import java.util.LinkedHashMap;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.ex.ConfigurationException;
@@ -199,11 +200,11 @@ public class IMAPServer extends AbstractConfigurableAsyncServer implements ImapC
}
@Override
- public void disconnect(Username user) {
+ public void disconnect(Predicate<Username> user) {
imapChannelGroup.stream()
.filter(channel -> Optional.ofNullable(channel.attr(IMAP_SESSION_ATTRIBUTE_KEY).get())
.flatMap(session -> Optional.ofNullable(session.getUserName()))
- .map(user::equals)
+ .map(user::test)
.orElse(false))
.forEach(channel -> channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE));
}
diff --git a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServerFactory.java b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServerFactory.java
index 7880a634e9..9ddcdb30b0 100644
--- a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServerFactory.java
+++ b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServerFactory.java
@@ -20,6 +20,7 @@ package org.apache.james.imapserver.netty;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Predicate;
import jakarta.inject.Inject;
@@ -98,7 +99,7 @@ public class IMAPServerFactory extends AbstractServerFactory implements Disconne
}
@Override
- public void disconnect(Username username) {
+ public void disconnect(Predicate<Username> username) {
getServers()
.stream()
.map(server -> (IMAPServer) server)
diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index d467988fbd..54a76fdb86 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -253,7 +253,7 @@ class IMAPServerTest {
testIMAPClient.connect("127.0.0.1", port)
.login(USER.asString(), USER_PASS);
- imapServer.disconnect(USER);
+ imapServer.disconnect(USER::equals);
assertThatThrownBy(() -> testIMAPClient
.append("INBOX", SMALL_MESSAGE));
diff --git a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
index b56c28f904..8ead90f36a 100644
--- a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
+++ b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
@@ -27,6 +27,7 @@ import java.net.URISyntaxException;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Predicate;
import jakarta.inject.Inject;
@@ -426,11 +427,11 @@ public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServe
}
@Override
- public void disconnect(Username user) {
+ public void disconnect(Predicate<Username> user) {
smtpChannelGroup.stream()
.filter(channel -> {
if (channel.attr(SESSION_ATTRIBUTE_KEY).get() instanceof SMTPSession smtpSession) {
- return user.equals(smtpSession.getUsername());
+ return user.test(smtpSession.getUsername());
}
return false;
})
diff --git a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServerFactory.java b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServerFactory.java
index 644ae1acc4..17b6197660 100644
--- a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServerFactory.java
+++ b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServerFactory.java
@@ -21,6 +21,7 @@ package org.apache.james.smtpserver.netty;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Predicate;
import jakarta.inject.Inject;
@@ -74,7 +75,7 @@ public class SMTPServerFactory extends AbstractServerFactory implements Disconne
}
@Override
- public void disconnect(Username username) {
+ public void disconnect(Predicate<Username> username) {
getServers()
.stream()
.map(server -> (SMTPServer) server)
diff --git a/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java b/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
index b21d58e481..9211ade17e 100644
--- a/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
+++ b/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
@@ -80,7 +80,14 @@ public class ProtocolServerRoutes implements Routes {
});
service.delete(SERVERS + "/users/:user", (request, response) -> {
- disconnector.disconnect(Username.of(request.params("user")));
+ Username username = Username.of(request.params("user"));
+ disconnector.disconnect(username::equals);
+
+ return Responses.returnNoContent(response);
+ });
+
+ service.delete(SERVERS + "/users", (request, response) -> {
+ disconnector.disconnect(any -> true);
return Responses.returnNoContent(response);
});
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
(james-project) 04/07: JAMES-4090 Add an interface: DisconnectorNotifier
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit c2c89da9c6e1d99913655e6ee290ff3c0fbb8513
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Sun Nov 17 10:21:49 2024 +0100
JAMES-4090 Add an interface: DisconnectorNotifier
We supply right away an In VM implementation.
We can then later supply a rabbitMQ based implementation
in order to support broadcast.
---
.../james/modules/server/ServerRouteModule.java | 4 ++++
.../org/apache/james/DisconnectorNotifier.java} | 24 ++++++++++++++--------
.../protocols/lib/netty/CertificateReloadable.java | 2 --
.../protocols/webadmin/webadmin-protocols/pom.xml | 4 ++++
.../protocols/webadmin/ProtocolServerRoutes.java | 6 +++---
5 files changed, 27 insertions(+), 13 deletions(-)
diff --git a/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/ServerRouteModule.java b/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/ServerRouteModule.java
index 7d34b6114b..f3e3af0556 100644
--- a/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/ServerRouteModule.java
+++ b/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/ServerRouteModule.java
@@ -19,6 +19,7 @@
package org.apache.james.modules.server;
+import org.apache.james.DisconnectorNotifier;
import org.apache.james.core.Disconnector;
import org.apache.james.protocols.lib.netty.AbstractServerFactory;
import org.apache.james.protocols.webadmin.ProtocolServerRoutes;
@@ -41,5 +42,8 @@ public class ServerRouteModule extends AbstractModule {
bind(Disconnector.class).to(Disconnector.CompositeDisconnector.class);
bind(Disconnector.CompositeDisconnector.class).in(Scopes.SINGLETON);
+
+ bind(DisconnectorNotifier.class).to(DisconnectorNotifier.InVMDisconnectorNotifier.class);
+ bind(DisconnectorNotifier.InVMDisconnectorNotifier.class).in(Scopes.SINGLETON);
}
}
diff --git a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java b/server/data/data-api/src/main/java/org/apache/james/DisconnectorNotifier.java
similarity index 69%
copy from server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java
copy to server/data/data-api/src/main/java/org/apache/james/DisconnectorNotifier.java
index 9fdf4195cf..e0f4b3786e 100644
--- a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java
+++ b/server/data/data-api/src/main/java/org/apache/james/DisconnectorNotifier.java
@@ -16,18 +16,26 @@
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
-package org.apache.james.protocols.lib.netty;
-import java.util.stream.Stream;
+package org.apache.james;
+import jakarta.inject.Inject;
+
+import org.apache.james.core.Disconnector;
import org.apache.james.core.Username;
-public interface CertificateReloadable {
- interface Factory {
- Stream<? extends CertificateReloadable> certificatesReloadable();
- }
+public interface DisconnectorNotifier extends Disconnector {
+ class InVMDisconnectorNotifier implements DisconnectorNotifier {
+ private final Disconnector disconnector;
- void reloadSSLCertificate() throws Exception;
+ @Inject
+ public InVMDisconnectorNotifier(Disconnector disconnector) {
+ this.disconnector = disconnector;
+ }
- int getPort();
+ @Override
+ public void disconnect(Username username) {
+ disconnector.disconnect(username);
+ }
+ }
}
diff --git a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java
index 9fdf4195cf..79656203f5 100644
--- a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java
+++ b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java
@@ -20,8 +20,6 @@ package org.apache.james.protocols.lib.netty;
import java.util.stream.Stream;
-import org.apache.james.core.Username;
-
public interface CertificateReloadable {
interface Factory {
Stream<? extends CertificateReloadable> certificatesReloadable();
diff --git a/server/protocols/webadmin/webadmin-protocols/pom.xml b/server/protocols/webadmin/webadmin-protocols/pom.xml
index 48e0f6b0f2..6c29a73f63 100644
--- a/server/protocols/webadmin/webadmin-protocols/pom.xml
+++ b/server/protocols/webadmin/webadmin-protocols/pom.xml
@@ -30,6 +30,10 @@
<description>Finner grained management for protocols</description>
<dependencies>
+ <dependency>
+ <groupId>${james.groupId}</groupId>
+ <artifactId>james-server-data-api</artifactId>
+ </dependency>
<dependency>
<groupId>${james.groupId}</groupId>
<artifactId>james-server-protocols-library</artifactId>
diff --git a/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java b/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
index 9dffaa7d53..b21d58e481 100644
--- a/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
+++ b/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
@@ -24,7 +24,7 @@ import java.util.Set;
import jakarta.inject.Inject;
-import org.apache.james.core.Disconnector;
+import org.apache.james.DisconnectorNotifier;
import org.apache.james.core.Username;
import org.apache.james.protocols.lib.netty.CertificateReloadable;
import org.apache.james.util.Port;
@@ -44,10 +44,10 @@ public class ProtocolServerRoutes implements Routes {
public static final String SERVERS = "servers";
private final Set<CertificateReloadable.Factory> servers;
- private final Disconnector disconnector;
+ private final DisconnectorNotifier disconnector;
@Inject
- public ProtocolServerRoutes(Set<CertificateReloadable.Factory> servers, Disconnector disconnector) {
+ public ProtocolServerRoutes(Set<CertificateReloadable.Factory> servers, DisconnectorNotifier disconnector) {
this.servers = servers;
this.disconnector = disconnector;
}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
(james-project) 07/07: JAMES-4090 Documentation forclosing users channels
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit c505aabcac03968368474100fa5c622ae4a0f5ea
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Wed Nov 20 11:59:23 2024 +0100
JAMES-4090 Documentation forclosing users channels
---
.../modules/servers/partials/operate/webadmin.adoc | 54 ++++++++++++++++++++-
.../protocols/webadmin/ProtocolServerRoutes.java | 4 +-
src/site/markdown/server/manage-webadmin.md | 55 +++++++++++++++++++++-
3 files changed, 107 insertions(+), 6 deletions(-)
diff --git a/docs/modules/servers/partials/operate/webadmin.adoc b/docs/modules/servers/partials/operate/webadmin.adoc
index 196e3401f1..9403184aaa 100644
--- a/docs/modules/servers/partials/operate/webadmin.adoc
+++ b/docs/modules/servers/partials/operate/webadmin.adoc
@@ -4733,7 +4733,9 @@ This is an example of returned body.
}
....
-== Reloading server certificates
+== Server administration
+
+=== Reloading server certificates
Certificates for TCP based protocols (IMAP, SMTP, POP3, LMTP and ManageSieve) can be updated at
runtime, without service interuption and without closing existing connections.
@@ -4754,4 +4756,52 @@ Optional query parameters:
Return code:
- 204: the certificate is reloaded
-- 400: Invalid request.
\ No newline at end of file
+- 400: Invalid request.
+
+=== Disconnecting users
+
+James maintains a set of stateful connections and provide an API allowing to close any of the existing
+connections, including:
+
+ - IMAP protocol
+ - SMTP protocol
+ - JMAP websocket and event source sub protocols
+
+James keeps track of active channels and would iterate through them, destroying corresponding channels.
+
+==== Disconnecting a specific user
+
+....
+curl -XDELETE /servers/channels/bob@domain.tld
+....
+
+Will destroy channels belonging to `bob@domain.tld`.
+
+Return code:
+
+- 204: the certificate is reloaded
+
+==== Disconnecting all users
+
+....
+curl -XDELETE /servers/channels
+....
+
+Will close all channels.
+
+Return code:
+
+- 204: the certificate is reloaded
+
+==== Disconnecting a group of users
+
+....
+curl -XDELETE /servers/channels -d `["badGuy1@domain.tld","badGuy1@domain.tld"]`
+....
+
+Will disconnect `badGuy1@domain.tld` and `badGuy2@domain.tld`.
+
+Return code:
+
+- 204: the certificate is reloaded
+- 400: Invalid request
diff --git a/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java b/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
index d0b9de4c1c..c1901f7f15 100644
--- a/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
+++ b/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
@@ -88,14 +88,14 @@ public class ProtocolServerRoutes implements Routes {
return Responses.returnNoContent(response);
});
- service.delete(SERVERS + "/users/:user", (request, response) -> {
+ service.delete(SERVERS + "/channels/:user", (request, response) -> {
Username username = Username.of(request.params("user"));
disconnector.disconnect(username::equals);
return Responses.returnNoContent(response);
});
- service.delete(SERVERS + "/users", (request, response) -> {
+ service.delete(SERVERS + "/channels", (request, response) -> {
String body = request.body();
if (Strings.isNullOrEmpty(body)) {
diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md
index a8836ca75f..d1ddc4ba01 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -4921,7 +4921,10 @@ Response codes :
- 201: the taskId of the created task
- 400: Invalid action argument for performing operation on mappings data
-## Reloading server certificates
+
+## Server administration
+
+### Reloading server certificates
Certificates for TCP based protocols (IMAP, SMTP, POP3, LMTP and ManageSieve) can be updated at
runtime, without service interuption and without closing existing connections.
@@ -4942,4 +4945,52 @@ Optional query parameters:
Return code:
- 204: the certificate is reloaded
- - 400: Invalid request.
\ No newline at end of file
+ - 400: Invalid request.
+
+### Disconnecting users
+
+James maintains a set of stateful connections and provide an API allowing to close any of the existing
+connections, including:
+
+- IMAP protocol
+- SMTP protocol
+- JMAP websocket and event source sub protocols
+
+James keeps track of active channels and would iterate through them, destroying corresponding channels.
+
+#### Disconnecting a specific user
+
+```
+curl -XDELETE /servers/channels/bob@domain.tld
+```
+
+Will destroy channels belonging to `bob@domain.tld`.
+
+Return code:
+
+- 204: the certificate is reloaded
+
+#### Disconnecting all users
+
+```
+curl -XDELETE /servers/channels
+```
+
+Will close all channels.
+
+Return code:
+
+- 204: the certificate is reloaded
+
+#### Disconnecting a group of users
+
+```
+curl -XDELETE /servers/channels -d `["badGuy1@domain.tld","badGuy1@domain.tld"]`
+```
+
+Will disconnect `badGuy1@domain.tld` and `badGuy2@domain.tld`.
+
+Return code:
+
+- 204: the certificate is reloaded
+- 400: Invalid request
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
(james-project) 02/07: JAMES-4090 Propose a generic framework to use disconnector outside of the protocol-api
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 696c62c0e4e26513a8cbd678e9820dc83b528d76
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Sun Nov 17 09:50:45 2024 +0100
JAMES-4090 Propose a generic framework to use disconnector outside of the protocol-api
---
core/pom.xml | 4 +++
.../java/org/apache/james/core/Disconnector.java | 29 ++++++++++++++--------
.../james/modules/protocols/IMAPServerModule.java | 3 +++
.../james/modules/protocols/SMTPServerModule.java | 2 ++
.../james/modules/server/ServerRouteModule.java | 7 ++++++
.../apache/james/imapserver/netty/IMAPServer.java | 6 +++--
.../james/imapserver/netty/IMAPServerFactory.java | 12 ++++++++-
.../james/imapserver/netty/IMAPServerTest.java | 2 +-
.../protocols/lib/netty/CertificateReloadable.java | 4 ---
.../apache/james/smtpserver/netty/SMTPServer.java | 5 ++--
.../james/smtpserver/netty/SMTPServerFactory.java | 11 +++++++-
.../protocols/webadmin/ProtocolServerRoutes.java | 10 ++++----
12 files changed, 69 insertions(+), 26 deletions(-)
diff --git a/core/pom.xml b/core/pom.xml
index 4f26db199f..03bc742762 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -57,6 +57,10 @@
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>jakarta.inject</groupId>
+ <artifactId>jakarta.inject-api</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
diff --git a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java b/core/src/main/java/org/apache/james/core/Disconnector.java
similarity index 65%
copy from server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java
copy to core/src/main/java/org/apache/james/core/Disconnector.java
index 212cceedca..cd22b01046 100644
--- a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java
+++ b/core/src/main/java/org/apache/james/core/Disconnector.java
@@ -16,22 +16,31 @@
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
-package org.apache.james.protocols.lib.netty;
-import java.util.stream.Stream;
+package org.apache.james.core;
-import org.apache.james.core.Username;
+import java.util.Set;
-public interface CertificateReloadable {
- interface Factory {
- Stream<? extends CertificateReloadable> certificatesReloadable();
- }
+import jakarta.inject.Inject;
+
+public interface Disconnector {
+ Disconnector NOOP = user -> {
+
+ };
- void reloadSSLCertificate() throws Exception;
+ void disconnect(Username username);
- int getPort();
+ class CompositeDisconnector implements Disconnector {
+ private final Set<Disconnector> disconnectorSet;
- default void logout(Username user) {
+ @Inject
+ public CompositeDisconnector(Set<Disconnector> disconnectorSet) {
+ this.disconnectorSet = disconnectorSet;
+ }
+ @Override
+ public void disconnect(Username username) {
+ disconnectorSet.forEach(disconnector -> disconnector.disconnect(username));
+ }
}
}
diff --git a/server/container/guice/protocols/imap/src/main/java/org/apache/james/modules/protocols/IMAPServerModule.java b/server/container/guice/protocols/imap/src/main/java/org/apache/james/modules/protocols/IMAPServerModule.java
index 617f75630f..b79d43afa8 100644
--- a/server/container/guice/protocols/imap/src/main/java/org/apache/james/modules/protocols/IMAPServerModule.java
+++ b/server/container/guice/protocols/imap/src/main/java/org/apache/james/modules/protocols/IMAPServerModule.java
@@ -28,6 +28,7 @@ import org.apache.commons.configuration2.tree.ImmutableNode;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.james.ProtocolConfigurationSanitizer;
import org.apache.james.RunArguments;
+import org.apache.james.core.Disconnector;
import org.apache.james.core.healthcheck.HealthCheck;
import org.apache.james.filesystem.api.FileSystem;
import org.apache.james.imap.ImapSuite;
@@ -116,6 +117,8 @@ public class IMAPServerModule extends AbstractModule {
bind(ConnectionCheckFactory.class).to(ConnectionCheckFactoryImpl.class);
Multibinder.newSetBinder(binder(), HealthCheck.class).addBinding().to(IMAPHealthCheck.class);
+
+ Multibinder.newSetBinder(binder(), Disconnector.class).addBinding().to(IMAPServerFactory.class);
}
@Provides
diff --git a/server/container/guice/protocols/smtp/src/main/java/org/apache/james/modules/protocols/SMTPServerModule.java b/server/container/guice/protocols/smtp/src/main/java/org/apache/james/modules/protocols/SMTPServerModule.java
index b4f6b08e3c..9612eb0bf6 100644
--- a/server/container/guice/protocols/smtp/src/main/java/org/apache/james/modules/protocols/SMTPServerModule.java
+++ b/server/container/guice/protocols/smtp/src/main/java/org/apache/james/modules/protocols/SMTPServerModule.java
@@ -21,6 +21,7 @@ package org.apache.james.modules.protocols;
import org.apache.james.ProtocolConfigurationSanitizer;
import org.apache.james.RunArguments;
+import org.apache.james.core.Disconnector;
import org.apache.james.filesystem.api.FileSystem;
import org.apache.james.lifecycle.api.ConfigurationSanitizer;
import org.apache.james.protocols.lib.netty.CertificateReloadable;
@@ -46,6 +47,7 @@ public class SMTPServerModule extends AbstractModule {
Multibinder.newSetBinder(binder(), GuiceProbe.class).addBinding().to(SmtpGuiceProbe.class);
Multibinder.newSetBinder(binder(), CertificateReloadable.Factory.class).addBinding().to(SMTPServerFactory.class);
+ Multibinder.newSetBinder(binder(), Disconnector.class).addBinding().to(SMTPServerFactory.class);
}
@ProvidesIntoSet
diff --git a/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/ServerRouteModule.java b/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/ServerRouteModule.java
index a8c20b902f..7d34b6114b 100644
--- a/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/ServerRouteModule.java
+++ b/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/ServerRouteModule.java
@@ -19,11 +19,13 @@
package org.apache.james.modules.server;
+import org.apache.james.core.Disconnector;
import org.apache.james.protocols.lib.netty.AbstractServerFactory;
import org.apache.james.protocols.webadmin.ProtocolServerRoutes;
import org.apache.james.webadmin.Routes;
import com.google.inject.AbstractModule;
+import com.google.inject.Scopes;
import com.google.inject.multibindings.Multibinder;
public class ServerRouteModule extends AbstractModule {
@@ -34,5 +36,10 @@ public class ServerRouteModule extends AbstractModule {
Multibinder.newSetBinder(binder(), Routes.class)
.addBinding()
.to(ProtocolServerRoutes.class);
+
+ Multibinder.newSetBinder(binder(), Disconnector.class);
+
+ bind(Disconnector.class).to(Disconnector.CompositeDisconnector.class);
+ bind(Disconnector.CompositeDisconnector.class).in(Scopes.SINGLETON);
}
}
diff --git a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
index b929758a56..01b0ec4b6f 100644
--- a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
+++ b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
@@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.james.core.Disconnector;
import org.apache.james.core.Username;
import org.apache.james.imap.api.ConnectionCheck;
import org.apache.james.imap.api.ImapConfiguration;
@@ -68,7 +69,7 @@ import io.netty.util.concurrent.GlobalEventExecutor;
/**
* NIO IMAP Server which use Netty.
*/
-public class IMAPServer extends AbstractConfigurableAsyncServer implements ImapConstants, IMAPServerMBean, NettyConstants {
+public class IMAPServer extends AbstractConfigurableAsyncServer implements ImapConstants, IMAPServerMBean, NettyConstants, Disconnector {
private static final Logger LOG = LoggerFactory.getLogger(IMAPServer.class);
public static class AuthenticationConfiguration {
@@ -197,7 +198,8 @@ public class IMAPServer extends AbstractConfigurableAsyncServer implements ImapC
processor.configure(imapConfiguration);
}
- public void logout(Username user) {
+ @Override
+ public void disconnect(Username user) {
imapChannelGroup.stream()
.filter(channel -> Optional.ofNullable(channel.attr(IMAP_SESSION_ATTRIBUTE_KEY).get())
.flatMap(session -> Optional.ofNullable(session.getUserName()))
diff --git a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServerFactory.java b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServerFactory.java
index ca6a97ff42..7880a634e9 100644
--- a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServerFactory.java
+++ b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServerFactory.java
@@ -25,6 +25,8 @@ import jakarta.inject.Inject;
import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.james.core.Disconnector;
+import org.apache.james.core.Username;
import org.apache.james.filesystem.api.FileSystem;
import org.apache.james.imap.ImapSuite;
import org.apache.james.imap.api.ConnectionCheckFactory;
@@ -38,7 +40,7 @@ import org.apache.james.protocols.lib.netty.AbstractServerFactory;
import com.github.fge.lambdas.functions.ThrowingFunction;
-public class IMAPServerFactory extends AbstractServerFactory {
+public class IMAPServerFactory extends AbstractServerFactory implements Disconnector {
protected final FileSystem fileSystem;
protected final ThrowingFunction<HierarchicalConfiguration<ImmutableNode>, ImapSuite> imapSuiteProvider;
@@ -94,4 +96,12 @@ public class IMAPServerFactory extends AbstractServerFactory {
.map(IMAPServer.class::cast)
.toList();
}
+
+ @Override
+ public void disconnect(Username username) {
+ getServers()
+ .stream()
+ .map(server -> (IMAPServer) server)
+ .forEach(imapServer -> imapServer.disconnect(username));
+ }
}
diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index f2896fbaf2..d467988fbd 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -253,7 +253,7 @@ class IMAPServerTest {
testIMAPClient.connect("127.0.0.1", port)
.login(USER.asString(), USER_PASS);
- imapServer.logout(USER);
+ imapServer.disconnect(USER);
assertThatThrownBy(() -> testIMAPClient
.append("INBOX", SMALL_MESSAGE));
diff --git a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java
index 212cceedca..9fdf4195cf 100644
--- a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java
+++ b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/CertificateReloadable.java
@@ -30,8 +30,4 @@ public interface CertificateReloadable {
void reloadSSLCertificate() throws Exception;
int getPort();
-
- default void logout(Username user) {
-
- }
}
diff --git a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
index 5ed535b7d3..b56c28f904 100644
--- a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
+++ b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
@@ -33,6 +33,7 @@ import jakarta.inject.Inject;
import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.james.core.Disconnector;
import org.apache.james.core.Username;
import org.apache.james.dnsservice.api.DNSService;
import org.apache.james.dnsservice.library.netmatcher.NetMatcher;
@@ -66,7 +67,7 @@ import io.netty.util.concurrent.GlobalEventExecutor;
/**
* NIO SMTPServer which use Netty
*/
-public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServerMBean {
+public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServerMBean, Disconnector {
private static final Logger LOGGER = LoggerFactory.getLogger(SMTPServer.class);
private SMTPProtocol transport;
@@ -425,7 +426,7 @@ public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServe
}
@Override
- public void logout(Username user) {
+ public void disconnect(Username user) {
smtpChannelGroup.stream()
.filter(channel -> {
if (channel.attr(SESSION_ATTRIBUTE_KEY).get() instanceof SMTPSession smtpSession) {
diff --git a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServerFactory.java b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServerFactory.java
index 80562cd7a3..644ae1acc4 100644
--- a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServerFactory.java
+++ b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServerFactory.java
@@ -26,6 +26,8 @@ import jakarta.inject.Inject;
import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.james.core.Disconnector;
+import org.apache.james.core.Username;
import org.apache.james.dnsservice.api.DNSService;
import org.apache.james.filesystem.api.FileSystem;
import org.apache.james.metrics.api.MetricFactory;
@@ -33,7 +35,7 @@ import org.apache.james.protocols.lib.handler.ProtocolHandlerLoader;
import org.apache.james.protocols.lib.netty.AbstractConfigurableAsyncServer;
import org.apache.james.protocols.lib.netty.AbstractServerFactory;
-public class SMTPServerFactory extends AbstractServerFactory {
+public class SMTPServerFactory extends AbstractServerFactory implements Disconnector {
protected final DNSService dns;
protected final ProtocolHandlerLoader loader;
@@ -71,4 +73,11 @@ public class SMTPServerFactory extends AbstractServerFactory {
return servers;
}
+ @Override
+ public void disconnect(Username username) {
+ getServers()
+ .stream()
+ .map(server -> (SMTPServer) server)
+ .forEach(smtpServer -> smtpServer.disconnect(username));
+ }
}
diff --git a/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java b/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
index 00aebd1528..9dffaa7d53 100644
--- a/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
+++ b/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
@@ -24,6 +24,7 @@ import java.util.Set;
import jakarta.inject.Inject;
+import org.apache.james.core.Disconnector;
import org.apache.james.core.Username;
import org.apache.james.protocols.lib.netty.CertificateReloadable;
import org.apache.james.util.Port;
@@ -43,10 +44,12 @@ public class ProtocolServerRoutes implements Routes {
public static final String SERVERS = "servers";
private final Set<CertificateReloadable.Factory> servers;
+ private final Disconnector disconnector;
@Inject
- public ProtocolServerRoutes(Set<CertificateReloadable.Factory> servers) {
+ public ProtocolServerRoutes(Set<CertificateReloadable.Factory> servers, Disconnector disconnector) {
this.servers = servers;
+ this.disconnector = disconnector;
}
@Override
@@ -77,10 +80,7 @@ public class ProtocolServerRoutes implements Routes {
});
service.delete(SERVERS + "/users/:user", (request, response) -> {
- Username username = Username.of(request.params("user"));
- servers.stream()
- .flatMap(CertificateReloadable.Factory::certificatesReloadable)
- .forEach(s -> s.logout(username));
+ disconnector.disconnect(Username.of(request.params("user")));
return Responses.returnNoContent(response);
});
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
(james-project) 06/07: JAMES-4090 Allow disconnecting several user at once
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit c4d7861ae0376a9a80bc5ed0b3b686fbee3a82f7
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Wed Nov 20 11:47:33 2024 +0100
JAMES-4090 Allow disconnecting several user at once
This allows doing so with a single pass onto the channels
---
.../java/org/apache/james/DisconnectorNotifier.java | 4 +++-
.../james/jmap/routes/EventSourceRoutes.scala | 3 ++-
.../apache/james/jmap/routes/WebSocketRoutes.scala | 3 ++-
.../protocols/webadmin/ProtocolServerRoutes.java | 21 ++++++++++++++++++++-
4 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/server/data/data-api/src/main/java/org/apache/james/DisconnectorNotifier.java b/server/data/data-api/src/main/java/org/apache/james/DisconnectorNotifier.java
index e0f4b3786e..f65610f333 100644
--- a/server/data/data-api/src/main/java/org/apache/james/DisconnectorNotifier.java
+++ b/server/data/data-api/src/main/java/org/apache/james/DisconnectorNotifier.java
@@ -19,6 +19,8 @@
package org.apache.james;
+import java.util.function.Predicate;
+
import jakarta.inject.Inject;
import org.apache.james.core.Disconnector;
@@ -34,7 +36,7 @@ public interface DisconnectorNotifier extends Disconnector {
}
@Override
- public void disconnect(Username username) {
+ public void disconnect(Predicate<Username> username) {
disconnector.disconnect(username);
}
}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/EventSourceRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/EventSourceRoutes.scala
index 0d530e2f3c..c67888bd24 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/EventSourceRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/EventSourceRoutes.scala
@@ -21,7 +21,9 @@ package org.apache.james.jmap.routes
import java.nio.charset.StandardCharsets
import java.util.concurrent.atomic.AtomicReference
+import java.util.function.Predicate
import java.util.stream
+
import cats.implicits._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.numeric.Positive
@@ -52,7 +54,6 @@ import reactor.core.scala.publisher.{SFlux, SMono}
import reactor.core.scheduler.Schedulers
import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse}
-import java.util.function.Predicate
import scala.concurrent.duration._
import scala.jdk.CollectionConverters._
import scala.language.postfixOps
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala
index f2c9e7ee9d..2471f79e97 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala
@@ -21,7 +21,9 @@ package org.apache.james.jmap.routes
import java.nio.charset.StandardCharsets
import java.util.concurrent.atomic.AtomicReference
+import java.util.function.Predicate
import java.util.stream
+
import io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE
import io.netty.handler.codec.http.websocketx.WebSocketFrame
import io.netty.handler.codec.http.{HttpHeaderNames, HttpMethod}
@@ -51,7 +53,6 @@ import reactor.core.scheduler.Schedulers
import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse}
import reactor.netty.http.websocket.{WebsocketInbound, WebsocketOutbound}
-import java.util.function.Predicate
import scala.jdk.CollectionConverters._
object WebSocketRoutes {
diff --git a/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java b/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
index 9211ade17e..d0b9de4c1c 100644
--- a/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
+++ b/server/protocols/webadmin/webadmin-protocols/src/main/java/org/apache/james/protocols/webadmin/ProtocolServerRoutes.java
@@ -19,6 +19,7 @@
package org.apache.james.protocols.webadmin;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -33,15 +34,23 @@ import org.apache.james.webadmin.utils.ErrorResponder;
import org.apache.james.webadmin.utils.Responses;
import org.eclipse.jetty.http.HttpStatus;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.fge.lambdas.Throwing;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
import spark.Request;
import spark.Service;
public class ProtocolServerRoutes implements Routes {
public static final String SERVERS = "servers";
+ public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+ public static final TypeReference<List<String>> LIST_OF_STRING = new TypeReference<>() {
+
+ };
private final Set<CertificateReloadable.Factory> servers;
private final DisconnectorNotifier disconnector;
@@ -87,7 +96,17 @@ public class ProtocolServerRoutes implements Routes {
});
service.delete(SERVERS + "/users", (request, response) -> {
- disconnector.disconnect(any -> true);
+ String body = request.body();
+
+ if (Strings.isNullOrEmpty(body)) {
+ disconnector.disconnect(any -> true);
+ } else {
+ ImmutableSet<Username> userSet = OBJECT_MAPPER.readValue(body, LIST_OF_STRING)
+ .stream()
+ .map(Username::of)
+ .collect(ImmutableSet.toImmutableSet());
+ disconnector.disconnect(userSet::contains);
+ }
return Responses.returnNoContent(response);
});
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
(james-project) 03/07: JAMES-4090 Disconectors for JMAP websocket and event source
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 4f75a6fc574eb38f27a545561c82a04adaba51e8
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Sun Nov 17 10:14:28 2024 +0100
JAMES-4090 Disconectors for JMAP websocket and event source
---
.../james/jmap/rfc8621/RFC8621MethodsModule.java | 5 +++++
.../james/jmap/routes/EventSourceRoutes.scala | 20 +++++++++++++++++--
.../apache/james/jmap/routes/WebSocketRoutes.scala | 23 +++++++++++++++++++---
3 files changed, 43 insertions(+), 5 deletions(-)
diff --git a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
index cbea6c24b6..05e789d9df 100644
--- a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
+++ b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
@@ -27,6 +27,7 @@ import java.util.Set;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.james.core.Disconnector;
import org.apache.james.jmap.JMAPRoutes;
import org.apache.james.jmap.JMAPRoutesHandler;
import org.apache.james.jmap.Version;
@@ -199,6 +200,10 @@ public class RFC8621MethodsModule extends AbstractModule {
blobResolverMultibinder.addBinding().to(MessageBlobResolver.class);
blobResolverMultibinder.addBinding().to(UploadResolver.class);
blobResolverMultibinder.addBinding().to(MessagePartBlobResolver.class);
+
+ Multibinder<Disconnector> disconnectorMultibinder = Multibinder.newSetBinder(binder(), Disconnector.class);
+ disconnectorMultibinder.addBinding().to(WebSocketRoutes.class);
+ disconnectorMultibinder.addBinding().to(EventSourceRoutes.class);
}
@ProvidesIntoSet
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/EventSourceRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/EventSourceRoutes.scala
index a51f1de8a5..35f0105504 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/EventSourceRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/EventSourceRoutes.scala
@@ -22,7 +22,6 @@ package org.apache.james.jmap.routes
import java.nio.charset.StandardCharsets
import java.util.concurrent.atomic.AtomicReference
import java.util.stream
-
import cats.implicits._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.numeric.Positive
@@ -30,6 +29,7 @@ import eu.timepit.refined.refineV
import io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE
import io.netty.handler.codec.http.{HttpMethod, QueryStringDecoder}
import jakarta.inject.{Inject, Named}
+import org.apache.james.core.{Disconnector, Username}
import org.apache.james.events.{EventBus, Registration, RegistrationKey}
import org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE
import org.apache.james.jmap.JMAPUrls.EVENT_SOURCE
@@ -159,7 +159,8 @@ class EventSourceRoutes@Inject() (@Named(InjectionKeys.RFC_8621) val authenticat
@Named(JMAPInjectionKeys.JMAP) eventBus: EventBus,
pushSerializer: PushSerializer,
typeStateFactory: TypeStateFactory,
- delegationStore: DelegationStore) extends JMAPRoutes {
+ delegationStore: DelegationStore) extends JMAPRoutes with Disconnector {
+ private val connectedUsers: java.util.concurrent.ConcurrentHashMap[ClientContext, ClientContext] = new java.util.concurrent.ConcurrentHashMap[ClientContext, ClientContext]
override def routes(): stream.Stream[JMAPRoute] = stream.Stream.of(
JMAPRoute.builder
@@ -185,6 +186,7 @@ class EventSourceRoutes@Inject() (@Named(InjectionKeys.RFC_8621) val authenticat
private def registerSSE(response: HttpServerResponse, session: MailboxSession, options: EventSourceOptions): SMono[Unit] = {
val sink: Sinks.Many[OutboundMessage] = Sinks.many().unicast().onBackpressureBuffer()
val context = ClientContext(sink, new AtomicReference[Registration](), session)
+ connectedUsers.put(context, context)
val pingDisposable = options.pingPolicy
.asFlux()
@@ -207,6 +209,7 @@ class EventSourceRoutes@Inject() (@Named(InjectionKeys.RFC_8621) val authenticat
.map(asSSEEvent),
StandardCharsets.UTF_8).`then`
.doFinally(_ => context.clean())
+ .doFinally(_ => connectedUsers.remove(context))
.doFinally(_ => pingDisposable.dispose())
.`then`())
.`then`()
@@ -232,4 +235,17 @@ class EventSourceRoutes@Inject() (@Named(InjectionKeys.RFC_8621) val authenticat
.sendString(SMono.fromCallable(() => ResponseSerializer.serialize(details).toString),
StandardCharsets.UTF_8)
.`then`)
+
+ override def disconnect(username: Username): Unit = {
+ val contexts = connectedUsers.values()
+ .stream()
+ .filter(context => username.equals(context.session.getUser))
+ .toList
+
+ contexts
+ .forEach(context => {
+ context.clean()
+ connectedUsers.remove(context)
+ })
+ }
}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala
index a861a99be1..bd35b6f7c3 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala
@@ -22,12 +22,11 @@ package org.apache.james.jmap.routes
import java.nio.charset.StandardCharsets
import java.util.concurrent.atomic.AtomicReference
import java.util.stream
-
import io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE
import io.netty.handler.codec.http.websocketx.WebSocketFrame
import io.netty.handler.codec.http.{HttpHeaderNames, HttpMethod}
import jakarta.inject.{Inject, Named}
-import org.apache.james.core.Username
+import org.apache.james.core.{Disconnector, Username}
import org.apache.james.events.{EventBus, Registration, RegistrationKey}
import org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE
import org.apache.james.jmap.JMAPUrls.JMAP_WS
@@ -81,9 +80,10 @@ class WebSocketRoutes @Inject() (@Named(InjectionKeys.RFC_8621) val authenticato
pushSerializer: PushSerializer,
typeStateFactory: TypeStateFactory,
delegationStore: DelegationStore,
- metricFactory: MetricFactory) extends JMAPRoutes {
+ metricFactory: MetricFactory) extends JMAPRoutes with Disconnector {
private val openingConnectionsMetric: Metric = metricFactory.generate("jmap_websocket_opening_connections_count")
private val requestCountMetric: Metric = metricFactory.generate("jmap_websocket_requests_count")
+ private val connectedUsers: java.util.concurrent.ConcurrentHashMap[ClientContext, ClientContext] = new java.util.concurrent.ConcurrentHashMap[ClientContext, ClientContext]
override def routes(): stream.Stream[JMAPRoute] = stream.Stream.of(
JMAPRoute.builder
@@ -118,10 +118,13 @@ class WebSocketRoutes @Inject() (@Named(InjectionKeys.RFC_8621) val authenticato
frame.content().readBytes(bytes)
new String(bytes, StandardCharsets.UTF_8)
})
+ .doOnNext(_ => connectedUsers.put(context, context))
.doOnNext(_ => requestCountMetric.increment())
.flatMap(message => handleClientMessages(context)(message))
.doOnTerminate(context.clean)
.doOnCancel(context.clean)
+ .doOnTerminate(() => connectedUsers.remove(context))
+ .doOnCancel(() => connectedUsers.remove(context))
out.sendString(
SFlux.merge(Seq(responseFlux, sink.asFlux()))
@@ -151,6 +154,7 @@ class WebSocketRoutes @Inject() (@Named(InjectionKeys.RFC_8621) val authenticato
.doOnNext(newRegistration => clientContext.withRegistration(newRegistration))
.`then`(sendPushStateIfRequested(pushEnable, clientContext))
case WebSocketPushDisable => SMono.fromCallable(() => clientContext.clean())
+ .`then`(SMono.fromCallable(() => connectedUsers.remove(clientContext)))
.`then`(SMono.empty)
})
@@ -190,4 +194,17 @@ class WebSocketRoutes @Inject() (@Named(InjectionKeys.RFC_8621) val authenticato
.sendString(SMono.fromCallable(() => ResponseSerializer.serialize(details).toString),
StandardCharsets.UTF_8)
.`then`)
+
+ override def disconnect(username: Username): Unit = {
+ val contexts = connectedUsers.values()
+ .stream()
+ .filter(context => username.equals(context.session.getUser))
+ .toList
+
+ contexts
+ .forEach(context => {
+ context.clean()
+ connectedUsers.remove(context)
+ })
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org