[shotwell] Tumblr: Add Authenticator to framework



commit 314e369bb7b6302d39cbbe93bd3bef6904549f9b
Author: Jens Georg <mail jensge org>
Date:   Sun Oct 22 19:49:31 2017 +0200

    Tumblr: Add Authenticator to framework

 authenticator.am                                   |    4 +-
 .../shotwell/ShotwellAuthenticatorFactory.vala     |    3 +
 .../shotwell/TumblrAuthenticator.vala              |  479 ++++++++++++++++++++
 plugins/authenticator/shotwell/meson.build         |    5 +-
 .../org.gnome.Shotwell.Authenticator.gresource.xml |    1 +
 .../shotwell}/tumblr_authentication_pane.ui        |    0
 plugins/shotwell-publishing/TumblrPublishing.vala  |  372 +++-------------
 .../org.gnome.Shotwell.Publishing.gresource.xml    |    1 -
 po/POTFILES.in                                     |    2 +-
 publish.am                                         |    1 -
 10 files changed, 542 insertions(+), 326 deletions(-)
---
diff --git a/authenticator.am b/authenticator.am
index 0ba2898..d4e973d 100644
--- a/authenticator.am
+++ b/authenticator.am
@@ -2,6 +2,7 @@ lib_LTLIBRARIES += plugins/authenticator/libshotwell-authenticator.la
 
 dist_noinst_DATA += \
        plugins/authenticator/shotwell/flickr_pin_entry_pane.ui \
+       plugins/authenticator/shotwell/tumblr_authentication_pane.ui \
        plugins/authenticator/shotwell/org.gnome.Shotwell.Authenticator.gresource.xml \
        plugins/authenticator/shotwell-authenticator.h
 
@@ -55,5 +56,6 @@ plugins_authenticator_libshotwell_authenticator_la_SOURCES += \
        plugins/authenticator/shotwell/FacebookPublishingAuthenticator.vala \
        plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala \
        plugins/authenticator/shotwell/GoogleAuthenticator.vala \
-       plugins/authenticator/shotwell/OAuth1Authenticator.vala
+       plugins/authenticator/shotwell/OAuth1Authenticator.vala \
+       plugins/authenticator/shotwell/TumblrAuthenticator.vala
 endif
diff --git a/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala 
b/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala
index 17980e6..0d813ac 100644
--- a/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala
+++ b/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala
@@ -16,6 +16,7 @@ namespace Publishing.Authenticator {
             list.add("facebook");
             list.add("picasa");
             list.add("youtube");
+            list.add("tumblr");
 
             return list;
         }
