[geary/wip/17-noisy-problem-reports: 12/12] Implement client-based password and auth failure status prompting



commit 2da01b29e436b4cf392e8cb1debf4596bc1736b5
Author: Michael Gratton <mike vee net>
Date:   Tue Jan 1 22:19:24 2019 +1100

    Implement client-based password and auth failure status prompting
    
    This re-ads support for prompting for service passwords in the client,
    and also gracefully handles showing auth failure infobar and getting
    multple failures at once.

 src/client/application/geary-controller.vala | 113 +++++++++++++++++++++++++--
 src/client/components/main-window.vala       |  23 ++++--
 2 files changed, 124 insertions(+), 12 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 15772f40..59a0394d 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -59,6 +59,7 @@ public class GearyController : Geary.BaseObject {
 
     private const string PROP_ATTEMPT_OPEN_ACCOUNT = "attempt-open-account";
 
+    private const uint MAX_AUTH_ATTEMPTS = 3;
 
     private static string untitled_file_name;
 
@@ -81,6 +82,11 @@ public class GearyController : Geary.BaseObject {
         public Geary.Account account { get; private set; }
         public Geary.Folder? inbox = null;
         public Geary.App.EmailStore store { get; private set; }
+
+        public bool authentication_failed = false;
+        public bool authentication_prompting = false;
+        public uint authentication_attempts = 0;
+
         public Cancellable cancellable { get; private set; default = new Cancellable(); }
 
         public AccountContext(Geary.Account account) {
@@ -625,6 +631,9 @@ public class GearyController : Geary.BaseObject {
     }
 
     private void open_account(Geary.Account account) {
+        account.information.authentication_failure.connect(
+            on_authentication_failure
+        );
         account.notify["current-status"].connect(
             on_account_status_notify
         );
@@ -658,6 +667,9 @@ public class GearyController : Geary.BaseObject {
             // Stop updating status and showing errors when closing
             // the account - the user doesn't care any more
             account.report_problem.disconnect(on_report_problem);
+            account.information.authentication_failure.disconnect(
+                on_authentication_failure
+            );
             account.notify["current-status"].disconnect(
                 on_account_status_notify
             );
@@ -890,17 +902,98 @@ public class GearyController : Geary.BaseObject {
         }
     }
 
-    private void update_account_status() {
-        Geary.Account.Status effective_status =
-            this.accounts.values.fold<Geary.Account.Status>(
-                (ctx, status) => ctx.get_effective_status() | status,
-                0
+    private bool is_currently_prompting() {
+        return this.accounts.values.fold<bool>(
+            (ctx, seed) => ctx.authentication_prompting | seed,
+            false
+        );
+    }
+
+    private async void prompt_for_password(AccountContext context,
+                                           Geary.ServiceInformation service) {
+        Geary.AccountInformation account = context.account.information;
+        bool is_incoming = (service == account.incoming);
+        Geary.Credentials credentials = is_incoming
+            ? account.incoming.credentials
+            : account.get_outgoing_credentials();
+
+        bool handled = true;
+        if (this.account_manager.is_goa_account(account) ||
+            context.authentication_attempts > MAX_AUTH_ATTEMPTS ||
+            credentials == null) {
+            // A managed account has had a problem, we have run out of
+            // authentication attempts, or have been asked for creds
+            // but don't even have a login. So just bail out
+            // immediately and flag the account as needing attention.
+            handled = false;
+        } else {
+            context.authentication_prompting = true;
+            this.application.present();
+            PasswordDialog password_dialog = new PasswordDialog(
+                this.application.get_active_window(),
+                account,
+                service
             );
+            if (password_dialog.run()) {
+                service.credentials = service.credentials.copy_with_token(
+                    password_dialog.password
+                );
+                service.remember_password = password_dialog.remember_password;
+
+                // The update the credentials for the service that the
+                // credentials actually came from
+                Geary.ServiceInformation creds_service =
+                    credentials == account.incoming.credentials
+                    ? account.incoming
+                    : account.outgoing;
+                SecretMediator libsecret = (SecretMediator) account.mediator;
+                try {
+                    yield libsecret.update_token(
+                        account, creds_service, context.cancellable
+                    );
+                    // Update the actual service in the engine though
+                    yield this.application.engine.update_account_service(
+                        account, service, context.cancellable
+                    );
+                } catch (GLib.IOError.CANCELLED err) {
+                    // all good
+                } catch (GLib.Error err) {
+                    report_problem(
+                        new Geary.ServiceProblemReport(
+                            Geary.ProblemType.GENERIC_ERROR,
+                            account,
+                            service,
+                            err
+                        )
+                    );
+                }
+                context.authentication_attempts++;
+            } else {
+                // User cancelled, bail out unconditionally
+                handled = false;
+            }
+            context.authentication_prompting = false;
+        }
+
+        if (!handled) {
+            context.authentication_attempts = 0;
+            context.authentication_failed = true;
+            update_account_status();
+        }
+    }
+
+    private void update_account_status() {
+        Geary.Account.Status effective_status = 0;
+        bool auth_error = false;
+        foreach (AccountContext context in this.accounts.values) {
+            effective_status |= context.get_effective_status();
+            auth_error |= context.authentication_failed;
+        }
 
         foreach (Gtk.Window window in this.application.get_windows()) {
             MainWindow? main = window as MainWindow;
             if (main != null) {
-                main.update_account_status(effective_status);
+                main.update_account_status(effective_status, auth_error);
             }
         }
     }
@@ -999,6 +1092,14 @@ public class GearyController : Geary.BaseObject {
         report_problem(problem);
     }
 
+    private void on_authentication_failure(Geary.AccountInformation account,
+                                           Geary.ServiceInformation service) {
+        AccountContext? context = this.accounts.get(account);
+        if (context != null && !is_currently_prompting()) {
+            this.prompt_for_password.begin(context, service);
+        }
+    }
+
     private void on_retry_service_problem(Geary.ClientService.Status type) {
         AccountContext? context = Geary.traverse(this.accounts.values)
             .first_matching((ctx) => (
diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala
index 1fce807d..9d7e3307 100644
--- a/src/client/components/main-window.vala
+++ b/src/client/components/main-window.vala
@@ -104,19 +104,30 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
     }
 
     /** Updates the window's account status info bars. */
-    public void update_account_status(Geary.Account.Status status) {
+    public void update_account_status(Geary.Account.Status status,
+                                      bool auth_error) {
         // Only ever show one at a time. Offline is primary since
         // nothing else can happen when offline. Service problems are
         // secondary since auth and cert problems can't be resolved
         // when the service isn't talking to the server. Auth and cert
         // problems are enabled elsewhere, since the controller might
         // be already prompting the user about it.
-        this.offline_infobar.set_visible(!status.is_online());
-        this.service_problem_infobar.set_visible(
-            status.is_online() && status.has_service_problem()
-        );
-        this.auth_problem_infobar.hide();
+        bool show_offline = false;
+        bool show_service = false;
+        bool show_auth = false;
+
+        if (!status.is_online()) {
+            show_offline = true;
+        } else if (status.has_service_problem()) {
+            show_service = true;
+        } else if (auth_error) {
+            show_auth = true;
+        }
+
+        this.offline_infobar.set_visible(show_offline);
+        this.service_problem_infobar.set_visible(show_service);
         this.cert_problem_infobar.hide();
+        this.auth_problem_infobar.set_visible(show_auth);
         update_infobar_frame();
     }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]