Add support for (X)OAuth2 SMTP authentication.
This commit is contained in:
parent
b8b1482e06
commit
ada7f3fdbb
6 changed files with 119 additions and 25 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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 = " ";
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
55
src/engine/smtp/smtp-oauth2-authenticator.vala
Normal file
55
src/engine/smtp/smtp-oauth2-authenticator.vala
Normal 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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue