[evolution-ews] Bug 790450 - Check response headers for credential expiration hints



commit 34ad6ca51a8cc88ce67095b0c53e3abb59fdd7f7
Author: Milan Crha <mcrha redhat com>
Date:   Thu Nov 30 17:40:36 2017 +0100

    Bug 790450 - Check response headers for credential expiration hints

 src/camel/camel-ews-store.c   |   55 +++++++++++++++-
 src/server/e-ews-connection.c |  146 +++++++++++++++++++++++++++++++++++++++--
 src/server/e-ews-connection.h |    4 +
 3 files changed, 196 insertions(+), 9 deletions(-)
---
diff --git a/src/camel/camel-ews-store.c b/src/camel/camel-ews-store.c
index 6abc6b9..5137aed 100644
--- a/src/camel/camel-ews-store.c
+++ b/src/camel/camel-ews-store.c
@@ -73,6 +73,7 @@ struct _CamelEwsStorePrivate {
        GMutex connection_lock;
        gboolean has_ooo_set;
        CamelEwsStoreOooAlertState ooo_alert_state;
+       gint password_expires_in_days;
 
        gboolean listen_notifications;
        guint subscription_key;
@@ -1291,6 +1292,52 @@ ews_connect_sync (CamelService *service,
 }
 
 static void
+camel_ews_store_password_will_expire_cb (EEwsConnection *connection,
+                                        gint in_days,
+                                        const gchar *service_url,
+                                        gpointer user_data)
+{
+       CamelEwsStore *ews_store = user_data;
+
+       g_return_if_fail (CAMEL_IS_EWS_STORE (ews_store));
+
+       if (ews_store->priv->password_expires_in_days < 0 ||
+           ews_store->priv->password_expires_in_days > in_days) {
+               CamelService *service;
+               CamelSession *session;
+
+               ews_store->priv->password_expires_in_days = in_days;
+
+               service = CAMEL_SERVICE (ews_store);
+               session = camel_service_ref_session (service);
+
+               if (session) {
+                       gchar *msg;
+
+                       if (service_url) {
+                               msg = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE,
+                                       /* Translators: The "%s" is a service URL, provided by the server */
+                                       _("Password will expire in %d day. Open “%s” to change it."),
+                                       _("Password will expire in %d days. Open “%s” to change it."),
+                                       in_days),
+                                       in_days, service_url);
+                       } else {
+                               msg = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE,
+                                       _("Password will expire in one day."),
+                                       _("Password will expire in %d days."),
+                                       in_days),
+                                       in_days);
+                       }
+
+                       camel_session_user_alert (session, service, CAMEL_SESSION_ALERT_WARNING, msg);
+
+                       g_object_unref (session);
+                       g_free (msg);
+               }
+       }
+}
+
+static void
 stop_pending_updates (CamelEwsStore *ews_store)
 {
        CamelEwsStorePrivate *priv;
@@ -1348,8 +1395,9 @@ ews_store_unset_connection_locked (CamelEwsStore *ews_store)
                        ews_store->priv->listen_notifications = FALSE;
                }
 
-               e_ews_connection_set_password (
-                       ews_store->priv->connection, NULL);
+               e_ews_connection_set_password (ews_store->priv->connection, NULL);
+               g_signal_handlers_disconnect_by_func (ews_store->priv->connection,
+                       G_CALLBACK (camel_ews_store_password_will_expire_cb), ews_store);
                g_object_unref (ews_store->priv->connection);
                ews_store->priv->connection = NULL;
        }
