diff options
Diffstat (limited to 'plugins/authenticator')
| -rw-r--r-- | plugins/authenticator/shotwell/TumblrAuthenticator.vala | 250 | 
1 files changed, 108 insertions, 142 deletions
| diff --git a/plugins/authenticator/shotwell/TumblrAuthenticator.vala b/plugins/authenticator/shotwell/TumblrAuthenticator.vala index e77814b..3ad6b9f 100644 --- a/plugins/authenticator/shotwell/TumblrAuthenticator.vala +++ b/plugins/authenticator/shotwell/TumblrAuthenticator.vala @@ -7,125 +7,35 @@   * (version 2.1 or later).  See the COPYING file in this distribution.   */ +using Shotwell.Plugins; +  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(); -        } +    internal const string SERVICE_WELCOME_MESSAGE = _("You are not currently logged into Tumblr.\n\nClick Log in to log into Tumblr in your Web browser. You will have to authorize Shotwell Connect to link to your Tumblr account."); -        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() { +    internal class AuthenticationRequestTransaction : Publishing.RESTSupport.OAuth1.Transaction { +        public AuthenticationRequestTransaction(Publishing.RESTSupport.OAuth1.Session session, string cookie) { +            base.with_uri(session, "https://www.tumblr.com/oauth/request_token", +                    Publishing.RESTSupport.HttpMethod.POST); +            add_argument("oauth_callback", "shotwell-oauth2://localhost?sw_auth_cookie=%s".printf(cookie));          }      }      internal class AccessTokenFetchTransaction : Publishing.RESTSupport.OAuth1.Transaction { -        public AccessTokenFetchTransaction(Publishing.RESTSupport.OAuth1.Session session, string username, string password) { +        public AccessTokenFetchTransaction(Publishing.RESTSupport.OAuth1.Session session, string user_verifier, string cookie) {              base.with_uri(session, "https://www.tumblr.com/oauth/access_token",                      Publishing.RESTSupport.HttpMethod.POST); -            add_argument("x_auth_username", username); -            add_argument("x_auth_password", password); -            add_argument("x_auth_mode", "client_auth"); +                    add_argument("oauth_verifier", user_verifier); +                    add_argument("oauth_token", session.get_request_phase_token()); +                    add_argument("oauth_callback", "shotwell-oauth2://localhost?sw_auth_cookie=%s".printf(cookie));          }      }      internal class Tumblr : Publishing.Authenticator.Shotwell.OAuth1.Authenticator { +        private string auth_cookie = Uuid.string_random(); +          public Tumblr(Spit.Publishing.PluginHost host) {              base("Tumblr", API_KEY, API_SECRET, host);          } @@ -167,79 +77,135 @@ namespace Publishing.Authenticator.Shotwell.Tumblr {           *           * @param mode the mode for the authentication pane           */ -        private void do_show_authentication_pane(AuthenticationPane.Mode mode = AuthenticationPane.Mode.INTRO) { +        private void do_show_authentication_pane() {              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()); +            host.install_welcome_pane("%s".printf(SERVICE_WELCOME_MESSAGE), on_welcome_pane_login_clicked);          } -        /** -         * 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"); +        private void on_welcome_pane_login_clicked() { +            debug("EVENT: user clicked 'Login' button in the welcome pane"); -            do_network_login.begin(username, password); +            do_run_authentication_request_transaction.begin();          } -        /** -         * 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 async void do_network_login(string username, string password) { -            debug("ACTION: logging in"); +        private async void do_run_authentication_request_transaction() { +            debug("ACTION: running authentication request transaction"); +              host.set_service_locked(true); -            host.install_login_wait_pane(); +            host.install_static_message_pane(_("Preparing for login…")); -            AccessTokenFetchTransaction txn = new AccessTokenFetchTransaction(session,username,password); +            AuthenticationRequestTransaction txn = new AuthenticationRequestTransaction(session, auth_cookie);              try {                  yield txn.execute_async();                  debug("EVENT: OAuth authentication request transaction completed; response = '%s'", -                txn.get_response()); - +                    txn.get_response());                  do_parse_token_info_from_auth_request(txn.get_response());              } catch (Error err) {                  debug("EVENT: OAuth authentication request transaction caused a network error");                  host.post_error(err); + +                this.authentication_failed();              }          }          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; + +            var data = Soup.Form.decode(response); +            data.lookup_extended("oauth_token", null, out oauth_token); +            data.lookup_extended("oauth_token_secret", null, out oauth_token_secret); + +            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)); + + +            on_authentication_token_available(oauth_token, oauth_token_secret); +        } + +        private void on_authentication_token_available(string token, string token_secret) { +            debug("EVENT: OAuth authentication token (%s) and token secret (%s) available", +                    token, token_secret); + +            session.set_request_phase_credentials(token, token_secret); + +            do_web_authentication.begin(token); +        } +        private class AuthCallback : Spit.Publishing.AuthenticatedCallback, Object { +            public signal void auth(GLib.HashTable<string, string> params); + +            public void authenticated(GLib.HashTable<string, string> params) { +                auth(params); +            } +        } + +        private async void do_web_authentication(string token) { +            var uri = "https://www.tumblr.com/oauth/authorize?oauth_token=%s&perms=write".printf(token); +            var pane = new Common.ExternalWebPane(uri); +            host.install_dialog_pane(pane); +            var auth_callback = new AuthCallback(); +            string? web_auth_code = null; +            auth_callback.auth.connect((prm) => { +                if ("oauth_verifier" in prm) { +                    web_auth_code = prm["oauth_verifier"]; +                } +                do_web_authentication.callback(); +            }); +            host.register_auth_callback(auth_cookie, auth_callback); +            yield; +            host.unregister_auth_callback(auth_cookie); +            yield do_verify_pin(web_auth_code); +        } + +        private async void do_verify_pin(string pin) { +            debug("ACTION: validating authorization PIN %s", pin); + +            host.set_service_locked(true); +            host.install_static_message_pane(_("Verifying authorization…")); + +            AccessTokenFetchTransaction txn = new AccessTokenFetchTransaction(session, pin, auth_cookie); + +            try { +                yield txn.execute_async(); +                debug("EVENT: fetching OAuth access token over the network succeeded"); + +                do_extract_access_phase_credentials_from_response(txn.get_response()); +            } catch (Error err) { +                debug("EVENT: fetching OAuth access token over the network caused an error."); + +                host.post_error(err); +                this.authentication_failed(); +            } +        } + +        private void do_extract_access_phase_credentials_from_response(string response) {              debug("ACTION: extracting access phase credentials from '%s'", response);              string? token = null;              string? token_secret = null; +            string? username = "unused";              var data = Soup.Form.decode(response);              data.lookup_extended("oauth_token", null, out token);              data.lookup_extended("oauth_token_secret", null, out token_secret); -            debug("access phase credentials: { token = '%s'; token_secret = '%s' }", -                    token, token_secret); +            debug("access phase credentials: { token = '%s'; token_secret = '%s'; username = '%s' }", +                    token, token_secret, username); -            if (token == null || token_secret == null) { -                host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE("Expected " + -                            "access phase credentials to contain token and token secret but at " + +            if (token == null || token_secret == null || username == null) { +                host.post_error(new Spit.Publishing.PublishingError.MALFORMED_RESPONSE("expected " + +                            "access phase credentials to contain token, token secret, and username but at " +                              "least one of these is absent"));                  this.authentication_failed();              } else { -                session.set_access_phase_credentials(token, token_secret, "unused"); +                session.set_access_phase_credentials(token, token_secret, username);              }          } +      }  } | 
