Commit c692e8da authored by Michael Terry's avatar Michael Terry

Switch from GOA to our own API keys

parent 30b0d7dd
# 38.5
...
# 39.0
- Switch away from GNOME Online Accounts to our own cloud keys
- It was brought to our attention that we shouldn't be using GNOME's keys,
as they are intended for GNOME only.
- Google accounts will have to be re-authenticated with our keys.
- Nextcloud accounts will now appear as webdav network server accounts.
- Adds new `pydrive_pkgs` option to list the package names needed for the
pydrive duplicity backend (for now, the system package that provides the
`pydrive` python2 package should suffice).
- Adds new `google_client_id` option if you want to override our default
account key and use your own. You likely won't want to do this.
- Adds new dependencies on `libjson-glib` and `libsoup`.
- Drops `libgoa-backend` dependency. The `libgoa` dependency will stay
during a transition period from the old keys to the new keys.
# 38.4
- Update app icon
......
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>$TITLE</title>
<style>
body {
background-color: #FFFFFD;
font-family: sans-serif;
}
.box {
height: 60vh;
width: 60vw;
margin: 18vh 20vw;
background-color: #EEE;
border-radius: 1rem;
border-width: 0.25rem;
border-style: solid;
border-color: #76ABDF;
text-align: center;
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
}
.headline {
font-size: 4rem;
width: 40vw;
}
.text {
font-size: 2rem;
width: 40vw;
}
</style>
</head>
<body>
<div class="box">
<p class="headline">$TITLE</p>
<p class="text">$TEXT</p>
</div>
</body>
</html>
\ No newline at end of file
......@@ -77,6 +77,7 @@
<choice value='drive'/>
<choice value='gcs'/>
<choice value='goa'/>
<choice value='google'/>
<choice value='rackspace'/>
<choice value='openstack'/>
<choice value='s3'/>
......@@ -93,6 +94,7 @@
<child name="s3" schema="org.gnome.DejaDup.S3"/>
<child name="gcs" schema="org.gnome.DejaDup.GCS"/>
<child name='goa' schema="org.gnome.DejaDup.GOA"/>
<child name='google' schema="org.gnome.DejaDup.Google"/>
<child name='remote' schema="org.gnome.DejaDup.Remote"/>
<child name='local' schema="org.gnome.DejaDup.Local"/>
<child name='drive' schema="org.gnome.DejaDup.Drive"/>
......@@ -179,6 +181,14 @@
</key>
</schema>
<schema id="org.gnome.DejaDup.Google" path="/org/gnome/deja-dup/google/">
<key name="folder" type="s">
<default>'$HOSTNAME'</default>
<summary>The folder where backups are stored</summary>
<description>The folder path where backups are stored. Paths can be absolute or relative to the host.</description>
</key>
</schema>
<schema id="org.gnome.DejaDup.Rackspace" path="/org/gnome/deja-dup/rackspace/">
<key name="container" type="s">
<default>'$HOSTNAME'</default>
......@@ -192,24 +202,6 @@
</key>
</schema>
<schema id="org.gnome.DejaDup.GOA" path="/org/gnome/deja-dup/goa/">
<key name="id" type="s">
<default>''</default>
<summary>Account ID</summary>
<description>The unique ID for the GNOME Online Account.</description>
</key>
<key name="type" type="s">
<default>''</default>
<summary>The type of account</summary>
<description>The cached type of account that ID represents.</description>
</key>
<key name="folder" type="s">
<default>'$HOSTNAME'</default>
<summary>The folder where backups are stored</summary>
<description>The folder hierarchy where backups are stored. Paths can be absolute or relative to the host.</description>
</key>
</schema>
<schema id="org.gnome.DejaDup.OpenStack" path="/org/gnome/deja-dup/openstack/">
<key name="container" type="s">
<default>'$HOSTNAME'</default>
......@@ -233,7 +225,7 @@
</key>
</schema>
<!-- deprecated schema -->
<!-- deprecated schemas -->
<schema id="org.gnome.DejaDup.File" path="/org/gnome/deja-dup/file/">
<key name="path" type="s">
<default>''</default>
......@@ -265,4 +257,19 @@
</key>
</schema>
<schema id="org.gnome.DejaDup.GOA" path="/org/gnome/deja-dup/goa/">
<key name="id" type="s">
<default>''</default>
</key>
<key name="type" type="s">
<default>''</default>
</key>
<key name="folder" type="s">
<default>'$HOSTNAME'</default>
</key>
<key name="migrated" type="b">
<default>false</default>
</key>
</schema>
</schemalist>
{
"scopes": [
"https://www.googleapis.com/auth/drive.file"
],
"id_token": null,
"id_token_jwt": null,
"user_agent": null,
"token_info_uri": "https://oauth2.googleapis.com/tokeninfo",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"revoke_uri": "https://oauth2.googleapis.com/revoke",
"token_expiry": "",
"invalid": false,
"client_id": "$CLIENT_ID",
"client_secret": "",
"access_token": "$ACCESS_TOKEN",
"refresh_token": "$REFRESH_TOKEN",
"_module": "oauth2client.client",
"_class": "OAuth2Credentials"
}
client_config_backend: settings
client_config:
client_id: $CLIENT_ID
client_secret: CLIENT_SECRET_NOT_USED_BUT_REQUIRED_BY_PYDRIVE
save_credentials: True
save_credentials_backend: file
save_credentials_file: $PATH/credentials.json
get_refresh_token: True
oauth_scope:
- https://www.googleapis.com/auth/drive.file
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/DejaDup@profile@">
<!-- ui files -->
<file compressed="true">restore-missing.ui</file>
<file compressed="true">server-hint.ui</file>
<!-- files used for OAuth consent screens !-->
<file>access-granted.html</file>
<!-- templates used for pydrive support -->
<file>pydrive-credentials.json</file>
<file>pydrive-settings.yaml</file>
<!-- icons -->
<file>icons/deja-dup-google-drive.png</file>
<file compressed="true">icons/deja-dup-cloud.svg</file>
<!-- add our main app icons as fallbacks of last resort -->
<file compressed="true" alias="icons/@icon@.svg">icons/org.gnome.DejaDup.svg</file>
<file compressed="true" alias="icons/@icon@-symbolic.svg">icons/org.gnome.DejaDup-symbolic.svg</file>
</gresource>
<gresource prefix="/org/gnome/DejaDup@profile@/gtk">
<file compressed="true">help-overlay.ui</file>
......
......@@ -240,6 +240,11 @@ public abstract class Assistant : Gtk.Window
{
var title = Markup.printf_escaped("<span size=\"xx-large\" weight=\"ultrabold\">%s</span>", info.title);
header_title.set_markup(title);
if (info.title == "")
header_title.hide();
else
header_title.show();
}
void page_changed()
......
......@@ -61,6 +61,10 @@ public abstract class AssistantOperation : Assistant
List<Gtk.Widget> first_password_widgets;
MainLoop password_ask_loop;
Gtk.Label consent_label;
string consent_url;
protected Gtk.Grid consent_page {get; private set;}
Gtk.Label question_label;
protected Gtk.Widget question_page {get; private set;}
......@@ -100,6 +104,7 @@ public abstract class AssistantOperation : Assistant
add_confirm_page();
add_password_page();
add_nag_page();
add_consent_page();
add_question_page();
add_progress_page();
add_summary_page();
......@@ -481,6 +486,33 @@ public abstract class AssistantOperation : Assistant
return page;
}
protected Gtk.Grid make_consent_page()
{
int rows = 0;
var page = new Gtk.Grid();
page.row_spacing = 36;
page.column_spacing = 6;
page.border_width = 12;
var l = new Gtk.Label("");
l.xalign = 0.0f;
l.max_width_chars = 50;
l.wrap = true;
page.attach(l, 0, rows, 3, 1);
++rows;
consent_label = l;
var b = new Gtk.Button.with_mnemonic(_("_Grant Access"));
b.clicked.connect(() => {
DejaDup.show_uri(get_toplevel() as Gtk.Window, consent_url);
});
page.attach(b, 1, rows, 1, 1);
++rows;
return page;
}
protected Gtk.Widget make_question_page()
{
int rows = 0;
......@@ -575,6 +607,13 @@ public abstract class AssistantOperation : Assistant
nag_page = page;
}
void add_consent_page()
{
consent_page = make_consent_page();
append_page(consent_page, Type.INTERRUPT);
set_page_title(consent_page, _("Grant Access"));
}
void add_question_page()
{
var page = make_question_page();
......@@ -632,6 +671,17 @@ public abstract class AssistantOperation : Assistant
}
}
protected void show_oauth_consent_page(string? message, string? url)
{
consent_label.label = message;
consent_url = url;
if (url == null) {
go_forward();
} else {
interrupt(consent_page, false);
}
}
protected async void do_apply()
{
/*
......@@ -659,6 +709,7 @@ public abstract class AssistantOperation : Assistant
#endif
op.backend.mount_op = new MountOperationAssistant(this);
op.backend.pause_op.connect(pause_op);
op.backend.show_oauth_consent_page.connect(show_oauth_consent_page);
ensure_status_icon(op);
......
......@@ -310,7 +310,8 @@ public class DejaDupApp : Gtk.Application
void backup_full(bool automatic)
{
assign_op(new AssistantBackup(automatic));
var backop = new AssistantBackup(automatic);
assign_op(backop);
// showing or not is handled by AssistantBackup
}
......
......@@ -32,7 +32,13 @@ deja_dup = executable('deja-dup',
resources,
vala_args: common_vflags,
c_args: common_cflags,
dependencies: [gio_unix_dep, goa_dep, gtk_dep, packagekit_dep, secret_dep],
dependencies: [
gio_unix_dep,
goa_dep,
gtk_dep,
packagekit_dep,
secret_dep,
],
link_with: [libdeja, libwidgets],
include_directories: [libdeja_inc, libwidgets_inc],
install: true,
......
This diff is collapsed.
/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 2 -*- */
/*
This file is part of Déjà Dup.
For copyright information, see AUTHORS.
Déjà Dup is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Déjà Dup is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Déjà Dup. If not, see <http://www.gnu.org/licenses/>.
*/
using GLib;
namespace DejaDup {
public class ConfigLocationGoa : ConfigLocationTable
{
public Goa.Account account {get; construct;}
public ConfigLocationGoa(Gtk.SizeGroup sg, FilteredSettings settings, Goa.Account? account) {
Object(label_sizes: sg, settings: settings, account: account);
}
Gtk.Grid hint;
Gtk.Label label;
construct {
add_widget(_("_Folder"),
new ConfigFolder(DejaDup.GOA_FOLDER_KEY, DejaDup.GOA_ROOT, settings));
// Add optional widget that warns if the account has Files disabled
create_hint();
notify["account"].connect(connect_account);
connect_account();
}
void connect_account()
{
if (account != null)
account.notify["files-disabled"].connect(check_goa);
check_goa();
}
void check_goa()
{
if (account == null) {
label.set_markup("<b><big>%s</big></b>".printf(_("This account is not yet configured. It cannot be used until you add it to your Online Accounts.")));
hint.visible = true;
} else if (account.files_disabled) {
// Translators: 'Files' here refers to the feature label in GNOME Online Accounts
label.set_markup("<b><big>%s</big></b>".printf(_("This account has disabled Files support. It cannot be used until Files support is enabled again for this Online Account.")));
hint.visible = true;
} else {
hint.visible = false;
}
}
void create_hint()
{
hint = new Gtk.Grid();
hint.row_spacing = 24;
hint.column_spacing = 12;
hint.border_width = 12;
hint.margin_top = 12;
hint.halign = Gtk.Align.CENTER;
add_wide_widget(hint);
var icon = new Gtk.Image.from_icon_name("dialog-warning-symbolic", Gtk.IconSize.DIALOG);
icon.valign = Gtk.Align.CENTER;
hint.attach(icon, 0, 0, 1, 1);
label = new Gtk.Label("");
label.wrap = true;
label.max_width_chars = 50;
label.valign = Gtk.Align.START;
hint.attach(label, 1, 0, 1, 1);
if (Environment.find_program_in_path("gnome-control-center") != null) {
var button = new Gtk.Button.with_mnemonic(_("_Open Online Account Settings"));
button.hexpand = false;
button.halign = Gtk.Align.CENTER;
button.clicked.connect(() => {
try {
Process.spawn_async(null, {"gnome-control-center", "online-accounts"}, null,
SpawnFlags.STDOUT_TO_DEV_NULL |
SpawnFlags.STDERR_TO_DEV_NULL |
SpawnFlags.SEARCH_PATH,
null, null);
} catch (Error e) {warning("%s", e.message);}
});
hint.attach(button, 0, 1, 2, 1);
}
hint.show_all();
hint.no_show_all = true;
}
}
}
/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 2 -*- */
/*
This file is part of Déjà Dup.
For copyright information, see AUTHORS.
Déjà Dup is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Déjà Dup is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Déjà Dup. If not, see <http://www.gnu.org/licenses/>.
*/
using GLib;
namespace DejaDup {
public class ConfigLocationGoogle : ConfigLocationTable
{
public ConfigLocationGoogle(Gtk.SizeGroup sg, FilteredSettings settings) {
Object(label_sizes: sg, settings: settings);
}
construct {
add_widget(_("_Folder"),
new ConfigFolder(DejaDup.GOOGLE_FOLDER_KEY, DejaDup.GOOGLE_ROOT, settings));
}
}
}
......@@ -31,7 +31,7 @@ libwidgets = shared_library('widgets',
'ConfigLocationCustom.vala',
'ConfigLocationFile.vala',
'ConfigLocationGCS.vala',
'ConfigLocationGoa.vala',
'ConfigLocationGoogle.vala',
'ConfigLocationOpenstack.vala',
'ConfigLocationRackspace.vala',
'ConfigLocationS3.vala',
......@@ -43,7 +43,7 @@ libwidgets = shared_library('widgets',
'WidgetUtils.vala'],
vala_args: common_vflags + ['--pkg=uriutils'],
c_args: common_cflags,
dependencies: [goa_dep, goabackend_dep, gtk_dep, secret_dep],
dependencies: [goa_dep, gtk_dep, secret_dep],
link_with: [libdeja],
include_directories: [libdeja_inc],
install: true,
......
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-02-22 20:06-0500\n"
"POT-Creation-Date: 2019-03-24 20:46-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......
......@@ -28,6 +28,10 @@ public abstract class Backend : Object
public signal void envp_ready(bool success, List<string>? envp, string? error = null);
public signal void pause_op(string? header, string? msg);
// This signal might be too specific to the one backend that uses it (Google).
// Subject to change as we experiment with more oauth backends.
public signal void show_oauth_consent_page(string? message, string? url);
public MountOperation mount_op {get; set;}
public abstract bool is_native(); // must be callable when nothing is mounted, nothing is prepared
......@@ -45,14 +49,16 @@ public abstract class Backend : Object
envp_ready(true, new List<string>());
}
// This call is designed to help with the GOA -> Google migration - can be deleted when done with that
public virtual async Backend? report_full_backups(bool first_backup) {return null;}
// Only called during backup
public static uint64 INFINITE_SPACE = uint64.MAX;
public virtual async uint64 get_space(bool free = true) {return INFINITE_SPACE;}
// Arguments needed only when the particular mode is active
// If mode == INVALID, arguments needed any time the backup is referenced.
public virtual void add_argv(ToolJob.Mode mode, ref List<string> argv) {}
public abstract Backend clone();
public static Backend get_for_type(string backend_name, Settings? settings = null)
{
......@@ -60,8 +66,8 @@ public abstract class Backend : Object
return new BackendS3(settings);
else if (backend_name == "gcs")
return new BackendGCS(settings);
else if (backend_name == "goa")
return new BackendGOA(settings);
else if (backend_name == "google")
return new BackendGoogle(settings);
else if (backend_name == "u1")
return new BackendU1();
else if (backend_name == "rackspace")
......@@ -85,7 +91,7 @@ public abstract class Backend : Object
if (backend != "auto" &&
backend != "s3" &&
backend != "gcs" &&
backend != "goa" &&
backend != "google" &&
backend != "u1" &&
backend != "rackspace" &&
backend != "openstack" &&
......
......@@ -23,10 +23,6 @@ namespace DejaDup {
public class BackendAuto : Backend
{
public override Backend clone() {
return new BackendAuto();
}
public override bool is_native() {
return false;
}
......@@ -49,16 +45,17 @@ public class BackendAuto : Backend
}
construct {
// We used to check various backends to see if we had the right installed
// files for them and pick a best one. But now that we support GOA, we
// just set that. We should consider getting rid of this class. The
// intent was that changing gsettings defaults wouldn't change the user's
// backup (i.e. ensuring that the storage location gsettings would be
// actively set, not relying on the gschema default).
// The intent here is that changing gsettings defaults won't
// change the user's backup (i.e. ensuring that the storage location
// gsettings would be actively set, not relying on the gschema default).
//
// Here is a brief history of defaults:
// 1) Amazon S3
// 2) We checked various dependencies to see which backend to use
// 3) Google Drive via GNOME Online Accounts
// 4) Google Drive (relying on packagekit support to install dependencies)
var settings = get_settings();
var goa_settings = get_settings(GOA_ROOT);
goa_settings.set_string(GOA_TYPE_KEY, "google");
settings.set_string(BACKEND_KEY, "goa");
settings.set_string(BACKEND_KEY, "google");
}
}
......
......@@ -44,10 +44,6 @@ public class BackendDrive : BackendFile
Object(settings: (settings != null ? settings : get_settings(DRIVE_ROOT)));
}
public override Backend clone() {
return new BackendDrive(settings);
}
string get_folder()
{
return get_folder_key(settings, DRIVE_FOLDER_KEY);
......
......@@ -29,7 +29,7 @@ public abstract class BackendFile : Backend
}
// Get mountable root
protected abstract File? get_root_from_settings();
public abstract File? get_root_from_settings();
// Get full URI to backup folder
protected abstract File? get_file_from_settings();
......@@ -160,7 +160,7 @@ public abstract class BackendFile : Backend
envp_ready(true, new List<string>());
}
protected virtual async void mount() throws Error {}
public virtual async void mount() throws Error {}
public override async uint64 get_space(bool free = true)
{
......@@ -194,4 +194,3 @@ public abstract class BackendFile : Backend
}
} // end namespace
......@@ -34,10 +34,6 @@ public class BackendGCS : Backend
Object(settings: (settings != null ? settings : get_settings(GCS_ROOT)));
}
public override Backend clone() {
return new BackendGCS(settings);
}
public override string[] get_dependencies()
{
return Config.BOTO_PACKAGES.split(",");
......
......@@ -25,6 +25,7 @@ public const string GOA_ROOT = "GOA";
public const string GOA_ID_KEY = "id";
public const string GOA_FOLDER_KEY = "folder";
public const string GOA_TYPE_KEY = "type";
public const string GOA_MIGRATED_KEY = "migrated";
public class BackendGOA : BackendRemote
{
......@@ -34,22 +35,6 @@ public class BackendGOA : BackendRemote
Object(settings: (settings != null ? settings : get_settings(GOA_ROOT)));
}
public override Backend clone() {
return new BackendGOA(settings);
}
public static async Goa.Client get_client()
{
if (_client == null) {
try {
_client = yield new Goa.Client(null);
} catch (Error e) {
warning("Couldn't get GOA client: %s", e.message);
}
}
return _client;
}
public static Goa.Client get_client_sync()
{
if (_client == null) {
......@@ -62,111 +47,47 @@ public class BackendGOA : BackendRemote
return _client;
}
protected override string get_folder()
{
return get_folder_key(settings, GOA_FOLDER_KEY, true);
}
public Goa.Object? get_object_from_settings()
{
var id = settings.get_string(GOA_ID_KEY);
return get_client_sync().lookup_by_id(id);
}
protected override File? get_root_from_settings()
public async string? get_access_token()
{
var obj = get_object_from_settings();
if (obj == null)
return null;
var files = obj.get_files();
if (files == null)
var oauth2 = obj.get_oauth2_based();
if (oauth2 == null)
return null;
return File.new_for_uri(files.uri);
}
public static string get_provider_name(Goa.Account account)
{
// Use this until GNOME bug 787413 is fixed, which asks for service-
// specific branding to be exposed.
switch (account.provider_type) {
case "google":
return _("Google Drive");
default:
return account.provider_name;
try {
string access_token;
// the async version didn't work when I tested it, maybe a bad binding
oauth2.call_get_access_token_sync(out access_token, null);
return access_token;
}
catch (Error e) {
return null;
}
}