@@ -1855,6 +1903,8 @@ ews_authenticate_sync (CamelService *service,
                g_mutex_lock (&ews_store->priv->connection_lock);
                ews_store_unset_connection_locked (ews_store);
                ews_store->priv->connection = g_object_ref (connection);
+               g_signal_connect (ews_store->priv->connection, "password-will-expire",
+                       G_CALLBACK (camel_ews_store_password_will_expire_cb), ews_store);
                g_mutex_unlock (&ews_store->priv->connection_lock);
 
                /* This consumes all allocated result data. */
@@ -3707,6 +3757,7 @@ camel_ews_store_init (CamelEwsStore *ews_store)
        ews_store->priv->subscription_key = 0;
        ews_store->priv->update_folder_id = 0;
        ews_store->priv->update_folder_list_id = 0;
+       ews_store->priv->password_expires_in_days = -1;
        g_mutex_init (&ews_store->priv->get_finfo_lock);
        g_mutex_init (&ews_store->priv->connection_lock);
        g_rec_mutex_init (&ews_store->priv->update_lock);
diff --git a/src/server/e-ews-connection.c b/src/server/e-ews-connection.c
index 2dd9da1..0751bd7 100644
--- a/src/server/e-ews-connection.c
+++ b/src/server/e-ews-connection.c
@@ -118,6 +118,7 @@ enum {
 
 enum {
        SERVER_NOTIFICATION,
+       PASSWORD_WILL_EXPIRE,
        LAST_SIGNAL,
 };
 
@@ -776,6 +777,94 @@ e_ews_connection_queue_request (EEwsConnection *cnc,
        ews_trigger_next_request (cnc);
 }
 
+static void
+ews_connection_expired_password_to_error (const gchar *service_url,
+                                         GError **error)
+{
+       if (!error)
+               return;
+
+       if (service_url) {
+               g_set_error (error, EWS_CONNECTION_ERROR, EWS_CONNECTION_ERROR_PASSWORDEXPIRED,
+                       _("Password expired. Change password at “%s”."), service_url);
+       } else {
+               g_set_error_literal (error, EWS_CONNECTION_ERROR, EWS_CONNECTION_ERROR_PASSWORDEXPIRED,
+                       _("Password expired."));
+       }
+}
+
+static gboolean
+ews_connection_check_x_ms_credential_headers (SoupMessage *message,
+                                             gint *out_expire_in_days,
+                                             gboolean *out_expired,
+                                             gchar **out_service_url)
+{
+       gboolean any_found = FALSE;
+       const gchar *header;
+
+       if (!message || !message->response_headers)
+               return FALSE;
+
+       header = soup_message_headers_get_list (message->response_headers, 
"X-MS-Credential-Service-CredExpired");
+       if (header && g_ascii_strcasecmp (header, "true") == 0) {
+               any_found = TRUE;
+
+               if (out_expired)
+                       *out_expired = TRUE;
+       }
+
+       header = soup_message_headers_get_list (message->response_headers, "X-MS-Credentials-Expire");
+       if (header) {
+               gint in_days;
+
+               in_days = g_ascii_strtoll (header, NULL, 10);
+               if (in_days <= 30 && in_days >= 0) {
+                       any_found = TRUE;
+
+                       if (out_expire_in_days)
+                               *out_expire_in_days = in_days;
+               }
+       }
+
+       if (any_found && out_service_url) {
+               header = soup_message_headers_get_list (message->response_headers, 
"X-MS-Credential-Service-Url");
+
+               *out_service_url = g_strdup (header);
+       }
+
+       return any_found;
+}
+
+static gboolean
+ews_connection_credentials_failed (EEwsConnection *connection,
+                                  SoupMessage *message,
+                                  GSimpleAsyncResult *simple)
+{
+       gint expire_in_days = 0;
+       gboolean expired = FALSE;
+       gchar *service_url = NULL;
+
+       g_return_val_if_fail (E_IS_EWS_CONNECTION (connection), FALSE);
+       g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE);
+       g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), FALSE);
+
+       if (!ews_connection_check_x_ms_credential_headers (message, &expire_in_days, &expired, &service_url))
+               return FALSE;
+
+       if (expired) {
+               GError *error = NULL;
+
+               ews_connection_expired_password_to_error (service_url, &error);
+               g_simple_async_result_take_error (simple, error);
+       } else if (expire_in_days > 0) {
+               g_signal_emit (connection, signals[PASSWORD_WILL_EXPIRE], 0, expire_in_days, service_url);
+       }
+
+       g_free (service_url);
+
+       return expired;
+}
+
 /* Response callbacks */
 
 static void
