From 0e9348b2ed2aac73c4e46d3551a393517e414404 Mon Sep 17 00:00:00 2001 From: Rune Henriksen Date: Sat, 3 Nov 2018 17:53:18 +0100 Subject: [PATCH 01/13] added checkbox to allow exporting backup passwords --- .../Server/WebServer/RESTMethods/Backup.cs | 7 ++++++ .../scripts/controllers/ExportController.js | 6 ++--- .../ngax/scripts/services/AppService.js | 23 ++++++++++--------- .../Server/webroot/ngax/templates/export.html | 6 ++++- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/Duplicati/Server/WebServer/RESTMethods/Backup.cs b/Duplicati/Server/WebServer/RESTMethods/Backup.cs index a7f6d1460..466b7fb1c 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Backup.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Backup.cs @@ -120,6 +120,13 @@ namespace Duplicati.Server.WebServer.RESTMethods { var cmdline = Library.Utility.Utility.ParseBool(info.Request.QueryString["cmdline"].Value, false); var argsonly = Library.Utility.Utility.ParseBool(info.Request.QueryString["argsonly"].Value, false); + var exportPasswords = Library.Utility.Utility.ParseBool(info.Request.QueryString["export-passwords"].Value, false); + var passwordFields = new string[] { "passphrase", "--send-mail-password", "--send-xmpp-password" }; + if (!exportPasswords) + { + backup.Settings = (Duplicati.Server.Serialization.Interface.ISetting[])backup.Settings.Where((setting) => !passwordFields.Contains(setting.Name)).ToArray(); + } + if (cmdline) { info.OutputOK(new { Command = Runner.GetCommandLine(Runner.CreateTask(DuplicatiOperation.Backup, backup)) }); diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js index 0dd6550a6..f1cca7561 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js @@ -13,12 +13,12 @@ backupApp.controller('ExportController', function($scope, $routeParams, AppServi if ($scope.ExportType == 'commandline') { $scope.Connecting = true; - AppService.get('/backup/' + $scope.BackupID + '/export?cmdline=true').then( + AppService.get('/backup/' + $scope.BackupID + '/export?cmdline=true&export-passwords=' + encodeURIComponent($scope.ExportPasswords)).then( function(resp) { $scope.Connecting = false; $scope.Completed = true; $scope.CommandLine = resp.data.Command; - }, + }, function(resp) { $scope.Connecting = false; var message = resp.statusText; @@ -29,7 +29,7 @@ backupApp.controller('ExportController', function($scope, $routeParams, AppServi } ); } else { - $scope.DownloadURL = AppService.get_export_url($scope.BackupID, $scope.UseEncryption ? $scope.Passphrase : null); + $scope.DownloadURL = AppService.get_export_url($scope.BackupID, $scope.UseEncryption ? $scope.Passphrase : null, $scope.ExportPasswords); $scope.Completed = true; } diff --git a/Duplicati/Server/webroot/ngax/scripts/services/AppService.js b/Duplicati/Server/webroot/ngax/scripts/services/AppService.js index 54ffbd112..d03654d11 100644 --- a/Duplicati/Server/webroot/ngax/scripts/services/AppService.js +++ b/Duplicati/Server/webroot/ngax/scripts/services/AppService.js @@ -35,19 +35,19 @@ backupApp.service('AppService', function($http, $cookies, $q, $cookies, DialogSe if (($cookies.get('ui-locale') || '').trim().length > 0) options.headers['X-UI-Language'] = $cookies.get('ui-locale'); - + if (self.proxy_config != null) self.proxy_config(method, options, data, targeturl); return options; }; - - var installResponseHook = function(promise) { + + var installResponseHook = function(promise) { var deferred = $q.defer(); - + promise.then(function successCallback(response) { deferred.resolve(response); - }, function errorCallback(response) { + }, function errorCallback(response) { if (response.status == 401){ DialogService.dismissAll(); DialogService.accept('Not logged in', function () { @@ -55,18 +55,18 @@ backupApp.service('AppService', function($http, $cookies, $q, $cookies, DialogSe }); return; } - deferred.reject(response); + deferred.reject(response); }); - + return deferred.promise; }; - this.get = function(url, options) { + this.get = function(url, options) { var rurl = this.apiurl + url; - + return installResponseHook($http.get(this.proxy_url == null ? rurl : this.proxy_url, setupConfig('GET', options, null, rurl))); }; - + this.patch = function(url, data, options) { var rurl = this.apiurl + url; return installResponseHook($http.patch(this.proxy_url == null ? rurl : this.proxy_url, data, setupConfig('PATCH', options, data, rurl))); @@ -88,8 +88,9 @@ backupApp.service('AppService', function($http, $cookies, $q, $cookies, DialogSe }; - this.get_export_url = function(backupid, passphrase) { + this.get_export_url = function(backupid, passphrase, exportPasswords) { var rurl = this.apiurl + '/backup/' + backupid + '/export?x-xsrf-token=' + encodeURIComponent($cookies.get('xsrf-token')); + rurl += '&export-passwords=' + encodeURIComponent(exportPasswords); if ((passphrase || '').trim().length > 0) rurl += '&passphrase=' + encodeURIComponent(passphrase); diff --git a/Duplicati/Server/webroot/ngax/templates/export.html b/Duplicati/Server/webroot/ngax/templates/export.html index d9caf839b..b82bbd807 100644 --- a/Duplicati/Server/webroot/ngax/templates/export.html +++ b/Duplicati/Server/webroot/ngax/templates/export.html @@ -13,11 +13,15 @@ +
+ + +
-
+
From 7ff0e908888392db1a4a2b2ab833a1d1dd98a9c4 Mon Sep 17 00:00:00 2001 From: Rune Henriksen Date: Sat, 3 Nov 2018 18:16:34 +0100 Subject: [PATCH 02/13] pleasing Codacy --- Duplicati/Server/WebServer/RESTMethods/Backup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Duplicati/Server/WebServer/RESTMethods/Backup.cs b/Duplicati/Server/WebServer/RESTMethods/Backup.cs index 466b7fb1c..0d73c8077 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Backup.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Backup.cs @@ -121,7 +121,7 @@ namespace Duplicati.Server.WebServer.RESTMethods var cmdline = Library.Utility.Utility.ParseBool(info.Request.QueryString["cmdline"].Value, false); var argsonly = Library.Utility.Utility.ParseBool(info.Request.QueryString["argsonly"].Value, false); var exportPasswords = Library.Utility.Utility.ParseBool(info.Request.QueryString["export-passwords"].Value, false); - var passwordFields = new string[] { "passphrase", "--send-mail-password", "--send-xmpp-password" }; + var passwordFields = new [] { "passphrase", "--send-mail-password", "--send-xmpp-password" }; if (!exportPasswords) { backup.Settings = (Duplicati.Server.Serialization.Interface.ISetting[])backup.Settings.Where((setting) => !passwordFields.Contains(setting.Name)).ToArray(); From 083dd122ad4239261af4c7dddd9ef73ff1da50df Mon Sep 17 00:00:00 2001 From: Rune Henriksen Date: Sun, 4 Nov 2018 16:51:40 +0100 Subject: [PATCH 03/13] added warning about exporting passwords insecurely --- .../scripts/controllers/ExportController.js | 76 +++++++++++++------ 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js index f1cca7561..e48d2e9c7 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js @@ -1,38 +1,66 @@ backupApp.controller('ExportController', function($scope, $routeParams, AppService, DialogService, gettextCatalog) { $scope.ExportType = 'file'; + $scope.ExportPasswords = true; $scope.Connecting = false; $scope.BackupID = $routeParams.backupid; $scope.Completed = false; $scope.doExport = function() { - if ($scope.UseEncryption && $scope.ExportType == 'file' && ($scope.Passphrase || '').trim().length == 0) { - DialogService.dialog(gettextCatalog.getString('No passphrase entered'), gettextCatalog.getString('To export without a passphrase, uncheck the "Encrypt file" box')); - return; + // Helper function(s) + warnUnencryptedPasswords = function(continuation) { + if ($scope.ExportType == 'file' && $scope.ExportPasswords && !$scope.fileEncrypted) { + DialogService.dialog(gettextCatalog.getString('Not using encryption'), gettextCatalog.getString('The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?'), [gettextCatalog.getString('Cancel'), gettextCatalog.getString('Yes, I understand the risk')], function(ix) { + if (ix == 0) + $scope.CurrentStep = 0; + else + continuation(); + }); + } else { + continuation(); + } + } - if ($scope.ExportType == 'commandline') { - $scope.Connecting = true; - AppService.get('/backup/' + $scope.BackupID + '/export?cmdline=true&export-passwords=' + encodeURIComponent($scope.ExportPasswords)).then( - function(resp) { - $scope.Connecting = false; - $scope.Completed = true; - $scope.CommandLine = resp.data.Command; - }, - function(resp) { - $scope.Connecting = false; - var message = resp.statusText; - if (resp.data != null && resp.data.Message != null) - message = resp.data.Message; + // The actual export function to call after all checks pass + getExport = function() { + if ($scope.ExportType == 'commandline') { + $scope.Connecting = true; + AppService.get('/backup/' + $scope.BackupID + '/export?cmdline=true&export-passwords=' + encodeURIComponent($scope.ExportPasswords)).then( + function(resp) { + $scope.Connecting = false; + $scope.Completed = true; + $scope.CommandLine = resp.data.Command; + }, + function(resp) { + $scope.Connecting = false; + var message = resp.statusText; + if (resp.data != null && resp.data.Message != null) + message = resp.data.Message; - DialogService.dialog(gettextCatalog.getString('Error'), gettextCatalog.getString('Failed to connect: {{message}}', { message: message })); - } - ); - } else { - $scope.DownloadURL = AppService.get_export_url($scope.BackupID, $scope.UseEncryption ? $scope.Passphrase : null, $scope.ExportPasswords); - $scope.Completed = true; + DialogService.dialog(gettextCatalog.getString('Error'), gettextCatalog.getString('Failed to connect: {{message}}', { message: message })); + } + ); + } else { + $scope.DownloadURL = AppService.get_export_url($scope.BackupID, $scope.UseEncryption ? $scope.Passphrase : null, $scope.ExportPasswords); + $scope.Completed = true; + } + + }; + + // Make checks that do not require user input + $scope.fileEncrypted = false; + if ($scope.UseEncryption && $scope.ExportType == 'file') { + if (($scope.Passphrase || '').trim().length == 0) { + DialogService.dialog(gettextCatalog.getString('No passphrase entered'), gettextCatalog.getString('To export without a passphrase, uncheck the "Encrypt file" box')); + return; + } + else { + $scope.fileEncrypted = true; + } } - }; - + // Chain various checks that require user input + warnUnencryptedPasswords(function() { getExport(); }); + } }); From 72b569ce01fc001ffcb9e385a9744e64f58cad5b Mon Sep 17 00:00:00 2001 From: Rune Henriksen Date: Sun, 4 Nov 2018 17:17:59 +0100 Subject: [PATCH 04/13] add explicit function declaration --- .../webroot/ngax/scripts/controllers/ExportController.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js index e48d2e9c7..fa170402d 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js @@ -8,7 +8,7 @@ backupApp.controller('ExportController', function($scope, $routeParams, AppServi $scope.doExport = function() { // Helper function(s) - warnUnencryptedPasswords = function(continuation) { + function warnUnencryptedPasswords(continuation) { if ($scope.ExportType == 'file' && $scope.ExportPasswords && !$scope.fileEncrypted) { DialogService.dialog(gettextCatalog.getString('Not using encryption'), gettextCatalog.getString('The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?'), [gettextCatalog.getString('Cancel'), gettextCatalog.getString('Yes, I understand the risk')], function(ix) { if (ix == 0) @@ -23,7 +23,7 @@ backupApp.controller('ExportController', function($scope, $routeParams, AppServi } // The actual export function to call after all checks pass - getExport = function() { + function getExport() { if ($scope.ExportType == 'commandline') { $scope.Connecting = true; AppService.get('/backup/' + $scope.BackupID + '/export?cmdline=true&export-passwords=' + encodeURIComponent($scope.ExportPasswords)).then( From 15dc3b3b27f08a8ed700ca42dd757970d159eae6 Mon Sep 17 00:00:00 2001 From: Rune Henriksen Date: Sun, 4 Nov 2018 17:33:23 +0100 Subject: [PATCH 05/13] made some curly brace adjustments (Codacy) --- .../scripts/controllers/ExportController.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js index fa170402d..48273108c 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js @@ -11,10 +11,11 @@ backupApp.controller('ExportController', function($scope, $routeParams, AppServi function warnUnencryptedPasswords(continuation) { if ($scope.ExportType == 'file' && $scope.ExportPasswords && !$scope.fileEncrypted) { DialogService.dialog(gettextCatalog.getString('Not using encryption'), gettextCatalog.getString('The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?'), [gettextCatalog.getString('Cancel'), gettextCatalog.getString('Yes, I understand the risk')], function(ix) { - if (ix == 0) - $scope.CurrentStep = 0; - else - continuation(); + if (ix == 0) { + $scope.CurrentStep = 0; + } else { + continuation(); + } }); } else { continuation(); @@ -35,8 +36,9 @@ backupApp.controller('ExportController', function($scope, $routeParams, AppServi function(resp) { $scope.Connecting = false; var message = resp.statusText; - if (resp.data != null && resp.data.Message != null) + if (resp.data != null && resp.data.Message != null) { message = resp.data.Message; + } DialogService.dialog(gettextCatalog.getString('Error'), gettextCatalog.getString('Failed to connect: {{message}}', { message: message })); } @@ -46,7 +48,7 @@ backupApp.controller('ExportController', function($scope, $routeParams, AppServi $scope.Completed = true; } - }; + } // Make checks that do not require user input $scope.fileEncrypted = false; @@ -54,8 +56,7 @@ backupApp.controller('ExportController', function($scope, $routeParams, AppServi if (($scope.Passphrase || '').trim().length == 0) { DialogService.dialog(gettextCatalog.getString('No passphrase entered'), gettextCatalog.getString('To export without a passphrase, uncheck the "Encrypt file" box')); return; - } - else { + } else { $scope.fileEncrypted = true; } } From af92bfb1029f11e7dc675eddde0487b1f15806a4 Mon Sep 17 00:00:00 2001 From: Rune Henriksen Date: Sun, 4 Nov 2018 17:46:33 +0100 Subject: [PATCH 06/13] double quotes for strings (Codacy) --- .../scripts/controllers/ExportController.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js index 48273108c..1e9770b75 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js @@ -1,5 +1,5 @@ -backupApp.controller('ExportController', function($scope, $routeParams, AppService, DialogService, gettextCatalog) { - $scope.ExportType = 'file'; +backupApp.controller("ExportController", function($scope, $routeParams, AppService, DialogService, gettextCatalog) { + $scope.ExportType = "file"; $scope.ExportPasswords = true; $scope.Connecting = false; $scope.BackupID = $routeParams.backupid; @@ -9,8 +9,8 @@ backupApp.controller('ExportController', function($scope, $routeParams, AppServi $scope.doExport = function() { // Helper function(s) function warnUnencryptedPasswords(continuation) { - if ($scope.ExportType == 'file' && $scope.ExportPasswords && !$scope.fileEncrypted) { - DialogService.dialog(gettextCatalog.getString('Not using encryption'), gettextCatalog.getString('The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?'), [gettextCatalog.getString('Cancel'), gettextCatalog.getString('Yes, I understand the risk')], function(ix) { + if ($scope.ExportType == "file" && $scope.ExportPasswords && !$scope.fileEncrypted) { + DialogService.dialog(gettextCatalog.getString("Not using encryption"), gettextCatalog.getString("The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?"), [gettextCatalog.getString("Cancel"), gettextCatalog.getString("Yes, I understand the risk")], function(ix) { if (ix == 0) { $scope.CurrentStep = 0; } else { @@ -25,9 +25,9 @@ backupApp.controller('ExportController', function($scope, $routeParams, AppServi // The actual export function to call after all checks pass function getExport() { - if ($scope.ExportType == 'commandline') { + if ($scope.ExportType == "commandline") { $scope.Connecting = true; - AppService.get('/backup/' + $scope.BackupID + '/export?cmdline=true&export-passwords=' + encodeURIComponent($scope.ExportPasswords)).then( + AppService.get("/backup/" + $scope.BackupID + "/export?cmdline=true&export-passwords=" + encodeURIComponent($scope.ExportPasswords)).then( function(resp) { $scope.Connecting = false; $scope.Completed = true; @@ -40,7 +40,7 @@ backupApp.controller('ExportController', function($scope, $routeParams, AppServi message = resp.data.Message; } - DialogService.dialog(gettextCatalog.getString('Error'), gettextCatalog.getString('Failed to connect: {{message}}', { message: message })); + DialogService.dialog(gettextCatalog.getString("Error"), gettextCatalog.getString("Failed to connect: {{message}}", { message: message })); } ); } else { @@ -52,9 +52,9 @@ backupApp.controller('ExportController', function($scope, $routeParams, AppServi // Make checks that do not require user input $scope.fileEncrypted = false; - if ($scope.UseEncryption && $scope.ExportType == 'file') { - if (($scope.Passphrase || '').trim().length == 0) { - DialogService.dialog(gettextCatalog.getString('No passphrase entered'), gettextCatalog.getString('To export without a passphrase, uncheck the "Encrypt file" box')); + if ($scope.UseEncryption && $scope.ExportType == "file") { + if (($scope.Passphrase || "").trim().length == 0) { + DialogService.dialog(gettextCatalog.getString("No passphrase entered"), gettextCatalog.getString("To export without a passphrase, uncheck the \"Encrypt file\" box")); return; } else { $scope.fileEncrypted = true; @@ -63,5 +63,5 @@ backupApp.controller('ExportController', function($scope, $routeParams, AppServi // Chain various checks that require user input warnUnencryptedPasswords(function() { getExport(); }); - } + }; }); From 11a3d8c4d7a25d0fc4024d10396dacd8611f7c03 Mon Sep 17 00:00:00 2001 From: Rune Henriksen Date: Sun, 4 Nov 2018 17:53:41 +0100 Subject: [PATCH 07/13] update to use type-safe comparison --- .../ngax/scripts/controllers/ExportController.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js index 1e9770b75..8a7cbf230 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js @@ -9,9 +9,9 @@ backupApp.controller("ExportController", function($scope, $routeParams, AppServi $scope.doExport = function() { // Helper function(s) function warnUnencryptedPasswords(continuation) { - if ($scope.ExportType == "file" && $scope.ExportPasswords && !$scope.fileEncrypted) { + if ($scope.ExportType === "file" && $scope.ExportPasswords && !$scope.fileEncrypted) { DialogService.dialog(gettextCatalog.getString("Not using encryption"), gettextCatalog.getString("The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?"), [gettextCatalog.getString("Cancel"), gettextCatalog.getString("Yes, I understand the risk")], function(ix) { - if (ix == 0) { + if (ix === 0) { $scope.CurrentStep = 0; } else { continuation(); @@ -25,7 +25,7 @@ backupApp.controller("ExportController", function($scope, $routeParams, AppServi // The actual export function to call after all checks pass function getExport() { - if ($scope.ExportType == "commandline") { + if ($scope.ExportType === "commandline") { $scope.Connecting = true; AppService.get("/backup/" + $scope.BackupID + "/export?cmdline=true&export-passwords=" + encodeURIComponent($scope.ExportPasswords)).then( function(resp) { @@ -52,8 +52,8 @@ backupApp.controller("ExportController", function($scope, $routeParams, AppServi // Make checks that do not require user input $scope.fileEncrypted = false; - if ($scope.UseEncryption && $scope.ExportType == "file") { - if (($scope.Passphrase || "").trim().length == 0) { + if ($scope.UseEncryption && $scope.ExportType === "file") { + if (($scope.Passphrase || "").trim().length === 0) { DialogService.dialog(gettextCatalog.getString("No passphrase entered"), gettextCatalog.getString("To export without a passphrase, uncheck the \"Encrypt file\" box")); return; } else { From 043d119964662d75a86f093a268e9e3000815676 Mon Sep 17 00:00:00 2001 From: Rune Henriksen Date: Sun, 4 Nov 2018 18:12:11 +0100 Subject: [PATCH 08/13] object-shorthand (Codacy) --- .../Server/webroot/ngax/scripts/controllers/ExportController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js index 8a7cbf230..a9ab2a428 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/ExportController.js @@ -40,7 +40,7 @@ backupApp.controller("ExportController", function($scope, $routeParams, AppServi message = resp.data.Message; } - DialogService.dialog(gettextCatalog.getString("Error"), gettextCatalog.getString("Failed to connect: {{message}}", { message: message })); + DialogService.dialog(gettextCatalog.getString("Error"), gettextCatalog.getString("Failed to connect: {{message}}", { message })); } ); } else { From a7909c421c3a2eef68b0edeeabd14f8a741b69a4 Mon Sep 17 00:00:00 2001 From: Rune Henriksen Date: Sun, 4 Nov 2018 22:07:24 +0100 Subject: [PATCH 09/13] url sanitization to strip tokens and passwords --- Duplicati/Server/Database/Backup.cs | 21 ++++++++++++++++++- .../Interface/IBackup.cs | 4 +++- .../Server/WebServer/RESTMethods/Backup.cs | 1 + 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Duplicati/Server/Database/Backup.cs b/Duplicati/Server/Database/Backup.cs index c3808c004..269c11e60 100644 --- a/Duplicati/Server/Database/Backup.cs +++ b/Duplicati/Server/Database/Backup.cs @@ -18,6 +18,7 @@ using System; using Duplicati.Server.Serialization.Interface; using System.Collections.Generic; +using System.Text.RegularExpressions; namespace Duplicati.Server.Database { @@ -65,7 +66,7 @@ namespace Duplicati.Server.Database /// public string[] Tags { get; set; } /// - /// The backup target url, excluding username/password + /// The backup target url /// public string TargetURL { get; set; } /// @@ -98,6 +99,24 @@ namespace Duplicati.Server.Database /// public bool IsTemporary { get { return ID == null ? false : ID.IndexOf("-", StringComparison.Ordinal) > 0; } } + /// + /// Sanitizes the backup TargetUrl + /// + public void SanitizeTargetUrl() + { + var url = this.TargetURL; + // Remove authid + url = Regex.Replace(url, "(!?authid)=[^&\n]+[&]?", ""); + // remove auth-password + url = Regex.Replace(url, "(!?auth-password)=[^&\n]+[&]?", ""); + // Remove edge case of '?&' + url = Regex.Replace(url, Regex.Escape("?&"), ""); + // Remove edge case of '&' at end of line + url = Regex.Replace(url, "&$", ""); + // Remove edge case of '?' at end of line + url = Regex.Replace(url, Regex.Escape("?"), ""); + this.TargetURL = url; + } } } diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IBackup.cs b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IBackup.cs index e54ed5a9e..1330332d6 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IBackup.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IBackup.cs @@ -42,7 +42,7 @@ namespace Duplicati.Server.Serialization.Interface /// string[] Tags { get; set; } /// - /// The backup target url, excluding username/password + /// The backup target url /// string TargetURL { get; set; } /// @@ -74,6 +74,8 @@ namespace Duplicati.Server.Serialization.Interface /// Gets a value indicating if this instance is not persisted to the database /// bool IsTemporary { get; } + + void SanitizeTargetUrl(); } } diff --git a/Duplicati/Server/WebServer/RESTMethods/Backup.cs b/Duplicati/Server/WebServer/RESTMethods/Backup.cs index 0d73c8077..7ad204990 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Backup.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Backup.cs @@ -125,6 +125,7 @@ namespace Duplicati.Server.WebServer.RESTMethods if (!exportPasswords) { backup.Settings = (Duplicati.Server.Serialization.Interface.ISetting[])backup.Settings.Where((setting) => !passwordFields.Contains(setting.Name)).ToArray(); + backup.SanitizeTargetUrl(); } if (cmdline) From 38513da34264d9e4eaa8ed6e7c4d8abe8543dea5 Mon Sep 17 00:00:00 2001 From: Rune Henriksen Date: Sun, 4 Nov 2018 23:13:47 +0100 Subject: [PATCH 10/13] added filter for sia password --- Duplicati/Server/Database/Backup.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Duplicati/Server/Database/Backup.cs b/Duplicati/Server/Database/Backup.cs index 269c11e60..b4d6d21f1 100644 --- a/Duplicati/Server/Database/Backup.cs +++ b/Duplicati/Server/Database/Backup.cs @@ -106,9 +106,11 @@ namespace Duplicati.Server.Database { var url = this.TargetURL; // Remove authid - url = Regex.Replace(url, "(!?authid)=[^&\n]+[&]?", ""); + url = Regex.Replace(url, "authid=[^&\n]+[&]?", ""); // remove auth-password - url = Regex.Replace(url, "(!?auth-password)=[^&\n]+[&]?", ""); + url = Regex.Replace(url, "auth-password=[^&\n]+[&]?", ""); + // remove backupsia-password + url = Regex.Replace(url, "backupsia-password=[^&\n]+[&]?", ""); // Remove edge case of '?&' url = Regex.Replace(url, Regex.Escape("?&"), ""); // Remove edge case of '&' at end of line From 919eea59fca2426fff86d11fbc3f05bdb7b8a0f3 Mon Sep 17 00:00:00 2001 From: Rune Henriksen Date: Tue, 6 Nov 2018 23:44:36 +0100 Subject: [PATCH 11/13] fixed sia and trailing ? regex --- Duplicati/Server/Database/Backup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Duplicati/Server/Database/Backup.cs b/Duplicati/Server/Database/Backup.cs index b4d6d21f1..efc74bfc3 100644 --- a/Duplicati/Server/Database/Backup.cs +++ b/Duplicati/Server/Database/Backup.cs @@ -110,13 +110,13 @@ namespace Duplicati.Server.Database // remove auth-password url = Regex.Replace(url, "auth-password=[^&\n]+[&]?", ""); // remove backupsia-password - url = Regex.Replace(url, "backupsia-password=[^&\n]+[&]?", ""); + url = Regex.Replace(url, "sia-password=[^&\n]+[&]?", ""); // Remove edge case of '?&' url = Regex.Replace(url, Regex.Escape("?&"), ""); // Remove edge case of '&' at end of line url = Regex.Replace(url, "&$", ""); // Remove edge case of '?' at end of line - url = Regex.Replace(url, Regex.Escape("?"), ""); + url = Regex.Replace(url, Regex.Escape("?") + "$", ""); this.TargetURL = url; } } From c41f2c6a604fd78f992ce75a4ff8666616b0ba07 Mon Sep 17 00:00:00 2001 From: Rune Henriksen Date: Thu, 8 Nov 2018 20:00:51 +0100 Subject: [PATCH 12/13] moved all sanitization logic into backup class and moved from regex to using internal Uri library --- Duplicati/Server/Database/Backup.cs | 50 ++++++++++++------- .../Interface/IBackup.cs | 2 + .../Server/WebServer/RESTMethods/Backup.cs | 3 +- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/Duplicati/Server/Database/Backup.cs b/Duplicati/Server/Database/Backup.cs index efc74bfc3..3f38fd8fc 100644 --- a/Duplicati/Server/Database/Backup.cs +++ b/Duplicati/Server/Database/Backup.cs @@ -19,12 +19,27 @@ using System; using Duplicati.Server.Serialization.Interface; using System.Collections.Generic; using System.Text.RegularExpressions; +using System.Linq; namespace Duplicati.Server.Database { public class Backup : IBackup - { - + { + // Sensitive information that may be stored in TargetUrl + private readonly string[] UrlPasswords = new[]{ + "authid", + "auth-password", + "sia-password", + }; + + // Sensitive information that may be stored in Settings + private readonly string[] SettingPasswords = new[]{ + "passphrase", + "--authid", + "--send-mail-password", + "--send-xmpp-password" + }; + public Backup() { this.ID = null; @@ -100,24 +115,25 @@ namespace Duplicati.Server.Database public bool IsTemporary { get { return ID == null ? false : ID.IndexOf("-", StringComparison.Ordinal) > 0; } } /// - /// Sanitizes the backup TargetUrl + /// Sanitizes the backup TargetUrl from any fields in the PasswordFields list. /// public void SanitizeTargetUrl() { - var url = this.TargetURL; - // Remove authid - url = Regex.Replace(url, "authid=[^&\n]+[&]?", ""); - // remove auth-password - url = Regex.Replace(url, "auth-password=[^&\n]+[&]?", ""); - // remove backupsia-password - url = Regex.Replace(url, "sia-password=[^&\n]+[&]?", ""); - // Remove edge case of '?&' - url = Regex.Replace(url, Regex.Escape("?&"), ""); - // Remove edge case of '&' at end of line - url = Regex.Replace(url, "&$", ""); - // Remove edge case of '?' at end of line - url = Regex.Replace(url, Regex.Escape("?") + "$", ""); - this.TargetURL = url; + var url = new Duplicati.Library.Utility.Uri(this.TargetURL); + var filteredParameters = url.QueryParameters; + foreach (string field in UrlPasswords) { + filteredParameters.Remove(field); + } + url = url.SetQuery(Duplicati.Library.Utility.Uri.BuildUriQuery(url.QueryParameters)); + this.TargetURL = url.ToString(); + } + + /// + /// Sanitizes the settings from any fields in the PassworldFields list. + /// + public void SanitizeSettings() + { + this.Settings = this.Settings.Where((setting) => !SettingPasswords.Contains(setting.Name)).ToArray(); } } } diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IBackup.cs b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IBackup.cs index 1330332d6..9b2f59c76 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IBackup.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IBackup.cs @@ -76,6 +76,8 @@ namespace Duplicati.Server.Serialization.Interface bool IsTemporary { get; } void SanitizeTargetUrl(); + + void SanitizeSettings(); } } diff --git a/Duplicati/Server/WebServer/RESTMethods/Backup.cs b/Duplicati/Server/WebServer/RESTMethods/Backup.cs index 7ad204990..e1057982b 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Backup.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Backup.cs @@ -121,10 +121,9 @@ namespace Duplicati.Server.WebServer.RESTMethods var cmdline = Library.Utility.Utility.ParseBool(info.Request.QueryString["cmdline"].Value, false); var argsonly = Library.Utility.Utility.ParseBool(info.Request.QueryString["argsonly"].Value, false); var exportPasswords = Library.Utility.Utility.ParseBool(info.Request.QueryString["export-passwords"].Value, false); - var passwordFields = new [] { "passphrase", "--send-mail-password", "--send-xmpp-password" }; if (!exportPasswords) { - backup.Settings = (Duplicati.Server.Serialization.Interface.ISetting[])backup.Settings.Where((setting) => !passwordFields.Contains(setting.Name)).ToArray(); + backup.SanitizeSettings(); backup.SanitizeTargetUrl(); } From ee7a5cad504cb50463a2ff1a6bdab48897cf154b Mon Sep 17 00:00:00 2001 From: Rune Henriksen Date: Sat, 10 Nov 2018 17:01:16 +0100 Subject: [PATCH 13/13] trailing comma for consistency --- Duplicati/Server/Database/Backup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Duplicati/Server/Database/Backup.cs b/Duplicati/Server/Database/Backup.cs index 3f38fd8fc..5eba71a59 100644 --- a/Duplicati/Server/Database/Backup.cs +++ b/Duplicati/Server/Database/Backup.cs @@ -37,7 +37,7 @@ namespace Duplicati.Server.Database "passphrase", "--authid", "--send-mail-password", - "--send-xmpp-password" + "--send-xmpp-password", }; public Backup()