From 5303f650e34763817d7eeb1d3ba774bdec3e1a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Thu, 22 Feb 2024 17:32:07 +0100 Subject: New upstream version 0.32.6 --- .../shotwell/FlickrPublishingAuthenticator.vala | 111 +++++------------- .../shotwell/GoogleAuthenticator.vala | 122 +++++-------------- plugins/authenticator/shotwell/meson.build | 2 +- plugins/common/WebAuthenticationPane.vala | 129 +++++---------------- plugins/meson.build | 2 +- plugins/shotwell-publishing/meson.build | 2 +- plugins/shotwell-transitions/meson.build | 2 +- 7 files changed, 96 insertions(+), 274 deletions(-) (limited to 'plugins') diff --git a/plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala b/plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala index 23de183..e381ae9 100644 --- a/plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala +++ b/plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala @@ -18,86 +18,26 @@ namespace Publishing.Authenticator.Shotwell.Flickr { internal const string SERVICE_DISCLAIMER = "This product uses the Flickr API but is not endorsed or certified by SmugMug, Inc."; internal class AuthenticationRequestTransaction : Publishing.RESTSupport.OAuth1.Transaction { - public AuthenticationRequestTransaction(Publishing.RESTSupport.OAuth1.Session session) { + public AuthenticationRequestTransaction(Publishing.RESTSupport.OAuth1.Session session, string cookie) { base.with_uri(session, "https://www.flickr.com/services/oauth/request_token", Publishing.RESTSupport.HttpMethod.GET); - add_argument("oauth_callback", "shotwell-auth://local-callback"); + 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 user_verifier) { + public AccessTokenFetchTransaction(Publishing.RESTSupport.OAuth1.Session session, string user_verifier, string cookie) { 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://local-callback"); - } - } - - 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"; - - public signal void authorized(string auth_code); - public signal void error(); - - public WebAuthenticationPane(string token) { - Object(login_uri : LOGIN_URI.printf(token)); - } - - public override void constructed() { - base.constructed(); - - var ctx = WebKit.WebContext.get_default(); - ctx.register_uri_scheme("shotwell-auth", this.on_shotwell_auth_request_cb); - - var mgr = ctx.get_security_manager(); - mgr.register_uri_scheme_as_secure("shotwell-auth"); - mgr.register_uri_scheme_as_cors_enabled("shotwell-auth"); - } - - public override void on_page_load() { - if (this.load_error != null) { - this.error(); - - return; - } - - try { - var uri = GLib.Uri.parse(get_view().get_uri(), GLib.UriFlags.NONE); - if (uri.get_scheme() == "shotwell-auth" && this.auth_code == null) { - var form_data = Soup.Form.decode (uri.get_query()); - this.auth_code = form_data.lookup("oauth_verifier"); - } - } catch (Error err) { - this.error(); - - return; - } - - if (this.auth_code != null) { - this.authorized(this.auth_code); - } - } - - private void on_shotwell_auth_request_cb(WebKit.URISchemeRequest request) { - try { - var uri = GLib.Uri.parse(request.get_uri(), GLib.UriFlags.NONE); - var form_data = Soup.Form.decode (uri.get_query()); - this.auth_code = form_data.lookup("oauth_verifier"); - } catch (Error err) { - debug ("Failed to parse URI %s: %s", request.get_uri(), err.message); - } - - var response = ""; - var mins = new MemoryInputStream.from_data(response.data); - request.finish(mins, -1, "text/plain"); + add_argument("oauth_callback", "shotwell-oauth2://localhost?sw_auth_cookie=%s".printf(cookie)); } } internal class Flickr : Publishing.Authenticator.Shotwell.OAuth1.Authenticator { - private WebAuthenticationPane pane; + private Common.ExternalWebPane pane; + private string auth_cookie = Uuid.string_random(); public Flickr(Spit.Publishing.PluginHost host) { base("Flickr", API_KEY, API_SECRET, host); @@ -147,7 +87,7 @@ namespace Publishing.Authenticator.Shotwell.Flickr { host.set_service_locked(true); host.install_static_message_pane(_("Preparing for login…")); - AuthenticationRequestTransaction txn = new AuthenticationRequestTransaction(session); + AuthenticationRequestTransaction txn = new AuthenticationRequestTransaction(session, auth_cookie); try { yield txn.execute_async(); debug("EVENT: OAuth authentication request transaction completed; response = '%s'", @@ -185,22 +125,33 @@ namespace Publishing.Authenticator.Shotwell.Flickr { session.set_request_phase_credentials(token, token_secret); - do_web_authentication(token); + do_web_authentication.begin(token); } - private void do_web_authentication(string token) { - pane = new WebAuthenticationPane(token); - host.install_dialog_pane(pane); - pane.authorized.connect((pin) => { this.do_verify_pin.begin(pin); }); - pane.error.connect(this.on_web_login_error); - } + private class AuthCallback : Spit.Publishing.AuthenticatedCallback, Object { + public signal void auth(GLib.HashTable params); - private void on_web_login_error() { - if (pane.load_error != null) { - host.post_error(pane.load_error); - return; + public void authenticated(GLib.HashTable params) { + auth(params); } - host.post_error(new Spit.Publishing.PublishingError.PROTOCOL_ERROR(_("Flickr authorization failed"))); + } + + private async void do_web_authentication(string token) { + var uri = "https://www.flickr.com/services/oauth/authorize?oauth_token=%s&perms=write".printf(token); + 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) { @@ -209,7 +160,7 @@ namespace Publishing.Authenticator.Shotwell.Flickr { host.set_service_locked(true); host.install_static_message_pane(_("Verifying authorization…")); - AccessTokenFetchTransaction txn = new AccessTokenFetchTransaction(session, pin); + AccessTokenFetchTransaction txn = new AccessTokenFetchTransaction(session, pin, auth_cookie); try { yield txn.execute_async(); diff --git a/plugins/authenticator/shotwell/GoogleAuthenticator.vala b/plugins/authenticator/shotwell/GoogleAuthenticator.vala index 9fc5b27..5a0d934 100644 --- a/plugins/authenticator/shotwell/GoogleAuthenticator.vala +++ b/plugins/authenticator/shotwell/GoogleAuthenticator.vala @@ -5,73 +5,11 @@ namespace Publishing.Authenticator.Shotwell.Google { private const string OAUTH_CLIENT_ID = "534227538559-hvj2e8bj0vfv2f49r7gvjoq6jibfav67.apps.googleusercontent.com"; private const string REVERSE_CLIENT_ID = "com.googleusercontent.apps.534227538559-hvj2e8bj0vfv2f49r7gvjoq6jibfav67"; private const string OAUTH_CLIENT_SECRET = "pwpzZ7W1TCcD5uIfYCu8sM7x"; - private const string OAUTH_CALLBACK_URI = REVERSE_CLIENT_ID + ":/auth-callback"; + private const string OAUTH_CALLBACK_URI = REVERSE_CLIENT_ID + ":/localhost"; private const string SCHEMA_KEY_PROFILE_ID = "shotwell-profile-id"; private const string SCHEMA_KEY_ACCOUNTNAME = "accountname"; - private class WebAuthenticationPane : Common.WebAuthenticationPane { - public static bool cache_dirty = false; - private string? auth_code = null; - - public signal void error(); - - public override void constructed() { - base.constructed(); - - var ctx = WebKit.WebContext.get_default(); - ctx.register_uri_scheme(REVERSE_CLIENT_ID, this.on_shotwell_auth_request_cb); - } - - public override void on_page_load() { - if (this.load_error != null) { - this.error (); - - return; - } - - try { - var uri = GLib.Uri.parse(get_view().get_uri(), UriFlags.NONE); - if (uri.get_scheme() == REVERSE_CLIENT_ID && this.auth_code == null) { - var form_data = Soup.Form.decode (uri.get_query()); - this.auth_code = form_data.lookup("code"); - } - } catch (Error err) { - debug ("Failed to parse auth code from URI %s: %s", get_view().get_uri(), - err.message); - } - - if (this.auth_code != null) { - this.authorized(this.auth_code); - } - } - - private void on_shotwell_auth_request_cb(WebKit.URISchemeRequest request) { - try { - var uri = GLib.Uri.parse(request.get_uri(), GLib.UriFlags.NONE); - debug("URI: %s", request.get_uri()); - var form_data = Soup.Form.decode (uri.get_query()); - this.auth_code = form_data.lookup("code"); - } catch (Error err) { - debug("Failed to parse request URI: %s", err.message); - } - - var response = ""; - var mins = new MemoryInputStream.from_data(response.data); - request.finish(mins, -1, "text/plain"); - } - - public signal void authorized(string auth_code); - - public WebAuthenticationPane(string auth_sequence_start_url) { - Object (login_uri : auth_sequence_start_url); - } - - public static bool is_cache_dirty() { - return cache_dirty; - } - } - private class Session : Publishing.RESTSupport.Session { public string access_token = null; public string refresh_token = null; @@ -132,7 +70,6 @@ namespace Publishing.Authenticator.Shotwell.Google { private string accountname = "default"; private Spit.Publishing.PluginHost host = null; private GLib.HashTable params = null; - private WebAuthenticationPane web_auth_pane = null; private Session session = null; private string welcome_message = null; private Secret.Schema? schema = null; @@ -166,14 +103,7 @@ namespace Publishing.Authenticator.Shotwell.Google { return; } - // FIXME: Find a way for a proper logout - if (WebAuthenticationPane.is_cache_dirty()) { - host.set_service_locked(false); - - host.install_static_message_pane(_("You have already logged in and out of a Google service during this Shotwell session.\n\nTo continue publishing to Google services, quit and restart Shotwell, then try publishing again.")); - } else { - this.do_show_service_welcome_pane(); - } + this.do_show_service_welcome_pane(); } public bool can_logout() { @@ -202,8 +132,15 @@ namespace Publishing.Authenticator.Shotwell.Google { public void set_accountname(string accountname) { this.accountname = accountname; } + private class AuthCallback : Spit.Publishing.AuthenticatedCallback, Object { + public signal void auth(GLib.HashTable params); + + public void authenticated(GLib.HashTable params) { + auth(params); + } + } - private void do_hosted_web_authentication() { + private async void do_hosted_web_authentication() { debug("ACTION: running OAuth authentication flow in hosted web pane."); string user_authorization_url = "https://accounts.google.com/o/oauth2/auth?" + @@ -216,23 +153,26 @@ namespace Publishing.Authenticator.Shotwell.Google { "access_type=offline&" + "approval_prompt=force"; - web_auth_pane = new WebAuthenticationPane(user_authorization_url); - web_auth_pane.authorized.connect(on_web_auth_pane_authorized); - web_auth_pane.error.connect(on_web_auth_pane_error); - - host.install_dialog_pane(web_auth_pane); - } - - private void on_web_auth_pane_authorized(string auth_code) { - web_auth_pane.authorized.disconnect(on_web_auth_pane_authorized); - - debug("EVENT: user authorized scope %s with auth_code %s", scope, auth_code); - - do_get_access_tokens.begin(auth_code); - } + var auth_callback = new AuthCallback(); + string? web_auth_code = null; + auth_callback.auth.connect((prm) => { + if ("code" in prm) { + web_auth_code = prm["code"]; + } + do_hosted_web_authentication.callback(); + }); + host.register_auth_callback(REVERSE_CLIENT_ID, auth_callback); + try { + AppInfo.launch_default_for_uri(user_authorization_url, null); + host.install_login_wait_pane(); + yield; - private void on_web_auth_pane_error() { - host.post_error(web_auth_pane.load_error); + yield do_get_access_tokens(web_auth_code); + } catch (Error err) { + host.post_error(err); + } finally { + host.unregister_auth_callback(REVERSE_CLIENT_ID); + } } private async void do_get_access_tokens(string auth_code) { @@ -384,7 +324,6 @@ namespace Publishing.Authenticator.Shotwell.Google { } this.authenticated(); - web_auth_pane.clear(); } private async void do_exchange_refresh_token_for_access_token() { @@ -421,7 +360,6 @@ namespace Publishing.Authenticator.Shotwell.Google { Idle.add (() => { this.authenticate(); return false; }); } - web_auth_pane.clear(); host.post_error(err); } } @@ -435,7 +373,7 @@ namespace Publishing.Authenticator.Shotwell.Google { private void on_service_welcome_login() { debug("EVENT: user clicked 'Login' in welcome pane."); - this.do_hosted_web_authentication(); + this.do_hosted_web_authentication.begin(); } } } diff --git a/plugins/authenticator/shotwell/meson.build b/plugins/authenticator/shotwell/meson.build index 037ec3b..a6475e0 100644 --- a/plugins/authenticator/shotwell/meson.build +++ b/plugins/authenticator/shotwell/meson.build @@ -11,7 +11,7 @@ authenticator_shotwell_resources = gnome.compile_resources('authenticator-resour source_dir : meson.project_source_root()) authenticator_shotwell_deps = [gee, gtk, gio, soup, json_glib, sw_plugin, - sw_plugin_common_dep, json_glib, xml, webkit, secret] + sw_plugin_common_dep, json_glib, xml, secret] authenticator = library('shotwell-authenticator', authenticator_shotwell_sources + authenticator_shotwell_resources, diff --git a/plugins/common/WebAuthenticationPane.vala b/plugins/common/WebAuthenticationPane.vala index b9f7280..f745252 100644 --- a/plugins/common/WebAuthenticationPane.vala +++ b/plugins/common/WebAuthenticationPane.vala @@ -6,109 +6,44 @@ using Spit.Publishing; namespace Shotwell.Plugins.Common { - public abstract class WebAuthenticationPane : Spit.Publishing.DialogPane, Object { + public class ExternalWebPane : Spit.Publishing.DialogPane, Object { public DialogPane.GeometryOptions preferred_geometry { get; construct; default = DialogPane.GeometryOptions.COLOSSAL_SIZE; } - public string login_uri { owned get; construct; } - public Error load_error { get; private set; default = null; } - - private WebKit.WebView webview; - private Gtk.Widget widget; - private Gtk.Entry entry; + public Gtk.Widget widget; - public void clear() { - debug("Clearing the data of WebKit..."); - this.webview.get_website_data_manager().clear.begin(WebKit.WebsiteDataTypes.ALL, (GLib.TimeSpan)0); + public ExternalWebPane(string uri) { + Object(login_uri: uri); } + public signal void browser_toggled(); + public override void constructed () { base.constructed (); - var ctx = WebKit.WebContext.get_default(); - if (!ctx.get_sandbox_enabled()) { - ctx.set_sandbox_enabled(true); - } - - var box = new Gtk.Box(Gtk.Orientation.VERTICAL, 4); - this.widget = box; - this.entry = new Gtk.Entry(); - this.entry.editable = false; - this.entry.get_style_context().add_class("flat"); - this.entry.get_style_context().add_class("read-only"); - box.pack_start (entry, false, false, 6); - - this.webview = new WebKit.WebView (); - - this.webview.load_changed.connect (this.on_page_load_changed); - this.webview.load_failed.connect (this.on_page_load_failed); - this.webview.context_menu.connect ( () => { return false; }); - this.webview.decide_policy.connect (this.on_decide_policy); - this.webview.bind_property("uri", this.entry, "text", GLib.BindingFlags.DEFAULT); - box.pack_end (this.webview); - } - - private bool on_decide_policy(WebKit.PolicyDecision decision, WebKit.PolicyDecisionType type) { - switch (type) { - case WebKit.PolicyDecisionType.NEW_WINDOW_ACTION: { - var navigation = (WebKit.NavigationPolicyDecision) decision; - var action = navigation.get_navigation_action(); - var uri = action.get_request().uri; - decision.ignore(); - AppInfo.launch_default_for_uri_async.begin(uri, null); - return true; - } - default: - break; - } - - return false; - } - - public abstract void on_page_load (); - - protected void set_cursor (Gdk.CursorType type) { - var window = webview.get_window (); - if (window == null) - return; - - var display = window.get_display (); - if (display == null) - return; - - var cursor = new Gdk.Cursor.for_display (display, type); - window.set_cursor (cursor); - } - - private bool on_page_load_failed (WebKit.LoadEvent load_event, string uri, Error error) { - // OAuth call-back scheme. Produces a load error because it is not HTTP(S) - // Do not set the load_error, but continue the error handling - if (uri.has_prefix ("shotwell-auth://")) - return false; - - critical ("Failed to load uri %s: %s", uri, error.message); - this.load_error = error; - - return false; - } - - private void on_page_load_changed (WebKit.LoadEvent load_event) { - switch (load_event) { - case WebKit.LoadEvent.STARTED: - case WebKit.LoadEvent.REDIRECTED: - this.set_cursor (Gdk.CursorType.WATCH); - break; - case WebKit.LoadEvent.FINISHED: - this.set_cursor (Gdk.CursorType.LEFT_PTR); - this.on_page_load (); - break; - default: - break; - } - } - - public WebKit.WebView get_view () { - return this.webview; + var box = new Gtk.Box(Gtk.Orientation.VERTICAL, 18); + box.set_halign(Gtk.Align.CENTER); + box.hexpand = true; + box.set_valign(Gtk.Align.CENTER); + box.vexpand = true; + var image = new Gtk.Image.from_icon_name ("web-browser-symbolic", Gtk.IconSize.DIALOG); + image.get_style_context().add_class("dim-label"); + image.set_pixel_size(128); + box.add(image); + + var label = new Gtk.Label(_("Sign in with your browser to setup an account")); + label.get_style_context().add_class("heading"); + box.add(label); + var button = new Gtk.Button.with_label (_("Continue")); + button.set_halign(Gtk.Align.CENTER); + button.get_style_context().add_class ("suggested-action"); + button.clicked.connect(() => { + AppInfo.launch_default_for_uri_async.begin(login_uri, null); + browser_toggled(); + }); + box.pack_end(button); + + widget = box; } public DialogPane.GeometryOptions get_preferred_geometry() { @@ -120,11 +55,9 @@ namespace Shotwell.Plugins.Common { } public void on_pane_installed () { - this.get_view ().load_uri (this.login_uri); } public void on_pane_uninstalled() { - this.clear(); - } - } + } + } } diff --git a/plugins/meson.build b/plugins/meson.build index e9c0e49..bd4ac9e 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -7,7 +7,7 @@ sw_plugin_common = library('shotwell-plugin-common', 'common/BuilderPane.vala', 'common/OAuth1Support.vala'], version: meson.project_version(), - dependencies : [gtk, gee, webkit, soup, xml, sw_plugin], + dependencies : [gtk, gee, soup, xml, sw_plugin], vala_header : 'shotwell-plugin-common.h', vala_vapi : 'shotwell-plugin-common.vapi', include_directories : config_incdir, diff --git a/plugins/shotwell-publishing/meson.build b/plugins/shotwell-publishing/meson.build index a93726b..962195c 100644 --- a/plugins/shotwell-publishing/meson.build +++ b/plugins/shotwell-publishing/meson.build @@ -18,7 +18,7 @@ shotwell_publishing_resources = gnome.compile_resources('publishing-resource', shared_module('shotwell-publishing', shotwell_publishing_sources + shotwell_publishing_resources, dependencies : [gtk, soup, gexiv2, gee, sw_plugin, json_glib, - webkit, sw_plugin_common_dep, xml, gcr, + sw_plugin_common_dep, xml, gcr, gcr_ui, authenticator_dep, secret], c_args : ['-DPLUGIN_RESOURCE_PATH="/org/gnome/Shotwell/Publishing"', '-DGCR_API_SUBJECT_TO_CHANGE'], diff --git a/plugins/shotwell-transitions/meson.build b/plugins/shotwell-transitions/meson.build index 8134292..f809ade 100644 --- a/plugins/shotwell-transitions/meson.build +++ b/plugins/shotwell-transitions/meson.build @@ -20,7 +20,7 @@ libm = cc.find_library('m', required : false) shared_module('shotwell-transitions', shotwell_transitions_sources + shotwell_transitions_resources, dependencies : [gio, gdk_pixbuf, cairo, gtk, gdk, xml, sw_plugin, - sw_plugin_common_dep, gee, soup, webkit, libm], + sw_plugin_common_dep, gee, soup, libm], vala_args : [ '--gresources', 'org.gnome.Shotwell.Transitions.gresource.xml', ], -- cgit v1.2.3