diff options
Diffstat (limited to 'plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala')
-rw-r--r-- | plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala | 389 |
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(); - } } } |