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]

Reply via email to