[shotwell/wip/pluggable-auth: 10/16] wip: Extract Google authenticator



commit 04ee3b128d91c975597a3f87fb6d4f0ecda7328c
Author: Jens Georg <mail jensge org>
Date:   Wed Feb 8 23:15:04 2017 +0100

    wip: Extract Google authenticator
    
    Signed-off-by: Jens Georg <mail jensge org>

 authenticator.am                                   |    3 +-
 configure.ac                                       |    4 +-
 .../shotwell/GoogleAuthenticator.vala              |  370 ++++++++++++++++++++
 .../shotwell/ShotwellAuthenticatorFactory.vala     |   11 +-
 plugins/common/RESTSupport.vala                    |  363 ++------------------
 plugins/shotwell-publishing/PicasaPublishing.vala  |    4 +
 plugins/shotwell-publishing/YouTubePublishing.vala |    4 +
 7 files changed, 413 insertions(+), 346 deletions(-)
---
diff --git a/authenticator.am b/authenticator.am
index 442ad5b..ba0dda2 100644
--- a/authenticator.am
+++ b/authenticator.am
@@ -48,5 +48,6 @@ AUTHENTICATOR_RESOURCE_SOURCEDIR := $(abs_top_srcdir)/plugins/authenticator/shot
 plugins_authenticator_libshotwell_authenticator_la_SOURCES += \
        plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala \
        plugins/authenticator/shotwell/FacebookPublishingAuthenticator.vala \
-       plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala
+       plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala \
+       plugins/authenticator/shotwell/GoogleAuthenticator.vala
 endif
