using System; using System.Text; namespace S22.Sasl.Mechanisms { /// /// Implements the Sasl OAuth 2.0 authentication method. /// internal class SaslOAuth2 : SaslMechanism { bool Completed = false; /// /// The server sends an error response in case authentication fails /// which must be acknowledged. /// int Step = 0; /// /// Client sends the first message in the authentication exchange. /// public override bool HasInitial { get { return true; } } /// /// True if the authentication exchange between client and server /// has been completed. /// public override bool IsCompleted { get { return Completed; } } /// /// The IANA name for the OAuth 2.0 authentication mechanism. /// public override string Name { get { return "XOAUTH2"; } } /// /// The username to authenticate with. /// string Username { get { return Properties.ContainsKey("Username") ? Properties["Username"] as string : null; } set { Properties["Username"] = value; } } /// /// The access token to authenticate with. /// string AccessToken { get { return Properties.ContainsKey("AccessToken") ? Properties["AccessToken"] as string : null; } set { Properties["AccessToken"] = value; } } /// /// Private constructor for use with Sasl.SaslFactory. /// private SaslOAuth2() { // Nothing to do here. } /// /// Creates and initializes a new instance of the SaslOAuth class /// using the specified username and password. /// /// The username to authenticate with. /// The username to authenticate with. /// Thrown if the username /// or the accessToken parameter is null. /// Thrown if the username or /// the accessToken parameter is empty. public SaslOAuth2(string username, string accessToken) { username.ThrowIfNull("username"); accessToken.ThrowIfNull("accessToken"); if (username == String.Empty) throw new ArgumentException("The username must not be empty."); if(accessToken == String.Empty) throw new ArgumentException("The access token must not be empty."); Username = username; AccessToken = accessToken; } /// /// Computes the client response to an XOAUTH2 challenge. /// /// The challenge sent by the server. /// The response to the OAuth2 challenge. /// Thrown if the response could not /// be computed. protected override byte[] ComputeResponse(byte[] challenge) { if (Step == 1) Completed = true; // If authentication fails, the server responds with another // challenge (error response) which the client must acknowledge // with a CRLF. byte[] ret = Step == 0 ? ComputeInitialResponse(challenge) : new byte[0]; Step = Step + 1; return ret; } /// /// Computes the initial client response to an XOAUTH2 challenge. /// /// The challenge sent by the server. /// The response to the OAuth2 challenge. /// Thrown if the response could not /// be computed. private byte[] ComputeInitialResponse(byte[] challenge) { // Precondition: Ensure access token is not null and is not empty. if (String.IsNullOrEmpty(Username) || String.IsNullOrEmpty(AccessToken)) { throw new SaslException("The username and access token must not be" + " null or empty."); } // ^A = Control A = (U+0001) char A = '\u0001'; string s = "user=" + Username + A + "auth=Bearer " + AccessToken + A + A; // The response is encoded as ASCII. return Encoding.ASCII.GetBytes(s); } } }