Add basic pluggable keyring support; fix #6219

Squashed commit of the following:

commit 9167aeb56be6789d49a3e7cdba2a21d2b015e40d
Author: Charles Lindsay <chaz@yorba.org>
Date:   Fri Jan 25 16:11:20 2013 -0800

    Fix for code guidelines

commit 7612a7ddc3df14ef207b9e74ee32fa23710e1ce9
Author: Charles Lindsay <chaz@yorba.org>
Date:   Fri Jan 25 15:26:48 2013 -0800

    Fix code review issues

commit 46635544c98df7a8b6c76f028715814907274389
Merge: 30b611e 6de36ae
Author: Charles Lindsay <chaz@yorba.org>
Date:   Fri Jan 25 12:54:03 2013 -0800

    Merge branch 'master' into pluggable-auth

commit 30b611ed415fe7a2c1d62746f94620132ec86623
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 24 17:12:24 2013 -0800

    Only prompt for IMAP password on startup

commit ca2953b4b33cb367c060164df9f2258217e94046
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 24 16:34:39 2013 -0800

    Fix compile errors; cleanup

commit 545764a6a914ff36a1ca187444d0830a28502bab
Merge: 0460a68 e498354
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 24 16:31:43 2013 -0800

    Merge branch 'master' into pluggable-auth

    Conflicts:
    	src/client/geary-application.vala

commit 0460a68af4de3e762522fd641c16675cfc7d2241
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 24 16:20:31 2013 -0800

    Use Engine's mediator implicitly

commit 22cbb8740e711ca3151b0389aef9cbb8c21928c7
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 24 16:19:55 2013 -0800

    Use flags on things that are supposed to be flags

commit 4dc0eb15d2fe23c92d8cc6cff8a3138b6cb1baf4
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 24 15:49:50 2013 -0800

    Fix prompting in certain cases

commit 56bb2265a6635a754b9a00b469ec457105390896
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 24 14:37:12 2013 -0800

    Fix typo

commit 926f47024f1280271bc36cd8c60eb948bed4a432
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 24 11:43:05 2013 -0800

    Cleanup, compile, smoke test

commit 9ff4257d125e67828f0c813e0806d3d34c114550
Author: Charles Lindsay <chaz@yorba.org>
Date:   Fri Jan 18 10:41:17 2013 -0800

    First stab at new pluggable auth API
This commit is contained in:
Charles Lindsay 2013-01-25 16:13:46 -08:00
parent 6de36ae3eb
commit e971275375
13 changed files with 484 additions and 314 deletions

View file

@ -23,6 +23,7 @@ engine/api/geary-contact-store.vala
engine/api/geary-conversation.vala
engine/api/geary-conversation-monitor.vala
engine/api/geary-credentials.vala
engine/api/geary-credentials-mediator.vala
engine/api/geary-email-flag.vala
engine/api/geary-email-flags.vala
engine/api/geary-email-identifier.vala
@ -200,6 +201,7 @@ client/geary-application.vala
client/geary-args.vala
client/geary-config.vala
client/geary-controller.vala
client/gnome-keyring-mediator.vala
client/main.vala
client/accounts/account-dialog.vala
@ -244,7 +246,6 @@ client/util/util-email.vala
client/util/util-files.vala
client/util/util-gravatar.vala
client/util/util-gtk.vala
client/util/util-keyring.vala
client/util/util-webkit.vala
client/views/formatted-conversation-data.vala

View file