diff --git a/configure.ac b/configure.ac
index e41b006..9d904a9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -141,8 +141,8 @@ AS_IF([test "x$with_authenticator" = "xshotwell"],
       [
         PKG_CHECK_MODULES(AUTHENTICATOR, [gobject-2.0 glib-2.0 libsoup-2.4
                                           webkit2gtk-4.0 gee-0.8 gtk+-3.0
-                                          libxml-2.0])
-        AC_SUBST(AUTHENTICATOR_PACKAGES, ["--pkg webkit2gtk-4.0 --pkg gtk+-3.0 --pkg libsoup-2.4 --pkg 
gee-0.8 --pkg libxml-2.0"])
+                                          libxml-2.0 json-glib-1.0])
+        AC_SUBST(AUTHENTICATOR_PACKAGES, ["--pkg webkit2gtk-4.0 --pkg gtk+-3.0 --pkg libsoup-2.4 --pkg 
gee-0.8 --pkg libxml-2.0 --pkg json-glib-1.0"])
       ])
 
 dnl ***********************************************************************
diff --git a/plugins/authenticator/shotwell/GoogleAuthenticator.vala 
b/plugins/authenticator/shotwell/GoogleAuthenticator.vala
new file mode 100644
index 0000000..e92455e
--- /dev/null
+++ b/plugins/authenticator/shotwell/GoogleAuthenticator.vala
@@ -0,0 +1,370 @@
+using Shotwell;
+using Shotwell.Plugins;
+
+namespace Publishing.Authenticator.Shotwell.Google {
+    private const string OAUTH_CLIENT_ID = 
"1073902228337-gm4uf5etk25s0hnnm0g7uv2tm2bm1j0b.apps.googleusercontent.com";
+    private const string OAUTH_CLIENT_SECRET = "_kA4RZz72xqed4DqfO7xMmMN";
+
+    private class WebAuthenticationPane : Common.WebAuthenticationPane {
+        public static bool cache_dirty = false;
+        
+        public signal void authorized(string auth_code);
+
+        public WebAuthenticationPane(string auth_sequence_start_url) {
+            Object (login_uri : auth_sequence_start_url);
+        }
+        
+        public static bool is_cache_dirty() {
+            return cache_dirty;
+        }
+        
+        public override void on_page_load() {
+            string page_title = get_view ().get_title();
+            if (page_title.index_of("state=connect") > 0) {
+                int auth_code_field_start = page_title.index_of("code=");
+                if (auth_code_field_start < 0)
+                    return;
+
+                string auth_code =
+                    page_title.substring(auth_code_field_start + 5); // 5 = "code=".length
+
+                cache_dirty = true;
+
+                authorized(auth_code);
+            }
+        }
+    }
+
+    private class Session : Publishing.RESTSupport.Session {
+        public string access_token = null;
+        public string refresh_token = null;
+
+        public override bool is_authenticated() {
+            return (access_token != null);
+        }
+    }
+    
+    private class GetAccessTokensTransaction : Publishing.RESTSupport.Transaction {
+        private const string ENDPOINT_URL = "https://accounts.google.com/o/oauth2/token";;
+        
+        public GetAccessTokensTransaction(Session session, string auth_code) {
+            base.with_endpoint_url(session, ENDPOINT_URL);
+            
+            add_argument("code", auth_code);
+            add_argument("client_id", OAUTH_CLIENT_ID);
+            add_argument("client_secret", OAUTH_CLIENT_SECRET);
+            add_argument("redirect_uri", "urn:ietf:wg:oauth:2.0:oob");
+            add_argument("grant_type", "authorization_code");
+        }
+    }
+
+    private class RefreshAccessTokenTransaction : Publishing.RESTSupport.Transaction {
+        private const string ENDPOINT_URL = "https://accounts.google.com/o/oauth2/token";;
+        
+        public RefreshAccessTokenTransaction(Session session) {
+            base.with_endpoint_url(session, ENDPOINT_URL);
+        
+            add_argument("client_id", OAUTH_CLIENT_ID);
+            add_argument("client_secret", OAUTH_CLIENT_SECRET);
+            add_argument("refresh_token", session.refresh_token);
+            add_argument("grant_type", "refresh_token");
+        }
+    }
+
+    private class UsernameFetchTransaction : Publishing.RESTSupport.Transaction {
+        private const string ENDPOINT_URL = "https://www.googleapis.com/oauth2/v1/userinfo";;
+        public UsernameFetchTransaction(Session session) {
+            base.with_endpoint_url(session, ENDPOINT_URL, Publishing.RESTSupport.HttpMethod.GET);
+            add_header("Authorization", "Bearer " + session.access_token);
+        }
+    }
+
+    internal class Google : Spit.Publishing.Authenticator, Object {
+        private string scope = null;
+        private Spit.Publishing.PluginHost host = null;
+        private GLib.HashTable<string, Variant> params = null;
+        private WebAuthenticationPane web_auth_pane = null;
+        private Session session = null;
+
+        public Google(string scope, Spit.Publishing.PluginHost host) {
+            this.host = host;
+            this.params = new GLib.HashTable<string, Variant>(str_hash, str_equal);
+            this.scope = scope;
+            this.session = new Session();
+        }
+
+        public void authenticate() {
+            var refresh_token = host.get_config_string("refresh_token", null);
+            if (refresh_token != null && refresh_token != "") {
+                session.refresh_token = refresh_token;
+                do_exchange_refresh_token_for_access_token();
+            }
+
+            // FIXME: Find a way for a proper logout
+            if (WebAuthenticationPane.is_cache_dirty()) {
+                host.set_service_locked(false);
+
+                host.install_static_message_pane(_("You have already logged in and out of a Google service 
during this Shotwell session.\n\nTo continue publishing to Google services, quit and restart Shotwell, then 
try publishing again."));
+            } else {
+                this.do_hosted_web_authentication();
+            }
+        }
+
+        public bool can_logout() {
+            return true;
+        }
+
+        public GLib.HashTable<string, Variant> get_authentication_parameter() {
+            return this.params;
+        }
+
+        public void invalidate_persistent_session() {
+        }
+
+        public void logout() {
+        }
+
+        private void do_hosted_web_authentication() {
+            debug("ACTION: running OAuth authentication flow in hosted web pane.");
+
+            string user_authorization_url = "https://accounts.google.com/o/oauth2/auth?"; +
+                "response_type=code&" +
+                "client_id=" + OAUTH_CLIENT_ID + "&" +
+                "redirect_uri=" + Soup.URI.encode("urn:ietf:wg:oauth:2.0:oob", null) + "&" +
+                "scope=" + Soup.URI.encode(this.scope, null) + "+" +
+                Soup.URI.encode("https://www.googleapis.com/auth/userinfo.profile";, null) + "&" +
+                "state=connect&" +
+                "access_type=offline&" +
+                "approval_prompt=force";
+
+            web_auth_pane = new WebAuthenticationPane(user_authorization_url);
+            web_auth_pane.authorized.connect(on_web_auth_pane_authorized);
+
+            host.install_dialog_pane(web_auth_pane);
+
+        }
+
+        private void on_web_auth_pane_authorized(string auth_code) {
+            web_auth_pane.authorized.disconnect(on_web_auth_pane_authorized);
+
+            debug("EVENT: user authorized scope %s with auth_code %s", scope, auth_code);
+
+            do_get_access_tokens(auth_code);
+        }
+
+        private void do_get_access_tokens(string auth_code) {
+            debug("ACTION: exchanging authorization code for access & refresh tokens");
+
+            host.install_login_wait_pane();
+
+            GetAccessTokensTransaction tokens_txn = new GetAccessTokensTransaction(session, auth_code);
+            tokens_txn.completed.connect(on_get_access_tokens_complete);
+            tokens_txn.network_error.connect(on_get_access_tokens_error);
+
+            try {
+                tokens_txn.execute();
+            } catch (Spit.Publishing.PublishingError err) {
+                host.post_error(err);
+            }
+        }
+
+        private void on_get_access_tokens_complete(Publishing.RESTSupport.Transaction txn) {
+            txn.completed.disconnect(on_get_access_tokens_complete);
+            txn.network_error.disconnect(on_get_access_tokens_error);
+
+            debug("EVENT: network transaction to exchange authorization code for access tokens " +
+                    "completed successfully.");
+
+            do_extract_tokens(txn.get_response());
+        }
+
+        private void on_get_access_tokens_error(Publishing.RESTSupport.Transaction txn,
+                Spit.Publishing.PublishingError err) {
+            txn.completed.disconnect(on_get_access_tokens_complete);
+            txn.network_error.disconnect(on_get_access_tokens_error);
+
+            debug("EVENT: network transaction to exchange authorization code for access tokens " +
+                    "failed; response = '%s'", txn.get_response());
+
+            host.post_error(err);
+        }
+
+        private void do_extract_tokens(string response_body) {
+            debug("ACTION: extracting OAuth tokens from body of server response");
+
+            Json.Parser parser = new Json.Parser();
+
+            try {
+                parser.load_from_data(response_body);
+            } catch (Error err) {
+                host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
+                    "Couldn't parse JSON response: " + err.message));
+                return;
+            }
+
+            Json.Object response_obj = parser.get_root().get_object();
+
+            if ((!response_obj.has_member("access_token")) && (!response_obj.has_member("refresh_token"))) {
+                host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
+                    "neither access_token nor refresh_token not present in server response"));
+                return;
+            }
+
+            if (response_obj.has_member("refresh_token")) {
+                string refresh_token = response_obj.get_string_member("refresh_token");
+
+                if (refresh_token != "")
+                    on_refresh_token_available(refresh_token);
+            }
+
+            if (response_obj.has_member("access_token")) {
+                string access_token = response_obj.get_string_member("access_token");
+
+                if (access_token != "")
+                    on_access_token_available(access_token);
+            }
+        }
+        private void on_refresh_token_available(string token) {
+            debug("EVENT: an OAuth refresh token has become available; token = '%s'.", token);
+            this.params.insert("RefreshToken", new Variant.string(token));
+
+            session.refresh_token = token;
+        }
+
+        private void on_access_token_available(string token) {
+            debug("EVENT: an OAuth access token has become available; token = '%s'.", token);
+
+            session.access_token = token;
+            this.params.insert("AccessToken", new Variant.string(token));
+
+            do_fetch_username();
+        }
+
+        private void do_fetch_username() {
+            debug("ACTION: running network transaction to fetch username.");
+
+            host.install_login_wait_pane();
+            host.set_service_locked(true);
+
+            UsernameFetchTransaction txn = new UsernameFetchTransaction(session);
+            txn.completed.connect(on_fetch_username_transaction_completed);
+            txn.network_error.connect(on_fetch_username_transaction_error);
+
+            try {
+                txn.execute();
+            } catch (Error err) {
+                host.post_error(err);
+            }
+        }
+
+        private void on_fetch_username_transaction_completed(Publishing.RESTSupport.Transaction txn) {
+            txn.completed.disconnect(on_fetch_username_transaction_completed);
+            txn.network_error.disconnect(on_fetch_username_transaction_error);
+
+            debug("EVENT: username fetch transaction completed successfully.");
+
+            do_extract_username(txn.get_response());
+        }
+
+        private void on_fetch_username_transaction_error(Publishing.RESTSupport.Transaction txn,
+                Spit.Publishing.PublishingError err) {
+            txn.completed.disconnect(on_fetch_username_transaction_completed);
+            txn.network_error.disconnect(on_fetch_username_transaction_error);
+
+            debug("EVENT: username fetch transaction caused a network error");
+
+            host.post_error(err);
+        }
+
+        private void do_extract_username(string response_body) {
+            debug("ACTION: extracting username from body of server response");
+
+            Json.Parser parser = new Json.Parser();
+
+            try {
+                parser.load_from_data(response_body);
+            } catch (Error err) {
+                host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
+                            "Couldn't parse JSON response: " + err.message));
+                return;
+            }
+
+            Json.Object response_obj = parser.get_root().get_object();
+
+            if (response_obj.has_member("name")) {
+                string username = response_obj.get_string_member("name");
+
+                if (username != "")
+                    this.params.insert("UserName", new Variant.string(username));
+            }
+
+            if (response_obj.has_member("access_token")) {
+                string access_token = response_obj.get_string_member("access_token");
+
+                if (access_token != "")
+                    this.params.insert("AccessToken", new Variant.string(access_token));
+            }
+
+            // by the time we get a username, the session should be authenticated, or else something
+            // really tragic has happened
+            assert(session.is_authenticated());
+
+            this.authenticated();
+        }
+
+
+        private void do_exchange_refresh_token_for_access_token() {
+            debug("ACTION: exchanging OAuth refresh token for OAuth access token.");
+
+            host.install_login_wait_pane();
+
+            RefreshAccessTokenTransaction txn = new RefreshAccessTokenTransaction(session);
+
+            txn.completed.connect(on_refresh_access_token_transaction_completed);
+            txn.network_error.connect(on_refresh_access_token_transaction_error);
+
+            try {
+                txn.execute();
+            } catch (Spit.Publishing.PublishingError err) {
+                host.post_error(err);
+            }
+        }
+
+        private void on_refresh_access_token_transaction_completed(Publishing.RESTSupport.Transaction
+                txn) {
+            txn.completed.disconnect(on_refresh_access_token_transaction_completed);
+            txn.network_error.disconnect(on_refresh_access_token_transaction_error);
+
+            debug("EVENT: refresh access token transaction completed successfully.");
+
+            if (session.is_authenticated()) // ignore these events if the session is already auth'd
+                return;
+
+            do_extract_tokens(txn.get_response());
+        }
+
+        private void on_refresh_access_token_transaction_error(Publishing.RESTSupport.Transaction txn,
+                Spit.Publishing.PublishingError err) {
+            txn.completed.disconnect(on_refresh_access_token_transaction_completed);
+            txn.network_error.disconnect(on_refresh_access_token_transaction_error);
+
+            debug("EVENT: refresh access token transaction caused a network error.");
+
+            if (session.is_authenticated()) // ignore these events if the session is already auth'd
+                return;
+
+            // 400 errors indicate that the OAuth client ID and secret have become invalid. In most
+            // cases, this can be fixed by logging the user out
+#if 0
+            if (txn.get_status_code() == 400) {
+                do_logout();
+                return;
+            }
+#endif
+
+            host.post_error(err);
+        }
+
+    }
+ 
+}
diff --git a/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala 
b/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala
index d804440..b059b78 100644
--- a/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala
+++ b/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala
@@ -12,8 +12,10 @@ namespace Publishing.Authenticator {
 
         public GLib.List<string> get_available_authenticators() {
             var list = new GLib.List<string>();
-            list.append ("flickr");
-            list.append ("facebook");
+            list.append("flickr");
+            list.append("facebook");
+            list.append("picasa");
+            list.append("youtube");
 
             return list;
         }
@@ -25,6 +27,11 @@ namespace Publishing.Authenticator {
                     return new Shotwell.Flickr.Flickr(host);
                 case "facebook":
                     return new Shotwell.Facebook.Facebook(host);
+                case "picasa":
+                    return new Shotwell.Google.Google("https://picasaweb.google.com/data/";, host);
+
+                case "youtube":
+                    return new Shotwell.Google.Google("https://gdata.youtube.com/";, host);
                 default:
                     return null;
             }
diff --git a/plugins/common/RESTSupport.vala b/plugins/common/RESTSupport.vala
index 1a9052b..0821ea9 100644
--- a/plugins/common/RESTSupport.vala
+++ b/plugins/common/RESTSupport.vala
@@ -791,63 +791,6 @@ public abstract class GooglePublisher : Object, Spit.Publishing.Publisher {
         }
     }
     
