SaslXoauth2Message.java

package com.icegreen.greenmail.util;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
 * Parses and holds the details of a SASL XOAUTH2 message.
 */
public class SaslXoauth2Message {
    private final String username;
    private final String accessToken;

    /**
     * Constructs a new SASL XOAUTH2 message.
     * @param username the username
     * @param accessToken the XOAUTH2 access token
     */
    public SaslXoauth2Message(String username, String accessToken) {
        if (username == null || accessToken == null) {
            throw new IllegalArgumentException("Username and access token must not be null");
        }
        this.username = username;
        this.accessToken = accessToken;
    }

    public String getUsername() {
        return username;
    }

    public String getAccessToken() {
        return accessToken;
    }

    /**
     * Parses a bas64-encoded SASL XOAUTH@ mechanism message.
     * See {@link #parse(String)} for details.
     *
     * @param message the base64-encoded SASL message
     * @return the parsed SASL XOAUTH2 message
     * @throws IllegalArgumentException on invalid format or Base64 decoding errors
     */
    public static SaslXoauth2Message parseBase64Encoded(String message) {
        try {
            byte[] decodedBytes = Base64.getDecoder().decode(message);
            String decodedString = new String(decodedBytes, StandardCharsets.UTF_8);
            return parse(decodedString);
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("Invalid Base64 or malformed input: " + e.getMessage());
        }
    }

    /**
     * Parses a SASL XOAUTH@ mechanism message.
     * See <a href="https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth#sasl-xoauth2">RFC 7628</a> for details.
     * <p>
     *     Message format: base64("user=" + userName + "^Aauth=Bearer " + accessToken + "^A^A")
     *     ^A represents a Control + A (%x01).
     * </p>
     *
     * @param message the SASL message
     * @return the parsed SASL XOAUTH2 message
     * @throws IllegalArgumentException on invalid format or Base64 decoding errors
     */
    public static SaslXoauth2Message parse(String message) {
        try {
            String[] parts = message.split("\\u0001");
            if (parts.length > 2 || !parts[0].startsWith("user=") || !parts[1].startsWith("auth=Bearer ")) {
                throw new IllegalArgumentException("Invalid authentication string format");
            }

            return new SaslXoauth2Message(parts[0].substring(5), parts[1].substring(12));
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("Invalid Base64 or malformed input: " + e.getMessage());
        }
    }

}