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);
}
}
}