-    private class WebAuthenticationPane : Shotwell.Plugins.Common.WebAuthenticationPane {
-        public static bool cache_dirty = false;
-        
-        public signal void authorized(string auth_code);
-
-        public WebAuthenticationPane(string auth_sequence_start_url) {
-            Object (login_uri : auth_sequence_start_url);
-        }
-        
-        public static bool is_cache_dirty() {
-            return cache_dirty;
-        }
-        
-        public override void on_page_load() {
-            string page_title = get_view ().get_title();
-            if (page_title.index_of("state=connect") > 0) {
-                int auth_code_field_start = page_title.index_of("code=");
-                if (auth_code_field_start < 0)
-                    return;
-
-                string auth_code =
-                    page_title.substring(auth_code_field_start + 5); // 5 = "code=".length
-
-                cache_dirty = true;
-
-                authorized(auth_code);
-            }
-        }
-    }
-    
-    private class GetAccessTokensTransaction : Publishing.RESTSupport.Transaction {
-        private const string ENDPOINT_URL = "https://accounts.google.com/o/oauth2/token";;
-        
-        public GetAccessTokensTransaction(Session session, string auth_code) {
-            base.with_endpoint_url(session, ENDPOINT_URL);
-            
-            add_argument("code", auth_code);
-            add_argument("client_id", OAUTH_CLIENT_ID);
-            add_argument("client_secret", OAUTH_CLIENT_SECRET);
-            add_argument("redirect_uri", "urn:ietf:wg:oauth:2.0:oob");
-            add_argument("grant_type", "authorization_code");
-        }
-    }
-
-    private class RefreshAccessTokenTransaction : Publishing.RESTSupport.Transaction {
-        private const string ENDPOINT_URL = "https://accounts.google.com/o/oauth2/token";;
-        
-        public RefreshAccessTokenTransaction(Session session) {
-            base.with_endpoint_url(session, ENDPOINT_URL);
-        
-            add_argument("client_id", OAUTH_CLIENT_ID);
-            add_argument("client_secret", OAUTH_CLIENT_SECRET);
-            add_argument("refresh_token", ((GoogleSession) session).get_refresh_token());
-            add_argument("grant_type", "refresh_token");
-        }
-    }
-
     public class AuthenticatedTransaction : Publishing.RESTSupport.Transaction {
         private AuthenticatedTransaction.with_endpoint_url(GoogleSession session,
             string endpoint_url, Publishing.RESTSupport.HttpMethod method) {
@@ -863,19 +806,11 @@ public abstract class GooglePublisher : Object, Spit.Publishing.Publisher {
         }
     }
 
-    private class UsernameFetchTransaction : AuthenticatedTransaction {
-        private const string ENDPOINT_URL = "https://www.googleapis.com/oauth2/v1/userinfo";;
-        
-        public UsernameFetchTransaction(GoogleSession session) {
-            base(session, ENDPOINT_URL, Publishing.RESTSupport.HttpMethod.GET);
-        }
-    }
-    
     private string scope;
     private GoogleSessionImpl session;
-    private WebAuthenticationPane? web_auth_pane;
     private weak Spit.Publishing.PluginHost host;
     private weak Spit.Publishing.Service service;
+    private Spit.Publishing.Authenticator authenticator;
     
     protected GooglePublisher(Spit.Publishing.Service service, Spit.Publishing.PluginHost host,
         string scope) {
@@ -883,272 +818,11 @@ public abstract class GooglePublisher : Object, Spit.Publishing.Publisher {
         this.session = new GoogleSessionImpl();
         this.service = service;
         this.host = host;
-        this.web_auth_pane = null;
-    }
-    
-    private void on_web_auth_pane_authorized(string auth_code) {
-        web_auth_pane.authorized.disconnect(on_web_auth_pane_authorized);
-        
-        debug("EVENT: user authorized scope %s with auth_code %s", scope, auth_code);
-        
-        if (!is_running())
-            return;
-        
-        do_get_access_tokens(auth_code);
-    }
-
-    private void on_get_access_tokens_complete(Publishing.RESTSupport.Transaction txn) {
-        txn.completed.disconnect(on_get_access_tokens_complete);
-        txn.network_error.disconnect(on_get_access_tokens_error);
-
-        debug("EVENT: network transaction to exchange authorization code for access tokens " +
-            "completed successfully.");
-
-        if (!is_running())
-            return;
-
-        do_extract_tokens(txn.get_response());
+        this.authenticator = this.get_authenticator();
+        this.authenticator.authenticated.connect(on_authenticator_authenticated);
     }
 
-    private void on_get_access_tokens_error(Publishing.RESTSupport.Transaction txn,
-        Spit.Publishing.PublishingError err) {
-        txn.completed.disconnect(on_get_access_tokens_complete);
-        txn.network_error.disconnect(on_get_access_tokens_error);
-
-        debug("EVENT: network transaction to exchange authorization code for access tokens " +
-            "failed; response = '%s'", txn.get_response());
-
-        if (!is_running())
-            return;
-
-        host.post_error(err);
-    }
-
-    private void on_refresh_access_token_transaction_completed(Publishing.RESTSupport.Transaction
-        txn) {
-        txn.completed.disconnect(on_refresh_access_token_transaction_completed);
-        txn.network_error.disconnect(on_refresh_access_token_transaction_error);
-
-        debug("EVENT: refresh access token transaction completed successfully.");
-
-        if (!is_running())
-            return;
-
-        if (session.is_authenticated()) // ignore these events if the session is already auth'd
-            return;
-        
-        do_extract_tokens(txn.get_response());
-    }
-    
-    private void on_refresh_access_token_transaction_error(Publishing.RESTSupport.Transaction txn,
-        Spit.Publishing.PublishingError err) {
-        txn.completed.disconnect(on_refresh_access_token_transaction_completed);
-        txn.network_error.disconnect(on_refresh_access_token_transaction_error);
-
-        debug("EVENT: refresh access token transaction caused a network error.");
-
-        if (!is_running())
-            return;
-
-        if (session.is_authenticated()) // ignore these events if the session is already auth'd
-            return;
-        
-        // 400 errors indicate that the OAuth client ID and secret have become invalid. In most
-        // cases, this can be fixed by logging the user out
-        if (txn.get_status_code() == 400) {
-            do_logout();
-            return;
-        }
-        
-        host.post_error(err);       
-    }
-
-    private void on_refresh_token_available(string token) {
-        debug("EVENT: an OAuth refresh token has become available; token = '%s'.", token);
-
-        if (!is_running())
-            return;
-
-        session.refresh_token = token;
-    }
-    
-    private void on_access_token_available(string token) {
-        debug("EVENT: an OAuth access token has become available; token = '%s'.", token);
-
-        if (!is_running())
-            return;
-
-        session.access_token = token;
-        
-        do_fetch_username();
-    }
-
-    private void on_fetch_username_transaction_completed(Publishing.RESTSupport.Transaction txn) {
-        txn.completed.disconnect(on_fetch_username_transaction_completed);
-        txn.network_error.disconnect(on_fetch_username_transaction_error);
-        
-        debug("EVENT: username fetch transaction completed successfully.");
-
-        if (!is_running())
-            return;
-
-        do_extract_username(txn.get_response());
-    }
-    
-    private void on_fetch_username_transaction_error(Publishing.RESTSupport.Transaction txn,
-        Spit.Publishing.PublishingError err) {
-        txn.completed.disconnect(on_fetch_username_transaction_completed);
-        txn.network_error.disconnect(on_fetch_username_transaction_error);
-
-        debug("EVENT: username fetch transaction caused a network error");
-
-        if (!is_running())
-            return;
-
-        host.post_error(err);
-    }
-
-    private void do_get_access_tokens(string auth_code) {
-        debug("ACTION: exchanging authorization code for access & refresh tokens");
-        
-        host.install_login_wait_pane();
-        
-        GetAccessTokensTransaction tokens_txn = new GetAccessTokensTransaction(session, auth_code);
-        tokens_txn.completed.connect(on_get_access_tokens_complete);
-        tokens_txn.network_error.connect(on_get_access_tokens_error);
-        
-        try {
-            tokens_txn.execute();
-        } catch (Spit.Publishing.PublishingError err) {
-            host.post_error(err);
-        }
-    }
-    
-    private void do_hosted_web_authentication() {
-        debug("ACTION: running OAuth authentication flow in hosted web pane.");
-        
-        string user_authorization_url = "https://accounts.google.com/o/oauth2/auth?"; +
-            "response_type=code&" +
-            "client_id=" + OAUTH_CLIENT_ID + "&" +
-            "redirect_uri=" + Soup.URI.encode("urn:ietf:wg:oauth:2.0:oob", null) + "&" +
-            "scope=" + Soup.URI.encode(scope, null) + "+" +
-            Soup.URI.encode("https://www.googleapis.com/auth/userinfo.profile";, null) + "&" +
-            "state=connect&" +
-            "access_type=offline&" +
-            "approval_prompt=force";
-
-        web_auth_pane = new WebAuthenticationPane(user_authorization_url);
-        web_auth_pane.authorized.connect(on_web_auth_pane_authorized);
-        
-        host.install_dialog_pane(web_auth_pane);
-        
-    }
-
-    private void do_exchange_refresh_token_for_access_token() {
-        debug("ACTION: exchanging OAuth refresh token for OAuth access token.");
-        
-        host.install_login_wait_pane();
-        
-        RefreshAccessTokenTransaction txn = new RefreshAccessTokenTransaction(session);
-        
-        txn.completed.connect(on_refresh_access_token_transaction_completed);
-        txn.network_error.connect(on_refresh_access_token_transaction_error);
-        
-        try {
-            txn.execute();
-        } catch (Spit.Publishing.PublishingError err) {
-            host.post_error(err);
-        }
-    }
-
-    private void do_extract_tokens(string response_body) {
-        debug("ACTION: extracting OAuth tokens from body of server response");
-        
-        Json.Parser parser = new Json.Parser();
-        
-        try {
-            parser.load_from_data(response_body);
-        } catch (Error err) {
-            host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
-                "Couldn't parse JSON response: " + err.message));
-            return;
-        }
-        
-        Json.Object response_obj = parser.get_root().get_object();
-        
-        if ((!response_obj.has_member("access_token")) && (!response_obj.has_member("refresh_token"))) {
-            host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
-                "neither access_token nor refresh_token not present in server response"));
-            return;
-        }
-
-        if (response_obj.has_member("refresh_token")) {
-            string refresh_token = response_obj.get_string_member("refresh_token");
-
-            if (refresh_token != "")
-                on_refresh_token_available(refresh_token);
-        }
-        
-        if (response_obj.has_member("access_token")) {
-            string access_token = response_obj.get_string_member("access_token");
-
-            if (access_token != "")
-                on_access_token_available(access_token);
-        }
-    }
-
-    private void do_fetch_username() {
-        debug("ACTION: running network transaction to fetch username.");
-
-        host.install_login_wait_pane();
-        host.set_service_locked(true);
-        
-        UsernameFetchTransaction txn = new UsernameFetchTransaction(session);
-        txn.completed.connect(on_fetch_username_transaction_completed);
-        txn.network_error.connect(on_fetch_username_transaction_error);
-        
-        try {
-            txn.execute();
-        } catch (Error err) {
-            host.post_error(err);
-        }
-    }
-
-    private void do_extract_username(string response_body) {
-        debug("ACTION: extracting username from body of server response");
-        
-        Json.Parser parser = new Json.Parser();
-        
-        try {
-            parser.load_from_data(response_body);
-        } catch (Error err) {
-            host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
-                "Couldn't parse JSON response: " + err.message));
-            return;
-        }
-        
-        Json.Object response_obj = parser.get_root().get_object();
-
-        if (response_obj.has_member("name")) {
-            string username = response_obj.get_string_member("name");
-
-            if (username != "")
-                session.user_name = username;
-        }
-        
-        if (response_obj.has_member("access_token")) {
-            string access_token = response_obj.get_string_member("access_token");
-
-            if (access_token != "")
-                session.access_token = access_token;
-        }
-        
-        // by the time we get a username, the session should be authenticated, or else something
-        // really tragic has happened
-        assert(session.is_authenticated());
-        
-        on_login_flow_complete();
-    }
+    protected abstract Spit.Publishing.Authenticator get_authenticator();
 
     protected unowned Spit.Publishing.PluginHost get_host() {
         return host;
@@ -1159,17 +833,7 @@ public abstract class GooglePublisher : Object, Spit.Publishing.Publisher {
     }
 
     protected void start_oauth_flow(string? refresh_token = null) {
-        if (refresh_token != null && refresh_token != "") {
-            session.refresh_token = refresh_token;
-            do_exchange_refresh_token_for_access_token();
-        } else {
-            if (WebAuthenticationPane.is_cache_dirty()) {
-                host.install_static_message_pane(_("You have already logged in and out of a Google service 
during this Shotwell session.\n\nTo continue publishing to Google services, quit and restart Shotwell, then 
try publishing again."));
-                return;
-            }
-
-            do_hosted_web_authentication();
-        }
+        authenticator.authenticate();
     }
     
     protected abstract void on_login_flow_complete();
@@ -1185,6 +849,23 @@ public abstract class GooglePublisher : Object, Spit.Publishing.Publisher {
     public Spit.Publishing.Service get_service() {
         return service;
     }
+
+    private void on_authenticator_authenticated() {
+        var params = this.authenticator.get_authentication_parameter();
+        Variant refresh_token = null;
+        Variant access_token = null;
+        Variant user_name = null;
+
+        params.lookup_extended("RefreshToken", null, out refresh_token);
+        params.lookup_extended("AccessToken", null, out access_token);
+        params.lookup_extended("UserName", null, out user_name);
+
+        this.session.refresh_token = refresh_token.get_string();
+        this.session.access_token = access_token.get_string();
+        this.session.user_name = user_name.get_string();
+
+        this.on_login_flow_complete();
+    }
 }
 
 }
diff --git a/plugins/shotwell-publishing/PicasaPublishing.vala 
b/plugins/shotwell-publishing/PicasaPublishing.vala
index 976ba42..cff9beb 100644
--- a/plugins/shotwell-publishing/PicasaPublishing.vala
+++ b/plugins/shotwell-publishing/PicasaPublishing.vala
@@ -459,6 +459,10 @@ public class PicasaPublisher : Publishing.RESTSupport.GooglePublisher {
 
         running = false;
     }
+
+    protected override Spit.Publishing.Authenticator get_authenticator() {
+        return Publishing.Authenticator.Factory.get_instance().create("picasa", get_host());
+    }
 }
 
 internal class Album {
diff --git a/plugins/shotwell-publishing/YouTubePublishing.vala 
b/plugins/shotwell-publishing/YouTubePublishing.vala
index bf2399a..ee7c158 100644
--- a/plugins/shotwell-publishing/YouTubePublishing.vala
+++ b/plugins/shotwell-publishing/YouTubePublishing.vala
@@ -415,6 +415,10 @@ public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
 
         do_show_service_welcome_pane();
     }
+
+    protected override Spit.Publishing.Authenticator get_authenticator() {
+        return Publishing.Authenticator.Factory.get_instance().create("picasa", get_host());
+    }
 }
 
 internal class PublishingOptionsPane : Spit.Publishing.DialogPane, GLib.Object {


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