This is an automated email from the ASF dual-hosted git repository.

quantranhong1999 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 381e20feb0deb813297904cf4ac979f0214ee033
Author: Quan Tran <[email protected]>
AuthorDate: Thu Aug 13 09:45:51 2026 +0700

    JAMES-4210 Add tests to cover ManageSieve SASL behaviors
    
    Test coverage includes:
    - PLAIN initial-response and continuation authentication;
    - invalid credentials, delegation, disabled mechanisms, and unknown 
mechanisms;
    - custom multi-step exchanges returning final server data;
    - literal initial responses split across network frames;
    - exchange cleanup after oversized responses and client disconnection.
---
 .../james/managesieveserver/AuthenticateTest.java  | 269 ++++++++++++++++++---
 .../james/managesieveserver/ManageSieveClient.java |  22 +-
 2 files changed, 257 insertions(+), 34 deletions(-)

diff --git 
a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/AuthenticateTest.java
 
b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/AuthenticateTest.java
index 1f59044735..19e3596faf 100644
--- 
a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/AuthenticateTest.java
+++ 
b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/AuthenticateTest.java
@@ -19,17 +19,106 @@
 
 package org.apache.james.managesieveserver;
 
+import static org.assertj.core.api.Assertions.assertThat;
+
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.Base64;
-
-import org.assertj.core.api.Assertions;
+import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.james.core.Username;
+import org.apache.james.protocols.api.sasl.SaslAuthenticator;
+import org.apache.james.protocols.api.sasl.SaslExchange;
+import org.apache.james.protocols.api.sasl.SaslIdentity;
+import org.apache.james.protocols.api.sasl.SaslInitialRequest;
+import org.apache.james.protocols.api.sasl.SaslMechanism;
+import org.apache.james.protocols.api.sasl.SaslStep;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
+import com.google.common.collect.ImmutableList;
+
 class AuthenticateTest {
+    private static final SaslIdentity IDENTITY = new 
SaslIdentity(Username.of("authenticated"), Username.of("authorized"));
+
+    private static byte[] bytes(String value) {
+        return value.getBytes(StandardCharsets.UTF_8);
+    }
+
+    private record CustomMechanism(RecordingExchange exchange) implements 
SaslMechanism {
+        @Override
+        public String name() {
+            return "CUSTOM";
+        }
+
+        @Override
+        public SaslExchange start(SaslInitialRequest request, 
SaslAuthenticator authenticator) {
+            return exchange;
+        }
+    }
+
+    private static class InitialResponseMechanism implements SaslMechanism {
+        private byte[] initialResponse;
+        private int closeCount;
+
+        @Override
+        public String name() {
+            return "INITIAL";
+        }
+
+        @Override
+        public SaslExchange start(SaslInitialRequest request, 
SaslAuthenticator authenticator) {
+            initialResponse = request.initialResponse().orElseThrow();
+            return new SaslExchange() {
+                @Override
+                public SaslStep firstStep() {
+                    return new SaslStep.Success(IDENTITY, Optional.empty());
+                }
+
+                @Override
+                public SaslStep onResponse(byte[] clientResponse) {
+                    throw new IllegalStateException("Initial response 
mechanism does not expect a continuation");
+                }
+
+                @Override
+                public void close() {
+                    closeCount++;
+                }
+            };
+        }
+    }
+
+    private static class RecordingExchange implements SaslExchange {
+        private final AtomicInteger closeCount = new AtomicInteger();
+        private final CountDownLatch closed = new CountDownLatch(1);
+        private byte[] clientResponse;
+
+        @Override
+        public SaslStep firstStep() {
+            return new SaslStep.Challenge(Optional.of(bytes("challenge")));
+        }
+
+        @Override
+        public SaslStep onResponse(byte[] clientResponse) {
+            this.clientResponse = clientResponse;
+            return new SaslStep.Success(IDENTITY, 
Optional.of(bytes("server-data")));
+        }
+
+        @Override
+        public void close() {
+            closeCount.incrementAndGet();
+            closed.countDown();
+        }
+    }
+
     private ManageSieveClient client;
     private final ManageSieveServerTestSystem testSystem;
 
@@ -60,7 +149,7 @@ class AuthenticateTest {
         String initialClientResponse = "\0" + 
ManageSieveServerTestSystem.USERNAME.asString() + "\0" + 
ManageSieveServerTestSystem.PASSWORD + "wrong";
         this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + 
Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8))
 + "\"");
         ManageSieveClient.ServerResponse authenticationResponse = 