@@ -32,6 +33,8 @@ namespace Publishing.Authenticator {
 
                 case "youtube":
                     return new Shotwell.Google.Google("https://gdata.youtube.com/";, _("You are not currently 
logged into YouTube.\n\nYou must have already signed up for a Google account and set it up for use with 
YouTube to continue. You can set up most accounts by using your browser to log into the YouTube site at least 
once."), host);
+                case "tumblr":
+                    return new Shotwell.Tumblr.Tumblr(host);
                 default:
                     return null;
             }
diff --git a/plugins/authenticator/shotwell/TumblrAuthenticator.vala 
b/plugins/authenticator/shotwell/TumblrAuthenticator.vala
new file mode 100644
index 0000000..70f2654
--- /dev/null
+++ b/plugins/authenticator/shotwell/TumblrAuthenticator.vala
@@ -0,0 +1,479 @@
+/* Copyright 2012 BJA Electronics
+ * Copyright 2017 Jens Georg
+ * Author: Jeroen Arnoldus (b j arnoldus bja-electronics nl)
+ * Author: Jens Georg <mail jensge org>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace Publishing.Authenticator.Shotwell.Tumblr {
+    internal const string ENDPOINT_URL = "https://www.tumblr.com/";;
+    internal const string API_KEY = "NdXvXQuKVccOsCOj0H4k9HUJcbcjDBYSo2AkaHzXFECHGNuP9k";
+    internal const string API_SECRET = "BN0Uoig0MwbeD27OgA0IwYlp3Uvonyfsrl9pf1cnnMj1QoEUvi";
+    internal const string ENCODE_RFC_3986_EXTRA = "!*'();:@&=+$,/?%#[] \\";
+
+    /**
+     * The authentication pane used when asking service URL, user name and password
+     * from the user.
+     */
+    internal class AuthenticationPane : Spit.Publishing.DialogPane, Object {
+        public enum Mode {
+            INTRO,
+            FAILED_RETRY_USER
+        }
+        private static string INTRO_MESSAGE = _("Enter the username and password associated with your Tumblr 
account.");
+        private static string FAILED_RETRY_USER_MESSAGE = _("Username and/or password invalid. Please try 
again");
+
+        private Gtk.Box pane_widget = null;
+        private Gtk.Builder builder;
+        private Gtk.Entry username_entry;
+        private Gtk.Entry password_entry;
+        private Gtk.Button login_button;
+
+        public signal void login(string user, string password);
+
+        public AuthenticationPane(Mode mode = Mode.INTRO) {
+            this.pane_widget = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
+
+            try {
+                builder = new Gtk.Builder();
+                builder.add_from_resource (Resources.RESOURCE_PATH + "/tumblr_authentication_pane.ui");
+                builder.connect_signals(null);
+                var content = builder.get_object ("content") as Gtk.Widget;
+
+                Gtk.Label message_label = builder.get_object("message_label") as Gtk.Label;
+                switch (mode) {
+                    case Mode.INTRO:
+                        message_label.set_text(INTRO_MESSAGE);
+                        break;
+
+                    case Mode.FAILED_RETRY_USER:
+                        message_label.set_markup("<b>%s</b>\n\n%s".printf(_(
+                                        "Invalid User Name or Password"), FAILED_RETRY_USER_MESSAGE));
+                        break;
+                }
+
+                username_entry = builder.get_object ("username_entry") as Gtk.Entry;
+
+                password_entry = builder.get_object ("password_entry") as Gtk.Entry;
+
+
+
+                login_button = builder.get_object("login_button") as Gtk.Button;
+
+                username_entry.changed.connect(on_user_changed);
+                password_entry.changed.connect(on_password_changed);
+                login_button.clicked.connect(on_login_button_clicked);
+
+                content.parent.remove (content);
+                pane_widget.add (content);
+            } catch (Error e) {
+                warning(_("Could not load UI: %s"), e.message);
+            }
+        }
+
+        public Gtk.Widget get_default_widget() {
+            return login_button;
+        }
+
+        private void on_login_button_clicked() {
+            login(username_entry.get_text(),
+                    password_entry.get_text());
+        }
+
+
+        private void on_user_changed() {
+            update_login_button_sensitivity();
+        }
+
+        private void on_password_changed() {
+            update_login_button_sensitivity();
+        }
+
+        private void update_login_button_sensitivity() {
+            login_button.set_sensitive(username_entry.text_length > 0 &&
+                    password_entry.text_length > 0);
+        }
+
+        public Gtk.Widget get_widget() {
+            return pane_widget;
+        }
+
+        public Spit.Publishing.DialogPane.GeometryOptions get_preferred_geometry() {
+            return Spit.Publishing.DialogPane.GeometryOptions.NONE;
+        }
+
+        public void on_pane_installed() {
+            username_entry.grab_focus();
+            password_entry.set_activates_default(true);
+            login_button.can_default = true;
+            update_login_button_sensitivity();
+        }
+
+        public void on_pane_uninstalled() {
+        }
+    }
+
+    // REST support classes
+    internal class Transaction : Publishing.RESTSupport.Transaction {
+        public Transaction(Session session, Publishing.RESTSupport.HttpMethod method =
+                Publishing.RESTSupport.HttpMethod.POST) {
+            base(session, method);
+
+        }
+
+        public Transaction.with_uri(Session session, string uri,
+                Publishing.RESTSupport.HttpMethod method = Publishing.RESTSupport.HttpMethod.POST) {
+            base.with_endpoint_url(session, uri, method);
+
+            add_argument("oauth_nonce", session.get_oauth_nonce());
+            add_argument("oauth_signature_method", "HMAC-SHA1");
+            add_argument("oauth_version", "1.0");
+            add_argument("oauth_timestamp", session.get_oauth_timestamp());
+            add_argument("oauth_consumer_key", API_KEY);
+            if (session.get_access_phase_token() != null) {
+                add_argument("oauth_token", session.get_access_phase_token());
+            }
+        }
+
+        public override void execute() throws Spit.Publishing.PublishingError {
+            ((Session) get_parent_session()).sign_transaction(this);
+
+            base.execute();
+        }
+
+    }
+
+    internal class AccessTokenFetchTransaction : Transaction {
+        public AccessTokenFetchTransaction(Session session, string username, string password) {
+            base.with_uri(session, "https://www.tumblr.com/oauth/access_token";,
+                    Publishing.RESTSupport.HttpMethod.POST);
+            add_argument("x_auth_username", Soup.URI.encode(username, ENCODE_RFC_3986_EXTRA));
+            add_argument("x_auth_password", password);
+            add_argument("x_auth_mode", "client_auth");
+        }
+    }
+
+
+    /**
+     * Session class that keeps track of the authentication status and of the
+     * user token tumblr.
+     */
+    internal class Session : Publishing.RESTSupport.Session {
+        private string? access_phase_token = null;
+        private string? access_phase_token_secret = null;
+        private string? username = null;
+
+        public Session() {
+            base(ENDPOINT_URL);
+        }
+
+        public override bool is_authenticated() {
+            return (access_phase_token != null && access_phase_token_secret != null);
+        }
+
+        public void authenticate_from_persistent_credentials(string token, string secret) {
+            this.access_phase_token = token;
+            this.access_phase_token_secret = secret;
+
+
+            debug("Emitting authenticated() signal");
+            authenticated();
+        }
+
+        public void deauthenticate() {
+            access_phase_token = null;
+            access_phase_token_secret = null;
+        }
+
+        public void sign_transaction(Publishing.RESTSupport.Transaction txn) {
+            string http_method = txn.get_method().to_string();
+
+            debug("signing transaction with parameters:");
+            debug("HTTP method = " + http_method);
+            string? signing_key = null;
+            if (access_phase_token_secret != null) {
+                debug("access phase token secret available; using it as signing key");
+
+                signing_key = API_SECRET + "&" + this.get_access_phase_token_secret();
+            } else {
+                debug("Access phase token secret not available; using API " +
+                        "key as signing key");
+
+                signing_key = API_SECRET + "&";
+            }
+
+
+            Publishing.RESTSupport.Argument[] base_string_arguments = txn.get_arguments();
+
+            Publishing.RESTSupport.Argument[] sorted_args =
+                Publishing.RESTSupport.Argument.sort(base_string_arguments);
+
+            string arguments_string = "";
+            for (int i = 0; i < sorted_args.length; i++) {
+                arguments_string += (sorted_args[i].key + "=" + sorted_args[i].value);
+                if (i < sorted_args.length - 1)
+                    arguments_string += "&";
+            }
+
+
+            string signature_base_string = http_method + "&" + Soup.URI.encode(
+                    txn.get_endpoint_url(), ENCODE_RFC_3986_EXTRA) + "&" +
+                Soup.URI.encode(arguments_string, ENCODE_RFC_3986_EXTRA);
+
+            debug("signature base string = '%s'", signature_base_string);
+            debug("signing key = '%s'", signing_key);
+
+            // compute the signature
+            string signature = Publishing.RESTSupport.hmac_sha1(signing_key, signature_base_string);
+            debug("signature = '%s'", signature);
+            signature = Soup.URI.encode(signature, ENCODE_RFC_3986_EXTRA);
+
+            debug("signature after RFC encode = '%s'", signature);
+
+            txn.add_argument("oauth_signature", signature);
+        }
+
+        public void set_access_phase_credentials(string token, string secret) {
+            this.access_phase_token = token;
+            this.access_phase_token_secret = secret;
+
+            authenticated();
+        }
+
+        public string get_access_phase_token() {
+            return access_phase_token;
+        }
+
+
+        public string get_access_phase_token_secret() {
+            return access_phase_token_secret;
+        }
+
+        public string get_username() {
+            return this.username;
+        }
+
+        public void set_username(string username) {
+            this.username = username;
+        }
+
+        public string get_oauth_nonce() {
+            TimeVal currtime = TimeVal();
+            currtime.get_current_time();
+
+            return Checksum.compute_for_string(ChecksumType.MD5, currtime.tv_sec.to_string() +
+                    currtime.tv_usec.to_string());
+        }
+
+        public string get_oauth_timestamp() {
+            return GLib.get_real_time().to_string().substring(0, 10);
+        }
+    }
+
+    internal class Tumblr : Spit.Publishing.Authenticator, GLib.Object {
+        private GLib.HashTable<string, Variant> params;
+        private Spit.Publishing.PluginHost host;
+        private Session session;
+
+        public Tumblr(Spit.Publishing.PluginHost host) {
+            base();
+
+            this.host = host;
+
+            this.params = new GLib.HashTable<string, Variant>(str_hash, str_equal);
+            params.insert("ConsumerKey", API_KEY);
+            params.insert("ConsumerSecret", API_SECRET);
+
+            this.session = new Session();
+            this.session.authenticated.connect(this.on_session_authenticated);
+        }
+
+        ~Tumblr() {
+            this.session.authenticated.disconnect(this.on_session_authenticated);
+        }
+
+        public void authenticate() {
+            if (is_persistent_session_valid()) {
+                debug("attempt start: a persistent session is available; using it");
+
+                session.authenticate_from_persistent_credentials(get_persistent_access_phase_token(),
+                        get_persistent_access_phase_token_secret());
+            } else {
+                debug("attempt start: no persistent session available; showing login welcome pane");
+
+                do_show_authentication_pane();
+            }
+        }
+
+        public bool can_logout() {
+            return true;
+        }
+
+        public GLib.HashTable<string, Variant> get_authentication_parameter() {
+            return this.params;
+        }
+
+        public void logout() {
+            this.session.deauthenticate();
+            invalidate_persistent_session();
+        }
+
+        public void refresh() { }
+
+        private void on_session_authenticated() {
+            params.insert("AuthToken", session.get_access_phase_token());
+            params.insert("AuthTokenSecret", session.get_access_phase_token_secret());
+            params.insert("Username", session.get_username());
+
+            set_persistent_access_phase_token(session.get_access_phase_token());
+            set_persistent_access_phase_token_secret(session.get_access_phase_token_secret());
+
+            this.authenticated();
+        }
+
+        private void invalidate_persistent_session() {
+            set_persistent_access_phase_token("");
+            set_persistent_access_phase_token_secret("");
+        }
+
+        private void set_persistent_access_phase_token(string? token) {
+            host.set_config_string("token", token);
+        }
+
+        private void set_persistent_access_phase_token_secret(string? token_secret) {
+            host.set_config_string("token_secret", token_secret);
+        }
+
+        private bool is_persistent_session_valid() {
+            string? access_phase_token = get_persistent_access_phase_token();
+            string? access_phase_token_secret = get_persistent_access_phase_token_secret();
+
+            bool valid = ((access_phase_token != null) && (access_phase_token_secret != null));
+
+            if (valid)
+                debug("existing Tumblr session found in configuration database; using it.");
+            else
+                debug("no persisted Tumblr session exists.");
+
+            return valid;
+        }
+
+        public string? get_persistent_access_phase_token() {
+            return host.get_config_string("token", null);
+        }
+
+        public string? get_persistent_access_phase_token_secret() {
+            return host.get_config_string("token_secret", null);
+        }
+
+        /**
+         * Action that shows the authentication pane.
+         *
+         * This action method shows the authentication pane. It is shown at the
+         * very beginning of the interaction when no persistent parameters are found
+         * or after a failed login attempt using persisted parameters. It can be
+         * given a mode flag to specify whether it should be displayed in initial
+         * mode or in any of the error modes that it supports.
+         *
+         * @param mode the mode for the authentication pane
+         */
+        private void do_show_authentication_pane(AuthenticationPane.Mode mode = 
AuthenticationPane.Mode.INTRO) {
+            debug("ACTION: installing authentication pane");
+
+            host.set_service_locked(false);
+            AuthenticationPane authentication_pane = new AuthenticationPane(mode);
+            authentication_pane.login.connect(on_authentication_pane_login_clicked);
+            host.install_dialog_pane(authentication_pane, Spit.Publishing.PluginHost.ButtonMode.CLOSE);
+            host.set_dialog_default_widget(authentication_pane.get_default_widget());
+        }
+
+        /**
+         * Event triggered when the login button in the authentication panel is
+         * clicked.
+         *
+         * This event is triggered when the login button in the authentication
+         * panel is clicked. It then triggers a network login interaction.
+         *
+         * @param username the name of the Tumblr user as entered in the dialog
+         * @param password the password of the Tumblr as entered in the dialog
+         */
+        private void on_authentication_pane_login_clicked( string username, string password ) {
+            debug("EVENT: on_authentication_pane_login_clicked");
+
+            do_network_login(username, password);
+        }
+
+        /**
+         * Action to perform a network login to a Tumblr blog.
+         *
+         * This action performs a network login a Tumblr blog specified the given user name and password as 
credentials.
+         *
+         * @param username the name of the Tumblr user used to login
+         * @param password the password of the Tumblr user used to login
+         */
+        private void do_network_login(string username, string password) {
+            debug("ACTION: logging in");
+            host.set_service_locked(true);
+            host.install_login_wait_pane();
+            session.set_username(username);
+
+            AccessTokenFetchTransaction txn = new AccessTokenFetchTransaction(session,username,password);
+            txn.completed.connect(on_auth_request_txn_completed);
+            txn.network_error.connect(on_auth_request_txn_error);
+
+            try {
+                txn.execute();
+            } catch (Spit.Publishing.PublishingError err) {
+                host.post_error(err);
+            }
+        }
+
+        private void on_auth_request_txn_completed(Publishing.RESTSupport.Transaction txn) {
+            txn.completed.disconnect(on_auth_request_txn_completed);
+            txn.network_error.disconnect(on_auth_request_txn_error);
+
+            debug("EVENT: OAuth authentication request transaction completed; response = '%s'",
+                  txn.get_response());
+
+            do_parse_token_info_from_auth_request(txn.get_response());
+        }
+
+        private void on_auth_request_txn_error(Publishing.RESTSupport.Transaction txn,
+                                               Spit.Publishing.PublishingError err) {
+            txn.completed.disconnect(on_auth_request_txn_completed);
+            txn.network_error.disconnect(on_auth_request_txn_error);
+
+            debug("EVENT: OAuth authentication request transaction caused a network error");
+            host.post_error(err);
+        }
+
+        private void do_parse_token_info_from_auth_request(string response) {
+            debug("ACTION: parsing authorization request response '%s' into token and secret", response);
+
+            string? oauth_token = null;
+            string? oauth_token_secret = null;
+
+            string[] key_value_pairs = response.split("&");
+            foreach (string pair in key_value_pairs) {
+                string[] split_pair = pair.split("=");
+
+                if (split_pair.length != 2)
+                    host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
+                                _("“%s” isn’t a valid response to an OAuth authentication request"), 
response));
+
+                if (split_pair[0] == "oauth_token")
+                    oauth_token = split_pair[1];
+                else if (split_pair[0] == "oauth_token_secret")
+                    oauth_token_secret = split_pair[1];
+            }
+
+            if (oauth_token == null || oauth_token_secret == null)
+                host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
+                            _("“%s” isn’t a valid response to an OAuth authentication request"), response));
+
+            session.set_access_phase_credentials(oauth_token, oauth_token_secret);
+        }
+    }
+}
diff --git a/plugins/authenticator/shotwell/meson.build b/plugins/authenticator/shotwell/meson.build
index 37f8a3b..4404242 100644
--- a/plugins/authenticator/shotwell/meson.build
+++ b/plugins/authenticator/shotwell/meson.build
@@ -3,7 +3,8 @@ authenticator_shotwell_sources = [
         'FacebookPublishingAuthenticator.vala',
         'FlickrPublishingAuthenticator.vala',
         'GoogleAuthenticator.vala',
-        'OAuth1Authenticator.vala'
+        'OAuth1Authenticator.vala',
+        'TumblrAuthenticator.vala'
         ]
 
 authenticator_shotwell_resources = gnome.compile_resources('authenticator-resource',
@@ -14,7 +15,7 @@ authenticator_shotwell_deps = [gee, gtk, gio, soup, json_glib, sw_plugin,
                                sw_plugin_common_dep, json_glib, xml, webkit]
 
 authenticator = library('shotwell-authenticator',
-                        authenticator_shotwell_sources,
+                        authenticator_shotwell_sources + authenticator_shotwell_resources,
                         dependencies : authenticator_shotwell_deps,
                         include_directories : config_incdir,
                         version: meson.project_version(),
diff --git a/plugins/authenticator/shotwell/org.gnome.Shotwell.Authenticator.gresource.xml 
b/plugins/authenticator/shotwell/org.gnome.Shotwell.Authenticator.gresource.xml
index c40e700..d942f58 100644
--- a/plugins/authenticator/shotwell/org.gnome.Shotwell.Authenticator.gresource.xml
+++ b/plugins/authenticator/shotwell/org.gnome.Shotwell.Authenticator.gresource.xml
@@ -2,5 +2,6 @@
 <gresources>
   <gresource prefix="/org/gnome/Shotwell/Authenticator">
       <file>flickr_pin_entry_pane.ui</file>
+      <file>tumblr_authentication_pane.ui</file>
   </gresource>
 </gresources>
diff --git a/plugins/shotwell-publishing/tumblr_authentication_pane.ui 
b/plugins/authenticator/shotwell/tumblr_authentication_pane.ui
similarity index 100%
rename from plugins/shotwell-publishing/tumblr_authentication_pane.ui
rename to plugins/authenticator/shotwell/tumblr_authentication_pane.ui
diff --git a/plugins/shotwell-publishing/TumblrPublishing.vala 
b/plugins/shotwell-publishing/TumblrPublishing.vala
index fdb1659..1ab0b70 100644
--- a/plugins/shotwell-publishing/TumblrPublishing.vala
+++ b/plugins/shotwell-publishing/TumblrPublishing.vala
@@ -5,7 +5,6 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
-
 public class TumblrService : Object, Spit.Pluggable, Spit.Publishing.Service {
    private const string ICON_FILENAME = "tumblr.png";
 
@@ -60,8 +59,6 @@ namespace Publishing.Tumblr {
 
 internal const string SERVICE_NAME = "Tumblr";
 internal const string ENDPOINT_URL = "https://www.tumblr.com/";;
-internal const string API_KEY = "NdXvXQuKVccOsCOj0H4k9HUJcbcjDBYSo2AkaHzXFECHGNuP9k";
-internal const string API_SECRET = "BN0Uoig0MwbeD27OgA0IwYlp3Uvonyfsrl9pf1cnnMj1QoEUvi";
 internal const string ENCODE_RFC_3986_EXTRA = "!*'();:@&=+$,/?%#[] \\";
 internal const int ORIGINAL_SIZE = -1;
 
@@ -96,7 +93,8 @@ public class TumblrPublisher : Spit.Publishing.Publisher, GLib.Object {
     private PublishingOptionsPane publishing_options_pane = null;
     private SizeEntry[] sizes = null;
     private BlogEntry[] blogs = null;
-       private string username = "";
+    private string username = "";
+    private Spit.Publishing.Authenticator authenticator;
 
 
     private SizeEntry[] create_sizes() {
@@ -126,19 +124,18 @@ public class TumblrPublisher : Spit.Publishing.Publisher, GLib.Object {
         this.service = service;
         this.host = host;
         this.session = new Session();
-               this.sizes = this.create_sizes();
-               this.blogs = this.create_blogs();
-        session.authenticated.connect(on_session_authenticated);
+        this.sizes = this.create_sizes();
+        this.blogs = this.create_blogs();
+
+        this.authenticator = Publishing.Authenticator.Factory.get_instance().create("tumblr", host);
+        debug("=> Connecting to signal!");
+        this.authenticator.authenticated.connect(on_session_authenticated);
     }
 
     ~TumblrPublisher() {
-        session.authenticated.disconnect(on_session_authenticated);
+        this.authenticator.authenticated.disconnect(on_session_authenticated);
     }
 
-    private void invalidate_persistent_session() {
-        set_persistent_access_phase_token("");
-        set_persistent_access_phase_token_secret("");
-    }
     // Publisher interface implementation
 
     public Spit.Publishing.Service get_service() {
@@ -153,39 +150,6 @@ public class TumblrPublisher : Spit.Publishing.Publisher, GLib.Object {
         return running;
     }
 
-    private bool is_persistent_session_valid() {
-        string? access_phase_token = get_persistent_access_phase_token();
-        string? access_phase_token_secret = get_persistent_access_phase_token_secret();
-
-        bool valid = ((access_phase_token != null) && (access_phase_token_secret != null));
-
-        if (valid)
-            debug("existing Tumblr session found in configuration database; using it.");
-        else
-            debug("no persisted Tumblr session exists.");
-
-        return valid;
-    }
-
-
-
-
-    public string? get_persistent_access_phase_token() {
-        return host.get_config_string("token", null);
-    }
-
-    private void set_persistent_access_phase_token(string? token) {
-        host.set_config_string("token", token);
-    }
-
-    public string? get_persistent_access_phase_token_secret() {
-        return host.get_config_string("token_secret", null);
-    }
-
-    private void set_persistent_access_phase_token_secret(string? token_secret) {
-        host.set_config_string("token_secret", token_secret);
-    }
-
     internal int get_persistent_default_size() {
         return host.get_config_int("default_size", 1);
     }
@@ -204,138 +168,31 @@ public class TumblrPublisher : Spit.Publishing.Publisher, GLib.Object {
 
     // Actions and events implementation
 
-    /**
-     * Action that shows the authentication pane.
-     *
-     * This action method shows the authentication pane. It is shown at the
-     * very beginning of the interaction when no persistent parameters are found
-     * or after a failed login attempt using persisted parameters. It can be
-     * given a mode flag to specify whether it should be displayed in initial
-     * mode or in any of the error modes that it supports.
-     *
-     * @param mode the mode for the authentication pane
-     */
-    private void do_show_authentication_pane(AuthenticationPane.Mode mode = AuthenticationPane.Mode.INTRO) {
-        debug("ACTION: installing authentication pane");
-
-        host.set_service_locked(false);
-        AuthenticationPane authentication_pane =
-            new AuthenticationPane(this, mode);
-        authentication_pane.login.connect(on_authentication_pane_login_clicked);
-        host.install_dialog_pane(authentication_pane, Spit.Publishing.PluginHost.ButtonMode.CLOSE);
-        host.set_dialog_default_widget(authentication_pane.get_default_widget());
-    }
-
-    /**
-     * Event triggered when the login button in the authentication panel is
-     * clicked.
-     *
-     * This event is triggered when the login button in the authentication
-     * panel is clicked. It then triggers a network login interaction.
-     *
-     * @param username the name of the Tumblr user as entered in the dialog
-     * @param password the password of the Tumblr as entered in the dialog
-     */
-    private void on_authentication_pane_login_clicked( string username, string password ) {
-        debug("EVENT: on_authentication_pane_login_clicked");
-        if (!running)
-            return;
-
-        do_network_login(username, password);
-    }
-
-    /**
-     * Action to perform a network login to a Tumblr blog.
-     *
-     * This action performs a network login a Tumblr blog specified the given user name and password as 
credentials.
-     *
-     * @param username the name of the Tumblr user used to login
-     * @param password the password of the Tumblr user used to login
-     */
-    private void do_network_login(string username, string password) {
-        debug("ACTION: logging in");
-        host.set_service_locked(true);
-        host.install_login_wait_pane();
-
-
-        AccessTokenFetchTransaction txn = new AccessTokenFetchTransaction(session,username,password);
-        txn.completed.connect(on_auth_request_txn_completed);
-        txn.network_error.connect(on_auth_request_txn_error);
-
-        try {
-            txn.execute();
-        } catch (Spit.Publishing.PublishingError err) {
-            host.post_error(err);
-        }
-    }
-
-
-    private void on_auth_request_txn_completed(Publishing.RESTSupport.Transaction txn) {
-        txn.completed.disconnect(on_auth_request_txn_completed);
-        txn.network_error.disconnect(on_auth_request_txn_error);
-
-        if (!is_running())
-            return;
-
-        debug("EVENT: OAuth authentication request transaction completed; response = '%s'",
-            txn.get_response());
-
-        do_parse_token_info_from_auth_request(txn.get_response());
-    }
-
-    private void on_auth_request_txn_error(Publishing.RESTSupport.Transaction txn,
-        Spit.Publishing.PublishingError err) {
-        txn.completed.disconnect(on_auth_request_txn_completed);
-        txn.network_error.disconnect(on_auth_request_txn_error);
-
+    private void on_session_authenticated() {
         if (!is_running())
             return;
 
-        debug("EVENT: OAuth authentication request transaction caused a network error");
-        host.post_error(err);
-    }
-
-
-    private void do_parse_token_info_from_auth_request(string response) {
-        debug("ACTION: parsing authorization request response '%s' into token and secret", response);
-
-        string? oauth_token = null;
-        string? oauth_token_secret = null;
+        debug("EVENT: a fully authenticated session has become available");
 
-        string[] key_value_pairs = response.split("&");
-        foreach (string pair in key_value_pairs) {
-            string[] split_pair = pair.split("=");
+        var params = this.authenticator.get_authentication_parameter();
+        Variant consumer_key = null;
+        Variant consumer_secret = null;
+        Variant auth_token = null;
+        Variant auth_token_secret = null;
 
-            if (split_pair.length != 2)
-                host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
-                    _("“%s” isn’t a valid response to an OAuth authentication request"), response));
+        params.lookup_extended("ConsumerKey", null, out consumer_key);
+        params.lookup_extended("ConsumerSecret", null, out consumer_secret);
+        session.set_api_credentials(consumer_key.get_string(), consumer_secret.get_string());
 
-            if (split_pair[0] == "oauth_token")
-                oauth_token = split_pair[1];
-            else if (split_pair[0] == "oauth_token_secret")
-                oauth_token_secret = split_pair[1];
-        }
+        params.lookup_extended("AuthToken", null, out auth_token);
+        params.lookup_extended("AuthTokenSecret", null, out auth_token_secret);
+        session.set_access_phase_credentials(auth_token.get_string(),
+                auth_token_secret.get_string());
 
-        if (oauth_token == null || oauth_token_secret == null)
-            host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE(
-                _("“%s” isn’t a valid response to an OAuth authentication request"), response));
 
-        session.set_access_phase_credentials(oauth_token, oauth_token_secret);
+        do_get_blogs();
     }
 
-
-
-    private void on_session_authenticated() {
-        if (!is_running())
-            return;
-
-        debug("EVENT: a fully authenticated session has become available");
-        set_persistent_access_phase_token(session.get_access_phase_token());
-        set_persistent_access_phase_token_secret(session.get_access_phase_token_secret());
-               do_get_blogs();
-
-}
-
     private void do_get_blogs() {
         debug("ACTION: obtain all blogs of the tumblr user");
         UserInfoFetchTransaction txn = new UserInfoFetchTransaction(session);
@@ -395,7 +252,7 @@ public class TumblrPublisher : Spit.Publishing.Publisher, GLib.Object {
             return;
 
         session.deauthenticate();
-        invalidate_persistent_session();
+        //invalidate_persistent_session();
         debug("EVENT: user info request transaction caused a network error");
         host.post_error(err);
     }
@@ -524,8 +381,9 @@ public class TumblrPublisher : Spit.Publishing.Publisher, GLib.Object {
     private void do_logout() {
         debug("ACTION: logging user out, deauthenticating session, and erasing stored credentials");
 
-        session.deauthenticate();
-        invalidate_persistent_session();
+        if (this.authenticator.can_logout()) {
+            this.authenticator.logout();
+        }
 
         running = false;
 
@@ -539,16 +397,7 @@ public class TumblrPublisher : Spit.Publishing.Publisher, GLib.Object {
         debug("TumblrPublisher: starting interaction.");
 
         running = true;
-        if (is_persistent_session_valid()) {
-            debug("attempt start: a persistent session is available; using it");
-
-            session.authenticate_from_persistent_credentials(get_persistent_access_phase_token(),
-                get_persistent_access_phase_token_secret());
-        } else {
-            debug("attempt start: no persistent session available; showing login welcome pane");
-
-            do_show_authentication_pane();
-        }
+        this.authenticator.authenticate();
     }
 
     public void start() {
@@ -575,109 +424,6 @@ public class TumblrPublisher : Spit.Publishing.Publisher, GLib.Object {
 
 // UI elements
 
-/**
- * The authentication pane used when asking service URL, user name and password
- * from the user.
- */
-internal class AuthenticationPane : Spit.Publishing.DialogPane, Object {
-    public enum Mode {
-        INTRO,
-        FAILED_RETRY_USER
-    }
-    private static string INTRO_MESSAGE = _("Enter the username and password associated with your Tumblr 
account.");
-    private static string FAILED_RETRY_USER_MESSAGE = _("Username and/or password invalid. Please try 
again");
-
-    private Gtk.Box pane_widget = null;
-    private Gtk.Builder builder;
-    private Gtk.Entry username_entry;
-    private Gtk.Entry password_entry;
-    private Gtk.Button login_button;
-
-    public signal void login(string user, string password);
-
-    public AuthenticationPane(TumblrPublisher publisher, Mode mode = Mode.INTRO) {
-        this.pane_widget = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
-
-        try {
-            builder = new Gtk.Builder();
-            builder.add_from_resource (Resources.RESOURCE_PATH + "/tumblr_authentication_pane.ui");
-            builder.connect_signals(null);
-            var content = builder.get_object ("content") as Gtk.Widget;
-
-            Gtk.Label message_label = builder.get_object("message_label") as Gtk.Label;
-            switch (mode) {
-                case Mode.INTRO:
-                    message_label.set_text(INTRO_MESSAGE);
-                    break;
-
-                case Mode.FAILED_RETRY_USER:
-                    message_label.set_markup("<b>%s</b>\n\n%s".printf(_(
-                        "Invalid User Name or Password"), FAILED_RETRY_USER_MESSAGE));
-                    break;
-            }
-
-            username_entry = builder.get_object ("username_entry") as Gtk.Entry;
-
-            password_entry = builder.get_object ("password_entry") as Gtk.Entry;
-
-
-
-            login_button = builder.get_object("login_button") as Gtk.Button;
-
-            username_entry.changed.connect(on_user_changed);
-            password_entry.changed.connect(on_password_changed);
-            login_button.clicked.connect(on_login_button_clicked);
-
-            content.parent.remove (content);
-            pane_widget.add (content);
-            publisher.get_host().set_dialog_default_widget(login_button);
-        } catch (Error e) {
-            warning(_("Could not load UI: %s"), e.message);
-        }
-    }
-
-    public Gtk.Widget get_default_widget() {
-        return login_button;
-    }
-
-    private void on_login_button_clicked() {
-        login(username_entry.get_text(),
-            password_entry.get_text());
-    }
-
-
-    private void on_user_changed() {
-        update_login_button_sensitivity();
-    }
-
-    private void on_password_changed() {
-        update_login_button_sensitivity();
-    }
-
-    private void update_login_button_sensitivity() {
-        login_button.set_sensitive(username_entry.text_length > 0 &&
-                                   password_entry.text_length > 0);
-    }
-
-    public Gtk.Widget get_widget() {
-        return pane_widget;
-    }
-
-    public Spit.Publishing.DialogPane.GeometryOptions get_preferred_geometry() {
-        return Spit.Publishing.DialogPane.GeometryOptions.NONE;
-    }
-
-    public void on_pane_installed() {
-        username_entry.grab_focus();
-        password_entry.set_activates_default(true);
-        login_button.can_default = true;
-        update_login_button_sensitivity();
-    }
-
-    public void on_pane_uninstalled() {
-    }
-}
-
 
 /**
  * The publishing options pane.
@@ -821,25 +567,23 @@ internal class PublishingOptionsPane : Spit.Publishing.DialogPane, GLib.Object {
     }
 }
 
-
-// REST support classes
 internal class Transaction : Publishing.RESTSupport.Transaction {
     public Transaction(Session session, Publishing.RESTSupport.HttpMethod method =
-        Publishing.RESTSupport.HttpMethod.POST) {
+            Publishing.RESTSupport.HttpMethod.POST) {
         base(session, method);
 
     }
 
     public Transaction.with_uri(Session session, string uri,
-        Publishing.RESTSupport.HttpMethod method = Publishing.RESTSupport.HttpMethod.POST) {
+            Publishing.RESTSupport.HttpMethod method = Publishing.RESTSupport.HttpMethod.POST) {
         base.with_endpoint_url(session, uri, method);
 
         add_argument("oauth_nonce", session.get_oauth_nonce());
         add_argument("oauth_signature_method", "HMAC-SHA1");
         add_argument("oauth_version", "1.0");
         add_argument("oauth_timestamp", session.get_oauth_timestamp());
-        add_argument("oauth_consumer_key", API_KEY);
-               if (session.get_access_phase_token() != null) {
+        add_argument("oauth_consumer_key", session.get_consumer_key());
+        if (session.get_access_phase_token() != null) {
             add_argument("oauth_token", session.get_access_phase_token());
         }
     }
@@ -852,17 +596,6 @@ internal class Transaction : Publishing.RESTSupport.Transaction {
 
 }
 
-
-internal class AccessTokenFetchTransaction : Transaction {
-    public AccessTokenFetchTransaction(Session session, string username, string password) {
-        base.with_uri(session, "https://www.tumblr.com/oauth/access_token";,
-            Publishing.RESTSupport.HttpMethod.POST);
-        add_argument("x_auth_username", Soup.URI.encode(username, ENCODE_RFC_3986_EXTRA));
-        add_argument("x_auth_password", password);
-        add_argument("x_auth_mode", "client_auth");
-    }
-}
-
 internal class UserInfoFetchTransaction : Transaction {
     public UserInfoFetchTransaction(Session session) {
         base.with_uri(session, "https://api.tumblr.com/v2/user/info";,
@@ -870,7 +603,6 @@ internal class UserInfoFetchTransaction : Transaction {
     }
 }
 
-
 internal class UploadTransaction : Publishing.RESTSupport.UploadTransaction {
     private Session session;
     private Publishing.RESTSupport.Argument[] auth_header_fields;
@@ -931,7 +663,7 @@ internal class UploadTransaction : Publishing.RESTSupport.UploadTransaction {
         add_authorization_header_field("oauth_signature_method", "HMAC-SHA1");
         add_authorization_header_field("oauth_version", "1.0");
         add_authorization_header_field("oauth_timestamp", session.get_oauth_timestamp());
-        add_authorization_header_field("oauth_consumer_key", API_KEY);
+        add_authorization_header_field("oauth_consumer_key", session.get_consumer_key());
         add_authorization_header_field("oauth_token", session.get_access_phase_token());
 
 
@@ -1022,7 +754,8 @@ internal class Uploader : Publishing.RESTSupport.BatchUploader {
 internal class Session : Publishing.RESTSupport.Session {
     private string? access_phase_token = null;
     private string? access_phase_token_secret = null;
-
+    private string? consumer_key = null;
+    private string? consumer_secret = null;
 
     public Session() {
         base(ENDPOINT_URL);
@@ -1032,17 +765,24 @@ internal class Session : Publishing.RESTSupport.Session {
         return (access_phase_token != null && access_phase_token_secret != null);
     }
 
-    public void authenticate_from_persistent_credentials(string token, string secret) {
-        this.access_phase_token = token;
-        this.access_phase_token_secret = secret;
+    public void deauthenticate() {
+        access_phase_token = null;
+        access_phase_token_secret = null;
+    }
 
+    public void set_api_credentials(string consumer_key, string consumer_secret) {
+        this.consumer_key = consumer_key;
+        this.consumer_secret = consumer_secret;
+    }
 
-        authenticated();
+    public string get_consumer_key() {
+        return this.consumer_key;
     }
 
-    public void deauthenticate() {
-        access_phase_token = null;
-        access_phase_token_secret = null;
+
+    public void set_access_phase_credentials(string token, string secret) {
+        this.access_phase_token = token;
+        this.access_phase_token_secret = secret;
     }
 
     public void sign_transaction(Publishing.RESTSupport.Transaction txn) {
@@ -1054,12 +794,12 @@ internal class Session : Publishing.RESTSupport.Session {
         if (access_phase_token_secret != null) {
             debug("access phase token secret available; using it as signing key");
 
-            signing_key = API_SECRET + "&" + this.get_access_phase_token_secret();
+            signing_key = this.consumer_secret + "&" + this.get_access_phase_token_secret();
         } else {
             debug("Access phase token secret not available; using API " +
                 "key as signing key");
 
-            signing_key = API_SECRET + "&";
+            signing_key = this.consumer_secret + "&";
         }
 
 
@@ -1110,14 +850,6 @@ internal class Session : Publishing.RESTSupport.Session {
 
     }
 
-    public void set_access_phase_credentials(string token, string secret) {
-        this.access_phase_token = token;
-        this.access_phase_token_secret = secret;
-
-
-        authenticated();
-    }
-
     public string get_access_phase_token() {
         return access_phase_token;
     }
diff --git a/plugins/shotwell-publishing/org.gnome.Shotwell.Publishing.gresource.xml 
b/plugins/shotwell-publishing/org.gnome.Shotwell.Publishing.gresource.xml
index d5a37fd..5e8ce1c 100644
--- a/plugins/shotwell-publishing/org.gnome.Shotwell.Publishing.gresource.xml
+++ b/plugins/shotwell-publishing/org.gnome.Shotwell.Publishing.gresource.xml
@@ -14,7 +14,6 @@
       <file>piwigo_publishing_options_pane.ui</file>
       <file>piwigo_ssl_failure_pane.ui</file>
       <file>youtube_publishing_options_pane.ui</file>
-      <file>tumblr_authentication_pane.ui</file>
       <file>tumblr_publishing_options_pane.ui</file>
   </gresource>
 </gresources>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 91290ad..955943a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -10,6 +10,7 @@ plugins/authenticator/shotwell/flickr_pin_entry_pane.ui
 plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala
 plugins/authenticator/shotwell/GoogleAuthenticator.vala
 plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala
+plugins/authenticator/shotwell/tumblr_authentication_pane.ui
 plugins/common/Resources.vala
 plugins/common/RESTSupport.vala
 plugins/shotwell-publishing-extras/gallery3_authentication_pane.ui
@@ -32,7 +33,6 @@ plugins/shotwell-publishing/piwigo_publishing_options_pane.ui
 plugins/shotwell-publishing/PiwigoPublishing.vala
 plugins/shotwell-publishing/piwigo_ssl_failure_pane.ui
 plugins/shotwell-publishing/shotwell-publishing.vala
-plugins/shotwell-publishing/tumblr_authentication_pane.ui
 plugins/shotwell-publishing/tumblr_publishing_options_pane.ui
 plugins/shotwell-publishing/TumblrPublishing.vala
 plugins/shotwell-publishing/youtube_publishing_options_pane.ui
diff --git a/publish.am b/publish.am
index bb0fcce..e98d533 100644
--- a/publish.am
+++ b/publish.am
@@ -13,7 +13,6 @@ dist_noinst_DATA += \
        plugins/shotwell-publishing/piwigo_authentication_pane.ui \
        plugins/shotwell-publishing/piwigo_publishing_options_pane.ui \
        plugins/shotwell-publishing/piwigo_ssl_failure_pane.ui \
-       plugins/shotwell-publishing/tumblr_authentication_pane.ui \
        plugins/shotwell-publishing/tumblr_publishing_options_pane.ui \
        plugins/shotwell-publishing/youtube_publishing_options_pane.ui \
        plugins/shotwell-publishing/org.gnome.Shotwell.Publishing.gresource.xml



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