From 9e6c0f79efa33d8ed0bd1ca18d6db3d5fdd7219f Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 12 Apr 2017 08:45:31 -0400 Subject: [PATCH] Added export vault function to tools. --- gulpfile.js | 4 + package.json | 3 +- src/_locales/en/messages.json | 20 ++++ src/_locales/fr/messages.json | 2 +- src/popup/app/config.js | 7 ++ src/popup/app/tools/toolsExportController.js | 116 +++++++++++++++++++ src/popup/app/tools/views/tools.html | 5 + src/popup/app/tools/views/toolsExport.html | 29 +++++ src/popup/index.html | 2 + src/services/cryptoService.js | 1 + 10 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 src/popup/app/tools/toolsExportController.js create mode 100644 src/popup/app/tools/views/toolsExport.html diff --git a/gulpfile.js b/gulpfile.js index b56d022aa8..a12dbf96fb 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -123,6 +123,10 @@ gulp.task('lib', ['clean:lib'], function () { { src: paths.npmDir + 'ng-infinite-scroll/build/ng-infinite-scroll.js', dest: paths.libDir + 'ng-infinite-scroll' + }, + { + src: paths.npmDir + 'papaparse/papaparse*.js', + dest: paths.libDir + 'papaparse' } ]; diff --git a/package.json b/package.json index 57f4feb733..93b755be65 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "ng-infinite-scroll": "1.3.0", "node-forge": "0.7.0", "webpack-stream": "3.2.0", - "gulp-json-editor": "2.2.1" + "gulp-json-editor": "2.2.1", + "papaparse": "4.2.0" } } diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index e185cc33b2..a59ec73beb 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -654,5 +654,25 @@ "disableContextMenuItemDesc": { "message": "Context menu options provide quick access to password generation and logins for the website in your current tab.", "desription": "Context menu options provide quick access to password generation and logins for the website in your current tab." + }, + "exportVault": { + "message": "Export Vault", + "desription": "Export Vault" + }, + "warning": { + "message": "WARNING", + "desription": "WARNING (should stay in capitalized letters if the language permits)" + }, + "exportWarning": { + "message": "This export contains your unencrypted data in .csv format. You should not store or send it over unsecure channels (such as email). Delete it immediately after your are done using it.", + "desription": "This export contains your unencrypted data in .csv format. You should not store or send it over unsecure channels (such as email). Delete it immediately after your are done using it." + }, + "exportMasterPassword": { + "message": "Enter your master password to export your vault data.", + "desription": "Enter your master password to export your vault data." + }, + "exportVaultInfo": { + "message": "Export your vault data in .csv format so that you can easily modify it or move it elsewhere.", + "desription": "Export your vault data in .csv format so that you can easily modify it or move it elsewhere." } } diff --git a/src/_locales/fr/messages.json b/src/_locales/fr/messages.json index ff9ee0530e..b1b1440aa3 100644 --- a/src/_locales/fr/messages.json +++ b/src/_locales/fr/messages.json @@ -652,7 +652,7 @@ "description": "Disable Context Menu Options" }, "disableContextMenuItemDesc": { - "message": "Les options de menu contextuelles permettent un accès rapide à la génération de mots de passe et d\'identifiants pour le site web de l\'onglet actuel", + "message": "Les options de menu contextuelles permettent un accès rapide à la génération de mots de passe et d'identifiants pour le site web de l'onglet actuel", "desription": "Context menu options provide quick access to password generation and logins for the website in your current tab." } } diff --git a/src/popup/app/config.js b/src/popup/app/config.js index c4340a907d..39af96b634 100644 --- a/src/popup/app/config.js +++ b/src/popup/app/config.js @@ -141,6 +141,13 @@ data: { authorize: true }, params: { animation: null, addState: null, editState: null } }) + .state('export', { + url: '/export', + templateUrl: 'app/tools/views/toolsExport.html', + controller: 'toolsExportController', + data: { authorize: true }, + params: { animation: null } + }) .state('about', { url: '/about', diff --git a/src/popup/app/tools/toolsExportController.js b/src/popup/app/tools/toolsExportController.js new file mode 100644 index 0000000000..4658779bfe --- /dev/null +++ b/src/popup/app/tools/toolsExportController.js @@ -0,0 +1,116 @@ +angular + .module('bit.tools') + + .controller('toolsExportController', function ($scope, $state, toastr, $q, $analytics, + i18nService, cryptoService, userService, folderService, loginService) { + $scope.i18n = i18nService; + + $scope.submitPromise = null; + $scope.submit = function () { + $scope.submitPromise = checkPassword().then(function () { + return getCsv(); + }).then(function (csv) { + downloadFile(csv); + }, function () { + toastr.error(i18nService.invalidMasterPassword, i18nService.errorsOccurred); + }); + }; + + function checkPassword() { + var deferred = $q.defer(); + + userService.getEmail(function (email) { + var key = cryptoService.makeKey($scope.masterPassword, email); + cryptoService.hashPassword($scope.masterPassword, key, function (keyHash) { + cryptoService.getKeyHash(true, function (storedKeyHash) { + if (storedKeyHash && keyHash && storedKeyHash === keyHash) { + deferred.resolve(); + } + else { + deferred.reject(); + } + }); + }); + }); + + return deferred.promise; + } + + function getCsv() { + var deferred = $q.defer(); + var decFolders = []; + var decLogins = []; + var promises = []; + + var folderPromise = $q.when(folderService.getAllDecrypted()); + folderPromise.then(function (folders) { + decFolders = folders; + }); + promises.push(folderPromise); + + var loginPromise = $q.when(loginService.getAllDecrypted()); + loginPromise.then(function (logins) { + decLogins = logins; + }); + promises.push(loginPromise); + + $q.all(promises).then(function () { + var exportLogins = []; + for (var i = 0; i < decLogins.length; i++) { + var login = { + name: decLogins[i].name, + uri: decLogins[i].uri, + username: decLogins[i].username, + password: decLogins[i].password, + notes: decLogins[i].notes, + folder: null + }; + + for (var j = 0; j < decFolders.length; j++) { + if (decFolders[j].id === decLogins[i].folderId && decFolders[j].name !== i18nService.noneFolder) { + login.folder = decFolders[j].name; + break; + } + } + + exportLogins.push(login); + } + + var csv = Papa.unparse(exportLogins); + deferred.resolve(csv); + }); + + return deferred.promise; + } + + function downloadFile(csvString) { + var csvBlob = new Blob([csvString]); + if (window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveBlob(csvBlob, makeFileName()); + } + else { + var a = window.document.createElement('a'); + a.href = window.URL.createObjectURL(csvBlob, { type: 'text/plain' }); + a.download = makeFileName(); + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + } + } + + function makeFileName() { + var now = new Date(); + var dateString = + now.getFullYear() + '' + padNumber(now.getMonth() + 1, 2) + '' + padNumber(now.getDate(), 2) + + padNumber(now.getHours(), 2) + '' + padNumber(now.getMinutes(), 2) + + padNumber(now.getSeconds(), 2); + + return 'bitwarden_export_' + dateString + '.csv'; + } + + function padNumber(number, width, paddingCharacter) { + paddingCharacter = paddingCharacter || '0'; + number = number + ''; + return number.length >= width ? number : new Array(width - number.length + 1).join(paddingCharacter) + number; + } + }); diff --git a/src/popup/app/tools/views/tools.html b/src/popup/app/tools/views/tools.html index d63768696b..136aed4642 100644 --- a/src/popup/app/tools/views/tools.html +++ b/src/popup/app/tools/views/tools.html @@ -30,6 +30,11 @@ {{i18n.importLogins}} {{i18n.importLoginsInfo}} + + + {{i18n.exportVault}} + {{i18n.exportVaultInfo}} + diff --git a/src/popup/app/tools/views/toolsExport.html b/src/popup/app/tools/views/toolsExport.html new file mode 100644 index 0000000000..f05232e6b7 --- /dev/null +++ b/src/popup/app/tools/views/toolsExport.html @@ -0,0 +1,29 @@ +
+
+ +
+ +
+
{{i18n.exportVault}}
+
+
+
+
+
+
+ + + +
+
+ +
+
+
+
diff --git a/src/popup/index.html b/src/popup/index.html index 8c860045cb..7285483ab8 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -14,6 +14,7 @@ + @@ -82,6 +83,7 @@ + diff --git a/src/services/cryptoService.js b/src/services/cryptoService.js index 81bed737d6..b521bf24dc 100644 --- a/src/services/cryptoService.js +++ b/src/services/cryptoService.js @@ -131,6 +131,7 @@ function initCryptoService() { if (b64 && b64 === true && _b64KeyHash) { callback(_b64KeyHash); + return; } else if (!b64 && _keyHash) { callback(_keyHash);