summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff.email>2025-06-09 10:50:03 +0200
committerJörg Frings-Fürst <debian@jff.email>2025-06-09 10:50:03 +0200
commit62ae476eab4e600d6b7d662735910db0db2c4aa1 (patch)
treecb3f8e53587ee51cd0201765e6140dcc423ba4b0 /plugins
parente10377c3781fe84f10b3758b35bf403f91e6603a (diff)
parent361eb97e74a85fd3cbbb67a7a17281c49e2585f4 (diff)
Merge branch 'feature/upstream' into develop
Diffstat (limited to 'plugins')
-rw-r--r--plugins/authenticator/shotwell/GoogleAuthenticator.vala35
-rw-r--r--plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala4
-rw-r--r--plugins/shotwell-publishing/PhotosPublisher.vala22
-rw-r--r--plugins/shotwell-publishing/YouTubePublishing.vala3
4 files changed, 44 insertions, 20 deletions
diff --git a/plugins/authenticator/shotwell/GoogleAuthenticator.vala b/plugins/authenticator/shotwell/GoogleAuthenticator.vala
index 5a0d934..1fe2448 100644
--- a/plugins/authenticator/shotwell/GoogleAuthenticator.vala
+++ b/plugins/authenticator/shotwell/GoogleAuthenticator.vala
@@ -64,7 +64,7 @@ namespace Publishing.Authenticator.Shotwell.Google {
internal class Google : Spit.Publishing.Authenticator, Object {
private const string PASSWORD_SCHEME = "org.gnome.Shotwell.Google";
- private string scope = null;
+ private string[] scopes = null;
// Prepare for multiple user accounts
private string accountname = "default";
@@ -74,12 +74,12 @@ namespace Publishing.Authenticator.Shotwell.Google {
private string welcome_message = null;
private Secret.Schema? schema = null;
- public Google(string scope,
+ public Google(string[] scopes,
string welcome_message,
Spit.Publishing.PluginHost host) {
this.host = host;
this.params = new GLib.HashTable<string, Variant>(str_hash, str_equal);
- this.scope = scope;
+ this.scopes = scopes;
this.session = new Session();
this.welcome_message = welcome_message;
this.schema = new Secret.Schema(PASSWORD_SCHEME, Secret.SchemaFlags.NONE,
@@ -93,7 +93,7 @@ namespace Publishing.Authenticator.Shotwell.Google {
try {
refresh_token = Secret.password_lookup_sync(this.schema, null,
SCHEMA_KEY_PROFILE_ID, host.get_current_profile_id(),
- SCHEMA_KEY_ACCOUNTNAME, this.accountname, "scope", this.scope);
+ SCHEMA_KEY_ACCOUNTNAME, this.accountname, "scope", get_scopes());
} catch (Error err) {
critical("Failed to lookup refresh_token from password store: %s", err.message);
}
@@ -106,6 +106,10 @@ namespace Publishing.Authenticator.Shotwell.Google {
this.do_show_service_welcome_pane();
}
+ public string get_scopes(string separator=",") {
+ return string.joinv(separator, this.scopes);
+ }
+
public bool can_logout() {
return true;
}
@@ -119,9 +123,9 @@ namespace Publishing.Authenticator.Shotwell.Google {
try {
Secret.password_clear_sync(this.schema, null,
SCHEMA_KEY_PROFILE_ID, host.get_current_profile_id(),
- SCHEMA_KEY_ACCOUNTNAME, this.accountname, "scope", this.scope);
+ SCHEMA_KEY_ACCOUNTNAME, this.accountname, "scope", get_scopes());
} catch (Error err) {
- critical("Failed to remove password for scope %s: %s", this.scope, err.message);
+ critical("Failed to remove password for scope %s: %s", get_scopes(), err.message);
}
}
@@ -147,7 +151,7 @@ namespace Publishing.Authenticator.Shotwell.Google {
"response_type=code&" +
"client_id=" + OAUTH_CLIENT_ID + "&" +
"redirect_uri=" + GLib.Uri.escape_string(OAUTH_CALLBACK_URI, null) + "&" +
- "scope=" + GLib.Uri.escape_string(this.scope, null) + "+" +
+ "scope=" + GLib.Uri.escape_string(get_scopes(" "), null) + "+" +
GLib.Uri.escape_string("https://www.googleapis.com/auth/userinfo.profile", null) + "&" +
"state=connect&" +
"access_type=offline&" +
@@ -155,18 +159,25 @@ namespace Publishing.Authenticator.Shotwell.Google {
var auth_callback = new AuthCallback();
string? web_auth_code = null;
+
auth_callback.auth.connect((prm) => {
if ("code" in prm) {
web_auth_code = prm["code"];
}
+ if ("scope" in prm) {
+ debug("Effective scopes as returned from login: %s", prm["scope"]);
+ }
do_hosted_web_authentication.callback();
});
host.register_auth_callback(REVERSE_CLIENT_ID, auth_callback);
try {
+ debug("Launching external authentication on URI %s", user_authorization_url);
AppInfo.launch_default_for_uri(user_authorization_url, null);
host.install_login_wait_pane();
yield;
+ // FIXME throw error missing scopes
+
yield do_get_access_tokens(web_auth_code);
} catch (Error err) {
host.post_error(err);
@@ -315,12 +326,12 @@ namespace Publishing.Authenticator.Shotwell.Google {
assert(session.is_authenticated());
try {
Secret.password_store_sync(this.schema, Secret.COLLECTION_DEFAULT,
- "Shotwell publishing (Google account scope %s@%s)".printf(this.accountname, this.scope),
+ "Shotwell publishing (Google account scope %s@%s)".printf(this.accountname, get_scopes()),
session.refresh_token, null,
SCHEMA_KEY_PROFILE_ID, host.get_current_profile_id(),
- SCHEMA_KEY_ACCOUNTNAME, this.accountname, "scope", this.scope);
+ SCHEMA_KEY_ACCOUNTNAME, this.accountname, "scope", get_scopes());
} catch (Error err) {
- critical("Failed to look up password for scope %s: %s", this.scope, err.message);
+ critical("Failed to look up password for scope %s: %s", get_scopes(), err.message);
}
this.authenticated();
@@ -352,9 +363,9 @@ namespace Publishing.Authenticator.Shotwell.Google {
try {
Secret.password_clear_sync(this.schema, null,
SCHEMA_KEY_PROFILE_ID, host.get_current_profile_id(),
- SCHEMA_KEY_ACCOUNTNAME, this.accountname, "scope", this.scope);
+ SCHEMA_KEY_ACCOUNTNAME, this.accountname, "scope", get_scopes());
} catch (Error err) {
- critical("Failed to remove password for accountname@scope %s@%s: %s", this.accountname, this.scope, err.message);
+ critical("Failed to remove password for accountname@scope %s@%s: %s", this.accountname, get_scopes(), err.message);
}
Idle.add (() => { this.authenticate(); return false; });
diff --git a/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala b/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala
index 01fa3c3..c289006 100644
--- a/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala
+++ b/plugins/authenticator/shotwell/ShotwellAuthenticatorFactory.vala
@@ -27,11 +27,11 @@ namespace Publishing.Authenticator {
case "flickr":
return new Shotwell.Flickr.Flickr(host);
case "youtube":
- return new Shotwell.Google.Google("https://www.googleapis.com/auth/youtube", _("You are not currently logged into YouTube.\n\nYou must have already signed up for a Google account and set it up for use with YouTube to continue. You can set up most accounts by using your browser to log into the YouTube site at least once.\n\nShotwell uses the YouTube API services <a href=\"https://developers.google.com/youtube\">https://developers.google.com/youtube</a> for accessing your YouTube channel and upload the videos. By using Shotwell to access YouTube, you agree to be bound to the YouTube Terms of Service as available at <a href=\"https://www.youtube.com/t/terms\">https://www.youtube.com/t/terms</a>\n\nShotwell's privacy policy regarding the use of data related to your Google account in general and YouTube in particular can be found in our <a href=\"help:shotwell/privacy-policy\">online services privacy policy</a>\n\nFor Google's own privacy policy, please refer to <a href=\"https://policies.google.com/privacy\">https://policies.google.com/privacy</a>"), host);
+ return new Shotwell.Google.Google({"https://www.googleapis.com/auth/youtube"}, _("You are not currently logged into YouTube.\n\nYou must have already signed up for a Google account and set it up for use with YouTube to continue. You can set up most accounts by using your browser to log into the YouTube site at least once.\n\nShotwell uses the YouTube API services <a href=\"https://developers.google.com/youtube\">https://developers.google.com/youtube</a> for accessing your YouTube channel and upload the videos. By using Shotwell to access YouTube, you agree to be bound to the YouTube Terms of Service as available at <a href=\"https://www.youtube.com/t/terms\">https://www.youtube.com/t/terms</a>\n\nShotwell's privacy policy regarding the use of data related to your Google account in general and YouTube in particular can be found in our <a href=\"help:shotwell/privacy-policy\">online services privacy policy</a>\n\nFor Google's own privacy policy, please refer to <a href=\"https://policies.google.com/privacy\">https://policies.google.com/privacy</a>"), host);
case "tumblr":
return new Shotwell.Tumblr.Tumblr(host);
case "google-photos":
- return new Shotwell.Google.Google("https://www.googleapis.com/auth/photoslibrary", _("You are not currently logged into Google Photos.\n\nYou must have already signed up for a Google account and set it up for use with Google Photos. Shotwell uses the Google Photos API services <a href=\"https://developers.google.com/photos/\">https://developers.google.com/photos/</a> for all interaction with your Google Photos data. You will have to grant access Shotwell to your Google Photos library.\n\nShotwell's privacy policy regarding the use of data related to your Google account in general and Google Photos in particular can be found in our <a href=\"help:shotwell/privacy-policy\">online services privacy policy</a>. For Google's own privacy policy, please refer to <a href=\"https://policies.google.com/privacy\">https://policies.google.com/privacy</a>"), host);
+ return new Shotwell.Google.Google({"https://www.googleapis.com/auth/photoslibrary.appendonly", "https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata"}, _("You are not currently logged into Google Photos.\n\nYou must have already signed up for a Google account and set it up for use with Google Photos. Shotwell uses the Google Photos API services <a href=\"https://developers.google.com/photos/\">https://developers.google.com/photos/</a> for all interaction with your Google Photos data. You will have to grant access Shotwell to your Google Photos library.\n\nShotwell's privacy policy regarding the use of data related to your Google account in general and Google Photos in particular can be found in our <a href=\"help:shotwell/privacy-policy\">online services privacy policy</a>. For Google's own privacy policy, please refer to <a href=\"https://policies.google.com/privacy\">https://policies.google.com/privacy</a>"), host);
default:
return null;
}
diff --git a/plugins/shotwell-publishing/PhotosPublisher.vala b/plugins/shotwell-publishing/PhotosPublisher.vala
index b592317..67c3ecb 100644
--- a/plugins/shotwell-publishing/PhotosPublisher.vala
+++ b/plugins/shotwell-publishing/PhotosPublisher.vala
@@ -111,6 +111,7 @@ internal class PublishingParameters {
}
private class MediaCreationTransaction : Publishing.RESTSupport.GooglePublisher.AuthenticatedTransaction {
+ // SCOPE: photoslibrary.appendonly
private const string ENDPOINT_URL = "https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate";
private string[] upload_tokens;
private string[] titles;
@@ -154,6 +155,7 @@ private class MediaCreationTransaction : Publishing.RESTSupport.GooglePublisher.
}
private class AlbumCreationTransaction : Publishing.RESTSupport.GooglePublisher.AuthenticatedTransaction {
+ // SCOPE: photoslibrary.appendonly
private const string ENDPOINT_URL = "https://photoslibrary.googleapis.com/v1/albums";
private string title;
@@ -179,6 +181,7 @@ private class AlbumCreationTransaction : Publishing.RESTSupport.GooglePublisher.
}
private class AlbumDirectoryTransaction : Publishing.RESTSupport.GooglePublisher.AuthenticatedTransaction {
+ // SCOPE: photoslibrary.readonly.appcreateddata
private const string ENDPOINT_URL = "https://photoslibrary.googleapis.com/v1/albums";
public AlbumDirectoryTransaction(Publishing.RESTSupport.GoogleSession session, string? token) {
@@ -244,8 +247,15 @@ public class Publisher : Publishing.RESTSupport.GooglePublisher {
if (!is_running())
return;
+
var json = Json.from_string (txn.get_response());
var object = json.get_object ();
+ // Work-around for Google sometimes sending an empty JSON object '{}' instead of
+ // not setting the nextPageToken on the previous page
+ if (object.get_size() == 0) {
+ break;
+ }
+
if (!object.has_member ("albums")) {
throw new Spit.Publishing.PublishingError.MALFORMED_RESPONSE("Album fetch did not contain expected data");
}
@@ -279,7 +289,10 @@ public class Publisher : Publishing.RESTSupport.GooglePublisher {
debug("EVENT: fetching album information failed; response = '%s'.",
txn.get_response());
- if (txn.get_status_code() == 403 || txn.get_status_code() == 404) {
+ if (txn.get_status_code() == 403) {
+ debug("Lacking permission to download album list, showing publishing options anyway");
+ show_publishing_options_pane();
+ } else if (txn.get_status_code() == 404) {
do_logout();
} else {
// If we get any other kind of error, we can't recover, so just post it to the user
@@ -345,10 +358,13 @@ public class Publisher : Publishing.RESTSupport.GooglePublisher {
yield do_upload();
} catch (Error err) {
- debug("EVENT: creating album failed; response = '%s'.",
+ debug("EVENT: creating album failed; status = '%u', response = '%s'.", txn.get_status_code(),
txn.get_response());
- if (txn.get_status_code() == 403 || txn.get_status_code() == 404) {
+ if (txn.get_status_code() == 403) {
+ get_host().install_static_message_pane(_("Could not create album, Shotwell is lacking permission to do so. Please re-authenticate and grant Shotwell the required permission to create new media and albums"),
+ Spit.Publishing.PluginHost.ButtonMode.CLOSE);
+ } else if (txn.get_status_code() == 404) {
do_logout();
} else {
// If we get any other kind of error, we can't recover, so just post it to the user
diff --git a/plugins/shotwell-publishing/YouTubePublishing.vala b/plugins/shotwell-publishing/YouTubePublishing.vala
index 88218cc..13e4afd 100644
--- a/plugins/shotwell-publishing/YouTubePublishing.vala
+++ b/plugins/shotwell-publishing/YouTubePublishing.vala
@@ -44,9 +44,6 @@ public class YouTubeService : Object, Spit.Pluggable, Spit.Publishing.Service {
namespace Publishing.YouTube {
-private const string DEVELOPER_KEY =
- "AIzaSyB6hLnm0n5j8Y6Bkvh9bz3i8ADM2bJdYeY";
-
private enum PrivacySetting {
PUBLIC,
UNLISTED,