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
The following commit(s) were added to refs/heads/master by this push:
new 3bba59d6af boyscoot: test Authentication parsing (#2906)
3bba59d6af is described below
commit 3bba59d6afa5d5f9f37c7293798de455e908d5c9
Author: Matthieu Baechler <[email protected]>
AuthorDate: Fri Jan 9 07:55:03 2026 +0000
boyscoot: test Authentication parsing (#2906)
---
.../protocols/smtp/core/esmtp/AuthCmdHandler.java | 79 +++++++++++++---------
.../smtp/core/esmtp/AuthCmdHandlerTest.java | 68 +++++++++++++++++++
2 files changed, 114 insertions(+), 33 deletions(-)
diff --git
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/AuthCmdHandler.java
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/AuthCmdHandler.java
index 7cf0f14abc..95055945cd 100644
---
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/AuthCmdHandler.java
+++
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/AuthCmdHandler.java
@@ -18,12 +18,10 @@
****************************************************************/
-
package org.apache.james.protocols.smtp.core.esmtp;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
@@ -31,7 +29,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Function;
-import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.james.core.Username;
@@ -55,8 +52,9 @@ import org.apache.james.util.AuditTrail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
-import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -268,44 +266,59 @@ public class AuthCmdHandler
*/
private Response doPlainAuth(SMTPSession session, String line) {
try {
- List<String> tokens = Optional.ofNullable(decodeBase64(line))
- .map(userpass1 -> Arrays.stream(userpass1.split("\0"))
- .filter(token -> !token.isBlank())
- .collect(Collectors.toList()))
- .orElse(List.of());
- Preconditions.checkArgument(tokens.size() == 1 || tokens.size() ==
2 || tokens.size() == 3);
- Response response = null;
-
- if (tokens.size() == 1) {
- response = doDelegation(session, Username.of(tokens.get(0)));
- } else if (tokens.size() == 2) {
- // If we got here, this is what happened. RFC 2595
- // says that "the client may leave the authorization
- // identity empty to indicate that it is the same as
- // the authentication identity." As noted above,
- // that would be represented as a decoded string of
- // the form: "\0authenticate-id\0password". The
- // first call to nextToken will skip the empty
- // authorize-id, and give us the authenticate-id,
- // which we would store as the authorize-id. The
- // second call will give us the password, which we
- // think is the authenticate-id (user). Then when
- // we ask for the password, there are no more
- // elements, leading to the exception we just
- // caught. So we need to move the user to the
- // password, and the authorize_id to the user.
- response = doAuthTest(session,
Optional.of(Username.of(tokens.get(0))), Optional.of(tokens.get(1)),
AUTH_TYPE_PLAIN);
+
+ AuthValues authValues =
+ Optional.ofNullable(decodeBase64(line))
+ .flatMap(AuthCmdHandler::parseAuthValues)
+ .orElseThrow(() -> new
IllegalArgumentException("Can't parse line as authentication values"));
+
+ Response response;
+
+ if (authValues.password.isEmpty()) {
+ response = doDelegation(session, authValues.username);
} else {
- response = doAuthTest(session,
Optional.of(Username.of(tokens.get(1))), Optional.of(tokens.get(2)),
AUTH_TYPE_PLAIN);
+ response = doAuthTest(session,
Optional.of(authValues.username), authValues.password, AUTH_TYPE_PLAIN);
}
+
session.popLineHandler();
return response;
} catch (Exception e) {
LOGGER.info("Could not decode parameters for AUTH PLAIN", e);
- return new SMTPResponse(SMTPRetCode.SYNTAX_ERROR_ARGUMENTS,"Could
not decode parameters for AUTH PLAIN");
+ return new SMTPResponse(SMTPRetCode.SYNTAX_ERROR_ARGUMENTS, "Could
not decode parameters for AUTH PLAIN");
}
}
+ @VisibleForTesting
+ static Optional<AuthValues> parseAuthValues(String input) {
+
+ List<String> parts =
Splitter.on('\0').splitToStream(input).filter(token ->
!token.isBlank()).toList();
+
+ return switch (parts.size()) {
+ case 1 -> Optional.of(new AuthValues(Username.of(parts.get(0)),
Optional.empty()));
+ // If we got here, this is what happened. RFC 2595
+ // says that "the client may leave the authorization
+ // identity empty to indicate that it is the same as
+ // the authentication identity." As noted above,
+ // that would be represented as a decoded string of
+ // the form: "\0authenticate-id\0password". The
+ // first call to nextToken will skip the empty
+ // authorize-id, and give us the authenticate-id,
+ // which we would store as the authorize-id. The
+ // second call will give us the password, which we
+ // think is the authenticate-id (user). Then when
+ // we ask for the password, there are no more
+ // elements, leading to the exception we just
+ // caught. So we need to move the user to the
+ // password, and the authorize_id to the user.
+ case 2 -> Optional.of(new AuthValues(Username.of(parts.get(0)),
Optional.of(parts.get(1))));
+ case 3 -> Optional.of(new AuthValues(Username.of(parts.get(1)),
Optional.of(parts.get(2))));
+ default -> Optional.empty();
+ };
+ }
+
+ record AuthValues(Username username, Optional<String> password) {
+ }
+
private String decodeBase64(String line) {
if (line != null) {
String lineWithoutTrailingCrLf = StringUtils.replace(line, "\r\n",
"");
diff --git
a/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/core/esmtp/AuthCmdHandlerTest.java
b/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/core/esmtp/AuthCmdHandlerTest.java
new file mode 100644
index 0000000000..edf407331d
--- /dev/null
+++
b/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/core/esmtp/AuthCmdHandlerTest.java
@@ -0,0 +1,68 @@
+/****************************************************************
+ * 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.protocols.smtp.core.esmtp;
+
+import java.util.Optional;
+
+import org.apache.james.core.Username;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class AuthCmdHandlerTest {
+
+ @Test
+ void shouldReturnEmptyWhenEmptyInput() {
+ Assertions.assertThat(AuthCmdHandler.parseAuthValues("")).isEmpty();
+ }
+
+ @Test
+ void shouldReturnEmptyWhenBlankInput() {
+ Assertions.assertThat(AuthCmdHandler.parseAuthValues("
\t\n")).isEmpty();
+ }
+
+ @Test
+ void shouldReturnEmptyWhenTwoBlankParts() {
+ Assertions.assertThat(AuthCmdHandler.parseAuthValues("
\0\t\n")).isEmpty();
+ }
+
+ @Test
+ void shouldReturnEmptyWhenThreeBlankParts() {
+ Assertions.assertThat(AuthCmdHandler.parseAuthValues(" \0\0
")).isEmpty();
+ }
+
+ @Test
+ void shouldReturnUsernameWhenSinglePart() {
+ Assertions.assertThat(AuthCmdHandler.parseAuthValues("bob"))
+ .contains(new AuthCmdHandler.AuthValues(Username.of("bob"),
Optional.empty()));
+ }
+
+ @Test
+ void shouldReturnUsernameAndPassWhenTwoParts() {
+ Assertions.assertThat(AuthCmdHandler.parseAuthValues("bob\0pass"))
+ .contains(new AuthCmdHandler.AuthValues(Username.of("bob"),
Optional.of("pass")));
+ }
+
+ @Test
+ void shouldReturnUsernameAndPassWhenThreeParts() {
+
Assertions.assertThat(AuthCmdHandler.parseAuthValues("something\0bob\0pass"))
+ .contains(new AuthCmdHandler.AuthValues(Username.of("bob"),
Optional.of("pass")));
+ }
+
+
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]