summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMilan Crha <mcrha@redhat.com>2018-01-22 13:32:29 +0100
committerMilan Crha <mcrha@redhat.com>2018-01-22 13:32:29 +0100
commit9a4c9d6a58e6dd5d4bbf3f8dc5d79040219dfbca (patch)
treedf078253f22bcd8c33802c8156b3892b32ba78cf
parent01857f50c25d411f5ed83bff2d4554c65e3947bd (diff)
downloadevolution-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.
-rw-r--r--CMakeLists.txt20
-rw-r--r--config.h.in4
-rw-r--r--data/org.gnome.evolution-data-server.gschema.xml.in17
-rw-r--r--docs/reference/camel/camel-docs.sgml.in2
-rw-r--r--docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in10
-rw-r--r--po/POTFILES.in6
-rw-r--r--src/addressbook/backends/google/e-book-backend-google.c10
-rw-r--r--src/addressbook/backends/webdav/e-book-backend-webdav.c7
-rw-r--r--src/calendar/backends/caldav/e-cal-backend-caldav.c7
-rw-r--r--src/calendar/backends/gtasks/e-cal-backend-gtasks.c9
-rw-r--r--src/camel/CMakeLists.txt4
-rw-r--r--src/camel/camel-sasl-xoauth2-google.c44
-rw-r--r--src/camel/camel-sasl-xoauth2-google.h63
-rw-r--r--src/camel/camel-sasl-xoauth2.c110
-rw-r--r--src/camel/camel-sasl-xoauth2.h63
-rw-r--r--src/camel/camel-sasl.c59
-rw-r--r--src/camel/camel-sasl.h1
-rw-r--r--src/camel/camel-session.c35
-rw-r--r--src/camel/camel-session.h14
-rw-r--r--src/camel/camel.h2
-rw-r--r--src/camel/providers/imapx/camel-imapx-command.c2
-rw-r--r--src/camel/providers/imapx/camel-imapx-server.c3
-rw-r--r--src/camel/providers/pop3/camel-pop3-store.c4
-rw-r--r--src/camel/providers/smtp/camel-smtp-transport.c4
-rw-r--r--src/libebackend/e-server-side-source.c5
-rw-r--r--src/libebackend/e-source-registry-server.c21
-rw-r--r--src/libebackend/e-source-registry-server.h3
-rw-r--r--src/libebackend/e-webdav-collection-backend.c24
-rw-r--r--src/libedataserver/CMakeLists.txt18
-rw-r--r--src/libedataserver/e-data-server-util.c145
-rw-r--r--src/libedataserver/e-data-server-util.h10
-rw-r--r--src/libedataserver/e-gdata-oauth2-authorizer.c4
-rw-r--r--src/libedataserver/e-oauth2-service-base.c65
-rw-r--r--src/libedataserver/e-oauth2-service-base.h63
-rw-r--r--src/libedataserver/e-oauth2-service-google.c157
-rw-r--r--src/libedataserver/e-oauth2-service-google.h62
-rw-r--r--src/libedataserver/e-oauth2-service.c1379
-rw-r--r--src/libedataserver/e-oauth2-service.h179
-rw-r--r--src/libedataserver/e-oauth2-services.c438
-rw-r--r--src/libedataserver/e-oauth2-services.h100
-rw-r--r--src/libedataserver/e-soup-session.c48
-rw-r--r--src/libedataserver/e-soup-session.h2
-rw-r--r--src/libedataserver/e-source-credentials-provider-impl-google.c853
-rw-r--r--src/libedataserver/e-source-credentials-provider-impl-google.h106
-rw-r--r--src/libedataserver/e-source-credentials-provider-impl-oauth2.c109
-rw-r--r--src/libedataserver/e-source-credentials-provider-impl-oauth2.h78
-rw-r--r--src/libedataserver/e-source-credentials-provider.c72
-rw-r--r--src/libedataserver/e-source-registry.c22
-rw-r--r--src/libedataserver/e-source-registry.h4
-rw-r--r--src/libedataserver/libedataserver.h6
-rw-r--r--src/libedataserverui/CMakeLists.txt10
-rw-r--r--src/libedataserverui/e-credentials-prompter-impl-google.c1181
-rw-r--r--src/libedataserverui/e-credentials-prompter-impl-google.h78
-rw-r--r--src/libedataserverui/e-credentials-prompter-impl-oauth2.c856
-rw-r--r--src/libedataserverui/e-credentials-prompter-impl-oauth2.h79
-rw-r--r--src/libedataserverui/e-credentials-prompter.c4
-rw-r--r--src/libedataserverui/libedataserverui.h2
-rw-r--r--src/modules/CMakeLists.txt4
-rw-r--r--src/modules/google-backend/module-google-backend.c51
-rw-r--r--src/modules/oauth2-services/CMakeLists.txt17
-rw-r--r--src/modules/oauth2-services/module-oauth2-services.c296
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 (&registry);
- 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 (&registry->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)
+{
+}