File deja-dup-GoogleAuthChange.patch of Package deja-dup.25334
diff --git a/data/meson.build b/data/meson.build
index 41db2e04..11571c9e 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -3,6 +3,13 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: Michael Terry
+google_client_id_parts = get_option('google_client_id').split('.')
+google_client_id_parts_reversed = []
+foreach part : google_client_id_parts
+ google_client_id_parts_reversed = [part] + google_client_id_parts_reversed
+endforeach
+google_reversedns = '.'.join(google_client_id_parts_reversed)
+
conf_data = configuration_data()
conf_data.set('appid', application_id)
conf_data.set('bindir', bindir)
@@ -11,6 +18,7 @@ conf_data.set('gsettingspath', profile == '' ? 'deja-dup' : 'deja-dup-' + profil
conf_data.set('icon', application_id)
conf_data.set('pkglibexecdir', pkglibexecdir)
conf_data.set('profile', profile)
+conf_data.set('scheme_google', google_reversedns)
conf_data.set('version', meson.project_version())
install_data(join_paths('icons', '@0@.svg'.format(application_id)),
diff --git a/data/org.gnome.DejaDup.desktop.in b/data/org.gnome.DejaDup.desktop.in
index afa30e4a..5784ccef 100644
--- a/data/org.gnome.DejaDup.desktop.in
+++ b/data/org.gnome.DejaDup.desktop.in
@@ -7,7 +7,7 @@ Comment=Change your backup settings
Icon=@icon@
-Exec=deja-dup
+Exec=deja-dup %u
StartupNotify=true
DBusActivatable=true
@@ -15,6 +15,9 @@ DBusActivatable=true
Type=Application
Categories=Utility;Archiving;GNOME;GTK;X-GNOME-Utilities;
+# Used for oauth flows (server redirects to this custom scheme and we catch it)
+MimeType=x-scheme-handler/@scheme_google@;
+
# Translators: Add whatever keywords you want in your language, separated by semicolons
# These keywords are used when searching for applications in dashes, etc.
Keywords=déjà;deja;dup;
diff --git a/data/post-install.sh b/data/post-install.sh
index 2de522d8..7648584b 100644
--- a/data/post-install.sh
+++ b/data/post-install.sh
@@ -12,4 +12,7 @@ if [ -z "$DESTDIR" ]; then
echo "Updating gsettings cache..."
glib-compile-schemas "$datadir/glib-2.0/schemas"
+
+ echo "Updating desktop mime cache..."
+ update-desktop-database -q "$datadir/applications"
fi
diff --git a/deja-dup/AssistantOperation.vala b/deja-dup/AssistantOperation.vala
index 08e77654..543c6c89 100644
--- a/deja-dup/AssistantOperation.vala
+++ b/deja-dup/AssistantOperation.vala
@@ -103,6 +103,11 @@ public abstract class AssistantOperation : Assistant
delete_event.connect(do_minimize_to_tray);
}
+ public DejaDup.Backend? get_backend()
+ {
+ return op == null ? null : op.backend;
+ }
+
/*
* Creates confirmation page for particular assistant
*
diff --git a/deja-dup/main.vala b/deja-dup/main.vala
index 93ef0adb..0bceba80 100644
--- a/deja-dup/main.vala
+++ b/deja-dup/main.vala
@@ -43,8 +43,14 @@ public class DejaDupApp : Gtk.Application
public DejaDupApp()
{
- Object(application_id: Config.APPLICATION_ID,
- flags: ApplicationFlags.HANDLES_COMMAND_LINE);
+ Object(
+ application_id: Config.APPLICATION_ID,
+ flags: ApplicationFlags.HANDLES_COMMAND_LINE |
+ // HANDLES_OPEN is required to support Open calls over dbus, which
+ // we use for our registered custom schemes (which support our
+ // oauth2 workflow).
+ ApplicationFlags.HANDLES_OPEN
+ );
add_main_option_entries(OPTIONS);
}
@@ -61,10 +67,11 @@ public class DejaDupApp : Gtk.Application
{
var options = command_line.get_options_dict();
- string[] filenames = {};
+ File[] files = {};
if (options.contains("")) {
var variant = options.lookup_value("", VariantType.BYTESTRING_ARRAY);
- filenames = variant.get_bytestring_array();
+ foreach (var filename in variant.get_bytestring_array())
+ files += command_line.create_file_for_arg(filename);
}
if (options.contains("restore")) {
@@ -74,10 +81,9 @@ public class DejaDupApp : Gtk.Application
}
List<File> file_list = new List<File>();
- if (filenames.length > 0) {
- int i = 0;
- while (filenames[i] != null)
- file_list.append(command_line.create_file_for_arg(filenames[i++]));
+ if (files.length > 0) {
+ foreach (var file in files)
+ file_list.append(file);
}
restore_full(file_list);
@@ -88,16 +94,16 @@ public class DejaDupApp : Gtk.Application
return 1;
}
- if (filenames.length == 0) {
+ if (files.length == 0) {
command_line.printerr("%s\n", _("No directory provided"));
return 1;
}
- else if (filenames.length > 1) {
+ else if (files.length > 1) {
command_line.printerr("%s\n", _("Only one directory can be shown at once"));
return 1;
}
- File list_directory = command_line.create_file_for_arg(filenames[0]);
+ File list_directory = files[0];
if (!list_directory.query_exists(null)) {
command_line.printerr("%s\n", _("Directory does not exist"));
return 1;
@@ -123,6 +129,14 @@ public class DejaDupApp : Gtk.Application
}
else if (options.contains("prompt")) {
prompt(this);
+ }
+ else if (files.length > 0) {
+ // If we were called without a mode (like --restore) but with file arguments,
+ // let's do our "Open" action (which is mostly used for our oauth flow).
+ // That oauth flow can happen via command line in some environments like
+ // snaps, whereas the dbus Open call might happen for flatpaks. Regardless
+ // of how they come in, treat them the same.
+ open(files, "");
} else {
activate();
}
@@ -182,6 +196,28 @@ public class DejaDupApp : Gtk.Application
}
}
+ public override void open(GLib.File[] files, string hint)
+ {
+ var google_backend = this.op == null ? null : this.op.get_backend() as DejaDup.BackendGoogle;
+
+ // We might be in middle of oauth flow, and are given an expected redirect uri
+ // like 'com.googleusercontent.apps.123:/oauth2redirect?code=xxx'
+ if (files.length == 1 && google_backend != null)
+ {
+ var provided_uri = files[0].get_uri();
+ // Normalize backend URI through gio, so it matches incoming URI format (slashes after colon, etc)
+ var expected_uri = File.new_for_uri(google_backend.get_redirect_uri()).get_uri();
+ if (provided_uri.has_prefix(expected_uri) && google_backend.continue_authorization(provided_uri)) {
+ activate();
+ return;
+ }
+ }
+
+ // Got passed files, but we don't know what to do with them.
+ foreach (var file in files)
+ warning("Ignoring unexpected file: %s", file.get_parse_name());
+ }
+
bool exit_cleanly()
{
quit();
diff --git a/libdeja/BackendGoogle.vala b/libdeja/BackendGoogle.vala
index 3f79da40..047ddcb1 100644
--- a/libdeja/BackendGoogle.vala
+++ b/libdeja/BackendGoogle.vala
@@ -23,9 +23,7 @@ public const string GOOGLE_SERVER = "google.com";
public class BackendGoogle : Backend
{
- Soup.Server server;
Soup.Session session;
- string local_address;
string pkce;
string credentials_dir;
string access_token;
@@ -443,7 +441,7 @@ public class BackendGoogle : Backend
"GET",
"https://accounts.google.com/o/oauth2/v2/auth",
"client_id", Config.GOOGLE_CLIENT_ID,
- "redirect_uri", local_address,
+ "redirect_uri", get_redirect_uri(),
"response_type", "code",
"code_challenge", pkce,
"scope", "https://www.googleapis.com/auth/drive.file"
@@ -457,7 +455,7 @@ public class BackendGoogle : Backend
"POST",
"https://www.googleapis.com/oauth2/v4/token",
"client_id", Config.GOOGLE_CLIENT_ID,
- "redirect_uri", local_address,
+ "redirect_uri", get_redirect_uri(),
"grant_type", "authorization_code",
"code_verifier", pkce,
"code", code
@@ -477,49 +475,12 @@ public class BackendGoogle : Backend
yield get_tokens(message);
}
- void oauth_server_request_received(Soup.Server server, Soup.Message message,
- string path,
- HashTable<string, string>? query,
- Soup.ClientContext client)
- {
- if (path != "/") {
- message.status_code = Soup.Status.NOT_FOUND;
- return;
- }
-
- message.status_code = Soup.Status.ACCEPTED;
- server = null;
-
- string? error = query == null ? null : query.lookup("error");
- if (error != null) {
- stop_login(error);
- return;
- }
-
- string? code = query == null ? null : query.lookup("code");
- if (code == null) {
- stop_login(null);
- return;
- }
-
- // Show consent granted screen
- var html = DejaDup.get_access_granted_html();
- message.response_body.append_take(html.data);
-
- show_oauth_consent_page(null, null); // continue on from paused screen
- get_credentials.begin(code);
- }
-
void start_authorization() throws Error
{
- // Start a server and listen on it
- server = new Soup.Server("server-header",
- "%s/%s ".printf(Config.PACKAGE, Config.VERSION));
- server.listen_local(0, Soup.ServerListenOptions.IPV4_ONLY);
- local_address = server.get_uris().data.to_string(false);
-
// Prepare to handle requests that finish the consent process
- server.add_handler(null, oauth_server_request_received);
+ error_msg = null;
+ code = null;
+ authorization_instance.set(this);
// We need a random string between 43 and 128 chars. UUIDs are an easy way
// to get random strings, but they are only 37 chars long. So just add two.
@@ -589,6 +550,53 @@ public class BackendGoogle : Backend
envp.append("GOOGLE_DRIVE_SETTINGS=%s/settings.yaml".printf(credentials_dir));
envp_ready(true, envp);
}
+
+ static WeakRef authorization_instance;
+ string error_msg;
+ string code;
+
+ public string get_redirect_uri()
+ {
+ var id_parts = Config.GOOGLE_CLIENT_ID.split(".");
+ string[] reversed = {};
+ for (int i = id_parts.length - 1; i >= 0; i--) {
+ reversed += id_parts[i];
+ }
+ // Exact path does not matter and is optional. But it seems wise to use some
+ // path and this one seems reasonable (and is the example in their oauth docs).
+ return "%s:/oauth2redirect".printf(string.joinv(".", reversed));
+ }
+
+ // Meant to be called externally when the oauth service redirects back to us.
+ // This passed-in uri will hold the code and error values from the service.
+ public bool continue_authorization(string command_line_redirect_uri)
+ {
+ if (authorization_instance.get() == null)
+ return false; // no auth in progress
+
+ var active_backend = (BackendGoogle)authorization_instance.get();
+ active_backend.continue_authorization_helper(command_line_redirect_uri);
+ return true;
+ }
+
+ void continue_authorization_helper(string command_line_redirect_uri)
+ {
+ var uri = new Soup.URI(command_line_redirect_uri);
+ var query = Soup.Form.decode(uri.query);
+
+ if (error_msg == null && query != null)
+ error_msg = query.lookup("error");
+
+ if (error_msg == null && query != null)
+ code = query.lookup("code");
+
+ if (error_msg == null && code == null)
+ error_msg = ""; // default to just a blank contextual error message
+
+ authorization_instance.set(null);
+ show_oauth_consent_page(null, null); // continue on from paused screen
+ get_credentials.begin(code);
+ }
}
} // end namespace