[evolution-data-server] Miscellaneous changes related to built-in OAuth2 services



commit c48a46403443174ece8535aed173b753c62a53ea
Author: Milan Crha <mcrha redhat com>
Date:   Tue Jan 23 13:53:16 2018 +0100

    Miscellaneous changes related to built-in OAuth2 services

 .../evolution-data-server-docs.sgml.in             |    2 +-
 src/camel/providers/imapx/camel-imapx-store.c      |   23 ++-
 src/libedataserver/e-oauth2-service-google.c       |   59 +++---
 src/libedataserver/e-oauth2-service.c              |  200 +++++++++++++++++---
 src/libedataserver/e-oauth2-service.h              |   51 +++++
 .../e-credentials-prompter-impl-oauth2.c           |  160 ++++++++++++++--
 6 files changed, 405 insertions(+), 90 deletions(-)
---
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 c0c94b7..2ce2b79 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,7 +248,7 @@
     </chapter>
 
     <chapter>
-      <title>Built-in OAuth2 authentication</title>
+      <title>Built-in OAuth 2.0 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"/>
diff --git a/src/camel/providers/imapx/camel-imapx-store.c b/src/camel/providers/imapx/camel-imapx-store.c
index 721be3c..9d4e4d6 100644
--- a/src/camel/providers/imapx/camel-imapx-store.c
+++ b/src/camel/providers/imapx/camel-imapx-store.c
@@ -1079,10 +1079,8 @@ imapx_query_auth_types_sync (CamelService *service,
                              GCancellable *cancellable,
                              GError **error)
 {
-       CamelServiceAuthType *authtype;
        CamelIMAPXStore *imapx_store;
-       GList *sasl_types = NULL;
-       GList *t, *next;
+       GList *sasl_types;
        CamelIMAPXServer *server;
        const struct _capability_info *cinfo;
 
@@ -1098,14 +1096,19 @@ imapx_query_auth_types_sync (CamelService *service,
 
        cinfo = camel_imapx_server_get_capability_info (server);
 
-       sasl_types = camel_sasl_authtype_list (FALSE);
-       for (t = sasl_types; t; t = next) {
-               authtype = t->data;
-               next = t->next;
+       sasl_types = NULL;
 
-               if (!cinfo || !g_hash_table_lookup (cinfo->auth_types, authtype->authproto)) {
-                       sasl_types = g_list_remove_link (sasl_types, t);
-                       g_list_free_1 (t);
+       if (cinfo && cinfo->auth_types) {
+               GHashTableIter iter;
+               gpointer key;
+
+               g_hash_table_iter_init (&iter, cinfo->auth_types);
+               while (g_hash_table_iter_next (&iter, &key, NULL)) {
+                       CamelServiceAuthType *auth_type;
+
+                       auth_type = camel_sasl_authtype (key);
+                       if (auth_type)
+                               sasl_types = g_list_prepend (sasl_types, auth_type);
                }
        }
 
diff --git a/src/libedataserver/e-oauth2-service-google.c b/src/libedataserver/e-oauth2-service-google.c
index 34f7dee..5951060 100644
--- a/src/libedataserver/e-oauth2-service-google.c
+++ b/src/libedataserver/e-oauth2-service-google.c
@@ -32,9 +32,9 @@ G_DEFINE_TYPE_WITH_CODE (EOAuth2ServiceGoogle, e_oauth2_service_google, E_TYPE_O
        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)
+eos_google_guess_can_process (EOAuth2Service *service,
+                             const gchar *protocol,
+                             const gchar *hostname)
 {
        return hostname && (
                e_util_utf8_strstrcase (hostname, ".google.com") ||
@@ -44,46 +44,46 @@ e_oauth2_service_google_guess_can_process (EOAuth2Service *service,
 }
 
 static const gchar *
-e_oauth2_service_google_get_name (EOAuth2Service *service)
+eos_google_get_name (EOAuth2Service *service)
 {
        return "Google";
 }
 
 static const gchar *
-e_oauth2_service_google_get_display_name (EOAuth2Service *service)
+eos_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)
+eos_google_get_client_id (EOAuth2Service *service)
 {
        return GOOGLE_CLIENT_ID;
 }
 
 static const gchar *
-e_oauth2_service_google_get_client_secret (EOAuth2Service *service)
+eos_google_get_client_secret (EOAuth2Service *service)
 {
        return GOOGLE_CLIENT_SECRET;
 }
 
 static const gchar *
-e_oauth2_service_google_get_authentication_uri (EOAuth2Service *service)
+eos_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)
+eos_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)
+eos_google_prepare_authentication_uri_query (EOAuth2Service *service,
+                                            ESource *source,
+                                            GHashTable *uri_query)
 {
        const gchar *GOOGLE_SCOPE =
                /* GMail IMAP and SMTP access */
@@ -99,19 +99,16 @@ e_oauth2_service_google_prepare_authentication_uri_query (EOAuth2Service *servic
 
        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
+       e_oauth2_service_util_set_to_form (uri_query, "scope", GOOGLE_SCOPE);
+       e_oauth2_service_util_set_to_form (uri_query, "include_granted_scopes", "false");
 }
 
 static gboolean
-e_oauth2_service_google_extract_authorization_code (EOAuth2Service *service,
-                                                   const gchar *page_title,
-                                                   const gchar *page_uri,
-                                                   gchar **out_authorization_code)
+eos_google_extract_authorization_code (EOAuth2Service *service,
+                                      const gchar *page_title,
+                                      const gchar *page_uri,
+                                      const gchar *page_content,
+                                      gchar **out_authorization_code)
 {
        g_return_val_if_fail (out_authorization_code != NULL, FALSE);
 
@@ -135,15 +132,15 @@ e_oauth2_service_google_extract_authorization_code (EOAuth2Service *service,
 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;
+       iface->guess_can_process = eos_google_guess_can_process;
+       iface->get_name = eos_google_get_name;
+       iface->get_display_name = eos_google_get_display_name;
+       iface->get_client_id = eos_google_get_client_id;
+       iface->get_client_secret = eos_google_get_client_secret;
+       iface->get_authentication_uri = eos_google_get_authentication_uri;
+       iface->get_refresh_uri = eos_google_get_refresh_uri;
+       iface->prepare_authentication_uri_query = eos_google_prepare_authentication_uri_query;
+       iface->extract_authorization_code = eos_google_extract_authorization_code;
 }
 
 static void
diff --git a/src/libedataserver/e-oauth2-service.c b/src/libedataserver/e-oauth2-service.c
index 5c602ed..794fef7 100644
--- a/src/libedataserver/e-oauth2-service.c
+++ b/src/libedataserver/e-oauth2-service.c
@@ -139,16 +139,20 @@ eos_default_guess_can_process (EOAuth2Service *service,
        return can;
 }
 
+static guint32
+eos_default_get_flags (EOAuth2Service *service)
+{
+       return E_OAUTH2_SERVICE_FLAG_NONE;
+}
+
 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));
+       e_oauth2_service_util_set_to_form (uri_query, "response_type", "code");
+       e_oauth2_service_util_set_to_form (uri_query, "client_id", e_oauth2_service_get_client_id (service));
+       e_oauth2_service_util_set_to_form (uri_query, "redirect_uri", DEFAULT_REDIRECT_URI);
 
        if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) {
                ESourceAuthentication *auth_extension;
@@ -158,12 +162,10 @@ eos_default_prepare_authentication_uri_query (EOAuth2Service *service,
                user = e_source_authentication_dup_user (auth_extension);
 
                if (user && *user)
-                       add_to_form ("login_hint", user);
+                       e_oauth2_service_util_take_to_form (uri_query, "login_hint", user);
                else
                        g_free (user);
        }
-
-       #undef add_to_form
 }
 
 static void
@@ -171,15 +173,11 @@ 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
+       e_oauth2_service_util_set_to_form (form, "code", authorization_code);
+       e_oauth2_service_util_set_to_form (form, "client_id", e_oauth2_service_get_client_id (service));
+       e_oauth2_service_util_set_to_form (form, "client_secret", e_oauth2_service_get_client_secret 
(service));
+       e_oauth2_service_util_set_to_form (form, "redirect_uri", DEFAULT_REDIRECT_URI);
+       e_oauth2_service_util_set_to_form (form, "grant_type", "authorization_code");
 }
 
 static void
@@ -193,14 +191,10 @@ 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
+       e_oauth2_service_util_set_to_form (form, "refresh_token", refresh_token);
+       e_oauth2_service_util_set_to_form (form, "client_id", e_oauth2_service_get_client_id (service));
+       e_oauth2_service_util_set_to_form (form, "client_secret", e_oauth2_service_get_client_secret 
(service));
+       e_oauth2_service_util_set_to_form (form, "grant_type", "refresh_token");
 }
 
 static void
