[evolution-ews] I#89 - Calendar: Ability to create Online meeting
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-ews] I#89 - Calendar: Ability to create Online meeting
- Date: Fri, 15 Jul 2022 08:46:23 +0000 (UTC)
commit d850f84006bcd7c63e85f847d37f79886cac4488
Author: Milan Crha <mcrha redhat com>
Date: Fri Jul 15 10:45:16 2022 +0200
I#89 - Calendar: Ability to create Online meeting
Closes https://gitlab.gnome.org/GNOME/evolution-ews/-/issues/89
po/POTFILES.in | 2 +
src/EWS/calendar/CMakeLists.txt | 73 ++++++
src/EWS/calendar/e-cal-backend-ews-m365.c | 205 ++++++++++++++++
src/EWS/calendar/e-cal-backend-ews-m365.h | 27 ++
src/EWS/calendar/e-cal-backend-ews.c | 47 ++++
src/EWS/evolution/CMakeLists.txt | 5 +
src/EWS/evolution/e-ews-comp-editor-extension.c | 273 +++++++++++++++++++++
src/EWS/evolution/e-ews-comp-editor-extension.h | 18 ++
src/EWS/evolution/module-ews-configuration.c | 2 +
src/EWS/registry/CMakeLists.txt | 2 +
src/EWS/registry/e-ews-backend.c | 122 +++++++--
src/Microsoft365/common/e-m365-connection.c | 32 ++-
.../common/e-oauth2-service-microsoft365.c | 3 +
13 files changed, 787 insertions(+), 24 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e879e5b5..4f632eac 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -4,6 +4,7 @@
org.gnome.Evolution-ews.metainfo.xml.in
src/EWS/addressbook/e-book-backend-ews.c
src/EWS/calendar/e-cal-backend-ews.c
+src/EWS/calendar/e-cal-backend-ews-m365.c
src/EWS/calendar/e-cal-backend-ews-utils.c
src/EWS/camel/camel-ews-folder.c
src/EWS/camel/camel-ews-provider.c
@@ -19,6 +20,7 @@ src/EWS/common/e-ews-connection-utils.c
src/EWS/common/e-ews-folder.c
src/EWS/common/e-oauth2-service-office365.c
src/EWS/evolution/e-book-config-ews.c
+src/EWS/evolution/e-ews-comp-editor-extension.c
src/EWS/evolution/e-ews-config-lookup.c
src/EWS/evolution/e-ews-config-utils.c
src/EWS/evolution/e-ews-edit-folder-permissions.c
diff --git a/src/EWS/calendar/CMakeLists.txt b/src/EWS/calendar/CMakeLists.txt
index 3a6ce029..edb6efed 100644
--- a/src/EWS/calendar/CMakeLists.txt
+++ b/src/EWS/calendar/CMakeLists.txt
@@ -2,14 +2,83 @@ install(FILES windowsZones.xml
DESTINATION ${ewsdatadir}
)
+# helper static library
+
set(DEPENDENCIES
evolution-ews
)
+add_library(ews-m365 STATIC
+ e-cal-backend-ews-m365.h
+ e-cal-backend-ews-m365.c
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365/common/camel-m365-settings.c
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365/common/camel-m365-settings.h
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365/common/e-m365-connection.c
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365/common/e-m365-connection.h
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365/common/e-m365-json-utils.c
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365/common/e-m365-json-utils.h
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365/common/e-m365-tz-utils.c
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365/common/e-m365-tz-utils.h
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365/calendar/e-cal-backend-m365-utils.c
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365/calendar/e-cal-backend-m365-utils.h
+)
+
+add_dependencies(ews-m365
+ ${DEPENDENCIES}
+)
+
+target_compile_definitions(ews-m365 PRIVATE
+ -DG_LOG_DOMAIN=\"ecalbackendews-m365\"
+ -DM365_DATADIR=\"${ewsdatadir}\"
+)
+
+target_compile_options(ews-m365 PUBLIC
+ ${CAMEL_CFLAGS}
+ ${EVOLUTION_CALENDAR_CFLAGS}
+ ${LIBEBACKEND_CFLAGS}
+ ${LIBECAL_CFLAGS}
+ ${LIBEDATACAL_CFLAGS}
+ ${SOUP_CFLAGS}
+)
+
+target_include_directories(ews-m365 PUBLIC
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}
+ ${CMAKE_BINARY_DIR}/src/EWS
+ ${CMAKE_SOURCE_DIR}/src/EWS
+ ${CMAKE_BINARY_DIR}/src/Microsoft365
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CAMEL_INCLUDE_DIRS}
+ ${EVOLUTION_CALENDAR_INCLUDE_DIRS}
+ ${LIBEBACKEND_INCLUDE_DIRS}
+ ${LIBECAL_INCLUDE_DIRS}
+ ${LIBEDATACAL_INCLUDE_DIRS}
+ ${SOUP_INCLUDE_DIRS}
+)
+
+target_link_libraries(ews-m365
+ ${DEPENDENCIES}
+ ${CAMEL_LDFLAGS}
+ ${EVOLUTION_CALENDAR_LDFLAGS}
+ ${LIBEBACKEND_LDFLAGS}
+ ${LIBECAL_LDFLAGS}
+ ${LIBEDATACAL_LDFLAGS}
+ ${SOUP_LDFLAGS}
+)
+
+# the main module
+
+set(DEPENDENCIES
+ evolution-ews
+ ews-m365
+)
+
set(SOURCES
e-cal-backend-ews.c
e-cal-backend-ews.h
e-cal-backend-ews-factory.c
+ e-cal-backend-ews-m365.h
e-cal-backend-ews-utils.c
e-cal-backend-ews-utils.h
)
@@ -22,6 +91,10 @@ add_dependencies(ecalbackendews
${DEPENDENCIES}
)
+set_target_properties(ecalbackendews PROPERTIES
+ C_VISIBILITY_PRESET hidden
+)
+
target_compile_definitions(ecalbackendews PRIVATE
-DG_LOG_DOMAIN=\"ecalbackendews\"
-DEXCHANGE_EWS_DATADIR=\"${ewsdatadir}\"
diff --git a/src/EWS/calendar/e-cal-backend-ews-m365.c b/src/EWS/calendar/e-cal-backend-ews-m365.c
new file mode 100644
index 00000000..f1771ca8
--- /dev/null
+++ b/src/EWS/calendar/e-cal-backend-ews-m365.c
@@ -0,0 +1,205 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "common/camel-m365-settings.h"
+#include "common/e-m365-connection.h"
+#include "common/e-m365-json-utils.h"
+#include "common/e-m365-tz-utils.h"
+#include "calendar/e-cal-backend-m365-utils.h"
+
+#include "e-cal-backend-ews-m365.h"
+
+static gboolean
+ecb_ews_m365_authenticate (EM365Connection *m365_cnc,
+ GCancellable *cancellable,
+ GError **error)
+{
+ ESourceAuthenticationResult auth_result;
+ ESourceCredentialsReason cred_reason;
+ gchar *certificate_pem = NULL;
+ GTlsCertificateFlags certificate_errors = 0;
+ ESource *source;
+ GError *local_error = NULL;
+
+ auth_result = e_m365_connection_authenticate_sync (m365_cnc, NULL, E_M365_FOLDER_KIND_CALENDAR,
+ NULL, NULL, &certificate_pem, &certificate_errors, cancellable, &local_error);
+
+ if (auth_result == E_SOURCE_AUTHENTICATION_ACCEPTED)
+ return TRUE;
+
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED,
+ _("Cannot connect to the server, repeat the action once you login to the server."));
+
+ source = e_m365_connection_get_source (m365_cnc);
+
+ switch (auth_result) {
+ case E_SOURCE_AUTHENTICATION_ERROR:
+ cred_reason = E_SOURCE_CREDENTIALS_REASON_ERROR;
+ break;
+ case E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED:
+ cred_reason = E_SOURCE_CREDENTIALS_REASON_SSL_FAILED;
+ break;
+ case E_SOURCE_AUTHENTICATION_REJECTED:
+ cred_reason = E_SOURCE_CREDENTIALS_REASON_REJECTED;
+ break;
+ default:
+ cred_reason = E_SOURCE_CREDENTIALS_REASON_REQUIRED;
+ break;
+ }
+
+ e_source_invoke_credentials_required (source, cred_reason, certificate_pem, certificate_errors,
local_error, NULL, NULL, NULL);
+
+ g_clear_error (&local_error);
+
+ return FALSE;
+}
+
+static ESource *
+ecb_ews_m365_find_helper_source (ESourceRegistry *registry,
+ ESource *calendar_source)
+{
+ ESource *result = NULL;
+ ESource *collection_source;
+ GList *sources, *link;
+ const gchar *parent_uid;
+
+ collection_source = e_source_registry_find_extension (registry, calendar_source,
E_SOURCE_EXTENSION_COLLECTION);
+ if (!collection_source)
+ return NULL;
+
+ parent_uid = e_source_get_uid (collection_source);
+ sources = e_source_registry_list_sources (registry, E_SOURCE_EXTENSION_AUTHENTICATION);
+
+ for (link = sources; link; link = g_list_next (link)) {
+ ESource *adept = link->data;
+
+ if (g_strcmp0 (parent_uid, e_source_get_parent (adept)) == 0) {
+ ESourceAuthentication *auth_extension;
+
+ auth_extension = e_source_get_extension (adept, E_SOURCE_EXTENSION_AUTHENTICATION);
+
+ if (g_strcmp0 ("Microsoft365", e_source_authentication_get_method (auth_extension))
== 0) {
+ result = g_object_ref (adept);
+ break;
+ }
+ }
+ }
+
+ g_list_free_full (sources, g_object_unref);
+
+ return result;
+}
+
+gboolean
+ecb_ews_save_as_online_meeting_sync (ESourceRegistry *registry,
+ EEwsConnection *ews_cnc,
+ ETimezoneCache *timezone_cache,
+ ECalComponent *comp,
+ gchar **out_new_uid,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CamelEwsSettings *ews_settings;
+ CamelM365Settings *m365_settings;
+ EM365Connection *m365_cnc;
+ ESource *source;
+ gboolean success = FALSE;
+
+ ews_settings = e_ews_connection_ref_settings (ews_cnc);
+ m365_settings = g_object_new (CAMEL_TYPE_M365_SETTINGS, NULL);
+
+ camel_m365_settings_set_concurrent_connections (m365_settings, 1);
+
+ e_binding_bind_property (
+ ews_settings, "user",
+ m365_settings, "user",
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ ews_settings, "timeout",
+ m365_settings, "timeout",
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ ews_settings, "use-impersonation",
+ m365_settings, "use-impersonation",
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ ews_settings, "impersonate-user",
+ m365_settings, "impersonate-user",
+ G_BINDING_SYNC_CREATE);
+
+ /* Only the above properties are used in the m365_cnc */
+
+ source = ecb_ews_m365_find_helper_source (registry, e_ews_connection_get_source (ews_cnc));
+
+ if (!source) {
+ /* Translators: The '%s' is replaced with a UID of the collection source */
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("Cannot find Microsoft365 helper
source for calendar ā%sā"),
+ e_source_get_uid (e_ews_connection_get_source (ews_cnc)));
+
+ g_clear_object (&ews_settings);
+ g_clear_object (&m365_settings);
+
+ return FALSE;
+ }
+
+ m365_cnc = e_m365_connection_new_full (source, m365_settings, FALSE);
+ g_clear_object (&source);
+
+ e_binding_bind_property (
+ ews_cnc, "proxy-resolver",
+ m365_cnc, "proxy-resolver",
+ G_BINDING_SYNC_CREATE);
+
+ success = ecb_ews_m365_authenticate (m365_cnc, cancellable, error);
+
+ if (success) {
+ ICalComponent *new_comp;
+ JsonBuilder *builder;
+
+ new_comp = e_cal_component_get_icalcomponent (comp);
+
+ e_m365_tz_utils_ref_windows_zones ();
+
+ builder = e_cal_backend_m365_utils_ical_to_json (m365_cnc, NULL, NULL, timezone_cache,
I_CAL_VEVENT_COMPONENT,
+ new_comp, NULL, cancellable, error);
+
+ if (builder) {
+ JsonObject *created_item = NULL;
+
+ success = e_m365_connection_create_event_sync (m365_cnc, NULL, NULL, NULL, builder,
&created_item, cancellable, error);
+
+ if (success && created_item) {
+ const gchar *m365_id = e_m365_event_get_id (created_item);
+
+ success = e_cal_backend_m365_utils_ical_to_json_2nd_go (m365_cnc, NULL, NULL,
timezone_cache,
+ I_CAL_VEVENT_COMPONENT, new_comp, NULL, m365_id, cancellable, error);
+ }
+
+ if (success && created_item)
+ *out_new_uid = g_strdup (e_m365_event_get_id (created_item));
+
+ g_clear_pointer (&created_item, json_object_unref);
+ g_clear_object (&builder);
+ }
+
+ e_m365_tz_utils_unref_windows_zones ();
+ }
+
+ g_clear_object (&ews_settings);
+ g_clear_object (&m365_settings);
+ g_clear_object (&m365_cnc);
+
+ return success;
+}
diff --git a/src/EWS/calendar/e-cal-backend-ews-m365.h b/src/EWS/calendar/e-cal-backend-ews-m365.h
new file mode 100644
index 00000000..2345fa22
--- /dev/null
+++ b/src/EWS/calendar/e-cal-backend-ews-m365.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_CAL_BACKEND_EWS_M365_H
+#define E_CAL_BACKEND_EWS_M365_H
+
+#include <libecal/libecal.h>
+#include <libedataserver/libedataserver.h>
+
+#include "common/e-ews-connection.h"
+
+G_BEGIN_DECLS
+
+gboolean ecb_ews_save_as_online_meeting_sync (ESourceRegistry *registry,
+ EEwsConnection *ews_cnc,
+ ETimezoneCache *timezone_cache,
+ ECalComponent *comp,
+ gchar **out_new_uid,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* E_CAL_BACKEND_EWS_M365_H */
diff --git a/src/EWS/calendar/e-cal-backend-ews.c b/src/EWS/calendar/e-cal-backend-ews.c
index de719c79..3ab00a1e 100644
--- a/src/EWS/calendar/e-cal-backend-ews.c
+++ b/src/EWS/calendar/e-cal-backend-ews.c
@@ -31,6 +31,7 @@
#include "e-cal-backend-ews.h"
#include "e-cal-backend-ews-utils.h"
+#include "e-cal-backend-ews-m365.h"
#ifndef O_BINARY
#define O_BINARY 0
@@ -66,6 +67,8 @@ struct _ECalBackendEwsPrivate {
gboolean is_freebusy_calendar;
gchar *attachments_dir;
+
+ EThreeState is_user_calendar;
};
#define ECB_EWS_SYNC_TAG_STAMP_KEY "ews-sync-tag-stamp"
@@ -3089,6 +3092,7 @@ ecb_ews_save_component_sync (ECalMetaBackend *meta_backend,
EwsFolderId *fid;
GSList *link;
const gchar *uid;
+ gboolean is_online_meeting;
gboolean success = TRUE;
g_return_val_if_fail (E_IS_CAL_BACKEND_EWS (meta_backend), FALSE);
@@ -3115,6 +3119,7 @@ ecb_ews_save_component_sync (ECalMetaBackend *meta_backend,
g_rec_mutex_lock (&cbews->priv->cnc_lock);
+ is_online_meeting = e_cal_util_component_has_x_property (e_cal_component_get_icalcomponent (master),
"X-M365-ONLINE-MEETING");
uid = e_cal_component_get_uid (master);
fid = e_ews_folder_id_new (cbews->priv->folder_id, NULL, FALSE);
@@ -3190,6 +3195,47 @@ ecb_ews_save_component_sync (ECalMetaBackend *meta_backend,
!ecb_ews_organizer_is_user (cbews, master)) {
success = FALSE;
g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_PERMISSION_DENIED, _("Cannot create
meetings organized by other users in an Exchange Web Services calendar.")));
+ } else if (is_online_meeting && !instances->next) {
+ /* Check whether the folder is the user calendar */
+ if (cbews->priv->is_user_calendar == E_THREE_STATE_INCONSISTENT) {
+ GSList folder_ids, *folders = NULL;
+ EwsFolderId dfid;
+
+ memset (&dfid, 0, sizeof (dfid));
+ memset (&folder_ids, 0, sizeof (folder_ids));
+
+ dfid.id = (gchar *) "calendar";
+ dfid.is_distinguished_id = TRUE;
+
+ folder_ids.data = &dfid;
+
+ success = e_ews_connection_get_folder_sync (cbews->priv->cnc, G_PRIORITY_DEFAULT,
"IdOnly", NULL, &folder_ids, &folders, cancellable, error);
+ if (success) {
+ gboolean is_user_calendar = FALSE;
+
+ if (folders) {
+ EEwsFolder *folder = folders->data;
+ const EwsFolderId *fid = folder ? e_ews_folder_get_id (folder) : NULL;
+
+ is_user_calendar = fid && g_strcmp0 (cbews->priv->folder_id, fid->id)
== 0;
+ }
+
+ g_slist_free_full (folders, g_object_unref);
+
+ cbews->priv->is_user_calendar = is_user_calendar ? E_THREE_STATE_ON :
E_THREE_STATE_OFF;
+ }
+ }
+
+ if (success) {
+ /* Verify the folder is the user calendar, the online meeting cannot be created
elsewhere */
+ success = cbews->priv->is_user_calendar == E_THREE_STATE_ON;
+
+ if (!success)
+ g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_PERMISSION_DENIED,
_("Online meeting can be created only in the main user Calendar.")));
+ }
+
+ success = success && ecb_ews_save_as_online_meeting_sync (e_cal_backend_get_registry
(E_CAL_BACKEND (cbews)),
+ cbews->priv->cnc, E_TIMEZONE_CACHE (cbews), master, out_new_uid, cancellable, error);
} else {
GHashTable *removed_indexes;
EwsCalendarConvertData convert_data = { 0 };
@@ -4653,6 +4699,7 @@ static void
e_cal_backend_ews_init (ECalBackendEws *cbews)
{
cbews->priv = e_cal_backend_ews_get_instance_private (cbews);
+ cbews->priv->is_user_calendar = E_THREE_STATE_INCONSISTENT;
g_rec_mutex_init (&cbews->priv->cnc_lock);
diff --git a/src/EWS/evolution/CMakeLists.txt b/src/EWS/evolution/CMakeLists.txt
index 70f65bde..8c11d2cf 100644
--- a/src/EWS/evolution/CMakeLists.txt
+++ b/src/EWS/evolution/CMakeLists.txt
@@ -35,6 +35,8 @@ set(sources
e-mail-parser-ews-sharing-metadata.h
e-mail-part-ews-sharing-metadata.c
e-mail-part-ews-sharing-metadata.h
+ e-ews-comp-editor-extension.c
+ e-ews-comp-editor-extension.h
e-ews-config-lookup.c
e-ews-config-lookup.h
e-ews-config-ui-extension.c
@@ -56,18 +58,21 @@ set(extra_defines)
set(extra_cflags
${EVOLUTION_MAIL_CFLAGS}
${EVOLUTION_SHELL_CFLAGS}
+ ${EVOLUTION_CALENDAR_CFLAGS}
${LIBECAL_CFLAGS}
${LIBEBOOK_CFLAGS}
)
set(extra_incdirs
${EVOLUTION_MAIL_INCLUDE_DIRS}
${EVOLUTION_SHELL_INCLUDE_DIRS}
+ ${EVOLUTION_CALENDAR_INCLUDE_DIRS}
${LIBECAL_INCLUDE_DIRS}
${LIBEBOOK_INCLUDE_DIRS}
)
set(extra_ldflags
${EVOLUTION_MAIL_LDFLAGS}
${EVOLUTION_SHELL_LDFLAGS}
+ ${EVOLUTION_CALENDAR_LDFLAGS}
${LIBECAL_LDFLAGS}
${LIBEBOOK_LDFLAGS}
)
diff --git a/src/EWS/evolution/e-ews-comp-editor-extension.c b/src/EWS/evolution/e-ews-comp-editor-extension.c
new file mode 100644
index 00000000..ff7a900c
--- /dev/null
+++ b/src/EWS/evolution/e-ews-comp-editor-extension.c
@@ -0,0 +1,273 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-ews-config.h"
+
+#include <glib/gi18n-lib.h>
+#include <glib-object.h>
+
+#include <calendar/gui/e-comp-editor.h>
+#include <calendar/gui/e-comp-editor-event.h>
+#include <calendar/gui/e-comp-editor-page-general.h>
+
+#include "e-ews-comp-editor-extension.h"
+
+#define E_TYPE_EWS_COMP_EDITOR_EXTENSION (e_ews_comp_editor_extension_get_type ())
+
+GType e_ews_comp_editor_extension_get_type (void);
+
+typedef struct _EEwsCompEditorExtension {
+ EExtension parent;
+} EEwsCompEditorExtension;
+
+typedef struct _EEwsCompEditorExtensionClass {
+ EExtensionClass parent_class;
+} EEwsCompEditorExtensionClass;
+
+G_DEFINE_DYNAMIC_TYPE (EEwsCompEditorExtension, e_ews_comp_editor_extension, E_TYPE_EXTENSION)
+
+static void
+e_ews_comp_editor_extension_update_actions (ECompEditor *comp_editor)
+{
+ GtkAction *action;
+ gboolean can_use = FALSE;
+
+ action = e_comp_editor_get_action (comp_editor, "ews-online-meeting");
+
+ g_return_if_fail (action != NULL);
+
+ /* Requirements to see the action:
+ * - it's a new component (the option cannot be changed, it can be only set on create)
+ * - it is a meeting
+ * - the target calendar is EWS
+ * - the authentication method is OAuth2
+ *
+ * It would check the host this connects to, but there are different
+ * end points, not only outlook.office365.com, thus check whether the auth
+ * method is OAuth2. It is also not accurate, because on-premise Exchange
+ * servers can have configured OAuth2, without using the Microsoft servers,
+ * but it might be good enough test.
+ */
+ can_use = (e_comp_editor_get_flags (comp_editor) & E_COMP_EDITOR_FLAG_IS_NEW) != 0;
+
+ if (can_use) {
+ ECompEditorPage *page_general;
+
+ page_general = e_comp_editor_get_page (comp_editor, E_TYPE_COMP_EDITOR_PAGE_GENERAL);
+
+ can_use = page_general && e_comp_editor_page_general_get_show_attendees
(E_COMP_EDITOR_PAGE_GENERAL (page_general));
+ }
+
+ if (can_use) {
+ ECalClient *target_client;
+
+ target_client = e_comp_editor_get_target_client (comp_editor);
+
+ can_use = target_client != NULL;
+
+ if (can_use) {
+ ESource *source;
+
+ source = e_client_get_source (E_CLIENT (target_client));
+
+ can_use = source && e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR) &&
+ g_strcmp0 (e_source_backend_get_backend_name (e_source_get_extension (source,
E_SOURCE_EXTENSION_CALENDAR)), "ews") == 0;
+
+ if (can_use) {
+ ESourceRegistry *registry;
+ ESource *collection_source;
+
+ registry = e_shell_get_registry (e_comp_editor_get_shell (comp_editor));
+ collection_source = e_source_registry_find_extension (registry, source,
E_SOURCE_EXTENSION_COLLECTION);
+ can_use = collection_source && e_source_has_extension (source,
E_SOURCE_EXTENSION_AUTHENTICATION);
+
+ if (can_use) {
+ EOAuth2Services *oauth2_services;
+ const gchar *method;
+
+ oauth2_services = e_source_registry_get_oauth2_services (registry);
+ method = e_source_authentication_get_method (e_source_get_extension
(source, E_SOURCE_EXTENSION_AUTHENTICATION));
+
+ can_use = method && e_oauth2_services_is_oauth2_alias
(oauth2_services, method);
+ }
+
+ g_clear_object (&collection_source);
+ }
+ }
+ }
+
+ gtk_action_set_visible (action, can_use);
+}
+
+static void
+e_ews_comp_editor_extension_fill_widgets_cb (ECompEditor *comp_editor,
+ ICalComponent *component)
+{
+ GtkAction *action;
+
+ action = e_comp_editor_get_action (comp_editor, "ews-online-meeting");
+
+ if (action)
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), FALSE);
+
+ e_ews_comp_editor_extension_update_actions (comp_editor);
+}
+
+static gboolean
+e_ews_comp_editor_extension_fill_component_cb (ECompEditor *comp_editor,
+ ICalComponent *component)
+{
+ GtkAction *action;
+
+ action = e_comp_editor_get_action (comp_editor, "ews-online-meeting");
+
+ if (action && gtk_action_get_visible (action) &&
+ gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
+ e_cal_util_component_set_x_property (component, "X-M365-ONLINE-MEETING", "1");
+ } else {
+ e_cal_util_component_remove_x_property (component, "X-M365-ONLINE-MEETING");
+ }
+
+ return TRUE;
+}
+
+static void
+e_ews_comp_editor_extension_handle_map_unmap (ECompEditor *comp_editor,
+ gboolean is_map)
+{
+ ECompEditorPage *page_general;
+
+ /* Cannot do this in the 'constructed' method, because the extensions are loaded
+ before the Event editor fills the comp editor with the pages. */
+ page_general = e_comp_editor_get_page (comp_editor, E_TYPE_COMP_EDITOR_PAGE_GENERAL);
+
+ if (page_general) {
+ if (is_map) {
+ g_signal_connect_object (page_general, "notify::show-attendees",
+ G_CALLBACK (e_ews_comp_editor_extension_update_actions), comp_editor,
+ G_CONNECT_SWAPPED);
+ } else {
+ g_signal_handlers_disconnect_by_func (page_general,
+ G_CALLBACK (e_ews_comp_editor_extension_update_actions), comp_editor);
+ }
+ }
+}
+
+static void
+e_ews_comp_editor_extension_map_cb (ECompEditor *comp_editor,
+ gpointer user_data)
+{
+ e_ews_comp_editor_extension_handle_map_unmap (comp_editor, TRUE);
+}
+
+static void
+e_ews_comp_editor_extension_unmap_cb (ECompEditor *comp_editor,
+ gpointer user_data)
+{
+ e_ews_comp_editor_extension_handle_map_unmap (comp_editor, FALSE);
+}
+
+static void
+e_ews_comp_editor_extension_constructed (GObject *object)
+{
+ const gchar *ui =
+ "<ui>"
+ " <menubar action='main-menu'>"
+ " <menu action='options-menu'>"
+ " <placeholder name='toggles'>"
+ " <menuitem action='ews-online-meeting'/>"
+ " </placeholder>"
+ " </menu>"
+ " </menubar>"
+ " <toolbar name='main-toolbar'>"
+ " <placeholder name='content'>\n"
+ " <toolitem action='ews-online-meeting'/>\n"
+ " </placeholder>"
+ " </toolbar>"
+ "</ui>";
+
+ GtkToggleActionEntry entries[] = {
+ { "ews-online-meeting",
+ "stock_people",
+ N_("Online Meeting"),
+ NULL,
+ N_("Create the meeting as an online meeting in the main user calendar"),
+ NULL,
+ FALSE }
+ };
+ EExtensible *extensible;
+
+ /* Chain up to parent's method */
+ G_OBJECT_CLASS (e_ews_comp_editor_extension_parent_class)->constructed (object);
+
+ extensible = e_extension_get_extensible (E_EXTENSION (object));
+
+ if (E_IS_COMP_EDITOR_EVENT (extensible)) {
+ ECompEditor *comp_editor = E_COMP_EDITOR (extensible);
+ GtkUIManager *ui_manager;
+ GtkActionGroup *action_group;
+ GError *error = NULL;
+
+ ui_manager = e_comp_editor_get_ui_manager (comp_editor);
+ action_group = e_comp_editor_get_action_group (comp_editor, "individual");
+
+ gtk_action_group_add_toggle_actions (action_group, entries, G_N_ELEMENTS (entries),
comp_editor);
+
+ gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
+
+ if (error) {
+ g_critical ("%s: %s", G_STRFUNC, error->message);
+ g_error_free (error);
+ }
+
+ g_signal_connect (comp_editor, "map",
+ G_CALLBACK (e_ews_comp_editor_extension_map_cb), NULL);
+
+ g_signal_connect (comp_editor, "unmap",
+ G_CALLBACK (e_ews_comp_editor_extension_unmap_cb), NULL);
+
+ g_signal_connect (comp_editor, "notify::target-client",
+ G_CALLBACK (e_ews_comp_editor_extension_update_actions), NULL);
+
+ g_signal_connect (comp_editor, "notify::flags",
+ G_CALLBACK (e_ews_comp_editor_extension_update_actions), NULL);
+
+ g_signal_connect (comp_editor, "fill-widgets",
+ G_CALLBACK (e_ews_comp_editor_extension_fill_widgets_cb), NULL);
+
+ g_signal_connect (comp_editor, "fill-component",
+ G_CALLBACK (e_ews_comp_editor_extension_fill_component_cb), NULL);
+ }
+}
+
+static void
+e_ews_comp_editor_extension_class_init (EEwsCompEditorExtensionClass *klass)
+{
+ GObjectClass *object_class;
+ EExtensionClass *extension_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->constructed = e_ews_comp_editor_extension_constructed;
+
+ extension_class = E_EXTENSION_CLASS (klass);
+ extension_class->extensible_type = E_TYPE_COMP_EDITOR;
+}
+
+static void
+e_ews_comp_editor_extension_class_finalize (EEwsCompEditorExtensionClass *klass)
+{
+}
+
+static void
+e_ews_comp_editor_extension_init (EEwsCompEditorExtension *extension)
+{
+}
+
+void
+e_ews_comp_editor_extension_type_register (GTypeModule *type_module)
+{
+ e_ews_comp_editor_extension_register_type (type_module);
+}
diff --git a/src/EWS/evolution/e-ews-comp-editor-extension.h b/src/EWS/evolution/e-ews-comp-editor-extension.h
new file mode 100644
index 00000000..89360463
--- /dev/null
+++ b/src/EWS/evolution/e-ews-comp-editor-extension.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat, Inc. (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_EWS_COMP_EDITOR_EXTENSION_H
+#define E_EWS_COMP_EDITOR_EXTENSION_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+void e_ews_comp_editor_extension_type_register (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* E_EWS_COMP_EDITOR_EXTENSION_H */
diff --git a/src/EWS/evolution/module-ews-configuration.c b/src/EWS/evolution/module-ews-configuration.c
index 747fc8db..7ce0e8fe 100644
--- a/src/EWS/evolution/module-ews-configuration.c
+++ b/src/EWS/evolution/module-ews-configuration.c
@@ -27,6 +27,7 @@
#include "e-ews-config-lookup.h"
#include "e-ews-photo-source.h"
+#include "e-ews-comp-editor-extension.h"
#include "e-ews-config-ui-extension.h"
#include "common/camel-sasl-xoauth2-office365.h"
#include "common/e-oauth2-service-office365.h"
@@ -57,6 +58,7 @@ e_module_load (GTypeModule *type_module)
e_mail_parser_ews_multipart_mixed_type_register (type_module);
e_mail_parser_ews_sharing_metadata_type_register (type_module);
e_mail_part_ews_sharing_metadata_type_register (type_module);
+ e_ews_comp_editor_extension_type_register (type_module);
e_ews_config_lookup_type_register (type_module);
e_ews_config_ui_extension_type_register (type_module);
e_ews_ooo_notificator_type_register (type_module);
diff --git a/src/EWS/registry/CMakeLists.txt b/src/EWS/registry/CMakeLists.txt
index b8aaa16f..2c131cfb 100644
--- a/src/EWS/registry/CMakeLists.txt
+++ b/src/EWS/registry/CMakeLists.txt
@@ -5,6 +5,8 @@ set(sources
e-ews-backend.h
e-ews-backend-factory.c
e-ews-backend-factory.h
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365/common/camel-m365-settings.c
+ ${CMAKE_SOURCE_DIR}/src/Microsoft365/common/camel-m365-settings.h
)
set(extra_defines)
set(extra_cflags)
diff --git a/src/EWS/registry/e-ews-backend.c b/src/EWS/registry/e-ews-backend.c
index 8b03b37e..80289ae2 100644
--- a/src/EWS/registry/e-ews-backend.c
+++ b/src/EWS/registry/e-ews-backend.c
@@ -12,6 +12,9 @@
#include "common/e-ews-connection-utils.h"
#include "common/e-source-ews-folder.h"
+#include "../Microsoft365/common/camel-m365-settings.h"
+
+#define EWS_HELPER_M365_RESOURCE_ID "helper-m365-calendar"
typedef struct _SyncFoldersClosure SyncFoldersClosure;
@@ -591,6 +594,77 @@ ews_backend_add_gal_source (EEwsBackend *backend)
g_object_unref (source);
}
+/* This source is used by the Calendar backend, when creating online meeting */
+static void
+ews_backend_maybe_add_m365_source (EEwsBackend *ews_backend)
+{
+ CamelEwsSettings *ews_settings;
+ ECollectionBackend *collection_backend;
+ ESource *collection_source;
+ ESource *m365_source;
+ ESourceAuthentication *auth_extension;
+ ESourceAuthentication *collection_auth_extension;
+ ESourceCollection *collection_extension = NULL;
+ ESourceExtension *source_extension;
+ ESourceRegistryServer *server;
+ gboolean can_enable;
+ gchar *display_name;
+
+ ews_settings = ews_backend_get_settings (ews_backend);
+
+ if (camel_ews_settings_get_auth_mechanism (ews_settings) != EWS_AUTH_TYPE_OAUTH2)
+ return;
+
+ collection_source = e_backend_get_source (E_BACKEND (ews_backend));
+
+ if (!collection_source)
+ return;
+
+ /* Make sure the ESourceCamel knows about it, even when no Microsoft365 mail account is created */
+ e_source_camel_generate_subtype ("microsoft365", CAMEL_TYPE_M365_SETTINGS);
+
+ if (e_source_has_extension (collection_source, E_SOURCE_EXTENSION_COLLECTION))
+ collection_extension = e_source_get_extension (collection_source,
E_SOURCE_EXTENSION_COLLECTION);
+
+ can_enable = !collection_extension || (e_source_get_enabled (collection_source) &&
+ e_source_collection_get_contacts_enabled (collection_extension));
+
+ collection_backend = E_COLLECTION_BACKEND (ews_backend);
+ m365_source = e_collection_backend_new_child (collection_backend, EWS_HELPER_M365_RESOURCE_ID);
+ e_source_set_enabled (m365_source, can_enable);
+
+ display_name = g_strconcat (e_source_get_display_name (collection_source), " (Microsoft365)", NULL);
+
+ source_extension = e_source_get_extension (m365_source, e_source_camel_get_extension_name
("microsoft365"));
+ if (source_extension) {
+ CamelSettings *settings = e_source_camel_get_settings (E_SOURCE_CAMEL (source_extension));
+
+ if (settings) {
+ g_object_set (settings,
+ "host", "graph.microsoft.com",
+ "auth-mechanism", "Microsoft365",
+ NULL);
+ }
+ }
+
+ collection_auth_extension = e_source_get_extension (collection_source,
E_SOURCE_EXTENSION_AUTHENTICATION);
+
+ e_source_set_display_name (m365_source, display_name);
+
+ auth_extension = e_source_get_extension (m365_source, E_SOURCE_EXTENSION_AUTHENTICATION);
+ e_source_authentication_set_host (auth_extension, "graph.microsoft.com");
+ e_source_authentication_set_method (auth_extension, "Microsoft365");
+ e_source_authentication_set_user (auth_extension,
+ e_source_authentication_get_user (collection_auth_extension));
+
+ server = e_collection_backend_ref_server (collection_backend);
+ e_source_registry_server_add_source (server, m365_source);
+
+ g_object_unref (m365_source);
+ g_object_unref (server);
+ g_free (display_name);
+}
+
static void ews_backend_populate (ECollectionBackend *backend);
static void
@@ -838,6 +912,7 @@ ews_backend_populate (ECollectionBackend *collection_backend)
}
ews_backend_add_gal_source (ews_backend);
+ ews_backend_maybe_add_m365_source (ews_backend);
ews_backend_claim_old_resources (collection_backend);
if (e_backend_get_online (backend)) {
@@ -864,6 +939,15 @@ ews_backend_dup_resource_id (ECollectionBackend *backend,
ESourceEwsFolder *extension;
const gchar *extension_name;
+ if (e_source_has_extension (child_source, E_SOURCE_EXTENSION_AUTHENTICATION)) {
+ ESourceAuthentication *auth_extension;
+
+ auth_extension = e_source_get_extension (child_source, E_SOURCE_EXTENSION_AUTHENTICATION);
+
+ if (g_strcmp0 (e_source_authentication_get_method (auth_extension), "Microsoft365") == 0)
+ return g_strdup (EWS_HELPER_M365_RESOURCE_ID);
+ }
+
extension_name = E_SOURCE_EXTENSION_EWS_FOLDER;
extension = e_source_get_extension (child_source, extension_name);
@@ -882,25 +966,29 @@ ews_backend_child_added (ECollectionBackend *backend,
extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
if (e_source_has_extension (child_source, extension_name)) {
ESourceAuthentication *auth_child_extension;
- ESourceAuthentication *auth_collection_extension;
auth_child_extension = e_source_get_extension (child_source, extension_name);
- auth_collection_extension = e_source_get_extension (collection_source, extension_name);
-
- e_binding_bind_property (
- auth_collection_extension, "host",
- auth_child_extension, "host",
- G_BINDING_SYNC_CREATE);
-
- e_binding_bind_property (
- auth_collection_extension, "user",
- auth_child_extension, "user",
- G_BINDING_SYNC_CREATE);
-
- e_binding_bind_property (
- auth_collection_extension, "method",
- auth_child_extension, "method",
- G_BINDING_SYNC_CREATE);
+
+ if (g_strcmp0 (e_source_authentication_get_method (auth_child_extension), "Microsoft365") !=
0) {
+ ESourceAuthentication *auth_collection_extension;
+
+ auth_collection_extension = e_source_get_extension (collection_source,
extension_name);
+
+ e_binding_bind_property (
+ auth_collection_extension, "host",
+ auth_child_extension, "host",
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ auth_collection_extension, "user",
+ auth_child_extension, "user",
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ auth_collection_extension, "method",
+ auth_child_extension, "method",
+ G_BINDING_SYNC_CREATE);
+ }
}
/* We track EWS folders in a hash table by folder ID. */
diff --git a/src/Microsoft365/common/e-m365-connection.c b/src/Microsoft365/common/e-m365-connection.c
index 02664874..2d62fbf8 100644
--- a/src/Microsoft365/common/e-m365-connection.c
+++ b/src/Microsoft365/common/e-m365-connection.c
@@ -295,9 +295,12 @@ m365_connection_constructed (GObject *object)
soup_session_add_feature_by_type (cnc->priv->soup_session, E_TYPE_SOUP_AUTH_BEARER);
/* This can use only OAuth2 authentication, which should be set on the ESource */
- soup_session_remove_feature_by_type (cnc->priv->soup_session, SOUP_TYPE_AUTH_BASIC);
- soup_session_remove_feature_by_type (cnc->priv->soup_session, SOUP_TYPE_AUTH_NTLM);
- soup_session_remove_feature_by_type (cnc->priv->soup_session, SOUP_TYPE_AUTH_NEGOTIATE);
+ if (soup_session_has_feature (cnc->priv->soup_session, SOUP_TYPE_AUTH_BASIC))
+ soup_session_remove_feature_by_type (cnc->priv->soup_session, SOUP_TYPE_AUTH_BASIC);
+ if (soup_session_has_feature (cnc->priv->soup_session, SOUP_TYPE_AUTH_NTLM))
+ soup_session_remove_feature_by_type (cnc->priv->soup_session, SOUP_TYPE_AUTH_NTLM);
+ if (soup_session_has_feature (cnc->priv->soup_session, SOUP_TYPE_AUTH_NEGOTIATE))
+ soup_session_remove_feature_by_type (cnc->priv->soup_session, SOUP_TYPE_AUTH_NEGOTIATE);
soup_session_add_feature_by_type (cnc->priv->soup_session, E_TYPE_SOUP_AUTH_BEARER);
cnc->priv->hash_key = camel_network_settings_dup_user (CAMEL_NETWORK_SETTINGS (cnc->priv->settings));
@@ -791,11 +794,17 @@ e_m365_connection_json_node_from_message (SoupMessage *message,
if (content_type && g_ascii_strcasecmp (content_type, "application/json") == 0) {
JsonParser *json_parser;
+ GByteArray *msg_bytes = NULL;
json_parser = json_parser_new_immutable ();
+ if (!input_stream)
+ msg_bytes = e_soup_session_util_get_message_bytes (message);
+
if (input_stream) {
success = json_parser_load_from_stream (json_parser, input_stream,
cancellable, error);
+ } else if (msg_bytes) {
+ success = json_parser_load_from_data (json_parser, (const gchar *)
msg_bytes->data, msg_bytes->len, error);
} else {
/* This should not happen, it's for safety check only, thus the string is not
localized */
success = FALSE;
@@ -1013,6 +1022,17 @@ m365_connection_send_request_sync (EM365Connection *cnc,
if (node)
json_node_unref (node);
+ } else if (e_soup_session_util_get_message_bytes (message)) {
+ /* Try to extract detailed error */
+ JsonNode *node = NULL;
+
+ if (!e_m365_connection_json_node_from_message (message, input_stream, &node,
cancellable, &local_error) && local_error) {
+ g_clear_error (error);
+ g_propagate_error (error, local_error);
+ local_error = NULL;
+ }
+
+ g_clear_pointer (&node, json_node_unref);
}
g_clear_object (&input_stream);
@@ -3865,7 +3885,6 @@ e_m365_connection_create_event_sync (EM365Connection *cnc,
gchar *uri;
g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
- g_return_val_if_fail (calendar_id != NULL, FALSE);
g_return_val_if_fail (event != NULL, FALSE);
g_return_val_if_fail (out_created_event != NULL, FALSE);
@@ -3878,7 +3897,7 @@ e_m365_connection_create_event_sync (EM365Connection *cnc,
"", "events",
NULL);
} else {
- uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, "me",
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, "users",
"events", NULL, NULL, NULL);
}
@@ -4422,7 +4441,6 @@ e_m365_connection_add_event_attachment_sync (EM365Connection *cnc,
gchar *uri;
g_return_val_if_fail (E_IS_M365_CONNECTION (cnc), FALSE);
- g_return_val_if_fail (calendar_id != NULL, FALSE);
g_return_val_if_fail (event_id != NULL, FALSE);
g_return_val_if_fail (in_attachment != NULL, FALSE);
@@ -4437,7 +4455,7 @@ e_m365_connection_add_event_attachment_sync (EM365Connection *cnc,
"", "attachments",
NULL);
} else {
- uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, "me",
+ uri = e_m365_connection_construct_uri (cnc, TRUE, user_override, E_M365_API_V1_0, "users",
"events", NULL, NULL,
"", event_id,
"", "attachments",
diff --git a/src/Microsoft365/common/e-oauth2-service-microsoft365.c
b/src/Microsoft365/common/e-oauth2-service-microsoft365.c
index 3b51384c..3cf3fabe 100644
--- a/src/Microsoft365/common/e-oauth2-service-microsoft365.c
+++ b/src/Microsoft365/common/e-oauth2-service-microsoft365.c
@@ -430,6 +430,9 @@ e_oauth2_service_microsoft365_class_init (EOAuth2ServiceMicrosoft365Class *klass
object_class = G_OBJECT_CLASS (klass);
object_class->finalize = eos_microsoft365_finalize;
+
+ /* Make sure the ESourceCamel knows about it, even when no Microsoft365 mail account is created */
+ e_source_camel_generate_subtype ("microsoft365", CAMEL_TYPE_M365_SETTINGS);
}
static void
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]