Add support for (X)OAuth2 SMTP authentication.

This commit is contained in:
Michael James Gratton 2018-05-29 05:51:11 +02:00
parent b8b1482e06
commit ada7f3fdbb
6 changed files with 119 additions and 25 deletions

View file

@ -370,6 +370,7 @@ src/engine/smtp/smtp-data-format.vala
src/engine/smtp/smtp-error.vala
src/engine/smtp/smtp-greeting.vala
src/engine/smtp/smtp-login-authenticator.vala
src/engine/smtp/smtp-oauth2-authenticator.vala
src/engine/smtp/smtp-plain-authenticator.vala
src/engine/smtp/smtp-request.vala
src/engine/smtp/smtp-response-code.vala

View file

@ -288,6 +288,7 @@ engine/smtp/smtp-data-format.vala
engine/smtp/smtp-error.vala
engine/smtp/smtp-greeting.vala
engine/smtp/smtp-login-authenticator.vala
engine/smtp/smtp-oauth2-authenticator.vala
engine/smtp/smtp-plain-authenticator.vala
engine/smtp/smtp-request.vala
engine/smtp/smtp-response.vala

View file

@ -284,6 +284,7 @@ geary_engine_vala_sources = files(
'smtp/smtp-error.vala',
'smtp/smtp-greeting.vala',
'smtp/smtp-login-authenticator.vala',
'smtp/smtp-oauth2-authenticator.vala',
'smtp/smtp-plain-authenticator.vala',
'smtp/smtp-request.vala',
'smtp/smtp-response.vala',

View file

@ -5,12 +5,14 @@
*/
public class Geary.Smtp.Capabilities : Geary.GenericCapabilities {
public const string STARTTLS = "starttls";
public const string AUTH = "auth";
public const string AUTH_PLAIN = "plain";
public const string AUTH_LOGIN = "login";
public const string AUTH_OAUTH2 = "xoauth2";
public const string NAME_SEPARATOR = " ";
public const string VALUE_SEPARATOR = " ";

View file

@ -55,34 +55,64 @@ public class Geary.Smtp.ClientSession {
// Returns authenticator used for successful authentication, otherwise throws exception
private async Authenticator attempt_authentication_async(Credentials creds, Cancellable? cancellable)
throws Error {
// build an authentication style ordering to attempt, going from reported capabilities to standard
// fallbacks, while avoiding repetition ... this is necessary due to server bugs that report
// an authentication type is available but actually isn't, see
// build an authentication style ordering to attempt, going
// from reported capabilities to standard fallbacks, while
// avoiding repetition ... this is necessary due to server
// bugs that report an authentication type is available but
// actually isn't, see
//
// http://redmine.yorba.org/issues/6091
//
// and
//
// http://comments.gmane.org/gmane.mail.pine.general/4004
Gee.ArrayList<string> auth_order = new Gee.ArrayList<string>(String.stri_equal);
// start with advertised authentication styles, in order of our preference (PLAIN
// only requires one round-trip)
if (cx.capabilities != null) {
if (cx.capabilities.has_setting(Capabilities.AUTH, Capabilities.AUTH_PLAIN))
switch (creds.supported_method) {
case Credentials.Method.PASSWORD:
// start with advertised authentication styles, in order of our preference (PLAIN
// only requires one round-trip)
if (cx.capabilities != null) {
if (cx.capabilities.has_setting(Capabilities.AUTH, Capabilities.AUTH_PLAIN))
auth_order.add(Capabilities.AUTH_PLAIN);
if (cx.capabilities.has_setting(Capabilities.AUTH, Capabilities.AUTH_LOGIN))
auth_order.add(Capabilities.AUTH_LOGIN);
}
// fallback on commonly-implemented styles, again in our order of preference
if (!auth_order.contains(Capabilities.AUTH_PLAIN))
auth_order.add(Capabilities.AUTH_PLAIN);
if (cx.capabilities.has_setting(Capabilities.AUTH, Capabilities.AUTH_LOGIN))
if (!auth_order.contains(Capabilities.AUTH_LOGIN))
auth_order.add(Capabilities.AUTH_LOGIN);
if (auth_order.is_empty) {
throw new SmtpError.AUTHENTICATION_FAILED(
"Unable to authenticate using PASSWORD credentials against %s",
to_string()
);
}
break;
case Credentials.Method.OAUTH2:
if (cx.capabilities != null &&
!cx.capabilities.has_setting(Capabilities.AUTH,
Capabilities.AUTH_OAUTH2)) {
throw new SmtpError.AUTHENTICATION_FAILED(
"Unable to authenticate using OAUTH2 credentials against %s",
to_string()
);
}
auth_order.add(Capabilities.AUTH_OAUTH2);
break;
default:
throw new SmtpError.AUTHENTICATION_FAILED(
"Unsupported auth method: %s", creds.supported_method.to_string()
);
}
// fallback on commonly-implemented styles, again in our order of preference
if (!auth_order.contains(Capabilities.AUTH_PLAIN))
auth_order.add(Capabilities.AUTH_PLAIN);
if (!auth_order.contains(Capabilities.AUTH_LOGIN))
auth_order.add(Capabilities.AUTH_LOGIN);
// in current situation, should always have one authentication type to attempt
assert(auth_order.size > 0);
// go through the list, in order, until one style is accepted
do {
Authenticator? authenticator;
@ -90,11 +120,15 @@ public class Geary.Smtp.ClientSession {
case Capabilities.AUTH_PLAIN:
authenticator = new PlainAuthenticator(creds);
break;
case Capabilities.AUTH_LOGIN:
authenticator = new LoginAuthenticator(creds);
break;
case Capabilities.AUTH_OAUTH2:
authenticator = new OAuth2Authenticator(creds);
break;
default:
assert_not_reached();
}

View file

@ -0,0 +1,55 @@
/*
* Copyright 2018 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* Google's proprietary OAuth 2 authentication.
*
* See [[http://tools.ietf.org/html/rfc4616]]
*/
public class Geary.Smtp.OAuth2Authenticator : Geary.Smtp.Authenticator {
private const string OAUTH2_RESP = "user=%s\001auth=Bearer %s\001\001";
public OAuth2Authenticator(Credentials credentials) {
base ("XOAUTH2", credentials);
}
public override Request initiate() {
return new Request(Command.AUTH, { "xoauth2" });
}
public override Memory.Buffer? challenge(int step, Response response)
throws SmtpError {
Memory.Buffer? buf = null;
switch (step) {
case 0:
// The initial AUTH command
buf = new Memory.StringBuffer(
Base64.encode(
OAUTH2_RESP.printf(
credentials.user ?? "",
credentials.token ?? ""
).data
)
);
break;
case 1:
// Server sent a challenge, which will be a Base64 encoded
// JSON blob and which indicates a login failure. We don't
// really care about that (do we?) though since once
// we acknowledge it with a zero-length string the server
// will respond with a SMTP error.
buf = new Memory.StringBuffer("");
break;
}
return buf;
}
}