@@ -214,6 +208,7 @@ e_oauth2_service_default_init (EOAuth2ServiceInterface *iface)
 {
        iface->can_process = eos_default_can_process;
        iface->guess_can_process = eos_default_guess_can_process;
+       iface->get_flags = eos_default_get_flags;
        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;
@@ -307,6 +302,29 @@ e_oauth2_service_guess_can_process (EOAuth2Service *service,
 }
 
 /**
+ * e_oauth2_service_get_flags:
+ * @service: an #EOAuth2Service
+ *
+ * Returns: bit-or of #EOAuth2ServiceFlags for the @service. The default
+ *    implementation returns %E_OAUTH2_SERVICE_FLAG_NONE.
+ *
+ * Since: 3.28
+ **/
+guint32
+e_oauth2_service_get_flags (EOAuth2Service *service)
+{
+       EOAuth2ServiceInterface *iface;
+
+       g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), E_OAUTH2_SERVICE_FLAG_NONE);
+
+       iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
+       g_return_val_if_fail (iface != NULL, E_OAUTH2_SERVICE_FLAG_NONE);
+       g_return_val_if_fail (iface->get_flags != NULL, E_OAUTH2_SERVICE_FLAG_NONE);
+
+       return iface->get_flags (service);
+}
+
+/**
  * e_oauth2_service_get_name:
  * @service: an #EOAuth2Service
  *
@@ -487,23 +505,62 @@ e_oauth2_service_prepare_authentication_uri_query (EOAuth2Service *service,
 }
 
 /**
+ * e_oauth2_service_get_authentication_policy:
+ * @service: an #EOAuth2Service
+ * @uri: a URI of the navigation resource
+ *
+ * Used to decide what to do when the server redirects to the next page.
+ * The default implementation always returns %E_OAUTH2_SERVICE_NAVIGATION_POLICY_ALLOW.
+ *
+ * This method is called before e_oauth2_service_extract_authorization_code() and
+ * can be used to block certain resources or to abort the authentication when
+ * the server redirects to an unexpected page (like when user denies authorization
+ * in the page).
+ *
+ * Returns: one of #EOAuth2ServiceNavigationPolicy
+ *
+ * Since: 3.28
+ **/
+EOAuth2ServiceNavigationPolicy
+e_oauth2_service_get_authentication_policy (EOAuth2Service *service,
+                                           const gchar *uri)
+{
+       EOAuth2ServiceInterface *iface;
+
+       g_return_val_if_fail (E_IS_OAUTH2_SERVICE (service), E_OAUTH2_SERVICE_NAVIGATION_POLICY_ABORT);
+       g_return_val_if_fail (uri != NULL, E_OAUTH2_SERVICE_NAVIGATION_POLICY_ABORT);
+
+       iface = E_OAUTH2_SERVICE_GET_INTERFACE (service);
+       g_return_val_if_fail (iface != NULL, E_OAUTH2_SERVICE_NAVIGATION_POLICY_ABORT);
+       g_return_val_if_fail (iface->get_authentication_policy != NULL, 
E_OAUTH2_SERVICE_NAVIGATION_POLICY_ABORT);
+
+       return iface->get_authentication_policy (service, uri);
+}
+
+/**
  * e_oauth2_service_extract_authorization_code:
  * @service: an #EOAuth2Service
  * @page_title: a web page title
  * @page_uri: a web page URI
+ * @page_content: (nullable): a web page content
  * @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
+ * the authentication code from the page information, 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.
  *
+ * The @page_content is %NULL, unless flags returned by e_oauth2_service_get_flags()
+ * contain also %E_OAUTH2_SERVICE_FLAG_EXTRACT_REQUIRES_PAGE_CONTENT.
+ *
+ * This method is always called after e_oauth2_service_get_authentication_policy().
+ *
  * Returns: whether could recognized successful or failed server response.
  *    The @out_authorization_code is populated on success too.
  *
@@ -513,6 +570,7 @@ gboolean
 e_oauth2_service_extract_authorization_code (EOAuth2Service *service,
                                             const gchar *page_title,
                                             const gchar *page_uri,
+                                            const gchar *page_content,
                                             gchar **out_authorization_code)
 {
        EOAuth2ServiceInterface *iface;
@@ -523,7 +581,7 @@ e_oauth2_service_extract_authorization_code (EOAuth2Service *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);
+       return iface->extract_authorization_code (service, page_title, page_uri, page_content, 
out_authorization_code);
 }
 
 /**
@@ -664,11 +722,15 @@ eos_create_soup_session (EOAuth2ServiceRefSourceFunc ref_source,
                         gpointer ref_source_user_data,
                         ESource *source)
 {
+       static gint oauth2_debug = -1;
        ESourceAuthentication *auth_extension;
        ESource *proxy_source = NULL;
        SoupSession *session;
        gchar *uid;
 
+       if (oauth2_debug == -1)
+               oauth2_debug = g_strcmp0 (g_getenv ("OAUTH2_DEBUG"), "1") == 0 ? 1 : 0;
+
        session = soup_session_new ();
        g_object_set (
                session,
@@ -678,6 +740,14 @@ eos_create_soup_session (EOAuth2ServiceRefSourceFunc ref_source,
                SOUP_SESSION_ACCEPT_LANGUAGE_AUTO, TRUE,
                NULL);
 
+       if (oauth2_debug) {
+               SoupLogger *logger;
+
+               logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
+               soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
+               g_object_unref (logger);
+       }
+
        if (!e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION))
                return session;
 
@@ -776,7 +846,18 @@ eos_send_message (SoupSession *session,
                        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);
+               GString *error_msg;
+
+               error_msg = g_string_new (message->reason_phrase);
+               if (message->response_body && message->response_body->length) {
+                       g_string_append (error_msg, " (");
+                       g_string_append_len (error_msg, message->response_body->data, 
message->response_body->length);
+                       g_string_append (error_msg, ")");
+               }
+
+               g_set_error_literal (error, SOUP_HTTP_ERROR, message->status_code, error_msg->str);
+
+               g_string_free (error_msg, TRUE);
        }
 
        return success;
@@ -1046,7 +1127,7 @@ eos_lookup_token_sync (EOAuth2Service *service,
                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"),
+                       _("Source “%s” (%s) is not valid for “%s” OAuth2 service"),
                        e_source_get_display_name (source),
                        e_source_get_uid (source),
                        e_oauth2_service_get_name (service));
@@ -1377,3 +1458,64 @@ e_oauth2_service_get_access_token_sync (EOAuth2Service *service,
 
        return success;
 }
+
+/**
+ * e_oauth2_service_util_set_to_form:
+ * @form: a #GHashTable
+ * @name: a property name
+ * @value: (nullable): a property value
+ *
+ * Sets @value for @name to @form. The @form should be
+ * the one used in e_oauth2_service_prepare_authentication_uri_query(),
+ * e_oauth2_service_prepare_get_token_form() or
+ * e_oauth2_service_prepare_refresh_token_form().
+ *
+ * If the @value is %NULL, then the property named @name is removed
+ * from the @form instead.
+ *
+ * Since: 3.28
+ **/
+void
+e_oauth2_service_util_set_to_form (GHashTable *form,
+                                  const gchar *name,
+                                  const gchar *value)
+{
+       g_return_if_fail (form != NULL);
+       g_return_if_fail (name != NULL);
+
+       if (value)
+               g_hash_table_insert (form, g_strdup (name), g_strdup (value));
+       else
+               g_hash_table_remove (form, name);
+}
+
+/**
+ * e_oauth2_service_util_take_to_form:
+ * @form: a #GHashTable
+ * @name: a property name
+ * @value: (transfer full) (nullable): a property value
+ *
+ * Takes ownership of @value and sets it for @name to @form. The @value
+ * will be freed with g_free(), when no longer needed. The @form should be
+ * the one used in e_oauth2_service_prepare_authentication_uri_query(),
+ * e_oauth2_service_prepare_get_token_form() or
+ * e_oauth2_service_prepare_refresh_token_form().
+ *
+ * If the @value is %NULL, then the property named @name is removed
+ * from the @form instead.
+ *
+ * Since: 3.28
+ **/
+void
+e_oauth2_service_util_take_to_form (GHashTable *form,
+                                   const gchar *name,
+                                   gchar *value)
+{
+       g_return_if_fail (form != NULL);
+       g_return_if_fail (name != NULL);
+
+       if (value)
+               g_hash_table_insert (form, g_strdup (name), value);
+       else
+               g_hash_table_remove (form, name);
+}
diff --git a/src/libedataserver/e-oauth2-service.h b/src/libedataserver/e-oauth2-service.h
index 92b5ccb..f2d48e3 100644
--- a/src/libedataserver/e-oauth2-service.h
+++ b/src/libedataserver/e-oauth2-service.h
@@ -48,6 +48,39 @@
 G_BEGIN_DECLS
 
 /**
+ * EOAuth2ServiceFlags:
+ * @E_OAUTH2_SERVICE_FLAG_NONE: No flag set
+ * @E_OAUTH2_SERVICE_FLAG_EXTRACT_REQUIRES_PAGE_CONTENT: the service requires also page
+ *    content to be passed to e_oauth2_service_extract_authorization_code()
+ *
+ * Flags of the OAuth2 service.
+ *
+ * Since: 3.28
+ **/
+typedef enum {
+       E_OAUTH2_SERVICE_FLAG_NONE                              = 0,
+       E_OAUTH2_SERVICE_FLAG_EXTRACT_REQUIRES_PAGE_CONTENT     = (1 << 1)
+} EOAuth2ServiceFlags;
+
+/**
+ * EOAuth2ServiceNavigationPolicy:
+ * @E_OAUTH2_SERVICE_NAVIGATION_POLICY_DENY: Deny navigation to the given web resource
+ * @E_OAUTH2_SERVICE_NAVIGATION_POLICY_ALLOW: Allow navigation to the given web resource
+ * @E_OAUTH2_SERVICE_NAVIGATION_POLICY_ABORT: Abort authentication processing
+ *
+ * A value used during querying authentication URI, to decide whether certain
+ * resource can be used or not. The @E_OAUTH2_SERVICE_NAVIGATION_POLICY_ABORT
+ * can be used to abort the authentication query, like when user cancelled it.
+ *
+ * Since: 3.28
+ **/
+typedef enum {
+       E_OAUTH2_SERVICE_NAVIGATION_POLICY_DENY,
+       E_OAUTH2_SERVICE_NAVIGATION_POLICY_ALLOW,
+       E_OAUTH2_SERVICE_NAVIGATION_POLICY_ABORT
+} EOAuth2ServiceNavigationPolicy;
+
+/**
  * EOAuth2ServiceRefSourceFunc:
  * @user_data: user data, as passed to e_oauth2_service_get_token_sync()
  *    or e_oauth2_service_refresh_token_sync()
@@ -80,6 +113,7 @@ struct _EOAuth2ServiceInterface {
        gboolean        (* guess_can_process)           (EOAuth2Service *service,
                                                         const gchar *protocol,
                                                         const gchar *hostname);
+       guint32         (* get_flags)                   (EOAuth2Service *service);
        const gchar *   (* get_name)                    (EOAuth2Service *service);
        const gchar *   (* get_display_name)            (EOAuth2Service *service);
        const gchar *   (* get_client_id)               (EOAuth2Service *service);
@@ -90,9 +124,13 @@ struct _EOAuth2ServiceInterface {
                                                        (EOAuth2Service *service,
                                                         ESource *source,
                                                         GHashTable *uri_query);
+       EOAuth2ServiceNavigationPolicy
+                       (* get_authentication_policy)   (EOAuth2Service *service,
+                                                        const gchar *uri);
        gboolean        (* extract_authorization_code)  (EOAuth2Service *service,
                                                         const gchar *page_title,
                                                         const gchar *page_uri,
+                                                        const gchar *page_content,
                                                         gchar **out_authorization_code);
        void            (* prepare_get_token_form)      (EOAuth2Service *service,
                                                         const gchar *authorization_code,
@@ -116,6 +154,7 @@ gboolean    e_oauth2_service_can_process            (EOAuth2Service *service,
 gboolean       e_oauth2_service_guess_can_process      (EOAuth2Service *service,
                                                         const gchar *protocol,
                                                         const gchar *hostname);
+guint32                e_oauth2_service_get_flags              (EOAuth2Service *service);
 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);
@@ -126,10 +165,15 @@ void              e_oauth2_service_prepare_authentication_uri_query
                                                        (EOAuth2Service *service,
                                                         ESource *source,
                                                         GHashTable *uri_query);
+EOAuth2ServiceNavigationPolicy
+               e_oauth2_service_get_authentication_policy
+                                                       (EOAuth2Service *service,
+                                                        const gchar *uri);
 gboolean       e_oauth2_service_extract_authorization_code
                                                        (EOAuth2Service *service,
                                                         const gchar *page_title,
                                                         const gchar *page_uri,
+                                                        const gchar *page_content,
                                                         gchar **out_authorization_code);
 void           e_oauth2_service_prepare_get_token_form (EOAuth2Service *service,
                                                         const gchar *authorization_code,
@@ -174,6 +218,13 @@ gboolean   e_oauth2_service_get_access_token_sync  (EOAuth2Service *service,
                                                         GCancellable *cancellable,
                                                         GError **error);
 
+void           e_oauth2_service_util_set_to_form       (GHashTable *form,
+                                                        const gchar *name,
+                                                        const gchar *value);
+void           e_oauth2_service_util_take_to_form      (GHashTable *form,
+                                                        const gchar *name,
+                                                        gchar *value);
+
 G_END_DECLS
 
 #endif /* E_OAUTH2_SERVICE_H */
