summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/authenticator/shotwell/TumblrAuthenticator.vala250
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);
}
}
+
}
}