summaryrefslogtreecommitdiff
path: root/plugins/authenticator/shotwell/TumblrAuthenticator.vala
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/authenticator/shotwell/TumblrAuthenticator.vala')
-rw-r--r--plugins/authenticator/shotwell/TumblrAuthenticator.vala262
1 files changed, 262 insertions, 0 deletions
diff --git a/plugins/authenticator/shotwell/TumblrAuthenticator.vala b/plugins/authenticator/shotwell/TumblrAuthenticator.vala
new file mode 100644
index 0000000..35fdce9
--- /dev/null
+++ b/plugins/authenticator/shotwell/TumblrAuthenticator.vala
@@ -0,0 +1,262 @@
+/* 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() {
+ }
+ }
+
+ internal class AccessTokenFetchTransaction : Publishing.RESTSupport.OAuth1.Transaction {
+ public AccessTokenFetchTransaction(Publishing.RESTSupport.OAuth1.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 Tumblr : Publishing.Authenticator.Shotwell.OAuth1.Authenticator {
+ public Tumblr(Spit.Publishing.PluginHost host) {
+ base(API_KEY, API_SECRET, host);
+ }
+
+ public override 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 override bool can_logout() {
+ return true;
+ }
+
+ public override void logout() {
+ this.session.deauthenticate();
+ invalidate_persistent_session();
+ }
+
+ public override void refresh() {
+ // No-op with Tumblr
+ }
+
+ /**
+ * 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();
+
+ 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: extracting access phase credentials from '%s'", response);
+
+ string? token = null;
+ string? token_secret = null;
+
+ 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);
+
+ 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 " +
+ "least one of these is absent"));
+ this.authentication_failed();
+ } else {
+ session.set_access_phase_credentials(token, token_secret, "");
+ }
+ }
+ }
+}