[evolution-data-server] Change how built-in OAuth2 authentication works
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server] Change how built-in OAuth2 authentication works
- Date: Mon, 22 Jan 2018 12:34:19 +0000 (UTC)
commit 9a4c9d6a58e6dd5d4bbf3f8dc5d79040219dfbca
Author: Milan Crha <mcrha redhat com>
Date: Mon Jan 22 13:32:29 2018 +0100
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.
CMakeLists.txt | 20 +-
config.h.in | 4 +-
.../org.gnome.evolution-data-server.gschema.xml.in | 17 +
docs/reference/camel/camel-docs.sgml.in | 2 +
.../evolution-data-server-docs.sgml.in | 10 +-
po/POTFILES.in | 6 +-
.../backends/google/e-book-backend-google.c | 10 +-
.../backends/webdav/e-book-backend-webdav.c | 7 +-
.../backends/caldav/e-cal-backend-caldav.c | 7 +-
.../backends/gtasks/e-cal-backend-gtasks.c | 9 +-
src/camel/CMakeLists.txt | 4 +
src/camel/camel-sasl-xoauth2-google.c | 44 +
src/camel/camel-sasl-xoauth2-google.h | 63 +
src/camel/camel-sasl-xoauth2.c | 110 ++
src/camel/camel-sasl-xoauth2.h | 63 +
src/camel/camel-sasl.c | 59 +-
src/camel/camel-sasl.h | 1 +
src/camel/camel-session.c | 35 +
src/camel/camel-session.h | 14 +
src/camel/camel.h | 2 +
src/camel/providers/imapx/camel-imapx-command.c | 2 +-
src/camel/providers/imapx/camel-imapx-server.c | 3 +-
src/camel/providers/pop3/camel-pop3-store.c | 4 +-
src/camel/providers/smtp/camel-smtp-transport.c | 4 +-
src/libebackend/e-server-side-source.c | 5 +-
src/libebackend/e-source-registry-server.c | 21 +
src/libebackend/e-source-registry-server.h | 3 +
src/libebackend/e-webdav-collection-backend.c | 24 +-
src/libedataserver/CMakeLists.txt | 18 +-
src/libedataserver/e-data-server-util.c | 145 ++-
src/libedataserver/e-data-server-util.h | 10 +-
src/libedataserver/e-gdata-oauth2-authorizer.c | 4 +-
src/libedataserver/e-oauth2-service-base.c | 65 +
src/libedataserver/e-oauth2-service-base.h | 63 +
src/libedataserver/e-oauth2-service-google.c | 157 +++
src/libedataserver/e-oauth2-service-google.h | 62 +
src/libedataserver/e-oauth2-service.c | 1379 ++++++++++++++++++++
src/libedataserver/e-oauth2-service.h | 179 +++
src/libedataserver/e-oauth2-services.c | 438 +++++++
src/libedataserver/e-oauth2-services.h | 100 ++
src/libedataserver/e-soup-session.c | 48 +-
src/libedataserver/e-soup-session.h | 2 +
.../e-source-credentials-provider-impl-google.c | 853 ------------
.../e-source-credentials-provider-impl-google.h | 106 --
.../e-source-credentials-provider-impl-oauth2.c | 109 ++
.../e-source-credentials-provider-impl-oauth2.h | 78 ++
src/libedataserver/e-source-credentials-provider.c | 72 +-
src/libedataserver/e-source-registry.c | 22 +
src/libedataserver/e-source-registry.h | 4 +
src/libedataserver/libedataserver.h | 6 +-
src/libedataserverui/CMakeLists.txt | 10 +-
.../e-credentials-prompter-impl-google.c | 1181 -----------------
.../e-credentials-prompter-impl-google.h | 78 --
.../e-credentials-prompter-impl-oauth2.c | 856 ++++++++++++
.../e-credentials-prompter-impl-oauth2.h | 79 ++
src/libedataserverui/e-credentials-prompter.c | 4 +-
src/libedataserverui/libedataserverui.h | 2 +-
src/modules/CMakeLists.txt | 4 +
src/modules/google-backend/module-google-backend.c | 51 +-
src/modules/oauth2-services/CMakeLists.txt | 17 +
.../oauth2-services/module-oauth2-services.c | 296 +++++
61 files changed, 4599 insertions(+), 2452 deletions(-)
---
diff --git a/CMakeLists.txt b/CMakeLists.txt
index be416bc..a6d583b 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 7ebf216..8c41fa5 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 a669207..805f689 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 36f414c..1e7d67a 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 77c8c58..c0c94b7 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 6803960..0849028 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 2ec0d24..78492e5 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 b4c3ecf..cc98b9b 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 5f46f8e..4551dea 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 9815242..c1a16ab 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 acc6b1a..a9f50d4 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 0000000..fb016a2
--- /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 0000000..66d0b0e
--- /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 0000000..627def4
--- /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 0000000..f613d9d
--- /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 69efc10..ac93713 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 befc009..6a5e8d8 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 a5868ab..74634a9 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 cdf8184..c01a935 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 daf9068..758e766 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 e76f38e..5ef56a5 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 5937c8b..b0bf228 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 8614de1..77d9740 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 d249ed9..7810be4 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 8ba51ef..3a1fd6a 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 738d1c4..db86130 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 e1dd0a9..e53b33a 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 757fbc3..024779b 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 3a16a57..a16c433 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 750e18f..703413d 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 a6d0925..20d203e 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 6c40e8d..2d2e34b 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 0000000..ae4ddcf
--- /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 0000000..5f6ecd4
--- /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 0000000..34f7dee
--- /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 0000000..c37f9c4
--- /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 0000000..5c602ed
--- /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 0000000..92b5ccb
--- /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 0000000..48df4a2
--- /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 0000000..9c3fc17
--- /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 76907c3..5f1721a 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 c877829..9ea18a9 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-oauth2.c
b/src/libedataserver/e-source-credentials-provider-impl-oauth2.c
new file mode 100644
index 0000000..1915a51
--- /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 0000000..f246452
--- /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 7eea898..36764ba 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 b03a6ab..63970ff 100644
--- a/src/libedataserver/e-source-registry.c
+++ b/src/libedataserver/e-source-registry.c
@@ -121,6 +121,8 @@ struct _ESourceRegistryPrivate {
gboolean initialized;
GError *init_error;
GMutex init_lock;
+
+ EOAuth2Services *oauth2_services;
};
struct _AsyncContext {
@@ -1350,6 +1352,8 @@ source_registry_finalize (GObject *object)
g_clear_error (&priv->init_error);
g_mutex_clear (&priv->init_lock);
+ g_clear_object (&priv->oauth2_services);
+
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (e_source_registry_parent_class)->finalize (object);
}
@@ -1733,6 +1737,8 @@ e_source_registry_init (ESourceRegistry *registry)
G_CALLBACK (source_registry_settings_changed_cb), registry);
g_mutex_init (®istry->priv->init_lock);
+
+ registry->priv->oauth2_services = e_oauth2_services_new ();
}
/**
@@ -1855,6 +1861,22 @@ e_source_registry_new_finish (GAsyncResult *result,
return g_task_propagate_pointer (G_TASK (result), error);
}
+/**
+ * e_source_registry_get_oauth2_services:
+ * @registry: an #ESourceRegistry
+ *
+ * Returns: (transfer none): an instance of #EOAuth2Services, owned by @registry
+ *
+ * Since: 3.28
+ **/
+EOAuth2Services *
+e_source_registry_get_oauth2_services (ESourceRegistry *registry)
+{
+ g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+ return registry->priv->oauth2_services;
+}
+
/* Helper for e_source_registry_commit_source() */
static void
source_registry_commit_source_thread (GSimpleAsyncResult *simple,
diff --git a/src/libedataserver/e-source-registry.h b/src/libedataserver/e-source-registry.h
index 2392331..66c5759 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 f3df296..062c345 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 599a035..e6d45ee 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-oauth2.c
b/src/libedataserverui/e-credentials-prompter-impl-oauth2.c
new file mode 100644
index 0000000..9d92f29
--- /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 0000000..81a8316
--- /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 a848c0c..91f7cea 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 062eb09..4e3a875 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 9bd3a16..7fb3438 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 4efa245..5b6f47f 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 0000000..be92c44
--- /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 0000000..31540f7
--- /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)
+{
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]