diff --git a/src/libedataserverui/e-credentials-prompter-impl-oauth2.c 
b/src/libedataserverui/e-credentials-prompter-impl-oauth2.c
index 9d92f29..5cf02d0 100644
--- a/src/libedataserverui/e-credentials-prompter-impl-oauth2.c
+++ b/src/libedataserverui/e-credentials-prompter-impl-oauth2.c
@@ -209,29 +209,20 @@ cpi_oauth2_get_access_token_thread (gpointer user_data)
 }
 
 static void
-cpi_oauth2_document_load_changed_cb (WebKitWebView *web_view,
-                                    WebKitLoadEvent load_event,
-                                    ECredentialsPrompterImplOAuth2 *prompter_oauth2)
+cpi_oauth2_extract_authentication_code (ECredentialsPrompterImplOAuth2 *prompter_oauth2,
+                                       const gchar *page_title,
+                                       const gchar *page_uri,
+                                       const gchar *page_content)
 {
-       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))
+               page_title, page_uri, page_content, &authorization_code)) {
                return;
+       }
 
        if (authorization_code) {
                ECredentialsPrompter *prompter;
@@ -239,10 +230,10 @@ cpi_oauth2_document_load_changed_cb (WebKitWebView *web_view,
                AccessTokenThreadData *td;
                GThread *thread;
 
-               e_credentials_prompter_impl_oauth2_show_html (web_view,
+               e_credentials_prompter_impl_oauth2_show_html (prompter_oauth2->priv->web_view,
                        "Checking returned code", _("Requesting access token, please wait..."));
 
-               gtk_widget_set_sensitive (GTK_WIDGET (web_view), FALSE);
+               gtk_widget_set_sensitive (GTK_WIDGET (prompter_oauth2->priv->web_view), FALSE);
 
                e_named_parameters_set (prompter_oauth2->priv->credentials, E_SOURCE_CREDENTIAL_PASSWORD, 
NULL);
 
@@ -266,6 +257,119 @@ cpi_oauth2_document_load_changed_cb (WebKitWebView *web_view,
 }
 
 static void
+cpi_oauth2_web_view_resource_get_data_done_cb (GObject *source_object,
+                                              GAsyncResult *result,
+                                              gpointer user_data)
+{
+       ECredentialsPrompterImplOAuth2 *prompter_oauth2 = user_data;
+       GByteArray *page_content = NULL;
+       const gchar *title, *uri;
+       guchar *data;
+       gsize len = 0;
+       GError *local_error = NULL;
+
+       g_return_if_fail (WEBKIT_IS_WEB_RESOURCE (source_object));
+       g_return_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_OAUTH2 (prompter_oauth2));
+
+       data = webkit_web_resource_get_data_finish (WEBKIT_WEB_RESOURCE (source_object), result, &len, 
&local_error);
+       if (data) {
+               page_content = g_byte_array_new_take ((guint8 *) data, len);
+
+               /* NULL-terminate the array, to be able to use it as a string */
+               g_byte_array_append (page_content, (const guint8 *) "", 1);
+       } else if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+               g_clear_error (&local_error);
+               return;
+       }
+
+       g_clear_error (&local_error);
+
+       title = webkit_web_view_get_title (prompter_oauth2->priv->web_view);
+       uri = webkit_web_view_get_uri (prompter_oauth2->priv->web_view);
+
+       cpi_oauth2_extract_authentication_code (prompter_oauth2, title, uri, page_content ? (const gchar *) 
page_content->data : NULL);
+
+       if (page_content)
+               g_byte_array_free (page_content, TRUE);
+}
+
+static gboolean
+cpi_oauth2_decide_policy_cb (WebKitWebView *web_view,
+                            WebKitPolicyDecision *decision,
+                            WebKitPolicyDecisionType decision_type,
+                            ECredentialsPrompterImplOAuth2 *prompter_oauth2)
+{
+       WebKitNavigationAction *navigation_action;
+       WebKitURIRequest *request;
+
+       g_return_val_if_fail (E_IS_CREDENTIALS_PROMPTER_IMPL_OAUTH2 (prompter_oauth2), FALSE);
+       g_return_val_if_fail (WEBKIT_IS_POLICY_DECISION (decision), FALSE);
+
+       if (decision_type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION)
+               return FALSE;
+
+       navigation_action = webkit_navigation_policy_decision_get_navigation_action 
(WEBKIT_NAVIGATION_POLICY_DECISION (decision));
+       if (!navigation_action)
+               return FALSE;
+
+       request = webkit_navigation_action_get_request (navigation_action);
+       if (!request || !webkit_uri_request_get_uri (request))
+               return FALSE;
+
+       g_return_val_if_fail (prompter_oauth2->priv->service != NULL, FALSE);
+
+       switch (e_oauth2_service_get_authentication_policy (prompter_oauth2->priv->service, 
webkit_uri_request_get_uri (request))) {
+       case E_OAUTH2_SERVICE_NAVIGATION_POLICY_DENY:
+               webkit_policy_decision_ignore (decision);
+               break;
+       case E_OAUTH2_SERVICE_NAVIGATION_POLICY_ALLOW:
+               webkit_policy_decision_use (decision);
+               break;
+       case E_OAUTH2_SERVICE_NAVIGATION_POLICY_ABORT:
+               g_cancellable_cancel (prompter_oauth2->priv->cancellable);
+               gtk_dialog_response (prompter_oauth2->priv->dialog, GTK_RESPONSE_CANCEL);
+               break;
+       default:
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static void
+cpi_oauth2_document_load_changed_cb (WebKitWebView *web_view,
+                                    WebKitLoadEvent load_event,
+                                    ECredentialsPrompterImplOAuth2 *prompter_oauth2)
+{
+       const gchar *title, *uri;
+
+       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_get_flags (prompter_oauth2->priv->service) & 
E_OAUTH2_SERVICE_FLAG_EXTRACT_REQUIRES_PAGE_CONTENT) != 0) {
+               WebKitWebResource *main_resource;
+
+               main_resource = webkit_web_view_get_main_resource (web_view);
+               if (main_resource) {
+                       webkit_web_resource_get_data (main_resource, prompter_oauth2->priv->cancellable,
+                               cpi_oauth2_web_view_resource_get_data_done_cb, prompter_oauth2);
+               }
+       } else {
+               cpi_oauth2_extract_authentication_code (prompter_oauth2, title, uri, NULL);
+       }
+}
+
+static void
 cpi_oauth2_notify_estimated_load_progress_cb (WebKitWebView *web_view,
                                              GParamSpec *param,
                                              GtkProgressBar *progress_bar)
@@ -449,6 +553,7 @@ e_credentials_prompter_impl_oauth2_show_dialog (ECredentialsPrompterImplOAuth2 *
        GtkScrolledWindow *scrolled_window;
        GtkWindow *dialog_parent;
        ECredentialsPrompter *prompter;
+       WebKitSettings *webkit_settings;
        gchar *title, *uri;
        GString *info_markup;
        gint row = 0;
@@ -544,7 +649,19 @@ e_credentials_prompter_impl_oauth2_show_dialog (ECredentialsPrompterImplOAuth2 *
 
        scrolled_window = GTK_SCROLLED_WINDOW (widget);
 
-       widget = webkit_web_view_new ();
+       webkit_settings = webkit_settings_new_with_settings (
+               "auto-load-images", TRUE,
+               "default-charset", "utf-8",
+               "enable-html5-database", FALSE,
+               "enable-dns-prefetching", FALSE,
+               "enable-html5-local-storage", FALSE,
+               "enable-offline-web-application-cache", FALSE,
+               "enable-page-cache", FALSE,
+               "enable-plugins", FALSE,
+               "media-playback-allows-inline", FALSE,
+               NULL);
+
+       widget = webkit_web_view_new_with_settings (webkit_settings);
        g_object_set (
                G_OBJECT (widget),
                "hexpand", TRUE,
@@ -553,6 +670,7 @@ e_credentials_prompter_impl_oauth2_show_dialog (ECredentialsPrompterImplOAuth2 *
                "valign", GTK_ALIGN_FILL,
                NULL);
        gtk_container_add (GTK_CONTAINER (scrolled_window), widget);
+       g_object_unref (webkit_settings);
 
        prompter_oauth2->priv->web_view = WEBKIT_WEB_VIEW (widget);
 
@@ -578,8 +696,10 @@ e_credentials_prompter_impl_oauth2_show_dialog (ECredentialsPrompterImplOAuth2 *
                success = FALSE;
        } else {
                WebKitWebView *web_view = prompter_oauth2->priv->web_view;
-               gulong load_finished_handler_id, progress_handler_id;
+               gulong decide_policy_handler_id, load_finished_handler_id, progress_handler_id;
 
+               decide_policy_handler_id = g_signal_connect (web_view, "decide-policy",
+                       G_CALLBACK (cpi_oauth2_decide_policy_cb), prompter_oauth2);
                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",
@@ -589,6 +709,8 @@ e_credentials_prompter_impl_oauth2_show_dialog (ECredentialsPrompterImplOAuth2 *
 
                success = gtk_dialog_run (prompter_oauth2->priv->dialog) == GTK_RESPONSE_OK;
 
+               if (decide_policy_handler_id)
+                       g_signal_handler_disconnect (web_view, decide_policy_handler_id);
                if (load_finished_handler_id)
                        g_signal_handler_disconnect (web_view, load_finished_handler_id);
                if (progress_handler_id)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]