[gnome-online-accounts/ebassi/caldav-rebased: 2/2] Add generic CalDAV Provider




commit 424fe8a397bc863617ef296b45a746a06db56edb
Author: Will <author@will.party>
Date:   Tue Aug 25 22:29:21 2020 -0700

    Add generic CalDAV Provider
    
    CalDAV is a mechanism for sharing and updating calendars over WebDAV.
    
    The CalDAV provider allows us to share authentication to calendar
    providers; evolution-data-server already allows access, but we still
    require Evolution to configure authentication.
    
    See: #1

 meson.build                        |   7 +
 meson_options.txt                  |   1 +
 po/POTFILES.in                     |   1 +
 src/goabackend/goabackendenums.h   |   3 +
 src/goabackend/goacaldavprovider.c | 897 +++++++++++++++++++++++++++++++++++++
 src/goabackend/goacaldavprovider.h |  37 ++
 src/goabackend/goaprovider.c       |   4 +
 src/goabackend/meson.build         |   1 +
 8 files changed, 951 insertions(+)
---
diff --git a/meson.build b/meson.build
index c9270d59..16bb26dc 100644
--- a/meson.build
+++ b/meson.build
@@ -156,6 +156,12 @@ config_h.set_quoted('GOA_IMAP_SMTP_NAME', 'imap_smtp')
 enable_imap_smtp = get_option('imap_smtp')
 config_h.set('GOA_IMAP_SMTP_ENABLED', enable_imap_smtp)
 
+# CalDAV
+config_h.set_quoted('GOA_CALDAV_NAME', 'caldav')
+
+enable_caldav = get_option('caldav')
+config_h.set('GOA_CALDAV_ENABLED', enable_caldav)
+
 # Kerberos
 config_h.set_quoted('GOA_KERBEROS_NAME', 'kerberos')
 
