diff options
| author | Milan Crha <mcrha@redhat.com> | 2018-01-22 13:32:29 +0100 |
|---|---|---|
| committer | Milan Crha <mcrha@redhat.com> | 2018-01-22 13:32:29 +0100 |
| commit | 9a4c9d6a58e6dd5d4bbf3f8dc5d79040219dfbca (patch) | |
| tree | df078253f22bcd8c33802c8156b3892b32ba78cf | |
| parent | 01857f50c25d411f5ed83bff2d4554c65e3947bd (diff) | |
| download | evolution-data-server-9a4c9d6a58e6dd5d4bbf3f8dc5d79040219dfbca.tar.gz | |
Change how built-in OAuth2 authentication works
This change allows easier extending of built-in OAuth2 authentications
with minimal code "duplication".
A CMake option ENABLE_GOOGLE_AUTH had been renamed to ENABLE_OAUTH2
to reflect this extensibility as well.
61 files changed, 4599 insertions, 2452 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index be416bc84..a6d583b13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,7 @@ set(USER_PROMPTER_DBUS_SERVICE_NAME "org.gnome.evolution.dataserver.UserPrompter # ****************************** # Library versioning # ****************************** -set(LIBCAMEL_CURRENT 60) +set(LIBCAMEL_CURRENT 61) set(LIBCAMEL_REVISION 0) set(LIBCAMEL_AGE 0) @@ -52,11 +52,11 @@ set(LIBEBACKEND_CURRENT 10) set(LIBEBACKEND_REVISION 0) set(LIBEBACKEND_AGE 0) -set(LIBEDATASERVER_CURRENT 22) +set(LIBEDATASERVER_CURRENT 23) set(LIBEDATASERVER_REVISION 0) set(LIBEDATASERVER_AGE 0) -set(LIBEDATASERVERUI_CURRENT 1) +set(LIBEDATASERVERUI_CURRENT 2) set(LIBEDATASERVERUI_REVISION 0) set(LIBEDATASERVERUI_AGE 0) @@ -380,14 +380,14 @@ if(ENABLE_GTK) set(HAVE_GTK 1) endif(ENABLE_GTK) -# *************************************************** -# Check for WebKitGTK+ and json-glib for google auth -# *************************************************** +# ************************************************************** +# Check for WebKitGTK+ and json-glib for OAuth2 authentications +# ************************************************************** -add_printable_option(ENABLE_GOOGLE_AUTH "Enable built-in Google authentication" ON) +add_printable_option(ENABLE_OAUTH2 "Enable built-in OAuth2 authentications" ON) -if(ENABLE_GOOGLE_AUTH) - pkg_check_modules_for_option(ENABLE_GOOGLE_AUTH "Google authentication support" GOOGLE_AUTH +if(ENABLE_OAUTH2) + pkg_check_modules_for_option(ENABLE_OAUTH2 "OAuth2 authentication support" OAUTH2 webkit2gtk-4.0>=${webkit2gtk_minimum_version} json-glib-1.0>=${json_glib_minimum_version} ) @@ -402,7 +402,7 @@ if(ENABLE_GOOGLE_AUTH) if(WITH_GOOGLE_CLIENT_SECRET STREQUAL "") set(WITH_GOOGLE_CLIENT_SECRET "mtfUe5W8Aal9DcgVipOY1T9G") endif(WITH_GOOGLE_CLIENT_SECRET STREQUAL "") -endif(ENABLE_GOOGLE_AUTH) +endif(ENABLE_OAUTH2) # ****************************************** # Check whether to build examples/demos diff --git a/config.h.in b/config.h.in index 7ebf21605..8c41fa5ea 100644 --- a/config.h.in +++ b/config.h.in @@ -57,8 +57,8 @@ /* Define to 1 if you have the gtk+-3.0 package. */ #cmakedefine HAVE_GTK 1 -/* Define to 1 if Google autentication support is enabled. */ -#cmakedefine ENABLE_GOOGLE_AUTH 1 +/* Define to 1 if OAuth2 autentication support is enabled. */ +#cmakedefine ENABLE_OAUTH2 1 /* Define to 1 if the examples should be built. */ #cmakedefine BUILD_EXAMPLES 1 diff --git a/data/org.gnome.evolution-data-server.gschema.xml.in b/data/org.gnome.evolution-data-server.gschema.xml.in index a669207cd..805f6895d 100644 --- a/data/org.gnome.evolution-data-server.gschema.xml.in +++ b/data/org.gnome.evolution-data-server.gschema.xml.in @@ -29,5 +29,22 @@ <_summary>A list of variables which can be part of the autoconfig .source files</_summary> <_description>Each item of the array is expected to be of the form: name=value. These variables are checked before environment variables, but after the predefined USER, REALNAME and HOST variables.</_description> </key> + <key name="oauth2-services-hint" type="as"> + <default>['']</default> + <_summary>A list of hints for OAuth2 services</_summary> + <_description>Users can extend the list of supported protocols and hostnames for defined OAuth2 services, in addition to those hard-coded. + Each line can be of the form: + servicename[-protocol]:hostname1,hostname2,... + where 'servicename' is the actual service name; + the '-protocol' is optional, and if written, then the service can be used only if both 'protocol' and 'hostnameX' match; + the 'hostnameX' is the actual host name to compare with, case insensitively. + Each line can contain multiple values, separated by comma. There can be provided multiple lines + for one OAuth2 service. Note that the actual URL where the token is requested and refreshed cannot + be changed here, the hostname is to allow other servers, where the OAuth2 service can be used. + + Examples: + Company:mail.company.com - enables 'Company' OAuth2 authentication for 'mail.company.com' host + Company-CalDAV:caldav.company.com - enables 'Company' OAuth2 authentication for any 'CalDAV' source, which reads data from 'caldav.companycaldav.com' host</_description> + </key> </schema> </schemalist> diff --git a/docs/reference/camel/camel-docs.sgml.in b/docs/reference/camel/camel-docs.sgml.in index 36f414c4e..1e7d67a1c 100644 --- a/docs/reference/camel/camel-docs.sgml.in +++ b/docs/reference/camel/camel-docs.sgml.in @@ -149,6 +149,8 @@ <xi:include href="xml/camel-sasl-ntlm.xml"/> <xi:include href="xml/camel-sasl-plain.xml"/> <xi:include href="xml/camel-sasl-popb4smtp.xml"/> + <xi:include href="xml/camel-sasl-xoauth2.xml"/> + <xi:include href="xml/camel-sasl-xoauth2-google.xml"/> </chapter> <chapter id="MIME"> diff --git a/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in b/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in index 77c8c58b6..c0c94b719 100644 --- a/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in +++ b/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in @@ -248,6 +248,14 @@ </chapter> <chapter> + <title>Built-in OAuth2 authentication</title> + <xi:include href="xml/e-oauth2-service.xml"/> + <xi:include href="xml/e-oauth2-services.xml"/> + <xi:include href="xml/e-oauth2-service-base.xml"/> + <xi:include href="xml/e-oauth2-service-google.xml"/> + </chapter> + + <chapter> <title>Available only for backends</title> <xi:include href="xml/e-file-cache.xml"/> <xi:include href="xml/e-db3-utils.xml"/> @@ -284,7 +292,7 @@ <xi:include href="xml/e-source-credentials-provider.xml"/> <xi:include href="xml/e-source-credentials-provider-impl.xml"/> <xi:include href="xml/e-source-credentials-provider-impl-password.xml"/> - <xi:include href="xml/e-source-credentials-provider-impl-google.xml"/> + <xi:include href="xml/e-source-credentials-provider-impl-oauth2.xml"/> </chapter> </part> diff --git a/po/POTFILES.in b/po/POTFILES.in index 6803960f1..0849028a2 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -113,6 +113,7 @@ src/camel/camel-sasl-login.c src/camel/camel-sasl-ntlm.c src/camel/camel-sasl-plain.c src/camel/camel-sasl-popb4smtp.c +src/camel/camel-sasl-xoauth2-google.c src/camel/camel-search-private.c src/camel/camel-service.c src/camel/camel-session.c @@ -190,10 +191,11 @@ src/libebackend/e-subprocess-factory.c src/libebackend/e-user-prompter-server.c src/libedataserver/e-categories.c src/libedataserver/e-client.c +src/libedataserver/e-oauth2-service.c +src/libedataserver/e-oauth2-service-google.c src/libedataserver/e-soup-session.c src/libedataserver/e-source.c src/libedataserver/e-source-credentials-provider-impl.c -src/libedataserver/e-source-credentials-provider-impl-google.c src/libedataserver/e-source-credentials-provider-impl-password.c src/libedataserver/e-source-mail-signature.c src/libedataserver/e-source-proxy.c @@ -203,7 +205,7 @@ src/libedataserver/e-time-utils.c src/libedataserver/e-webdav-discover.c src/libedataserver/e-webdav-session.c src/libedataserverui/e-credentials-prompter.c -src/libedataserverui/e-credentials-prompter-impl-google.c +src/libedataserverui/e-credentials-prompter-impl-oauth2.c src/libedataserverui/e-credentials-prompter-impl-password.c src/libedataserverui/e-trust-prompt.c src/libedataserverui/e-webdav-discover-widget.c diff --git a/src/addressbook/backends/google/e-book-backend-google.c b/src/addressbook/backends/google/e-book-backend-google.c index 2ec0d2481..78492e55b 100644 --- a/src/addressbook/backends/google/e-book-backend-google.c +++ b/src/addressbook/backends/google/e-book-backend-google.c @@ -399,10 +399,12 @@ ebb_google_connect_sync (EBookMetaBackend *meta_backend, if (!success) { if (g_error_matches (local_error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED)) { - if (!e_named_parameters_exists (credentials, E_SOURCE_CREDENTIAL_PASSWORD)) - *out_auth_result = E_SOURCE_AUTHENTICATION_REQUIRED; - else - *out_auth_result = E_SOURCE_AUTHENTICATION_REJECTED; + *out_auth_result = E_SOURCE_AUTHENTICATION_REJECTED; + } else if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED) || + g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { + *out_auth_result = E_SOURCE_AUTHENTICATION_REJECTED; + g_propagate_error (error, local_error); + local_error = NULL; } else { *out_auth_result = E_SOURCE_AUTHENTICATION_ERROR; ebb_google_data_book_error_from_gdata_error (error, local_error); diff --git a/src/addressbook/backends/webdav/e-book-backend-webdav.c b/src/addressbook/backends/webdav/e-book-backend-webdav.c index b4c3ecf46..cc98b9b18 100644 --- a/src/addressbook/backends/webdav/e-book-backend-webdav.c +++ b/src/addressbook/backends/webdav/e-book-backend-webdav.c @@ -213,7 +213,8 @@ ebb_webdav_connect_sync (EBookMetaBackend *meta_backend, gboolean credentials_empty; gboolean is_ssl_error; - credentials_empty = !credentials || !e_named_parameters_count (credentials); + credentials_empty = (!credentials || !e_named_parameters_count (credentials)) && + !e_soup_session_get_authentication_requires_credentials (E_SOUP_SESSION (bbdav->priv->webdav)); is_ssl_error = g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED); *out_auth_result = E_SOURCE_AUTHENTICATION_ERROR; @@ -229,6 +230,10 @@ ebb_webdav_connect_sync (EBookMetaBackend *meta_backend, *out_auth_result = E_SOURCE_AUTHENTICATION_REQUIRED; else *out_auth_result = E_SOURCE_AUTHENTICATION_REJECTED; + } else if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED) || + (!e_soup_session_get_authentication_requires_credentials (E_SOUP_SESSION (bbdav->priv->webdav)) && + g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))) { + *out_auth_result = E_SOURCE_AUTHENTICATION_REJECTED; } else if (!local_error) { g_set_error_literal (&local_error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unknown error")); diff --git a/src/calendar/backends/caldav/e-cal-backend-caldav.c b/src/calendar/backends/caldav/e-cal-backend-caldav.c index 5f46f8e7e..4551dea66 100644 --- a/src/calendar/backends/caldav/e-cal-backend-caldav.c +++ b/src/calendar/backends/caldav/e-cal-backend-caldav.c @@ -174,7 +174,8 @@ ecb_caldav_connect_sync (ECalMetaBackend *meta_backend, gboolean credentials_empty; gboolean is_ssl_error; - credentials_empty = !credentials || !e_named_parameters_count (credentials); + credentials_empty = (!credentials || !e_named_parameters_count (credentials)) && + !e_soup_session_get_authentication_requires_credentials (E_SOUP_SESSION (cbdav->priv->webdav)); is_ssl_error = g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED); *out_auth_result = E_SOURCE_AUTHENTICATION_ERROR; @@ -190,6 +191,10 @@ ecb_caldav_connect_sync (ECalMetaBackend *meta_backend, *out_auth_result = E_SOURCE_AUTHENTICATION_REQUIRED; else *out_auth_result = E_SOURCE_AUTHENTICATION_REJECTED; + } else if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED) || + (!e_soup_session_get_authentication_requires_credentials (E_SOUP_SESSION (cbdav->priv->webdav)) && + g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))) { + *out_auth_result = E_SOURCE_AUTHENTICATION_REJECTED; } else if (!local_error) { g_set_error_literal (&local_error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unknown error")); diff --git a/src/calendar/backends/gtasks/e-cal-backend-gtasks.c b/src/calendar/backends/gtasks/e-cal-backend-gtasks.c index 98152421f..c1a16abd8 100644 --- a/src/calendar/backends/gtasks/e-cal-backend-gtasks.c +++ b/src/calendar/backends/gtasks/e-cal-backend-gtasks.c @@ -442,11 +442,12 @@ ecb_gtasks_connect_sync (ECalMetaBackend *meta_backend, if (!success) { if (g_error_matches (local_error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED)) { - if (!e_named_parameters_exists (credentials, E_SOURCE_CREDENTIAL_PASSWORD)) - *out_auth_result = E_SOURCE_AUTHENTICATION_REQUIRED; - else - *out_auth_result = E_SOURCE_AUTHENTICATION_REJECTED; + *out_auth_result = E_SOURCE_AUTHENTICATION_REJECTED; g_clear_error (&local_error); + } else if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED) || + g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { + *out_auth_result = E_SOURCE_AUTHENTICATION_REJECTED; + g_propagate_error (error, local_error); } else { *out_auth_result = E_SOURCE_AUTHENTICATION_ERROR; g_propagate_error (error, local_error); diff --git a/src/camel/CMakeLists.txt b/src/camel/CMakeLists.txt index acc6b1a41..a9f50d48f 100644 --- a/src/camel/CMakeLists.txt +++ b/src/camel/CMakeLists.txt @@ -89,6 +89,8 @@ set(SOURCES camel-sasl-ntlm.c camel-sasl-plain.c camel-sasl-popb4smtp.c + camel-sasl-xoauth2.c + camel-sasl-xoauth2-google.c camel-sasl.c camel-search-private.c camel-search-sql-sexp.c @@ -226,6 +228,8 @@ set(HEADERS camel-sasl-ntlm.h camel-sasl-plain.h camel-sasl-popb4smtp.h + camel-sasl-xoauth2.h + camel-sasl-xoauth2-google.h camel-sasl.h camel-search-private.h camel-search-sql-sexp.h diff --git a/src/camel/camel-sasl-xoauth2-google.c b/src/camel/camel-sasl-xoauth2-google.c new file mode 100644 index 000000000..fb016a273 --- /dev/null +++ b/src/camel/camel-sasl-xoauth2-google.c @@ -0,0 +1,44 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "evolution-data-server-config.h" + +#include <glib/gi18n-lib.h> + +#include "camel-sasl-xoauth2-google.h" + +static CamelServiceAuthType sasl_xoauth2_google_auth_type = { + N_("OAuth2 (Google)"), + N_("This option will use an OAuth 2.0 " + "access token to connect to the Google server"), + "Google", + FALSE +}; + +G_DEFINE_TYPE (CamelSaslXOAuth2Google, camel_sasl_xoauth2_google, CAMEL_TYPE_SASL_XOAUTH2) + +static void +camel_sasl_xoauth2_google_class_init (CamelSaslXOAuth2GoogleClass *klass) +{ + CamelSaslClass *sasl_class; + + sasl_class = CAMEL_SASL_CLASS (klass); + sasl_class->auth_type = &sasl_xoauth2_google_auth_type; +} + +static void +camel_sasl_xoauth2_google_init (CamelSaslXOAuth2Google *sasl) +{ +} diff --git a/src/camel/camel-sasl-xoauth2-google.h b/src/camel/camel-sasl-xoauth2-google.h new file mode 100644 index 000000000..66d0b0ef7 --- /dev/null +++ b/src/camel/camel-sasl-xoauth2-google.h @@ -0,0 +1,63 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION) +#error "Only <camel/camel.h> can be included directly." +#endif + +#ifndef CAMEL_SASL_XOAUTH2_GOOGLE_H +#define CAMEL_SASL_XOAUTH2_GOOGLE_H + +#include <camel/camel-sasl-xoauth2.h> + +/* Standard GObject macros */ +#define CAMEL_TYPE_SASL_XOAUTH2_GOOGLE \ + (camel_sasl_xoauth2_google_get_type ()) +#define CAMEL_SASL_XOAUTH2_GOOGLE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), CAMEL_TYPE_SASL_XOAUTH2_GOOGLE, CamelSaslXOAuth2Google)) +#define CAMEL_SASL_XOAUTH2_GOOGLE_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), CAMEL_TYPE_SASL_XOAUTH2_GOOGLE, CamelSaslXOAuth2GoogleClass)) +#define CAMEL_IS_SASL_XOAUTH2_GOOGLE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), CAMEL_TYPE_SASL_XOAUTH2_GOOGLE)) +#define CAMEL_IS_SASL_XOAUTH2_GOOGLE_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), CAMEL_TYPE_SASL_XOAUTH2_GOOGLE)) +#define CAMEL_SASL_XOAUTH2_GOOGLE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), CAMEL_TYPE_SASL_XOAUTH2_GOOGLE, CamelSaslXOAuth2GoogleClass)) + +G_BEGIN_DECLS + +typedef struct _CamelSaslXOAuth2Google CamelSaslXOAuth2Google; +typedef struct _CamelSaslXOAuth2GoogleClass CamelSaslXOAuth2GoogleClass; +typedef struct _CamelSaslXOAuth2GooglePrivate CamelSaslXOAuth2GooglePrivate; + +struct _CamelSaslXOAuth2Google { + CamelSaslXOAuth2 parent; + CamelSaslXOAuth2GooglePrivate *priv; +}; + +struct _CamelSaslXOAuth2GoogleClass { + CamelSaslXOAuth2Class parent_class; +}; + +GType camel_sasl_xoauth2_google_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* CAMEL_SASL_XOAUTH2_GOOGLE_H */ diff --git a/src/camel/camel-sasl-xoauth2.c b/src/camel/camel-sasl-xoauth2.c new file mode 100644 index 000000000..627def4f5 --- /dev/null +++ b/src/camel/camel-sasl-xoauth2.c @@ -0,0 +1,110 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "evolution-data-server-config.h" + +#include "camel-network-settings.h" +#include "camel-session.h" + +#include "camel-sasl-xoauth2.h" + +G_DEFINE_ABSTRACT_TYPE (CamelSaslXOAuth2, camel_sasl_xoauth2, CAMEL_TYPE_SASL) + +static void +sasl_xoauth2_append_request (GByteArray *byte_array, + const gchar *user, + const gchar *access_token) +{ + GString *request; + + g_return_if_fail (user != NULL); + g_return_if_fail (access_token != NULL); + + /* Compared to OAuth 1.0, this step is trivial. */ + + /* The request is easier to assemble with a GString. */ + request = g_string_sized_new (512); + + g_string_append (request, "user="); + g_string_append (request, user); + g_string_append_c (request, 1); + g_string_append (request, "auth=Bearer "); + g_string_append (request, access_token); + g_string_append_c (request, 1); + g_string_append_c (request, 1); + + /* Copy the GString content to the GByteArray. */ + g_byte_array_append ( + byte_array, (guint8 *) request->str, request->len); + + g_string_free (request, TRUE); +} + +static GByteArray * +sasl_xoauth2_challenge_sync (CamelSasl *sasl, + GByteArray *token, + GCancellable *cancellable, + GError **error) +{ + GByteArray *byte_array = NULL; + CamelService *service; + CamelSession *session; + CamelSettings *settings; + gchar *access_token = NULL; + gint expires_in = -1; + gboolean success; + + service = camel_sasl_get_service (sasl); + session = camel_service_ref_session (service); + settings = camel_service_ref_settings (service); + + success = camel_session_get_oauth2_access_token_sync (session, service, + &access_token, &expires_in, cancellable, error); + + if (success && expires_in > 0) { + CamelNetworkSettings *network_settings; + gchar *user; + + network_settings = CAMEL_NETWORK_SETTINGS (settings); + user = camel_network_settings_dup_user (network_settings); + + byte_array = g_byte_array_new (); + sasl_xoauth2_append_request (byte_array, user, access_token); + + g_free (user); + } + + g_free (access_token); + g_object_unref (settings); + g_object_unref (session); + + /* IMAP and SMTP services will Base64-encode the request. */ + + return byte_array; +} + +static void +camel_sasl_xoauth2_class_init (CamelSaslXOAuth2Class *class) +{ + CamelSaslClass *sasl_class; + + sasl_class = CAMEL_SASL_CLASS (class); + sasl_class->challenge_sync = sasl_xoauth2_challenge_sync; +} + +static void +camel_sasl_xoauth2_init (CamelSaslXOAuth2 *sasl) +{ +} diff --git a/src/camel/camel-sasl-xoauth2.h b/src/camel/camel-sasl-xoauth2.h new file mode 100644 index 000000000..f613d9da1 --- /dev/null +++ b/src/camel/camel-sasl-xoauth2.h @@ -0,0 +1,63 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#if !defined (__CAMEL_H_INSIDE__) && !defined (CAMEL_COMPILATION) +#error "Only <camel/camel.h> can be included directly." +#endif + +#ifndef CAMEL_SASL_XOAUTH2_H +#define CAMEL_SASL_XOAUTH2_H + +#include <camel/camel-sasl.h> + +/* Standard GObject macros */ +#define CAMEL_TYPE_SASL_XOAUTH2 \ + (camel_sasl_xoauth2_get_type ()) +#define CAMEL_SASL_XOAUTH2(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), CAMEL_TYPE_SASL_XOAUTH2, CamelSaslXOAuth2)) +#define CAMEL_SASL_XOAUTH2_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), CAMEL_TYPE_SASL_XOAUTH2, CamelSaslXOAuth2Class)) +#define CAMEL_IS_SASL_XOAUTH2(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), CAMEL_TYPE_SASL_XOAUTH2)) +#define CAMEL_IS_SASL_XOAUTH2_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), CAMEL_TYPE_SASL_XOAUTH2)) +#define CAMEL_SASL_XOAUTH2_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), CAMEL_TYPE_SASL_XOAUTH2, CamelSaslXOAuth2Class)) + +G_BEGIN_DECLS + +typedef struct _CamelSaslXOAuth2 CamelSaslXOAuth2; +typedef struct _CamelSaslXOAuth2Class CamelSaslXOAuth2Class; +typedef struct _CamelSaslXOAuth2Private CamelSaslXOAuth2Private; + +struct _CamelSaslXOAuth2 { + CamelSasl parent; + CamelSaslXOAuth2Private *priv; +}; + +struct _CamelSaslXOAuth2Class { + CamelSaslClass parent_class; +}; + +GType camel_sasl_xoauth2_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* CAMEL_SASL_XOAUTH2_H */ diff --git a/src/camel/camel-sasl.c b/src/camel/camel-sasl.c index 69efc10c1..ac937131f 100644 --- a/src/camel/camel-sasl.c +++ b/src/camel/camel-sasl.c @@ -31,6 +31,7 @@ #include "camel-sasl-ntlm.h" #include "camel-sasl-plain.h" #include "camel-sasl-popb4smtp.h" +#include "camel-sasl-xoauth2-google.h" #include "camel-sasl.h" #include "camel-service.h" @@ -119,16 +120,17 @@ sasl_build_class_table (void) GHashTable *class_table; /* Register known types. */ - CAMEL_TYPE_SASL_ANONYMOUS; - CAMEL_TYPE_SASL_CRAM_MD5; - CAMEL_TYPE_SASL_DIGEST_MD5; + g_type_ensure (CAMEL_TYPE_SASL_ANONYMOUS); + g_type_ensure (CAMEL_TYPE_SASL_CRAM_MD5); + g_type_ensure (CAMEL_TYPE_SASL_DIGEST_MD5); #ifdef HAVE_KRB5 - CAMEL_TYPE_SASL_GSSAPI; + g_type_ensure (CAMEL_TYPE_SASL_GSSAPI); #endif - CAMEL_TYPE_SASL_LOGIN; - CAMEL_TYPE_SASL_NTLM; - CAMEL_TYPE_SASL_PLAIN; - CAMEL_TYPE_SASL_POPB4SMTP; + g_type_ensure (CAMEL_TYPE_SASL_LOGIN); + g_type_ensure (CAMEL_TYPE_SASL_NTLM); + g_type_ensure (CAMEL_TYPE_SASL_PLAIN); + g_type_ensure (CAMEL_TYPE_SASL_POPB4SMTP); + g_type_ensure (CAMEL_TYPE_SASL_XOAUTH2_GOOGLE); class_table = g_hash_table_new_full ( (GHashFunc) g_str_hash, @@ -949,3 +951,44 @@ camel_sasl_authtype (const gchar *mechanism) return auth_type; } + +/** + * camel_sasl_is_xoauth2_alias: + * @mechanism: (nullable): an authentication mechanism + * + * Checks whether exists a #CamelSasl method for the @mechanism and + * whether it derives from #CamelSaslXOAuth2. Such mechanisms are + * also treated as XOAUTH2, even their real name is different. + * + * Returns: whether exists #CamelSasl for the given @mechanism, + * which also derives from #CamelSaslXOAuth2. + * + * Since: 3.28 + **/ +gboolean +camel_sasl_is_xoauth2_alias (const gchar *mechanism) +{ + GHashTable *class_table; + CamelSaslClass *sasl_class; + gboolean exists = FALSE; + + if (!mechanism || !*mechanism) + return FALSE; + + class_table = sasl_build_class_table (); + sasl_class = g_hash_table_lookup (class_table, mechanism); + if (sasl_class) { + gpointer parent_class = sasl_class; + + while (parent_class = g_type_class_peek_parent (parent_class), parent_class) { + if (CAMEL_IS_SASL_XOAUTH2_CLASS (parent_class)) { + exists = TRUE; + break; + } + } + } + + g_hash_table_destroy (class_table); + + return exists; +} diff --git a/src/camel/camel-sasl.h b/src/camel/camel-sasl.h index befc00945..6a5e8d8cb 100644 --- a/src/camel/camel-sasl.h +++ b/src/camel/camel-sasl.h @@ -132,6 +132,7 @@ gchar * camel_sasl_challenge_base64_finish GList * camel_sasl_authtype_list (gboolean include_plain); CamelServiceAuthType * camel_sasl_authtype (const gchar *mechanism); +gboolean camel_sasl_is_xoauth2_alias (const gchar *mechanism); G_END_DECLS diff --git a/src/camel/camel-session.c b/src/camel/camel-session.c index a5868ab3f..74634a933 100644 --- a/src/camel/camel-session.c +++ b/src/camel/camel-session.c @@ -1922,3 +1922,38 @@ camel_session_forward_to_finish (CamelSession *session, return g_task_propagate_boolean (G_TASK (result), error); } +/** + * camel_session_get_oauth2_access_token_sync: + * @session: a #CamelSession + * @service: a #CamelService + * @out_access_token: (out) (nullable): return location for the access token, or %NULL + * @out_expires_in: (out) (nullable): return location for the token expiry, or %NULL + * @cancellable: optional #GCancellable object, or %NULL + * @error: return location for a #GError, or %NULL + * + * Obtains the OAuth 2.0 access token for @service along with its expiry + * in seconds from the current time (or 0 if unknown). + * + * Free the returned access token with g_free() when no longer needed. + * + * Returns: whether succeeded + * + * Since: 3.28 + **/ +gboolean +camel_session_get_oauth2_access_token_sync (CamelSession *session, + CamelService *service, + gchar **out_access_token, + gint *out_expires_in, + GCancellable *cancellable, + GError **error) +{ + CamelSessionClass *klass; + + g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE); + + klass = CAMEL_SESSION_GET_CLASS (session); + g_return_val_if_fail (klass->get_oauth2_access_token_sync != NULL, FALSE); + + return klass->get_oauth2_access_token_sync (session, service, out_access_token, out_expires_in, cancellable, error); +} diff --git a/src/camel/camel-session.h b/src/camel/camel-session.h index cdf818489..c01a93569 100644 --- a/src/camel/camel-session.h +++ b/src/camel/camel-session.h @@ -132,6 +132,13 @@ struct _CamelSessionClass { const gchar *address, GCancellable *cancellable, GError **error); + gboolean (*get_oauth2_access_token_sync) + (CamelSession *session, + CamelService *service, + gchar **out_access_token, + gint *out_expires_in, + GCancellable *cancellable, + GError **error); /* Padding for future expansion */ gpointer reserved_methods[20]; @@ -259,6 +266,13 @@ void camel_session_forward_to (CamelSession *session, gboolean camel_session_forward_to_finish (CamelSession *session, GAsyncResult *result, GError **error); +gboolean camel_session_get_oauth2_access_token_sync + (CamelSession *session, + CamelService *service, + gchar **out_access_token, + gint *out_expires_in, + GCancellable *cancellable, + GError **error); G_END_DECLS diff --git a/src/camel/camel.h b/src/camel/camel.h index daf90682e..758e76666 100644 --- a/src/camel/camel.h +++ b/src/camel/camel.h @@ -108,6 +108,8 @@ #include <camel/camel-sasl-ntlm.h> #include <camel/camel-sasl-plain.h> #include <camel/camel-sasl-popb4smtp.h> +#include <camel/camel-sasl-xoauth2.h> +#include <camel/camel-sasl-xoauth2-google.h> #include <camel/camel-service.h> #include <camel/camel-session.h> #include <camel/camel-settings.h> diff --git a/src/camel/providers/imapx/camel-imapx-command.c b/src/camel/providers/imapx/camel-imapx-command.c index e76f38eea..5ef56a54f 100644 --- a/src/camel/providers/imapx/camel-imapx-command.c +++ b/src/camel/providers/imapx/camel-imapx-command.c @@ -406,7 +406,7 @@ camel_imapx_command_add_part (CamelIMAPXCommand *ic, /* we presume we'll need to get additional data only if we're not authenticated yet */ g_object_ref (ob); mechanism = camel_sasl_get_mechanism (CAMEL_SASL (ob)); - if (g_strcmp0 (mechanism, "Google") == 0) + if (camel_sasl_is_xoauth2_alias (mechanism)) mechanism = "XOAUTH2"; g_string_append (buffer, mechanism); if (!camel_sasl_get_authenticated ((CamelSasl *) ob)) diff --git a/src/camel/providers/imapx/camel-imapx-server.c b/src/camel/providers/imapx/camel-imapx-server.c index 5937c8bb3..b0bf228cd 100644 --- a/src/camel/providers/imapx/camel-imapx-server.c +++ b/src/camel/providers/imapx/camel-imapx-server.c @@ -2918,7 +2918,8 @@ camel_imapx_server_authenticate_sync (CamelIMAPXServer *is, g_mutex_lock (&is->priv->stream_lock); if (is->priv->cinfo && !g_hash_table_lookup (is->priv->cinfo->auth_types, mechanism) && ( - !g_str_equal (mechanism, "Google") || !g_hash_table_lookup (is->priv->cinfo->auth_types, "XOAUTH2"))) { + !camel_sasl_is_xoauth2_alias (mechanism) || + !g_hash_table_lookup (is->priv->cinfo->auth_types, "XOAUTH2"))) { g_mutex_unlock (&is->priv->stream_lock); g_set_error ( error, CAMEL_SERVICE_ERROR, diff --git a/src/camel/providers/pop3/camel-pop3-store.c b/src/camel/providers/pop3/camel-pop3-store.c index 8614de167..77d97404e 100644 --- a/src/camel/providers/pop3/camel-pop3-store.c +++ b/src/camel/providers/pop3/camel-pop3-store.c @@ -308,7 +308,7 @@ try_sasl (CamelPOP3Store *store, goto exit; } - string = g_strdup_printf ("AUTH %s\r\n", g_strcmp0 (mechanism, "Google") == 0 ? "XOAUTH2" : mechanism); + string = g_strdup_printf ("AUTH %s\r\n", camel_sasl_is_xoauth2_alias (mechanism) ? "XOAUTH2" : mechanism); ret = camel_stream_write_string ( CAMEL_STREAM (pop3_stream), string, cancellable, error); g_free (string); @@ -766,7 +766,7 @@ pop3_store_authenticate_sync (CamelService *service, GList *link; const gchar *test_mechanism = mechanism; - if (g_strcmp0 (test_mechanism, "Google") == 0) + if (camel_sasl_is_xoauth2_alias (test_mechanism)) test_mechanism = "XOAUTH2"; link = pop3_engine->auth; diff --git a/src/camel/providers/smtp/camel-smtp-transport.c b/src/camel/providers/smtp/camel-smtp-transport.c index d249ed99d..7810be4e3 100644 --- a/src/camel/providers/smtp/camel-smtp-transport.c +++ b/src/camel/providers/smtp/camel-smtp-transport.c @@ -542,7 +542,7 @@ smtp_transport_connect_sync (CamelService *service, goto exit; } - if (g_hash_table_lookup (transport->authtypes, g_strcmp0 (mechanism, "Google") == 0 ? "XOAUTH2" : mechanism)) { + if (g_hash_table_lookup (transport->authtypes, camel_sasl_is_xoauth2_alias (mechanism) ? "XOAUTH2" : mechanism)) { gint tries = 0; GError *local_error = NULL; @@ -676,7 +676,7 @@ smtp_transport_authenticate_sync (CamelService *service, if (challenge) { auth_challenge = TRUE; cmdbuf = g_strdup_printf ( - "AUTH %s %s\r\n", g_strcmp0 (mechanism, "Google") == 0 ? "XOAUTH2" : mechanism, challenge); + "AUTH %s %s\r\n", camel_sasl_is_xoauth2_alias (mechanism) ? "XOAUTH2" : mechanism, challenge); g_free (challenge); } else if (local_error) { d (fprintf (stderr, "[SMTP] SASL challenge failed: %s", local_error->message)); diff --git a/src/libebackend/e-server-side-source.c b/src/libebackend/e-server-side-source.c index 8ba51ef45..3a1fd6ae5 100644 --- a/src/libebackend/e-server-side-source.c +++ b/src/libebackend/e-server-side-source.c @@ -460,7 +460,10 @@ server_side_source_credentials_lookup_cb (GObject *source_object, g_strfreev (arg_credentials); } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { if (e_source_registry_debug_enabled ()) { - printf ("%s: Failed to lookup password: %s\n", G_STRFUNC, error ? error->message : "Unknown error"); + printf ("%s: Failed to lookup password for source %s (%s): %s\n", G_STRFUNC, + e_source_get_uid (E_SOURCE (data->source)), + e_source_get_display_name (E_SOURCE (data->source)), + error ? error->message : "Unknown error"); fflush (stdout); } diff --git a/src/libebackend/e-source-registry-server.c b/src/libebackend/e-source-registry-server.c index 738d1c459..db861308a 100644 --- a/src/libebackend/e-source-registry-server.c +++ b/src/libebackend/e-source-registry-server.c @@ -71,6 +71,8 @@ struct _ESourceRegistryServerPrivate { GMutex file_monitor_lock; GHashTable *file_monitor_events; /* gchar *uid ~> FileEventData * */ GSource *file_monitor_source; + + EOAuth2Services *oauth2_services; }; enum { @@ -724,6 +726,7 @@ source_registry_server_constructed (GObject *object) G_OBJECT_CLASS (e_source_registry_server_parent_class)->constructed (object); server->priv->credentials_provider = e_server_side_source_credentials_provider_new (server); + server->priv->oauth2_services = e_oauth2_services_new (); } static void @@ -776,6 +779,8 @@ source_registry_server_finalize (GObject *object) g_mutex_clear (&priv->orphans_lock); g_mutex_clear (&priv->file_monitor_lock); + g_clear_object (&priv->oauth2_services); + /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_source_registry_server_parent_class)-> finalize (object); @@ -1174,6 +1179,22 @@ e_source_registry_server_ref_credentials_provider (ESourceRegistryServer *server } /** + * e_source_registry_server_get_oauth2_services: + * @server: an #ESourceRegistryServer + * + * Returns: (transfer none): an #EOAuth2Services instance owned by @server + * + * Since: 3.28 + **/ +EOAuth2Services * +e_source_registry_server_get_oauth2_services (ESourceRegistryServer *server) +{ + g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL); + + return server->priv->oauth2_services; +} + +/** * e_source_registry_server_add_source: * @server: an #ESourceRegistryServer * @source: an #ESource diff --git a/src/libebackend/e-source-registry-server.h b/src/libebackend/e-source-registry-server.h index e1dd0a9d0..e53b33acb 100644 --- a/src/libebackend/e-source-registry-server.h +++ b/src/libebackend/e-source-registry-server.h @@ -112,6 +112,9 @@ EDBusServer * e_source_registry_server_new (void); ESourceCredentialsProvider * e_source_registry_server_ref_credentials_provider (ESourceRegistryServer *server); +EOAuth2Services * + e_source_registry_server_get_oauth2_services + (ESourceRegistryServer *server); void e_source_registry_server_add_source (ESourceRegistryServer *server, ESource *source); diff --git a/src/libebackend/e-webdav-collection-backend.c b/src/libebackend/e-webdav-collection-backend.c index 757fbc38a..024779b76 100644 --- a/src/libebackend/e-webdav-collection-backend.c +++ b/src/libebackend/e-webdav-collection-backend.c @@ -302,7 +302,6 @@ webdav_collection_backend_populate (ECollectionBackend *collection) } g_list_free_full (list, g_object_unref); - g_object_unref (server); source = e_backend_get_source (E_BACKEND (collection)); collection_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_COLLECTION); @@ -310,9 +309,28 @@ webdav_collection_backend_populate (ECollectionBackend *collection) if (e_source_get_enabled (source) && ( e_source_collection_get_calendar_enabled (collection_extension) || e_source_collection_get_contacts_enabled (collection_extension))) { - e_backend_schedule_credentials_required (E_BACKEND (collection), - E_SOURCE_CREDENTIALS_REASON_REQUIRED, NULL, 0, NULL, NULL, G_STRFUNC); + gboolean needs_credentials = TRUE; + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) { + ESourceAuthentication *auth_extension; + gchar *method; + + auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); + method = e_source_authentication_dup_method (auth_extension); + needs_credentials = g_strcmp0 (method, "OAuth2") != 0 && + !e_oauth2_services_is_oauth2_alias (e_source_registry_server_get_oauth2_services (server), method); + g_free (method); + } + + if (needs_credentials) { + e_backend_schedule_credentials_required (E_BACKEND (collection), + E_SOURCE_CREDENTIALS_REASON_REQUIRED, NULL, 0, NULL, NULL, G_STRFUNC); + } else { + e_backend_schedule_authenticate (E_BACKEND (collection), NULL); + } } + + g_object_unref (server); } static void diff --git a/src/libedataserver/CMakeLists.txt b/src/libedataserver/CMakeLists.txt index 3a16a578b..a16c43382 100644 --- a/src/libedataserver/CMakeLists.txt +++ b/src/libedataserver/CMakeLists.txt @@ -65,6 +65,10 @@ set(SOURCES e-memory.c e-module.c e-network-monitor.c + e-oauth2-service.c + e-oauth2-service-base.c + e-oauth2-service-google.c + e-oauth2-services.c e-operation-pool.c e-proxy.c e-secret-store.c @@ -86,7 +90,7 @@ set(SOURCES e-source-contacts.c e-source-credentials-provider.c e-source-credentials-provider-impl.c - e-source-credentials-provider-impl-google.c + e-source-credentials-provider-impl-oauth2.c e-source-credentials-provider-impl-password.c e-source-goa.c e-source-ldap.c @@ -149,6 +153,10 @@ set(HEADERS e-memory.h e-module.h e-network-monitor.h + e-oauth2-service.h + e-oauth2-service-base.h + e-oauth2-service-google.h + e-oauth2-services.h e-operation-pool.h e-proxy.h e-secret-store.h @@ -169,7 +177,7 @@ set(HEADERS e-source-contacts.h e-source-credentials-provider.h e-source-credentials-provider-impl.h - e-source-credentials-provider-impl-google.h + e-source-credentials-provider-impl-oauth2.h e-source-credentials-provider-impl-password.h e-source-enums.h e-source-extension.h @@ -243,7 +251,7 @@ target_compile_options(edataserver PUBLIC ${GCR_BASE_CFLAGS} ${GIO_UNIX_CFLAGS} ${ICU_CFLAGS} - ${GOOGLE_AUTH_CFLAGS} + ${OAUTH2_CFLAGS} ${LIBGDATA_CFLAGS} ) @@ -258,7 +266,7 @@ target_include_directories(edataserver PUBLIC ${GCR_BASE_INCLUDE_DIRS} ${GIO_UNIX_INCLUDE_DIRS} ${ICU_INCLUDE_DIRS} - ${GOOGLE_AUTH_INCLUDE_DIRS} + ${OAUTH2_INCLUDE_DIRS} ${LIBGDATA_INCLUDE_DIRS} ) @@ -268,7 +276,7 @@ target_link_libraries(edataserver ${GCR_BASE_LDFLAGS} ${GIO_UNIX_LDFLAGS} ${ICU_LDFLAGS} - ${GOOGLE_AUTH_LDFLAGS} + ${OAUTH2_LDFLAGS} ${LIBGDATA_LDFLAGS} ) diff --git a/src/libedataserver/e-data-server-util.c b/src/libedataserver/e-data-server-util.c index 750e18fdc..703413d16 100644 --- a/src/libedataserver/e-data-server-util.c +++ b/src/libedataserver/e-data-server-util.c @@ -35,7 +35,7 @@ #include "e-source.h" #include "e-source-authentication.h" #include "e-source-backend.h" -#include "e-source-credentials-provider-impl-google.h" +#include "e-source-collection.h" #include "e-source-enumtypes.h" #include "e-source-mail-identity.h" #include "e-source-mail-submission.h" @@ -2917,60 +2917,6 @@ e_util_get_source_full_name (ESourceRegistry *registry, return g_string_free (fullname, FALSE); } - -/** - * e_util_get_source_oauth2_access_token_sync: - * @source: an #ESource - * @credentials: an ENamedParameters - * @out_access_token: (allow-none) (out): return location for the access token, - * or %NULL - * @out_expires_in_seconds: (allow-none) (out): return location for the token expiry, - * or %NULL - * @cancellable: (allow-none): optional #GCancellable object, or %NULL - * @error: return location for a #GError, or %NULL - * - * Obtains the OAuth 2.0 access token for @source along with its expiry - * in seconds from the current time (or 0 if unknown). - * - * Free the returned access token with g_free() when finished with it. - * If an error occurs, the function will set @error and return %FALSE. - * - * Returns: %TRUE on success, %FALSE on error - **/ -gboolean -e_util_get_source_oauth2_access_token_sync (ESource *source, - const ENamedParameters *credentials, - gchar **out_access_token, - gint *out_expires_in_seconds, - GCancellable *cancellable, - GError **error) -{ - gchar *auth_method = NULL; - gboolean success = FALSE; - - g_return_val_if_fail (E_IS_SOURCE (source), FALSE); - - if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) { - ESourceAuthentication *extension; - - extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); - auth_method = e_source_authentication_dup_method (extension); - } - - if (g_strcmp0 (auth_method, "OAuth2") == 0) { - success = e_source_get_oauth2_access_token_sync ( - source, cancellable, out_access_token, - out_expires_in_seconds, error); - } else if (g_strcmp0 (auth_method, "Google") == 0) { - success = e_source_credentials_google_get_access_token_sync ( - source, credentials, out_access_token, out_expires_in_seconds, cancellable, error); - } - - g_free (auth_method); - - return success; -} - static gpointer unref_object_in_thread (gpointer ptr) { @@ -3131,3 +3077,92 @@ e_util_identity_can_send (ESourceRegistry *registry, return can_send; } + +/** + * e_util_can_use_collection_as_credential_source: + * @collection_source: (nullable): a collection #ESource, or %NULL + * @child_source: a children of @collection_source + * + * Checks whether the @collection_source can be used as a credential source + * for the @child_source. The relationship is not tested in the function. + * When the @collection_source is %NULL, then it simply returns %FALSE. + * + * Returns: whether @collection_source can be used as a credential source + * for @child_source, that is, whether they share credentials. + * + * Since: 3.28 + **/ +gboolean +e_util_can_use_collection_as_credential_source (ESource *collection_source, + ESource *child_source) +{ + gboolean can_use_collection = FALSE; + + if (collection_source) + g_return_val_if_fail (E_IS_SOURCE (collection_source), FALSE); + g_return_val_if_fail (E_IS_SOURCE (child_source), FALSE); + + if (collection_source && e_source_has_extension (collection_source, E_SOURCE_EXTENSION_COLLECTION)) { + /* Use the found parent collection source for credentials store only if + the child source doesn't have any authentication information, or this + information is not filled, or if either the host name or the user name + are the same with the collection source. + + This allows to create a collection of sources which has one source + (like message send) on a different server, thus this source uses + its own credentials. + */ + if (!e_source_has_extension (child_source, E_SOURCE_EXTENSION_AUTHENTICATION)) { + can_use_collection = TRUE; + } else if (e_source_has_extension (collection_source, E_SOURCE_EXTENSION_AUTHENTICATION)) { + ESourceAuthentication *auth_source, *auth_collection; + gchar *host_source, *host_collection; + + auth_source = e_source_get_extension (child_source, E_SOURCE_EXTENSION_AUTHENTICATION); + auth_collection = e_source_get_extension (collection_source, E_SOURCE_EXTENSION_AUTHENTICATION); + + host_source = e_source_authentication_dup_host (auth_source); + host_collection = e_source_authentication_dup_host (auth_collection); + + if (host_source && host_collection && g_ascii_strcasecmp (host_source, host_collection) == 0) { + gchar *username_source, *username_collection; + + username_source = e_source_authentication_dup_user (auth_source); + username_collection = e_source_authentication_dup_user (auth_collection); + + if (username_source && username_collection && g_ascii_strcasecmp (username_source, username_collection) == 0) { + can_use_collection = TRUE; + } else { + can_use_collection = !username_source || !*username_source; + } + + g_free (username_source); + g_free (username_collection); + } else { + /* Only one of them is filled, then use the collection; otherwise + both are filled and they do not match, thus do not use collection. */ + can_use_collection = (host_collection && *host_collection && (!host_source || !*host_source)) || + (host_source && *host_source && (!host_collection || !*host_collection)); + + if (can_use_collection) { + gchar *method_source, *method_collection; + + /* Also check the method; if different, then rather not use the collection */ + method_source = e_source_authentication_dup_method (auth_source); + method_collection = e_source_authentication_dup_method (auth_collection); + + can_use_collection = !method_source || !method_collection || + g_ascii_strcasecmp (method_source, method_collection) == 0; + + g_free (method_source); + g_free (method_collection); + } + } + + g_free (host_source); + g_free (host_collection); + } + } + + return can_use_collection; +} diff --git a/src/libedataserver/e-data-server-util.h b/src/libedataserver/e-data-server-util.h index a6d0925a0..20d203e6e 100644 --- a/src/libedataserver/e-data-server-util.h +++ b/src/libedataserver/e-data-server-util.h @@ -274,13 +274,6 @@ void e_type_traverse (GType parent_type, gchar * e_util_get_source_full_name (struct _ESourceRegistry *registry, struct _ESource *source); -gboolean e_util_get_source_oauth2_access_token_sync - (struct _ESource *source, - const ENamedParameters *credentials, - gchar **out_access_token, - gint *out_expires_in_seconds, - GCancellable *cancellable, - GError **error); void e_util_unref_in_thread (gpointer object); @@ -288,6 +281,9 @@ gchar * e_util_generate_uid (void); gboolean e_util_identity_can_send (struct _ESourceRegistry *registry, struct _ESource *identity_source); +gboolean e_util_can_use_collection_as_credential_source + (struct _ESource *collection_source, + struct _ESource *child_source); G_END_DECLS diff --git a/src/libedataserver/e-gdata-oauth2-authorizer.c b/src/libedataserver/e-gdata-oauth2-authorizer.c index 6c40e8d4c..2d2e34b2d 100644 --- a/src/libedataserver/e-gdata-oauth2-authorizer.c +++ b/src/libedataserver/e-gdata-oauth2-authorizer.c @@ -258,8 +258,8 @@ e_gdata_oauth2_authorizer_refresh_authorization (GDataAuthorizer *authorizer, g_mutex_lock (&mutex); - success = e_util_get_source_oauth2_access_token_sync (source, oauth2_authorizer->priv->credentials, - &access_token, &expires_in_seconds, cancellable, error); + success = e_source_get_oauth2_access_token_sync (source, cancellable, + &access_token, &expires_in_seconds, error); /* Returned token is the same, thus no refresh happened, thus rather fail. */ if (access_token && g_strcmp0 (access_token, oauth2_authorizer->priv->access_token) == 0) { diff --git a/src/libedataserver/e-oauth2-service-base.c b/src/libedataserver/e-oauth2-service-base.c new file mode 100644 index 000000000..ae4ddcf05 --- /dev/null +++ b/src/libedataserver/e-oauth2-service-base.c @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/** + * SECTION: e-oauth2-service-base + * @include: libebackend/libebackend.h + * @short_description: An abstract base class for #EOAuth2Service implementations + * + * An abstract base class, which can be used by any #EOAuth2Service + * implementation. It registers itself to #EOAuth2Services at the end + * of its constructed method. The descendant implements the #EOAuth2ServiceInterface. + **/ + +#include "evolution-data-server-config.h" + +#include "e-extension.h" +#include "e-oauth2-services.h" + +#include "e-oauth2-service-base.h" + +G_DEFINE_ABSTRACT_TYPE (EOAuth2ServiceBase, e_oauth2_service_base, E_TYPE_EXTENSION) + +static void +oauth2_service_base_constructed (GObject *object) +{ + EExtensible *extensible; + + extensible = e_extension_get_extensible (E_EXTENSION (object)); + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_oauth2_service_base_parent_class)->constructed (object); + + e_oauth2_services_add (E_OAUTH2_SERVICES (extensible), E_OAUTH2_SERVICE (object)); +} + +static void +e_oauth2_service_base_class_init (EOAuth2ServiceBaseClass *klass) +{ + GObjectClass *object_class; + EExtensionClass *extension_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->constructed = oauth2_service_base_constructed; + + extension_class = E_EXTENSION_CLASS (klass); + extension_class->extensible_type = E_TYPE_OAUTH2_SERVICES; +} + +static void +e_oauth2_service_base_init (EOAuth2ServiceBase *base) +{ +} diff --git a/src/libedataserver/e-oauth2-service-base.h b/src/libedataserver/e-oauth2-service-base.h new file mode 100644 index 000000000..5f6ecd4be --- /dev/null +++ b/src/libedataserver/e-oauth2-service-base.h @@ -0,0 +1,63 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#if !defined (__LIBEDATASERVER_H_INSIDE__) && !defined (LIBEDATASERVER_COMPILATION) +#error "Only <libedataserver/libedataserver.h> should be included directly." +#endif + +#ifndef E_OAUTH2_SERVICE_BASE_H +#define E_OAUTH2_SERVICE_BASE_H + +#include <libedataserver/e-extension.h> + +/* Standard GObject macros */ +#define E_TYPE_OAUTH2_SERVICE_BASE \ + (e_oauth2_service_base_get_type ()) +#define E_OAUTH2_SERVICE_BASE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_OAUTH2_SERVICE_BASE, EOAuth2ServiceBase)) +#define E_OAUTH2_SERVICE_BASE_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_OAUTH2_SERVICE_BASE, EOAuth2ServiceBaseClass)) +#define E_IS_OAUTH2_SERVICE_BASE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_OAUTH2_SERVICE_BASE)) +#define E_IS_OAUTH2_SERVICE_BASE_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_OAUTH2_SERVICE_BASE)) +#define E_OAUTH2_SERVICE_BASE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_OAUTH2_SERVICE_BASE, EOAuth2ServiceBaseClass)) + +G_BEGIN_DECLS + +typedef struct _EOAuth2ServiceBase EOAuth2ServiceBase; +typedef struct _EOAuth2ServiceBaseClass EOAuth2ServiceBaseClass; + +struct _EOAuth2ServiceBase { + EExtension parent; +}; + +struct _EOAuth2ServiceBaseClass { + EExtensionClass parent_class; +}; + +GType e_oauth2_service_base_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* E_OAUTH2_SERVICE_BASE_H */ diff --git a/src/libedataserver/e-oauth2-service-google.c b/src/libedataserver/e-oauth2-service-google.c new file mode 100644 index 000000000..34f7dee8a --- /dev/null +++ b/src/libedataserver/e-oauth2-service-google.c @@ -0,0 +1,157 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "evolution-data-server-config.h" + +#include <glib/gi18n-lib.h> + +#include "e-oauth2-service.h" +#include "e-oauth2-service-base.h" + +#include "e-oauth2-service-google.h" + +/* https://developers.google.com/identity/protocols/OAuth2InstalledApp */ + +/* Forward Declarations */ +static void e_oauth2_service_google_oauth2_service_init (EOAuth2ServiceInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (EOAuth2ServiceGoogle, e_oauth2_service_google, E_TYPE_OAUTH2_SERVICE_BASE, + G_IMPLEMENT_INTERFACE (E_TYPE_OAUTH2_SERVICE, e_oauth2_service_google_oauth2_service_init)) + +static gboolean +e_oauth2_service_google_guess_can_process (EOAuth2Service *service, + const gchar *protocol, + const gchar *hostname) +{ + return hostname && ( + e_util_utf8_strstrcase (hostname, ".google.com") || + e_util_utf8_strstrcase (hostname, ".googlemail.com") || + e_util_utf8_strstrcase (hostname, ".googleusercontent.com") || + e_util_utf8_strstrcase (hostname, ".gmail.com")); +} + +static const gchar * +e_oauth2_service_google_get_name (EOAuth2Service *service) +{ + return "Google"; +} + +static const gchar * +e_oauth2_service_google_get_display_name (EOAuth2Service *service) +{ + /* Translators: This is a user-visible string, display name of an OAuth2 service. */ + return C_("OAuth2Service", "Google"); +} + +static const gchar * +e_oauth2_service_google_get_client_id (EOAuth2Service *service) +{ + return GOOGLE_CLIENT_ID; +} + +static const gchar * +e_oauth2_service_google_get_client_secret (EOAuth2Service *service) +{ + return GOOGLE_CLIENT_SECRET; +} + +static const gchar * +e_oauth2_service_google_get_authentication_uri (EOAuth2Service *service) +{ + return "https://accounts.google.com/o/oauth2/auth"; +} + +static const gchar * +e_oauth2_service_google_get_refresh_uri (EOAuth2Service *service) +{ + return "https://www.googleapis.com/oauth2/v3/token"; +} + +static void +e_oauth2_service_google_prepare_authentication_uri_query (EOAuth2Service *service, + ESource *source, + GHashTable *uri_query) +{ + const gchar *GOOGLE_SCOPE = + /* GMail IMAP and SMTP access */ + "https://mail.google.com/ " + /* Google Calendar API (CalDAV and GData) */ + "https://www.googleapis.com/auth/calendar " + /* Google Contacts API (GData) */ + "https://www.google.com/m8/feeds/ " + /* Google Contacts API (CardDAV) - undocumented */ + "https://www.googleapis.com/auth/carddav " + /* Google Tasks - undocumented */ + "https://www.googleapis.com/auth/tasks"; + + g_return_if_fail (uri_query != NULL); + + #define add_to_form(name, value) g_hash_table_insert (uri_query, g_strdup (name), g_strdup (value)) + + add_to_form ("scope", GOOGLE_SCOPE); + add_to_form ("include_granted_scopes", "false"); + + #undef add_to_form +} + +static gboolean +e_oauth2_service_google_extract_authorization_code (EOAuth2Service *service, + const gchar *page_title, + const gchar *page_uri, + gchar **out_authorization_code) +{ + g_return_val_if_fail (out_authorization_code != NULL, FALSE); + + *out_authorization_code = NULL; + + if (!page_title || !*page_title) + return FALSE; + + /* Known response, but no authorization code */ + if (g_ascii_strncasecmp (page_title, "Denied ", 7) == 0) + return TRUE; + + if (g_ascii_strncasecmp (page_title, "Success code=", 13) == 0) { + *out_authorization_code = g_strdup (page_title + 13); + return TRUE; + } + + return FALSE; +} + +static void +e_oauth2_service_google_oauth2_service_init (EOAuth2ServiceInterface *iface) +{ + iface->guess_can_process = e_oauth2_service_google_guess_can_process; + iface->get_name = e_oauth2_service_google_get_name; + iface->get_display_name = e_oauth2_service_google_get_display_name; + iface->get_client_id = e_oauth2_service_google_get_client_id; + iface->get_client_secret = e_oauth2_service_google_get_client_secret; + iface->get_authentication_uri = e_oauth2_service_google_get_authentication_uri; + iface->get_refresh_uri = e_oauth2_service_google_get_refresh_uri; + iface->prepare_authentication_uri_query = e_oauth2_service_google_prepare_authentication_uri_query; + iface->extract_authorization_code = e_oauth2_service_google_extract_authorization_code; +} + +static void +e_oauth2_service_google_class_init (EOAuth2ServiceGoogleClass *klass) +{ +} + +static void +e_oauth2_service_google_init (EOAuth2ServiceGoogle *oauth2_google) +{ +} diff --git a/src/libedataserver/e-oauth2-service-google.h b/src/libedataserver/e-oauth2-service-google.h new file mode 100644 index 000000000..c37f9c459 --- /dev/null +++ b/src/libedataserver/e-oauth2-service-google.h @@ -0,0 +1,62 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#if !defined (__LIBEDATASERVER_H_INSIDE__) && !defined (LIBEDATASERVER_COMPILATION) +#error "Only <libedataserver/libedataserver.h> should be included directly." +#endif + +#ifndef E_OAUTH2_SERVICE_GOOGLE_H +#define E_OAUTH2_SERVICE_GOOGLE_H + +#include <libedataserver/e-oauth2-service-base.h> + +/* Standard GObject macros */ +#define E_TYPE_OAUTH2_SERVICE_GOOGLE \ + (e_oauth2_service_google_get_type ()) +#define E_OAUTH2_SERVICE_GOOGLE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_OAUTH2_SERVICE_GOOGLE, EOAuth2ServiceGoogle)) +#define E_OAUTH2_SERVICE_GOOGLE_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_OAUTH2_SERVICE_GOOGLE, EOAuth2ServiceGoogleClass)) +#define E_IS_OAUTH2_SERVICE_GOOGLE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_OAUTH2_SERVICE_GOOGLE)) +#define E_IS_OAUTH2_SERVICE_GOOGLE_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_OAUTH2_SERVICE_GOOGLE)) +#define E_OAUTH2_SERVICE_GOOGLE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_OAUTH2_SERVICE_GOOGLE, EOAuth2ServiceGoogleClass)) + +G_BEGIN_DECLS + +typedef struct _EOAuth2ServiceGoogle EOAuth2ServiceGoogle; +typedef struct _EOAuth2ServiceGoogleClass EOAuth2ServiceGoogleClass; + +struct _EOAuth2ServiceGoogle { + EOAuth2ServiceBase parent; +}; + +struct _EOAuth2ServiceGoogleClass { + EOAuth2ServiceBaseClass parent_class; +}; + +GType e_oauth2_service_google_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* E_OAUTH2_SERVICE_GOOGLE_H */ diff --git a/src/libedataserver/e-oauth2-service.c b/src/libedataserver/e-oauth2-service.c new file mode 100644 index 000000000..5c602ed53 --- /dev/null +++ b/src/libedataserver/e-oauth2-service.c @@ -0,0 +1,1379 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/** + * SECTION: e-oauth2-service + * @include: libedataserver/libedataserver.h + * @short_description: An interface for an OAuth2 service + * + * An interface for an OAuth2 service. Any descendant might be defined + * as an extension of #EOAuth2Services and it should add itself into it + * with e_oauth2_services_add(). To make it easier, an #EOAuth2ServiceBase + * is provided for convenience. + **/ + +#include "evolution-data-server-config.h" + +#include <string.h> +#include <glib/gi18n-lib.h> + +#ifdef ENABLE_OAUTH2 +#include <json-glib/json-glib.h> +#endif + +#include "e-secret-store.h" +#include "e-soup-ssl-trust.h" +#include "e-source-authentication.h" +#include "e-source-goa.h" +#include "e-source-uoa.h" + +#include "e-oauth2-service.h" + +#define DEFAULT_REDIRECT_URI "urn:ietf:wg:oauth:2.0:oob" + +G_DEFINE_INTERFACE (EOAuth2Service, e_oauth2_service, G_TYPE_OBJECT) + +static gboolean +eos_default_can_process (EOAuth2Service *service, + ESource *source) +{ + gboolean can = FALSE; + + g_return_val_if_fail (E_IS_SOURCE (source), FALSE); + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_GOA) || + e_source_has_extension (source, E_SOURCE_EXTENSION_UOA)) { + return FALSE; + } + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) { + ESourceAuthentication *auth_extension; + gchar *method; + + auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); + method = e_source_authentication_dup_method (auth_extension); + + if (g_strcmp0 (method, e_oauth2_service_get_name (service)) == 0) { + g_free (method); + return TRUE; + } + + g_free (method); + } + + return can; +} + +static gboolean +eos_default_guess_can_process (EOAuth2Service *service, + const gchar *protocol, + const gchar *hostname) +{ + gboolean can = FALSE; + GSettings *settings; + gchar **values; + gint ii, name_len, hostname_len; + const gchar *name; + + if (!hostname || !*hostname) + return FALSE; + + name = e_oauth2_service_get_name (service); + g_return_val_if_fail (name != NULL, FALSE); + name_len = strlen (name); + hostname_len = strlen (hostname); + + settings = g_settings_new ("org.gnome.evolution-data-server"); + values = g_settings_get_strv (settings, "oauth2-services-hint"); + g_object_unref (settings); + + for (ii = 0; !can && values && values[ii]; ii++) { + const gchar *line = values[ii]; + gint len; + + if (!g_str_has_prefix (line, name) || + (line[name_len] != ':' && line[name_len] != '-')) + continue; + + if (line[name_len] == '-') { + len = protocol ? strlen (protocol) : -1; + + if (len <= 0 || g_ascii_strncasecmp (line + name_len + 1, protocol, len) != 0 || + line[name_len + len + 1] != ':') + continue; + + line += name_len + len + 2; + } else { /* line[name_len] == ':' */ + line += name_len + 1; + } + + while (line && *line) { + if (g_ascii_strncasecmp (line, hostname, hostname_len) == 0 && + (line[hostname_len] == ',' || line[hostname_len] == '\0')) { + can = TRUE; + break; + } + + line = strchr (line, ','); + if (line) + line++; + } + } + + g_strfreev (values); + + return can; +} + +static void +eos_default_prepare_authentication_uri_query (EOAuth2Service *service, + ESource *source, + GHashTable *uri_query) +{ + #define add_to_form(name, value) g_hash_table_insert (uri_query, g_strdup (name), value) + + add_to_form ("response_type", g_strdup ("code")); + add_to_form ("client_id", g_strdup (e_oauth2_service_get_client_id (service))); + add_to_form ("redirect_uri", g_strdup (DEFAULT_REDIRECT_URI)); + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) { + ESourceAuthentication *auth_extension; + gchar *user; + + auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); + user = e_source_authentication_dup_user (auth_extension); + + if (user && *user) + add_to_form ("login_hint", user); + else + g_free (user); + } + + #undef add_to_form +} + +static void +eos_default_prepare_get_token_form (EOAuth2Service *service, + const gchar *authorization_code, + GHashTable *form) +{ + #define add_to_form(name, value) g_hash_table_insert (form, g_strdup (name), g_strdup (value)) + + add_to_form ("code", authorization_code); + add_to_form ("client_id", e_oauth2_service_get_client_id (service)); + add_to_form ("client_secret", e_oauth2_service_get_client_secret (service)); + add_to_form ("redirect_uri", DEFAULT_REDIRECT_URI); + add_to_form ("grant_type", "authorization_code"); + + #undef add_to_form +} + +static void +eos_default_prepare_get_token_message (EOAuth2Service *service, + SoupMessage *message) +{ +} + +static void +eos_default_prepare_refresh_token_form (EOAuth2Service *service, + const gchar *refresh_token, + GHashTable *form) +{ + #define add_to_form(name, value) g_hash_table_insert (form, g_strdup (name), g_strdup (value)) + + add_to_form ("refresh_token", refresh_token); + add_to_form ("client_id", e_oauth2_service_get_client_id (service)); + add_to_form ("client_secret", e_oauth2_service_get_client_secret (service)); + add_to_form ("grant_type", "refresh_token"); + + #undef add_to_form +} + +static void +eos_default_prepare_refresh_token_message (EOAuth2Service *service, + SoupMessage *message) +{ +} + +static void +e_oauth2_service_default_init (EOAuth2ServiceInterface *iface) +{ + iface->can_process = eos_default_can_process; + iface->guess_can_process = eos_default_guess_can_process; + iface->prepare_authentication_uri_query = eos_default_prepare_authentication_uri_query; + iface->prepare_get_token_form = eos_default_prepare_get_token_form; + iface->prepare_get_token_message = eos_default_prepare_get_token_message; + iface->prepare_refresh_token_form = eos_default_prepare_refresh_token_form; + iface->prepare_refresh_token_message = eos_default_prepare_refresh_token_message; +} + +/** + * e_oauth2_service_can_process: + * @service: an #EOAuth2Service + * @source: (nullable): an #ESource, or %NULL + * + * Checks whether the @service can be used with the given @source. + * + * The default implementation checks whether the @source has an #ESourceAuthentication + * extension and when its method matches e_oauth2_service_get_name(), then it automatically + * returns %TRUE. Contrary, when the @source contains GNOME Online Accounts or Ubuntu + * Online Accounts extension, then it returns %FALSE. + * + * The default implementation is tried always as the first and when it fails, then + * the descendant's implementation is called. + * + * Returns: Whether the @service can be used for the given @source + * + * Since: 3.28 + **/ +gboolean +e_oauth2_service_can_process (EOAuth2Service *service, + ESource *source) +{ + EOAuth2ServiceInterface *iface; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE); + g_return_val_if_fail (E_IS_SOURCE (source), FALSE); + + iface = E_OAUTH2_SERVICE_GET_INTERFACE (service); + g_return_val_if_fail (iface != NULL, FALSE); + g_return_val_if_fail (iface->can_process != NULL, FALSE); + + if (eos_default_can_process (service, source)) + return TRUE; + + return iface->can_process != eos_default_can_process && + iface->can_process (service, source); +} + +/** + * e_oauth2_service_guess_can_process: + * @service: an #EOAuth2Service + * @protocol: (nullable): a protocol to search the service for, like "imap", or %NULL + * @hostname: (nullable): a host name to search the service for, like "server.example.com", or %NULL + * + * Checks whether the @service can be used with the given @protocol and/or @hostname. + * Any of @protocol and @hostname can be %NULL, but not both. It's up to each implementer + * to decide, which of the arguments are important and whether all or only any of them + * can be required. + * + * The function is meant to check whether the @service can be offered + * for example when configuring a new account. The real usage is + * determined by e_oauth2_service_can_process(). + * + * The default implementation consults org.gnome.evolution-data-server.oauth2-services-hint + * GSettings key against given hostname. See its description for more information. + * + * The default implementation is tried always as the first and when it fails, then + * the descendant's implementation is called. + * + * Returns: Whether the @service can be used for the given arguments + * + * Since: 3.28 + **/ +gboolean +e_oauth2_service_guess_can_process (EOAuth2Service *service, + const gchar *protocol, + const gchar *hostname) +{ + EOAuth2ServiceInterface *iface; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE); + g_return_val_if_fail (protocol || hostname, FALSE); + + iface = E_OAUTH2_SERVICE_GET_INTERFACE (service); + g_return_val_if_fail (iface != NULL, FALSE); + g_return_val_if_fail (iface->guess_can_process != NULL, FALSE); + + if (eos_default_guess_can_process (service, protocol, hostname)) + return TRUE; + + return iface->guess_can_process != eos_default_guess_can_process && + iface->guess_can_process (service, protocol, hostname); +} + +/** + * e_oauth2_service_get_name: + * @service: an #EOAuth2Service + * + * Returns a unique name of the service. It can be named for example + * by the server or the company from which it receives the OAuth2 + * token and where it refreshes it, like "Company" for login.company.com. + * + * Returns: the name of the @service + * + * Since: 3.28 + **/ +const gchar * +e_oauth2_service_get_name (EOAuth2Service *service) +{ + EOAuth2ServiceInterface *iface; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), NULL); + + iface = E_OAUTH2_SERVICE_GET_INTERFACE (service); + g_return_val_if_fail (iface != NULL, NULL); + g_return_val_if_fail (iface->get_name != NULL, NULL); + + return iface->get_name (service); +} + +/** + * e_oauth2_service_get_display_name: + * @service: an #EOAuth2Service + * + * Returns a human readable name of the service. This is similar to + * e_oauth2_service_get_name(), except this string should be localized, + * because it will be used in user-visible strings. + * + * Returns: the display name of the @service + * + * Since: 3.28 + **/ +const gchar * +e_oauth2_service_get_display_name (EOAuth2Service *service) +{ + EOAuth2ServiceInterface *iface; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), NULL); + + iface = E_OAUTH2_SERVICE_GET_INTERFACE (service); + g_return_val_if_fail (iface != NULL, NULL); + g_return_val_if_fail (iface->get_display_name != NULL, NULL); + + return iface->get_display_name (service); +} + +/** + * e_oauth2_service_get_client_id: + * @service: an #EOAuth2Service + * + * Returns: application client ID, as provided by the server + * + * Since: 3.28 + **/ +const gchar * +e_oauth2_service_get_client_id (EOAuth2Service *service) +{ + EOAuth2ServiceInterface *iface; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), NULL); + + iface = E_OAUTH2_SERVICE_GET_INTERFACE (service); + g_return_val_if_fail (iface != NULL, NULL); + g_return_val_if_fail (iface->get_client_id != NULL, NULL); + + return iface->get_client_id (service); +} + +/** + * e_oauth2_service_get_client_secret: + * @service: an #EOAuth2Service + * + * Returns: application client secret, as provided by the server + * + * Since: 3.28 + **/ +const gchar * +e_oauth2_service_get_client_secret (EOAuth2Service *service) +{ + EOAuth2ServiceInterface *iface; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), NULL); + + iface = E_OAUTH2_SERVICE_GET_INTERFACE (service); + g_return_val_if_fail (iface != NULL, NULL); + g_return_val_if_fail (iface->get_client_secret != NULL, NULL); + + return iface->get_client_secret (service); +} + +/** + * e_oauth2_service_get_authentication_uri: + * @service: an #EOAuth2Service + * + * Returns: an authentication URI, to be used to obtain + * the authentication code + * + * Since: 3.28 + **/ +const gchar * +e_oauth2_service_get_authentication_uri (EOAuth2Service *service) +{ + EOAuth2ServiceInterface *iface; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), NULL); + + iface = E_OAUTH2_SERVICE_GET_INTERFACE (service); + g_return_val_if_fail (iface != NULL, NULL); + g_return_val_if_fail (iface->get_authentication_uri != NULL, NULL); + + return iface->get_authentication_uri (service); +} + +/** + * e_oauth2_service_get_refresh_uri: + * @service: an #EOAuth2Service + * + * Returns: a URI to be used to refresh the authentication token + * + * Since: 3.28 + **/ +const gchar * +e_oauth2_service_get_refresh_uri (EOAuth2Service *service) +{ + EOAuth2ServiceInterface *iface; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), NULL); + + iface = E_OAUTH2_SERVICE_GET_INTERFACE (service); + g_return_val_if_fail (iface != NULL, NULL); + g_return_val_if_fail (iface->get_refresh_uri != NULL, NULL); + + return iface->get_refresh_uri (service); +} + +/** + * e_oauth2_service_prepare_authentication_uri_query: + * @service: an #EOAuth2Service + * @source: an #ESource containing information about the user and such + * @uri_query: (element-type utf8 utf8): query for the URI to use + * + * The @service can change what arguments are passed in the authentication URI + * in this method. The default implementation sets some values too, namely + * "response_type", "client_id", "redirect_uri" and "login_hint", if available + * in the @source. These parameters are always provided, even when the interface + * implementer overrides this method. + * + * The @uri_query hash table expects both key and value to be newly allocated + * strings, which will be freed together with the hash table or when the key + * is replaced. + * + * Since: 3.28 + **/ +void +e_oauth2_service_prepare_authentication_uri_query (EOAuth2Service *service, + ESource *source, + GHashTable *uri_query) +{ + EOAuth2ServiceInterface *iface; + + g_return_if_fail (E_IS_OAUTH2_SERVICE (service)); + g_return_if_fail (E_IS_SOURCE (source)); + g_return_if_fail (uri_query != NULL); + + eos_default_prepare_authentication_uri_query (service, source, uri_query); + + iface = E_OAUTH2_SERVICE_GET_INTERFACE (service); + g_return_if_fail (iface != NULL); + g_return_if_fail (iface->prepare_authentication_uri_query != NULL); + + if (iface->prepare_authentication_uri_query != eos_default_prepare_authentication_uri_query) + iface->prepare_authentication_uri_query (service, source, uri_query); +} + +/** + * e_oauth2_service_extract_authorization_code: + * @service: an #EOAuth2Service + * @page_title: a web page title + * @page_uri: a web page URI + * @out_authorization_code: (out) (transfer full): the extracted authorization code + * + * Tries to extract an authorization code from a web page provided by the server. + * The function can be called multiple times, whenever the page load is finished. + * + * There can happen three states: 1) either the @service cannot determine + * the authentication code from the @page_title nor @page_uri, then the %FALSE is + * returned and the @out_authorization_code is left untouched; or 2) the server + * reported a failure, in which case the function returns %TRUE and lefts + * the @out_authorization_code untouched; or 3) the @service could extract + * the authentication code from the given arguments, then the function + * returns %TRUE and sets the received authorization code to @out_authorization_code. + * + * Returns: whether could recognized successful or failed server response. + * The @out_authorization_code is populated on success too. + * + * Since: 3.28 + **/ +gboolean +e_oauth2_service_extract_authorization_code (EOAuth2Service *service, + const gchar *page_title, + const gchar *page_uri, + gchar **out_authorization_code) +{ + EOAuth2ServiceInterface *iface; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE); + + iface = E_OAUTH2_SERVICE_GET_INTERFACE (service); + g_return_val_if_fail (iface != NULL, FALSE); + g_return_val_if_fail (iface->extract_authorization_code != NULL, FALSE); + + return iface->extract_authorization_code (service, page_title, page_uri, out_authorization_code); +} + +/** + * e_oauth2_service_prepare_get_token_form: + * @service: an #EOAuth2Service + * @authorization_code: authorization code, as returned from e_oauth2_service_extract_authorization_code() + * @form: (element-type utf8 utf8): form parameters to be used in the POST request + * + * Sets additional form parameters to be used in the POST request when requesting + * access token after successfully obtained authorization code. + * The default implementation sets some values too, namely + * "code", "client_id", "client_secret", "redirect_uri" and "grant_type". + * These parameters are always provided, even when the interface implementer overrides this method. + * + * The @form hash table expects both key and value to be newly allocated + * strings, which will be freed together with the hash table or when the key + * is replaced. + * + * Since: 3.28 + **/ +void +e_oauth2_service_prepare_get_token_form (EOAuth2Service *service, + const gchar *authorization_code, + GHashTable *form) +{ + EOAuth2ServiceInterface *iface; + + g_return_if_fail (E_IS_OAUTH2_SERVICE (service)); + g_return_if_fail (authorization_code != NULL); + g_return_if_fail (form != NULL); + + eos_default_prepare_get_token_form (service, authorization_code, form); + + iface = E_OAUTH2_SERVICE_GET_INTERFACE (service); + g_return_if_fail (iface != NULL); + g_return_if_fail (iface->prepare_get_token_form != NULL); + + if (iface->prepare_get_token_form != eos_default_prepare_get_token_form) + iface->prepare_get_token_form (service, authorization_code, form); +} + +/** + * e_oauth2_service_prepare_get_token_message: + * @service: an #EOAuth2Service + * @message: a #SoupMessage + * + * The @service can change the @message before it's sent to + * the e_oauth2_service_get_authentication_uri(), with POST data + * being provided by e_oauth2_service_prepare_get_token_form(). + * The default implementation does nothing with the @message. + * + * Since: 3.28 + **/ +void +e_oauth2_service_prepare_get_token_message (EOAuth2Service *service, + SoupMessage *message) +{ + EOAuth2ServiceInterface *iface; + + g_return_if_fail (E_IS_OAUTH2_SERVICE (service)); + g_return_if_fail (SOUP_IS_MESSAGE (message)); + + iface = E_OAUTH2_SERVICE_GET_INTERFACE (service); + g_return_if_fail (iface != NULL); + g_return_if_fail (iface->prepare_get_token_message != NULL); + + iface->prepare_get_token_message (service, message); +} + +/** + * e_oauth2_service_prepare_refresh_token_form: + * @service: an #EOAuth2Service + * @refresh_token: a refresh token to be used + * @form: (element-type utf8 utf8): form parameters to be used in the POST request + * + * Sets additional form parameters to be used in the POST request when requesting + * to refresh an access token. + * The default implementation sets some values too, namely + * "refresh_token", "client_id", "client_secret" and "grant_type". + * These parameters are always provided, even when the interface implementer overrides this method. + * + * The @form hash table expects both key and value to be newly allocated + * strings, which will be freed together with the hash table or when the key + * is replaced. + * + * Since: 3.28 + **/ +void +e_oauth2_service_prepare_refresh_token_form (EOAuth2Service *service, + const gchar *refresh_token, + GHashTable *form) +{ + EOAuth2ServiceInterface *iface; + + g_return_if_fail (E_IS_OAUTH2_SERVICE (service)); + g_return_if_fail (refresh_token != NULL); + g_return_if_fail (form != NULL); + + eos_default_prepare_refresh_token_form (service, refresh_token, form); + + iface = E_OAUTH2_SERVICE_GET_INTERFACE (service); + g_return_if_fail (iface != NULL); + g_return_if_fail (iface->prepare_refresh_token_form != NULL); + + if (iface->prepare_refresh_token_form != eos_default_prepare_refresh_token_form) + iface->prepare_refresh_token_form (service, refresh_token, form); +} + +/** + * e_oauth2_service_prepare_refresh_token_message: + * @service: an #EOAuth2Service + * + * The @service can change the @message before it's sent to + * the e_oauth2_service_get_refresh_uri(), with POST data + * being provided by e_oauth2_service_prepare_refresh_token_form(). + * The default implementation does nothing with the @message. + * + * Since: 3.28 + **/ +void +e_oauth2_service_prepare_refresh_token_message (EOAuth2Service *service, + SoupMessage *message) +{ + EOAuth2ServiceInterface *iface; + + g_return_if_fail (E_IS_OAUTH2_SERVICE (service)); + g_return_if_fail (SOUP_IS_MESSAGE (message)); + + iface = E_OAUTH2_SERVICE_GET_INTERFACE (service); + g_return_if_fail (iface != NULL); + g_return_if_fail (iface->prepare_refresh_token_message != NULL); + + iface->prepare_refresh_token_message (service, message); +} + +static SoupSession * +eos_create_soup_session (EOAuth2ServiceRefSourceFunc ref_source, + gpointer ref_source_user_data, + ESource *source) +{ + ESourceAuthentication *auth_extension; + ESource *proxy_source = NULL; + SoupSession *session; + gchar *uid; + + session = soup_session_new (); + g_object_set ( + session, + SOUP_SESSION_TIMEOUT, 90, + SOUP_SESSION_SSL_STRICT, TRUE, + SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, + SOUP_SESSION_ACCEPT_LANGUAGE_AUTO, TRUE, + NULL); + + if (!e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) + return session; + + auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); + uid = e_source_authentication_dup_proxy_uid (auth_extension); + if (uid) { + proxy_source = ref_source (ref_source_user_data, uid); + + g_free (uid); + } + + if (proxy_source) { + GProxyResolver *proxy_resolver; + + proxy_resolver = G_PROXY_RESOLVER (proxy_source); + if (g_proxy_resolver_is_supported (proxy_resolver)) + g_object_set (session, SOUP_SESSION_PROXY_RESOLVER, proxy_resolver, NULL); + + g_object_unref (proxy_source); + } + + return session; +} + +static SoupMessage * +eos_create_soup_message (ESource *source, + const gchar *uri, + GHashTable *post_form) +{ + SoupMessage *message; + gchar *post_data; + + g_return_val_if_fail (E_IS_SOURCE (source), NULL); + g_return_val_if_fail (uri != NULL, NULL); + g_return_val_if_fail (post_form != NULL, NULL); + + message = soup_message_new (SOUP_METHOD_POST, uri); + g_return_val_if_fail (message != NULL, NULL); + + post_data = soup_form_encode_hash (post_form); + if (!post_data) { + g_warn_if_fail (post_data != NULL); + g_object_unref (message); + + return NULL; + } + + soup_message_set_request (message, "application/x-www-form-urlencoded", + SOUP_MEMORY_TAKE, post_data, strlen (post_data)); + + e_soup_ssl_trust_connect (message, source); + + soup_message_headers_append (message->request_headers, "Connection", "close"); + + return message; +} + +static void +eos_abort_session_cb (GCancellable *cancellable, + SoupSession *session) +{ + soup_session_abort (session); +} + +static gboolean +eos_send_message (SoupSession *session, + SoupMessage *message, + gchar **out_response_body, + GCancellable *cancellable, + GError **error) +{ + guint status_code = SOUP_STATUS_CANCELLED; + gboolean success = FALSE; + + g_return_val_if_fail (SOUP_IS_SESSION (session), FALSE); + g_return_val_if_fail (SOUP_IS_MESSAGE (message), FALSE); + g_return_val_if_fail (out_response_body != NULL, FALSE); + + if (!g_cancellable_set_error_if_cancelled (cancellable, error)) { + gulong cancel_handler_id = 0; + + if (cancellable) + cancel_handler_id = g_cancellable_connect (cancellable, G_CALLBACK (eos_abort_session_cb), session, NULL); + + status_code = soup_session_send_message (session, message); + + if (cancel_handler_id) + g_cancellable_disconnect (cancellable, cancel_handler_id); + } + + if (SOUP_STATUS_IS_SUCCESSFUL (status_code)) { + if (message->response_body) { + *out_response_body = g_strndup (message->response_body->data, message->response_body->length); + success = TRUE; + } else { + status_code = SOUP_STATUS_MALFORMED; + } + } else if (status_code != SOUP_STATUS_CANCELLED) { + g_set_error_literal (error, SOUP_HTTP_ERROR, message->status_code, message->reason_phrase); + } + + return success; +} + +static gboolean +eos_generate_secret_uid (EOAuth2Service *service, + ESource *source, + gchar **out_uid) +{ + ESourceAuthentication *authentication_extension; + gchar *user; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE); + g_return_val_if_fail (E_IS_SOURCE (source), FALSE); + + if (out_uid) + *out_uid = NULL; + + if (!e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) + return FALSE; + + authentication_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); + user = e_source_authentication_dup_user (authentication_extension); + if (!user || !*user) { + g_free (user); + return FALSE; + } + + if (out_uid) + *out_uid = g_strdup_printf ("OAuth2::%s[%s]", e_oauth2_service_get_name (service), user); + + g_free (user); + + return TRUE; +} + +static gboolean +eos_encode_to_secret (gchar **out_secret, + const gchar *key1_name, + const gchar *value1, + ...) G_GNUC_NULL_TERMINATED; + +static gboolean +eos_encode_to_secret (gchar **out_secret, + const gchar *key1_name, + const gchar *value1, + ...) +{ +#ifdef ENABLE_OAUTH2 + JsonBuilder *builder; + JsonNode *node; + const gchar *key, *value; + va_list va; + + g_return_val_if_fail (out_secret != NULL, FALSE); + g_return_val_if_fail (key1_name != NULL, FALSE); + g_return_val_if_fail (value1 != NULL, FALSE); + + *out_secret = NULL; + + builder = json_builder_new (); + + va_start (va, value1); + key = key1_name; + value = value1; + + json_builder_begin_object (builder); + + while (key && value) { + json_builder_set_member_name (builder, key); + json_builder_add_string_value (builder, value); + + key = va_arg (va, const gchar *); + if (!key) + break; + + value = va_arg (va, const gchar *); + g_warn_if_fail (value != NULL); + } + + va_end (va); + + json_builder_end_object (builder); + node = json_builder_get_root (builder); + + g_object_unref (builder); + + if (node) { + JsonGenerator *generator; + + generator = json_generator_new (); + json_generator_set_root (generator, node); + + *out_secret = json_generator_to_data (generator, NULL); + + g_object_unref (generator); + json_node_free (node); + } + + return *out_secret != NULL; +#else + return FALSE; +#endif +} + +static gboolean +eos_decode_from_secret (const gchar *secret, + const gchar *key1_name, + gchar **out_value1, + ...) G_GNUC_NULL_TERMINATED; + +static gboolean +eos_decode_from_secret (const gchar *secret, + const gchar *key1_name, + gchar **out_value1, + ...) +{ +#ifdef ENABLE_OAUTH2 + JsonParser *parser; + JsonReader *reader; + const gchar *key; + gchar **out_value; + va_list va; + GError *error = NULL; + + g_return_val_if_fail (key1_name != NULL, FALSE); + g_return_val_if_fail (out_value1 != NULL, FALSE); + + if (!secret || !*secret) + return FALSE; + + parser = json_parser_new (); + if (!json_parser_load_from_data (parser, secret, -1, &error)) { + g_object_unref (parser); + + g_debug ("%s: Failed to parse secret '%s': %s", G_STRFUNC, secret, error ? error->message : "Unknown error"); + g_clear_error (&error); + + return FALSE; + } + + reader = json_reader_new (json_parser_get_root (parser)); + key = key1_name; + out_value = out_value1; + + va_start (va, out_value1); + + while (key && out_value) { + *out_value = NULL; + + if (json_reader_read_member (reader, key)) { + *out_value = g_strdup (json_reader_get_string_value (reader)); + if (!*out_value) { + const GError *reader_error = json_reader_get_error (reader); + + if (g_error_matches (reader_error, JSON_READER_ERROR, JSON_READER_ERROR_INVALID_TYPE)) { + gint64 iv64; + + json_reader_end_member (reader); + + iv64 = json_reader_get_int_value (reader); + + if (!json_reader_get_error (reader)) + *out_value = g_strdup_printf ("%" G_GINT64_FORMAT, iv64); + } + } + + if (*out_value && !**out_value) { + g_free (*out_value); + *out_value = NULL; + } + } + + json_reader_end_member (reader); + + key = va_arg (va, const gchar *); + if (!key) + break; + + out_value = va_arg (va, gchar **); + g_warn_if_fail (out_value != NULL); + } + + g_object_unref (reader); + g_object_unref (parser); + va_end (va); + + return TRUE; +#else + return FALSE; +#endif +} + +static gboolean +eos_store_token_sync (EOAuth2Service *service, + ESource *source, + const gchar *refresh_token, + const gchar *access_token, + const gchar *expires_in, + GCancellable *cancellable, + GError **error) +{ + gint64 expires_after_tm; + gchar *expires_after, *secret = NULL, *uid = NULL; + gboolean success = FALSE; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE); + + if (!refresh_token || !access_token || !expires_in) + return FALSE; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return FALSE; + + expires_after_tm = g_get_real_time () / G_USEC_PER_SEC; + expires_after_tm += g_ascii_strtoll (expires_in, NULL, 10); + expires_after = g_strdup_printf ("%" G_GINT64_FORMAT, expires_after_tm); + + if (eos_encode_to_secret (&secret, + E_OAUTH2_SECRET_REFRESH_TOKEN, refresh_token, + E_OAUTH2_SECRET_ACCESS_TOKEN, access_token, + E_OAUTH2_SECRET_EXPIRES_AFTER, expires_after, NULL) && + eos_generate_secret_uid (service, source, &uid)) { + gchar *label; + + label = g_strdup_printf ("Evolution Data Source - %s", strstr (uid, "::") + 2); + + success = e_secret_store_store_sync (uid, secret, label, TRUE, cancellable, error); + + g_free (label); + } + + g_free (uid); + g_free (secret); + g_free (expires_after); + + return success; +} + +/* Can return success when the access token is already expired and refresh token is available */ +static gboolean +eos_lookup_token_sync (EOAuth2Service *service, + ESource *source, + gchar **out_refresh_token, + gchar **out_access_token, + gint *out_expires_in, + GCancellable *cancellable, + GError **error) +{ + gchar *secret = NULL, *uid = NULL, *expires_after = NULL; + gboolean success = FALSE; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE); + g_return_val_if_fail (out_refresh_token != NULL, FALSE); + g_return_val_if_fail (out_access_token != NULL, FALSE); + g_return_val_if_fail (out_expires_in != NULL, FALSE); + + *out_refresh_token = NULL; + *out_access_token = NULL; + *out_expires_in = -1; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return FALSE; + + if (!eos_generate_secret_uid (service, source, &uid)) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + /* Translators: The first %s is a display name of the source, the second is its UID and + the third is the name of the OAuth service. */ + _("Source “%s” (%s) is not a valid for “%s” OAuth2 service"), + e_source_get_display_name (source), + e_source_get_uid (source), + e_oauth2_service_get_name (service)); + return FALSE; + } + + if (!e_secret_store_lookup_sync (uid, &secret, cancellable, error)) { + g_free (uid); + return FALSE; + } + + g_free (uid); + + if (!secret) { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("OAuth2 secret not found")); + return FALSE; + } + + success = eos_decode_from_secret (secret, + E_OAUTH2_SECRET_REFRESH_TOKEN, out_refresh_token, + E_OAUTH2_SECRET_ACCESS_TOKEN, out_access_token, + E_OAUTH2_SECRET_EXPIRES_AFTER, &expires_after, + NULL); + + if (success && expires_after) { + gint64 num_expires_after, num_now; + + num_expires_after = g_ascii_strtoll (expires_after, NULL, 10); + num_now = g_get_real_time () / G_USEC_PER_SEC; + + if (num_now < num_expires_after) + *out_expires_in = num_expires_after - num_now - 1; + } + + e_util_safe_free_string (secret); + g_free (expires_after); + + return success && *out_refresh_token != NULL; +} + +/** + * e_oauth2_service_receive_and_store_token_sync: + * @service: an #EOAuth2Service + * @source: an #ESource + * @authorization_code: authorization code provided by the server + * @ref_source: an #EOAuth2ServiceRefSourceFunc function to obtain an #ESource + * @ref_source_user_data: user data for @ref_source + * @cancellable: optional #GCancellable object, or %NULL + * @error: return location for a #GError, or %NULL + * + * Queries @service at e_oauth2_service_get_refresh_uri() with a request to obtain + * a new access token, associated with the given @authorization_code and stores + * it into the secret store on success. + * + * Returns: whether succeeded + * + * Since: 3.28 + **/ +gboolean +e_oauth2_service_receive_and_store_token_sync (EOAuth2Service *service, + ESource *source, + const gchar *authorization_code, + EOAuth2ServiceRefSourceFunc ref_source, + gpointer ref_source_user_data, + GCancellable *cancellable, + GError **error) +{ + SoupSession *session; + SoupMessage *message; + GHashTable *post_form; + gchar *response_json = NULL; + gboolean success; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE); + g_return_val_if_fail (E_IS_SOURCE (source), FALSE); + g_return_val_if_fail (authorization_code != NULL, FALSE); + g_return_val_if_fail (ref_source != NULL, FALSE); + + session = eos_create_soup_session (ref_source, ref_source_user_data, source); + if (!session) + return FALSE; + + post_form = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + e_oauth2_service_prepare_get_token_form (service, authorization_code, post_form); + + message = eos_create_soup_message (source, e_oauth2_service_get_refresh_uri (service), post_form); + + g_hash_table_destroy (post_form); + + if (!message) { + g_object_unref (session); + return FALSE; + } + + e_oauth2_service_prepare_get_token_message (service, message); + + success = eos_send_message (session, message, &response_json, cancellable, error); + if (success) { + gchar *access_token = NULL, *refresh_token = NULL, *expires_in = NULL, *token_type = NULL; + + if (eos_decode_from_secret (response_json, + "access_token", &access_token, + "refresh_token", &refresh_token, + "expires_in", &expires_in, + "token_type", &token_type, + NULL) && access_token && refresh_token && expires_in && token_type) { + + g_warn_if_fail (g_ascii_strcasecmp (token_type, "Bearer") == 0); + + success = eos_store_token_sync (service, source, + refresh_token, access_token, expires_in, cancellable, error); + } else { + success = FALSE; + } + + e_util_safe_free_string (access_token); + e_util_safe_free_string (refresh_token); + g_free (expires_in); + g_free (token_type); + } + + g_object_unref (message); + g_object_unref (session); + e_util_safe_free_string (response_json); + + return success; +} + +/** + * e_oauth2_service_refresh_and_store_token_sync: + * @service: an #EOAuth2Service + * @source: an #ESource + * @authorization_code: authorization code provided by the server + * @ref_source: an #EOAuth2ServiceRefSourceFunc function to obtain an #ESource + * @ref_source_user_data: user data for @ref_source + * @cancellable: optional #GCancellable object, or %NULL + * @error: return location for a #GError, or %NULL + * + * Queries @service at e_oauth2_service_get_refresh_uri() with a request to refresh + * existing @refresh_token and stores it into the secret store on success. + * + * Returns: whether succeeded + * + * Since: 3.28 + **/ +gboolean +e_oauth2_service_refresh_and_store_token_sync (EOAuth2Service *service, + ESource *source, + const gchar *refresh_token, + EOAuth2ServiceRefSourceFunc ref_source, + gpointer ref_source_user_data, + GCancellable *cancellable, + GError **error) +{ + SoupSession *session; + SoupMessage *message; + GHashTable *post_form; + gchar *response_json = NULL; + gboolean success; + GError *local_error = NULL; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE); + g_return_val_if_fail (E_IS_SOURCE (source), FALSE); + g_return_val_if_fail (refresh_token != NULL, FALSE); + g_return_val_if_fail (ref_source != NULL, FALSE); + + session = eos_create_soup_session (ref_source, ref_source_user_data, source); + if (!session) + return FALSE; + + post_form = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + e_oauth2_service_prepare_refresh_token_form (service, refresh_token, post_form); + + message = eos_create_soup_message (source, e_oauth2_service_get_refresh_uri (service), post_form); + + g_hash_table_destroy (post_form); + + if (!message) { + g_object_unref (session); + return FALSE; + } + + e_oauth2_service_prepare_refresh_token_message (service, message); + + success = eos_send_message (session, message, &response_json, cancellable, &local_error); + if (success) { + gchar *access_token = NULL, *expires_in = NULL, *new_refresh_token = NULL; + + if (eos_decode_from_secret (response_json, + "access_token", &access_token, + "expires_in", &expires_in, + "refresh_token", &new_refresh_token, + NULL) && access_token && expires_in) { + success = eos_store_token_sync (service, source, + (new_refresh_token && *new_refresh_token) ? new_refresh_token : refresh_token, + access_token, expires_in, cancellable, error); + } else { + success = FALSE; + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Received incorrect response from server “%s”."), + e_oauth2_service_get_refresh_uri (service)); + } + + e_util_safe_free_string (access_token); + g_free (new_refresh_token); + g_free (expires_in); + } else if (g_error_matches (local_error, SOUP_HTTP_ERROR, SOUP_STATUS_BAD_REQUEST)) { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED, + _("Failed to refresh access token. Sign to the server again, please.")); + g_clear_error (&local_error); + } + + if (local_error) + g_propagate_error (error, local_error); + + g_object_unref (message); + g_object_unref (session); + e_util_safe_free_string (response_json); + + return success; +} + +/** + * e_oauth2_service_delete_token_sync: + * @service: an #EOAuth2Service + * @source: an #ESource + * @cancellable: optional #GCancellable object, or %NULL + * @error: return location for a #GError, or %NULL + * + * Deletes token information for the @service and @source from the secret store. + * + * Returns: whether succeeded + * + * Since: 3.28 + **/ +gboolean +e_oauth2_service_delete_token_sync (EOAuth2Service *service, + ESource *source, + GCancellable *cancellable, + GError **error) +{ + gchar *uid = NULL; + gboolean success; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE); + g_return_val_if_fail (E_IS_SOURCE (source), FALSE); + + if (!eos_generate_secret_uid (service, source, &uid)) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + /* Translators: The first %s is a display name of the source, the second is its UID. */ + _("Source “%s” (%s) is not a valid OAuth2 source"), + e_source_get_display_name (source), + e_source_get_uid (source)); + return FALSE; + } + + success = e_secret_store_delete_sync (uid, cancellable, error); + + g_free (uid); + + return success; +} + +/** + * e_oauth2_service_get_access_token_sync: + * @service: an #EOAuth2Service + * @source: an #ESource + * @ref_source: an #EOAuth2ServiceRefSourceFunc function to obtain an #ESource + * @ref_source_user_data: user data for @ref_source + * @out_access_token: (out) (transfer full): return location for the access token + * @out_expires_in: (out): how many seconds the access token expires in + * @cancellable: optional #GCancellable object, or %NULL + * @error: return location for a #GError, or %NULL + * + * Reads access token information from the secret store for the @source and + * in case it's expired it refreshes the token, if possible. + * + * Free the returned @out_access_token with g_free(), when no longer needed. + * + * Returns: %TRUE, when the returned access token has been set and it's not expired, + * %FALSE otherwise. + * + * Since: 3.28 + **/ +gboolean +e_oauth2_service_get_access_token_sync (EOAuth2Service *service, + ESource *source, + EOAuth2ServiceRefSourceFunc ref_source, + gpointer ref_source_user_data, + gchar **out_access_token, + gint *out_expires_in, + GCancellable *cancellable, + GError **error) +{ + gchar *refresh_token = NULL; + gboolean success = FALSE; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), FALSE); + g_return_val_if_fail (E_IS_SOURCE (source), FALSE); + g_return_val_if_fail (ref_source != NULL, FALSE); + g_return_val_if_fail (out_access_token != NULL, FALSE); + g_return_val_if_fail (out_expires_in != NULL, FALSE); + + if (!eos_lookup_token_sync (service, source, &refresh_token, out_access_token, out_expires_in, cancellable, error)) + return FALSE; + + if (*out_expires_in <= 0 && refresh_token) { + success = e_oauth2_service_refresh_and_store_token_sync (service, source, refresh_token, + ref_source, ref_source_user_data, cancellable, error); + + g_clear_pointer (&refresh_token, e_util_safe_free_string); + + success = success && eos_lookup_token_sync (service, source, &refresh_token, out_access_token, out_expires_in, cancellable, error); + } + + e_util_safe_free_string (refresh_token); + + if (success && *out_expires_in <= 0) { + e_util_safe_free_string (*out_access_token); + *out_access_token = NULL; + success = FALSE; + + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED, + _("The access token is expired and it failed to refresh it. Sign to the server again, please.")); + } + + return success; +} diff --git a/src/libedataserver/e-oauth2-service.h b/src/libedataserver/e-oauth2-service.h new file mode 100644 index 000000000..92b5ccb82 --- /dev/null +++ b/src/libedataserver/e-oauth2-service.h @@ -0,0 +1,179 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#if !defined (__LIBEDATASERVER_H_INSIDE__) && !defined (LIBEDATASERVER_COMPILATION) +#error "Only <libedataserver/libedataserver.h> should be included directly." +#endif + +#ifndef E_OAUTH2_SERVICE_H +#define E_OAUTH2_SERVICE_H + +#include <glib.h> +#include <libsoup/soup.h> + +#include <libedataserver/e-source.h> + +/* Standard GObject macros */ +#define E_TYPE_OAUTH2_SERVICE \ + (e_oauth2_service_get_type ()) +#define E_OAUTH2_SERVICE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_OAUTH2_SERVICE, EOAuth2Service)) +#define E_IS_OAUTH2_SERVICE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_OAUTH2_SERVICE)) +#define E_OAUTH2_SERVICE_GET_INTERFACE(obj) \ + (G_TYPE_INSTANCE_GET_INTERFACE \ + ((obj), E_TYPE_OAUTH2_SERVICE, EOAuth2ServiceInterface)) + +/* Secret key names, saved by the code; not the names returned by the OAuth2 server */ +#define E_OAUTH2_SECRET_REFRESH_TOKEN "refresh_token" +#define E_OAUTH2_SECRET_ACCESS_TOKEN "access_token" +#define E_OAUTH2_SECRET_EXPIRES_AFTER "expires_after" + +G_BEGIN_DECLS + +/** + * EOAuth2ServiceRefSourceFunc: + * @user_data: user data, as passed to e_oauth2_service_get_token_sync() + * or e_oauth2_service_refresh_token_sync() + * @uid: an #ESource UID to return + * + * Returns: (transfer full) (nullable): an #ESource with UID @uid, or %NULL, if not found. + * Dereference the returned non-NULL #ESource with g_object_unref(), when no longer needed. + * + * Since: 3.28 + **/ +typedef ESource * (* EOAuth2ServiceRefSourceFunc) (gpointer user_data, + const gchar *uid); + +typedef struct _EOAuth2Service EOAuth2Service; +typedef struct _EOAuth2ServiceInterface EOAuth2ServiceInterface; + +/** + * EOAuth2Service: + * + * Contains only private data that should be read and manipulated using the + * functions below. + * + * Since: 3.28 + **/ +struct _EOAuth2ServiceInterface { + GTypeInterface parent_interface; + + gboolean (* can_process) (EOAuth2Service *service, + ESource *source); + gboolean (* guess_can_process) (EOAuth2Service *service, + const gchar *protocol, + const gchar *hostname); + const gchar * (* get_name) (EOAuth2Service *service); + const gchar * (* get_display_name) (EOAuth2Service *service); + const gchar * (* get_client_id) (EOAuth2Service *service); + const gchar * (* get_client_secret) (EOAuth2Service *service); + const gchar * (* get_authentication_uri) (EOAuth2Service *service); + const gchar * (* get_refresh_uri) (EOAuth2Service *service); + void (* prepare_authentication_uri_query) + (EOAuth2Service *service, + ESource *source, + GHashTable *uri_query); + gboolean (* extract_authorization_code) (EOAuth2Service *service, + const gchar *page_title, + const gchar *page_uri, + gchar **out_authorization_code); + void (* prepare_get_token_form) (EOAuth2Service *service, + const gchar *authorization_code, + GHashTable *form); + void (* prepare_get_token_message) (EOAuth2Service *service, + SoupMessage *message); + void (* prepare_refresh_token_form) (EOAuth2Service *service, + const gchar *refresh_token, + GHashTable *form); + void (* prepare_refresh_token_message) + (EOAuth2Service *service, + SoupMessage *message); + + /* Padding for future expansion */ + gpointer reserved[10]; +}; + +GType e_oauth2_service_get_type (void) G_GNUC_CONST; +gboolean e_oauth2_service_can_process (EOAuth2Service *service, + ESource *source); +gboolean e_oauth2_service_guess_can_process (EOAuth2Service *service, + const gchar *protocol, + const gchar *hostname); +const gchar * e_oauth2_service_get_name (EOAuth2Service *service); +const gchar * e_oauth2_service_get_display_name (EOAuth2Service *service); +const gchar * e_oauth2_service_get_client_id (EOAuth2Service *service); +const gchar * e_oauth2_service_get_client_secret (EOAuth2Service *service); +const gchar * e_oauth2_service_get_authentication_uri (EOAuth2Service *service); +const gchar * e_oauth2_service_get_refresh_uri (EOAuth2Service *service); +void e_oauth2_service_prepare_authentication_uri_query + (EOAuth2Service *service, + ESource *source, + GHashTable *uri_query); +gboolean e_oauth2_service_extract_authorization_code + (EOAuth2Service *service, + const gchar *page_title, + const gchar *page_uri, + gchar **out_authorization_code); +void e_oauth2_service_prepare_get_token_form (EOAuth2Service *service, + const gchar *authorization_code, + GHashTable *form); +void e_oauth2_service_prepare_get_token_message + (EOAuth2Service *service, + SoupMessage *message); +void e_oauth2_service_prepare_refresh_token_form + (EOAuth2Service *service, + const gchar *refresh_token, + GHashTable *form); +void e_oauth2_service_prepare_refresh_token_message + (EOAuth2Service *service, + SoupMessage *message); + +gboolean e_oauth2_service_receive_and_store_token_sync + (EOAuth2Service *service, + ESource *source, + const gchar *authorization_code, + EOAuth2ServiceRefSourceFunc ref_source, + gpointer ref_source_user_data, + GCancellable *cancellable, + GError **error); +gboolean e_oauth2_service_refresh_and_store_token_sync + (EOAuth2Service *service, + ESource *source, + const gchar *refresh_token, + EOAuth2ServiceRefSourceFunc ref_source, + gpointer ref_source_user_data, + GCancellable *cancellable, + GError **error); +gboolean e_oauth2_service_delete_token_sync (EOAuth2Service *service, + ESource *source, + GCancellable *cancellable, + GError **error); +gboolean e_oauth2_service_get_access_token_sync (EOAuth2Service *service, + ESource *source, + EOAuth2ServiceRefSourceFunc ref_source, + gpointer ref_source_user_data, + gchar **out_access_token, + gint *out_expires_in, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif /* E_OAUTH2_SERVICE_H */ diff --git a/src/libedataserver/e-oauth2-services.c b/src/libedataserver/e-oauth2-services.c new file mode 100644 index 000000000..48df4a24b --- /dev/null +++ b/src/libedataserver/e-oauth2-services.c @@ -0,0 +1,438 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/** + * SECTION: e-oauth2-services + * @include: libedataserver/libedataserver.h + * @short_description: An extensible object holding all known OAuth2 services + * + * The extensible object, which holds all known OAuth2 services. Each + * #EOAuth2Service extends this object and adds itself to it with + * e_oauth2_services_add(). The services can be later searched for + * with e_oauth2_services_find(), which returns the service suitable + * for the given protocol and/or host name. + **/ + +#include "evolution-data-server-config.h" + +#include <stdio.h> + +#include "e-extensible.h" +#include "e-oauth2-service.h" + +/* Known built-in implementations */ +#include "e-oauth2-service-google.h" + +#include "e-oauth2-services.h" + +struct _EOAuth2ServicesPrivate { + GMutex property_lock; + GSList *services; /* EOAuth2Service * */ +}; + +G_DEFINE_TYPE_WITH_CODE (EOAuth2Services, e_oauth2_services, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)) + +static GObject *services_singleton = NULL; +G_LOCK_DEFINE_STATIC (services_singleton); + +static void +services_singleton_weak_ref_cb (gpointer user_data, + GObject *object) +{ + G_LOCK (services_singleton); + + g_warn_if_fail (object == services_singleton); + services_singleton = NULL; + + G_UNLOCK (services_singleton); +} + +static GObject * +oauth2_services_constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + + G_LOCK (services_singleton); + + if (services_singleton) { + object = g_object_ref (services_singleton); + } else { + object = G_OBJECT_CLASS (e_oauth2_services_parent_class)->constructor (type, n_construct_params, construct_params); + + if (object) + g_object_weak_ref (object, services_singleton_weak_ref_cb, NULL); + + services_singleton = object; + } + + G_UNLOCK (services_singleton); + + return object; +} + +static void +oauth2_services_dispose (GObject *object) +{ + EOAuth2Services *services = E_OAUTH2_SERVICES (object); + + g_mutex_lock (&services->priv->property_lock); + g_slist_free_full (services->priv->services, g_object_unref); + g_mutex_unlock (&services->priv->property_lock); + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_oauth2_services_parent_class)->dispose (object); +} + +static void +oauth2_services_finalize (GObject *object) +{ + EOAuth2Services *services = E_OAUTH2_SERVICES (object); + + g_mutex_clear (&services->priv->property_lock); + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_oauth2_services_parent_class)->finalize (object); +} + +static void +oauth2_services_constructed (GObject *object) +{ + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_oauth2_services_parent_class)->constructed (object); + + e_extensible_load_extensions (E_EXTENSIBLE (object)); +} + +static void +e_oauth2_services_class_init (EOAuth2ServicesClass *klass) +{ + GObjectClass *object_class; + + g_type_class_add_private (klass, sizeof (EOAuth2ServicesPrivate)); + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = oauth2_services_dispose; + object_class->finalize = oauth2_services_finalize; + object_class->constructed = oauth2_services_constructed; + object_class->constructor = oauth2_services_constructor; + + /* Ensure built-in service types are registered */ + g_type_ensure (E_TYPE_OAUTH2_SERVICE_GOOGLE); +} + +static void +e_oauth2_services_init (EOAuth2Services *oauth2_services) +{ + oauth2_services->priv = G_TYPE_INSTANCE_GET_PRIVATE (oauth2_services, E_TYPE_OAUTH2_SERVICES, EOAuth2ServicesPrivate); + + g_mutex_init (&oauth2_services->priv->property_lock); +} + +/** + * e_oauth2_services_is_supported: + * + * Returns: %TRUE, when evolution-data-server had been compiled + * with OAuth2 authentication enabled, %FALSE otherwise. + * + * Since: 3.28 + **/ +gboolean +e_oauth2_services_is_supported (void) +{ +#ifdef ENABLE_OAUTH2 + return TRUE; +#else + return FALSE; +#endif +} + +/** + * e_oauth2_services_new: + * + * Creates a new #EOAuth2Services instance. + * + * Returns: (transfer full): an #EOAuth2Services + * + * Since: 3.28 + **/ +EOAuth2Services * +e_oauth2_services_new (void) +{ + return g_object_new (E_TYPE_OAUTH2_SERVICES, NULL); +} + +/** + * e_oauth2_services_add: + * @services: an #EOAuth2Services + * @service: an #EOAuth2Service to add + * + * Adds the @service to the list of known OAuth2 services into @services. + * It also adds a reference to @service. + * + * Since: 3.28 + **/ +void +e_oauth2_services_add (EOAuth2Services *services, + EOAuth2Service *service) +{ + GSList *link; + + g_return_if_fail (E_IS_OAUTH2_SERVICES (services)); + g_return_if_fail (E_IS_OAUTH2_SERVICE (service)); + + g_mutex_lock (&services->priv->property_lock); + + for (link = services->priv->services; link; link = g_slist_next (link)) { + if (link->data == service) + break; + } + + if (!link) + services->priv->services = g_slist_prepend (services->priv->services, g_object_ref (service)); + + g_mutex_unlock (&services->priv->property_lock); +} + +/** + * e_oauth2_services_remove: + * @services: an #EOAuth2Services + * @service: an #EOAuth2Service to remove + * + * Removes the @service from the list of known services in @services. + * The function does nothing, if the @service had not been added. + * + * Since: 3.28 + **/ +void +e_oauth2_services_remove (EOAuth2Services *services, + EOAuth2Service *service) +{ + GSList *link; + + g_return_if_fail (E_IS_OAUTH2_SERVICES (services)); + g_return_if_fail (E_IS_OAUTH2_SERVICE (service)); + + g_mutex_lock (&services->priv->property_lock); + + for (link = services->priv->services; link; link = g_slist_next (link)) { + if (link->data == service) { + g_object_unref (service); + services->priv->services = g_slist_remove (services->priv->services, service); + break; + } + } + + g_mutex_unlock (&services->priv->property_lock); +} + +/** + * e_oauth2_services_list: + * @services: an #EOAuth2Services + * + * Lists all currently known services, which had been added + * with e_oauth2_services_add(). Free the returned #GSList with + * g_slist_remove_full (known_services, g_object_unref); + * when no longer needed. + * + * Returns: (transfer full) (element-type EOAuth2Service): a newly allocated #GSList + * with all currently known #EOAuth2Service referenced instances + * + * Since: 3.28 + **/ +GSList * +e_oauth2_services_list (EOAuth2Services *services) +{ + GSList *result; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICES (services), NULL); + + g_mutex_lock (&services->priv->property_lock); + + result = g_slist_copy_deep (services->priv->services, (GCopyFunc) g_object_ref, NULL); + + g_mutex_unlock (&services->priv->property_lock); + + return result; +} + +/** + * e_oauth2_services_find: + * @services: an #EOAuth2Services + * @source: an #ESource + * + * Searches the list of currently known OAuth2 services for the one which + * can be used with the given @source. + * + * The returned #EOAuth2Service is referenced for thread safety, if found. + * + * Returns: (transfer full) (nullable): a referenced #EOAuth2Service, which can be used + * with given @source, or %NULL, when none was found. + * + * Since: 3.28 + **/ +EOAuth2Service * +e_oauth2_services_find (EOAuth2Services *services, + ESource *source) +{ + GSList *link; + EOAuth2Service *result = NULL; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICES (services), NULL); + g_return_val_if_fail (E_IS_SOURCE (source), NULL); + + g_mutex_lock (&services->priv->property_lock); + + for (link = services->priv->services; link; link = g_slist_next (link)) { + EOAuth2Service *service = link->data; + + if (e_oauth2_service_can_process (service, source)) { + result = g_object_ref (service); + break; + } + } + + g_mutex_unlock (&services->priv->property_lock); + + return result; +} + +/** + * e_oauth2_services_find: + * @services: an #EOAuth2Services + * @protocol: (nullable): a protocol to search the service for, like "imap", or %NULL + * @hostname: (nullable): a host name to search the service for, like "server.example.com", or %NULL + * + * Searches the list of currently known OAuth2 services for the one which + * can be used with the given @protocol and/or @hostname. + * Any of @protocol and @hostname can be %NULL, but not both. + * It's up to each #EOAuth2Service to decide, which of the arguments + * are important and whether all or only any of them can be required. + * + * The returned #EOAuth2Service is referenced for thread safety, if found. + * + * Returns: (transfer full) (nullable): a referenced #EOAuth2Service, which can be used + * with given constraints, or %NULL, when none was found. + * + * Since: 3.28 + **/ +EOAuth2Service * +e_oauth2_services_guess (EOAuth2Services *services, + const gchar *protocol, + const gchar *hostname) +{ + GSList *link; + EOAuth2Service *result = NULL; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICES (services), NULL); + g_return_val_if_fail (protocol || hostname, NULL); + + g_mutex_lock (&services->priv->property_lock); + + for (link = services->priv->services; link; link = g_slist_next (link)) { + EOAuth2Service *service = link->data; + + if (e_oauth2_service_guess_can_process (service, protocol, hostname)) { + result = g_object_ref (service); + break; + } + } + + g_mutex_unlock (&services->priv->property_lock); + + return result; +} + +static gboolean +e_oauth2_services_can_check_auth_method (const gchar *auth_method) +{ + return auth_method && *auth_method && + e_oauth2_services_is_supported () && + g_strcmp0 (auth_method, "none") != 0 && + g_strcmp0 (auth_method, "plain/password") != 0; +} + +/** + * e_oauth2_services_is_oauth2_alias: + * @services: an #EOAuth2Services + * @auth_method: (nullable): an authentication method, or %NULL + * + * Returns: whether exists any #EOAuth2Service, with the same name as @auth_method. + * + * See: e_oauth2_services_is_oauth2_alias_static() + * + * Since: 3.28 + **/ +gboolean +e_oauth2_services_is_oauth2_alias (EOAuth2Services *services, + const gchar *auth_method) +{ + GSList *link; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICES (services), FALSE); + + if (!e_oauth2_services_can_check_auth_method (auth_method)) + return FALSE; + + g_mutex_lock (&services->priv->property_lock); + + for (link = services->priv->services; link; link = g_slist_next (link)) { + EOAuth2Service *service = link->data; + const gchar *name; + + name = e_oauth2_service_get_name (service); + + if (name && g_ascii_strcasecmp (name, auth_method) == 0) + break; + } + + g_mutex_unlock (&services->priv->property_lock); + + return link != NULL; +} + +/** + * e_oauth2_services_is_oauth2_alias_static: + * @auth_method: (nullable): an authentication method, or %NULL + * + * This is the same as e_oauth2_services_is_oauth2_alias(), except + * it creates its own #EOAuth2Services instance and frees it at the end. + * The #EOAuth2Services is implemented as a singleton, thus it won't be + * much trouble, as long as there is something else having created one + * instance. + * + * Returns: whether exists any #EOAuth2Service, with the same name as @auth_method. + * + * Since: 3.28 + **/ +gboolean +e_oauth2_services_is_oauth2_alias_static (const gchar *auth_method) +{ + EOAuth2Services *services; + gboolean is_alias; + + if (!e_oauth2_services_can_check_auth_method (auth_method)) + return FALSE; + + services = e_oauth2_services_new (); + is_alias = e_oauth2_services_is_oauth2_alias (services, auth_method); + g_clear_object (&services); + + return is_alias; +} diff --git a/src/libedataserver/e-oauth2-services.h b/src/libedataserver/e-oauth2-services.h new file mode 100644 index 000000000..9c3fc1768 --- /dev/null +++ b/src/libedataserver/e-oauth2-services.h @@ -0,0 +1,100 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#if !defined (__LIBEDATASERVER_H_INSIDE__) && !defined (LIBEDATASERVER_COMPILATION) +#error "Only <libedataserver/libedataserver.h> should be included directly." +#endif + +#ifndef E_OAUTH2_SERVICES_H +#define E_OAUTH2_SERVICES_H + +#include <glib-object.h> + +#include <libedataserver/e-oauth2-service.h> + +/* Standard GObject macros */ +#define E_TYPE_OAUTH2_SERVICES \ + (e_oauth2_services_get_type ()) +#define E_OAUTH2_SERVICES(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_OAUTH2_SERVICES, EOAuth2Services)) +#define E_OAUTH2_SERVICES_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_OAUTH2_SERVICES, EOAuth2ServicesClass)) +#define E_IS_OAUTH2_SERVICES(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_OAUTH2_SERVICES)) +#define E_IS_OAUTH2_SERVICES_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_OAUTH2_SERVICES)) +#define E_OAUTH2_SERVICES_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_OAUTH2_SERVICES, EOAuth2ServicesClass)) + +G_BEGIN_DECLS + +typedef struct _EOAuth2Services EOAuth2Services; +typedef struct _EOAuth2ServicesClass EOAuth2ServicesClass; +typedef struct _EOAuth2ServicesPrivate EOAuth2ServicesPrivate; + +/** + * EOAuth2Services: + * + * Contains only private data that should be read and manipulated using the + * functions below. + * + * Since: 3.28 + **/ +struct _EOAuth2Services { + /*< private >*/ + GObject parent; + EOAuth2ServicesPrivate *priv; +}; + +struct _EOAuth2ServicesClass { + GObjectClass parent_class; + + /* Padding for future expansion */ + gpointer reserved[10]; +}; + +gboolean e_oauth2_services_is_supported (void); + +GType e_oauth2_services_get_type (void) G_GNUC_CONST; + +EOAuth2Services * + e_oauth2_services_new (void); +void e_oauth2_services_add (EOAuth2Services *services, + EOAuth2Service *service); +void e_oauth2_services_remove (EOAuth2Services *services, + EOAuth2Service *service); +GSList * e_oauth2_services_list (EOAuth2Services *services); +EOAuth2Service * + e_oauth2_services_find (EOAuth2Services *services, + ESource *source); +EOAuth2Service * + e_oauth2_services_guess (EOAuth2Services *services, + const gchar *protocol, + const gchar *hostname); +gboolean e_oauth2_services_is_oauth2_alias (EOAuth2Services *services, + const gchar *auth_method); +gboolean e_oauth2_services_is_oauth2_alias_static + (const gchar *auth_method); + +G_END_DECLS + +#endif /* E_OAUTH2_SERVICES_H */ diff --git a/src/libedataserver/e-soup-session.c b/src/libedataserver/e-soup-session.c index 76907c39b..5f1721adc 100644 --- a/src/libedataserver/e-soup-session.c +++ b/src/libedataserver/e-soup-session.c @@ -29,6 +29,7 @@ #include <stdio.h> #include <glib/gi18n-lib.h> +#include "e-oauth2-services.h" #include "e-soup-auth-bearer.h" #include "e-soup-ssl-trust.h" #include "e-source-authentication.h" @@ -113,7 +114,6 @@ e_soup_session_setup_bearer_auth (ESoupSession *session, GCancellable *cancellable, GError **error) { - ENamedParameters *credentials; ESource *source; gchar *access_token = NULL; gint expires_in_seconds = -1; @@ -123,31 +123,9 @@ e_soup_session_setup_bearer_auth (ESoupSession *session, g_return_val_if_fail (E_IS_SOUP_AUTH_BEARER (bearer), FALSE); source = e_soup_session_get_source (session); - credentials = e_soup_session_dup_credentials (session); - - if (!credentials || !e_named_parameters_count (credentials)) { - /* Google authentication requires prefilled data in 'credentials' */ - gboolean is_google = FALSE; - if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) { - ESourceAuthentication *extension; - gchar *auth_method; - - extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); - auth_method = e_source_authentication_dup_method (extension); - is_google = g_strcmp0 (auth_method, "Google") == 0; - g_free (auth_method); - } - - if (is_google) { - e_named_parameters_free (credentials); - g_set_error_literal (error, SOUP_HTTP_ERROR, SOUP_STATUS_UNAUTHORIZED, _("Credentials required")); - return FALSE; - } - } - - success = e_util_get_source_oauth2_access_token_sync (source, credentials, - &access_token, &expires_in_seconds, cancellable, error); + success = e_source_get_oauth2_access_token_sync (source, cancellable, + &access_token, &expires_in_seconds, error); if (success) { e_soup_auth_bearer_set_access_token (bearer, access_token, expires_in_seconds); @@ -156,7 +134,6 @@ e_soup_session_setup_bearer_auth (ESoupSession *session, e_soup_session_ensure_bearer_auth_usage (session, message, bearer); } - e_named_parameters_free (credentials); g_free (access_token); return success; @@ -186,7 +163,7 @@ e_soup_session_maybe_prepare_bearer_auth (ESoupSession *session, return TRUE; } - if (g_strcmp0 (auth_method, "OAuth2") != 0 && g_strcmp0 (auth_method, "Google") != 0) { + if (g_strcmp0 (auth_method, "OAuth2") != 0 && !e_oauth2_services_is_oauth2_alias_static (auth_method)) { g_free (auth_method); return TRUE; } @@ -631,6 +608,23 @@ e_soup_session_dup_credentials (ESoupSession *session) } /** + * e_soup_session_get_authentication_requires_credentials: + * @session: an #ESoupSession + * + * Returns: Whether the last connection attempt required any credentials. + * Authentications like OAuth2 do not want extra credentials to work. + * + * Since: 3.28 + **/ +gboolean +e_soup_session_get_authentication_requires_credentials (ESoupSession *session) +{ + g_return_val_if_fail (E_IS_SOUP_SESSION (session), FALSE); + + return session->priv->using_bearer_auth != NULL; +} + +/** * e_soup_session_get_ssl_error_details: * @session: an #ESoupSession * @out_certificate_pem: (out): return location for a server TLS/SSL certificate diff --git a/src/libedataserver/e-soup-session.h b/src/libedataserver/e-soup-session.h index c877829cb..9ea18a926 100644 --- a/src/libedataserver/e-soup-session.h +++ b/src/libedataserver/e-soup-session.h @@ -86,6 +86,8 @@ void e_soup_session_set_credentials (ESoupSession *session, const ENamedParameters *credentials); ENamedParameters * e_soup_session_dup_credentials (ESoupSession *session); +gboolean e_soup_session_get_authentication_requires_credentials + (ESoupSession *session); gboolean e_soup_session_get_ssl_error_details (ESoupSession *session, gchar **out_certificate_pem, GTlsCertificateFlags *out_certificate_errors); diff --git a/src/libedataserver/e-source-credentials-provider-impl-google.c b/src/libedataserver/e-source-credentials-provider-impl-google.c deleted file mode 100644 index f17f0a411..000000000 --- a/src/libedataserver/e-source-credentials-provider-impl-google.c +++ /dev/null @@ -1,853 +0,0 @@ -/* - * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. - * - */ - -#include "evolution-data-server-config.h" - -#include <glib.h> -#include <glib/gi18n-lib.h> - -#include <libedataserver/libedataserver.h> - -#ifdef ENABLE_GOOGLE_AUTH -#include <json-glib/json-glib.h> -#endif - -#include "e-source-credentials-provider-impl-google.h" - -struct _ESourceCredentialsProviderImplGooglePrivate { - gboolean dummy; -}; - -G_DEFINE_TYPE (ESourceCredentialsProviderImplGoogle, e_source_credentials_provider_impl_google, E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL) - -static gboolean -e_source_credentials_provider_impl_google_can_process (ESourceCredentialsProviderImpl *provider_impl, - ESource *source) -{ - g_return_val_if_fail (E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE (provider_impl), FALSE); - g_return_val_if_fail (E_IS_SOURCE (source), FALSE); - - return e_source_credentials_google_util_generate_secret_uid (source, NULL); -} - -static gboolean -e_source_credentials_provider_impl_google_can_store (ESourceCredentialsProviderImpl *provider_impl) -{ - g_return_val_if_fail (E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE (provider_impl), FALSE); - - return TRUE; -} - -static gboolean -e_source_credentials_provider_impl_google_can_prompt (ESourceCredentialsProviderImpl *provider_impl) -{ - g_return_val_if_fail (E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE (provider_impl), FALSE); - - return TRUE; -} - -static gboolean -e_source_credentials_google_util_store_secret_for_source_sync (ESource *source, - const gchar *secret, - gboolean permanently, - GCancellable *cancellable, - GError **error) -{ - gchar *uid = NULL, *label; - gboolean success; - - g_return_val_if_fail (E_IS_SOURCE (source), FALSE); - g_return_val_if_fail (secret != NULL, FALSE); - - if (!e_source_credentials_google_util_generate_secret_uid (source, &uid)) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - /* Translators: The first %s is a display name of the source, the second is its UID. */ - _("Source “%s” (%s) is not a valid Google source"), - e_source_get_display_name (source), - e_source_get_uid (source)); - return FALSE; - } - - label = g_strdup_printf ("Evolution Data Source - %s", strstr (uid, "::") + 2); - - success = e_secret_store_store_sync (uid, secret, label, permanently, cancellable, error); - - g_free (label); - g_free (uid); - - return success; -} - -static gboolean -e_source_credentials_google_util_get_access_token_from_secret (const gchar *secret, - gchar **out_access_token) -{ - gchar *expires_after = NULL, *access_token = NULL; - gboolean success = FALSE; - - if (!secret) - return FALSE; - - if (!e_source_credentials_google_util_decode_from_secret (secret, - E_GOOGLE_SECRET_ACCESS_TOKEN, &access_token, - E_GOOGLE_SECRET_EXPIRES_AFTER, &expires_after, - NULL)) - return FALSE; - - if (access_token && expires_after) { - success = TRUE; - - if (out_access_token && - g_ascii_strtoll (expires_after, NULL, 10) > g_get_real_time () / G_USEC_PER_SEC) { - *out_access_token = access_token; - access_token = NULL; - } - } - - g_free (expires_after); - g_free (access_token); - - return success; -} - -static gboolean -e_source_credentials_google_util_lookup_secret_for_source_sync (ESource *source, - gboolean can_return_expired, - GCancellable *cancellable, - ENamedParameters **out_credentials, - GError **error) -{ - gchar *uid = NULL, *secret = NULL, *access_token = NULL; - - g_return_val_if_fail (E_IS_SOURCE (source), FALSE); - g_return_val_if_fail (out_credentials != NULL, FALSE); - - *out_credentials = NULL; - - if (!e_source_credentials_google_util_generate_secret_uid (source, &uid)) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - /* Translators: The first %s is a display name of the source, the second is its UID. */ - _("Source “%s” (%s) is not a valid Google source"), - e_source_get_display_name (source), - e_source_get_uid (source)); - return FALSE; - } - - if (!e_secret_store_lookup_sync (uid, &secret, cancellable, error)) { - g_free (uid); - return FALSE; - } - - g_free (uid); - - if (!secret) { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("Google secret not found")); - return FALSE; - } - - if (e_source_credentials_google_util_get_access_token_from_secret (secret, &access_token)) { - if (access_token || can_return_expired) { - *out_credentials = e_named_parameters_new (); - e_named_parameters_set (*out_credentials, E_SOURCE_CREDENTIAL_GOOGLE_SECRET, secret); - e_named_parameters_set (*out_credentials, E_SOURCE_CREDENTIAL_PASSWORD, access_token); - } - } - - e_util_safe_free_string (access_token); - e_util_safe_free_string (secret); - - return *out_credentials != NULL; -} - -static gboolean -e_source_credentials_provider_impl_google_lookup_sync (ESourceCredentialsProviderImpl *provider_impl, - ESource *source, - GCancellable *cancellable, - ENamedParameters **out_credentials, - GError **error) -{ - gboolean success; - - g_return_val_if_fail (E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE (provider_impl), FALSE); - g_return_val_if_fail (E_IS_SOURCE (source), FALSE); - g_return_val_if_fail (out_credentials != NULL, FALSE); - - *out_credentials = NULL; - - if (!e_source_credentials_google_is_supported ()) { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - _("Google authentication is not supported")); - return FALSE; - } - - success = e_source_credentials_google_util_lookup_secret_for_source_sync (source, FALSE, cancellable, out_credentials, error); - - /* Try to refresh the access token, in case it's stored, but expired. */ - if (!success && !g_cancellable_is_cancelled (cancellable)) { - ENamedParameters *empty_credentials; - gchar *access_token = NULL; - - /* Use empty credentials, to read from store. */ - empty_credentials = e_named_parameters_new (); - - if (e_source_credentials_google_get_access_token_sync (source, empty_credentials, &access_token, NULL, cancellable, NULL)) { - g_free (access_token); - g_clear_error (error); - - success = e_source_credentials_google_util_lookup_secret_for_source_sync (source, FALSE, cancellable, out_credentials, error); - } - - e_named_parameters_free (empty_credentials); - } - - return success; -} - -static gboolean -e_source_credentials_provider_impl_google_store_sync (ESourceCredentialsProviderImpl *provider_impl, - ESource *source, - const ENamedParameters *credentials, - gboolean permanently, - GCancellable *cancellable, - GError **error) -{ - g_return_val_if_fail (E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE (provider_impl), FALSE); - g_return_val_if_fail (E_IS_SOURCE (source), FALSE); - g_return_val_if_fail (credentials != NULL, FALSE); - g_return_val_if_fail (e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_GOOGLE_SECRET) != NULL, FALSE); - - if (!e_source_credentials_google_is_supported ()) { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - _("Google authentication is not supported")); - return FALSE; - } - - return e_source_credentials_google_util_store_secret_for_source_sync (source, - e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_GOOGLE_SECRET), - permanently, cancellable, error); -} - -static gboolean -e_source_credentials_provider_impl_google_delete_sync (ESourceCredentialsProviderImpl *provider_impl, - ESource *source, - GCancellable *cancellable, - GError **error) -{ - gchar *uid = NULL; - gboolean success; - - g_return_val_if_fail (E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE (provider_impl), FALSE); - g_return_val_if_fail (E_IS_SOURCE (source), FALSE); - - if (!e_source_credentials_google_is_supported ()) { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - _("Google authentication is not supported")); - return FALSE; - } - - if (!e_source_credentials_google_util_generate_secret_uid (source, &uid)) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - /* Translators: The first %s is a display name of the source, the second is its UID. */ - _("Source “%s” (%s) is not a valid Google source"), - e_source_get_display_name (source), - e_source_get_uid (source)); - return FALSE; - } - - success = e_secret_store_delete_sync (uid, cancellable, error); - - g_free (uid); - - return success; -} - -static void -e_source_credentials_provider_impl_google_class_init (ESourceCredentialsProviderImplGoogleClass *klass) -{ - ESourceCredentialsProviderImplClass *impl_class; - - g_type_class_add_private (klass, sizeof (ESourceCredentialsProviderImplGooglePrivate)); - - impl_class = E_SOURCE_CREDENTIALS_PROVIDER_IMPL_CLASS (klass); - impl_class->can_process = e_source_credentials_provider_impl_google_can_process; - impl_class->can_store = e_source_credentials_provider_impl_google_can_store; - impl_class->can_prompt = e_source_credentials_provider_impl_google_can_prompt; - impl_class->lookup_sync = e_source_credentials_provider_impl_google_lookup_sync; - impl_class->store_sync = e_source_credentials_provider_impl_google_store_sync; - impl_class->delete_sync = e_source_credentials_provider_impl_google_delete_sync; -} - -static void -e_source_credentials_provider_impl_google_init (ESourceCredentialsProviderImplGoogle *provider_impl) -{ - provider_impl->priv = G_TYPE_INSTANCE_GET_PRIVATE (provider_impl, - E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE, ESourceCredentialsProviderImplGooglePrivate); -} - -gboolean -e_source_credentials_google_is_supported (void) -{ -#ifdef ENABLE_GOOGLE_AUTH - return TRUE; -#else - return FALSE; -#endif -} - -/* The "refresh token" code is mostly copied from e-credentials-prompter-impl-google.c, - which cannot be linked here, because the build dependency is opposite. */ - -#define GOOGLE_TOKEN_URI "https://www.googleapis.com/oauth2/v3/token" - -static gchar * -cpi_google_create_refresh_token_post_data (const gchar *refresh_token) -{ -#ifdef ENABLE_GOOGLE_AUTH - g_return_val_if_fail (refresh_token != NULL, NULL); - - return soup_form_encode ( - "refresh_token", refresh_token, - "client_id", GOOGLE_CLIENT_ID, - "client_secret", GOOGLE_CLIENT_SECRET, - "grant_type", "refresh_token", - NULL); -#else - return NULL; -#endif /* ENABLE_GOOGLE_AUTH */ -} - -static void -cpi_google_abort_session_cb (GCancellable *cancellable, - SoupSession *session) -{ - soup_session_abort (session); -} - -static gboolean -cpi_google_setup_proxy_resolver (SoupSession *session, - ESource *cred_source, - GCancellable *cancellable) -{ - ESourceAuthentication *auth_extension; - ESource *source = NULL; - gchar *uid; - gboolean success = TRUE; - - g_return_val_if_fail (SOUP_IS_SESSION (session), FALSE); - g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), FALSE); - g_return_val_if_fail (E_IS_SOURCE (cred_source), FALSE); - - if (!e_source_has_extension (cred_source, E_SOURCE_EXTENSION_AUTHENTICATION)) - return TRUE; - - auth_extension = e_source_get_extension (cred_source, E_SOURCE_EXTENSION_AUTHENTICATION); - uid = e_source_authentication_dup_proxy_uid (auth_extension); - - if (uid != NULL) { - ESourceRegistry *registry; - - registry = e_source_registry_new_sync (cancellable, NULL); - if (!registry) - success = FALSE; - else - source = e_source_registry_ref_source (registry, uid); - - g_clear_object (®istry); - g_free (uid); - } - - if (source != NULL) { - GProxyResolver *proxy_resolver; - - proxy_resolver = G_PROXY_RESOLVER (source); - if (g_proxy_resolver_is_supported (proxy_resolver)) - g_object_set (session, SOUP_SESSION_PROXY_RESOLVER, proxy_resolver, NULL); - - g_object_unref (source); - } - - return success; -} - -static guint -cpi_google_post_data_sync (const gchar *uri, - const gchar *post_data, - ESource *cred_source, - GCancellable *cancellable, - gchar **out_response_data) -{ - SoupSession *session; - SoupMessage *message; - guint status_code = SOUP_STATUS_CANCELLED; - - g_return_val_if_fail (uri != NULL, SOUP_STATUS_MALFORMED); - g_return_val_if_fail (post_data != NULL, SOUP_STATUS_MALFORMED); - /* g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), SOUP_STATUS_MALFORMED); */ - g_return_val_if_fail (E_IS_SOURCE (cred_source), SOUP_STATUS_MALFORMED); - g_return_val_if_fail (out_response_data != NULL, SOUP_STATUS_MALFORMED); - - *out_response_data = NULL; - - session = soup_session_new (); - g_object_set ( - session, - SOUP_SESSION_TIMEOUT, 90, - SOUP_SESSION_SSL_STRICT, TRUE, - SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, - SOUP_SESSION_ACCEPT_LANGUAGE_AUTO, TRUE, - NULL); - - if (!cpi_google_setup_proxy_resolver (session, cred_source, cancellable)) { - g_object_unref (session); - - return SOUP_STATUS_CANT_RESOLVE_PROXY; - } - - message = soup_message_new (SOUP_METHOD_POST, uri); - g_return_val_if_fail (message != NULL, SOUP_STATUS_MALFORMED); - - soup_message_set_request (message, "application/x-www-form-urlencoded", - SOUP_MEMORY_TEMPORARY, post_data, strlen (post_data)); - - e_soup_ssl_trust_connect (message, cred_source); - - soup_message_headers_append (message->request_headers, "Connection", "close"); - - if (!g_cancellable_is_cancelled (cancellable)) { - gulong cancel_handler_id = 0; - - if (cancellable) - cancel_handler_id = g_cancellable_connect (cancellable, G_CALLBACK (cpi_google_abort_session_cb), session, NULL); - - status_code = soup_session_send_message (session, message); - - if (cancel_handler_id) - g_cancellable_disconnect (cancellable, cancel_handler_id); - } - - if (SOUP_STATUS_IS_SUCCESSFUL (status_code)) { - if (message->response_body) { - *out_response_data = g_strndup (message->response_body->data, message->response_body->length); - } else { - status_code = SOUP_STATUS_MALFORMED; - } - } - - g_object_unref (message); - g_object_unref (session); - - return status_code; -} - -static gboolean -e_source_credentials_google_refresh_token_sync (ESource *source, - const ENamedParameters *credentials, - gchar **out_access_token, - gint *out_expires_in_seconds, - GCancellable *cancellable, - GError **error) -{ - const gchar *secret; - gchar *refresh_token = NULL; - gchar *post_data, *response_json = NULL; - guint soup_status; - gboolean success = FALSE; - - secret = e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_GOOGLE_SECRET); - if (!secret) { - if (error && !*error) - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Failed to get Google secret from credentials")); - - return FALSE; - } - - /* The caller can have set an error from the secret lookup, - which are propagated, but otherwise a success is reported. */ - if (error && *error) - g_clear_error (error); - - if (!e_source_credentials_google_util_decode_from_secret (secret, E_GOOGLE_SECRET_REFRESH_TOKEN, &refresh_token, NULL) || - !refresh_token || !*refresh_token) { - g_free (refresh_token); - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Refresh token not found in Google secret")); - return FALSE; - } - - post_data = cpi_google_create_refresh_token_post_data (refresh_token); - g_warn_if_fail (post_data != NULL); - if (!post_data) { - g_free (refresh_token); - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Failed to construct refresh_token request")); - return FALSE; - } - - soup_status = cpi_google_post_data_sync (GOOGLE_TOKEN_URI, post_data, source, cancellable, &response_json); - - if (SOUP_STATUS_IS_SUCCESSFUL (soup_status) && response_json) { - gchar *access_token = NULL, *expires_in = NULL; - - if (e_source_credentials_google_util_decode_from_secret (response_json, - "access_token", &access_token, - "expires_in", &expires_in, - NULL) && access_token && expires_in) { - gint64 expires_after_tm, expires_in_tm; - gchar *expires_after; - gchar *new_secret = NULL; - - expires_in_tm = g_ascii_strtoll (expires_in, NULL, 10) - 1; - expires_after_tm = g_get_real_time () / G_USEC_PER_SEC; - expires_after_tm += expires_in_tm; - expires_after = g_strdup_printf ("%" G_GINT64_FORMAT, expires_after_tm); - - if (e_source_credentials_google_util_encode_to_secret (&new_secret, - E_GOOGLE_SECRET_REFRESH_TOKEN, refresh_token, - E_GOOGLE_SECRET_ACCESS_TOKEN, access_token, - E_GOOGLE_SECRET_EXPIRES_AFTER, expires_after, NULL)) { - /* Oops, always storing permanently, though it makes more sense here. - Otherwise can read RememberPassword from [Authentication] and use - it here. */ - success = e_source_credentials_google_util_store_secret_for_source_sync (source, - new_secret, TRUE, cancellable, error); - g_free (new_secret); - } else { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Failed to encode new access token to Google secret")); - } - - if (success) { - if (out_access_token) - *out_access_token = g_strdup (access_token); - if (out_expires_in_seconds) - *out_expires_in_seconds = expires_in_tm; - } - } else { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Failed to get access token from refresh_token server response")); - } - - g_free (access_token); - g_free (expires_in); - } else { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Failed to refresh token")); - } - - g_free (refresh_token); - g_free (response_json); - g_free (post_data); - - return success; -} - -static gboolean -e_source_credentials_google_extract_from_credentials_valid_only (const ENamedParameters *credentials, - gchar **out_access_token, - gint *out_expires_in_seconds) -{ - gchar *access_token = NULL; - gint expires_in_seconds = -1; - - if (!credentials) - return FALSE; - - if (e_source_credentials_google_util_extract_from_credentials (credentials, &access_token, &expires_in_seconds)) { - if (expires_in_seconds <= 0) { - g_free (access_token); - access_token = NULL; - } - } else { - access_token = NULL; - } - - /* The token exists and is valid, just use it. */ - if (access_token) { - if (out_access_token) - *out_access_token = access_token; - else - g_free (access_token); - - if (out_expires_in_seconds) - *out_expires_in_seconds = expires_in_seconds; - - return TRUE; - } - - return FALSE; -} - -gboolean -e_source_credentials_google_get_access_token_sync (ESource *source, - const ENamedParameters *credentials, - gchar **out_access_token, - gint *out_expires_in_seconds, - GCancellable *cancellable, - GError **error) -{ - ENamedParameters *tmp_credentials = NULL; - gboolean success; - - g_return_val_if_fail (credentials != NULL, FALSE); - - if (!e_source_credentials_google_is_supported ()) { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - _("Google authentication is not supported")); - return FALSE; - } - - /* The token exists and is valid, just use it. */ - if (e_source_credentials_google_extract_from_credentials_valid_only (credentials, out_access_token, out_expires_in_seconds)) { - return TRUE; - } - - /* Maybe some other part refreshed the token already and stored it into the secret store, - thus try to read it and use it, if it contains proper data. - */ - if (e_source_credentials_google_util_lookup_secret_for_source_sync (source, TRUE, cancellable, &tmp_credentials, error) && - e_source_credentials_google_extract_from_credentials_valid_only (tmp_credentials, out_access_token, out_expires_in_seconds)) { - e_named_parameters_free (tmp_credentials); - - return TRUE; - } - - /* Try to refresh the token */ - success = e_source_credentials_google_refresh_token_sync (source, tmp_credentials ? tmp_credentials : credentials, - out_access_token, out_expires_in_seconds, cancellable, error); - - e_named_parameters_free (tmp_credentials); - - return success; -} - -gboolean -e_source_credentials_google_util_generate_secret_uid (ESource *source, - gchar **out_uid) -{ - ESourceAuthentication *authentication_extension; - gchar *auth_method, *user; - gboolean is_google_auth; - - g_return_val_if_fail (E_IS_SOURCE (source), FALSE); - - if (out_uid) - *out_uid = NULL; - - if (!e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) - return FALSE; - - authentication_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); - auth_method = e_source_authentication_dup_method (authentication_extension); - is_google_auth = g_strcmp0 (auth_method, "Google") == 0; - - g_free (auth_method); - - if (!is_google_auth) - return FALSE; - - user = e_source_authentication_dup_user (authentication_extension); - if (!user || !*user) { - g_free (user); - return FALSE; - } - - if (out_uid) - *out_uid = g_strdup_printf ("OAuth2::Google[%s]", user); - - g_free (user); - - return TRUE; -} - -gboolean -e_source_credentials_google_util_encode_to_secret (gchar **out_secret, - const gchar *key1_name, - const gchar *value1, - ...) -{ -#ifdef ENABLE_GOOGLE_AUTH - JsonBuilder *builder; - JsonNode *node; - const gchar *key, *value; - va_list va; - - g_return_val_if_fail (out_secret != NULL, FALSE); - g_return_val_if_fail (key1_name != NULL, FALSE); - g_return_val_if_fail (value1 != NULL, FALSE); - - *out_secret = NULL; - - builder = json_builder_new (); - - va_start (va, value1); - key = key1_name; - value = value1; - - json_builder_begin_object (builder); - - while (key && value) { - json_builder_set_member_name (builder, key); - json_builder_add_string_value (builder, value); - - key = va_arg (va, const gchar *); - if (!key) - break; - - value = va_arg (va, const gchar *); - g_warn_if_fail (value != NULL); - } - - va_end (va); - - json_builder_end_object (builder); - node = json_builder_get_root (builder); - - g_object_unref (builder); - - if (node) { - JsonGenerator *generator; - - generator = json_generator_new (); - json_generator_set_root (generator, node); - - *out_secret = json_generator_to_data (generator, NULL); - - g_object_unref (generator); - json_node_free (node); - } - - return *out_secret != NULL; -#else - return FALSE; -#endif -} - -gboolean -e_source_credentials_google_util_decode_from_secret (const gchar *secret, - const gchar *key1_name, - gchar **out_value1, - ...) -{ -#ifdef ENABLE_GOOGLE_AUTH - JsonParser *parser; - JsonReader *reader; - const gchar *key; - gchar **out_value; - va_list va; - GError *error = NULL; - - g_return_val_if_fail (key1_name != NULL, FALSE); - g_return_val_if_fail (out_value1 != NULL, FALSE); - - if (!secret || !*secret) - return FALSE; - - parser = json_parser_new (); - if (!json_parser_load_from_data (parser, secret, -1, &error)) { - g_object_unref (parser); - - g_debug ("%s: Failed to parse secret '%s': %s", G_STRFUNC, secret, error ? error->message : "Unknown error"); - g_clear_error (&error); - - return FALSE; - } - - reader = json_reader_new (json_parser_get_root (parser)); - key = key1_name; - out_value = out_value1; - - va_start (va, out_value1); - - while (key && out_value) { - *out_value = NULL; - - if (json_reader_read_member (reader, key)) { - *out_value = g_strdup (json_reader_get_string_value (reader)); - if (!*out_value) { - const GError *reader_error = json_reader_get_error (reader); - - if (g_error_matches (reader_error, JSON_READER_ERROR, JSON_READER_ERROR_INVALID_TYPE)) { - gint64 iv64; - - json_reader_end_member (reader); - - iv64 = json_reader_get_int_value (reader); - - if (!json_reader_get_error (reader)) - *out_value = g_strdup_printf ("%" G_GINT64_FORMAT, iv64); - } - } - - if (*out_value && !**out_value) { - g_free (*out_value); - *out_value = NULL; - } - } - - json_reader_end_member (reader); - - key = va_arg (va, const gchar *); - if (!key) - break; - - out_value = va_arg (va, gchar **); - g_warn_if_fail (out_value != NULL); - } - - g_object_unref (reader); - g_object_unref (parser); - va_end (va); - - return TRUE; -#else - return FALSE; -#endif -} - -gboolean -e_source_credentials_google_util_extract_from_credentials (const ENamedParameters *credentials, - gchar **out_access_token, - gint *out_expires_in_seconds) -{ - const gchar *secret; - gchar *expires_after = NULL; - gint64 expires_after_tm, now; - - if (!credentials || - !e_named_parameters_exists (credentials, E_SOURCE_CREDENTIAL_GOOGLE_SECRET) || - !e_named_parameters_exists (credentials, E_SOURCE_CREDENTIAL_PASSWORD)) - return FALSE; - - secret = e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_GOOGLE_SECRET); - - if (!e_source_credentials_google_util_decode_from_secret (secret, E_GOOGLE_SECRET_EXPIRES_AFTER, &expires_after, NULL) || - !expires_after) - return FALSE; - - expires_after_tm = g_ascii_strtoll (expires_after, NULL, 10); - g_free (expires_after); - - if (out_access_token) - *out_access_token = g_strdup (e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_PASSWORD)); - - if (out_expires_in_seconds) { - now = g_get_real_time () / G_USEC_PER_SEC; - if (expires_after_tm <= now) - expires_after_tm = now; - - *out_expires_in_seconds = (gint) (expires_after_tm - now); - if (*out_expires_in_seconds > 0) - *out_expires_in_seconds = (*out_expires_in_seconds) - 1; - } - - return TRUE; -} diff --git a/src/libedataserver/e-source-credentials-provider-impl-google.h b/src/libedataserver/e-source-credentials-provider-impl-google.h deleted file mode 100644 index b5486b4b3..000000000 --- a/src/libedataserver/e-source-credentials-provider-impl-google.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. - * - */ - -#if !defined (__LIBEDATASERVER_H_INSIDE__) && !defined (LIBEDATASERVER_COMPILATION) -#error "Only <libedataserver/libedataserver.h> should be included directly." -#endif - -#ifndef E_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE_H -#define E_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE_H - -#include <glib.h> -#include <glib-object.h> - -#include <libedataserver/e-source.h> -#include <libedataserver/e-source-credentials-provider-impl.h> - -/* Standard GObject macros */ -#define E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE \ - (e_source_credentials_provider_impl_google_get_type ()) -#define E_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST \ - ((obj), E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE, ESourceCredentialsProviderImplGoogle)) -#define E_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_CAST \ - ((cls), E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE, ESourceCredentialsProviderImplGoogleClass)) -#define E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE \ - ((obj), E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE)) -#define E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_TYPE \ - ((cls), E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE)) -#define E_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS \ - ((obj), E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE, ESourceCredentialsProviderImplGoogleClass)) - -/* The key names used in crendetials ENamedParameters structure */ -#define E_SOURCE_CREDENTIAL_GOOGLE_SECRET "Google-secret" - -/* Secret key names, saved by the code; not the names returned by the Google server */ -#define E_GOOGLE_SECRET_REFRESH_TOKEN "refresh_token" -#define E_GOOGLE_SECRET_ACCESS_TOKEN "access_token" -#define E_GOOGLE_SECRET_EXPIRES_AFTER "expires_after" - -G_BEGIN_DECLS - -typedef struct _ESourceCredentialsProviderImplGoogle ESourceCredentialsProviderImplGoogle; -typedef struct _ESourceCredentialsProviderImplGoogleClass ESourceCredentialsProviderImplGoogleClass; -typedef struct _ESourceCredentialsProviderImplGooglePrivate ESourceCredentialsProviderImplGooglePrivate; - -/** - * ESourceCredentialsProviderImplGoogle: - * - * Google based credentials provider implementation. - * - * Since: 3.20 - **/ -struct _ESourceCredentialsProviderImplGoogle { - /*< private >*/ - ESourceCredentialsProviderImpl parent; - ESourceCredentialsProviderImplGooglePrivate *priv; -}; - -struct _ESourceCredentialsProviderImplGoogleClass { - ESourceCredentialsProviderImplClass parent_class; -}; - -GType e_source_credentials_provider_impl_google_get_type (void); - -gboolean e_source_credentials_google_is_supported (void); -gboolean e_source_credentials_google_get_access_token_sync (ESource *source, - const ENamedParameters *credentials, - gchar **out_access_token, - gint *out_expires_in_seconds, - GCancellable *cancellable, - GError **error); -gboolean e_source_credentials_google_util_generate_secret_uid (ESource *source, - gchar **out_uid); -gboolean e_source_credentials_google_util_encode_to_secret (gchar **out_secret, - const gchar *key1_name, - const gchar *value1, - ...) G_GNUC_NULL_TERMINATED; -gboolean e_source_credentials_google_util_decode_from_secret (const gchar *secret, - const gchar *key1_name, - gchar **out_value1, - ...) G_GNUC_NULL_TERMINATED; -gboolean e_source_credentials_google_util_extract_from_credentials - (const ENamedParameters *credentials, - gchar **out_access_token, - gint *out_expires_in_seconds); -G_END_DECLS - -#endif /* E_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE_H */ diff --git a/src/libedataserver/e-source-credentials-provider-impl-oauth2.c b/src/libedataserver/e-source-credentials-provider-impl-oauth2.c new file mode 100644 index 000000000..1915a51c2 --- /dev/null +++ b/src/libedataserver/e-source-credentials-provider-impl-oauth2.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. (www.redhat.com) + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include "evolution-data-server-config.h" + +#include <glib.h> + +#include "e-oauth2-services.h" +#include "e-oauth2-service.h" + +#include "e-source-credentials-provider-impl-oauth2.h" + +struct _ESourceCredentialsProviderImplOAuth2Private { + EOAuth2Services *services; +}; + +G_DEFINE_TYPE (ESourceCredentialsProviderImplOAuth2, e_source_credentials_provider_impl_oauth2, E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL) + +static gboolean +e_source_credentials_provider_impl_oauth2_can_process (ESourceCredentialsProviderImpl *provider_impl, + ESource *source) +{ + ESourceCredentialsProviderImplOAuth2 *oauth2_provider; + EOAuth2Service *service; + gboolean can_process; + + g_return_val_if_fail (E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2 (provider_impl), FALSE); + g_return_val_if_fail (E_IS_SOURCE (source), FALSE); + + oauth2_provider = E_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2 (provider_impl); + + if (!e_oauth2_services_is_supported () || !oauth2_provider->priv->services) + return FALSE; + + service = e_oauth2_services_find (oauth2_provider->priv->services, source); + can_process = service != NULL; + g_clear_object (&service); + + return can_process; +} + +static gboolean +e_source_credentials_provider_impl_oauth2_can_store (ESourceCredentialsProviderImpl *provider_impl) +{ + g_return_val_if_fail (E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2 (provider_impl), FALSE); + + return FALSE; +} + +static gboolean +e_source_credentials_provider_impl_oauth2_can_prompt (ESourceCredentialsProviderImpl *provider_impl) +{ + g_return_val_if_fail (E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2 (provider_impl), FALSE); + + return e_oauth2_services_is_supported (); +} + +static void +e_source_credentials_provider_impl_oauth2_dispose (GObject *object) +{ + ESourceCredentialsProviderImplOAuth2 *provider_impl = E_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2 (object); + + g_clear_object (&provider_impl->priv->services); + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_source_credentials_provider_impl_oauth2_parent_class)->dispose (object); +} + +static void +e_source_credentials_provider_impl_oauth2_class_init (ESourceCredentialsProviderImplOAuth2Class *klass) +{ + ESourceCredentialsProviderImplClass *impl_class; + GObjectClass *object_class; + + g_type_class_add_private (klass, sizeof (ESourceCredentialsProviderImplOAuth2Private)); + + impl_class = E_SOURCE_CREDENTIALS_PROVIDER_IMPL_CLASS (klass); + impl_class->can_process = e_source_credentials_provider_impl_oauth2_can_process; + impl_class->can_store = e_source_credentials_provider_impl_oauth2_can_store; + impl_class->can_prompt = e_source_credentials_provider_impl_oauth2_can_prompt; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = e_source_credentials_provider_impl_oauth2_dispose; +} + +static void +e_source_credentials_provider_impl_oauth2_init (ESourceCredentialsProviderImplOAuth2 *provider_impl) +{ + provider_impl->priv = G_TYPE_INSTANCE_GET_PRIVATE (provider_impl, + E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2, ESourceCredentialsProviderImplOAuth2Private); + + if (e_oauth2_services_is_supported ()) + provider_impl->priv->services = e_oauth2_services_new (); +} diff --git a/src/libedataserver/e-source-credentials-provider-impl-oauth2.h b/src/libedataserver/e-source-credentials-provider-impl-oauth2.h new file mode 100644 index 000000000..f24645278 --- /dev/null +++ b/src/libedataserver/e-source-credentials-provider-impl-oauth2.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. (www.redhat.com) + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#if !defined (__LIBEDATASERVER_H_INSIDE__) && !defined (LIBEDATASERVER_COMPILATION) +#error "Only <libedataserver/libedataserver.h> should be included directly." +#endif + +#ifndef E_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2_H +#define E_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2_H + +#include <glib.h> +#include <glib-object.h> + +#include <libedataserver/e-source.h> +#include <libedataserver/e-source-credentials-provider-impl.h> + +/* Standard GObject macros */ +#define E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2 \ + (e_source_credentials_provider_impl_oauth2_get_type ()) +#define E_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2, ESourceCredentialsProviderImplOAuth2)) +#define E_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2, ESourceCredentialsProviderImplOAuth2Class)) +#define E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2)) +#define E_IS_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2)) +#define E_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2, ESourceCredentialsProviderImplOAuth2Class)) + +G_BEGIN_DECLS + +typedef struct _ESourceCredentialsProviderImplOAuth2 ESourceCredentialsProviderImplOAuth2; +typedef struct _ESourceCredentialsProviderImplOAuth2Class ESourceCredentialsProviderImplOAuth2Class; +typedef struct _ESourceCredentialsProviderImplOAuth2Private ESourceCredentialsProviderImplOAuth2Private; + +/** + * ESourceCredentialsProviderImplOAuth2: + * + * OAuth2 based credentials provider implementation. + * + * Since: 3.28 + **/ +struct _ESourceCredentialsProviderImplOAuth2 { + /*< private >*/ + ESourceCredentialsProviderImpl parent; + ESourceCredentialsProviderImplOAuth2Private *priv; +}; + +struct _ESourceCredentialsProviderImplOAuth2Class { + ESourceCredentialsProviderImplClass parent_class; +}; + +GType e_source_credentials_provider_impl_oauth2_get_type (void); + +G_END_DECLS + +#endif /* E_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2_H */ diff --git a/src/libedataserver/e-source-credentials-provider.c b/src/libedataserver/e-source-credentials-provider.c index 7eea89814..36764ba80 100644 --- a/src/libedataserver/e-source-credentials-provider.c +++ b/src/libedataserver/e-source-credentials-provider.c @@ -33,7 +33,7 @@ /* built-in source credentials provider implementations */ #include "e-source-credentials-provider-impl-password.h" -#include "e-source-credentials-provider-impl-google.h" +#include "e-source-credentials-provider-impl-oauth2.h" struct _ESourceCredentialsProviderPrivate { GWeakRef registry; /* The property can hold both client and server-side registry */ @@ -200,7 +200,7 @@ e_source_credentials_provider_class_init (ESourceCredentialsProviderClass *class /* Ensure built-in credential providers implementation types */ g_type_ensure (E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_PASSWORD); - g_type_ensure (E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_GOOGLE); + g_type_ensure (E_TYPE_SOURCE_CREDENTIALS_PROVIDER_IMPL_OAUTH2); } static void @@ -437,72 +437,8 @@ e_source_credentials_provider_ref_credentials_source (ESourceCredentialsProvider collection = parent; } - if (collection && e_source_has_extension (collection, E_SOURCE_EXTENSION_COLLECTION)) { - gboolean can_use_collection = FALSE; - - /* Use the found parent collection source for credentials store only if - the child source doesn't have any authentication information, or this - information is not filled, or if either the host name or the user name - are the same with the collection source. - - This allows to create a collection of sources which has one source - (like message send) on a different server, thus this source uses - its own credentials. - */ - if (!e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) { - can_use_collection = TRUE; - } else if (e_source_has_extension (collection, E_SOURCE_EXTENSION_AUTHENTICATION)) { - ESourceAuthentication *auth_source, *auth_collection; - gchar *host_source, *host_collection; - - auth_source = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); - auth_collection = e_source_get_extension (collection, E_SOURCE_EXTENSION_AUTHENTICATION); - - host_source = e_source_authentication_dup_host (auth_source); - host_collection = e_source_authentication_dup_host (auth_collection); - - if (host_source && host_collection && g_ascii_strcasecmp (host_source, host_collection) == 0) { - gchar *username_source, *username_collection; - - username_source = e_source_authentication_dup_user (auth_source); - username_collection = e_source_authentication_dup_user (auth_collection); - - if (username_source && username_collection && g_ascii_strcasecmp (username_source, username_collection) == 0) { - can_use_collection = TRUE; - } else { - can_use_collection = !username_source || !*username_source; - } - - g_free (username_source); - g_free (username_collection); - } else { - /* Only one of them is filled, then use the collection; otherwise - both are filled and they do not match, thus do not use collection. */ - can_use_collection = (host_collection && *host_collection && (!host_source || !*host_source)) || - (host_source && *host_source && (!host_collection || !*host_collection)); - - if (can_use_collection) { - gchar *method_source, *method_collection; - - /* Also check the method; if different, then rather not use the collection */ - method_source = e_source_authentication_dup_method (auth_source); - method_collection = e_source_authentication_dup_method (auth_collection); - - can_use_collection = !method_source || !method_collection || - g_ascii_strcasecmp (method_source, method_collection) == 0; - - g_free (method_source); - g_free (method_collection); - } - } - - g_free (host_source); - g_free (host_collection); - } - - if (can_use_collection) - cred_source = g_object_ref (collection); - } + if (e_util_can_use_collection_as_credential_source (collection, source)) + cred_source = g_object_ref (collection); g_clear_object (&collection); diff --git a/src/libedataserver/e-source-registry.c b/src/libedataserver/e-source-registry.c index b03a6ab43..63970ffb0 100644 --- a/src/libedataserver/e-source-registry.c +++ b/src/libedataserver/e-source-registry.c @@ -121,6 +121,8 @@ struct _ESourceRegistryPrivate { gboolean initialized; GError *init_error; GMutex init_lock; + + EOAuth2Services *oauth2_services; }; struct _AsyncContext { @@ -1350,6 +1352,8 @@ source_registry_finalize (GObject *object) g_clear_error (&priv->init_error); g_mutex_clear (&priv->init_lock); + g_clear_object (&priv->oauth2_services); + /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_source_registry_parent_class)->finalize (object); } @@ -1733,6 +1737,8 @@ e_source_registry_init (ESourceRegistry *registry) G_CALLBACK (source_registry_settings_changed_cb), registry); g_mutex_init (®istry->priv->init_lock); + + registry->priv->oauth2_services = e_oauth2_services_new (); } /** @@ -1855,6 +1861,22 @@ e_source_registry_new_finish (GAsyncResult *result, return g_task_propagate_pointer (G_TASK (result), error); } +/** + * e_source_registry_get_oauth2_services: + * @registry: an #ESourceRegistry + * + * Returns: (transfer none): an instance of #EOAuth2Services, owned by @registry + * + * Since: 3.28 + **/ +EOAuth2Services * +e_source_registry_get_oauth2_services (ESourceRegistry *registry) +{ + g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); + + return registry->priv->oauth2_services; +} + /* Helper for e_source_registry_commit_source() */ static void source_registry_commit_source_thread (GSimpleAsyncResult *simple, diff --git a/src/libedataserver/e-source-registry.h b/src/libedataserver/e-source-registry.h index 2392331d2..66c57590b 100644 --- a/src/libedataserver/e-source-registry.h +++ b/src/libedataserver/e-source-registry.h @@ -22,6 +22,7 @@ #ifndef E_SOURCE_REGISTRY_H #define E_SOURCE_REGISTRY_H +#include <libedataserver/e-oauth2-services.h> #include <libedataserver/e-source.h> /* Standard GObject macros */ @@ -95,6 +96,9 @@ void e_source_registry_new (GCancellable *cancellable, ESourceRegistry * e_source_registry_new_finish (GAsyncResult *result, GError **error); +EOAuth2Services * + e_source_registry_get_oauth2_services + (ESourceRegistry *registry); gboolean e_source_registry_commit_source_sync (ESourceRegistry *registry, ESource *source, diff --git a/src/libedataserver/libedataserver.h b/src/libedataserver/libedataserver.h index f3df296d2..062c34531 100644 --- a/src/libedataserver/libedataserver.h +++ b/src/libedataserver/libedataserver.h @@ -39,6 +39,10 @@ #include <libedataserver/e-memory.h> #include <libedataserver/e-module.h> #include <libedataserver/e-network-monitor.h> +#include <libedataserver/e-oauth2-service.h> +#include <libedataserver/e-oauth2-service-base.h> +#include <libedataserver/e-oauth2-service-google.h> +#include <libedataserver/e-oauth2-services.h> #include <libedataserver/e-operation-pool.h> #include <libedataserver/e-proxy.h> #include <libedataserver/e-secret-store.h> @@ -58,7 +62,7 @@ #include <libedataserver/e-source-contacts.h> #include <libedataserver/e-source-credentials-provider.h> #include <libedataserver/e-source-credentials-provider-impl.h> -#include <libedataserver/e-source-credentials-provider-impl-google.h> +#include <libedataserver/e-source-credentials-provider-impl-oauth2.h> #include <libedataserver/e-source-credentials-provider-impl-password.h> #include <libedataserver/e-source-enums.h> #include <libedataserver/e-source-enumtypes.h> diff --git a/src/libedataserverui/CMakeLists.txt b/src/libedataserverui/CMakeLists.txt index 599a035fd..e6d45eee1 100644 --- a/src/libedataserverui/CMakeLists.txt +++ b/src/libedataserverui/CMakeLists.txt @@ -4,7 +4,7 @@ set(SOURCES e-cell-renderer-color.c e-credentials-prompter.c e-credentials-prompter-impl.c - e-credentials-prompter-impl-google.c + e-credentials-prompter-impl-oauth2.c e-credentials-prompter-impl-password.c e-trust-prompt.c e-webdav-discover-widget.c @@ -15,7 +15,7 @@ set(HEADERS e-cell-renderer-color.h e-credentials-prompter.h e-credentials-prompter-impl.h - e-credentials-prompter-impl-google.h + e-credentials-prompter-impl-oauth2.h e-credentials-prompter-impl-password.h e-trust-prompt.h e-webdav-discover-widget.h @@ -56,7 +56,7 @@ target_compile_options(edataserverui PUBLIC ${GCR_BASE_CFLAGS} ${GCR_CFLAGS} ${GTK_CFLAGS} - ${GOOGLE_AUTH_CFLAGS} + ${OAUTH2_CFLAGS} ) target_include_directories(edataserverui PUBLIC @@ -72,7 +72,7 @@ target_include_directories(edataserverui PUBLIC ${GCR_BASE_INCLUDE_DIRS} ${GCR_INCLUDE_DIRS} ${GTK_INCLUDE_DIRS} - ${GOOGLE_AUTH_INCLUDE_DIRS} + ${OAUTH2_INCLUDE_DIRS} ) target_link_libraries(edataserverui @@ -85,7 +85,7 @@ target_link_libraries(edataserverui ${GCR_BASE_LDFLAGS} ${GCR_LDFLAGS} ${GTK_LDFLAGS} - ${GOOGLE_AUTH_LDFLAGS} + ${OAUTH2_LDFLAGS} ) install(TARGETS edataserverui diff --git a/src/libedataserverui/e-credentials-prompter-impl-google.c b/src/libedataserverui/e-credentials-prompter-impl-google.c deleted file mode 100644 index 4e3e4ed4c..000000000 --- a/src/libedataserverui/e-credentials-prompter-impl-google.c +++ /dev/null @@ -1,1181 +0,0 @@ -/* - * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. - * - */ - -#include "evolution-data-server-config.h" - -#include <glib.h> -#include <glib/gi18n-lib.h> - -#include <gtk/gtk.h> - -#include <libedataserver/libedataserver.h> - -#include "e-credentials-prompter.h" -#include "e-credentials-prompter-impl-google.h" - -#ifdef ENABLE_GOOGLE_AUTH -#include <webkit2/webkit2.h> - -/* https://developers.google.com/identity/protocols/OAuth2InstalledApp */ -#define GOOGLE_AUTH_URI "https://accounts.google.com/o/oauth2/auth" -#define GOOGLE_TOKEN_URI "https://www.googleapis.com/oauth2/v3/token" -#define GOOGLE_REDIRECT_URI "urn:ietf:wg:oauth:2.0:oob" - -static const gchar *GOOGLE_SCOPE = - /* GMail IMAP and SMTP access */ - "https://mail.google.com/ " - /* Google Calendar API (CalDAV and GData) */ - "https://www.googleapis.com/auth/calendar " - /* Google Contacts API (GData) */ - "https://www.google.com/m8/feeds/ " - /* Google Contacts API (CardDAV) - undocumented */ - "https://www.googleapis.com/auth/carddav " - /* Google Tasks - undocumented */ - "https://www.googleapis.com/auth/tasks"; -#endif /* ENABLE_GOOGLE_AUTH */ - -struct _ECredentialsPrompterImplGooglePrivate { - GMutex property_lock; - - gpointer prompt_id; - ESource *auth_source; - ESource *cred_source; - gchar *error_text; - ENamedParameters *credentials; - gboolean refresh_failed_with_transport_error; - - GtkDialog *dialog; -#ifdef ENABLE_GOOGLE_AUTH - WebKitWebView *web_view; -#endif - gulong show_dialog_idle_id; - - GCancellable *cancellable; -}; - -G_DEFINE_TYPE (ECredentialsPrompterImplGoogle, e_credentials_prompter_impl_google, E_TYPE_CREDENTIALS_PROMPTER_IMPL) - -#ifdef ENABLE_GOOGLE_AUTH -static gchar * -cpi_google_create_auth_uri (ESource *source) -{ - GHashTable *form; - SoupURI *soup_uri; - gchar *uri, *user = NULL; - - g_return_val_if_fail (E_IS_SOURCE (source), NULL); - - soup_uri = soup_uri_new (GOOGLE_AUTH_URI); - form = g_hash_table_new (g_str_hash, g_str_equal); - - #define add_to_form(name, value) g_hash_table_insert (form, (gpointer) name, (gpointer) value) - - add_to_form ("response_type", "code"); - add_to_form ("client_id", GOOGLE_CLIENT_ID); - add_to_form ("redirect_uri", GOOGLE_REDIRECT_URI); - add_to_form ("scope", GOOGLE_SCOPE); - - if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) { - ESourceAuthentication *auth_extension; - - auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); - user = e_source_authentication_dup_user (auth_extension); - - if (user && *user) - add_to_form ("login_hint", user); - } - - add_to_form ("include_granted_scopes", "false"); - - #undef add_to_form - - soup_uri_set_query_from_form (soup_uri, form); - - uri = soup_uri_to_string (soup_uri, FALSE); - - soup_uri_free (soup_uri); - g_hash_table_destroy (form); - g_free (user); - - return uri; -} - -static gchar * -cpi_google_create_token_post_data (const gchar *authorization_code) -{ - g_return_val_if_fail (authorization_code != NULL, NULL); - - return soup_form_encode ( - "code", authorization_code, - "client_id", GOOGLE_CLIENT_ID, - "client_secret", GOOGLE_CLIENT_SECRET, - "redirect_uri", GOOGLE_REDIRECT_URI, - "grant_type", "authorization_code", - NULL); -} - -static gchar * -cpi_google_create_refresh_token_post_data (const gchar *refresh_token) -{ - g_return_val_if_fail (refresh_token != NULL, NULL); - - return soup_form_encode ( - "refresh_token", refresh_token, - "client_id", GOOGLE_CLIENT_ID, - "client_secret", GOOGLE_CLIENT_SECRET, - "grant_type", "refresh_token", - NULL); -} - -static void -cpi_google_setup_proxy_resolver (SoupSession *session, - ESourceRegistry *registry, - ESource *cred_source) -{ - ESourceAuthentication *auth_extension; - ESource *source = NULL; - gchar *uid; - - g_return_if_fail (SOUP_IS_SESSION (session)); - g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); - g_return_if_fail (E_IS_SOURCE (cred_source)); - - if (!e_source_has_extension (cred_source, E_SOURCE_EXTENSION_AUTHENTICATION)) - return; - - auth_extension = e_source_get_extension (cred_source, E_SOURCE_EXTENSION_AUTHENTICATION); - uid = e_source_authentication_dup_proxy_uid (auth_extension); - if (uid != NULL) { - source = e_source_registry_ref_source (registry, uid); - - g_free (uid); - } - - if (source != NULL) { - GProxyResolver *proxy_resolver; - - proxy_resolver = G_PROXY_RESOLVER (source); - if (g_proxy_resolver_is_supported (proxy_resolver)) - g_object_set (session, SOUP_SESSION_PROXY_RESOLVER, proxy_resolver, NULL); - - g_object_unref (source); - } -} - -static void -cpi_google_abort_session_cb (GCancellable *cancellable, - SoupSession *session) -{ - soup_session_abort (session); -} - -static guint -cpi_google_post_data_sync (const gchar *uri, - const gchar *post_data, - ESourceRegistry *registry, - ESource *cred_source, - GCancellable *cancellable, - gchar **out_response_data) -{ - SoupSession *session; - SoupMessage *message; - guint status_code = SOUP_STATUS_CANCELLED; - - g_return_val_if_fail (uri != NULL, SOUP_STATUS_MALFORMED); - g_return_val_if_fail (post_data != NULL, SOUP_STATUS_MALFORMED); - g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), SOUP_STATUS_MALFORMED); - g_return_val_if_fail (E_IS_SOURCE (cred_source), SOUP_STATUS_MALFORMED); - g_return_val_if_fail (out_response_data != NULL, SOUP_STATUS_MALFORMED); - - *out_response_data = NULL; - - message = soup_message_new (SOUP_METHOD_POST, uri); - g_return_val_if_fail (message != NULL, SOUP_STATUS_MALFORMED); - - soup_message_set_request (message, "application/x-www-form-urlencoded", - SOUP_MEMORY_TEMPORARY, post_data, strlen (post_data)); - - e_soup_ssl_trust_connect (message, cred_source); - - session = soup_session_new (); - g_object_set ( - session, - SOUP_SESSION_TIMEOUT, 90, - SOUP_SESSION_SSL_STRICT, TRUE, - SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, - SOUP_SESSION_ACCEPT_LANGUAGE_AUTO, TRUE, - NULL); - - cpi_google_setup_proxy_resolver (session, registry, cred_source); - - soup_message_headers_append (message->request_headers, "Connection", "close"); - - if (!g_cancellable_is_cancelled (cancellable)) { - gulong cancel_handler_id = 0; - - if (cancellable) - cancel_handler_id = g_cancellable_connect (cancellable, G_CALLBACK (cpi_google_abort_session_cb), session, NULL); - - status_code = soup_session_send_message (session, message); - - if (cancel_handler_id) - g_cancellable_disconnect (cancellable, cancel_handler_id); - } - - if (SOUP_STATUS_IS_SUCCESSFUL (status_code)) { - if (message->response_body) { - *out_response_data = g_strndup (message->response_body->data, message->response_body->length); - } else { - status_code = SOUP_STATUS_MALFORMED; - } - } - - g_object_unref (message); - g_object_unref (session); - - return status_code; -} - -static gboolean -cpi_google_update_prompter_credentials (GWeakRef *prompter_google_wr, - const gchar *refresh_token, - const gchar *access_token, - const gchar *expires_in, - GCancellable *cancellable) -{ - ECredentialsPrompterImplGoogle *prompter_google; - gint64 expires_after_tm; - gchar *expires_after; - gboolean success = FALSE; - - g_return_val_if_fail (prompter_google_wr != NULL, FALSE); - - if (!refresh_token || !access_token || !expires_in) - return FALSE; - - if (g_cancellable_is_cancelled (cancellable)) - return FALSE; - - expires_after_tm = g_get_real_time () / G_USEC_PER_SEC; - expires_after_tm += g_ascii_strtoll (expires_in, NULL, 10); - expires_after = g_strdup_printf ("%" G_GINT64_FORMAT, expires_after_tm); - - prompter_google = g_weak_ref_get (prompter_google_wr); - if (prompter_google && !g_cancellable_is_cancelled (cancellable)) { - gchar *secret = NULL; - - if (e_source_credentials_google_util_encode_to_secret (&secret, - E_GOOGLE_SECRET_REFRESH_TOKEN, refresh_token, - E_GOOGLE_SECRET_ACCESS_TOKEN, access_token, - E_GOOGLE_SECRET_EXPIRES_AFTER, expires_after, NULL)) { - e_named_parameters_set (prompter_google->priv->credentials, - E_SOURCE_CREDENTIAL_GOOGLE_SECRET, secret); - e_named_parameters_set (prompter_google->priv->credentials, - E_SOURCE_CREDENTIAL_PASSWORD, access_token); - - success = TRUE; - } - - g_free (secret); - } - g_clear_object (&prompter_google); - - g_free (expires_after); - - return success; -} - -static void -e_credentials_prompter_impl_google_show_html (WebKitWebView *web_view, - const gchar *title, - const gchar *body_text) -{ - gchar *html; - - g_return_if_fail (WEBKIT_IS_WEB_VIEW (web_view)); - g_return_if_fail (title != NULL); - g_return_if_fail (body_text != NULL); - - html = g_strdup_printf ( - "<html>" - "<head><title>%s</title></head>" - "<body><div style=\"font-size:12pt; font-family:Helvetica,Arial;\">%s</div></body>" - "</html>", - title, - body_text); - webkit_web_view_load_html (web_view, html, "none-local://"); - g_free (html); -} - -static gboolean -e_credentials_prompter_impl_google_finish_dialog_idle_cb (gpointer user_data) -{ - ECredentialsPrompterImplGoogle *prompter_google = user_data; - - if (g_source_is_destroyed (g_main_current_source ())) - return FALSE; - - g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_GOOGLE (prompter_google), FALSE); - - g_mutex_lock (&prompter_google->priv->property_lock); - if (g_source_get_id (g_main_current_source ()) == prompter_google->priv->show_dialog_idle_id) { - prompter_google->priv->show_dialog_idle_id = 0; - g_mutex_unlock (&prompter_google->priv->property_lock); - - g_warn_if_fail (prompter_google->priv->dialog != NULL); - - if (e_named_parameters_exists (prompter_google->priv->credentials, E_SOURCE_CREDENTIAL_PASSWORD)) { - gtk_dialog_response (prompter_google->priv->dialog, GTK_RESPONSE_OK); - } else if (prompter_google->priv->error_text) { - e_credentials_prompter_impl_google_show_html (prompter_google->priv->web_view, - "Finished with error", prompter_google->priv->error_text); - } - } else { - g_warning ("%s: Source was cancelled? current:%d expected:%d", G_STRFUNC, (gint) g_source_get_id (g_main_current_source ()), (gint) prompter_google->priv->show_dialog_idle_id); - g_mutex_unlock (&prompter_google->priv->property_lock); - } - - return FALSE; -} - -typedef struct { - GWeakRef *prompter_google; /* ECredentialsPrompterImplGoogle * */ - GCancellable *cancellable; - ESource *cred_source; - ESourceRegistry *registry; - gchar *authorization_code; -} AccessTokenThreadData; - -static void -access_token_thread_data_free (gpointer user_data) -{ - AccessTokenThreadData *td = user_data; - - if (td) { - e_weak_ref_free (td->prompter_google); - g_clear_object (&td->cancellable); - g_clear_object (&td->cred_source); - g_clear_object (&td->registry); - g_free (td->authorization_code); - g_free (td); - } -} - -static gpointer -cpi_google_get_access_token_thread (gpointer user_data) -{ - AccessTokenThreadData *td = user_data; - ECredentialsPrompterImplGoogle *prompter_google; - GCancellable *cancellable; - gchar *post_data, *response_json = NULL; - guint soup_status; - gboolean success = FALSE; - - g_return_val_if_fail (td != NULL, NULL); - - cancellable = td->cancellable; - - if (g_cancellable_is_cancelled (cancellable)) { - soup_status = SOUP_STATUS_CANCELLED; - goto exit; - } - - - post_data = cpi_google_create_token_post_data (td->authorization_code); - g_warn_if_fail (post_data != NULL); - if (!post_data) { - soup_status = SOUP_STATUS_NO_CONTENT; - goto exit; - } - - soup_status = cpi_google_post_data_sync (GOOGLE_TOKEN_URI, post_data, - td->registry, td->cred_source, cancellable, &response_json); - - if (SOUP_STATUS_IS_SUCCESSFUL (soup_status) && response_json) { - gchar *access_token = NULL, *refresh_token = NULL, *expires_in = NULL, *token_type = NULL; - - if (e_source_credentials_google_util_decode_from_secret (response_json, - "access_token", &access_token, - "refresh_token", &refresh_token, - "expires_in", &expires_in, - "token_type", &token_type, - NULL) && access_token && refresh_token && expires_in && token_type) { - - g_warn_if_fail (g_str_equal (token_type, "Bearer")); - - if (!cpi_google_update_prompter_credentials (td->prompter_google, - refresh_token, access_token, expires_in, cancellable)) { - soup_status = SOUP_STATUS_MALFORMED; - } - } else { - soup_status = SOUP_STATUS_MALFORMED; - } - - g_free (access_token); - g_free (refresh_token); - g_free (expires_in); - g_free (token_type); - } - - g_free (response_json); - g_free (post_data); - - exit: - prompter_google = g_weak_ref_get (td->prompter_google); - if (prompter_google && !g_cancellable_is_cancelled (cancellable)) { - if (!success) { - g_free (prompter_google->priv->error_text); - prompter_google->priv->error_text = NULL; - - prompter_google->priv->error_text = g_strdup_printf ( - _("Failed to obtain access token from address “%s”. Error code %d (%s)"), - GOOGLE_TOKEN_URI, soup_status, soup_status_get_phrase (soup_status)); - } - - g_mutex_lock (&prompter_google->priv->property_lock); - prompter_google->priv->show_dialog_idle_id = g_idle_add ( - e_credentials_prompter_impl_google_finish_dialog_idle_cb, - prompter_google); - g_mutex_unlock (&prompter_google->priv->property_lock); - } - - g_clear_object (&prompter_google); - - access_token_thread_data_free (td); - - return NULL; -} - -static void -cpi_google_document_load_changed_cb (WebKitWebView *web_view, - WebKitLoadEvent load_event, - ECredentialsPrompterImplGoogle *prompter_google) -{ - const gchar *title; - - g_return_if_fail (WEBKIT_IS_WEB_VIEW (web_view)); - g_return_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_GOOGLE (prompter_google)); - - if (load_event != WEBKIT_LOAD_FINISHED) - return; - - title = webkit_web_view_get_title (web_view); - if (!title) - return; - - if (g_ascii_strncasecmp (title, "Denied ", 7) == 0) { - g_cancellable_cancel (prompter_google->priv->cancellable); - gtk_dialog_response (prompter_google->priv->dialog, GTK_RESPONSE_CANCEL); - return; - } - - if (g_ascii_strncasecmp (title, "Success code=", 13) == 0) { - ECredentialsPrompter *prompter; - ECredentialsPrompterImpl *prompter_impl; - AccessTokenThreadData *td; - GThread *thread; - - e_credentials_prompter_impl_google_show_html (web_view, - "Checking returned code", _("Requesting access token, please wait...")); - - gtk_widget_set_sensitive (GTK_WIDGET (web_view), FALSE); - - e_named_parameters_set (prompter_google->priv->credentials, E_SOURCE_CREDENTIAL_PASSWORD, NULL); - - prompter_impl = E_CREDENTIALS_PROMPTER_IMPL (prompter_google); - prompter = e_credentials_prompter_impl_get_credentials_prompter (prompter_impl); - - td = g_new0 (AccessTokenThreadData, 1); - td->prompter_google = e_weak_ref_new (prompter_google); - td->cancellable = g_object_ref (prompter_google->priv->cancellable); - td->cred_source = g_object_ref (prompter_google->priv->cred_source); - td->registry = g_object_ref (e_credentials_prompter_get_registry (prompter)); - td->authorization_code = g_strdup (title + 13); - - thread = g_thread_new (G_STRFUNC, cpi_google_get_access_token_thread, td); - g_thread_unref (thread); - - return; - } -} - -static void -cpi_google_notify_estimated_load_progress_cb (WebKitWebView *web_view, - GParamSpec *param, - GtkProgressBar *progress_bar) -{ - gboolean visible; - gdouble progress; - - g_return_if_fail (GTK_IS_PROGRESS_BAR (progress_bar)); - - progress = webkit_web_view_get_estimated_load_progress (web_view); - visible = progress > 1e-9 && progress < 1 - 1e-9; - - gtk_progress_bar_set_fraction (progress_bar, visible ? progress : 0.0); -} - -static void -credentials_prompter_impl_google_get_prompt_strings (ESourceRegistry *registry, - ESource *source, - gchar **prompt_title, - GString **prompt_description) -{ - GString *description; - const gchar *message; - gchar *display_name; - - /* Known types */ - enum { - TYPE_UNKNOWN, - TYPE_AMBIGUOUS, - TYPE_ADDRESS_BOOK, - TYPE_CALENDAR, - TYPE_MAIL_ACCOUNT, - TYPE_MAIL_TRANSPORT, - TYPE_MEMO_LIST, - TYPE_TASK_LIST - } type = TYPE_UNKNOWN; - - if (e_source_has_extension (source, E_SOURCE_EXTENSION_ADDRESS_BOOK)) { - type = TYPE_ADDRESS_BOOK; - } - - if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) { - if (type == TYPE_UNKNOWN) - type = TYPE_CALENDAR; - else - type = TYPE_AMBIGUOUS; - } - - if (e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_ACCOUNT)) { - if (type == TYPE_UNKNOWN) - type = TYPE_MAIL_ACCOUNT; - else - type = TYPE_AMBIGUOUS; - } - - if (e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_TRANSPORT)) { - if (type == TYPE_UNKNOWN) - type = TYPE_MAIL_TRANSPORT; - else - type = TYPE_AMBIGUOUS; - } - - if (e_source_has_extension (source, E_SOURCE_EXTENSION_MEMO_LIST)) { - if (type == TYPE_UNKNOWN) - type = TYPE_MEMO_LIST; - else - type = TYPE_AMBIGUOUS; - } - - if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) { - if (type == TYPE_UNKNOWN) - type = TYPE_TASK_LIST; - else - type = TYPE_AMBIGUOUS; - } - - switch (type) { - case TYPE_ADDRESS_BOOK: - message = _("Google Address book authentication request"); - break; - case TYPE_CALENDAR: - message = _("Google Calendar authentication request"); - break; - case TYPE_MEMO_LIST: - message = _("Google Memo List authentication request"); - break; - case TYPE_TASK_LIST: - message = _("Google Task List authentication request"); - break; - case TYPE_MAIL_ACCOUNT: - case TYPE_MAIL_TRANSPORT: - message = _("Google Mail authentication request"); - break; - default: /* generic account prompt */ - message = _("Google account authentication request"); - break; - } - - display_name = e_util_get_source_full_name (registry, source); - description = g_string_sized_new (256); - - g_string_append_printf (description, "<big><b>%s</b></big>\n\n", message); - switch (type) { - case TYPE_ADDRESS_BOOK: - g_string_append_printf (description, - _("Login to your Google account and accept conditions in order to access your address book “%s”."), display_name); - break; - case TYPE_CALENDAR: - g_string_append_printf (description, - _("Login to your Google account and accept conditions in order to access your calendar “%s”."), display_name); - break; - case TYPE_MAIL_ACCOUNT: - g_string_append_printf (description, - _("Login to your Google account and accept conditions in order to access your mail account “%s”."), display_name); - break; - case TYPE_MAIL_TRANSPORT: - g_string_append_printf (description, - _("Login to your Google account and accept conditions in order to access your mail transport “%s”."), display_name); - break; - case TYPE_MEMO_LIST: - g_string_append_printf (description, - _("Login to your Google account and accept conditions in order to access your memo list “%s”."), display_name); - break; - case TYPE_TASK_LIST: - g_string_append_printf (description, - _("Login to your Google account and accept conditions in order to access your task list “%s”."), display_name); - break; - default: /* generic account prompt */ - g_string_append_printf (description, - _("Login to your Google account and accept conditions in order to access your account “%s”."), display_name); - break; - } - - *prompt_title = g_strdup (message); - *prompt_description = description; - - g_free (display_name); -} -#endif /* ENABLE_GOOGLE_AUTH */ - -static gboolean -e_credentials_prompter_impl_google_show_dialog (ECredentialsPrompterImplGoogle *prompter_google) -{ -#ifdef ENABLE_GOOGLE_AUTH - GtkWidget *dialog, *content_area, *widget, *progress_bar, *vbox; - GtkGrid *grid; - GtkScrolledWindow *scrolled_window; - GtkWindow *dialog_parent; - ECredentialsPrompter *prompter; - gchar *title, *uri; - GString *info_markup; - gint row = 0; - gboolean success; - - g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_GOOGLE (prompter_google), FALSE); - g_return_val_if_fail (prompter_google->priv->prompt_id != NULL, FALSE); - g_return_val_if_fail (prompter_google->priv->dialog == NULL, FALSE); - - prompter = e_credentials_prompter_impl_get_credentials_prompter (E_CREDENTIALS_PROMPTER_IMPL (prompter_google)); - g_return_val_if_fail (prompter != NULL, FALSE); - - dialog_parent = e_credentials_prompter_get_dialog_parent (prompter); - - credentials_prompter_impl_google_get_prompt_strings (e_credentials_prompter_get_registry (prompter), - prompter_google->priv->auth_source, &title, &info_markup); - if (prompter_google->priv->error_text && *prompter_google->priv->error_text) { - gchar *escaped = g_markup_printf_escaped ("%s", prompter_google->priv->error_text); - - g_string_append_printf (info_markup, "\n\n%s", escaped); - g_free (escaped); - } - - dialog = gtk_dialog_new_with_buttons (title, dialog_parent, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - _("_Cancel"), GTK_RESPONSE_CANCEL, - NULL); - - gtk_window_set_default_size (GTK_WINDOW (dialog), 320, 480); - - prompter_google->priv->dialog = GTK_DIALOG (dialog); - gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE); - if (dialog_parent) - gtk_window_set_transient_for (GTK_WINDOW (dialog), dialog_parent); - gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER_ON_PARENT); - gtk_container_set_border_width (GTK_CONTAINER (dialog), 12); - - content_area = gtk_dialog_get_content_area (prompter_google->priv->dialog); - - /* Override GtkDialog defaults */ - gtk_box_set_spacing (GTK_BOX (content_area), 12); - gtk_container_set_border_width (GTK_CONTAINER (content_area), 0); - - grid = GTK_GRID (gtk_grid_new ()); - gtk_grid_set_column_spacing (grid, 12); - gtk_grid_set_row_spacing (grid, 6); - - gtk_box_pack_start (GTK_BOX (content_area), GTK_WIDGET (grid), FALSE, TRUE, 0); - - /* Info Label */ - widget = gtk_label_new (NULL); - gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE); - gtk_label_set_markup (GTK_LABEL (widget), info_markup->str); - g_object_set ( - G_OBJECT (widget), - "hexpand", TRUE, - "vexpand", FALSE, - "halign", GTK_ALIGN_FILL, - "valign", GTK_ALIGN_CENTER, - "width-chars", 60, - "max-width-chars", 80, - "xalign", 0.0, - NULL); - - gtk_grid_attach (grid, widget, 0, row, 1, 1); - row++; - - vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 1); - g_object_set ( - G_OBJECT (vbox), - "hexpand", TRUE, - "vexpand", TRUE, - "halign", GTK_ALIGN_FILL, - "valign", GTK_ALIGN_FILL, - NULL); - - gtk_grid_attach (grid, vbox, 0, row, 1, 1); - - widget = gtk_scrolled_window_new (NULL, NULL); - g_object_set ( - G_OBJECT (widget), - "hexpand", TRUE, - "vexpand", TRUE, - "halign", GTK_ALIGN_FILL, - "valign", GTK_ALIGN_FILL, - "hscrollbar-policy", GTK_POLICY_AUTOMATIC, - "vscrollbar-policy", GTK_POLICY_AUTOMATIC, - NULL); - - gtk_box_pack_start (GTK_BOX (vbox), widget, TRUE, TRUE, 0); - - scrolled_window = GTK_SCROLLED_WINDOW (widget); - - widget = webkit_web_view_new (); - g_object_set ( - G_OBJECT (widget), - "hexpand", TRUE, - "vexpand", TRUE, - "halign", GTK_ALIGN_FILL, - "valign", GTK_ALIGN_FILL, - NULL); - gtk_container_add (GTK_CONTAINER (scrolled_window), widget); - - prompter_google->priv->web_view = WEBKIT_WEB_VIEW (widget); - - progress_bar = gtk_progress_bar_new (); - g_object_set ( - G_OBJECT (progress_bar), - "hexpand", TRUE, - "vexpand", FALSE, - "halign", GTK_ALIGN_FILL, - "valign", GTK_ALIGN_START, - "orientation", GTK_ORIENTATION_HORIZONTAL, - "ellipsize", PANGO_ELLIPSIZE_END, - "fraction", 0.0, - NULL); - gtk_style_context_add_class (gtk_widget_get_style_context (progress_bar), GTK_STYLE_CLASS_OSD); - - gtk_box_pack_start (GTK_BOX (vbox), progress_bar, FALSE, FALSE, 0); - - gtk_widget_show_all (GTK_WIDGET (grid)); - - uri = cpi_google_create_auth_uri (prompter_google->priv->cred_source); - if (!uri) { - success = FALSE; - } else { - WebKitWebView *web_view = prompter_google->priv->web_view; - gulong load_finished_handler_id, progress_handler_id; - - load_finished_handler_id = g_signal_connect (web_view, "load-changed", - G_CALLBACK (cpi_google_document_load_changed_cb), prompter_google); - progress_handler_id = g_signal_connect (web_view, "notify::estimated-load-progress", - G_CALLBACK (cpi_google_notify_estimated_load_progress_cb), progress_bar); - - webkit_web_view_load_uri (web_view, uri); - - success = gtk_dialog_run (prompter_google->priv->dialog) == GTK_RESPONSE_OK; - - if (load_finished_handler_id) - g_signal_handler_disconnect (web_view, load_finished_handler_id); - if (progress_handler_id) - g_signal_handler_disconnect (web_view, progress_handler_id); - } - - if (prompter_google->priv->cancellable) - g_cancellable_cancel (prompter_google->priv->cancellable); - - prompter_google->priv->web_view = NULL; - prompter_google->priv->dialog = NULL; - gtk_widget_destroy (dialog); - - g_string_free (info_markup, TRUE); - g_free (title); - - return success; -#else /* ENABLE_GOOGLE_AUTH */ - return FALSE; -#endif /* ENABLE_GOOGLE_AUTH */ -} - -static void -e_credentials_prompter_impl_google_free_prompt_data (ECredentialsPrompterImplGoogle *prompter_google) -{ - g_return_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_GOOGLE (prompter_google)); - - prompter_google->priv->prompt_id = NULL; - - g_clear_object (&prompter_google->priv->auth_source); - g_clear_object (&prompter_google->priv->cred_source); - - g_free (prompter_google->priv->error_text); - prompter_google->priv->error_text = NULL; - - e_named_parameters_free (prompter_google->priv->credentials); - prompter_google->priv->credentials = NULL; -} - -static gboolean -e_credentials_prompter_impl_google_manage_dialog_idle_cb (gpointer user_data) -{ - ECredentialsPrompterImplGoogle *prompter_google = user_data; - - if (g_source_is_destroyed (g_main_current_source ())) - return FALSE; - - g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_GOOGLE (prompter_google), FALSE); - - g_mutex_lock (&prompter_google->priv->property_lock); - if (g_source_get_id (g_main_current_source ()) == prompter_google->priv->show_dialog_idle_id) { - gboolean success, refresh_failed_with_transport_error; - - prompter_google->priv->show_dialog_idle_id = 0; - refresh_failed_with_transport_error = prompter_google->priv->refresh_failed_with_transport_error; - - g_mutex_unlock (&prompter_google->priv->property_lock); - - g_warn_if_fail (prompter_google->priv->dialog == NULL); - - if (e_named_parameters_exists (prompter_google->priv->credentials, E_SOURCE_CREDENTIAL_PASSWORD)) - success = TRUE; - else if (refresh_failed_with_transport_error) - success = FALSE; - else - success = e_credentials_prompter_impl_google_show_dialog (prompter_google); - - e_credentials_prompter_impl_prompt_finish ( - E_CREDENTIALS_PROMPTER_IMPL (prompter_google), - prompter_google->priv->prompt_id, - success ? prompter_google->priv->credentials : NULL); - - e_credentials_prompter_impl_google_free_prompt_data (prompter_google); - } else { - gpointer prompt_id = prompter_google->priv->prompt_id; - - g_warning ("%s: Prompt's %p source cancelled? current:%d expected:%d", G_STRFUNC, prompt_id, (gint) g_source_get_id (g_main_current_source ()), (gint) prompter_google->priv->show_dialog_idle_id); - - if (!prompter_google->priv->show_dialog_idle_id) - e_credentials_prompter_impl_google_free_prompt_data (prompter_google); - - g_mutex_unlock (&prompter_google->priv->property_lock); - - if (prompt_id) - e_credentials_prompter_impl_prompt_finish (E_CREDENTIALS_PROMPTER_IMPL (prompter_google), prompt_id, NULL); - } - - return FALSE; -} - -#ifdef ENABLE_GOOGLE_AUTH -typedef struct { - GWeakRef *prompter_google; /* ECredentialsPrompterImplGoogle * */ - GCancellable *cancellable; - ESource *cred_source; - ESourceRegistry *registry; - gchar *secret; -} CheckExistingThreadData; - -static void -check_existing_thread_data_free (gpointer user_data) -{ - CheckExistingThreadData *td = user_data; - - if (td) { - e_weak_ref_free (td->prompter_google); - g_clear_object (&td->cancellable); - g_clear_object (&td->cred_source); - g_clear_object (&td->registry); - g_free (td->secret); - g_free (td); - } -} - -static gpointer -cpi_google_check_existing_token_thread (gpointer user_data) -{ - CheckExistingThreadData *td = user_data; - ECredentialsPrompterImplGoogle *prompter_google; - GCancellable *cancellable; - gchar *refresh_token = NULL; - - g_return_val_if_fail (td != NULL, NULL); - - cancellable = td->cancellable; - - prompter_google = g_weak_ref_get (td->prompter_google); - if (!prompter_google) - goto exit; - - if (g_cancellable_is_cancelled (cancellable)) - goto exit; - - if (!e_source_credentials_google_util_decode_from_secret (td->secret, E_GOOGLE_SECRET_REFRESH_TOKEN, &refresh_token, NULL)) - goto exit; - - g_mutex_lock (&prompter_google->priv->property_lock); - prompter_google->priv->refresh_failed_with_transport_error = FALSE; - g_mutex_unlock (&prompter_google->priv->property_lock); - - if (refresh_token) { - gchar *post_data, *response_json = NULL; - guint soup_status; - - post_data = cpi_google_create_refresh_token_post_data (refresh_token); - g_warn_if_fail (post_data != NULL); - if (!post_data) - goto exit; - - soup_status = cpi_google_post_data_sync (GOOGLE_TOKEN_URI, post_data, - td->registry, td->cred_source, cancellable, &response_json); - - if (SOUP_STATUS_IS_SUCCESSFUL (soup_status) && response_json) { - gchar *access_token = NULL, *expires_in = NULL; - - if (e_source_credentials_google_util_decode_from_secret (response_json, - "access_token", &access_token, - "expires_in", &expires_in, - NULL) && access_token && expires_in) { - cpi_google_update_prompter_credentials (td->prompter_google, - refresh_token, access_token, expires_in, cancellable); - } - - g_free (access_token); - g_free (expires_in); - } else if (SOUP_STATUS_IS_TRANSPORT_ERROR (soup_status)) { - g_mutex_lock (&prompter_google->priv->property_lock); - prompter_google->priv->refresh_failed_with_transport_error = TRUE; - g_mutex_unlock (&prompter_google->priv->property_lock); - } - - g_free (response_json); - g_free (post_data); - } - - exit: - if (prompter_google && !g_cancellable_is_cancelled (cancellable)) { - g_mutex_lock (&prompter_google->priv->property_lock); - prompter_google->priv->show_dialog_idle_id = g_idle_add ( - e_credentials_prompter_impl_google_manage_dialog_idle_cb, - prompter_google); - g_mutex_unlock (&prompter_google->priv->property_lock); - } - - g_clear_object (&prompter_google); - g_free (refresh_token); - - check_existing_thread_data_free (td); - - return NULL; -} -#endif /* ENABLE_GOOGLE_AUTH */ - -static void -e_credentials_prompter_impl_google_process_prompt (ECredentialsPrompterImpl *prompter_impl, - gpointer prompt_id, - ESource *auth_source, - ESource *cred_source, - const gchar *error_text, - const ENamedParameters *credentials) -{ - ECredentialsPrompterImplGoogle *prompter_google; - - g_return_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_GOOGLE (prompter_impl)); - - prompter_google = E_CREDENTIALS_PROMPTER_IMPL_GOOGLE (prompter_impl); - g_return_if_fail (prompter_google->priv->prompt_id == NULL); - - g_mutex_lock (&prompter_google->priv->property_lock); - if (prompter_google->priv->show_dialog_idle_id != 0) { - g_mutex_unlock (&prompter_google->priv->property_lock); - g_warning ("%s: Already processing other prompt", G_STRFUNC); - return; - } - g_mutex_unlock (&prompter_google->priv->property_lock); - - prompter_google->priv->prompt_id = prompt_id; - prompter_google->priv->auth_source = g_object_ref (auth_source); - prompter_google->priv->cred_source = g_object_ref (cred_source); - prompter_google->priv->error_text = g_strdup (error_text); - prompter_google->priv->credentials = e_named_parameters_new_clone (credentials); - prompter_google->priv->cancellable = g_cancellable_new (); - -#ifdef ENABLE_GOOGLE_AUTH - e_named_parameters_set (prompter_google->priv->credentials, E_SOURCE_CREDENTIAL_PASSWORD, NULL); - - /* Read stored secret directly from the secret store, if not provided, because - e_source_credentials_provider_impl_google_lookup_sync() returns the secret - only if the stored access token is not expired. - */ - if (!e_named_parameters_exists (prompter_google->priv->credentials, E_SOURCE_CREDENTIAL_GOOGLE_SECRET)) { - gchar *secret_uid = NULL; - - if (e_source_credentials_google_util_generate_secret_uid (cred_source, &secret_uid)) { - gchar *secret = NULL; - - if (e_secret_store_lookup_sync (secret_uid, &secret, prompter_google->priv->cancellable, NULL)) { - e_named_parameters_set (prompter_google->priv->credentials, - E_SOURCE_CREDENTIAL_GOOGLE_SECRET, secret); - } - - g_free (secret); - } - - g_free (secret_uid); - } - - if (e_named_parameters_exists (prompter_google->priv->credentials, E_SOURCE_CREDENTIAL_GOOGLE_SECRET)) { - ECredentialsPrompter *prompter; - CheckExistingThreadData *td; - GThread *thread; - - prompter = e_credentials_prompter_impl_get_credentials_prompter (prompter_impl); - - td = g_new0 (CheckExistingThreadData, 1); - td->prompter_google = e_weak_ref_new (prompter_google); - td->cancellable = g_object_ref (prompter_google->priv->cancellable); - td->secret = g_strdup (e_named_parameters_get (prompter_google->priv->credentials, E_SOURCE_CREDENTIAL_GOOGLE_SECRET)); - td->cred_source = g_object_ref (cred_source); - td->registry = g_object_ref (e_credentials_prompter_get_registry (prompter)); - - thread = g_thread_new (G_STRFUNC, cpi_google_check_existing_token_thread, td); - g_thread_unref (thread); - } else { -#endif /* ENABLE_GOOGLE_AUTH */ - g_mutex_lock (&prompter_google->priv->property_lock); - prompter_google->priv->refresh_failed_with_transport_error = FALSE; - prompter_google->priv->show_dialog_idle_id = g_idle_add ( - e_credentials_prompter_impl_google_manage_dialog_idle_cb, - prompter_google); - g_mutex_unlock (&prompter_google->priv->property_lock); -#ifdef ENABLE_GOOGLE_AUTH - } -#endif /* ENABLE_GOOGLE_AUTH */ -} - -static void -e_credentials_prompter_impl_google_cancel_prompt (ECredentialsPrompterImpl *prompter_impl, - gpointer prompt_id) -{ - ECredentialsPrompterImplGoogle *prompter_google; - - g_return_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_GOOGLE (prompter_impl)); - - prompter_google = E_CREDENTIALS_PROMPTER_IMPL_GOOGLE (prompter_impl); - g_return_if_fail (prompter_google->priv->prompt_id == prompt_id); - - if (prompter_google->priv->cancellable) - g_cancellable_cancel (prompter_google->priv->cancellable); - - /* This also closes the dialog. */ - if (prompter_google->priv->dialog) - gtk_dialog_response (prompter_google->priv->dialog, GTK_RESPONSE_CANCEL); -} - -static void -e_credentials_prompter_impl_google_dispose (GObject *object) -{ - ECredentialsPrompterImplGoogle *prompter_google = E_CREDENTIALS_PROMPTER_IMPL_GOOGLE (object); - - g_mutex_lock (&prompter_google->priv->property_lock); - if (prompter_google->priv->show_dialog_idle_id) { - g_source_remove (prompter_google->priv->show_dialog_idle_id); - prompter_google->priv->show_dialog_idle_id = 0; - } - g_mutex_unlock (&prompter_google->priv->property_lock); - - if (prompter_google->priv->cancellable) { - g_cancellable_cancel (prompter_google->priv->cancellable); - g_clear_object (&prompter_google->priv->cancellable); - } - - g_warn_if_fail (prompter_google->priv->prompt_id == NULL); - g_warn_if_fail (prompter_google->priv->dialog == NULL); - - e_credentials_prompter_impl_google_free_prompt_data (prompter_google); - - /* Chain up to parent's method. */ - G_OBJECT_CLASS (e_credentials_prompter_impl_google_parent_class)->dispose (object); -} - -static void -e_credentials_prompter_impl_google_finalize (GObject *object) -{ - ECredentialsPrompterImplGoogle *prompter_google = E_CREDENTIALS_PROMPTER_IMPL_GOOGLE (object); - - g_mutex_clear (&prompter_google->priv->property_lock); - - /* Chain up to parent's method. */ - G_OBJECT_CLASS (e_credentials_prompter_impl_google_parent_class)->finalize (object); -} - -static void -e_credentials_prompter_impl_google_class_init (ECredentialsPrompterImplGoogleClass *class) -{ - static const gchar *authentication_methods[] = { - "Google", - NULL - }; - - GObjectClass *object_class; - ECredentialsPrompterImplClass *prompter_impl_class; - - g_type_class_add_private (class, sizeof (ECredentialsPrompterImplGooglePrivate)); - - object_class = G_OBJECT_CLASS (class); - object_class->dispose = e_credentials_prompter_impl_google_dispose; - object_class->finalize = e_credentials_prompter_impl_google_finalize; - - prompter_impl_class = E_CREDENTIALS_PROMPTER_IMPL_CLASS (class); - prompter_impl_class->authentication_methods = (const gchar * const *) authentication_methods; - prompter_impl_class->process_prompt = e_credentials_prompter_impl_google_process_prompt; - prompter_impl_class->cancel_prompt = e_credentials_prompter_impl_google_cancel_prompt; -} - -static void -e_credentials_prompter_impl_google_init (ECredentialsPrompterImplGoogle *prompter_google) -{ - prompter_google->priv = G_TYPE_INSTANCE_GET_PRIVATE (prompter_google, - E_TYPE_CREDENTIALS_PROMPTER_IMPL_GOOGLE, ECredentialsPrompterImplGooglePrivate); - - g_mutex_init (&prompter_google->priv->property_lock); -} - -/** - * e_credentials_prompter_impl_google_new: - * - * Creates a new instance of an #ECredentialsPrompterImplGoogle. - * - * Returns: (transfer full): a newly created #ECredentialsPrompterImplGoogle, - * which should be freed with g_object_unref() when no longer needed. - * - * Since: 3.20 - **/ -ECredentialsPrompterImpl * -e_credentials_prompter_impl_google_new (void) -{ - return g_object_new (E_TYPE_CREDENTIALS_PROMPTER_IMPL_GOOGLE, NULL); -} diff --git a/src/libedataserverui/e-credentials-prompter-impl-google.h b/src/libedataserverui/e-credentials-prompter-impl-google.h deleted file mode 100644 index c4f3aea53..000000000 --- a/src/libedataserverui/e-credentials-prompter-impl-google.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. - * - */ - -#if !defined (__LIBEDATASERVERUI_H_INSIDE__) && !defined (LIBEDATASERVERUI_COMPILATION) -#error "Only <libedataserverui/libedataserverui.h> should be included directly." -#endif - -#ifndef E_CREDENTIALS_PROMPTER_IMPL_GOOGLE_H -#define E_CREDENTIALS_PROMPTER_IMPL_GOOGLE_H - -#include <glib.h> -#include <glib-object.h> - -#include <libedataserverui/e-credentials-prompter-impl.h> - -/* Standard GObject macros */ -#define E_TYPE_CREDENTIALS_PROMPTER_IMPL_GOOGLE \ - (e_credentials_prompter_impl_google_get_type ()) -#define E_CREDENTIALS_PROMPTER_IMPL_GOOGLE(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST \ - ((obj), E_TYPE_CREDENTIALS_PROMPTER_IMPL_GOOGLE, ECredentialsPrompterImplGoogle)) -#define E_CREDENTIALS_PROMPTER_IMPL_GOOGLE_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_CAST \ - ((cls), E_TYPE_CREDENTIALS_PROMPTER_IMPL_GOOGLE, ECredentialsPrompterImplGoogleClass)) -#define E_IS_CREDENTIALS_PROMPTER_IMPL_GOOGLE(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE \ - ((obj), E_TYPE_CREDENTIALS_PROMPTER_IMPL_GOOGLE)) -#define E_IS_CREDENTIALS_PROMPTER_IMPL_GOOGLE_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_TYPE \ - ((cls), E_TYPE_CREDENTIALS_PROMPTER_IMPL_GOOGLE)) -#define E_CREDENTIALS_PROMPTER_IMPL_GOOGLE_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS \ - ((obj), E_TYPE_CREDENTIALS_PROMPTER_IMPL_GOOGLE, ECredentialsPrompterImplGoogleClass)) - -G_BEGIN_DECLS - -typedef struct _ECredentialsPrompterImplGoogle ECredentialsPrompterImplGoogle; -typedef struct _ECredentialsPrompterImplGoogleClass ECredentialsPrompterImplGoogleClass; -typedef struct _ECredentialsPrompterImplGooglePrivate ECredentialsPrompterImplGooglePrivate; - -/** - * ECredentialsPrompterImplGoogle: - * - * Contains only private data that should be read and manipulated using the - * functions below. - * - * Since: 3.20 - **/ -struct _ECredentialsPrompterImplGoogle { - ECredentialsPrompterImpl parent; - ECredentialsPrompterImplGooglePrivate *priv; -}; - -struct _ECredentialsPrompterImplGoogleClass { - ECredentialsPrompterImplClass parent_class; -}; - -GType e_credentials_prompter_impl_google_get_type (void) G_GNUC_CONST; -ECredentialsPrompterImpl * - e_credentials_prompter_impl_google_new (void); - -G_END_DECLS - -#endif /* E_CREDENTIALS_PROMPTER_IMPL_GOOGLE_H */ diff --git a/src/libedataserverui/e-credentials-prompter-impl-oauth2.c b/src/libedataserverui/e-credentials-prompter-impl-oauth2.c new file mode 100644 index 000000000..9d92f29a8 --- /dev/null +++ b/src/libedataserverui/e-credentials-prompter-impl-oauth2.c @@ -0,0 +1,856 @@ +/* + * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + * + */ + +#include "evolution-data-server-config.h" + +#include <glib.h> +#include <glib/gi18n-lib.h> + +#include <gtk/gtk.h> + +#include <libedataserver/libedataserver.h> + +#include "e-credentials-prompter.h" +#include "e-credentials-prompter-impl-oauth2.h" + +#ifdef ENABLE_OAUTH2 +#include <webkit2/webkit2.h> +#endif /* ENABLE_OAUTH2 */ + +struct _ECredentialsPrompterImplOAuth2Private { + GMutex property_lock; + + EOAuth2Services *oauth2_services; + + gpointer prompt_id; + ESource *auth_source; + ESource *cred_source; + EOAuth2Service *service; + gchar *error_text; + ENamedParameters *credentials; + gboolean refresh_failed_with_transport_error; + + GtkDialog *dialog; +#ifdef ENABLE_OAUTH2 + WebKitWebView *web_view; +#endif + gulong show_dialog_idle_id; + + GCancellable *cancellable; +}; + +G_DEFINE_TYPE (ECredentialsPrompterImplOAuth2, e_credentials_prompter_impl_oauth2, E_TYPE_CREDENTIALS_PROMPTER_IMPL) + +#ifdef ENABLE_OAUTH2 + +static gchar * +cpi_oauth2_create_auth_uri (EOAuth2Service *service, + ESource *source) +{ + GHashTable *uri_query; + SoupURI *soup_uri; + gchar *uri; + + g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), NULL); + g_return_val_if_fail (E_IS_SOURCE (source), NULL); + + soup_uri = soup_uri_new (e_oauth2_service_get_authentication_uri (service)); + g_return_val_if_fail (soup_uri != NULL, NULL); + + uri_query = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + e_oauth2_service_prepare_authentication_uri_query (service, source, uri_query); + + soup_uri_set_query_from_form (soup_uri, uri_query); + + uri = soup_uri_to_string (soup_uri, FALSE); + + soup_uri_free (soup_uri); + g_hash_table_destroy (uri_query); + + return uri; +} + +static void +e_credentials_prompter_impl_oauth2_show_html (WebKitWebView *web_view, + const gchar *title, + const gchar *body_text) +{ + gchar *html; + + g_return_if_fail (WEBKIT_IS_WEB_VIEW (web_view)); + g_return_if_fail (title != NULL); + g_return_if_fail (body_text != NULL); + + html = g_strdup_printf ( + "<html>" + "<head><title>%s</title></head>" + "<body><div style=\"font-size:12pt; font-family:Helvetica,Arial;\">%s</div></body>" + "</html>", + title, + body_text); + webkit_web_view_load_html (web_view, html, "none-local://"); + g_free (html); +} + +static gboolean +e_credentials_prompter_impl_oauth2_finish_dialog_idle_cb (gpointer user_data) +{ + ECredentialsPrompterImplOAuth2 *prompter_oauth2 = user_data; + + if (g_source_is_destroyed (g_main_current_source ())) + return FALSE; + + g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_OAUTH2 (prompter_oauth2), FALSE); + + g_mutex_lock (&prompter_oauth2->priv->property_lock); + if (g_source_get_id (g_main_current_source ()) == prompter_oauth2->priv->show_dialog_idle_id) { + prompter_oauth2->priv->show_dialog_idle_id = 0; + g_mutex_unlock (&prompter_oauth2->priv->property_lock); + + g_warn_if_fail (prompter_oauth2->priv->dialog != NULL); + + if (prompter_oauth2->priv->error_text) { + e_credentials_prompter_impl_oauth2_show_html (prompter_oauth2->priv->web_view, + "Finished with error", prompter_oauth2->priv->error_text); + } else { + gtk_dialog_response (prompter_oauth2->priv->dialog, GTK_RESPONSE_OK); + } + } else { + g_warning ("%s: Source was cancelled? current:%d expected:%d", G_STRFUNC, (gint) g_source_get_id (g_main_current_source ()), (gint) prompter_oauth2->priv->show_dialog_idle_id); + g_mutex_unlock (&prompter_oauth2->priv->property_lock); + } + + return FALSE; +} + +typedef struct { + GWeakRef *prompter_oauth2; /* ECredentialsPrompterImplOAuth2 * */ + GCancellable *cancellable; + ESource *cred_source; + ESourceRegistry *registry; + gchar *authorization_code; + EOAuth2Service *service; +} AccessTokenThreadData; + +static void +access_token_thread_data_free (gpointer user_data) +{ + AccessTokenThreadData *td = user_data; + + if (td) { + e_weak_ref_free (td->prompter_oauth2); + g_clear_object (&td->cancellable); + g_clear_object (&td->cred_source); + g_clear_object (&td->registry); + g_clear_object (&td->service); + g_free (td->authorization_code); + g_free (td); + } +} + +static gpointer +cpi_oauth2_get_access_token_thread (gpointer user_data) +{ + AccessTokenThreadData *td = user_data; + ECredentialsPrompterImplOAuth2 *prompter_oauth2; + GError *local_error = NULL; + gboolean success = FALSE; + + g_return_val_if_fail (td != NULL, NULL); + + if (!g_cancellable_set_error_if_cancelled (td->cancellable, &local_error)) { + EOAuth2ServiceRefSourceFunc ref_source; + + ref_source = (EOAuth2ServiceRefSourceFunc) e_source_registry_ref_source; + + success = e_oauth2_service_receive_and_store_token_sync (td->service, td->cred_source, + td->authorization_code, ref_source, td->registry, td->cancellable, &local_error); + } + + prompter_oauth2 = g_weak_ref_get (td->prompter_oauth2); + if (prompter_oauth2 && !g_cancellable_is_cancelled (td->cancellable)) { + g_clear_pointer (&prompter_oauth2->priv->error_text, g_free); + + if (!success) { + prompter_oauth2->priv->error_text = g_strdup_printf ( + _("Failed to obtain access token from address “%s”: %s"), + e_oauth2_service_get_refresh_uri (td->service), + local_error ? local_error->message : _("Unknown error")); + } + + g_mutex_lock (&prompter_oauth2->priv->property_lock); + prompter_oauth2->priv->show_dialog_idle_id = g_idle_add ( + e_credentials_prompter_impl_oauth2_finish_dialog_idle_cb, + prompter_oauth2); + g_mutex_unlock (&prompter_oauth2->priv->property_lock); + } + + g_clear_object (&prompter_oauth2); + g_clear_error (&local_error); + + access_token_thread_data_free (td); + + return NULL; +} + +static void +cpi_oauth2_document_load_changed_cb (WebKitWebView *web_view, + WebKitLoadEvent load_event, + ECredentialsPrompterImplOAuth2 *prompter_oauth2) +{ + const gchar *title, *uri; + gchar *authorization_code = NULL; + + g_return_if_fail (WEBKIT_IS_WEB_VIEW (web_view)); + g_return_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_OAUTH2 (prompter_oauth2)); + + if (load_event != WEBKIT_LOAD_FINISHED) + return; + + title = webkit_web_view_get_title (web_view); + uri = webkit_web_view_get_uri (web_view); + if (!title || !uri) + return; + + g_return_if_fail (prompter_oauth2->priv->service != NULL); + + if (!e_oauth2_service_extract_authorization_code (prompter_oauth2->priv->service, + title, uri, &authorization_code)) + return; + + if (authorization_code) { + ECredentialsPrompter *prompter; + ECredentialsPrompterImpl *prompter_impl; + AccessTokenThreadData *td; + GThread *thread; + + e_credentials_prompter_impl_oauth2_show_html (web_view, + "Checking returned code", _("Requesting access token, please wait...")); + + gtk_widget_set_sensitive (GTK_WIDGET (web_view), FALSE); + + e_named_parameters_set (prompter_oauth2->priv->credentials, E_SOURCE_CREDENTIAL_PASSWORD, NULL); + + prompter_impl = E_CREDENTIALS_PROMPTER_IMPL (prompter_oauth2); + prompter = e_credentials_prompter_impl_get_credentials_prompter (prompter_impl); + + td = g_new0 (AccessTokenThreadData, 1); + td->prompter_oauth2 = e_weak_ref_new (prompter_oauth2); + td->service = g_object_ref (prompter_oauth2->priv->service); + td->cancellable = g_object_ref (prompter_oauth2->priv->cancellable); + td->cred_source = g_object_ref (prompter_oauth2->priv->cred_source); + td->registry = g_object_ref (e_credentials_prompter_get_registry (prompter)); + td->authorization_code = authorization_code; + + thread = g_thread_new (G_STRFUNC, cpi_oauth2_get_access_token_thread, td); + g_thread_unref (thread); + } else { + g_cancellable_cancel (prompter_oauth2->priv->cancellable); + gtk_dialog_response (prompter_oauth2->priv->dialog, GTK_RESPONSE_CANCEL); + } +} + +static void +cpi_oauth2_notify_estimated_load_progress_cb (WebKitWebView *web_view, + GParamSpec *param, + GtkProgressBar *progress_bar) +{ + gboolean visible; + gdouble progress; + + g_return_if_fail (GTK_IS_PROGRESS_BAR (progress_bar)); + + progress = webkit_web_view_get_estimated_load_progress (web_view); + visible = progress > 1e-9 && progress < 1 - 1e-9; + + gtk_progress_bar_set_fraction (progress_bar, visible ? progress : 0.0); +} + +static void +credentials_prompter_impl_oauth2_get_prompt_strings (ESourceRegistry *registry, + ESource *source, + const gchar *service_display_name, + gchar **prompt_title, + GString **prompt_description) +{ + GString *description; + gchar *message; + gchar *display_name; + + /* Known types */ + enum { + TYPE_UNKNOWN, + TYPE_AMBIGUOUS, + TYPE_ADDRESS_BOOK, + TYPE_CALENDAR, + TYPE_MAIL_ACCOUNT, + TYPE_MAIL_TRANSPORT, + TYPE_MEMO_LIST, + TYPE_TASK_LIST + } type = TYPE_UNKNOWN; + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_ADDRESS_BOOK)) { + type = TYPE_ADDRESS_BOOK; + } + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) { + if (type == TYPE_UNKNOWN) + type = TYPE_CALENDAR; + else + type = TYPE_AMBIGUOUS; + } + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_ACCOUNT)) { + if (type == TYPE_UNKNOWN) + type = TYPE_MAIL_ACCOUNT; + else + type = TYPE_AMBIGUOUS; + } + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_TRANSPORT)) { + if (type == TYPE_UNKNOWN) + type = TYPE_MAIL_TRANSPORT; + else + type = TYPE_AMBIGUOUS; + } + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_MEMO_LIST)) { + if (type == TYPE_UNKNOWN) + type = TYPE_MEMO_LIST; + else + type = TYPE_AMBIGUOUS; + } + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) { + if (type == TYPE_UNKNOWN) + type = TYPE_TASK_LIST; + else + type = TYPE_AMBIGUOUS; + } + + switch (type) { + case TYPE_ADDRESS_BOOK: + /* Translators: The %s is replaced with an OAuth2 service display name, like the strings from "OAuth2Service" translation context, + thus it can form a string like "Google Address Book authentication request". */ + message = g_strdup_printf (_("%s Address Book authentication request"), service_display_name); + break; + case TYPE_CALENDAR: + /* Translators: The %s is replaced with an OAuth2 service display name, like the strings from "OAuth2Service" translation context, + thus it can form a string like "Google Calendar authentication request". */ + message = g_strdup_printf (_("%s Calendar authentication request"), service_display_name); + break; + case TYPE_MEMO_LIST: + /* Translators: The %s is replaced with an OAuth2 service display name, like the strings from "OAuth2Service" translation context, + thus it can form a string like "Google Memo List authentication request". */ + message = g_strdup_printf (_("%s Memo List authentication request"), service_display_name); + break; + case TYPE_TASK_LIST: + /* Translators: The %s is replaced with an OAuth2 service display name, like the strings from "OAuth2Service" translation context, + thus it can form a string like "Google Task List authentication request". */ + message = g_strdup_printf (_("%s Task List authentication request"), service_display_name); + break; + case TYPE_MAIL_ACCOUNT: + case TYPE_MAIL_TRANSPORT: + /* Translators: The %s is replaced with an OAuth2 service display name, like the strings from "OAuth2Service" translation context, + thus it can form a string like "Google Mail authentication request". */ + message = g_strdup_printf (_("%s Mail authentication request"), service_display_name); + break; + default: /* generic account prompt */ + /* Translators: The %s is replaced with an OAuth2 service display name, like the strings from "OAuth2Service" translation context, + thus it can form a string like "Google account authentication request". */ + message = g_strdup_printf (_("%s account authentication request"), service_display_name); + break; + } + + display_name = e_util_get_source_full_name (registry, source); + description = g_string_sized_new (256); + + g_string_append_printf (description, "<big><b>%s</b></big>\n\n", message); + switch (type) { + case TYPE_ADDRESS_BOOK: + g_string_append_printf (description, + /* Translators: The first %s is replaced with an OAuth2 service display name, like the strings from "OAuth2Service" translation context, + thus it can form a string like "Login to your Google account and...". The second %s is the actual source display name, + like "On This Computer : Personal". */ + _("Login to your %s account and accept conditions in order to access your address book “%s”."), service_display_name, display_name); + break; + case TYPE_CALENDAR: + g_string_append_printf (description, + /* Translators: The first %s is replaced with an OAuth2 service display name, like the strings from "OAuth2Service" translation context, + thus it can form a string like "Login to your Google account and...". The second %s is the actual source display name, + like "On This Computer : Personal". */ + _("Login to your %s account and accept conditions in order to access your calendar “%s”."), service_display_name, display_name); + break; + case TYPE_MAIL_ACCOUNT: + g_string_append_printf (description, + /* Translators: The first %s is replaced with an OAuth2 service display name, like the strings from "OAuth2Service" translation context, + thus it can form a string like "Login to your Google account and...". The second %s is the actual source display name, + like "On This Computer : Personal". */ + _("Login to your %s account and accept conditions in order to access your mail account “%s”."), service_display_name, display_name); + break; + case TYPE_MAIL_TRANSPORT: + g_string_append_printf (description, + /* Translators: The first %s is replaced with an OAuth2 service display name, like the strings from "OAuth2Service" translation context, + thus it can form a string like "Login to your Google account and...". The second %s is the actual source display name, + like "On This Computer : Personal". */ + _("Login to your %s account and accept conditions in order to access your mail transport “%s”."), service_display_name, display_name); + break; + case TYPE_MEMO_LIST: + g_string_append_printf (description, + /* Translators: The first %s is replaced with an OAuth2 service display name, like the strings from "OAuth2Service" translation context, + thus it can form a string like "Login to your Google account and...". The second %s is the actual source display name, + like "On This Computer : Personal". */ + _("Login to your %s account and accept conditions in order to access your memo list “%s”."), service_display_name, display_name); + break; + case TYPE_TASK_LIST: + g_string_append_printf (description, + /* Translators: The first %s is replaced with an OAuth2 service display name, like the strings from "OAuth2Service" translation context, + thus it can form a string like "Login to your Google account and...". The second %s is the actual source display name, + like "On This Computer : Personal". */ + _("Login to your %s account and accept conditions in order to access your task list “%s”."), service_display_name, display_name); + break; + default: /* generic account prompt */ + g_string_append_printf (description, + /* Translators: The first %s is replaced with an OAuth2 service display name, like the strings from "OAuth2Service" translation context, + thus it can form a string like "Login to your Google account and...". The second %s is the actual source display name, + like "On This Computer : Personal". */ + _("Login to your %s account and accept conditions in order to access your account “%s”."), service_display_name, display_name); + break; + } + + *prompt_title = message; + *prompt_description = description; + + g_free (display_name); +} +#endif /* ENABLE_OAUTH2 */ + +static gboolean +e_credentials_prompter_impl_oauth2_show_dialog (ECredentialsPrompterImplOAuth2 *prompter_oauth2) +{ +#ifdef ENABLE_OAUTH2 + GtkWidget *dialog, *content_area, *widget, *progress_bar, *vbox; + GtkGrid *grid; + GtkScrolledWindow *scrolled_window; + GtkWindow *dialog_parent; + ECredentialsPrompter *prompter; + gchar *title, *uri; + GString *info_markup; + gint row = 0; + gboolean success; + + g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_OAUTH2 (prompter_oauth2), FALSE); + g_return_val_if_fail (prompter_oauth2->priv->prompt_id != NULL, FALSE); + g_return_val_if_fail (prompter_oauth2->priv->dialog == NULL, FALSE); + g_return_val_if_fail (prompter_oauth2->priv->service != NULL, FALSE); + + prompter = e_credentials_prompter_impl_get_credentials_prompter (E_CREDENTIALS_PROMPTER_IMPL (prompter_oauth2)); + g_return_val_if_fail (prompter != NULL, FALSE); + + dialog_parent = e_credentials_prompter_get_dialog_parent (prompter); + + credentials_prompter_impl_oauth2_get_prompt_strings (e_credentials_prompter_get_registry (prompter), + prompter_oauth2->priv->auth_source, + e_oauth2_service_get_display_name (prompter_oauth2->priv->service), + &title, &info_markup); + if (prompter_oauth2->priv->error_text && *prompter_oauth2->priv->error_text) { + gchar *escaped = g_markup_printf_escaped ("%s", prompter_oauth2->priv->error_text); + + g_string_append_printf (info_markup, "\n\n%s", escaped); + g_free (escaped); + } + + dialog = gtk_dialog_new_with_buttons (title, dialog_parent, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + _("_Cancel"), GTK_RESPONSE_CANCEL, + NULL); + + gtk_window_set_default_size (GTK_WINDOW (dialog), 320, 480); + + prompter_oauth2->priv->dialog = GTK_DIALOG (dialog); + gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE); + if (dialog_parent) + gtk_window_set_transient_for (GTK_WINDOW (dialog), dialog_parent); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER_ON_PARENT); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 12); + + content_area = gtk_dialog_get_content_area (prompter_oauth2->priv->dialog); + + /* Override GtkDialog defaults */ + gtk_box_set_spacing (GTK_BOX (content_area), 12); + gtk_container_set_border_width (GTK_CONTAINER (content_area), 0); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_column_spacing (grid, 12); + gtk_grid_set_row_spacing (grid, 6); + + gtk_box_pack_start (GTK_BOX (content_area), GTK_WIDGET (grid), FALSE, TRUE, 0); + + /* Info Label */ + widget = gtk_label_new (NULL); + gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE); + gtk_label_set_markup (GTK_LABEL (widget), info_markup->str); + g_object_set ( + G_OBJECT (widget), + "hexpand", TRUE, + "vexpand", FALSE, + "halign", GTK_ALIGN_FILL, + "valign", GTK_ALIGN_CENTER, + "width-chars", 60, + "max-width-chars", 80, + "xalign", 0.0, + NULL); + + gtk_grid_attach (grid, widget, 0, row, 1, 1); + row++; + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 1); + g_object_set ( + G_OBJECT (vbox), + "hexpand", TRUE, + "vexpand", TRUE, + "halign", GTK_ALIGN_FILL, + "valign", GTK_ALIGN_FILL, + NULL); + + gtk_grid_attach (grid, vbox, 0, row, 1, 1); + + widget = gtk_scrolled_window_new (NULL, NULL); + g_object_set ( + G_OBJECT (widget), + "hexpand", TRUE, + "vexpand", TRUE, + "halign", GTK_ALIGN_FILL, + "valign", GTK_ALIGN_FILL, + "hscrollbar-policy", GTK_POLICY_AUTOMATIC, + "vscrollbar-policy", GTK_POLICY_AUTOMATIC, + NULL); + + gtk_box_pack_start (GTK_BOX (vbox), widget, TRUE, TRUE, 0); + + scrolled_window = GTK_SCROLLED_WINDOW (widget); + + widget = webkit_web_view_new (); + g_object_set ( + G_OBJECT (widget), + "hexpand", TRUE, + "vexpand", TRUE, + "halign", GTK_ALIGN_FILL, + "valign", GTK_ALIGN_FILL, + NULL); + gtk_container_add (GTK_CONTAINER (scrolled_window), widget); + + prompter_oauth2->priv->web_view = WEBKIT_WEB_VIEW (widget); + + progress_bar = gtk_progress_bar_new (); + g_object_set ( + G_OBJECT (progress_bar), + "hexpand", TRUE, + "vexpand", FALSE, + "halign", GTK_ALIGN_FILL, + "valign", GTK_ALIGN_START, + "orientation", GTK_ORIENTATION_HORIZONTAL, + "ellipsize", PANGO_ELLIPSIZE_END, + "fraction", 0.0, + NULL); + gtk_style_context_add_class (gtk_widget_get_style_context (progress_bar), GTK_STYLE_CLASS_OSD); + + gtk_box_pack_start (GTK_BOX (vbox), progress_bar, FALSE, FALSE, 0); + + gtk_widget_show_all (GTK_WIDGET (grid)); + + uri = cpi_oauth2_create_auth_uri (prompter_oauth2->priv->service, prompter_oauth2->priv->cred_source); + if (!uri) { + success = FALSE; + } else { + WebKitWebView *web_view = prompter_oauth2->priv->web_view; + gulong load_finished_handler_id, progress_handler_id; + + load_finished_handler_id = g_signal_connect (web_view, "load-changed", + G_CALLBACK (cpi_oauth2_document_load_changed_cb), prompter_oauth2); + progress_handler_id = g_signal_connect (web_view, "notify::estimated-load-progress", + G_CALLBACK (cpi_oauth2_notify_estimated_load_progress_cb), progress_bar); + + webkit_web_view_load_uri (web_view, uri); + + success = gtk_dialog_run (prompter_oauth2->priv->dialog) == GTK_RESPONSE_OK; + + if (load_finished_handler_id) + g_signal_handler_disconnect (web_view, load_finished_handler_id); + if (progress_handler_id) + g_signal_handler_disconnect (web_view, progress_handler_id); + } + + if (prompter_oauth2->priv->cancellable) + g_cancellable_cancel (prompter_oauth2->priv->cancellable); + + prompter_oauth2->priv->web_view = NULL; + prompter_oauth2->priv->dialog = NULL; + gtk_widget_destroy (dialog); + + g_string_free (info_markup, TRUE); + g_free (title); + + return success; +#else /* ENABLE_OAUTH2 */ + return FALSE; +#endif /* ENABLE_OAUTH2 */ +} + +static void +e_credentials_prompter_impl_oauth2_free_prompt_data (ECredentialsPrompterImplOAuth2 *prompter_oauth2) +{ + g_return_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_OAUTH2 (prompter_oauth2)); + + prompter_oauth2->priv->prompt_id = NULL; + + g_clear_object (&prompter_oauth2->priv->auth_source); + g_clear_object (&prompter_oauth2->priv->cred_source); + g_clear_object (&prompter_oauth2->priv->service); + + g_free (prompter_oauth2->priv->error_text); + prompter_oauth2->priv->error_text = NULL; + + e_named_parameters_free (prompter_oauth2->priv->credentials); + prompter_oauth2->priv->credentials = NULL; +} + +static gboolean +e_credentials_prompter_impl_oauth2_manage_dialog_idle_cb (gpointer user_data) +{ + ECredentialsPrompterImplOAuth2 *prompter_oauth2 = user_data; + + if (g_source_is_destroyed (g_main_current_source ())) + return FALSE; + + g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_OAUTH2 (prompter_oauth2), FALSE); + + g_mutex_lock (&prompter_oauth2->priv->property_lock); + if (g_source_get_id (g_main_current_source ()) == prompter_oauth2->priv->show_dialog_idle_id) { + gboolean success, has_service; + + prompter_oauth2->priv->show_dialog_idle_id = 0; + has_service = prompter_oauth2->priv->service != NULL; + + g_mutex_unlock (&prompter_oauth2->priv->property_lock); + + g_warn_if_fail (prompter_oauth2->priv->dialog == NULL); + + if (has_service) + success = e_credentials_prompter_impl_oauth2_show_dialog (prompter_oauth2); + else + success = FALSE; + + e_credentials_prompter_impl_prompt_finish ( + E_CREDENTIALS_PROMPTER_IMPL (prompter_oauth2), + prompter_oauth2->priv->prompt_id, + success ? prompter_oauth2->priv->credentials : NULL); + + e_credentials_prompter_impl_oauth2_free_prompt_data (prompter_oauth2); + } else { + gpointer prompt_id = prompter_oauth2->priv->prompt_id; + + g_warning ("%s: Prompt's %p source cancelled? current:%d expected:%d", G_STRFUNC, prompt_id, (gint) g_source_get_id (g_main_current_source ()), (gint) prompter_oauth2->priv->show_dialog_idle_id); + + if (!prompter_oauth2->priv->show_dialog_idle_id) + e_credentials_prompter_impl_oauth2_free_prompt_data (prompter_oauth2); + + g_mutex_unlock (&prompter_oauth2->priv->property_lock); + + if (prompt_id) + e_credentials_prompter_impl_prompt_finish (E_CREDENTIALS_PROMPTER_IMPL (prompter_oauth2), prompt_id, NULL); + } + + return FALSE; +} + +static void +e_credentials_prompter_impl_oauth2_process_prompt (ECredentialsPrompterImpl *prompter_impl, + gpointer prompt_id, + ESource *auth_source, + ESource *cred_source, + const gchar *error_text, + const ENamedParameters *credentials) +{ + ECredentialsPrompterImplOAuth2 *prompter_oauth2; + + g_return_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_OAUTH2 (prompter_impl)); + + prompter_oauth2 = E_CREDENTIALS_PROMPTER_IMPL_OAUTH2 (prompter_impl); + g_return_if_fail (prompter_oauth2->priv->prompt_id == NULL); + + g_mutex_lock (&prompter_oauth2->priv->property_lock); + if (prompter_oauth2->priv->show_dialog_idle_id != 0) { + g_mutex_unlock (&prompter_oauth2->priv->property_lock); + g_warning ("%s: Already processing other prompt", G_STRFUNC); + return; + } + g_mutex_unlock (&prompter_oauth2->priv->property_lock); + + prompter_oauth2->priv->prompt_id = prompt_id; + prompter_oauth2->priv->auth_source = g_object_ref (auth_source); + prompter_oauth2->priv->cred_source = g_object_ref (cred_source); + prompter_oauth2->priv->service = e_oauth2_services_find (prompter_oauth2->priv->oauth2_services, cred_source); + prompter_oauth2->priv->error_text = g_strdup (error_text); + prompter_oauth2->priv->credentials = e_named_parameters_new_clone (credentials); + prompter_oauth2->priv->cancellable = g_cancellable_new (); + + g_mutex_lock (&prompter_oauth2->priv->property_lock); + prompter_oauth2->priv->refresh_failed_with_transport_error = FALSE; + prompter_oauth2->priv->show_dialog_idle_id = g_idle_add ( + e_credentials_prompter_impl_oauth2_manage_dialog_idle_cb, + prompter_oauth2); + g_mutex_unlock (&prompter_oauth2->priv->property_lock); +} + +static void +e_credentials_prompter_impl_oauth2_cancel_prompt (ECredentialsPrompterImpl *prompter_impl, + gpointer prompt_id) +{ + ECredentialsPrompterImplOAuth2 *prompter_oauth2; + + g_return_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_OAUTH2 (prompter_impl)); + + prompter_oauth2 = E_CREDENTIALS_PROMPTER_IMPL_OAUTH2 (prompter_impl); + g_return_if_fail (prompter_oauth2->priv->prompt_id == prompt_id); + + if (prompter_oauth2->priv->cancellable) + g_cancellable_cancel (prompter_oauth2->priv->cancellable); + + /* This also closes the dialog. */ + if (prompter_oauth2->priv->dialog) + gtk_dialog_response (prompter_oauth2->priv->dialog, GTK_RESPONSE_CANCEL); +} + +static void +e_credentials_prompter_impl_oauth2_constructed (GObject *object) +{ + ECredentialsPrompterImplOAuth2 *prompter_oauth2 = E_CREDENTIALS_PROMPTER_IMPL_OAUTH2 (object); + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_credentials_prompter_impl_oauth2_parent_class)->constructed (object); + + if (prompter_oauth2->priv->oauth2_services) { + ECredentialsPrompter *prompter; + ECredentialsPrompterImpl *prompter_impl; + GSList *services, *link; + + prompter_impl = E_CREDENTIALS_PROMPTER_IMPL (prompter_oauth2); + prompter = E_CREDENTIALS_PROMPTER (e_extension_get_extensible (E_EXTENSION (prompter_impl))); + + services = e_oauth2_services_list (prompter_oauth2->priv->oauth2_services); + + for (link = services; link; link = g_slist_next (link)) { + EOAuth2Service *service = link->data; + + if (service && e_oauth2_service_get_name (service)) { + e_credentials_prompter_register_impl (prompter, e_oauth2_service_get_name (service), prompter_impl); + } + } + + g_slist_free_full (services, g_object_unref); + } +} + +static void +e_credentials_prompter_impl_oauth2_dispose (GObject *object) +{ + ECredentialsPrompterImplOAuth2 *prompter_oauth2 = E_CREDENTIALS_PROMPTER_IMPL_OAUTH2 (object); + + g_mutex_lock (&prompter_oauth2->priv->property_lock); + if (prompter_oauth2->priv->show_dialog_idle_id) { + g_source_remove (prompter_oauth2->priv->show_dialog_idle_id); + prompter_oauth2->priv->show_dialog_idle_id = 0; + } + g_mutex_unlock (&prompter_oauth2->priv->property_lock); + + if (prompter_oauth2->priv->cancellable) { + g_cancellable_cancel (prompter_oauth2->priv->cancellable); + g_clear_object (&prompter_oauth2->priv->cancellable); + } + + g_warn_if_fail (prompter_oauth2->priv->prompt_id == NULL); + g_warn_if_fail (prompter_oauth2->priv->dialog == NULL); + + e_credentials_prompter_impl_oauth2_free_prompt_data (prompter_oauth2); + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_credentials_prompter_impl_oauth2_parent_class)->dispose (object); +} + +static void +e_credentials_prompter_impl_oauth2_finalize (GObject *object) +{ + ECredentialsPrompterImplOAuth2 *prompter_oauth2 = E_CREDENTIALS_PROMPTER_IMPL_OAUTH2 (object); + + g_clear_object (&prompter_oauth2->priv->oauth2_services); + g_mutex_clear (&prompter_oauth2->priv->property_lock); + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_credentials_prompter_impl_oauth2_parent_class)->finalize (object); +} + +static void +e_credentials_prompter_impl_oauth2_class_init (ECredentialsPrompterImplOAuth2Class *class) +{ + /* No static known, rather figure them out in runtime */ + static const gchar *authentication_methods[] = { + NULL + }; + + GObjectClass *object_class; + ECredentialsPrompterImplClass *prompter_impl_class; + + g_type_class_add_private (class, sizeof (ECredentialsPrompterImplOAuth2Private)); + + object_class = G_OBJECT_CLASS (class); + object_class->constructed = e_credentials_prompter_impl_oauth2_constructed; + object_class->dispose = e_credentials_prompter_impl_oauth2_dispose; + object_class->finalize = e_credentials_prompter_impl_oauth2_finalize; + + prompter_impl_class = E_CREDENTIALS_PROMPTER_IMPL_CLASS (class); + prompter_impl_class->authentication_methods = (const gchar * const *) authentication_methods; + prompter_impl_class->process_prompt = e_credentials_prompter_impl_oauth2_process_prompt; + prompter_impl_class->cancel_prompt = e_credentials_prompter_impl_oauth2_cancel_prompt; +} + +static void +e_credentials_prompter_impl_oauth2_init (ECredentialsPrompterImplOAuth2 *prompter_oauth2) +{ + prompter_oauth2->priv = G_TYPE_INSTANCE_GET_PRIVATE (prompter_oauth2, + E_TYPE_CREDENTIALS_PROMPTER_IMPL_OAUTH2, ECredentialsPrompterImplOAuth2Private); + + g_mutex_init (&prompter_oauth2->priv->property_lock); + + prompter_oauth2->priv->oauth2_services = e_oauth2_services_new (); +} + +/** + * e_credentials_prompter_impl_oauth2_new: + * + * Creates a new instance of an #ECredentialsPrompterImplOAuth2. + * + * Returns: (transfer full): a newly created #ECredentialsPrompterImplOAuth2, + * which should be freed with g_object_unref() when no longer needed. + * + * Since: 3.28 + **/ +ECredentialsPrompterImpl * +e_credentials_prompter_impl_oauth2_new (void) +{ + return g_object_new (E_TYPE_CREDENTIALS_PROMPTER_IMPL_OAUTH2, NULL); +} diff --git a/src/libedataserverui/e-credentials-prompter-impl-oauth2.h b/src/libedataserverui/e-credentials-prompter-impl-oauth2.h new file mode 100644 index 000000000..81a831666 --- /dev/null +++ b/src/libedataserverui/e-credentials-prompter-impl-oauth2.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. (www.redhat.com) + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#if !defined (__LIBEDATASERVERUI_H_INSIDE__) && !defined (LIBEDATASERVERUI_COMPILATION) +#error "Only <libedataserverui/libedataserverui.h> should be included directly." +#endif + +#ifndef E_CREDENTIALS_PROMPTER_IMPL_OAUTH2_H +#define E_CREDENTIALS_PROMPTER_IMPL_OAUTH2_H + +#include <glib.h> +#include <glib-object.h> + +#include <libedataserverui/e-credentials-prompter-impl.h> + +/* Standard GObject macros */ +#define E_TYPE_CREDENTIALS_PROMPTER_IMPL_OAUTH2 \ + (e_credentials_prompter_impl_oauth2_get_type ()) +#define E_CREDENTIALS_PROMPTER_IMPL_OAUTH2(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_CREDENTIALS_PROMPTER_IMPL_OAUTH2, ECredentialsPrompterImplOAuth2)) +#define E_CREDENTIALS_PROMPTER_IMPL_OAUTH2_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_CREDENTIALS_PROMPTER_IMPL_OAUTH2, ECredentialsPrompterImplOAuth2Class)) +#define E_IS_CREDENTIALS_PROMPTER_IMPL_OAUTH2(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_CREDENTIALS_PROMPTER_IMPL_OAUTH2)) +#define E_IS_CREDENTIALS_PROMPTER_IMPL_OAUTH2_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_CREDENTIALS_PROMPTER_IMPL_OAUTH2)) +#define E_CREDENTIALS_PROMPTER_IMPL_OAUTH2_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_CREDENTIALS_PROMPTER_IMPL_OAUTH2, ECredentialsPrompterImplOAuth2Class)) + +G_BEGIN_DECLS + +typedef struct _ECredentialsPrompterImplOAuth2 ECredentialsPrompterImplOAuth2; +typedef struct _ECredentialsPrompterImplOAuth2Class ECredentialsPrompterImplOAuth2Class; +typedef struct _ECredentialsPrompterImplOAuth2Private ECredentialsPrompterImplOAuth2Private; + +/** + * ECredentialsPrompterImplOAuth2: + * + * Contains only private data that should be read and manipulated using the + * functions below. + * + * Since: 3.28 + **/ +struct _ECredentialsPrompterImplOAuth2 { + ECredentialsPrompterImpl parent; + ECredentialsPrompterImplOAuth2Private *priv; +}; + +struct _ECredentialsPrompterImplOAuth2Class { + ECredentialsPrompterImplClass parent_class; +}; + +GType e_credentials_prompter_impl_oauth2_get_type (void) G_GNUC_CONST; +ECredentialsPrompterImpl * + e_credentials_prompter_impl_oauth2_new (void); + +G_END_DECLS + +#endif /* E_CREDENTIALS_PROMPTER_IMPL_OAUTH2_H */ diff --git a/src/libedataserverui/e-credentials-prompter.c b/src/libedataserverui/e-credentials-prompter.c index a848c0c84..91f7cea8b 100644 --- a/src/libedataserverui/e-credentials-prompter.c +++ b/src/libedataserverui/e-credentials-prompter.c @@ -27,7 +27,7 @@ /* built-in credentials prompter implementations */ #include "e-credentials-prompter-impl-password.h" -#include "e-credentials-prompter-impl-google.h" +#include "e-credentials-prompter-impl-oauth2.h" typedef struct _ProcessPromptData { GWeakRef *prompter; @@ -1044,7 +1044,7 @@ e_credentials_prompter_class_init (ECredentialsPrompterClass *class) /* Ensure built-in credential providers implementation types */ g_type_ensure (E_TYPE_CREDENTIALS_PROMPTER_IMPL_PASSWORD); - g_type_ensure (E_TYPE_CREDENTIALS_PROMPTER_IMPL_GOOGLE); + g_type_ensure (E_TYPE_CREDENTIALS_PROMPTER_IMPL_OAUTH2); } static void diff --git a/src/libedataserverui/libedataserverui.h b/src/libedataserverui/libedataserverui.h index 062eb091e..4e3a87515 100644 --- a/src/libedataserverui/libedataserverui.h +++ b/src/libedataserverui/libedataserverui.h @@ -23,7 +23,7 @@ #include <libedataserverui/e-cell-renderer-color.h> #include <libedataserverui/e-credentials-prompter.h> #include <libedataserverui/e-credentials-prompter-impl.h> -#include <libedataserverui/e-credentials-prompter-impl-google.h> +#include <libedataserverui/e-credentials-prompter-impl-oauth2.h> #include <libedataserverui/e-credentials-prompter-impl-password.h> #include <libedataserverui/e-trust-prompt.h> #include <libedataserverui/e-webdav-discover-widget.h> diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index 9bd3a1611..7fb343877 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -68,6 +68,10 @@ add_subdirectory(outlook-backend) add_subdirectory(webdav-backend) add_subdirectory(yahoo-backend) +if(ENABLE_OAUTH2) + add_subdirectory(oauth2-services) +endif(ENABLE_OAUTH2) + if(HAVE_GTK) add_subdirectory(trust-prompt) endif(HAVE_GTK) diff --git a/src/modules/google-backend/module-google-backend.c b/src/modules/google-backend/module-google-backend.c index 4efa245ea..5b6f47f14 100644 --- a/src/modules/google-backend/module-google-backend.c +++ b/src/modules/google-backend/module-google-backend.c @@ -36,6 +36,8 @@ /* Just for readability... */ #define METHOD(x) (CAMEL_NETWORK_SECURITY_METHOD_##x) +#define GOOGLE_OAUTH2_MEHTOD "Google" + /* IMAP Configuration Details */ #define GOOGLE_IMAP_BACKEND_NAME "imapx" #define GOOGLE_IMAP_HOST "imap.googlemail.com" @@ -104,10 +106,10 @@ google_backend_can_use_google_auth (ESource *source) g_return_val_if_fail (E_IS_SERVER_SIDE_SOURCE (source), FALSE); - if (!e_source_credentials_google_is_supported ()) + registry = e_server_side_source_get_server (E_SERVER_SIDE_SOURCE (source)); + if (!e_oauth2_services_is_oauth2_alias (e_source_registry_server_get_oauth2_services (registry), GOOGLE_OAUTH2_MEHTOD)) return FALSE; - registry = e_server_side_source_get_server (E_SERVER_SIDE_SOURCE (source)); g_object_ref (source); while (source && e_source_get_parent (source)) { @@ -176,6 +178,7 @@ google_backend_mail_update_auth_method (ESource *child_source, ESourceAuthentication *auth_extension; EOAuth2Support *oauth2_support; const gchar *method; + gboolean can_use_google_auth; auth_extension = e_source_get_extension (child_source, E_SOURCE_EXTENSION_AUTHENTICATION); @@ -186,10 +189,14 @@ google_backend_mail_update_auth_method (ESource *child_source, if (!oauth2_support && master_source) oauth2_support = e_server_side_source_ref_oauth2_support (E_SERVER_SIDE_SOURCE (master_source)); - if (oauth2_support != NULL) { + can_use_google_auth = google_backend_can_use_google_auth (child_source); + if (!can_use_google_auth && master_source) + can_use_google_auth = google_backend_can_use_google_auth (master_source); + + if (oauth2_support && !can_use_google_auth) { method = "XOAUTH2"; - } else if (google_backend_can_use_google_auth (child_source)) { - method = "Google"; + } else if (can_use_google_auth) { + method = GOOGLE_OAUTH2_MEHTOD; } else { method = NULL; } @@ -215,6 +222,7 @@ google_backend_calendar_update_auth_method (ESource *child_source, EOAuth2Support *oauth2_support; ESourceAuthentication *auth_extension; const gchar *method; + gboolean can_use_google_auth; auth_extension = e_source_get_extension (child_source, E_SOURCE_EXTENSION_AUTHENTICATION); @@ -225,10 +233,14 @@ google_backend_calendar_update_auth_method (ESource *child_source, if (!oauth2_support && master_source) oauth2_support = e_server_side_source_ref_oauth2_support (E_SERVER_SIDE_SOURCE (master_source)); - if (oauth2_support != NULL) { + can_use_google_auth = google_backend_can_use_google_auth (child_source); + if (!can_use_google_auth && master_source) + can_use_google_auth = google_backend_can_use_google_auth (master_source); + + if (oauth2_support && !can_use_google_auth) { method = "OAuth2"; - } else if (google_backend_can_use_google_auth (child_source)) { - method = "Google"; + } else if (can_use_google_auth) { + method = GOOGLE_OAUTH2_MEHTOD; } else { method = "plain/password"; } @@ -252,11 +264,10 @@ google_backend_contacts_update_auth_method (ESource *child_source, { EOAuth2Support *oauth2_support; ESourceAuthentication *extension; - const gchar *extension_name; const gchar *method; + gboolean can_use_google_auth; - extension_name = E_SOURCE_EXTENSION_AUTHENTICATION; - extension = e_source_get_extension (child_source, extension_name); + extension = e_source_get_extension (child_source, E_SOURCE_EXTENSION_AUTHENTICATION); if (!google_backend_is_google_host (extension)) return; @@ -265,12 +276,16 @@ google_backend_contacts_update_auth_method (ESource *child_source, if (!oauth2_support && master_source) oauth2_support = e_server_side_source_ref_oauth2_support (E_SERVER_SIDE_SOURCE (master_source)); - if (oauth2_support != NULL) + can_use_google_auth = google_backend_can_use_google_auth (child_source); + if (!can_use_google_auth && master_source) + can_use_google_auth = google_backend_can_use_google_auth (master_source); + + if (oauth2_support && !can_use_google_auth) method = "OAuth2"; - else /* if (google_backend_can_use_google_auth (source)) */ - method = "Google"; + else /* if (can_use_google_auth) */ + method = GOOGLE_OAUTH2_MEHTOD; /* "ClientLogin" for Contacts is not supported anymore, thus - force "Google" method regardless the evolution-data-server + force GOOGLE_OAUTH2_MEHTOD method regardless the evolution-data-server was compiled with it or not. */ e_source_authentication_set_method (extension, method); @@ -386,7 +401,7 @@ google_add_task_list (ECollectionBackend *collection, e_source_authentication_set_host (E_SOURCE_AUTHENTICATION (extension), "www.google.com"); if (google_backend_can_use_google_auth (collection_source)) - e_source_authentication_set_method (E_SOURCE_AUTHENTICATION (extension), "Google"); + e_source_authentication_set_method (E_SOURCE_AUTHENTICATION (extension), GOOGLE_OAUTH2_MEHTOD); else e_source_authentication_set_method (E_SOURCE_AUTHENTICATION (extension), "OAuth2"); @@ -470,7 +485,7 @@ google_backend_authenticate_sync (EBackend *backend, gchar *method; method = e_source_authentication_dup_method (auth_extension); - if (g_strcmp0 (method, "Google") == 0) + if (g_strcmp0 (method, GOOGLE_OAUTH2_MEHTOD) == 0) calendar_url = "https://apidata.googleusercontent.com/caldav/v2/"; g_free (method); @@ -487,7 +502,7 @@ google_backend_authenticate_sync (EBackend *backend, #ifdef HAVE_LIBGDATA if (result == E_SOURCE_AUTHENTICATION_ACCEPTED && e_source_collection_get_calendar_enabled (collection_extension) && - (goa_extension || e_source_credentials_google_is_supported ())) { + (goa_extension || e_oauth2_services_is_supported ())) { EGDataOAuth2Authorizer *authorizer; GDataTasksService *tasks_service; GError *local_error = NULL; diff --git a/src/modules/oauth2-services/CMakeLists.txt b/src/modules/oauth2-services/CMakeLists.txt new file mode 100644 index 000000000..be92c4469 --- /dev/null +++ b/src/modules/oauth2-services/CMakeLists.txt @@ -0,0 +1,17 @@ +set(extra_deps) +set(sources + module-oauth2-services.c +) +set(extra_defines) +set(extra_cflags) +set(extra_incdirs) +set(extra_ldflags) + +add_source_registry_module(module-oauth2-services + sources + extra_deps + extra_defines + extra_cflags + extra_incdirs + extra_ldflags +) diff --git a/src/modules/oauth2-services/module-oauth2-services.c b/src/modules/oauth2-services/module-oauth2-services.c new file mode 100644 index 000000000..31540f78d --- /dev/null +++ b/src/modules/oauth2-services/module-oauth2-services.c @@ -0,0 +1,296 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "evolution-data-server-config.h" + +#include <gmodule.h> + +#include <libedataserver/libedataserver.h> +#include <libebackend/libebackend.h> + +/* Standard GObject macros */ +#define E_TYPE_OAUTH2_SOURCE_MONITOR \ + (e_oauth2_source_monitor_get_type ()) +#define E_OAUTH2_SOURCE_MONITOR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_OAUTH2_SOURCE_MONITOR, EOAuth2SourceMonitor)) +#define E_IS_OAUTH2_SOURCE_MONITOR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_OAUTH2_SOURCE_MONITOR)) + +typedef struct _EOAuth2SourceMonitor EOAuth2SourceMonitor; +typedef struct _EOAuth2SourceMonitorClass EOAuth2SourceMonitorClass; + +struct _EOAuth2SourceMonitor { + EExtension parent; + + EOAuth2Services *oauth2_services; +}; + +struct _EOAuth2SourceMonitorClass { + EExtensionClass parent_class; +}; + +/* Forward Declarations */ +GType e_oauth2_source_monitor_get_type (void); +static void e_oauth2_source_monitor_oauth2_support_init (EOAuth2SupportInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (EOAuth2SourceMonitor, e_oauth2_source_monitor, E_TYPE_EXTENSION, 0, + G_IMPLEMENT_INTERFACE_DYNAMIC (E_TYPE_OAUTH2_SUPPORT, e_oauth2_source_monitor_oauth2_support_init)) + +static ESourceRegistryServer * +oauth2_source_monitor_get_registry_server (EOAuth2SourceMonitor *extension) +{ + return E_SOURCE_REGISTRY_SERVER (e_extension_get_extensible (E_EXTENSION (extension))); +} + +static gboolean +oauth2_source_monitor_get_access_token_sync (EOAuth2Support *support, + ESource *source, + GCancellable *cancellable, + gchar **out_access_token, + gint *out_expires_in, + GError **error) +{ + EOAuth2ServiceRefSourceFunc ref_source; + ESourceRegistryServer *registry_server; + EOAuth2SourceMonitor *extension; + EOAuth2Service *service; + gboolean success; + + g_return_val_if_fail (E_IS_OAUTH2_SOURCE_MONITOR (support), FALSE); + g_return_val_if_fail (E_IS_SOURCE (source), FALSE); + + extension = E_OAUTH2_SOURCE_MONITOR (support); + service = e_oauth2_services_find (extension->oauth2_services, source); + g_return_val_if_fail (service != NULL, FALSE); + + ref_source = (EOAuth2ServiceRefSourceFunc) e_source_registry_server_ref_source; + registry_server = oauth2_source_monitor_get_registry_server (extension); + + success = e_oauth2_service_get_access_token_sync (service, source, ref_source, registry_server, + out_access_token, out_expires_in, cancellable, error); + + g_clear_object (&service); + + return success; +} + +static void +oauth2_source_monitor_update_source (EOAuth2SourceMonitor *extension, + ESource *source, + gboolean is_new_source); + +static void +oauth2_source_monitor_method_changed_cb (ESourceExtension *auth_extension, + GParamSpec *param, + EOAuth2SourceMonitor *extension) +{ + ESource *source; + + g_return_if_fail (E_IS_SOURCE_EXTENSION (auth_extension)); + g_return_if_fail (E_IS_OAUTH2_SOURCE_MONITOR (extension)); + + source = e_source_extension_ref_source (auth_extension); + if (source) { + oauth2_source_monitor_update_source (extension, source, FALSE); + g_clear_object (&source); + } +} + +static void +oauth2_source_monitor_update_source (EOAuth2SourceMonitor *extension, + ESource *source, + gboolean is_new_source) +{ + ESourceAuthentication *authentication_extension; + EServerSideSource *server_source; + gchar *auth_method; + + g_return_if_fail (E_IS_OAUTH2_SOURCE_MONITOR (extension)); + g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source)); + + if (!extension->oauth2_services || + !e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION) || + e_source_has_extension (source, E_SOURCE_EXTENSION_GOA) || + e_source_has_extension (source, E_SOURCE_EXTENSION_UOA)) + return; + + server_source = E_SERVER_SIDE_SOURCE (source); + authentication_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); + + auth_method = e_source_authentication_dup_method (authentication_extension); + + if (e_oauth2_services_is_oauth2_alias (extension->oauth2_services, auth_method)) { + e_server_side_source_set_oauth2_support (server_source, E_OAUTH2_SUPPORT (extension)); + } else { + EOAuth2Support *existing; + + existing = e_server_side_source_ref_oauth2_support (server_source); + if (existing == E_OAUTH2_SUPPORT (extension)) + e_server_side_source_set_oauth2_support (server_source, NULL); + + g_clear_object (&existing); + } + + g_free (auth_method); + + if (is_new_source) { + g_signal_connect (authentication_extension, "notify::method", + G_CALLBACK (oauth2_source_monitor_method_changed_cb), extension); + } +} + +static void +oauth2_source_monitor_source_added_cb (ESourceRegistryServer *server, + ESource *source, + EOAuth2SourceMonitor *extension) +{ + g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server)); + g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source)); + g_return_if_fail (E_IS_OAUTH2_SOURCE_MONITOR (extension)); + + oauth2_source_monitor_update_source (extension, source, TRUE); +} + +static void +oauth2_source_monitor_bus_acquired_cb (EDBusServer *dbus_server, + GDBusConnection *connection, + EOAuth2SourceMonitor *extension) +{ + ESourceRegistryServer *server; + GList *sources, *link; + + g_return_if_fail (E_IS_OAUTH2_SOURCE_MONITOR (extension)); + + server = oauth2_source_monitor_get_registry_server (extension); + + if (!server || !extension->oauth2_services) + return; + + sources = e_source_registry_server_list_sources (server, NULL); + for (link = sources; link; link = g_list_next (link)) { + ESource *source = link->data; + + oauth2_source_monitor_source_added_cb (server, source, extension); + } + + g_list_free_full (sources, g_object_unref); + + g_signal_connect (server, "source-added", + G_CALLBACK (oauth2_source_monitor_source_added_cb), extension); +} + +static void +oauth2_source_monitor_dispose (GObject *object) +{ + EOAuth2SourceMonitor *extension; + ESourceRegistryServer *server; + + extension = E_OAUTH2_SOURCE_MONITOR (object); + + server = oauth2_source_monitor_get_registry_server (extension); + if (server) { + GList *sources, *link; + + sources = e_source_registry_server_list_sources (server, NULL); + for (link = sources; link; link = g_list_next (link)) { + ESource *source = link->data; + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) { + ESourceAuthentication *auth_extension; + + auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); + g_signal_handlers_disconnect_by_func (auth_extension, + G_CALLBACK (oauth2_source_monitor_method_changed_cb), extension); + } + } + + g_list_free_full (sources, g_object_unref); + } + + g_clear_object (&extension->oauth2_services); + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_oauth2_source_monitor_parent_class)->dispose (object); +} + +static void +oauth2_source_monitor_constructed (GObject *object) +{ + EExtension *extension; + EExtensible *extensible; + + extension = E_EXTENSION (object); + extensible = e_extension_get_extensible (extension); + + /* Wait for the registry service to acquire its well-known + * bus name so we don't do anything destructive beforehand. */ + + g_signal_connect ( + extensible, "bus-acquired", + G_CALLBACK (oauth2_source_monitor_bus_acquired_cb), + extension); + + /* Chain up to parent's method. */ + G_OBJECT_CLASS (e_oauth2_source_monitor_parent_class)->constructed (object); +} + +static void +e_oauth2_source_monitor_class_init (EOAuth2SourceMonitorClass *class) +{ + GObjectClass *object_class; + EExtensionClass *extension_class; + + object_class = G_OBJECT_CLASS (class); + object_class->dispose = oauth2_source_monitor_dispose; + object_class->constructed = oauth2_source_monitor_constructed; + + extension_class = E_EXTENSION_CLASS (class); + extension_class->extensible_type = E_TYPE_SOURCE_REGISTRY_SERVER; +} + +static void +e_oauth2_source_monitor_oauth2_support_init (EOAuth2SupportInterface *iface) +{ + iface->get_access_token_sync = oauth2_source_monitor_get_access_token_sync; +} + +static void +e_oauth2_source_monitor_class_finalize (EOAuth2SourceMonitorClass *class) +{ +} + +static void +e_oauth2_source_monitor_init (EOAuth2SourceMonitor *extension) +{ + extension->oauth2_services = e_oauth2_services_new (); +} + +/* Module Entry Points */ +void e_module_load (GTypeModule *type_module); +void e_module_unload (GTypeModule *type_module); + +G_MODULE_EXPORT void +e_module_load (GTypeModule *type_module) +{ + e_oauth2_source_monitor_register_type (type_module); +} + +G_MODULE_EXPORT void +e_module_unload (GTypeModule *type_module) +{ +} |