this.client.readResponse();
-        
Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+        
assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
     }
 
     @Test
@@ -68,7 +157,18 @@ class AuthenticateTest {
         String initialClientResponse = "\0" + 
ManageSieveServerTestSystem.USERNAME.asString() + "not-existing" + "\0" + "pwd";
         this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + 
Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8))
 + "\"");
         ManageSieveClient.ServerResponse authenticationResponse = 
this.client.readResponse();
-        
Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+        
assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+    }
+
+    @Test
+    void plainLoginShouldRejectDelegation() throws IOException {
+        String initialClientResponse = "other-user\0" + 
ManageSieveServerTestSystem.USERNAME.asString() + "\0" + 
ManageSieveServerTestSystem.PASSWORD;
+        this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + 
Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8))
 + "\"");
+
+        ManageSieveClient.ServerResponse authenticationResponse = 
this.client.readResponse();
+
+        
assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+        
assertThat(authenticationResponse.explanation()).contains("authentication 
failed");
     }
 
     @Test
@@ -76,7 +176,7 @@ class AuthenticateTest {
         String initialClientResponse = "\0" + 
ManageSieveServerTestSystem.USERNAME.asString() + "\0";
         this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + 
Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8))
 + "\"");
         ManageSieveClient.ServerResponse authenticationResponse = 
this.client.readResponse();
-        
Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+        
assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
     }
 
     // The SASL PLAIN standard (https://datatracker.ietf.org/doc/html/rfc4616) 
defines the following message:
@@ -87,7 +187,7 @@ class AuthenticateTest {
         String initialClientResponse = 
ManageSieveServerTestSystem.USERNAME.asString() + "\0" + 
ManageSieveServerTestSystem.PASSWORD;
         this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + 
Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8))
 + "\"");
         ManageSieveClient.ServerResponse authenticationResponse = 
this.client.readResponse();
-        
Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+        
assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
     }
 
     // The SASL PLAIN standard defines UTF8NUL as separator. To stay 
compatible with older versions of James,
@@ -97,7 +197,7 @@ class AuthenticateTest {
         String initialClientResponse = " " + 
ManageSieveServerTestSystem.USERNAME.asString() + " " + 
ManageSieveServerTestSystem.PASSWORD;
         this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + 
initialClientResponse + "\"");
         ManageSieveClient.ServerResponse authenticationResponse = 
this.client.readResponse();
-        
Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+        
assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
     }
 
     // This tests the combination of both lenient behaviors above.
@@ -106,7 +206,7 @@ class AuthenticateTest {
         String initialClientResponse = 
ManageSieveServerTestSystem.USERNAME.asString() + " " + 
ManageSieveServerTestSystem.PASSWORD;
         this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + 
initialClientResponse + "\"");
         ManageSieveClient.ServerResponse authenticationResponse = 
this.client.readResponse();
-        
Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+        
assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
     }
 
     @Test
@@ -114,7 +214,7 @@ class AuthenticateTest {
         String initialClientResponse = "\0" + 
ManageSieveServerTestSystem.USERNAME.asString() + "\0" + 
ManageSieveServerTestSystem.PASSWORD;
         this.client.sendCommand("AUTHENTICATE PLAIN \"" + 
Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8))
 + "\"");
         ManageSieveClient.ServerResponse authenticationResponse = 
this.client.readResponse();
-        
Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+        
assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
     }
 
     @Test
@@ -122,33 +222,65 @@ class AuthenticateTest {
         String initialClientResponse = "\0" + 
ManageSieveServerTestSystem.USERNAME.asString() + "\0" + 
ManageSieveServerTestSystem.PASSWORD;
         this.client.sendCommand("AUTHENTICATE \"PLAIN\" " + 
Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8)));
         ManageSieveClient.ServerResponse authenticationResponse = 