@@ -297,6 +303,7 @@ summary({
     'Fedora Account System': enable_fedora,
     'Google': enable_google,
     'IMAP/SMTP': enable_imap_smtp,
+    'CalDAV': enable_caldav,
     'Kerberos': enable_kerberos,
     'Last.fm': enable_lastfm,
     'Media Server': enable_media_server,
diff --git a/meson_options.txt b/meson_options.txt
index 6c8f32be..f0971c74 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -10,6 +10,7 @@ option('google_client_id', type: 'string', value: '44438659992-7kgjeitenc16ssihb
 option('google_client_secret', type: 'string', value: '-gMLuQyDiI0XrQS_vx_mhuYF', description: 'Google OAuth 
2.0 client secret')
 
 option('imap_smtp', type: 'boolean', value: true, description: 'Enable IMAP/SMTP provider')
+option('caldav', type: 'boolean', value: true, description: 'Enable CalDAV provider')
 
 option('kerberos', type: 'boolean', value: true, description: 'Enable kerberos provider')
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b2670722..effa9b38 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -8,6 +8,7 @@ src/goabackend/goafedoraprovider.c
 src/goabackend/goagoogleprovider.c
 src/goabackend/goaimapauthlogin.c
 src/goabackend/goaimapsmtpprovider.c
+src/goabackend/goacaldavprovider.c
 src/goabackend/goakerberosprovider.c
 src/goabackend/goalastfmprovider.c
 src/goabackend/goamediaserverprovider.c
diff --git a/src/goabackend/goabackendenums.h b/src/goabackend/goabackendenums.h
index ed2e8f13..7d9cd4ab 100644
--- a/src/goabackend/goabackendenums.h
+++ b/src/goabackend/goabackendenums.h
@@ -33,6 +33,8 @@ G_BEGIN_DECLS
  *   example, Google.
  * @GOA_PROVIDER_GROUP_CONTACTS: Providers that offer address book services.
  *   For example, CardDAV.
+ * @GOA_PROVIDER_GROUP_CALENDAR: Providers that offer calendar services.
+ *   For example, CalDAV.
  * @GOA_PROVIDER_GROUP_MAIL: Providers that offer email-like messaging
  *   services. For example, IMAP and SMTP.
  * @GOA_PROVIDER_GROUP_TICKETING: Providers with ticketing
@@ -50,6 +52,7 @@ typedef enum
 {
   GOA_PROVIDER_GROUP_BRANDED,
   GOA_PROVIDER_GROUP_CONTACTS,
+  GOA_PROVIDER_GROUP_CALENDAR,
   GOA_PROVIDER_GROUP_MAIL,
   GOA_PROVIDER_GROUP_TICKETING,
   GOA_PROVIDER_GROUP_CHAT,
diff --git a/src/goabackend/goacaldavprovider.c b/src/goabackend/goacaldavprovider.c
new file mode 100644
index 00000000..c9663ab1
--- /dev/null
+++ b/src/goabackend/goacaldavprovider.c
@@ -0,0 +1,897 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright © 2011 – 2017 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include <libsoup/soup.h>
+
+#include "goahttpclient.h"
+#include "goaprovider.h"
+#include "goacaldavprovider.h"
+#include "goaobjectskeletonutils.h"
+#include "goautils.h"
+
+struct _GoaCaldavProvider
+{
+  GoaProvider parent_instance;
+};
+
+G_DEFINE_TYPE_WITH_CODE (GoaCaldavProvider, goa_caldav_provider, GOA_TYPE_PROVIDER,
+                         goa_provider_ensure_extension_points_registered ();
+                         g_io_extension_point_implement (GOA_PROVIDER_EXTENSION_POINT_NAME,
+                                                        g_define_type_id,
+                                                        GOA_CALDAV_NAME,
+                                                        0));
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const gchar *
+get_provider_type (GoaProvider *provider)
+{
+  return GOA_CALDAV_NAME;
+}
+
+static gchar *
+get_provider_name (GoaProvider *provider, GoaObject *object)
+{
+  return g_strdup (_("CalDAV"));
+}
+
+static GoaProviderGroup
+get_provider_group (GoaProvider *provider)
+{
+  return GOA_PROVIDER_GROUP_CALENDAR;
+}
+
+static GoaProviderFeatures
+get_provider_features (GoaProvider *_provider)
+{
+  return GOA_PROVIDER_FEATURE_CALENDAR;
+}
+
+static GIcon *
+get_provider_icon (GoaProvider *provider, GoaObject *object)
+{
+  return g_themed_icon_new_with_default_fallbacks ("x-office-calendar-symbolic");
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean on_handle_get_password (GoaPasswordBased      *interface,
+                                        GDBusMethodInvocation *invocation,
+                                        const gchar           *id,
+                                        gpointer               user_data);
+
+static gboolean
+build_object (GoaProvider         *provider,
+              GoaObjectSkeleton   *object,
+              GKeyFile            *key_file,
+              const gchar         *group,
+              GDBusConnection     *connection,
+              gboolean             just_added,
+              GError             **error)
+{
+  GoaAccount *account = NULL;
+  gchar *uri_string = NULL;
+  GUri *uri = NULL;
+  gchar *uri_string_with_user = NULL;
+  GoaPasswordBased *password_based = NULL;
+  gboolean enabled;
+  gboolean accept_ssl_errors;
+  gboolean ret = FALSE;
+  const gchar *identity;
+
+  /* Chain up */
+  if (!GOA_PROVIDER_CLASS (goa_caldav_provider_parent_class)->build_object (provider,
+                                                                            object,
+                                                                            key_file,
+                                                                            group,
+                                                                            connection,
+                                                                            just_added,
+                                                                            error))
+    goto out;
+
+  password_based = goa_object_get_password_based (GOA_OBJECT (object));
+  if (password_based == NULL)
+    {
+      password_based = goa_password_based_skeleton_new ();
+      /* Ensure D-Bus method invocations run in their own thread */
+      g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (password_based),
+                                           
G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
+      goa_object_skeleton_set_password_based (object, password_based);
+      g_signal_connect (password_based,
+                        "handle-get-password",
+                        G_CALLBACK (on_handle_get_password),
+                        NULL);
+    }
+
+  account = goa_object_get_account (GOA_OBJECT (object));
+  identity = goa_account_get_identity (account);
+  uri_string = g_key_file_get_string (key_file, group, "Uri", NULL);
+  uri = g_uri_parse (uri_string, G_URI_FLAGS_ENCODED, NULL);
+  if (uri != NULL)
+    {
+      GUri *tmp_uri =
+        g_uri_build_with_user (g_uri_get_flags (uri),
+                               g_uri_get_scheme (uri),
+                               identity,
+                               g_uri_get_password (uri),
+                               g_uri_get_auth_params (uri),
+                               g_uri_get_host (uri),
+                               g_uri_get_port (uri),
+                               g_uri_get_path (uri),
+                               g_uri_get_query (uri),
+                               g_uri_get_fragment (uri));
+
+      uri_string_with_user = g_uri_to_string (tmp_uri);
+
+      g_uri_unref (uri);
+      uri = tmp_uri;
+    }
+
+  accept_ssl_errors = g_key_file_get_boolean (key_file, group, "AcceptSslErrors", NULL);
+
+  enabled = g_key_file_get_boolean (key_file, group, "Enabled", NULL);
+  goa_object_skeleton_attach_calendar (object, uri_string_with_user, enabled, accept_ssl_errors);
+  g_free (uri_string_with_user);
+
+  if (just_added)
+    {
+      goa_account_set_calendar_disabled (account, !enabled);
+      g_signal_connect (account,
+                        "notify::calendar-disabled",
+                        G_CALLBACK (goa_util_account_notify_property_cb),
+                        (gpointer) "Enabled");
+    }
+
+  ret = TRUE;
+
+ out:
+  g_clear_object (&account);
+  g_clear_object (&password_based);
+  g_clear_pointer (&uri, g_uri_unref);
+  g_free (uri_string);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+ensure_credentials_sync (GoaProvider         *provider,
+                         GoaObject           *object,
+                         gint                *out_expires_in,
+                         GCancellable        *cancellable,
+                         GError             **error)
+{
+  GoaHttpClient *http_client = NULL;
+  gboolean accept_ssl_errors;
+  gboolean ret = FALSE;
+  gchar *username = NULL;
+  gchar *password = NULL;
+  gchar *uri = NULL;
+
+  if (!goa_utils_get_credentials (provider, object, "password", &username, &password, cancellable, error))
+    {
+      if (error != NULL)
+        {
+          (*error)->domain = GOA_ERROR;
+          (*error)->code = GOA_ERROR_NOT_AUTHORIZED;
+        }
+      goto out;
+    }
+
+  accept_ssl_errors = goa_util_lookup_keyfile_boolean (object, "AcceptSslErrors");
+  uri = goa_util_lookup_keyfile_string (object, "Uri");
+
+  http_client = goa_http_client_new ();
+  ret = goa_http_client_check_sync (http_client,
+                                    uri,
+                                    username,
+                                    password,
+                                    accept_ssl_errors,
+                                    cancellable,
+                                    error);
+  if (!ret)
+    {
+      if (error != NULL)
+        {
+          g_prefix_error (error,
+                          /* Translators: the first %s is the username
+                           * (eg., debarshi ray gmail com or rishi), and the
+                           * (%s, %d) is the error domain and code.
+                           */
+                          _("Invalid password with username “%s” (%s, %d): "),
+                          username,
+                          g_quark_to_string ((*error)->domain),
+                          (*error)->code);
+          (*error)->domain = GOA_ERROR;
+          (*error)->code = GOA_ERROR_NOT_AUTHORIZED;
+        }
+      goto out;
+    }
+
+  if (out_expires_in != NULL)
+    *out_expires_in = 0;
+
+  ret = TRUE;
+
+ out:
+  g_clear_object (&http_client);
+  g_free (username);
+  g_free (password);
+  g_free (uri);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GCancellable *cancellable;
+
+  GtkDialog *dialog;
+  GMainLoop *loop;
+
+  GtkWidget *cluebar;
+  GtkWidget *cluebar_label;
+  GtkWidget *connect_button;
+  GtkWidget *progress_grid;
+
+  GtkWidget *uri;
+  GtkWidget *username;
+  GtkWidget *password;
+
+  gchar *account_object_path;
+
+  GError *error;
+} AddAccountData;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+add_entry (GtkWidget     *grid,
+           gint           row,
+           const gchar   *text,
+           GtkWidget    **out_entry)
+{
+  GtkStyleContext *context;
+  GtkWidget *label;
+  GtkWidget *entry;
+
+  label = gtk_label_new_with_mnemonic (text);
+  context = gtk_widget_get_style_context (label);
+  gtk_style_context_add_class (context, GTK_STYLE_CLASS_DIM_LABEL);
+  gtk_widget_set_halign (label, GTK_ALIGN_END);
+  gtk_widget_set_hexpand (label, TRUE);
+  gtk_grid_attach (GTK_GRID (grid), label, 0, row, 1, 1);
+
+  entry = gtk_entry_new ();
+  gtk_widget_set_hexpand (entry, TRUE);
+  gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+  gtk_grid_attach (GTK_GRID (grid), entry, 1, row, 3, 1);
+
+  gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+  if (out_entry != NULL)
+    *out_entry = entry;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_uri_username_or_password_changed (GtkEditable *editable, gpointer user_data)
+{
+  AddAccountData *data = user_data;
+  gboolean can_add = FALSE;
+  const gchar *address;
+  gchar *uri = NULL;
+
+  address = gtk_entry_get_text (GTK_ENTRY (data->uri));
+  uri = goa_utils_dav_normalize_uri (address, NULL);
+  if (uri == NULL)
+    goto out;
+
+  can_add = gtk_entry_get_text_length (GTK_ENTRY (data->username)) != 0
+            && gtk_entry_get_text_length (GTK_ENTRY (data->password)) != 0;
+
+ out:
+  gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, can_add);
+  g_free (uri);
+}
+
+static void
+create_account_details_ui (GoaProvider    *provider,
+                           GtkDialog      *dialog,
+                           GtkBox         *vbox,
+                           gboolean        new_account,
+                           gboolean        is_template,
+                           AddAccountData *data)
+{
+  GtkWidget *grid0;
+  GtkWidget *grid1;
+  GtkWidget *label;
+  GtkWidget *spinner;
+  gint row;
+  gint width;
+
+  goa_utils_set_dialog_title (provider, dialog, new_account);
+
+  grid0 = gtk_grid_new ();
+  gtk_container_set_border_width (GTK_CONTAINER (grid0), 5);
+  gtk_widget_set_margin_bottom (grid0, 6);
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (grid0), GTK_ORIENTATION_VERTICAL);
+  gtk_grid_set_row_spacing (GTK_GRID (grid0), 12);
+  gtk_container_add (GTK_CONTAINER (vbox), grid0);
+
+  data->cluebar = gtk_info_bar_new ();
+  gtk_info_bar_set_message_type (GTK_INFO_BAR (data->cluebar), GTK_MESSAGE_ERROR);
+  gtk_widget_set_hexpand (data->cluebar, TRUE);
+  gtk_widget_set_no_show_all (data->cluebar, TRUE);
+  gtk_container_add (GTK_CONTAINER (grid0), data->cluebar);
+
+  data->cluebar_label = gtk_label_new ("");
+  gtk_label_set_line_wrap (GTK_LABEL (data->cluebar_label), TRUE);
+  gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (data->cluebar))),
+                     data->cluebar_label);
+
+  grid1 = gtk_grid_new ();
+  gtk_grid_set_column_spacing (GTK_GRID (grid1), 12);
+  gtk_grid_set_row_spacing (GTK_GRID (grid1), 12);
+  gtk_container_add (GTK_CONTAINER (grid0), grid1);
+
+  row = 0;
+  add_entry (grid1, row++, _("_URL"), &data->uri);
+  add_entry (grid1, row++, _("User_name"), &data->username);
+  add_entry (grid1, row++, _("_Password"), &data->password);
+  gtk_entry_set_visibility (GTK_ENTRY (data->password), FALSE);
+
+  if (new_account)
+    gtk_widget_grab_focus (data->uri);
+  else if (is_template)
+    gtk_widget_grab_focus (data->username);
+  else
+    gtk_widget_grab_focus (data->password);
+
+  g_signal_connect (data->uri, "changed", G_CALLBACK (on_uri_username_or_password_changed), data);
+  g_signal_connect (data->username, "changed", G_CALLBACK (on_uri_username_or_password_changed), data);
+  g_signal_connect (data->password, "changed", G_CALLBACK (on_uri_username_or_password_changed), data);
+
+  gtk_dialog_add_button (data->dialog, _("_Cancel"), GTK_RESPONSE_CANCEL);
+  data->connect_button = gtk_dialog_add_button (data->dialog, _("C_onnect"), GTK_RESPONSE_OK);
+  gtk_dialog_set_default_response (data->dialog, GTK_RESPONSE_OK);
+  gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, FALSE);
+
+  data->progress_grid = gtk_grid_new ();
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (data->progress_grid), GTK_ORIENTATION_HORIZONTAL);
+  gtk_grid_set_column_spacing (GTK_GRID (data->progress_grid), 3);
+  gtk_container_add (GTK_CONTAINER (grid0), data->progress_grid);
+
+  spinner = gtk_spinner_new ();
+  gtk_widget_set_opacity (spinner, 0.0);
+  gtk_widget_set_size_request (spinner, 20, 20);
+  gtk_spinner_start (GTK_SPINNER (spinner));
+  gtk_container_add (GTK_CONTAINER (data->progress_grid), spinner);
+
+  label = gtk_label_new (_("Connecting…"));
+  gtk_widget_set_opacity (label, 0.0);
+  gtk_container_add (GTK_CONTAINER (data->progress_grid), label);
+
+  if (new_account)
+    {
+      gtk_window_get_size (GTK_WINDOW (data->dialog), &width, NULL);
+      gtk_window_set_default_size (GTK_WINDOW (data->dialog), width, -1);
+    }
+  else
+    {
+      GtkWindow *parent;
+
+      /* Keep in sync with GoaPanelAddAccountDialog in
+       * gnome-control-center.
+       */
+      parent = gtk_window_get_transient_for (GTK_WINDOW (data->dialog));
+      if (parent != NULL)
+        {
+          gtk_window_get_size (parent, &width, NULL);
+          gtk_window_set_default_size (GTK_WINDOW (data->dialog), (gint) (0.5 * width), -1);
+        }
+    }
+}
+
+static void
+show_progress_ui (GtkContainer *container, gboolean progress)
+{
+  GList *children;
+  GList *l;
+
+  children = gtk_container_get_children (container);
+  for (l = children; l != NULL; l = l->next)
+    {
+      GtkWidget *widget = GTK_WIDGET (l->data);
+      gdouble opacity;
+
+      opacity = progress ? 1.0 : 0.0;
+      gtk_widget_set_opacity (widget, opacity);
+    }
+
+  g_list_free (children);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+add_account_cb (GoaManager *manager, GAsyncResult *res, gpointer user_data)
+{
+  AddAccountData *data = user_data;
+  goa_manager_call_add_account_finish (manager,
+                                       &data->account_object_path,
+                                       res,
+                                       &data->error);
+  g_main_loop_quit (data->loop);
+}
+
+static void
+check_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+  GoaHttpClient *client = GOA_HTTP_CLIENT (source_object);
+  AddAccountData *data = user_data;
+
+  goa_http_client_check_finish (client, res, &data->error);
+  g_main_loop_quit (data->loop);
+  gtk_widget_set_sensitive (data->connect_button, TRUE);
+  show_progress_ui (GTK_CONTAINER (data->progress_grid), FALSE);
+}
+
+static void
+dialog_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data)
+{
+  AddAccountData *data = user_data;
+
+  if (response_id == GTK_RESPONSE_CANCEL || response_id == GTK_RESPONSE_DELETE_EVENT)
+    g_cancellable_cancel (data->cancellable);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GoaObject *
+add_account (GoaProvider    *provider,
+             GoaClient      *client,
+             GtkDialog      *dialog,
+             GtkBox         *vbox,
+             GError        **error)
+{
+  AddAccountData data;
+  GVariantBuilder credentials;
+  GVariantBuilder details;
+  GoaHttpClient *http_client = NULL;
+  GoaObject *ret = NULL;
+  gboolean accept_ssl_errors = FALSE;
+  const gchar *uri_text;
+  const gchar *password;
+  const gchar *username;
+  const gchar *provider_type;
+  gchar *server = NULL;
+  gchar *uri = NULL;
+  gint response;
+
+  memset (&data, 0, sizeof (AddAccountData));
+  data.cancellable = g_cancellable_new ();
+  data.loop = g_main_loop_new (NULL, FALSE);
+  data.dialog = dialog;
+  data.error = NULL;
+
+  create_account_details_ui (provider, dialog, vbox, TRUE, FALSE, &data);
+  gtk_widget_show_all (GTK_WIDGET (vbox));
+  g_signal_connect (dialog, "response", G_CALLBACK (dialog_response_cb), &data);
+
+  http_client = goa_http_client_new ();
+
+ http_again:
+  response = gtk_dialog_run (dialog);
+  if (response != GTK_RESPONSE_OK)
+    {
+      g_set_error (&data.error,
+                   GOA_ERROR,
+                   GOA_ERROR_DIALOG_DISMISSED,
+                   _("Dialog was dismissed"));
+      goto out;
+    }
+
+  uri_text = gtk_entry_get_text (GTK_ENTRY (data.uri));
+  username = gtk_entry_get_text (GTK_ENTRY (data.username));
+  password = gtk_entry_get_text (GTK_ENTRY (data.password));
+
+  uri = goa_utils_dav_normalize_uri (uri_text, &server);
+
+  /* See if there's already an account of this type with the
+   * given identity
+   */
+  provider_type = goa_provider_get_provider_type (provider);
+  if (!goa_utils_check_duplicate (client,
+                                  username,
+                                  username,
+                                  provider_type,
+                                  (GoaPeekInterfaceFunc) goa_object_peek_password_based,
+                                  &data.error))
+    goto out;
+
+  g_clear_object (&data.cancellable);
+  data.cancellable = g_cancellable_new ();
+
+  goa_http_client_check (http_client,
+                         uri,
+                         username,
+                         password,
+                         accept_ssl_errors,
+                         data.cancellable,
+                         check_cb,
+                         &data);
+
+  gtk_widget_set_sensitive (data.connect_button, FALSE);
+  show_progress_ui (GTK_CONTAINER (data.progress_grid), TRUE);
+  g_main_loop_run (data.loop);
+
+  if (g_cancellable_is_cancelled (data.cancellable))
+    {
+      g_prefix_error (&data.error,
+                      _("Dialog was dismissed (%s, %d): "),
+                      g_quark_to_string (data.error->domain),
+                      data.error->code);
+      data.error->domain = GOA_ERROR;
+      data.error->code = GOA_ERROR_DIALOG_DISMISSED;
+      goto out;
+    }
+  else if (data.error != NULL)
+    {
+      gchar *markup;
+
+      if (data.error->code == GOA_ERROR_SSL)
+        {
+          gtk_button_set_label (GTK_BUTTON (data.connect_button), _("_Ignore"));
+          accept_ssl_errors = TRUE;
+        }
+      else
+        {
+          gtk_button_set_label (GTK_BUTTON (data.connect_button), _("_Try Again"));
+          accept_ssl_errors = FALSE;
+        }
+
+      markup = g_strdup_printf ("<b>%s:</b>\n%s",
+                                _("Error connecting to CalDAV server"),
+                                data.error->message);
+      g_clear_error (&data.error);
+
+      gtk_label_set_markup (GTK_LABEL (data.cluebar_label), markup);
+      g_free (markup);
+
+      gtk_widget_set_no_show_all (data.cluebar, FALSE);
+      gtk_widget_show_all (data.cluebar);
+
+      g_clear_pointer (&server, g_free);
+      g_clear_pointer (&uri, g_free);
+      goto http_again;
+    }
+
+  gtk_widget_hide (GTK_WIDGET (dialog));
+
+  g_variant_builder_init (&credentials, G_VARIANT_TYPE_VARDICT);
+  g_variant_builder_add (&credentials, "{sv}", "password", g_variant_new_string (password));
+
+  g_variant_builder_init (&details, G_VARIANT_TYPE ("a{ss}"));
+  g_variant_builder_add (&details, "{ss}", "Enabled", "true");
+  g_variant_builder_add (&details, "{ss}", "Uri", uri);
+  g_variant_builder_add (&details, "{ss}", "Username", username);
+  g_variant_builder_add (&details, "{ss}", "AcceptSslErrors", (accept_ssl_errors) ? "true" : "false");
+
+  /* OK, everything is dandy, add the account */
+  /* we want the GoaClient to update before this method returns (so it
+   * can create a proxy for the new object) so run the mainloop while
+   * waiting for this to complete
+   */
+  goa_manager_call_add_account (goa_client_get_manager (client),
+                                goa_provider_get_provider_type (provider),
+                                username,
+                                username,
+                                g_variant_builder_end (&credentials),
+                                g_variant_builder_end (&details),
+                                NULL, /* GCancellable* */
+                                (GAsyncReadyCallback) add_account_cb,
+                                &data);
+  g_main_loop_run (data.loop);
+  if (data.error != NULL)
+    goto out;
+
+  ret = GOA_OBJECT (g_dbus_object_manager_get_object (goa_client_get_object_manager (client),
+                                                      data.account_object_path));
+
+ out:
+  /* We might have an object even when data.error is set.
+   * eg., if we failed to store the credentials in the keyring.
+   */
+  if (data.error != NULL)
+    g_propagate_error (error, data.error);
+  else
+    g_assert (ret != NULL);
+
+  g_signal_handlers_disconnect_by_func (dialog, dialog_response_cb, &data);
+
+  g_free (server);
+  g_free (uri);
+  g_free (data.account_object_path);
+  g_clear_pointer (&data.loop, g_main_loop_unref);
+  g_clear_object (&data.cancellable);
+  g_clear_object (&http_client);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+refresh_account (GoaProvider    *provider,
+                 GoaClient      *client,
+                 GoaObject      *object,
+                 GtkWindow      *parent,
+                 GError        **error)
+{
+  AddAccountData data;
+  GVariantBuilder credentials;
+  GoaAccount *account;
+  GoaHttpClient *http_client = NULL;
+  GtkWidget *dialog;
+  GtkWidget *vbox;
+  gboolean accept_ssl_errors;
+  gboolean is_template = FALSE;
+  gboolean ret = FALSE;
+  const gchar *password;
+  const gchar *username;
+  gchar *uri = NULL;
+  gint response;
+
+  g_return_val_if_fail (GOA_IS_CALDAV_PROVIDER (provider), FALSE);
+  g_return_val_if_fail (GOA_IS_CLIENT (client), FALSE);
+  g_return_val_if_fail (GOA_IS_OBJECT (object), FALSE);
+  g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  dialog = gtk_dialog_new_with_buttons (NULL,
+                                        parent,
+                                        GTK_DIALOG_MODAL
+                                        | GTK_DIALOG_DESTROY_WITH_PARENT
+                                        | GTK_DIALOG_USE_HEADER_BAR,
+                                        NULL,
+                                        NULL);
+  gtk_container_set_border_width (GTK_CONTAINER (dialog), 12);
+  gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+  gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+  vbox = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+  gtk_box_set_spacing (GTK_BOX (vbox), 12);
+
+  memset (&data, 0, sizeof (AddAccountData));
+  data.cancellable = g_cancellable_new ();
+  data.loop = g_main_loop_new (NULL, FALSE);
+  data.dialog = GTK_DIALOG (dialog);
+  data.error = NULL;
+
+  account = goa_object_peek_account (object);
+  username = goa_account_get_identity (account);
+  if (username == NULL || username[0] == '\0')
+    is_template = TRUE;
+
+  create_account_details_ui (provider, GTK_DIALOG (dialog), GTK_BOX (vbox), FALSE, is_template, &data);
+
+  accept_ssl_errors = goa_util_lookup_keyfile_boolean (object, "AcceptSslErrors");
+  uri = goa_util_lookup_keyfile_string (object, "Uri");
+  gtk_entry_set_text (GTK_ENTRY (data.uri), uri);
+  gtk_editable_set_editable (GTK_EDITABLE (data.uri), FALSE);
+
+  if (!is_template)
+    {
+      gtk_entry_set_text (GTK_ENTRY (data.username), username);
+      gtk_editable_set_editable (GTK_EDITABLE (data.username), FALSE);
+    }
+
+  gtk_widget_show_all (dialog);
+  g_signal_connect (dialog, "response", G_CALLBACK (dialog_response_cb), &data);
+
+  http_client = goa_http_client_new ();
+
+ http_again:
+  response = gtk_dialog_run (GTK_DIALOG (dialog));
+  if (response != GTK_RESPONSE_OK)
+    {
+      g_set_error (&data.error,
+                   GOA_ERROR,
+                   GOA_ERROR_DIALOG_DISMISSED,
+                   _("Dialog was dismissed"));
+      goto out;
+    }
+
+  if (is_template)
+    username = gtk_entry_get_text (GTK_ENTRY (data.username));
+
+  password = gtk_entry_get_text (GTK_ENTRY (data.password));
+
+  g_clear_object (&data.cancellable);
+  data.cancellable = g_cancellable_new ();
+
+  goa_http_client_check (http_client,
+                         uri,
+                         username,
+                         password,
+                         accept_ssl_errors,
+                         data.cancellable,
+                         check_cb,
+                         &data);
+  gtk_widget_set_sensitive (data.connect_button, FALSE);
+  show_progress_ui (GTK_CONTAINER (data.progress_grid), TRUE);
+  g_main_loop_run (data.loop);
+
+  if (g_cancellable_is_cancelled (data.cancellable))
+    {
+      g_prefix_error (&data.error,
+                      _("Dialog was dismissed (%s, %d): "),
+                      g_quark_to_string (data.error->domain),
+                      data.error->code);
+      data.error->domain = GOA_ERROR;
+      data.error->code = GOA_ERROR_DIALOG_DISMISSED;
+      goto out;
+    }
+  else if (data.error != NULL)
+    {
+      gchar *markup;
+
+      markup = g_strdup_printf ("<b>%s</b>\n%s",
+                                _("Error connecting to CalDAV server"),
+                                data.error->message);
+      g_clear_error (&data.error);
+
+      gtk_label_set_markup (GTK_LABEL (data.cluebar_label), markup);
+      g_free (markup);
+
+      gtk_button_set_label (GTK_BUTTON (data.connect_button), _("_Try Again"));
+      gtk_widget_set_no_show_all (data.cluebar, FALSE);
+      gtk_widget_show_all (data.cluebar);
+      goto http_again;
+    }
+
+  /* TODO: run in worker thread */
+  g_variant_builder_init (&credentials, G_VARIANT_TYPE_VARDICT);
+  g_variant_builder_add (&credentials, "{sv}", "password", g_variant_new_string (password));
+
+  if (is_template)
+    {
+      GVariantBuilder details;
+      GoaManager *manager;
+      const gchar *id;
+      const gchar *provider_type;
+
+      manager = goa_client_get_manager (client);
+      id = goa_account_get_id (account);
+      provider_type = goa_provider_get_provider_type (provider);
+
+      g_variant_builder_init (&details, G_VARIANT_TYPE ("a{ss}"));
+      g_variant_builder_add (&details, "{ss}", "Id", id);
+
+      goa_manager_call_add_account (manager,
+                                    provider_type,
+                                    username,
+                                    username,
+                                    g_variant_builder_end (&credentials),
+                                    g_variant_builder_end (&details),
+                                    NULL, /* GCancellable* */
+                                    (GAsyncReadyCallback) add_account_cb,
+                                    &data);
+
+      g_main_loop_run (data.loop);
+      if (data.error != NULL)
+        goto out;
+    }
+  else
+    {
+      if (!goa_utils_store_credentials_for_object_sync (provider,
+                                                        object,
+                                                        g_variant_builder_end (&credentials),
+                                                        NULL, /* GCancellable */
+                                                        &data.error))
+        goto out;
+    }
+
+  goa_account_call_ensure_credentials (account,
+                                       NULL, /* GCancellable */
+                                       NULL, NULL); /* callback, user_data */
+
+  ret = TRUE;
+
+ out:
+  if (data.error != NULL)
+    g_propagate_error (error, data.error);
+
+  gtk_widget_destroy (dialog);
+  g_free (uri);
+  g_free (data.account_object_path);
+  g_clear_pointer (&data.loop, g_main_loop_unref);
+  g_clear_object (&data.cancellable);
+  g_clear_object (&http_client);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+goa_caldav_provider_init (GoaCaldavProvider *provider) {}
+
+static void
+goa_caldav_provider_class_init (GoaCaldavProviderClass *klass)
+{
+  GoaProviderClass *provider_class;
+
+  provider_class = GOA_PROVIDER_CLASS (klass);
+  provider_class->get_provider_type          = get_provider_type;
+  provider_class->get_provider_name          = get_provider_name;
+  provider_class->get_provider_group         = get_provider_group;
+  provider_class->get_provider_features      = get_provider_features;
+  provider_class->get_provider_icon          = get_provider_icon;
+  provider_class->add_account                = add_account;
+  provider_class->refresh_account            = refresh_account;
+  provider_class->build_object               = build_object;
+  provider_class->ensure_credentials_sync    = ensure_credentials_sync;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+on_handle_get_password (GoaPasswordBased      *interface,
+                        GDBusMethodInvocation *invocation,
+                        const gchar           *id, /* unused */
+                        gpointer               user_data)
+{
+  GoaObject *object;
+  GoaAccount *account;
+  GoaProvider *provider;
+  GError *error;
+  const gchar *account_id;
+  const gchar *method_name;
+  const gchar *provider_type;
+  gchar *password = NULL;
+
+  /* TODO: maybe log what app is requesting access */
+
+  object = GOA_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (interface)));
+  account = goa_object_peek_account (object);
+  account_id = goa_account_get_id (account);
+  provider_type = goa_account_get_provider_type (account);
+  method_name = g_dbus_method_invocation_get_method_name (invocation);
+  g_debug ("Handling %s for account (%s, %s)", method_name, provider_type, account_id);
+
+  provider = goa_provider_get_for_provider_type (provider_type);
+
+  error = NULL;
+  if (!goa_utils_get_credentials (provider, object, "password", NULL, &password, NULL, &error))
+    {
+      g_dbus_method_invocation_take_error (invocation, error);
+      goto out;
+    }
+
+  goa_password_based_complete_get_password (interface, invocation, password);
+
+ out:
+  g_free (password);
+  g_object_unref (provider);
+  return TRUE; /* invocation was handled */
+}
diff --git a/src/goabackend/goacaldavprovider.h b/src/goabackend/goacaldavprovider.h
new file mode 100644
index 00000000..994fcaf1
--- /dev/null
+++ b/src/goabackend/goacaldavprovider.h
@@ -0,0 +1,37 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright © 2020 – 2026 Will Noble
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+#error "Only <goabackend/goabackend.h> can be included directly."
+#endif
+
+#ifndef __GOA_CALDAV_PROVIDER_H__
+#define __GOA_CALDAV_PROVIDER_H__
+
+#include <glib-object.h>
+
+#include "goaprovider-priv.h"
+
+G_BEGIN_DECLS
+
+#define GOA_TYPE_CALDAV_PROVIDER (goa_caldav_provider_get_type ())
+G_DECLARE_FINAL_TYPE (GoaCaldavProvider, goa_caldav_provider, GOA, CALDAV_PROVIDER, GoaProvider);
+
+G_END_DECLS
+
+#endif /* __GOA_CALDAV_PROVIDER_H__ */
diff --git a/src/goabackend/goaprovider.c b/src/goabackend/goaprovider.c
index 7996150e..64590dda 100644
--- a/src/goabackend/goaprovider.c
+++ b/src/goabackend/goaprovider.c
@@ -25,6 +25,7 @@
 #include "goaexchangeprovider.h"
 #include "goagoogleprovider.h"
 #include "goaimapsmtpprovider.h"
+#include "goacaldavprovider.h"
 #include "goaowncloudprovider.h"
 #include "goawindowsliveprovider.h"
 #include "goamediaserverprovider.h"
@@ -953,6 +954,9 @@ static struct
 #ifdef GOA_IMAP_SMTP_ENABLED
   { GOA_IMAP_SMTP_NAME, goa_imap_smtp_provider_get_type },
 #endif
+#ifdef GOA_CALDAV_ENABLED
+  { GOA_CALDAV_NAME, goa_caldav_provider_get_type },
+#endif
 #ifdef GOA_KERBEROS_ENABLED
   { GOA_KERBEROS_NAME, goa_kerberos_provider_get_type },
 #endif
diff --git a/src/goabackend/meson.build b/src/goabackend/meson.build
index f5382b34..2ac1f28e 100644
--- a/src/goabackend/meson.build
+++ b/src/goabackend/meson.build
@@ -4,6 +4,7 @@ libgoa_backend_headers_built = []
 libgoa_backend_sources = files(
   'goaoauth2provider.c',
   'goabackendinit.c',
+  'goacaldavprovider.c',
   'goadlnaservermanager.c',
   'goaewsclient.c',
   'goaexchangeprovider.c',


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