@@ -792,7 +881,9 @@ ews_response_cb (SoupSession *session,
        if (g_cancellable_is_cancelled (enode->cancellable))
                goto exit;
 
-       if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
+       if (ews_connection_credentials_failed (enode->cnc, msg, enode->simple)) {
+               goto exit;
+       } else if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
                g_simple_async_result_set_error (
                        enode->simple,
                        EWS_CONNECTION_ERROR,
@@ -1839,6 +1930,16 @@ e_ews_connection_class_init (EEwsConnectionClass *class)
                g_cclosure_marshal_VOID__POINTER,
                G_TYPE_NONE, 1,
                G_TYPE_POINTER);
+
+       signals[PASSWORD_WILL_EXPIRE] = g_signal_new (
+               "password-will-expire",
+               G_OBJECT_CLASS_TYPE (object_class),
+               G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED | G_SIGNAL_ACTION,
+               G_STRUCT_OFFSET (EEwsConnectionClass, password_will_expire),
+               NULL, NULL, NULL,
+               G_TYPE_NONE, 2,
+               G_TYPE_INT,
+               G_TYPE_STRING);
 }
 
 static void
@@ -1981,13 +2082,30 @@ ews_connection_authenticate (SoupSession *sess,
 {
        EEwsConnection *cnc = data;
        CamelNetworkSettings *network_settings;
-       gchar *user, *password;
+       gchar *user, *password, *service_url = NULL;
+       gboolean expired = FALSE;
 
        g_return_if_fail (cnc != NULL);
 
        if (retrying)
                e_ews_connection_set_password (cnc, NULL);
 
+       if (ews_connection_check_x_ms_credential_headers (msg, NULL, &expired, &service_url) && expired) {
+               GError *error = NULL;
+
+               ews_connection_expired_password_to_error (service_url, &error);
+
+               if (error)
+                       soup_message_set_status_full (msg, SOUP_STATUS_IO_ERROR, error->message);
+
+               g_clear_error (&error);
+               g_free (service_url);
+
+               return;
+       }
+
+       g_free (service_url);
+
        network_settings = CAMEL_NETWORK_SETTINGS (cnc->priv->settings);
        user = camel_network_settings_dup_user (network_settings);
 
@@ -2659,9 +2777,19 @@ autodiscover_response_cb (SoupSession *session,
        ad->msgs[idx] = NULL;
 
        if (status != 200) {
-               g_set_error (
-                       &error, SOUP_HTTP_ERROR, status,
-                       "%d %s", status, msg->reason_phrase);
+               gboolean expired = FALSE;
+               gchar *service_url = NULL;
+
+               if (ews_connection_check_x_ms_credential_headers (msg, NULL, &expired, &service_url) && 
expired) {
+                       ews_connection_expired_password_to_error (service_url, &error);
+               } else {
+                       g_set_error (
+                               &error, SOUP_HTTP_ERROR, status,
+                               "%d %s", status, msg->reason_phrase);
+               }
+
+               g_free (service_url);
+
                goto failed;
        }
 
@@ -3214,7 +3342,9 @@ oal_response_cb (SoupSession *soup_session,
        simple = G_SIMPLE_ASYNC_RESULT (user_data);
        data = g_simple_async_result_get_op_res_gpointer (simple);
 
-       if (soup_message->status_code != 200) {
+       if (ews_connection_credentials_failed (data->cnc, soup_message, simple)) {
+               goto exit;
+       } else if (soup_message->status_code != 200) {
                g_simple_async_result_set_error (
                        simple, SOUP_HTTP_ERROR,
                        soup_message->status_code,
@@ -3544,7 +3674,9 @@ oal_download_response_cb (SoupSession *soup_session,
        simple = G_SIMPLE_ASYNC_RESULT (user_data);
        data = g_simple_async_result_get_op_res_gpointer (simple);
 
-       if (soup_message->status_code != 200) {
+       if (ews_connection_credentials_failed (data->cnc, soup_message, simple)) {
+               g_unlink (data->cache_filename);
+       } else if (soup_message->status_code != 200) {
                g_simple_async_result_set_error (
                        simple, SOUP_HTTP_ERROR,
                        soup_message->status_code,
diff --git a/src/server/e-ews-connection.h b/src/server/e-ews-connection.h
index b596bc7..a50bcf9 100644
--- a/src/server/e-ews-connection.h
+++ b/src/server/e-ews-connection.h
@@ -65,6 +65,10 @@ struct _EEwsConnection {
 
 struct _EEwsConnectionClass {
        GObjectClass parent_class;
+
+       void    (* password_will_expire)        (EEwsConnection *connection,
+                                                gint in_days,
+                                                const gchar *service_url);
 };
 
 enum {


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