this.client.readResponse();
-        
Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+        
assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+    }
+
+    @Test
+    void shouldRejectUnknownSaslMechanism() throws IOException {
+        this.client.sendCommand("AUTHENTICATE {8+}");
+        this.client.sendCommand("BAD\"\r\nOK");
+
+        ManageSieveClient.ServerResponse authenticationResponse = 
this.client.readResponse();
+
+        
assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+        assertThat(authenticationResponse.explanation()).contains("Unknown 
SASL mechanism");
+        assertThat(authenticationResponse.responseLines()).isEmpty();
+    }
+
+    @Test
+    void 
shouldReportDisabledPlainAsUnknownSaslMechanismInsteadOfRequiringEncryption() 
throws Exception {
+        HierarchicalConfiguration<ImmutableNode> configuration = 
FileConfigurationProvider.getConfig(
+            ClassLoader.getSystemResourceAsStream("managesieveserver.xml"));
+        configuration.addProperty("auth.plainAuthEnabled", false);
+        client.disconnect();
+        testSystem.manageSieveServer.destroy();
+        testSystem.setUp(configuration);
+        client = new ManageSieveClient();
+        client.connect(testSystem.getBindedIP(), testSystem.getBindedPort());
+        client.readResponse();
+
+        client.sendCommand("AUTHENTICATE \"PLAIN\"");
+        ManageSieveClient.ServerResponse authenticationResponse = 
client.readResponse();
+
+        
assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+        assertThat(authenticationResponse.responseCode()).isEmpty();
+        assertThat(authenticationResponse.explanation()).contains("Unknown 
SASL mechanism");
     }
 
     @Test
     void plainLoginWithContinuationShouldSucceed() throws IOException {
         this.client.sendCommand("AUTHENTICATE \"PLAIN\"");
-        ManageSieveClient.ServerResponse continuationResponse = 
this.client.readResponse();
-        
Assertions.assertThat(continuationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.CONTINUATION);
-        
Assertions.assertThat(continuationResponse.explanation().get()).isEqualTo("");
+        ManageSieveClient.ServerResponse continuationResponse = 
this.client.readSaslChallenge();
+        
assertThat(continuationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.CONTINUATION);
+        assertThat(continuationResponse.explanation().get()).isEqualTo("");
 
         String initialClientResponse = "\0" + 
ManageSieveServerTestSystem.USERNAME.asString() + "\0" + 
ManageSieveServerTestSystem.PASSWORD;
         
this.client.sendCommand(Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8)));
         ManageSieveClient.ServerResponse authenticationResponse = 
this.client.readResponse();
-        
Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+        
assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
     }
 
     @Test
     void plainLoginWithContinuationCanBeAborted() throws IOException {
         this.client.sendCommand("AUTHENTICATE \"PLAIN\"");
-        ManageSieveClient.ServerResponse continuationResponse = 
this.client.readResponse();
-        
Assertions.assertThat(continuationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.CONTINUATION);
-        
Assertions.assertThat(continuationResponse.explanation().get()).isEqualTo("");
+        ManageSieveClient.ServerResponse continuationResponse = 
this.client.readSaslChallenge();
+        
assertThat(continuationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.CONTINUATION);
+        assertThat(continuationResponse.explanation().get()).isEqualTo("");
 
         this.client.sendCommand("*");
         ManageSieveClient.ServerResponse authenticationResponse = 
this.client.readResponse();
-        
Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
-        
Assertions.assertThat(authenticationResponse.explanation()).get().isEqualTo("Authentication
 failed with: authentication aborted by client");
+        
assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+        
assertThat(authenticationResponse.explanation()).get().isEqualTo("Authentication
 failed with: authentication aborted by client");
     }
 
     @Test
@@ -158,19 +290,19 @@ class AuthenticateTest {
 
         this.client.sendCommand(command);
         ManageSieveClient.ServerResponse firstAuthenticationResponse = 
this.client.readResponse();
-        
Assertions.assertThat(firstAuthenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+        
assertThat(firstAuthenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
 
         this.client.sendCommand(command);
         ManageSieveClient.ServerResponse secondAuthenticationResponse = 
this.client.readResponse();
-        
Assertions.assertThat(secondAuthenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
-        
Assertions.assertThat(secondAuthenticationResponse.explanation()).get().isEqualTo("already
 authenticated");
+        
assertThat(secondAuthenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+        
assertThat(secondAuthenticationResponse.explanation()).get().isEqualTo("already 
authenticated");
     }
 
     @Test
     void unauthenticateInUnauthenticatedStateShouldFail() throws IOException {
         this.client.sendCommand("UNAUTHENTICATE");
         ManageSieveClient.ServerResponse response = this.client.readResponse();
-        
Assertions.assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+        
assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
     }
 
     @Test
@@ -179,28 +311,28 @@ class AuthenticateTest {
 
         this.client.sendCommand("UNAUTHENTICATE");
         ManageSieveClient.ServerResponse response = this.client.readResponse();
-        
Assertions.assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+        
assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
     }
 
     @Test
     void authenticatedStateUnlocksNewCommands() throws IOException {
         this.client.sendCommand("LISTSCRIPTS");
         ManageSieveClient.ServerResponse unauthenticatedResponse = 
this.client.readResponse();
-        
Assertions.assertThat(unauthenticatedResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+        
assertThat(unauthenticatedResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
 
         this.authenticatePlain();
 
         this.client.sendCommand("LISTSCRIPTS");
         ManageSieveClient.ServerResponse authenticatedResponse = 
this.client.readResponse();
-        
Assertions.assertThat(authenticatedResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+        
assertThat(authenticatedResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
 
         this.client.sendCommand("UNAUTHENTICATE");
         ManageSieveClient.ServerResponse response = this.client.readResponse();
-        
Assertions.assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+        
assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
 
         this.client.sendCommand("LISTSCRIPTS");
         ManageSieveClient.ServerResponse loggedOutResponse = 
this.client.readResponse();
-        
Assertions.assertThat(loggedOutResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+        
assertThat(loggedOutResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
     }
 
     // The server actually disconnects but isConnected still returns True.
@@ -211,8 +343,8 @@ class AuthenticateTest {
     void logoutShouldWorkInUnauthenticatedState() throws IOException, 
InterruptedException {
         this.client.sendCommand("LOGOUT");
         ManageSieveClient.ServerResponse response = this.client.readResponse();
-        
Assertions.assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
-        Assertions.assertThat(this.client.isConnected()).isFalse();
+        
assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+        assertThat(this.client.isConnected()).isFalse();
     }
 
     // The server actually disconnects but isConnected still returns True.
@@ -225,14 +357,85 @@ class AuthenticateTest {
 
         this.client.sendCommand("LOGOUT");
         ManageSieveClient.ServerResponse response = this.client.readResponse();
-        
Assertions.assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
-        Assertions.assertThat(this.client.isConnected()).isFalse();
+        
assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+        assertThat(this.client.isConnected()).isFalse();
+    }
+
+    @Test
+    void shouldDriveCustomMultiStepMechanismAndReturnFinalServerData() throws 
Exception {
+        RecordingExchange exchange = new RecordingExchange();
+        useSaslMechanisms(ImmutableList.of(new CustomMechanism(exchange)));
+
+        client.sendCommand("AUTHENTICATE \"CUSTOM\"");
+        
assertThat(client.readSaslChallenge().explanation()).contains("Y2hhbGxlbmdl");
+
+        client.sendCommand("\"cmVzcG9uc2U=\"");
+        
assertThat(client.readSaslSuccessData()).containsExactly(bytes("server-data"));
+
+        client.sendCommand("CAPABILITY");
+        assertThat(client.readResponse().responseLines()).contains("\"OWNER\" 
\"authorized\"");
+        assertThat(exchange.clientResponse).containsExactly(bytes("response"));
+        assertThat(exchange.closeCount).hasValue(1);
+    }
+
+    @Test
+    void shouldAcceptLiteralInitialResponseAcrossNetworkFrames() throws 
Exception {
+        InitialResponseMechanism mechanism = new InitialResponseMechanism();
+        useSaslMechanisms(ImmutableList.of(mechanism));
+        String encodedResponse = "cmVzcG9uc2U=";
+
+        client.sendCommand("AUTHENTICATE \"INITIAL\" {" + 
encodedResponse.length() + "+}");
+        client.sendCommand(encodedResponse);
+
+        
assertThat(client.readResponse().responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+        
assertThat(mechanism.initialResponse).containsExactly(bytes("response"));
+        assertThat(mechanism.closeCount).isEqualTo(1);
+    }
+
+    @Test
+    void shouldCloseActiveExchangeWhenClientResponseExceedsMaximumLineLength() 
throws Exception {
+        RecordingExchange exchange = new RecordingExchange();
+        useSaslMechanisms(ImmutableList.of(new CustomMechanism(exchange)));
+        client.sendCommand("AUTHENTICATE \"CUSTOM\"");
+        client.readSaslChallenge();
+
+        client.sendCommand("A".repeat(9000));
+
+        ManageSieveClient.ServerResponse response = client.readResponse();
+        
assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+        assertThat(response.explanation()).contains("Maximum command line 
length exceeded");
+        assertThat(exchange.closeCount).hasValue(1);
+
+        client.sendCommand("CAPABILITY");
+        
assertThat(client.readResponse().responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+    }
+
+    @Test
+    void shouldCloseActiveExchangeWhenClientDisconnects() throws Exception {
+        RecordingExchange exchange = new RecordingExchange();
+        useSaslMechanisms(ImmutableList.of(new CustomMechanism(exchange)));
+        client.sendCommand("AUTHENTICATE \"CUSTOM\"");
+        client.readSaslChallenge();
+
+        client.disconnect();
+
+        assertThat(exchange.closed.await(1, TimeUnit.SECONDS)).isTrue();
+        assertThat(exchange.closeCount).hasValue(1);
+    }
+
+    private void useSaslMechanisms(ImmutableList<SaslMechanism> 
saslMechanisms) throws Exception {
+        client.disconnect();
+        testSystem.manageSieveServer.destroy();
+        testSystem.setUp(saslMechanisms);
+        client = new ManageSieveClient();
+        client.connect(testSystem.getBindedIP(), testSystem.getBindedPort());
+        client.readResponse();
     }
 
     void authenticatePlain() throws IOException {
         String initialClientResponse = "\0" + 
ManageSieveServerTestSystem.USERNAME.asString() + "\0" + 
ManageSieveServerTestSystem.PASSWORD;
         this.client.sendCommand("AUTHENTICATE \"PLAIN\" \"" + 
Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8))
 + "\"");
         ManageSieveClient.ServerResponse authenticationResponse = 
this.client.readResponse();
-        
Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+        
assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
     }
 }
diff --git 
a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveClient.java
 
b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveClient.java
index 0f1e7cb9e1..4a2a101b4d 100644
--- 
a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveClient.java
+++ 
b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveClient.java
@@ -26,6 +26,7 @@ import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Base64;
 import java.util.Optional;
 
 import org.apache.commons.lang3.EnumUtils;
@@ -93,7 +94,7 @@ public class ManageSieveClient extends SocketClient {
                 response = new ServerResponse(responseType, responseCode, 
explanation, lines);
             } else if (tokens[0].equals("+")) {
                 Optional<String> explanation = 
Optional.of(tokens[1].substring(1, tokens[1].length() - 1));
-                response = new ServerResponse(ResponseType.CONTINUATION, 
Optional.empty(), explanation, new ArrayList<String>());
+                response = new ServerResponse(ResponseType.CONTINUATION, 
Optional.empty(), explanation, new ArrayList<>());
             } else {
                 lines.addLast(line);
             }
@@ -101,6 +102,25 @@ public class ManageSieveClient extends SocketClient {
         return response;
     }
 
+    public ServerResponse readSaslChallenge() throws IOException {
+        String line = this.reader.readLine();
+        if (line.startsWith("\"") && line.endsWith("\"")) {
+            return new ServerResponse(ResponseType.CONTINUATION, 
Optional.empty(),
+                Optional.of(line.substring(1, line.length() - 1)), new 
ArrayList<>());
+        }
+        throw new IOException("Expected a ManageSieve SASL challenge but 
received: " + line);
+    }
+
+    public byte[] readSaslSuccessData() throws IOException {
+        String line = this.reader.readLine();
+        String prefix = "OK (SASL \"";
+        String suffix = "\")";
+        if (line.startsWith(prefix) && line.endsWith(suffix)) {
+            return Base64.getDecoder().decode(line.substring(prefix.length(), 
line.length() - suffix.length()));
+        }
+        throw new IOException("Expected a ManageSieve SASL success response 
but received: " + line);
+    }
+
     public void sendCommand(String command) throws IOException {
         this.writer.write(command + "\r\n");
         this.writer.flush();


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to