summaryrefslogtreecommitdiff
path: root/plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala')
-rw-r--r--plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala389
1 files changed, 48 insertions, 341 deletions
diff --git a/plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala b/plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala
index e389908..97629ed 100644
--- a/plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala
+++ b/plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala
@@ -4,318 +4,82 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
+using Shotwell.Plugins;
+
namespace Publishing.Authenticator.Shotwell.Flickr {
internal const string ENDPOINT_URL = "https://api.flickr.com/services/rest";
internal const string EXPIRED_SESSION_ERROR_CODE = "98";
- internal const string ENCODE_RFC_3986_EXTRA = "!*'();:@&=+$,/?%#[] \\";
-
- internal class Transaction : Publishing.RESTSupport.Transaction {
- public Transaction(Session session, Publishing.RESTSupport.HttpMethod method =
- Publishing.RESTSupport.HttpMethod.POST) {
- base(session, method);
- setup_arguments();
- }
-
- public Transaction.with_uri(Session session, string uri,
- Publishing.RESTSupport.HttpMethod method = Publishing.RESTSupport.HttpMethod.POST) {
- base.with_endpoint_url(session, uri, method);
- setup_arguments();
- }
-
- private void setup_arguments() {
- var session = (Session) get_parent_session();
-
- add_argument("oauth_nonce", session.get_oauth_nonce());
- add_argument("oauth_signature_method", "HMAC-SHA1");
- add_argument("oauth_version", "1.0");
- add_argument("oauth_callback", "oob");
- add_argument("oauth_timestamp", session.get_oauth_timestamp());
- add_argument("oauth_consumer_key", session.get_consumer_key());
- }
-
-
- public override void execute() throws Spit.Publishing.PublishingError {
- ((Session) get_parent_session()).sign_transaction(this);
-
- base.execute();
- }
- }
-
- internal class Session : Publishing.RESTSupport.Session {
- private string? request_phase_token = null;
- private string? request_phase_token_secret = null;
- private string? access_phase_token = null;
- private string? access_phase_token_secret = null;
- private string? username = null;
- private string? consumer_key = null;
- private string? consumer_secret = null;
-
- public Session() {
- base();
- }
-
- public override bool is_authenticated() {
- return (access_phase_token != null && access_phase_token_secret != null &&
- username != null);
- }
-
- public void authenticate_from_persistent_credentials(string token, string secret,
- string username) {
- this.access_phase_token = token;
- this.access_phase_token_secret = secret;
- this.username = username;
-
- this.authenticated();
- }
-
- public void deauthenticate() {
- access_phase_token = null;
- access_phase_token_secret = null;
- username = null;
- }
-
- public void set_api_credentials(string consumer_key, string consumer_secret) {
- this.consumer_key = consumer_key;
- this.consumer_secret = consumer_secret;
- }
-
- 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);
-
- 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? signing_key = null;
- if (access_phase_token_secret != null) {
- debug("access phase token secret available; using it as signing key");
-
- signing_key = consumer_secret + "&" + access_phase_token_secret;
- } else if (request_phase_token_secret != null) {
- debug("request phase token secret available; using it as signing key");
-
- signing_key = consumer_secret + "&" + request_phase_token_secret;
- } else {
- debug("neither access phase nor request phase token secrets available; using API " +
- "key as signing key");
-
- signing_key = consumer_secret + "&";
- }
-
- 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 = RESTSupport.hmac_sha1(signing_key, signature_base_string);
- signature = Soup.URI.encode(signature, ENCODE_RFC_3986_EXTRA);
-
- debug("signature = '%s'", signature);
-
- txn.add_argument("oauth_signature", signature);
- }
-
- public void set_request_phase_credentials(string token, string secret) {
- this.request_phase_token = token;
- this.request_phase_token_secret = secret;
- }
-
- public void set_access_phase_credentials(string token, string secret, string username) {
- this.access_phase_token = token;
- this.access_phase_token_secret = secret;
- this.username = username;
-
- authenticated();
- }
-
- 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);
- }
-
- public string get_consumer_key() {
- assert(consumer_key != null);
- return consumer_key;
- }
-
- public string get_request_phase_token() {
- assert(request_phase_token != null);
- return request_phase_token;
- }
-
- public string get_access_phase_token() {
- assert(access_phase_token != null);
- return access_phase_token;
- }
- public string get_access_phase_token_secret() {
- assert(access_phase_token_secret != null);
- return access_phase_token_secret;
- }
-
- public string get_username() {
- assert(is_authenticated());
- return username;
- }
- }
internal const string API_KEY = "60dd96d4a2ad04888b09c9e18d82c26f";
internal const string API_SECRET = "d0960565e03547c1";
internal const string SERVICE_WELCOME_MESSAGE =
_("You are not currently logged into Flickr.\n\nClick Log in to log into Flickr in your Web browser. You will have to authorize Shotwell Connect to link to your Flickr account.");
- internal class AuthenticationRequestTransaction : Transaction {
- public AuthenticationRequestTransaction(Session session) {
+ internal class AuthenticationRequestTransaction : Publishing.RESTSupport.OAuth1.Transaction {
+ public AuthenticationRequestTransaction(Publishing.RESTSupport.OAuth1.Session session) {
base.with_uri(session, "https://www.flickr.com/services/oauth/request_token",
Publishing.RESTSupport.HttpMethod.GET);
+ add_argument("oauth_callback", "shotwell-auth%3A%2F%2Flocal-callback");
}
}
- internal class AccessTokenFetchTransaction : Transaction {
- public AccessTokenFetchTransaction(Session session, string user_verifier) {
+ internal class AccessTokenFetchTransaction : Publishing.RESTSupport.OAuth1.Transaction {
+ public AccessTokenFetchTransaction(Publishing.RESTSupport.OAuth1.Session session, string user_verifier) {
base.with_uri(session, "https://www.flickr.com/services/oauth/access_token",
Publishing.RESTSupport.HttpMethod.GET);
add_argument("oauth_verifier", user_verifier);
add_argument("oauth_token", session.get_request_phase_token());
+ add_argument("oauth_callback", "shotwell-auth%3A%2F%2Flocal-callback");
}
}
- internal class PinEntryPane : Spit.Publishing.DialogPane, GLib.Object {
- private Gtk.Box pane_widget = null;
- private Gtk.Button continue_button = null;
- private Gtk.Entry pin_entry = null;
- private Gtk.Label pin_entry_caption = null;
- private Gtk.Label explanatory_text = null;
- private Gtk.Builder builder = null;
-
- public signal void proceed(PinEntryPane sender, string authorization_pin);
-
- public PinEntryPane(Gtk.Builder builder) {
- this.builder = builder;
- assert(builder != null);
- assert(builder.get_objects().length() > 0);
-
- explanatory_text = builder.get_object("explanatory_text") as Gtk.Label;
- pin_entry_caption = builder.get_object("pin_entry_caption") as Gtk.Label;
- pin_entry = builder.get_object("pin_entry") as Gtk.Entry;
- continue_button = builder.get_object("continue_button") as Gtk.Button;
-
- pane_widget = builder.get_object("pane_widget") as Gtk.Box;
+ internal class WebAuthenticationPane : Common.WebAuthenticationPane {
+ private string? auth_code = null;
+ private const string LOGIN_URI = "https://www.flickr.com/services/oauth/authorize?oauth_token=%s&perms=write";
- pane_widget.show_all();
+ public signal void authorized(string auth_code);
+ public signal void error();
- on_pin_entry_contents_changed();
+ public WebAuthenticationPane(string token) {
+ Object(login_uri : LOGIN_URI.printf(token));
}
- private void on_continue_clicked() {
- proceed(this, pin_entry.get_text());
- }
+ public override void constructed() {
+ base.constructed();
- private void on_pin_entry_contents_changed() {
- continue_button.set_sensitive(pin_entry.text_length > 0);
+ var ctx = WebKit.WebContext.get_default();
+ ctx.register_uri_scheme("shotwell-auth", this.on_shotwell_auth_request_cb);
}
- public Gtk.Widget get_widget() {
- return pane_widget;
- }
+ public override void on_page_load() {
+ var uri = new Soup.URI(get_view().get_uri());
+ if (uri.scheme == "shotwell-auth" && this.auth_code == null) {
+ this.error();
+ }
- public Spit.Publishing.DialogPane.GeometryOptions get_preferred_geometry() {
- return Spit.Publishing.DialogPane.GeometryOptions.NONE;
+ if (this.auth_code != null) {
+ this.authorized(this.auth_code);
+ }
}
- public void on_pane_installed() {
- continue_button.clicked.connect(on_continue_clicked);
- pin_entry.changed.connect(on_pin_entry_contents_changed);
- }
+ private void on_shotwell_auth_request_cb(WebKit.URISchemeRequest request) {
+ var uri = new Soup.URI(request.get_uri());
+ var form_data = Soup.Form.decode (uri.query);
+ this.auth_code = form_data.lookup("oauth_verifier");
- public void on_pane_uninstalled() {
- continue_button.clicked.disconnect(on_continue_clicked);
- pin_entry.changed.disconnect(on_pin_entry_contents_changed);
+ var response = "";
+ var mins = new MemoryInputStream.from_data(response.data, null);
+ request.finish(mins, -1, "text/plain");
}
}
-
- internal class Flickr : GLib.Object, Spit.Publishing.Authenticator {
- private GLib.HashTable<string, Variant> params;
- private Session session;
- private Spit.Publishing.PluginHost host;
-
+ internal class Flickr : Publishing.Authenticator.Shotwell.OAuth1.Authenticator {
public Flickr(Spit.Publishing.PluginHost host) {
- base();
-
- this.host = host;
- params = new GLib.HashTable<string, Variant>(str_hash, str_equal);
- params.insert("ConsumerKey", API_KEY);
- params.insert("ConsumerSecret", API_SECRET);
-
- session = new Session();
- session.set_api_credentials(API_KEY, API_SECRET);
- session.authenticated.connect(on_session_authenticated);
- }
-
- ~Flickr() {
- session.authenticated.disconnect(on_session_authenticated);
- }
-
- public void invalidate_persistent_session() {
- set_persistent_access_phase_token("");
- set_persistent_access_phase_token_secret("");
- set_persistent_access_phase_username("");
- }
-
- private bool is_persistent_session_valid() {
- return (get_persistent_access_phase_username() != null &&
- get_persistent_access_phase_token() != null &&
- get_persistent_access_phase_token_secret() != null);
- }
-
- private string? get_persistent_access_phase_username() {
- return host.get_config_string("access_phase_username", null);
- }
-
- private void set_persistent_access_phase_username(string username) {
- host.set_config_string("access_phase_username", username);
- }
-
- private string? get_persistent_access_phase_token() {
- return host.get_config_string("access_phase_token", null);
+ base(API_KEY, API_SECRET, host);
}
- private void set_persistent_access_phase_token(string token) {
- host.set_config_string("access_phase_token", token);
- }
-
- private string? get_persistent_access_phase_token_secret() {
- return host.get_config_string("access_phase_token_secret", null);
- }
-
- private void set_persistent_access_phase_token_secret(string secret) {
- host.set_config_string("access_phase_token_secret", secret);
- }
-
- public void authenticate() {
+ public override void authenticate() {
if (is_persistent_session_valid()) {
debug("attempt start: a persistent session is available; using it");
@@ -327,20 +91,16 @@ namespace Publishing.Authenticator.Shotwell.Flickr {
}
}
- public bool can_logout() {
+ public override bool can_logout() {
return true;
}
- public GLib.HashTable<string, Variant> get_authentication_parameter() {
- return this.params;
- }
-
- public void logout () {
+ public override void logout () {
session.deauthenticate();
invalidate_persistent_session();
}
- public void refresh() {
+ public override void refresh() {
// No-Op with flickr
}
@@ -419,59 +179,18 @@ namespace Publishing.Authenticator.Shotwell.Flickr {
session.set_request_phase_credentials(token, token_secret);
- do_launch_system_browser(token);
+ do_web_authentication(token);
}
- private void on_system_browser_launched() {
- debug("EVENT: system browser launched.");
-
- do_show_pin_entry_pane();
+ private void do_web_authentication(string token) {
+ var pane = new WebAuthenticationPane(token);
+ host.install_dialog_pane(pane);
+ pane.authorized.connect(this.do_verify_pin);
+ pane.error.connect(this.on_web_login_error);
}
- private void on_pin_entry_proceed(PinEntryPane sender, string pin) {
- sender.proceed.disconnect(on_pin_entry_proceed);
-
- debug("EVENT: user clicked 'Continue' in PIN entry pane.");
-
- do_verify_pin(pin);
- }
-
- private void do_launch_system_browser(string token) {
- string login_uri = "https://www.flickr.com/services/oauth/authorize?oauth_token=" + token +
- "&perms=write";
-
- debug("ACTION: launching system browser with uri = '%s'", login_uri);
-
- try {
- Process.spawn_command_line_async("xdg-open " + login_uri);
- } catch (SpawnError e) {
- host.post_error(new Spit.Publishing.PublishingError.LOCAL_FILE_ERROR(
- "couldn't launch system web browser to complete Flickr login"));
- return;
- }
-
- on_system_browser_launched();
- }
-
- private void do_show_pin_entry_pane() {
- debug("ACTION: showing PIN entry pane");
-
- Gtk.Builder builder = new Gtk.Builder();
-
- try {
- builder.add_from_resource (Resources.RESOURCE_PATH + "/" +
- "flickr_pin_entry_pane.ui");
- } catch (Error e) {
- warning("Could not parse UI file! Error: %s.", e.message);
- host.post_error(
- new Spit.Publishing.PublishingError.LOCAL_FILE_ERROR(
- _("A file required for publishing is unavailable. Publishing to Flickr can’t continue.")));
- return;
- }
-
- PinEntryPane pin_entry_pane = new PinEntryPane(builder);
- pin_entry_pane.proceed.connect(on_pin_entry_proceed);
- host.install_dialog_pane(pin_entry_pane);
+ private void on_web_login_error() {
+ host.post_error(new Spit.Publishing.PublishingError.PROTOCOL_ERROR(_("Flickr authorization failed")));
}
private void do_verify_pin(string pin) {
@@ -536,17 +255,5 @@ namespace Publishing.Authenticator.Shotwell.Flickr {
}
}
- 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());
- set_persistent_access_phase_username(session.get_username());
-
-
- this.authenticated();
- }
}
}