@ -23,14 +23,14 @@ public class PasswordDialog {
private Gtk.Button ok_button;
private Gtk.Grid grid_imap;
private Gtk.Grid grid_smtp;
private PasswordTypeFlag password_flags;
private Geary.CredentialsMediator.ServiceFlag password_flags;
public string imap_password { get; private set; default = ""; }
public string smtp_password { get; private set; default = ""; }
public bool remember_password { get; private set; }
public PasswordDialog(Geary.AccountInformation account_information, bool first_try,
PasswordTypeFlag password_flags) {
Geary.CredentialsMediator.ServiceFlag password_flags) {
this.password_flags = password_flags;
Gtk.Builder builder = GearyApplication.instance.create_builder("password-dialog.glade");
@ -67,12 +67,8 @@ public class PasswordDialog {
// Find server configuration information
Geary.Endpoint imap_endpoint;
Geary.Endpoint smtp_endpoint;
try {
imap_endpoint = account_information.get_imap_endpoint();
smtp_endpoint = account_information.get_smtp_endpoint();
} catch (Geary.EngineError err) {
error("Error getting endpoints: %s", err.message);
}
imap_endpoint = account_information.get_imap_endpoint();
smtp_endpoint = account_information.get_smtp_endpoint();
string imap_server_host = imap_endpoint.host_specifier;
uint16 imap_server_port = imap_endpoint.default_port;

View file

@ -132,7 +132,8 @@ along with Geary; if not, write to the Free Software Foundation, Inc.,
// Start Geary.
try {
yield Geary.Engine.instance.open_async(get_user_data_directory(), get_resource_directory());
yield Geary.Engine.instance.open_async(get_user_data_directory(), get_resource_directory(),
new GnomeKeyringMediator());
} catch (Error e) {
error("Error opening Geary.Engine instance: %s", e.message);
}
@ -168,10 +169,12 @@ along with Geary; if not, write to the Free Software Foundation, Inc.,
private void initialize_account() {
Geary.AccountInformation? account_information = get_account();
if (account_information == null)
if (account_information == null) {
create_account(null);
else
open_account(account_information.email, null, null, PasswordTypeFlag.IMAP | PasswordTypeFlag.SMTP, null);
} else {
open_account(account_information.email,
Geary.CredentialsMediator.ServiceFlag.IMAP, null);
}
}
private void create_account(string? email) {
@ -229,33 +232,29 @@ along with Geary; if not, write to the Free Software Foundation, Inc.,
// Attempts to validate and add an account. Returns true on success, else false.
public async bool validate_async(Geary.AccountInformation account_information,
Cancellable? cancellable = null) {
bool success = false;
try {
success = yield account_information.validate_async(cancellable);
} catch (Geary.EngineError err) {
if (!yield Geary.Engine.instance.validate_account_information_async(
account_information, cancellable))
return false;
} catch (Error err) {
debug("Error validating account: %s", err.message);
return false;
}
if (success) {
account_information.store_async.begin(cancellable);
try {
set_account(account_information.get_account());
debug("Successfully validated account information");
return true;
} catch (Geary.EngineError err) {
debug("Unable to retrieve email account: %s", err.message);
}
}
account_information.store_async.begin(cancellable);
return false;
try {
set_account(Geary.Engine.instance.get_account_instance(account_information));
debug("Successfully validated account information");
return true;
} catch (Error err) {
debug("Unable to retrieve email account: %s", err.message);
return false;
}
}
private void open_account(string email, string? old_imap_password, string? old_smtp_password,
PasswordTypeFlag password_flags, Cancellable? cancellable) {
private void open_account(string email, Geary.CredentialsMediator.ServiceFlag password_flags,
Cancellable? cancellable) {
Geary.AccountInformation account_information;
try {
account_information = Geary.Engine.instance.get_accounts().get(email);
@ -265,26 +264,24 @@ along with Geary; if not, write to the Free Software Foundation, Inc.,
error("Unable to open account information for label %s: %s", email, err.message);
}
bool imap_remember_password = account_information.imap_remember_password;
bool smtp_remember_password = account_information.smtp_remember_password;
string? imap_password, smtp_password;
get_passwords(account_information, password_flags, ref imap_remember_password,
ref smtp_remember_password, out imap_password, out smtp_password);
if (imap_password == null || smtp_password == null) {
set_account(null);
return;
}
account_information.imap_remember_password = imap_remember_password;
account_information.smtp_remember_password = smtp_remember_password;
account_information.store_async.begin(cancellable);
account_information.imap_credentials.pass = imap_password;
account_information.smtp_credentials.pass = smtp_password;
account_information.fetch_passwords_async.begin(password_flags,
on_open_account_fetch_passwords_finished);
}
private void on_open_account_fetch_passwords_finished(Object? object, AsyncResult result) {
Geary.AccountInformation? account_information = object as Geary.AccountInformation;
assert(account_information != null);
try {
set_account(account_information.get_account());
} catch (Geary.EngineError err) {
if (!account_information.fetch_passwords_async.end(result))
exit(1);
} catch (Error e) {
error("Error fetching stored passwords: %s", e.message);
}
try {
set_account(Geary.Engine.instance.get_account_instance(account_information));
} catch (Error err) {
// Our service provider is wrong. But we can't change it, because we don't want to
// change the service provider for an existing account.
debug("Unable to retrieve email account: %s", err.message);
@ -304,83 +301,11 @@ along with Geary; if not, write to the Free Software Foundation, Inc.,
return null;
}
private void get_passwords(Geary.AccountInformation old_account_information,
PasswordTypeFlag password_flags, ref bool imap_remember_password,
ref bool smtp_remember_password, out string? imap_password, out string? smtp_password) {
imap_password = null;
if (!old_account_information.imap_credentials.is_complete() && imap_remember_password)
imap_password = keyring_get_password(old_account_information.imap_credentials.user, PasswordType.IMAP);
smtp_password = null;
if (!old_account_information.smtp_credentials.is_complete() && smtp_remember_password)
smtp_password = keyring_get_password(old_account_information.smtp_credentials.user, PasswordType.SMTP);
if (Geary.String.is_empty_or_whitespace(imap_password) ||
Geary.String.is_empty_or_whitespace(smtp_password)) {
request_passwords(old_account_information, password_flags, out imap_password,
out smtp_password, out imap_remember_password, out smtp_remember_password);
}
}
private string get_default_real_name() {
string real_name = Environment.get_real_name();
return real_name == "Unknown" ? "" : real_name;
}
private void request_passwords(Geary.AccountInformation old_account_information,
PasswordTypeFlag password_flags, out string? imap_password, out string? smtp_password,
out bool imap_remember_password, out bool smtp_remember_password) {
Geary.AccountInformation temp_account_information;
try {
temp_account_information = Geary.Engine.instance.get_accounts().get(old_account_information.email);
if (temp_account_information == null)
temp_account_information = Geary.Engine.instance.create_orphan_account(old_account_information.email);
} catch (Error err) {
error("Unable to open account information for %s: %s", old_account_information.email,
err.message);
}
temp_account_information.imap_credentials = old_account_information.imap_credentials.copy();
temp_account_information.smtp_credentials = old_account_information.smtp_credentials.copy();
bool first_try = !temp_account_information.imap_credentials.is_complete() ||
!temp_account_information.smtp_credentials.is_complete();
PasswordDialog password_dialog = new PasswordDialog(temp_account_information, first_try,
password_flags);
if (!password_dialog.run()) {
exit(1);
imap_password = null;
smtp_password = null;
imap_remember_password = false;
smtp_remember_password = false;
return;
}
// password_dialog.password should never be null at this point. It will only be null when
// password_dialog.run() returns false, in which case we have already exited/returned.
imap_password = password_dialog.imap_password;
smtp_password = password_dialog.smtp_password;
imap_remember_password = smtp_remember_password = password_dialog.remember_password;
if (password_flags.has_imap()) {
if (imap_remember_password) {
keyring_save_password(new Geary.Credentials(temp_account_information.imap_credentials.user,
imap_password), PasswordType.IMAP);
} else {
keyring_delete_password(temp_account_information.imap_credentials.user, PasswordType.IMAP);
}
}
if (password_flags.has_smtp()) {
if (smtp_remember_password) {
keyring_save_password(new Geary.Credentials(temp_account_information.smtp_credentials.user,
smtp_password), PasswordType.SMTP);
} else {
keyring_delete_password(temp_account_information.smtp_credentials.user, PasswordType.SMTP);
}
}
}
// Prompt the user for a service, real name, username, and password, and try to start Geary.
private Geary.AccountInformation? request_account_information(Geary.AccountInformation? old_info) {
Geary.AccountInformation? new_info = old_info;
@ -416,23 +341,33 @@ along with Geary; if not, write to the Free Software Foundation, Inc.,
break;
}
if (new_info.imap_remember_password)
keyring_save_password(new_info.imap_credentials, PasswordType.IMAP);
else
keyring_delete_password(new_info.imap_credentials.user, PasswordType.IMAP);
if (new_info.smtp_remember_password)
keyring_save_password(new_info.smtp_credentials, PasswordType.SMTP);
else
keyring_delete_password(new_info.smtp_credentials.user, PasswordType.SMTP);
do_update_stored_passwords_async.begin(Geary.CredentialsMediator.ServiceFlag.IMAP |
Geary.CredentialsMediator.ServiceFlag.SMTP, new_info);
return new_info;
}
private async void do_update_stored_passwords_async(Geary.CredentialsMediator.ServiceFlag services,
Geary.AccountInformation account_information) {
try {
yield account_information.update_stored_passwords_async(services);
} catch (Error e) {
debug("Error updating stored passwords: %s", e.message);
}
}
private void on_report_problem(Geary.Account.Problem problem, Geary.AccountSettings settings,
Error? err) {
debug("Reported problem: %s Error: %s", problem.to_string(), err != null ? err.message : "(N/A)");
Geary.AccountInformation account_information;
try {
account_information = Geary.Engine.instance.get_accounts().get(settings.email.address);
} catch (Error e) {
error("Couldn't find previously-opened account %s", settings.email.address);
}
switch (problem) {
case Geary.Account.Problem.DATABASE_FAILURE:
case Geary.Account.Problem.HOST_UNREACHABLE:
@ -443,17 +378,15 @@ along with Geary; if not, write to the Free Software Foundation, Inc.,
// TODO: Different password dialog prompt for SMTP or IMAP login.
case Geary.Account.Problem.RECV_EMAIL_LOGIN_FAILED:
account.report_problem.disconnect(on_report_problem);
keyring_clear_password(account.settings.imap_credentials.user, PasswordType.IMAP);
open_account(account.settings.email.address, account.settings.imap_credentials.pass,
account.settings.smtp_credentials.pass, PasswordTypeFlag.IMAP, null);
do_password_failed_async.begin(Geary.CredentialsMediator.ServiceFlag.IMAP,
account_information);
break;
// TODO: Different password dialog prompt for SMTP or IMAP login.
case Geary.Account.Problem.SEND_EMAIL_LOGIN_FAILED:
account.report_problem.disconnect(on_report_problem);
keyring_clear_password(account.settings.smtp_credentials.user, PasswordType.SMTP);
open_account(account.settings.email.address, account.settings.imap_credentials.pass,
account.settings.smtp_credentials.pass, PasswordTypeFlag.SMTP, null);
do_password_failed_async.begin(Geary.CredentialsMediator.ServiceFlag.SMTP,
account_information);
break;
default:
@ -461,6 +394,17 @@ along with Geary; if not, write to the Free Software Foundation, Inc.,
}
}
private async void do_password_failed_async(Geary.CredentialsMediator.ServiceFlag services,
Geary.AccountInformation account_information) {
try {
yield account_information.clear_stored_passwords_async(services);
} catch (Error e) {
debug("Error clearing stored passwords: %s", e.message);
}
open_account(account_information.email, services, null);
}
public File get_user_data_directory() {
return File.new_for_path(Environment.get_user_data_dir()).get_child("geary");
}

View file

@ -0,0 +1,87 @@
/* Copyright 2011-2012 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class GnomeKeyringMediator : Geary.CredentialsMediator, Object {
private const string OLD_GEARY_USERNAME_PREFIX = "org.yorba.geary username:";
private string get_key_name(Geary.CredentialsMediator.Service service, string user) {
switch (service) {
case Service.IMAP:
return "org.yorba.geary imap_username:" + user;
case Service.SMTP:
return "org.yorba.geary smtp_username:" + user;
default:
assert_not_reached();
}
}
public virtual async string? get_password_async(
Geary.CredentialsMediator.Service service, string username) throws Error {
string password;
GnomeKeyring.Result result = GnomeKeyring.find_password_sync(GnomeKeyring.NETWORK_PASSWORD,
out password, "user", get_key_name(service, username));
if (result != GnomeKeyring.Result.OK) {
// fallback to the old keyring key string for upgrading users
result = GnomeKeyring.find_password_sync(GnomeKeyring.NETWORK_PASSWORD, out password,
"user", OLD_GEARY_USERNAME_PREFIX + username);
}
if (result != GnomeKeyring.Result.OK)
debug("Unable to fetch password in GNOME keyring: %s", result.to_string());
return (result == GnomeKeyring.Result.OK) ? password : null;
}
public virtual async void set_password_async(
Geary.CredentialsMediator.Service service, Geary.Credentials credentials) throws Error {
string key_name = get_key_name(service, credentials.user);
GnomeKeyring.Result result = GnomeKeyring.store_password_sync(GnomeKeyring.NETWORK_PASSWORD,
null, key_name, credentials.pass, "user", key_name);
if (result != GnomeKeyring.Result.OK)
debug("Unable to store password in GNOME keyring: %s", result.to_string());
}
public virtual async void clear_password_async(
Geary.CredentialsMediator.Service service, string username) throws Error {
// delete new-style and old-style locations
GnomeKeyring.delete_password_sync(GnomeKeyring.NETWORK_PASSWORD, "user",
get_key_name(service, username));
GnomeKeyring.delete_password_sync(GnomeKeyring.NETWORK_PASSWORD, "user",
OLD_GEARY_USERNAME_PREFIX + username);
}
public virtual async bool prompt_passwords_async(Geary.CredentialsMediator.ServiceFlag services,
Geary.AccountInformation account_information,
out string? imap_password, out string? smtp_password,
out bool imap_remember_password, out bool smtp_remember_password) throws Error {
bool first_try = !account_information.imap_credentials.is_complete() ||
!account_information.smtp_credentials.is_complete();
PasswordDialog password_dialog = new PasswordDialog(account_information, first_try,
services);
if (!password_dialog.run()) {
imap_password = null;
smtp_password = null;
imap_remember_password = false;
smtp_remember_password = false;
return false;
}
// password_dialog.password should never be null at this point. It will only be null when
// password_dialog.run() returns false, in which case we have already returned.
imap_password = password_dialog.imap_password;
smtp_password = password_dialog.smtp_password;
imap_remember_password = password_dialog.remember_password;
smtp_remember_password = password_dialog.remember_password;
return true;
}
}

View file

@ -1,94 +0,0 @@
/* Copyright 2011-2012 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
const string OLD_GEARY_USERNAME_PREFIX = "org.yorba.geary username:";
public enum PasswordType {
IMAP,
SMTP;
public string get_prefix() {
switch (this) {
case IMAP:
return "org.yorba.geary imap_username:";
case SMTP:
return "org.yorba.geary smtp_username:";
default:
assert_not_reached();
}
}
}
[Flags]
public enum PasswordTypeFlag {
IMAP,
SMTP;
public bool has_imap() {
return (this & IMAP) == IMAP;
}
public bool has_smtp() {
return (this & SMTP) == SMTP;
}
}
private static string keyring_get_key(PasswordType password_type, string username) {
return password_type.get_prefix() + username;
}
public static bool keyring_save_password(Geary.Credentials credentials, PasswordType password_type) {
string key = keyring_get_key(password_type, credentials.user);
GnomeKeyring.Result result = GnomeKeyring.store_password_sync(GnomeKeyring.NETWORK_PASSWORD,
null, key, credentials.pass, "user", key);
if (result != GnomeKeyring.Result.OK)
debug("Unable to store password in GNOME keyring: %s", result.to_string());
return (result == GnomeKeyring.Result.OK);
}
public bool keyring_clear_password(string username, PasswordType password_type) {
string key = keyring_get_key(password_type, username);
GnomeKeyring.Result result = GnomeKeyring.store_password_sync(GnomeKeyring.NETWORK_PASSWORD,
null, key, "", "user", key);
if (result != GnomeKeyring.Result.OK)
debug("Unable to clear password in GNOME keyring: %s", result.to_string());
return (result == GnomeKeyring.Result.OK);
}
public static void keyring_delete_password(string username, PasswordType password_type) {
// delete new-style and old-style locations
GnomeKeyring.delete_password_sync(GnomeKeyring.NETWORK_PASSWORD, "user",
keyring_get_key(password_type, username));
GnomeKeyring.delete_password_sync(GnomeKeyring.NETWORK_PASSWORD, "user",
OLD_GEARY_USERNAME_PREFIX + username);
}
// Returns the password for the given username, or null if not set.
public static string? keyring_get_password(string username, PasswordType password_type) {
string password;
GnomeKeyring.Result result = GnomeKeyring.find_password_sync(GnomeKeyring.NETWORK_PASSWORD,
out password, "user", keyring_get_key(password_type, username));
if (result != GnomeKeyring.Result.OK) {
// fallback to the old keyring key string for upgrading users
result = GnomeKeyring.find_password_sync(GnomeKeyring.NETWORK_PASSWORD, out password,
"user", OLD_GEARY_USERNAME_PREFIX + username);
}
if (result != GnomeKeyring.Result.OK)
debug("Unable to fetch password in GNOME keyring: %s", result.to_string());
return (result == GnomeKeyring.Result.OK) ? password : null;
}

View file

@ -27,7 +27,7 @@ public class Geary.DBus.Controller {
public async void start() {
try {
yield Geary.Engine.instance.open_async(get_user_data_directory(), get_resource_directory());
yield Geary.Engine.instance.open_async(get_user_data_directory(), get_resource_directory(), null);
connection = yield Bus.get(GLib.BusType.SESSION);
@ -41,8 +41,8 @@ public class Geary.DBus.Controller {
// convert AccountInformation into an Account
try {
account = account_information.get_account();
} catch (EngineError err) {
account = Geary.Engine.instance.get_account_instance(account_information);
} catch (Error err) {
error("Problem loading account from account information: %s", err.message);
}

View file

@ -91,61 +91,154 @@ public class Geary.AccountInformation : Object {
imap_server_pipeline = false;
}
public async bool validate_async(Cancellable? cancellable = null) throws EngineError {
AccountSettings settings = new AccountSettings(this);
/**
* Juggles get_passwords_async() and prompt_passwords_async() to fetch the
* passwords for the given services. Return true if all passwords were in
* the key store or the user proceeded normally, false if the user tried to
* cancel.
*/
public async bool fetch_passwords_async(CredentialsMediator.ServiceFlag services) throws Error {
CredentialsMediator.ServiceFlag unset_services =
yield get_passwords_async(services);
// validate IMAP, which requires logging in and establishing an AUTHORIZED cx state
bool imap_valid = false;
Geary.Imap.ClientSession? imap_session = new Imap.ClientSession(settings.imap_endpoint, true);
try {
yield imap_session.connect_async(cancellable);
yield imap_session.initiate_session_async(settings.imap_credentials, cancellable);
if (unset_services == 0)
return true;
return yield prompt_passwords_async(unset_services);
}
private void check_mediator_instance() throws EngineError {
if (Geary.Engine.instance.authentication_mediator == null)
throw new EngineError.OPEN_REQUIRED(
"Geary.Engine instance needs to be open with a valid Geary.CredentialsMediator");
}
/**
* Use Engine's authentication mediator to retrieve the passwords for the
* given services. The passwords will be stored in the appropriate
* credentials in this instance. Return any services that could *not* be
* retrieved from the key store (in which case you may want to call
* prompt_passwords_async() on the return value), or 0 if all were
* retrieved.
*/
public async CredentialsMediator.ServiceFlag get_passwords_async(
CredentialsMediator.ServiceFlag services) throws Error {
check_mediator_instance();
CredentialsMediator mediator = Geary.Engine.instance.authentication_mediator;
CredentialsMediator.ServiceFlag failed_services = 0;
if (services.has_imap()) {
string? imap_password = yield mediator.get_password_async(
CredentialsMediator.Service.IMAP, imap_credentials.user);
// Connected and initiated, still need to be sure connection authorized
string current_mailbox;
if (imap_session.get_context(out current_mailbox) == Imap.ClientSession.Context.AUTHORIZED)
imap_valid = true;
} catch (Error err) {
debug("Error validating IMAP account info: %s", err.message);
// fall through so session can be disconnected
if (imap_password != null)
imap_credentials.pass = imap_password;
else
failed_services |= CredentialsMediator.ServiceFlag.IMAP;
}
try {
yield imap_session.disconnect_async(cancellable);
} catch (Error err) {
// ignored
} finally {
imap_session = null;
if (services.has_smtp()) {
string? smtp_password = yield mediator.get_password_async(
CredentialsMediator.Service.SMTP, smtp_credentials.user);
if (smtp_password != null)
smtp_credentials.pass = smtp_password;
else
failed_services |= CredentialsMediator.ServiceFlag.SMTP;
}
if (!imap_valid)
return failed_services;
}
/**
* Use the Engine's authentication mediator to prompt for the passwords for
* the given services. The passwords will be stored in the appropriate
* credentials in this instance. After the prompt, the passwords will be
* updated in the key store using update_stored_passwords_async(). Return
* whether the user proceeded normally (false if they tried to cancel the
* prompt).
*/
public async bool prompt_passwords_async(
CredentialsMediator.ServiceFlag services) throws Error {
check_mediator_instance();
string? imap_password, smtp_password;
bool imap_remember_password, smtp_remember_password;
if (!yield Geary.Engine.instance.authentication_mediator.prompt_passwords_async(
services, this, out imap_password, out smtp_password,
out imap_remember_password, out smtp_remember_password))
return false;
// SMTP is simpler, merely see if login works and done (throws an SmtpError if not)
bool smtp_valid = false;
Geary.Smtp.ClientSession? smtp_session = new Geary.Smtp.ClientSession(settings.smtp_endpoint);
try {
yield smtp_session.login_async(settings.smtp_credentials, cancellable);
smtp_valid = true;
} catch (Error err) {
debug("Error validating SMTP account info: %s", err.message);
// fall through so session can be disconnected
if (services.has_imap()) {
imap_credentials.pass = imap_password;
this.imap_remember_password = imap_remember_password;
}
try {
yield smtp_session.logout_async(cancellable);
} catch (Error err) {
// ignored
} finally {
smtp_session = null;
if (services.has_smtp()) {
smtp_credentials.pass = smtp_password;
this.smtp_remember_password = smtp_remember_password;
}
return smtp_valid;
}
public Endpoint get_imap_endpoint() throws EngineError {
yield update_stored_passwords_async(services);
return true;
}
/**
* Use the Engine's authentication mediator to set or clear the passwords
* for the given services in the key store.
*/
public async void update_stored_passwords_async(
CredentialsMediator.ServiceFlag services) throws Error {
check_mediator_instance();
CredentialsMediator mediator = Geary.Engine.instance.authentication_mediator;
if (services.has_imap()) {
if (imap_remember_password) {
yield mediator.set_password_async(
CredentialsMediator.Service.IMAP, imap_credentials);
} else {
yield mediator.clear_password_async(
CredentialsMediator.Service.IMAP, imap_credentials.user);
}
}
if (services.has_smtp()) {
if (smtp_remember_password) {
yield mediator.set_password_async(
CredentialsMediator.Service.SMTP, smtp_credentials);
} else {
yield mediator.clear_password_async(
CredentialsMediator.Service.SMTP, smtp_credentials.user);
}
}
}
/**
* Use the Engine's authentication mediator to clear the passwords for the
* given services in the key store.
*/
public async void clear_stored_passwords_async(
CredentialsMediator.ServiceFlag services) throws Error {
check_mediator_instance();
CredentialsMediator mediator = Geary.Engine.instance.authentication_mediator;
if (services.has_imap()) {
yield mediator.clear_password_async(
CredentialsMediator.Service.IMAP, imap_credentials.user);
}
if (services.has_smtp()) {
yield mediator.clear_password_async(
CredentialsMediator.Service.SMTP, smtp_credentials.user);
}
}
public Endpoint get_imap_endpoint() {
switch (service_provider) {
case ServiceProvider.GMAIL:
return ImapEngine.GmailAccount.IMAP_ENDPOINT;
@ -164,12 +257,11 @@ public class Geary.AccountInformation : Object {
imap_flags, Imap.ClientConnection.RECOMMENDED_TIMEOUT_SEC);
default:
throw new EngineError.NOT_FOUND("Service provider of type %s not known",
service_provider.to_string());
assert_not_reached();
}
}
public Endpoint get_smtp_endpoint() throws EngineError {
public Endpoint get_smtp_endpoint() {
switch (service_provider) {
case ServiceProvider.GMAIL:
return ImapEngine.GmailAccount.SMTP_ENDPOINT;
@ -188,33 +280,7 @@ public class Geary.AccountInformation : Object {
smtp_flags, Smtp.ClientConnection.DEFAULT_TIMEOUT_SEC);
default:
throw new EngineError.NOT_FOUND("Service provider of type %s not known",
service_provider.to_string());
}
}
public Geary.Account get_account() throws EngineError {
AccountSettings settings = new AccountSettings(this);
ImapDB.Account local_account = new ImapDB.Account(settings);
Imap.Account remote_account = new Imap.Account(settings);
switch (service_provider) {
case ServiceProvider.GMAIL:
return new ImapEngine.GmailAccount("Gmail account %s".printf(email),
settings, remote_account, local_account);
case ServiceProvider.YAHOO:
return new ImapEngine.YahooAccount("Yahoo account %s".printf(email),
settings, remote_account, local_account);
case ServiceProvider.OTHER:
return new ImapEngine.OtherAccount("Other account %s".printf(email),
settings, remote_account, local_account);
default:
throw new EngineError.NOT_FOUND("Service provider of type %s not known",
service_provider.to_string());
assert_not_reached();
}
}

View file

@ -736,7 +736,7 @@ public class Geary.ConversationMonitor : Object {
// reset
retry_connection = false;
debug("Folder %s closed due to error, restablishing connection to continue monitoring conversations",
debug("Folder %s closed due to error, reestablishing connection to continue monitoring conversations",
folder.to_string());
// First retry is immediate; thereafter, a delay
@ -763,7 +763,7 @@ public class Geary.ConversationMonitor : Object {
debug("Reestablished connection to %s, continuing to monitor conversations",
folder.to_string());
} catch (Error start_err) {
debug("Unable to restablish connection to %s, retrying in %d seconds: %s", folder.to_string(),
debug("Unable to reestablish connection to %s, retrying in %d seconds: %s", folder.to_string(),
RETRY_CONNECTION_SEC, start_err.message);
schedule_retry(true);

View file

@ -0,0 +1,59 @@
/* Copyright 2011-2012 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public interface Geary.CredentialsMediator : Object {
public enum Service {
IMAP,
SMTP;
}
[Flags]
public enum ServiceFlag {
IMAP,
SMTP;
public bool has_imap() {
return (this & IMAP) == IMAP;
}
public bool has_smtp() {
return (this & SMTP) == SMTP;
}
}
/**
* Query the key store for the password of the given username for the given
* service. Return null if the password wasn't in the key store, or the
* password if it was.
*/
public abstract async string? get_password_async(Service service, string username) throws Error;
/**
* Add or update the key store's password entry for the given credentials
* for the given service.
*/
public abstract async void set_password_async(Service service,
Geary.Credentials credentials) throws Error;
/**
* Deletes the key store's password entry for the given credentials for the
* given service. Do nothing (and do *not* throw an error) if the
* credentials weren't in the key store.
*/
public abstract async void clear_password_async(Service service, string username) throws Error;
/**
* Prompt the user to enter passwords for the given services in the given
* account. Set the out parameters for the services to the values entered
* by the user (out parameters for services not being prompted for are
* ignored). Return false if the user tried to cancel the interaction, or
* true if they tried to proceed.
*/
public abstract async bool prompt_passwords_async(ServiceFlag services,
AccountInformation account_information,
out string? imap_password, out string? smtp_password,
out bool imap_remember_password, out bool smtp_remember_password) throws Error;
}

View file

@ -17,9 +17,11 @@ public class Geary.Engine {
public File? user_data_dir { get; private set; default = null; }
public File? resource_dir { get; private set; default = null; }
public Geary.CredentialsMediator? authentication_mediator { get; private set; default = null; }
private bool is_open = false;
private Gee.HashMap<string, AccountInformation>? accounts = null;
private Gee.HashMap<string, Account>? account_instances = null;
/**
* Fired when the engine is opened.
@ -66,17 +68,22 @@ public class Geary.Engine {
}
/**
* Initializes the engine, and makes all existing accounts available.
* Initializes the engine, and makes all existing accounts available. The
* given authentication mediator will be used to retrieve all passwords
* when necessary.
*/
public async void open_async(File user_data_dir, File resource_dir,
Geary.CredentialsMediator? authentication_mediator,
Cancellable? cancellable = null) throws Error {
if (is_open)
throw new EngineError.ALREADY_OPEN("Geary.Engine instance already open");
this.user_data_dir = user_data_dir;
this.resource_dir = resource_dir;
this.authentication_mediator = authentication_mediator;
accounts = new Gee.HashMap<string, AccountInformation>();
account_instances = new Gee.HashMap<string, Account>();
is_open = true;
opened();
@ -128,7 +135,9 @@ public class Geary.Engine {
user_data_dir = null;
resource_dir = null;
authentication_mediator = null;
accounts = null;
account_instances = null;
is_open = false;
closed();
@ -154,7 +163,107 @@ public class Geary.Engine {
return new AccountInformation(user_data_dir.get_child(email));
}
/**
* Returns whether the account information "validates", which means here
* that we can connect to the endpoints and authenticate using the supplied
* credentials.
*/
public async bool validate_account_information_async(AccountInformation account,
Cancellable? cancellable = null) throws Error {
check_opened();
// validate IMAP, which requires logging in and establishing an AUTHORIZED cx state
bool imap_valid = false;
Geary.Imap.ClientSession? imap_session = new Imap.ClientSession(account.get_imap_endpoint(), true);
try {
yield imap_session.connect_async(cancellable);
yield imap_session.initiate_session_async(account.imap_credentials, cancellable);
// Connected and initiated, still need to be sure connection authorized
string current_mailbox;
if (imap_session.get_context(out current_mailbox) == Imap.ClientSession.Context.AUTHORIZED)
imap_valid = true;
} catch (Error err) {
debug("Error validating IMAP account info: %s", err.message);
// fall through so session can be disconnected
}
try {
yield imap_session.disconnect_async(cancellable);
} catch (Error err) {
// ignored
} finally {
imap_session = null;
}
if (!imap_valid)
return false;
// SMTP is simpler, merely see if login works and done (throws an SmtpError if not)
bool smtp_valid = false;
Geary.Smtp.ClientSession? smtp_session = new Geary.Smtp.ClientSession(account.get_smtp_endpoint());
try {
yield smtp_session.login_async(account.smtp_credentials, cancellable);
smtp_valid = true;
} catch (Error err) {
debug("Error validating SMTP account info: %s", err.message);
// fall through so session can be disconnected
}
try {
yield smtp_session.logout_async(cancellable);
} catch (Error err) {
// ignored
} finally {
smtp_session = null;
}
return smtp_valid;
}
/**
* Creates a Geary.Account from a Geary.AccountInformation (which is what
* other methods in this interface deal in).
*/
public Geary.Account get_account_instance(AccountInformation account_information)
throws Error {
check_opened();
if (account_instances.has_key(account_information.email))
return account_instances.get(account_information.email);
AccountSettings settings = new AccountSettings(account_information);
ImapDB.Account local_account = new ImapDB.Account(settings);
Imap.Account remote_account = new Imap.Account(settings);
Geary.Account account;
switch (account_information.service_provider) {
case ServiceProvider.GMAIL:
account = new ImapEngine.GmailAccount("Gmail account %s".printf(account_information.email),
settings, remote_account, local_account);
break;
case ServiceProvider.YAHOO:
account = new ImapEngine.YahooAccount("Yahoo account %s".printf(account_information.email),
settings, remote_account, local_account);
break;
case ServiceProvider.OTHER:
account = new ImapEngine.OtherAccount("Other account %s".printf(account_information.email),
settings, remote_account, local_account);
break;
default:
assert_not_reached();
}
account_instances.set(account_information.email, account);
return account;
}
/**
* Adds the account to be tracked by the engine. Should only be called from
* AccountInformation.store_async() and this class.
@ -185,6 +294,8 @@ public class Geary.Engine {
// TODO: delete the account from disk.
account_removed(account);
account_instances.unset(account.email);
}
}
}

View file

@ -556,7 +556,7 @@ public class Geary.Imap.ClientSession {
*/
public async CommandResponse login_async(Geary.Credentials credentials, Cancellable? cancellable = null)
throws Error {
LoginParams params = new LoginParams(credentials.user, credentials.pass, cancellable,
LoginParams params = new LoginParams(credentials.user, credentials.pass ?? "", cancellable,
login_async.callback);
fsm.issue(Event.LOGIN, null, params);

View file

@ -23,7 +23,7 @@ public class Geary.Smtp.LoginAuthenticator : Geary.Smtp.AbstractAuthenticator {
return Base64.encode(credentials.user.data).data;
case 1:
return Base64.encode(credentials.pass.data).data;
return Base64.encode((credentials.pass ?? "").data).data;
default:
return null;

View file

@ -29,7 +29,7 @@ public class Geary.Smtp.PlainAuthenticator : Geary.Smtp.AbstractAuthenticator {
growable.append(nul);
growable.append(credentials.user.data);
growable.append(nul);
growable.append(credentials.pass.data);
growable.append((credentials.pass ?? "").data);
return Base64.encode(growable.get_array()).data;
}