[evolution-data-server] Change how built-in OAuth2 authentication works



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 (&registry->priv->init_lock);
+
+       registry->priv->oauth2_services = e_oauth2_services_new ();
 }
 
 /**
@@ -1855,6 +1861,22 @@ e_source_registry_new_finish (GAsyncResult *result,
        return g_task_propagate_pointer (G_TASK (result), error);
 }
 
+/**
+ * e_source_registry_get_oauth2_services:
+ * @registry: an #ESourceRegistry
+ *
+ * Returns: (transfer none): an instance of #EOAuth2Services, owned by @registry
+ *
+ * Since: 3.28
+ **/
+EOAuth2Services *
+e_source_registry_get_oauth2_services (ESourceRegistry *registry)
+{
+       g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+       return registry->priv->oauth2_services;
+}
+
 /* Helper for e_source_registry_commit_source() */
 static void
 source_registry_commit_source_thread (GSimpleAsyncResult *simple,
diff --git a/src/libedataserver/e-source-registry.h b/src/libedataserver/e-source-registry.h
index 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]