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 2026/01/19 16:18:19 UTC
(james-project) 05/17: test(managesieve): test authentication of managesieve server
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 0c8b5d20d40405a93621d25e3c70a7180c8b488f
Author: Felix Auringer <fe...@protonmail.com>
AuthorDate: Fri Aug 15 08:05:13 2025 +0200
test(managesieve): test authentication of managesieve server
---
.../org/apache/james/jwt/OidcTokenFixture.java | 2 +-
server/protocols/protocols-managesieve/pom.xml | 53 ++
.../james/managesieveserver/AuthenticateTest.java | 220 ++++++++
.../james/managesieveserver/CapabilityTest.java | 74 +++
.../james/managesieveserver/ManageSieveClient.java | 104 ++++
.../ManageSieveServerTestSystem.java | 93 ++++
.../apache/james/managesieveserver/OIDCTest.java | 568 +++++++++++++++++++++
.../src/test/resources/managesieveserver-oidc.xml | 16 +
.../src/test/resources/managesieveserver.xml | 9 +
9 files changed, 1138 insertions(+), 1 deletion(-)
diff --git a/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcTokenFixture.java b/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcTokenFixture.java
index d07646e5c4..dcf09137ba 100644
--- a/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcTokenFixture.java
+++ b/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcTokenFixture.java
@@ -107,7 +107,7 @@ public class OidcTokenFixture {
"}";
public static final String CLAIM = "email_address";
- // "email_address": "user@domain.org"
+ public static final String USER_EMAIL_ADDRESS = "user@domain.org";
public static final String VALID_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Inc4MFBzNUlhc24tYUdXbXcyVHJ4RGlOY2FocEgyc1h6NXBxZGhBbDlIWGMifQ.eyJleHAiOjM5Mzk1MDYxNjcsImlhdCI6MTYzOTUwNTg2NywiYXV0aF90aW1lIjozNjM5NTA1ODQxLCJqdGkiOiJjMjQ5ZTBkNi1jY2JiLTRmZDAtODI5Yi04OTM1MjczN2YzZGIiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvcmVhbG0xIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjIwNDUyNzFiLWMxYmItNDJiOC1hMTkwLThlYWI1MmYzYmEwOSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFjY291bnQtY29uc29sZSIsIm5 [...]
public static final String VALID_TOKEN_HAS_NOT_KID = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjM5Mzk1MDYxNjcsImlhdCI6MTYzOTUwNTg2NywiYXV0aF90aW1lIjozNjM5NTA1ODQxLCJqdGkiOiJjMjQ5ZTBkNi1jY2JiLTRmZDAtODI5Yi04OTM1MjczN2YzZGIiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvcmVhbG0xIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjIwNDUyNzFiLWMxYmItNDJiOC1hMTkwLThlYWI1MmYzYmEwOSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFjY291bnQtY29uc29sZSIsIm5vbmNlIjoiNWUyOGJjNTAtODE5NS00NjM3LThmMWEtYWUzNWFlYTk0NTc1I [...]
public static final String VALID_TOKEN_HAS_NOT_FOUND_KID = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im5vdEZvdW5kIn0.eyJleHAiOjM5Mzk1MDYxNjcsImlhdCI6MTYzOTUwNTg2NywiYXV0aF90aW1lIjozNjM5NTA1ODQxLCJqdGkiOiJjMjQ5ZTBkNi1jY2JiLTRmZDAtODI5Yi04OTM1MjczN2YzZGIiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvcmVhbG0xIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjIwNDUyNzFiLWMxYmItNDJiOC1hMTkwLThlYWI1MmYzYmEwOSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFjY291bnQtY29uc29sZSIsIm5vbmNlIjoiNWUyOGJjNTAtODE5NS00 [...]
diff --git a/server/protocols/protocols-managesieve/pom.xml b/server/protocols/protocols-managesieve/pom.xml
index 3a96075ebc..0c7f1e5b1d 100644
--- a/server/protocols/protocols-managesieve/pom.xml
+++ b/server/protocols/protocols-managesieve/pom.xml
@@ -13,14 +13,42 @@
<name>Apache James :: Server :: ManageSieve</name>
<dependencies>
+ <dependency>
+ <groupId>${james.groupId}</groupId>
+ <artifactId>james-server-data-file</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${james.groupId}</groupId>
+ <artifactId>james-server-data-memory</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${james.groupId}</groupId>
+ <artifactId>james-server-filesystem-api</artifactId>
+ </dependency>
<dependency>
<groupId>${james.groupId}</groupId>
<artifactId>james-server-filesystem-api</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${james.groupId}</groupId>
+ <artifactId>james-server-jwt</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>${james.groupId}</groupId>
<artifactId>james-server-protocols-library</artifactId>
</dependency>
+ <dependency>
+ <groupId>${james.groupId}</groupId>
+ <artifactId>james-server-protocols-library</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>${james.groupId}</groupId>
<artifactId>james-server-util</artifactId>
@@ -30,6 +58,16 @@
<artifactId>testing-base</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>${james.protocols.groupId}</groupId>
+ <artifactId>protocols-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${james.protocols.groupId}</groupId>
+ <artifactId>protocols-api</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>${james.protocols.groupId}</groupId>
<artifactId>protocols-managesieve</artifactId>
@@ -38,6 +76,11 @@
<groupId>${james.protocols.groupId}</groupId>
<artifactId>protocols-netty</artifactId>
</dependency>
+ <dependency>
+ <groupId>commons-net</groupId>
+ <artifactId>commons-net</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
@@ -54,6 +97,16 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mock-server</groupId>
+ <artifactId>mockserver-netty</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
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
new file mode 100644
index 0000000000..b9d0829aa3
--- /dev/null
+++ b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/AuthenticateTest.java
@@ -0,0 +1,220 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.managesieveserver;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+public class AuthenticateTest {
+ private ManageSieveClient client;
+ private final ManageSieveServerTestSystem testSystem;
+
+ public AuthenticateTest() throws Exception {
+ this.testSystem = new ManageSieveServerTestSystem();
+ }
+
+ @BeforeEach
+ void setUp() throws Exception {
+ this.testSystem.setUp();
+ this.client = new ManageSieveClient();
+ this.client.connect(this.testSystem.getBindedIP(), this.testSystem.getBindedPort());
+ this.client.readResponse();
+ }
+
+ @AfterEach
+ void tearDown() {
+ this.testSystem.manageSieveServer.destroy();
+ }
+
+ @Test
+ void plainLoginWithCorrectCredentialsShouldSucceed() throws IOException {
+ this.authenticatePlain();
+ }
+
+ @Test
+ void plainLoginWithWrongPasswordShouldNotSucceed() throws IOException {
+ 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);
+ }
+
+ @Test
+ void plainLoginWithNotExistingUserShouldNotSucceed() throws IOException {
+ 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);
+ }
+
+ @Test
+ void plainLoginWithoutPasswordShouldNotSucceed() throws IOException {
+ 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);
+ }
+
+ // The SASL PLAIN standard (https://datatracker.ietf.org/doc/html/rfc4616) defines the following message:
+ // message = [authzid] UTF8NUL authcid UTF8NUL passwd
+ // The current code is more lenient.
+ @Disabled
+ @Test
+ void plainLoginWithMalformedMessageShouldNotSucceed() throws IOException {
+ 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.NO);
+ }
+
+ @Test
+ void plainLoginWithoutMechanismQuotesShouldNotSucceed() 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.NO);
+ }
+
+ @Test
+ void plainLoginWithoutInitialResponseQuotesShouldNotSucceed() 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.NO);
+ }
+
+ @Test
+ void plainLoginWithContinuationShouldSucceed() throws IOException {
+ this.client.sendCommand("AUTHENTICATE \"PLAIN\"");
+ ManageSieveClient.ServerResponse continuationResponse = this.client.readResponse();
+ Assertions.assertThat(continuationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+ Assertions.assertThat(continuationResponse.responseLines()).containsExactly("\"\"");
+
+ 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);
+ }
+
+ @Test
+ void plainLoginWithContinuationCanBeAborted() throws IOException {
+ this.client.sendCommand("AUTHENTICATE \"PLAIN\"");
+ ManageSieveClient.ServerResponse continuationResponse = this.client.readResponse();
+ Assertions.assertThat(continuationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+ Assertions.assertThat(continuationResponse.responseLines()).containsExactly("\"\"");
+
+ this.client.sendCommand("\"*\"");
+ ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ Assertions.assertThat(authenticationResponse.explanation()).get().isEqualTo("authentication aborted");
+ }
+
+ @Test
+ void doubleAuthenticationShouldFail() throws IOException {
+ String initialClientResponse = ("\0" + ManageSieveServerTestSystem.USERNAME.asString() + "\0" + ManageSieveServerTestSystem.PASSWORD);
+ String command = "AUTHENTICATE \"PLAIN\" \"" + Base64.getEncoder().encodeToString(initialClientResponse.getBytes(StandardCharsets.UTF_8)) + "\"";
+
+ this.client.sendCommand(command);
+ ManageSieveClient.ServerResponse firstAuthenticationResponse = this.client.readResponse();
+ Assertions.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");
+ }
+
+ @Test
+ void unauthenticateInUnauthenticatedStateShouldFail() throws IOException {
+ this.client.sendCommand("UNAUTHENTICATE");
+ ManageSieveClient.ServerResponse response = this.client.readResponse();
+ Assertions.assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ }
+
+ @Test
+ void unauthenticateInAuthenticatedStateShouldSucceed() throws IOException {
+ this.authenticatePlain();
+
+ this.client.sendCommand("UNAUTHENTICATE");
+ ManageSieveClient.ServerResponse response = this.client.readResponse();
+ Assertions.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);
+
+ this.authenticatePlain();
+
+ this.client.sendCommand("LISTSCRIPTS");
+ ManageSieveClient.ServerResponse authenticatedResponse = this.client.readResponse();
+ Assertions.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);
+
+ this.client.sendCommand("LISTSCRIPTS");
+ ManageSieveClient.ServerResponse loggedOutResponse = this.client.readResponse();
+ Assertions.assertThat(loggedOutResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ }
+
+ // The server actually disconnects but isConnected still returns True.
+ // Even when adding a delay, it still returns True.
+ // There is probably something else broken with this test.
+ @Disabled
+ @Test
+ 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();
+ }
+
+ // The server actually disconnects but isConnected still returns True.
+ // Even when adding a delay, it still returns True.
+ // There is probably something else broken with this test.
+ @Disabled
+ @Test
+ void logoutShouldWorkInAuthenticatedState() throws IOException, InterruptedException {
+ this.authenticatePlain();
+
+ this.client.sendCommand("LOGOUT");
+ ManageSieveClient.ServerResponse response = this.client.readResponse();
+ Assertions.assertThat(response.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+ Assertions.assertThat(this.client.isConnected()).isFalse();
+ }
+
+ 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);
+ }
+}
diff --git a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/CapabilityTest.java b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/CapabilityTest.java
new file mode 100644
index 0000000000..f13ffe3d8f
--- /dev/null
+++ b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/CapabilityTest.java
@@ -0,0 +1,74 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.managesieveserver;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+class CapabilityTest {
+ private final ManageSieveServerTestSystem testSystem;
+
+ public CapabilityTest() throws Exception {
+ this.testSystem = new ManageSieveServerTestSystem();
+ }
+
+ @AfterEach
+ void tearDown() {
+ this.testSystem.manageSieveServer.destroy();
+ }
+
+ @Test
+ void shouldAnnounceOnlyPlainAuthenticationWithDefaultConfig() throws Exception {
+ this.testSystem.setUp();
+
+ ManageSieveClient client = new ManageSieveClient();
+ client.connect(this.testSystem.getBindedIP(), this.testSystem.getBindedPort());
+ ManageSieveClient.ServerResponse initialGreeting = client.readResponse();
+ Assertions.assertThat(getSASLMechanisms(initialGreeting)).containsExactlyInAnyOrder("PLAIN");
+
+ client.sendCommand("CAPABILITY");
+ ManageSieveClient.ServerResponse capabilityResponse = client.readResponse();
+ Assertions.assertThat(getSASLMechanisms(capabilityResponse)).containsExactlyInAnyOrder("PLAIN");
+ }
+
+ @Test
+ void shouldAnnouncePlainAndOauthWhenConfigured() throws Exception {
+ this.testSystem.setUp("managesieveserver-oidc.xml");
+
+ ManageSieveClient client = new ManageSieveClient();
+ client.connect(this.testSystem.getBindedIP(), this.testSystem.getBindedPort());
+ ManageSieveClient.ServerResponse initialGreeting = client.readResponse();
+ Assertions.assertThat(getSASLMechanisms(initialGreeting)).containsExactlyInAnyOrder("PLAIN", "XOAUTH2", "OAUTHBEARER");
+
+ client.sendCommand("CAPABILITY");
+ ManageSieveClient.ServerResponse capabilityResponse = client.readResponse();
+ Assertions.assertThat(getSASLMechanisms(capabilityResponse)).containsExactlyInAnyOrder("PLAIN", "XOAUTH2", "OAUTHBEARER");
+ }
+
+ private String[] getSASLMechanisms(ManageSieveClient.ServerResponse response) {
+ String saslLine = Assertions.assertThat(response.responseLines())
+ .filteredOn(line -> line.startsWith("\"SASL\""))
+ .hasSize(1)
+ .first()
+ .actual();
+ return saslLine.substring("\"SASL\" \"".length(), saslLine.length() - 1).split(" ");
+ }
+}
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
new file mode 100644
index 0000000000..e712489b70
--- /dev/null
+++ b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveClient.java
@@ -0,0 +1,104 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.managesieveserver;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Optional;
+
+import org.apache.commons.lang3.EnumUtils;
+import org.apache.commons.net.SocketClient;
+import org.apache.commons.net.io.CRLFLineReader;
+
+public class ManageSieveClient extends SocketClient {
+ private static final String ENCODING = StandardCharsets.UTF_8.name();
+
+ enum ResponseType {
+ BYE,
+ NO,
+ OK;
+ }
+
+ record ServerResponse(
+ ResponseType responseType,
+ Optional<String> responseCode,
+ Optional<String> explanation,
+ ArrayList<String> responseLines
+ ) {}
+
+ private BufferedReader reader;
+ private BufferedWriter writer;
+
+ @Override
+ protected void _connectAction_() throws IOException {
+ super._connectAction_();
+ this.reader = new CRLFLineReader(new InputStreamReader(_input_, ENCODING));
+ this.writer = new BufferedWriter(new OutputStreamWriter(_output_, ENCODING));
+ }
+
+ @Override
+ public void disconnect() throws IOException {
+ super.disconnect();
+ this.reader = null;
+ this.writer = null;
+ }
+
+ public ServerResponse readResponse() throws IOException {
+ ServerResponse response = null;
+ ArrayList<String> lines = new ArrayList<>();
+ while (response == null) {
+ String line = this.reader.readLine();
+ String[] tokens = line.split(" ", 3);
+ if (EnumUtils.isValidEnumIgnoreCase(ResponseType.class, tokens[0])) {
+ ResponseType responseType = EnumUtils.getEnumIgnoreCase(ResponseType.class, tokens[0]);
+ Optional<String> responseCode = Optional.empty();
+ Optional<String> explanation = Optional.empty();
+ if (tokens.length == 2 && tokens[1].startsWith("(")) {
+ responseCode = Optional.of(tokens[1].substring(1, tokens[1].length() - 1));
+ } else if (tokens.length == 2 && !tokens[1].startsWith("(")) {
+ explanation = Optional.of(tokens[1]);
+ } else if (tokens.length == 3 && tokens[1].startsWith("(")) {
+ responseCode = Optional.of(tokens[1].substring(1, tokens[1].length() - 1));
+ explanation = Optional.of(tokens[2]);
+ } else if (tokens.length == 3 && !tokens[1].startsWith("(")) {
+ explanation = Optional.of(tokens[1] + " " + tokens[2]);
+ }
+ if (explanation.isPresent() && explanation.get().charAt(0) == '"' && explanation.get().charAt(explanation.get().length() - 1) == '"') {
+ explanation = Optional.of(explanation.get().substring(1, explanation.get().length() - 1));
+ }
+
+ response = new ServerResponse(responseType, responseCode, explanation, lines);
+ } else {
+ lines.addLast(line);
+ }
+ }
+ return response;
+ }
+
+ public void sendCommand(String command) throws IOException {
+ this.writer.write(command + "\r\n");
+ this.writer.flush();
+ }
+}
diff --git a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveServerTestSystem.java b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveServerTestSystem.java
new file mode 100644
index 0000000000..185c40e48e
--- /dev/null
+++ b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveServerTestSystem.java
@@ -0,0 +1,93 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.managesieveserver;
+
+import java.net.InetAddress;
+
+import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.james.core.Username;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.filesystem.api.mock.MockFileSystem;
+import org.apache.james.managesieve.core.CoreProcessor;
+import org.apache.james.managesieve.jsieve.Parser;
+import org.apache.james.managesieve.transcode.ArgumentParser;
+import org.apache.james.managesieve.transcode.ManageSieveProcessor;
+import org.apache.james.managesieveserver.netty.ManageSieveServer;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.sieverepository.file.SieveFileRepository;
+import org.apache.james.user.memory.MemoryUsersRepository;
+
+class ManageSieveServerTestSystem {
+ private static final int MAX_LINE_LENGTH = 8000;
+ private static final DomainList NO_DOMAIN_LIST = null;
+ public static final String PASSWORD = "bobpwd";
+ public static final Username USERNAME = Username.of("bob");
+
+
+ private ManageSieveProcessor manageSieveProcessor;
+ public ManageSieveServer manageSieveServer;
+ private MemoryUsersRepository usersRepository;
+ private MockFileSystem fileSystem;
+
+ public ManageSieveServerTestSystem() throws Exception {
+ this.usersRepository = MemoryUsersRepository.withoutVirtualHosting(NO_DOMAIN_LIST);
+ this.usersRepository.addUser(USERNAME, PASSWORD);
+ this.fileSystem = new MockFileSystem();
+ this.manageSieveProcessor = new ManageSieveProcessor(
+ new ArgumentParser(
+ new CoreProcessor(
+ new SieveFileRepository(this.fileSystem),
+ this.usersRepository,
+ new Parser()
+ )
+ )
+ );
+ }
+
+ public void setUp(HierarchicalConfiguration<ImmutableNode> configuration) throws Exception {
+ this.fileSystem.clear();
+ this.manageSieveServer = new ManageSieveServer(
+ MAX_LINE_LENGTH,
+ this.manageSieveProcessor
+ );
+ this.manageSieveServer.setFileSystem(this.fileSystem);
+ this.manageSieveServer.configure(configuration);
+ this.manageSieveServer.init();
+ }
+
+ public void setUp(String configFilePath) throws Exception {
+ HierarchicalConfiguration<ImmutableNode> configuration = FileConfigurationProvider.getConfig(ClassLoader.getSystemResourceAsStream(configFilePath));
+ setUp(configuration);
+ }
+
+ public void setUp() throws Exception {
+ setUp("managesieveserver.xml");
+ }
+
+ public InetAddress getBindedIP() {
+ return new ProtocolServerUtils(this.manageSieveServer).retrieveBindedAddress().getAddress();
+ }
+
+ public int getBindedPort() {
+ return new ProtocolServerUtils(this.manageSieveServer).retrieveBindedAddress().getPort();
+ }
+}
diff --git a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/OIDCTest.java b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/OIDCTest.java
new file mode 100644
index 0000000000..d331c24f71
--- /dev/null
+++ b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/OIDCTest.java
@@ -0,0 +1,568 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.managesieveserver;
+
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.james.jwt.OidcTokenFixture;
+import org.apache.james.protocols.api.OIDCSASLHelper;
+import org.apache.james.protocols.lib.mock.ConfigLoader;
+import org.apache.james.util.ClassLoaderUtils;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
+import org.mockserver.integration.ClientAndServer;
+import org.mockserver.model.HttpRequest;
+import org.mockserver.model.HttpResponse;
+
+public class OIDCTest {
+ private static final String DISCOVERY_URI_PATH = "/oidc/.well-known/openid-configuration";
+ private static final String JWKS_URI_PATH = "/oidc/jwks";
+ private static final String INTROSPECTION_URI_PATH = "/oidc/introspect";
+ private static final String SCOPE = "scope";
+ private static final String USERINFO_URI_PATH = "/oidc/userinfo";
+ public static final String VALID_XOAUTH2_INITIAL_CLIENT_RESPONSE = OIDCSASLHelper.generateEncodedXOauth2InitialClientResponse(
+ OidcTokenFixture.USER_EMAIL_ADDRESS,
+ OidcTokenFixture.VALID_TOKEN
+ );
+ public static final String VALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE = OIDCSASLHelper.generateEncodedXOauth2InitialClientResponse(
+ OidcTokenFixture.USER_EMAIL_ADDRESS,
+ OidcTokenFixture.VALID_TOKEN
+ );
+ public static final String INVALID_XOAUTH2_INITIAL_CLIENT_RESPONSE = OIDCSASLHelper.generateEncodedXOauth2InitialClientResponse(
+ OidcTokenFixture.USER_EMAIL_ADDRESS,
+ OidcTokenFixture.INVALID_TOKEN
+ );
+ public static final String INVALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE = OIDCSASLHelper.generateEncodedXOauth2InitialClientResponse(
+ OidcTokenFixture.USER_EMAIL_ADDRESS,
+ OidcTokenFixture.INVALID_TOKEN
+ );
+
+ @Nested
+ @TestInstance(Lifecycle.PER_CLASS)
+ public class LocalValidation {
+ private ClientAndServer authServer;
+ private ManageSieveClient client;
+ private final ManageSieveServerTestSystem testSystem;
+ private final HierarchicalConfiguration<ImmutableNode> configuration;
+
+ public LocalValidation() throws Exception {
+ this.testSystem = new ManageSieveServerTestSystem();
+ this.authServer = ClientAndServer.startClientAndServer(0);
+ this.authServer
+ .when(HttpRequest.request().withPath(JWKS_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(OidcTokenFixture.JWKS_RESPONSE, StandardCharsets.UTF_8));
+ this.configuration = ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("managesieveserver.xml"));
+ this.configuration.addProperty("oidc.jwksURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), JWKS_URI_PATH));
+ this.configuration.addProperty("oidc.claim", OidcTokenFixture.CLAIM);
+ this.configuration.addProperty("oidc.oidcConfigurationURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), DISCOVERY_URI_PATH));
+ this.configuration.addProperty("oidc.scope", SCOPE);
+ }
+
+ @BeforeEach
+ void setUp() throws Exception {
+ this.testSystem.setUp(this.configuration);
+ this.client = new ManageSieveClient();
+ this.client.connect(testSystem.getBindedIP(), testSystem.getBindedPort());
+ this.client.readResponse();
+ }
+
+ @AfterEach
+ void tearDown() {
+ this.testSystem.manageSieveServer.destroy();
+ }
+
+ @AfterAll
+ void finalTearDown() {
+ this.authServer.stop();
+ }
+
+ @Test
+ void oauthbearerLoginWithValidTokenShouldSucceed() throws Exception {
+ this.client.sendCommand("AUTHENTICATE \"OAUTHBEARER\" \"" + VALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+ }
+
+ @Test
+ void oauthbearerLoginWithValidTokenAndContinuationShouldSucceed() throws Exception {
+ this.client.sendCommand("AUTHENTICATE \"OAUTHBEARER\"");
+ ManageSieveClient.ServerResponse continuationResponse = this.client.readResponse();
+ Assertions.assertThat(continuationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+ Assertions.assertThat(continuationResponse.responseLines()).containsExactly("\"\"");
+
+ this.client.sendCommand("\"" + VALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+ }
+
+ @Test
+ void oauthbearerLoginWithValidTokenAndContinuationCanBeAborted() throws Exception {
+ this.client.sendCommand("AUTHENTICATE \"OAUTHBEARER\"");
+ ManageSieveClient.ServerResponse continuationResponse = this.client.readResponse();
+ Assertions.assertThat(continuationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+ Assertions.assertThat(continuationResponse.responseLines()).containsExactly("\"\"");
+
+ this.client.sendCommand("\"*\"");
+ ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ Assertions.assertThat(authenticationResponse.explanation()).get().isEqualTo("authentication aborted");
+ }
+
+ @Test
+ void oauthbearerLoginWithInvalidTokenShouldNotSucceed() throws Exception {
+ this.client.sendCommand("AUTHENTICATE \"OAUTHBEARER\" \"" + INVALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ }
+
+ @Test
+ void xoauth2LoginWithValidTokenShouldSucceed() throws Exception {
+ this.client.sendCommand("AUTHENTICATE \"XOAUTH2\" \"" + VALID_XOAUTH2_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+ }
+
+ @Test
+ void xoauth2LoginWithValidTokenAndContinuationShouldSucceed() throws Exception {
+ this.client.sendCommand("AUTHENTICATE \"XOAUTH2\"");
+ ManageSieveClient.ServerResponse continuationResponse = this.client.readResponse();
+ Assertions.assertThat(continuationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+ Assertions.assertThat(continuationResponse.responseLines()).containsExactly("\"\"");
+
+ this.client.sendCommand("\"" + VALID_XOAUTH2_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+ }
+
+ @Test
+ void xoauth2LoginWithValidTokenAndContinuationCanBeAborted() throws Exception {
+ this.client.sendCommand("AUTHENTICATE \"XOAUTH2\"");
+ ManageSieveClient.ServerResponse continuationResponse = this.client.readResponse();
+ Assertions.assertThat(continuationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+ Assertions.assertThat(continuationResponse.responseLines()).containsExactly("\"\"");
+
+ this.client.sendCommand("\"*\"");
+ ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ Assertions.assertThat(authenticationResponse.explanation()).get().isEqualTo("authentication aborted");
+ }
+
+ @Test
+ void xoauth2LoginWithInvalidTokenShouldNotSucceed() throws Exception {
+ this.client.sendCommand("AUTHENTICATE \"XOAUTH2\" \"" + INVALID_XOAUTH2_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = this.client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ }
+ }
+
+ @Nested
+ public class Introspection {
+ private final ManageSieveServerTestSystem testSystem;
+ private ClientAndServer authServer;
+
+ public Introspection() throws Exception {
+ this.testSystem = new ManageSieveServerTestSystem();
+ }
+
+ @AfterEach
+ void tearDown() {
+ this.testSystem.manageSieveServer.destroy();
+ this.authServer.stop();
+ }
+
+ @Test
+ void oauthbearerShouldSucceedWhenIntrospectReturnsActiveUser() throws Exception {
+ this.authServer = ClientAndServer.startClientAndServer(0);
+ this.authServer
+ .when(HttpRequest.request().withPath(INTROSPECTION_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(String.format("{\"active\": true, \"%s\": \"%s\"}", OidcTokenFixture.CLAIM, OidcTokenFixture.USER_EMAIL_ADDRESS), StandardCharsets.UTF_8));
+ this.authServer
+ .when(HttpRequest.request().withPath(JWKS_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(OidcTokenFixture.JWKS_RESPONSE, StandardCharsets.UTF_8));
+ HierarchicalConfiguration<ImmutableNode> configuration = ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("managesieveserver.xml"));
+ configuration.addProperty("oidc.jwksURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), JWKS_URI_PATH));
+ configuration.addProperty("oidc.claim", OidcTokenFixture.CLAIM);
+ configuration.addProperty("oidc.oidcConfigurationURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), DISCOVERY_URI_PATH));
+ configuration.addProperty("oidc.scope", SCOPE);
+ configuration.addProperty("oidc.introspection.url", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), INTROSPECTION_URI_PATH));
+ testSystem.setUp(configuration);
+
+ ManageSieveClient client = new ManageSieveClient();
+ client.connect(testSystem.getBindedIP(), testSystem.getBindedPort());
+ client.readResponse();
+
+ client.sendCommand("AUTHENTICATE \"OAUTHBEARER\" \"" + VALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+ }
+
+ @Test
+ void oauthbearerShouldFailWhenIntrospectReturnsInactiveUser() throws Exception {
+ this.authServer = ClientAndServer.startClientAndServer(0);
+ this.authServer
+ .when(HttpRequest.request().withPath(INTROSPECTION_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(String.format("{\"active\": false, \"%s\": \"%s\"}", OidcTokenFixture.CLAIM, OidcTokenFixture.USER_EMAIL_ADDRESS), StandardCharsets.UTF_8));
+ this.authServer
+ .when(HttpRequest.request().withPath(JWKS_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(OidcTokenFixture.JWKS_RESPONSE, StandardCharsets.UTF_8));
+ HierarchicalConfiguration<ImmutableNode> configuration = ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("managesieveserver.xml"));
+ configuration.addProperty("oidc.jwksURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), JWKS_URI_PATH));
+ configuration.addProperty("oidc.claim", OidcTokenFixture.CLAIM);
+ configuration.addProperty("oidc.oidcConfigurationURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), DISCOVERY_URI_PATH));
+ configuration.addProperty("oidc.scope", SCOPE);
+ configuration.addProperty("oidc.introspection.url", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), INTROSPECTION_URI_PATH));
+ testSystem.setUp(configuration);
+
+ ManageSieveClient client = new ManageSieveClient();
+ client.connect(testSystem.getBindedIP(), testSystem.getBindedPort());
+ client.readResponse();
+
+ client.sendCommand("AUTHENTICATE \"OAUTHBEARER\" \"" + VALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ }
+
+ @Test
+ void oauthbearerShouldFailWhenIntrospectReturnsWrongActiveUser() throws Exception {
+ this.authServer = ClientAndServer.startClientAndServer(0);
+ this.authServer
+ .when(HttpRequest.request().withPath(INTROSPECTION_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(String.format("{\"active\": true, \"%s\": \"%s-wrong\"}", OidcTokenFixture.CLAIM, OidcTokenFixture.USER_EMAIL_ADDRESS), StandardCharsets.UTF_8));
+ this.authServer
+ .when(HttpRequest.request().withPath(JWKS_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(OidcTokenFixture.JWKS_RESPONSE, StandardCharsets.UTF_8));
+ HierarchicalConfiguration<ImmutableNode> configuration = ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("managesieveserver.xml"));
+ configuration.addProperty("oidc.jwksURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), JWKS_URI_PATH));
+ configuration.addProperty("oidc.claim", OidcTokenFixture.CLAIM);
+ configuration.addProperty("oidc.oidcConfigurationURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), DISCOVERY_URI_PATH));
+ configuration.addProperty("oidc.scope", SCOPE);
+ configuration.addProperty("oidc.introspection.url", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), INTROSPECTION_URI_PATH));
+ testSystem.setUp(configuration);
+
+ ManageSieveClient client = new ManageSieveClient();
+ client.connect(testSystem.getBindedIP(), testSystem.getBindedPort());
+ client.readResponse();
+
+ client.sendCommand("AUTHENTICATE \"OAUTHBEARER\" \"" + VALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ }
+
+ @Test
+ void oauthbearerShouldFailWhenIntrospectDoesNotContainActiveField() throws Exception {
+ this.authServer = ClientAndServer.startClientAndServer(0);
+ this.authServer
+ .when(HttpRequest.request().withPath(INTROSPECTION_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(String.format("{\"%s\": \"%s\"}", OidcTokenFixture.CLAIM, OidcTokenFixture.USER_EMAIL_ADDRESS), StandardCharsets.UTF_8));
+ this.authServer
+ .when(HttpRequest.request().withPath(JWKS_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(OidcTokenFixture.JWKS_RESPONSE, StandardCharsets.UTF_8));
+ HierarchicalConfiguration<ImmutableNode> configuration = ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("managesieveserver.xml"));
+ configuration.addProperty("oidc.jwksURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), JWKS_URI_PATH));
+ configuration.addProperty("oidc.claim", OidcTokenFixture.CLAIM);
+ configuration.addProperty("oidc.oidcConfigurationURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), DISCOVERY_URI_PATH));
+ configuration.addProperty("oidc.scope", SCOPE);
+ configuration.addProperty("oidc.introspection.url", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), INTROSPECTION_URI_PATH));
+ testSystem.setUp(configuration);
+
+ ManageSieveClient client = new ManageSieveClient();
+ client.connect(testSystem.getBindedIP(), testSystem.getBindedPort());
+ client.readResponse();
+
+ client.sendCommand("AUTHENTICATE \"OAUTHBEARER\" \"" + VALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ }
+
+ @Test
+ void oauthbearerShouldFailWhenIntrospectDoesNotContainUserField() throws Exception {
+ this.authServer = ClientAndServer.startClientAndServer(0);
+ this.authServer
+ .when(HttpRequest.request().withPath(INTROSPECTION_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody("{\"active\": true}", StandardCharsets.UTF_8));
+ this.authServer
+ .when(HttpRequest.request().withPath(JWKS_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(OidcTokenFixture.JWKS_RESPONSE, StandardCharsets.UTF_8));
+ HierarchicalConfiguration<ImmutableNode> configuration = ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("managesieveserver.xml"));
+ configuration.addProperty("oidc.jwksURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), JWKS_URI_PATH));
+ configuration.addProperty("oidc.claim", OidcTokenFixture.CLAIM);
+ configuration.addProperty("oidc.oidcConfigurationURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), DISCOVERY_URI_PATH));
+ configuration.addProperty("oidc.scope", SCOPE);
+ configuration.addProperty("oidc.introspection.url", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), INTROSPECTION_URI_PATH));
+ testSystem.setUp(configuration);
+
+ ManageSieveClient client = new ManageSieveClient();
+ client.connect(testSystem.getBindedIP(), testSystem.getBindedPort());
+ client.readResponse();
+
+ client.sendCommand("AUTHENTICATE \"OAUTHBEARER\" \"" + VALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ }
+
+ @Test
+ void oauthbearerShouldFailWhenIntrospectEndpointErrors() throws Exception {
+ this.authServer = ClientAndServer.startClientAndServer(0);
+ this.authServer
+ .when(HttpRequest.request().withPath(INTROSPECTION_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(500));
+ this.authServer
+ .when(HttpRequest.request().withPath(JWKS_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(OidcTokenFixture.JWKS_RESPONSE, StandardCharsets.UTF_8));
+ HierarchicalConfiguration<ImmutableNode> configuration = ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("managesieveserver.xml"));
+ configuration.addProperty("oidc.jwksURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), JWKS_URI_PATH));
+ configuration.addProperty("oidc.claim", OidcTokenFixture.CLAIM);
+ configuration.addProperty("oidc.oidcConfigurationURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), DISCOVERY_URI_PATH));
+ configuration.addProperty("oidc.scope", SCOPE);
+ configuration.addProperty("oidc.introspection.url", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), INTROSPECTION_URI_PATH));
+ testSystem.setUp(configuration);
+
+ ManageSieveClient client = new ManageSieveClient();
+ client.connect(testSystem.getBindedIP(), testSystem.getBindedPort());
+ client.readResponse();
+
+ client.sendCommand("AUTHENTICATE \"OAUTHBEARER\" \"" + VALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ }
+
+ @Test
+ void oauthbearerIntrospectionValidationShouldFailWhenLocalValidationFails() throws Exception {
+ this.authServer = ClientAndServer.startClientAndServer(0);
+ this.authServer
+ .when(HttpRequest.request().withPath(INTROSPECTION_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(String.format("{\"active\": true, \"%s\": \"%s\"}", OidcTokenFixture.CLAIM, OidcTokenFixture.USER_EMAIL_ADDRESS), StandardCharsets.UTF_8));
+ this.authServer
+ .when(HttpRequest.request().withPath(JWKS_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(500));
+ HierarchicalConfiguration<ImmutableNode> configuration = ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("managesieveserver.xml"));
+ configuration.addProperty("oidc.jwksURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), JWKS_URI_PATH));
+ configuration.addProperty("oidc.claim", OidcTokenFixture.CLAIM);
+ configuration.addProperty("oidc.oidcConfigurationURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), DISCOVERY_URI_PATH));
+ configuration.addProperty("oidc.scope", SCOPE);
+ configuration.addProperty("oidc.introspection.url", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), INTROSPECTION_URI_PATH));
+ testSystem.setUp(configuration);
+
+ ManageSieveClient client = new ManageSieveClient();
+ client.connect(testSystem.getBindedIP(), testSystem.getBindedPort());
+ client.readResponse();
+
+ client.sendCommand("AUTHENTICATE \"OAUTHBEARER\" \"" + VALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ }
+ }
+
+ @Nested
+ public class Userinfo {
+ private final ManageSieveServerTestSystem testSystem;
+ private ClientAndServer authServer;
+
+ public Userinfo() throws Exception {
+ this.testSystem = new ManageSieveServerTestSystem();
+ }
+
+ @AfterEach
+ void tearDown() {
+ this.testSystem.manageSieveServer.destroy();
+ this.authServer.stop();
+ }
+
+ @Test
+ void oauthbearerShouldSucceedWhenUserinfoClaimMatches() throws Exception {
+ this.authServer = ClientAndServer.startClientAndServer(0);
+ this.authServer
+ .when(HttpRequest.request().withPath(USERINFO_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(String.format("{\"%s\": \"%s\"}", OidcTokenFixture.CLAIM, OidcTokenFixture.USER_EMAIL_ADDRESS), StandardCharsets.UTF_8));
+ this.authServer
+ .when(HttpRequest.request().withPath(JWKS_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(OidcTokenFixture.JWKS_RESPONSE, StandardCharsets.UTF_8));
+ HierarchicalConfiguration<ImmutableNode> configuration = ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("managesieveserver.xml"));
+ configuration.addProperty("oidc.jwksURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), JWKS_URI_PATH));
+ configuration.addProperty("oidc.claim", OidcTokenFixture.CLAIM);
+ configuration.addProperty("oidc.oidcConfigurationURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), DISCOVERY_URI_PATH));
+ configuration.addProperty("oidc.scope", SCOPE);
+ configuration.addProperty("oidc.userinfo.url", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), USERINFO_URI_PATH));
+ testSystem.setUp(configuration);
+
+ ManageSieveClient client = new ManageSieveClient();
+ client.connect(testSystem.getBindedIP(), testSystem.getBindedPort());
+ client.readResponse();
+
+ client.sendCommand("AUTHENTICATE \"OAUTHBEARER\" \"" + VALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.OK);
+ }
+
+ @Test
+ void oauthbearerShouldFailWhenUserinfoClaimDiffers() throws Exception {
+ this.authServer = ClientAndServer.startClientAndServer(0);
+ this.authServer
+ .when(HttpRequest.request().withPath(USERINFO_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(String.format("{\"%s\": \"test\"}", OidcTokenFixture.CLAIM), StandardCharsets.UTF_8));
+ this.authServer
+ .when(HttpRequest.request().withPath(JWKS_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(OidcTokenFixture.JWKS_RESPONSE, StandardCharsets.UTF_8));
+ HierarchicalConfiguration<ImmutableNode> configuration = ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("managesieveserver.xml"));
+ configuration.addProperty("oidc.jwksURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), JWKS_URI_PATH));
+ configuration.addProperty("oidc.claim", OidcTokenFixture.CLAIM);
+ configuration.addProperty("oidc.oidcConfigurationURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), DISCOVERY_URI_PATH));
+ configuration.addProperty("oidc.scope", SCOPE);
+ configuration.addProperty("oidc.userinfo.url", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), USERINFO_URI_PATH));
+ testSystem.setUp(configuration);
+
+ ManageSieveClient client = new ManageSieveClient();
+ client.connect(testSystem.getBindedIP(), testSystem.getBindedPort());
+ client.readResponse();
+
+ client.sendCommand("AUTHENTICATE \"OAUTHBEARER\" \"" + VALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ }
+
+ @Test
+ void oauthbearerShouldFailWhenUserinfoClaimIsMissing() throws Exception {
+ this.authServer = ClientAndServer.startClientAndServer(0);
+ this.authServer
+ .when(HttpRequest.request().withPath(USERINFO_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(String.format("{}"), StandardCharsets.UTF_8));
+ this.authServer
+ .when(HttpRequest.request().withPath(JWKS_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(OidcTokenFixture.JWKS_RESPONSE, StandardCharsets.UTF_8));
+ HierarchicalConfiguration<ImmutableNode> configuration = ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("managesieveserver.xml"));
+ configuration.addProperty("oidc.jwksURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), JWKS_URI_PATH));
+ configuration.addProperty("oidc.claim", OidcTokenFixture.CLAIM);
+ configuration.addProperty("oidc.oidcConfigurationURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), DISCOVERY_URI_PATH));
+ configuration.addProperty("oidc.scope", SCOPE);
+ configuration.addProperty("oidc.userinfo.url", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), USERINFO_URI_PATH));
+ testSystem.setUp(configuration);
+
+ ManageSieveClient client = new ManageSieveClient();
+ client.connect(testSystem.getBindedIP(), testSystem.getBindedPort());
+ client.readResponse();
+
+ client.sendCommand("AUTHENTICATE \"OAUTHBEARER\" \"" + VALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ }
+
+ @Test
+ void oauthbearerShouldFailWhenUserinfoErrors() throws Exception {
+ this.authServer = ClientAndServer.startClientAndServer(0);
+ this.authServer
+ .when(HttpRequest.request().withPath(USERINFO_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(500));
+ this.authServer
+ .when(HttpRequest.request().withPath(JWKS_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(OidcTokenFixture.JWKS_RESPONSE, StandardCharsets.UTF_8));
+ HierarchicalConfiguration<ImmutableNode> configuration = ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("managesieveserver.xml"));
+ configuration.addProperty("oidc.jwksURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), JWKS_URI_PATH));
+ configuration.addProperty("oidc.claim", OidcTokenFixture.CLAIM);
+ configuration.addProperty("oidc.oidcConfigurationURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), DISCOVERY_URI_PATH));
+ configuration.addProperty("oidc.scope", SCOPE);
+ configuration.addProperty("oidc.userinfo.url", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), USERINFO_URI_PATH));
+ testSystem.setUp(configuration);
+
+ ManageSieveClient client = new ManageSieveClient();
+ client.connect(testSystem.getBindedIP(), testSystem.getBindedPort());
+ client.readResponse();
+
+ client.sendCommand("AUTHENTICATE \"OAUTHBEARER\" \"" + VALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ }
+
+ @Test
+ void oauthbearerUserinfoValidationShouldFailWhenLocalValidationFails() throws Exception {
+ this.authServer = ClientAndServer.startClientAndServer(0);
+ this.authServer
+ .when(HttpRequest.request().withPath(USERINFO_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(200)
+ .withHeader("Content-Type", "application/json")
+ .withBody(String.format("{\"%s\": \"%s\"}", OidcTokenFixture.CLAIM, OidcTokenFixture.USER_EMAIL_ADDRESS), StandardCharsets.UTF_8));
+ this.authServer
+ .when(HttpRequest.request().withPath(JWKS_URI_PATH))
+ .respond(HttpResponse.response().withStatusCode(500));
+ HierarchicalConfiguration<ImmutableNode> configuration = ConfigLoader.getConfig(ClassLoaderUtils.getSystemResourceAsSharedStream("managesieveserver.xml"));
+ configuration.addProperty("oidc.jwksURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), JWKS_URI_PATH));
+ configuration.addProperty("oidc.claim", OidcTokenFixture.CLAIM);
+ configuration.addProperty("oidc.oidcConfigurationURL", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), DISCOVERY_URI_PATH));
+ configuration.addProperty("oidc.scope", SCOPE);
+ configuration.addProperty("oidc.userinfo.url", String.format("http://127.0.0.1:%s%s", this.authServer.getLocalPort(), USERINFO_URI_PATH));
+ testSystem.setUp(configuration);
+
+ ManageSieveClient client = new ManageSieveClient();
+ client.connect(testSystem.getBindedIP(), testSystem.getBindedPort());
+ client.readResponse();
+
+ client.sendCommand("AUTHENTICATE \"OAUTHBEARER\" \"" + VALID_OAUTHBEARER_INITIAL_CLIENT_RESPONSE + "\"");
+ ManageSieveClient.ServerResponse authenticationResponse = client.readResponse();
+ Assertions.assertThat(authenticationResponse.responseType()).isEqualTo(ManageSieveClient.ResponseType.NO);
+ }
+ }
+}
diff --git a/server/protocols/protocols-managesieve/src/test/resources/managesieveserver-oidc.xml b/server/protocols/protocols-managesieve/src/test/resources/managesieveserver-oidc.xml
new file mode 100644
index 0000000000..9125d16891
--- /dev/null
+++ b/server/protocols/protocols-managesieve/src/test/resources/managesieveserver-oidc.xml
@@ -0,0 +1,16 @@
+<managesieveserver enabled="true">
+ <jmxName>managesieveserver</jmxName>
+ <bind>0.0.0.0:4190</bind>
+
+ <connectionBacklog>200</connectionBacklog>
+ <connectiontimeout>360</connectiontimeout>
+ <connectionLimit>0</connectionLimit>
+ <connectionLimitPerIP>0</connectionLimitPerIP>
+
+ <oidc>
+ <jwksURL>http://127.0.0.1/realms/test/protocol/openid-connect/certs</jwksURL>
+ <claim>sub</claim>
+ <oidcConfigurationURL>https://127.0.0.1/realms/test/.well-known/openid-configuration</oidcConfigurationURL>
+ <scope>email</scope>
+ </oidc>
+</managesieveserver>
diff --git a/server/protocols/protocols-managesieve/src/test/resources/managesieveserver.xml b/server/protocols/protocols-managesieve/src/test/resources/managesieveserver.xml
new file mode 100644
index 0000000000..77cc6f3a2a
--- /dev/null
+++ b/server/protocols/protocols-managesieve/src/test/resources/managesieveserver.xml
@@ -0,0 +1,9 @@
+<managesieveserver enabled="true">
+ <jmxName>managesieveserver</jmxName>
+ <bind>0.0.0.0:4190</bind>
+
+ <connectionBacklog>200</connectionBacklog>
+ <connectiontimeout>360</connectiontimeout>
+ <connectionLimit>0</connectionLimit>
+ <connectionLimitPerIP>0</connectionLimitPerIP>
+</managesieveserver>
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org