/* * Copyright (C) 2018 Red Hat, Inc. (www.redhat.com) * * 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. * * 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 . */ #include "evolution-ews-config.h" #include #include #include "server/camel-ews-settings.h" #include "e-oauth2-service-office365.h" /* https://portal.azure.com/ https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-developers-guide https://tsmatz.wordpress.com/2016/10/07/application-permission-with-v2-endpoint-and-microsoft-graph/ */ #define OFFICE365_RESOURCE "https://outlook.office.com" #define OFFICE365_SCOPE "openid offline_access profile " \ "Mail.ReadWrite " \ "Mail.ReadWrite.Shared " \ "Mail.Send " \ "Mail.Send.Shared " \ "Calendars.ReadWrite " \ "Calendars.ReadWrite.Shared " \ "Contacts.ReadWrite " \ "Contacts.ReadWrite.Shared " \ "Tasks.ReadWrite " \ "Tasks.ReadWrite.Shared " \ "MailboxSettings.ReadWrite " \ "People.Read " \ "User.ReadBasic.All" struct _EOAuth2ServiceOffice365Private { GMutex string_cache_lock; GHashTable *string_cache; }; /* Forward Declarations */ static void e_oauth2_service_office365_oauth2_service_init (EOAuth2ServiceInterface *iface); G_DEFINE_DYNAMIC_TYPE_EXTENDED (EOAuth2ServiceOffice365, e_oauth2_service_office365, E_TYPE_OAUTH2_SERVICE_BASE, 0, G_IMPLEMENT_INTERFACE_DYNAMIC (E_TYPE_OAUTH2_SERVICE, e_oauth2_service_office365_oauth2_service_init)) static const gchar * eos_office365_cache_string (EOAuth2ServiceOffice365 *oauth2_office365, gchar *str) /* takes ownership of the 'str' */ { const gchar *cached_str; g_return_val_if_fail (E_IS_OAUTH2_SERVICE_OFFICE365 (oauth2_office365), NULL); if (!str) return NULL; if (!*str) return ""; g_mutex_lock (&oauth2_office365->priv->string_cache_lock); cached_str = g_hash_table_lookup (oauth2_office365->priv->string_cache, str); if (cached_str) { g_free (str); } else { g_hash_table_insert (oauth2_office365->priv->string_cache, str, str); cached_str = str; } g_mutex_unlock (&oauth2_office365->priv->string_cache_lock); return cached_str; } static CamelEwsSettings * eos_office365_get_camel_settings (ESource *source) { ESourceCamel *extension; if (!source) return NULL; g_return_val_if_fail (E_IS_SOURCE (source), NULL); extension = e_source_get_extension (source, e_source_camel_get_extension_name ("ews")); return CAMEL_EWS_SETTINGS (e_source_camel_get_settings (extension)); } static gboolean eos_office365_guess_can_process (EOAuth2Service *service, const gchar *protocol, const gchar *hostname) { return e_oauth2_services_is_supported () && protocol && g_ascii_strcasecmp (protocol, "ews") == 0 && hostname && e_util_utf8_strstrcase (hostname, "outlook.office365.com"); } static const gchar * eos_office365_get_name (EOAuth2Service *service) { return "Office365"; } static const gchar * eos_office365_get_display_name (EOAuth2Service *service) { /* Translators: This is a user-visible string, display name of an OAuth2 service. */ return C_("OAuth2Service", "Office365"); } static const gchar * eos_office365_get_client_id (EOAuth2Service *service, ESource *source) { EOAuth2ServiceOffice365 *oauth2_office365 = E_OAUTH2_SERVICE_OFFICE365 (service); CamelEwsSettings *ews_settings; ews_settings = eos_office365_get_camel_settings (source); if (ews_settings && camel_ews_settings_get_override_oauth2 (ews_settings)) { gchar *client_id = camel_ews_settings_dup_oauth2_client_id (ews_settings); if (client_id && !*client_id) { g_free (client_id); client_id = NULL; } if (client_id) return eos_office365_cache_string (oauth2_office365, client_id); } return OFFICE365_CLIENT_ID; } static const gchar * eos_office365_get_client_secret (EOAuth2Service *service, ESource *source) { return NULL; } static const gchar * eos_office365_get_authentication_uri (EOAuth2Service *service, ESource *source) { EOAuth2ServiceOffice365 *oauth2_office365 = E_OAUTH2_SERVICE_OFFICE365 (service); CamelEwsSettings *ews_settings; ews_settings = eos_office365_get_camel_settings (source); if (ews_settings && camel_ews_settings_get_override_oauth2 (ews_settings)) { gchar *tenant; const gchar *res; tenant = camel_ews_settings_dup_oauth2_tenant (ews_settings); if (tenant && !*tenant) { g_free (tenant); tenant = NULL; } res = eos_office365_cache_string (oauth2_office365, g_strdup_printf ("https://login.microsoftonline.com/%s/oauth2/authorize", tenant ? tenant : OFFICE365_TENANT)); g_free (tenant); return res; } return "https://login.microsoftonline.com/" OFFICE365_TENANT "/oauth2/authorize"; } static const gchar * eos_office365_get_refresh_uri (EOAuth2Service *service, ESource *source) { EOAuth2ServiceOffice365 *oauth2_office365 = E_OAUTH2_SERVICE_OFFICE365 (service); CamelEwsSettings *ews_settings; ews_settings = eos_office365_get_camel_settings (source); if (ews_settings && camel_ews_settings_get_override_oauth2 (ews_settings)) { gchar *tenant; const gchar *res; tenant = camel_ews_settings_dup_oauth2_tenant (ews_settings); if (tenant && !*tenant) { g_free (tenant); tenant = NULL; } res = eos_office365_cache_string (oauth2_office365, g_strdup_printf ("https://login.microsoftonline.com/%s/oauth2/token", tenant ? tenant : OFFICE365_TENANT)); g_free (tenant); return res; } return "https://login.microsoftonline.com/" OFFICE365_TENANT "/oauth2/token"; } static const gchar * eos_office365_get_redirect_uri (EOAuth2Service *service, ESource *source) { EOAuth2ServiceOffice365 *oauth2_office365 = E_OAUTH2_SERVICE_OFFICE365 (service); CamelEwsSettings *ews_settings; const gchar *res; ews_settings = eos_office365_get_camel_settings (source); if (ews_settings && camel_ews_settings_get_override_oauth2 (ews_settings)) { gchar *redirect_uri; redirect_uri = camel_ews_settings_dup_oauth2_redirect_uri (ews_settings); if (redirect_uri && !*redirect_uri) { g_free (redirect_uri); redirect_uri = NULL; } if (redirect_uri) return eos_office365_cache_string (oauth2_office365, redirect_uri); } res = OFFICE365_REDIRECT_URI; if (res && *res) return res; return "https://login.microsoftonline.com/common/oauth2/nativeclient"; } static void eos_office365_prepare_authentication_uri_query (EOAuth2Service *service, ESource *source, GHashTable *uri_query) { g_return_if_fail (uri_query != NULL); e_oauth2_service_util_set_to_form (uri_query, "response_mode", "query"); e_oauth2_service_util_set_to_form (uri_query, "prompt", "login"); e_oauth2_service_util_set_to_form (uri_query, "scope", OFFICE365_SCOPE); e_oauth2_service_util_set_to_form (uri_query, "resource", OFFICE365_RESOURCE); } static gboolean eos_office365_extract_authorization_code (EOAuth2Service *service, ESource *source, const gchar *page_title, const gchar *page_uri, const gchar *page_content, gchar **out_authorization_code) { SoupURI *suri; gboolean known = FALSE; g_return_val_if_fail (out_authorization_code != NULL, FALSE); *out_authorization_code = NULL; if (!page_uri || !*page_uri) return FALSE; suri = soup_uri_new (page_uri); if (!suri) return FALSE; if (suri->query) { GHashTable *uri_query = soup_form_decode (suri->query); if (uri_query) { const gchar *code; code = g_hash_table_lookup (uri_query, "code"); if (code && *code) { *out_authorization_code = g_strdup (code); known = TRUE; } else if (g_hash_table_lookup (uri_query, "error")) { known = TRUE; if (g_strcmp0 (g_hash_table_lookup (uri_query, "error"), "access_denied") != 0) { const gchar *description; description = g_hash_table_lookup (uri_query, "error_description"); if (description) { g_warning ("%s: error:%s description:%s", G_STRFUNC, (const gchar *) g_hash_table_lookup (uri_query, "error"), description); } } } g_hash_table_unref (uri_query); } } soup_uri_free (suri); return known; } static void eos_office365_prepare_refresh_token_form (EOAuth2Service *service, ESource *source, const gchar *refresh_token, GHashTable *form) { g_return_if_fail (form != NULL); e_oauth2_service_util_set_to_form (form, "scope", OFFICE365_SCOPE); e_oauth2_service_util_set_to_form (form, "resource", OFFICE365_RESOURCE); e_oauth2_service_util_set_to_form (form, "redirect_uri", e_oauth2_service_get_redirect_uri (service, source)); } static void eos_office365_finalize (GObject *object) { EOAuth2ServiceOffice365 *oauth2_office365 = E_OAUTH2_SERVICE_OFFICE365 (object); g_mutex_lock (&oauth2_office365->priv->string_cache_lock); g_hash_table_destroy (oauth2_office365->priv->string_cache); g_mutex_unlock (&oauth2_office365->priv->string_cache_lock); g_mutex_clear (&oauth2_office365->priv->string_cache_lock); /* Chain up to parent's method. */ G_OBJECT_CLASS (e_oauth2_service_office365_parent_class)->finalize (object); } static void e_oauth2_service_office365_oauth2_service_init (EOAuth2ServiceInterface *iface) { iface->guess_can_process = eos_office365_guess_can_process; iface->get_name = eos_office365_get_name; iface->get_display_name = eos_office365_get_display_name; iface->get_client_id = eos_office365_get_client_id; iface->get_client_secret = eos_office365_get_client_secret; iface->get_authentication_uri = eos_office365_get_authentication_uri; iface->get_refresh_uri = eos_office365_get_refresh_uri; iface->get_redirect_uri = eos_office365_get_redirect_uri; iface->prepare_authentication_uri_query = eos_office365_prepare_authentication_uri_query; iface->extract_authorization_code = eos_office365_extract_authorization_code; iface->prepare_refresh_token_form = eos_office365_prepare_refresh_token_form; } static void e_oauth2_service_office365_class_init (EOAuth2ServiceOffice365Class *klass) { GObjectClass *object_class; g_type_class_add_private (klass, sizeof (EOAuth2ServiceOffice365Private)); object_class = G_OBJECT_CLASS (klass); object_class->finalize = eos_office365_finalize; } static void e_oauth2_service_office365_class_finalize (EOAuth2ServiceOffice365Class *klass) { } static void e_oauth2_service_office365_init (EOAuth2ServiceOffice365 *oauth2_office365) { oauth2_office365->priv = G_TYPE_INSTANCE_GET_PRIVATE (oauth2_office365, E_TYPE_OAUTH2_SERVICE_OFFICE365, EOAuth2ServiceOffice365Private); g_mutex_init (&oauth2_office365->priv->string_cache_lock); oauth2_office365->priv->string_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); } void e_oauth2_service_office365_type_register (GTypeModule *type_module) { e_oauth2_service_office365_register_type (type_module); }