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 06445aef14010bf788a6e8dac75867d320915b2e Author: Felix Auringer <[email protected]> AuthorDate: Thu Aug 14 20:42:27 2025 +0200 fix(oidc): correct differentiation between XOAUTH2 and OAUTHBEARER XOAUTH2 is described here: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol OAUTHBERER is described here: https://datatracker.ietf.org/doc/html/rfc5801#section-4 There is a small difference in how the user argument is called and OAUTHBEARER also includes a GS2 header. --- .../apache/james/protocols/api/OIDCSASLParser.java | 13 ++++--- .../apache/james/protocols/api/OIDCSASLHelper.java | 12 ++++++- .../james/imapserver/netty/IMAPServerTest.java | 26 +++++++------- .../org/apache/james/smtpserver/SMTPSaslTest.java | 40 +++++++++++----------- 4 files changed, 53 insertions(+), 38 deletions(-) diff --git a/protocols/api/src/main/java/org/apache/james/protocols/api/OIDCSASLParser.java b/protocols/api/src/main/java/org/apache/james/protocols/api/OIDCSASLParser.java index eb2f556f75..72df061214 100644 --- a/protocols/api/src/main/java/org/apache/james/protocols/api/OIDCSASLParser.java +++ b/protocols/api/src/main/java/org/apache/james/protocols/api/OIDCSASLParser.java @@ -33,9 +33,11 @@ public class OIDCSASLParser { public static final char SASL_SEPARATOR = 1; public static final String PREFIX_TOKEN = "Bearer "; public static final String TOKEN_PART_PREFIX = "auth="; - public static final String USER_PART_PREFIX = "user="; + public static final String XOAUTH2_USER_PART_PREFIX = "user="; + public static final String OAUTHBEARER_USER_PART_PREFIX = "a="; public static final int TOKEN_PART_INDEX = TOKEN_PART_PREFIX.length(); - public static final int USER_PART_INDEX = USER_PART_PREFIX.length(); + public static final int XOAUTH2_USER_PART_INDEX = XOAUTH2_USER_PART_PREFIX.length(); + public static final int OAUTHBEARER_USER_PART_INDEX = OAUTHBEARER_USER_PART_PREFIX.length(); public static class OIDCInitialResponse { private final String associatedUser; @@ -74,8 +76,11 @@ public class OIDCSASLParser { if (stringToken.startsWith(TOKEN_PART_PREFIX)) { tokenPart = StringUtils.replace(stringToken.substring(TOKEN_PART_INDEX), PREFIX_TOKEN, ""); tokenPartCounter++; - } else if (stringToken.startsWith(USER_PART_PREFIX)) { - userPart = stringToken.substring(USER_PART_INDEX); + } else if (stringToken.startsWith(XOAUTH2_USER_PART_PREFIX)) { + userPart = stringToken.substring(XOAUTH2_USER_PART_INDEX); + userPartCounter++; + } else if (stringToken.startsWith(OAUTHBEARER_USER_PART_PREFIX)) { + userPart = stringToken.substring(OAUTHBEARER_USER_PART_INDEX); userPartCounter++; } } diff --git a/protocols/api/src/test/java/org/apache/james/protocols/api/OIDCSASLHelper.java b/protocols/api/src/test/java/org/apache/james/protocols/api/OIDCSASLHelper.java index 1436649a18..b806a40acb 100644 --- a/protocols/api/src/test/java/org/apache/james/protocols/api/OIDCSASLHelper.java +++ b/protocols/api/src/test/java/org/apache/james/protocols/api/OIDCSASLHelper.java @@ -25,9 +25,19 @@ import java.util.Base64; import com.google.common.collect.ImmutableList; public class OIDCSASLHelper { - public static String generateOauthBearer(String username, String token) { + // See the XOAUTH2 specification athttps://developers.google.com/workspace/gmail/imap/xoauth2-protocol + // for details. + public static String generateEncodedXOauth2InitialClientResponse(String username, String token) { return Base64.getEncoder().encodeToString(String.join("" + OIDCSASLParser.SASL_SEPARATOR, ImmutableList.of("user=" + username, "auth=Bearer " + token, "", "")) .getBytes(StandardCharsets.US_ASCII)); } + + // See the OAUTHBEARER specification at https://datatracker.ietf.org/doc/html/rfc5801#section-4 + // for details. + public static String generateEncodedOauthbearerInitialClientResponse(String username, String token) { + return Base64.getEncoder().encodeToString(String.join("" + OIDCSASLParser.SASL_SEPARATOR, + ImmutableList.of("n,a=" + username, "auth=Bearer " + token, "", "")) + .getBytes(StandardCharsets.US_ASCII)); + } } diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java index e46c354152..5c390bf09c 100644 --- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java +++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java @@ -1379,7 +1379,7 @@ class IMAPServerTest { @Test void oauthShouldSuccessWhenValidToken() throws Exception { - String oauthBearer = OIDCSASLHelper.generateOauthBearer(USER.asString(), OidcTokenFixture.VALID_TOKEN); + String oauthBearer = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER.asString(), OidcTokenFixture.VALID_TOKEN); IMAPSClient client = imapsClient(port); client.sendCommand("AUTHENTICATE OAUTHBEARER " + oauthBearer); assertThat(client.getReplyString()).contains("OK AUTHENTICATE completed."); @@ -1420,9 +1420,9 @@ class IMAPServerTest { @Test void oauthShouldSupportOAUTH2Type() throws Exception { - String oauthBearer = OIDCSASLHelper.generateOauthBearer(USER.asString(), OidcTokenFixture.VALID_TOKEN); + String xoauth2 = OIDCSASLHelper.generateEncodedXOauth2InitialClientResponse(USER.asString(), OidcTokenFixture.VALID_TOKEN); IMAPSClient client = imapsClient(port); - client.sendCommand("AUTHENTICATE XOAUTH2 " + oauthBearer); + client.sendCommand("AUTHENTICATE XOAUTH2 " + xoauth2); assertThat(client.getReplyString()).contains("OK AUTHENTICATE completed."); } @@ -1441,7 +1441,7 @@ class IMAPServerTest { @Test void shouldNotOauthWhenAuthIsReady() throws Exception { - String oauthBearer = OIDCSASLHelper.generateOauthBearer(USER.asString(), OidcTokenFixture.VALID_TOKEN); + String oauthBearer = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER.asString(), OidcTokenFixture.VALID_TOKEN); IMAPSClient client = imapsClient(port); client.sendCommand("AUTHENTICATE OAUTHBEARER " + oauthBearer); client.sendCommand("AUTHENTICATE OAUTHBEARER " + oauthBearer); @@ -1450,7 +1450,7 @@ class IMAPServerTest { @Test void appendShouldSuccessWhenAuthenticated() throws Exception { - String oauthBearer = OIDCSASLHelper.generateOauthBearer(USER.asString(), OidcTokenFixture.VALID_TOKEN); + String oauthBearer = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER.asString(), OidcTokenFixture.VALID_TOKEN); IMAPSClient imapsClient = imapsClient(port); imapsClient.sendCommand("AUTHENTICATE OAUTHBEARER " + oauthBearer); imapsClient.create("INBOX"); @@ -1487,7 +1487,7 @@ class IMAPServerTest { int port = imapServer.getListenAddresses().get(0).getPort(); - String oauthBearer = OIDCSASLHelper.generateOauthBearer(USER.asString(), OidcTokenFixture.VALID_TOKEN); + String oauthBearer = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER.asString(), OidcTokenFixture.VALID_TOKEN); IMAPSClient client = imapsClient(port); client.sendCommand("AUTHENTICATE OAUTHBEARER " + oauthBearer); assertThat(client.getReplyString()).contains("NO AUTHENTICATE failed."); @@ -1514,7 +1514,7 @@ class IMAPServerTest { int port = imapServer.getListenAddresses().get(0).getPort(); - String oauthBearer = OIDCSASLHelper.generateOauthBearer(USER.asString(), OidcTokenFixture.VALID_TOKEN); + String oauthBearer = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER.asString(), OidcTokenFixture.VALID_TOKEN); IMAPSClient client = imapsClient(port); client.sendCommand("AUTHENTICATE OAUTHBEARER " + oauthBearer); assertThat(client.getReplyString()).contains("OK AUTHENTICATE completed."); @@ -1539,7 +1539,7 @@ class IMAPServerTest { int port = imapServer.getListenAddresses().get(0).getPort(); - String oauthBearer = OIDCSASLHelper.generateOauthBearer(USER.asString(), OidcTokenFixture.VALID_TOKEN); + String oauthBearer = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER.asString(), OidcTokenFixture.VALID_TOKEN); IMAPSClient client = imapsClient(port); client.sendCommand("AUTHENTICATE OAUTHBEARER " + oauthBearer); assertThat(client.getReplyString()).contains("NO AUTHENTICATE processing failed."); @@ -1566,7 +1566,7 @@ class IMAPServerTest { int port = imapServer.getListenAddresses().get(0).getPort(); - String oauthBearer = OIDCSASLHelper.generateOauthBearer(USER.asString(), OidcTokenFixture.VALID_TOKEN); + String oauthBearer = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER.asString(), OidcTokenFixture.VALID_TOKEN); IMAPSClient client = imapsClient(port); client.sendCommand("AUTHENTICATE OAUTHBEARER " + oauthBearer); assertThat(client.getReplyString()).contains("OK AUTHENTICATE completed."); @@ -1592,7 +1592,7 @@ class IMAPServerTest { int port = imapServer.getListenAddresses().get(0).getPort(); - String oauthBearer = OIDCSASLHelper.generateOauthBearer(USER.asString(), OidcTokenFixture.VALID_TOKEN); + String oauthBearer = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER.asString(), OidcTokenFixture.VALID_TOKEN); IMAPSClient client = imapsClient(port); client.sendCommand("AUTHENTICATE OAUTHBEARER " + oauthBearer); assertThat(client.getReplyString()).contains("NO AUTHENTICATE processing failed."); @@ -1600,7 +1600,7 @@ class IMAPServerTest { @Test void oauthShouldImpersonateFailWhenNOTDelegated() throws Exception { - String oauthBearer = OIDCSASLHelper.generateOauthBearer(USER3.asString(), OidcTokenFixture.VALID_TOKEN); + String oauthBearer = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER3.asString(), OidcTokenFixture.VALID_TOKEN); IMAPSClient client = imapsClient(port); client.sendCommand("AUTHENTICATE OAUTHBEARER " + oauthBearer); assertThat(client.getReplyString()).contains("NO AUTHENTICATE"); @@ -1608,7 +1608,7 @@ class IMAPServerTest { @Test void oauthShouldImpersonateSuccessWhenDelegated() throws Exception { - String oauthBearer = OIDCSASLHelper.generateOauthBearer(USER2.asString(), OidcTokenFixture.VALID_TOKEN); + String oauthBearer = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER2.asString(), OidcTokenFixture.VALID_TOKEN); IMAPSClient client = imapsClient(port); client.sendCommand("AUTHENTICATE OAUTHBEARER " + oauthBearer); assertThat(client.getReplyString()).contains("OK AUTHENTICATE completed."); @@ -1624,7 +1624,7 @@ class IMAPServerTest { // USER1 authenticate and impersonate as USER2 try (TestIMAPClient client = new TestIMAPClient(imapsClient(port))) { - String oauthBearer = OIDCSASLHelper.generateOauthBearer(USER2.asString(), OidcTokenFixture.VALID_TOKEN); + String oauthBearer = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER2.asString(), OidcTokenFixture.VALID_TOKEN); String authenticateResponse = client.sendCommand("AUTHENTICATE OAUTHBEARER " + oauthBearer); assertThat(authenticateResponse).contains("OK AUTHENTICATE completed."); diff --git a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPSaslTest.java b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPSaslTest.java index 0e8b515312..45bac66783 100644 --- a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPSaslTest.java +++ b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPSaslTest.java @@ -61,8 +61,8 @@ class SMTPSaslTest { public static final String SCOPE = "scope"; public static final String FAIL_RESPONSE_TOKEN = Base64.getEncoder().encodeToString( String.format("{\"status\":\"invalid_token\",\"scope\":\"%s\",\"schemes\":\"%s\"}", SCOPE, OIDC_URL).getBytes(UTF_8)); - public static final String VALID_TOKEN = OIDCSASLHelper.generateOauthBearer(USER.asString(), OidcTokenFixture.VALID_TOKEN); - public static final String INVALID_TOKEN = OIDCSASLHelper.generateOauthBearer(USER.asString(), OidcTokenFixture.INVALID_TOKEN); + public static final String VALID_OAUTHBEARER_TOKEN = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER.asString(), OidcTokenFixture.VALID_TOKEN); + public static final String INVALID_OAUTHBEARER_TOKEN = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER.asString(), OidcTokenFixture.INVALID_TOKEN); private final SMTPServerTestSystem testSystem = new SMTPServerTestSystem(); @@ -115,7 +115,7 @@ class SMTPSaslTest { void oauthShouldSuccessWhenValidToken() throws Exception { SMTPSClient client = initSMTPSClient(); - client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN); + client.sendCommand("AUTH OAUTHBEARER " + VALID_OAUTHBEARER_TOKEN); assertThat(client.getReplyString()).contains("235 Authentication successful."); @@ -129,7 +129,7 @@ class SMTPSaslTest { client.sendCommand("AUTH OAUTHBEARER"); assertThat(client.getReplyString()).contains("334"); - client.sendCommand(VALID_TOKEN); + client.sendCommand(VALID_OAUTHBEARER_TOKEN); assertThat(client.getReplyString()).contains("235 Authentication successful."); @@ -143,7 +143,7 @@ class SMTPSaslTest { client.sendCommand("AUTH XOAUTH2"); assertThat(client.getReplyString()).contains("334"); - client.sendCommand(VALID_TOKEN); + client.sendCommand(OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER.asString(), OidcTokenFixture.VALID_TOKEN)); assertThat(client.getReplyString()).contains("235 Authentication successful."); @@ -155,7 +155,7 @@ class SMTPSaslTest { void oauthShouldSupportXOAUTH2Type() throws Exception { SMTPSClient client = initSMTPSClient(); - client.sendCommand("AUTH XOAUTH2 " + VALID_TOKEN); + client.sendCommand("AUTH XOAUTH2 " + OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER.asString(), OidcTokenFixture.VALID_TOKEN)); assertThat(client.getReplyString()).contains("235 Authentication successful."); } @@ -171,7 +171,7 @@ class SMTPSaslTest { .as("Should not advertise OAUTHBEARER when no TLS connect.") .doesNotContain("OAUTHBEARER"); - client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN); + client.sendCommand("AUTH OAUTHBEARER " + VALID_OAUTHBEARER_TOKEN); assertThat(client.getReplyString()).contains("504 Unrecognized Authentication Type"); } @@ -179,7 +179,7 @@ class SMTPSaslTest { void oauthShouldFailWhenInvalidToken() throws Exception { SMTPSClient client = initSMTPSClient(); - client.sendCommand("AUTH OAUTHBEARER " + INVALID_TOKEN); + client.sendCommand("AUTH OAUTHBEARER " + INVALID_OAUTHBEARER_TOKEN); assertThat(client.getReplyString()).contains("334 " + FAIL_RESPONSE_TOKEN); client.sendCommand("AQ=="); @@ -192,7 +192,7 @@ class SMTPSaslTest { client.sendCommand("EHLO localhost"); - client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN); + client.sendCommand("AUTH OAUTHBEARER " + VALID_OAUTHBEARER_TOKEN); client.setSender(USER.asString()); client.addRecipient("[email protected]"); @@ -224,8 +224,8 @@ class SMTPSaslTest { void shouldNotOauthWhenAlreadyAuthenticated() throws Exception { SMTPSClient client = initSMTPSClient(); - client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN); - client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN); + client.sendCommand("AUTH OAUTHBEARER " + VALID_OAUTHBEARER_TOKEN); + client.sendCommand("AUTH OAUTHBEARER " + VALID_OAUTHBEARER_TOKEN); assertThat(client.getReplyString()).contains("503 5.5.0 User has previously authenticated. Further authentication is not required!"); } @@ -239,7 +239,7 @@ class SMTPSaslTest { SMTPSClient client = initSMTPSClient(); - client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN); + client.sendCommand("AUTH OAUTHBEARER " + VALID_OAUTHBEARER_TOKEN); assertThat(client.getReplyString()).contains("504 Unrecognized Authentication Type"); } @@ -323,7 +323,7 @@ class SMTPSaslTest { SMTPSClient client = initSMTPSClient(); - client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN); + client.sendCommand("AUTH OAUTHBEARER " + VALID_OAUTHBEARER_TOKEN); assertThat(client.getReplyString()).contains("334 " + FAIL_RESPONSE_TOKEN); @@ -352,7 +352,7 @@ class SMTPSaslTest { SMTPSClient client = initSMTPSClient(); - client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN); + client.sendCommand("AUTH OAUTHBEARER " + VALID_OAUTHBEARER_TOKEN); assertThat(client.getReplyString()).contains("235 Authentication successful."); } @@ -377,7 +377,7 @@ class SMTPSaslTest { SMTPSClient client = initSMTPSClient(); - client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN); + client.sendCommand("AUTH OAUTHBEARER " + VALID_OAUTHBEARER_TOKEN); assertThat(client.getReplyString()).contains("451 Unable to process request"); } @@ -402,7 +402,7 @@ class SMTPSaslTest { SMTPSClient client = initSMTPSClient(); - client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN); + client.sendCommand("AUTH OAUTHBEARER " + VALID_OAUTHBEARER_TOKEN); assertThat(client.getReplyString()).contains("235 Authentication successful."); } @@ -426,7 +426,7 @@ class SMTPSaslTest { SMTPSClient client = initSMTPSClient(); - client.sendCommand("AUTH OAUTHBEARER " + VALID_TOKEN); + client.sendCommand("AUTH OAUTHBEARER " + VALID_OAUTHBEARER_TOKEN); assertThat(client.getReplyString()).contains("451 Unable to process request"); } @@ -434,7 +434,7 @@ class SMTPSaslTest { @Test void oauthShouldImpersonateFailWhenNOTDelegated() throws Exception { SMTPSClient client = initSMTPSClient(); - String tokenWithImpersonation = OIDCSASLHelper.generateOauthBearer("[email protected]", OidcTokenFixture.VALID_TOKEN); + String tokenWithImpersonation = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse("[email protected]", OidcTokenFixture.VALID_TOKEN); client.sendCommand("AUTH OAUTHBEARER " + tokenWithImpersonation); assertThat(client.getReplyString()).contains("334 "); @@ -446,7 +446,7 @@ class SMTPSaslTest { @Test void oauthShouldImpersonateSuccessWhenDelegated() throws Exception { SMTPSClient client = initSMTPSClient(); - String tokenWithImpersonation = OIDCSASLHelper.generateOauthBearer(USER2.asString(), OidcTokenFixture.VALID_TOKEN); + String tokenWithImpersonation = OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER2.asString(), OidcTokenFixture.VALID_TOKEN); client.sendCommand("AUTH OAUTHBEARER " + tokenWithImpersonation); assertThat(client.getReplyString()).contains("235 Authentication successful."); @@ -458,7 +458,7 @@ class SMTPSaslTest { client.sendCommand("EHLO localhost"); - client.sendCommand("AUTH OAUTHBEARER " + OIDCSASLHelper.generateOauthBearer(USER2.asString(), OidcTokenFixture.VALID_TOKEN)); + client.sendCommand("AUTH OAUTHBEARER " + OIDCSASLHelper.generateEncodedOauthbearerInitialClientResponse(USER2.asString(), OidcTokenFixture.VALID_TOKEN)); client.setSender(USER2.asString()); client.addRecipient("[email protected]"); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
