From 70b5799c366ed5d92216355cff2d8a947ee1d491 Mon Sep 17 00:00:00 2001 From: binsky Date: Sat, 22 Apr 2023 17:56:54 +0200 Subject: [PATCH 01/11] some improvements; comment out a weird line in re-encryption js code --- js/app/controllers/credential.js | 6 +- js/app/controllers/share.js | 41 ++++++----- js/app/services/credentialservice.js | 101 ++++++++++++++++----------- 3 files changed, 86 insertions(+), 62 deletions(-) diff --git a/js/app/controllers/credential.js b/js/app/controllers/credential.js index 440af9942..73254adbe 100644 --- a/js/app/controllers/credential.js +++ b/js/app/controllers/credential.js @@ -181,10 +181,10 @@ private_key = ShareService.rsaPrivateKeyFromPEM(private_key); /** global: forge */ - crypted_shared_key = private_key.decrypt(forge.util.decode64(crypted_shared_key)); - crypted_shared_key = EncryptService.encryptString(crypted_shared_key); + const decrypted_shared_key = private_key.decrypt(forge.util.decode64(crypted_shared_key)); + const vault_key_encrypted_shared_key = EncryptService.encryptString(decrypted_shared_key); - ShareService.saveSharingRequest(share_request, crypted_shared_key).then(function () { + ShareService.saveSharingRequest(share_request, vault_key_encrypted_shared_key).then(function () { var idx = $scope.incoming_share_requests.indexOf(share_request); $scope.incoming_share_requests.splice(idx, 1); var active_share_requests = false; diff --git a/js/app/controllers/share.js b/js/app/controllers/share.js index 861020ed6..b34ec9660 100644 --- a/js/app/controllers/share.js +++ b/js/app/controllers/share.js @@ -278,7 +278,7 @@ $scope.$digest(); }) .then(function (result) { - $scope.share_settings.cypher_progress.times.push({ + $scope.share_settings.cypher_progress.times.push({ time: ((new Date().getTime() / 1000) - start), user: data[0].user_id }); @@ -320,7 +320,7 @@ $scope.share_settings.upload_progress.total = 0; //Credential is already shared if ($scope.storedCredential.shared_key && $scope.storedCredential.shared_key !== '' && $scope.storedCredential.shared_key !== null) { - var enc_key = EncryptService.decryptString(angular.copy($scope.storedCredential.shared_key)); + var enc_key = EncryptService.decryptString(angular.copy($scope.storedCredential.shared_key)); if ($scope.share_settings.linkSharing.enabled) { var expire_time = new Date(angular.copy($scope.share_settings.linkSharing.settings.expire_time)).getTime() / 1000; var shareObj = { @@ -355,28 +355,31 @@ $scope.sharing_complete = true; } else { - ShareService.generateSharedKey(20).then(function (key) { + ShareService.generateSharedKey(20).then(function (generated_shared_key) { + // copy complete credential to encryptedSharedCredential where it can be safely re-encrypted var encryptedSharedCredential = angular.copy($scope.storedCredential); - var old_key = VaultService.getActiveVault().vaultKey; - - CredentialService.reencryptCredential(encryptedSharedCredential.guid, old_key, key).progress(function () { - }).then(function (data) { - var _credential = data.cryptogram; - _credential.set_share_key = true; - _credential.skip_revision = true; - _credential.shared_key = EncryptService.encryptString(key); - CredentialService.updateCredential(_credential, true).then(function () { - $scope.storedCredential.shared_key = _credential.shared_key; - NotificationService.showNotification($translate.instant('credential.shared'), 4000); - $scope.sharing_complete = true; - }); - }); + var vault_key = VaultService.getActiveVault().vaultKey; + + CredentialService + .reencryptCredential(encryptedSharedCredential.guid, vault_key, generated_shared_key) + .progress(function () {}) + .then(function (data) { + var _credential = data.cryptogram; + _credential.set_share_key = true; + _credential.skip_revision = true; + _credential.shared_key = EncryptService.encryptString(generated_shared_key); + CredentialService.updateCredential(_credential, true).then(function () { + $scope.storedCredential.shared_key = _credential.shared_key; + NotificationService.showNotification($translate.instant('credential.shared'), 4000); + $scope.sharing_complete = true; + }); + }); var list = $scope.share_settings.credentialSharedWithUserAndGroup; for (var i = 0; i < list.length; i++) { if (list[i].type === "user") { - $scope.applyShareToUser(list[i], key); + $scope.applyShareToUser(list[i], generated_shared_key); } } @@ -390,7 +393,7 @@ expire_views: $scope.share_settings.linkSharing.settings.expire_views }; ShareService.createPublicSharedCredential(shareObj).then(function () { - var hash = window.btoa($scope.storedCredential.guid + '<::>' + key); + var hash = window.btoa($scope.storedCredential.guid + '<::>' + generated_shared_key); $scope.share_link = getShareLink(hash); }); } diff --git a/js/app/services/credentialservice.js b/js/app/services/credentialservice.js index c30756d63..f8fca1196 100644 --- a/js/app/services/credentialservice.js +++ b/js/app/services/credentialservice.js @@ -85,17 +85,13 @@ return _encryptedFields; }, updateCredential: function (credential, skipEncryption, key) { - var _credential = angular.copy(credential); + let _credential = angular.copy(credential); if (!skipEncryption) { - for (var i = 0; i < _encryptedFields.length; i++) { - var field = _encryptedFields[i]; - var fieldValue = angular.copy(credential[field]); - _credential[field] = EncryptService.encryptString(JSON.stringify(fieldValue), key); - } + _credential = this.encryptCredential(credential, key); } _credential.expire_time = new Date(angular.copy(credential.expire_time)).getTime() / 1000; - var queryUrl = OC.generateUrl('apps/passman/api/v2/credentials/' + credential.guid); + const queryUrl = OC.generateUrl('apps/passman/api/v2/credentials/' + credential.guid); return $http.patch(queryUrl, _credential).then(function (response) { if (response.data) { return response.data; @@ -215,23 +211,39 @@ var promise_credential_update = function () { service.getCredential(credential_guid).then((function (credential) { this.parent.plain_credential = service.decryptCredential(credential, this.parent.old_password); - var tmp = angular.copy(this.parent.plain_credential); - - if (tmp.hasOwnProperty('shared_key') && tmp.shared_key !== null && tmp.shared_key !== '' && !skipSharingKey) { - var shared_key = EncryptService.decryptString(angular.copy(tmp.shared_key)).trim(); - tmp.shared_key = EncryptService.encryptString(angular.copy(shared_key), this.parent.new_password); - tmp.set_share_key = true; - tmp.skip_revision = true; - this.parent.new_password = shared_key; + var plain_credential = angular.copy(this.parent.plain_credential); + + if ( + plain_credential.hasOwnProperty('shared_key') && + plain_credential.shared_key !== null && + plain_credential.shared_key !== '' && + !skipSharingKey + ) { + // re-encrypt the credential.shared_key with the new password + // (e.g. re-encrypt from vault_key to generated_shared_key) + const decrypted_credential_shared_key = EncryptService.decryptString(angular.copy(plain_credential.shared_key)).trim(); + plain_credential.shared_key = EncryptService.encryptString( + angular.copy(decrypted_credential_shared_key), + this.parent.new_password + ); + plain_credential.set_share_key = true; + plain_credential.skip_revision = true; + + // todo: temporary comment out this Brantje code line as is looks pointless to set the + // new encryption key to the decrypted_credential_shared_key + // this.parent.new_password = decrypted_credential_shared_key; } - this.parent.new_credential_cryptogram = service.encryptCredential(tmp, this.parent.new_password); + // before: re-encryption with the now out-commented decrypted_credential_shared_key if the credential has one + // now: re-encryption with the original parent.new_password (e.g. generated_shared_key) + this.parent.new_credential_cryptogram = service.encryptCredential(plain_credential, this.parent.new_password); this.call_progress(new progress_datatype(1, 2, 'credential')); // Save data this.parent.new_credential_cryptogram.skip_revision = true; service.updateCredential(this.parent.new_credential_cryptogram, true).then((function () { this.call_progress(new progress_datatype(2, 2, 'credential')); + // transfer plain and encrypted credential to the next promise in the complete re-encryption task this.call_then({ plain_text: this.parent.plain_credential, cryptogram: this.parent.new_credential_cryptogram @@ -241,36 +253,44 @@ }; var promise_files_update = function () { - // Add the double of the files so we take encryption phase and upload to the server into the math - this.total = this.parent.plain_credential.files.length * 2; // Binded on credential finish upload + if (this.parent.plain_credential.files.length === 0) { + this.call_progress(new progress_datatype(0, 0, 'files')); + this.call_then("No files to update"); + return; + } + + // add the double total progress value of the files count to be able to separate the decryption step and the re-encryption / update / upload phase + this.total = this.parent.plain_credential.files.length * 2; this.current = 0; - for (var i = 0; i < this.parent.plain_credential.files.length; i++) { - var _file = this.parent.plain_credential.files[i]; + for (let i = 0; i < this.parent.plain_credential.files.length; i++) { + const _file = this.parent.plain_credential.files[i]; /* jshint ignore:start */ FileService.getFile(_file).then((function (fileData) { - //Decrypt with old key - fileData.filename = EncryptService.decryptString(fileData.filename, this.parent.old_password); - fileData.file_data = EncryptService.decryptString(fileData.file_data, this.parent.old_password); - - this.current++; - - this.call_progress(new progress_datatype(this.current, this.total, 'files')); + try { + //Decrypt with old key + fileData.filename = EncryptService.decryptString(fileData.filename, this.parent.old_password); + fileData.file_data = EncryptService.decryptString(fileData.file_data, this.parent.old_password); - FileService.updateFile(fileData, this.parent.new_password).then((function () { + // increase due to successful decryption this.current++; this.call_progress(new progress_datatype(this.current, this.total, 'files')); - if (this.current === this.total) { - this.call_then('All files has been updated'); - } - }).bind(this)); + + FileService.updateFile(fileData, this.parent.new_password).then((function () { + // increase due to successful re-encryption / update / upload + this.current++; + this.call_progress(new progress_datatype(this.current, this.total, 'files')); + if (this.current === this.total) { + this.call_then('All files has been updated'); + } + }).bind(this)); + } catch (e) { + console.error(e); + console.error('Failed to re-encrypt fle. It seems to be corrupt.', _file); + } }).bind(this)); /* jshint ignore:end */ } - if (this.parent.plain_credential.files.length === 0) { - this.call_progress(new progress_datatype(0, 0, 'files')); - this.call_then("No files to update"); - } }; var promise_revisions_update = function () { @@ -287,12 +307,13 @@ this.call_then("No history to update"); return; } + var _revision = revisions[this.current]; - //Decrypt! - _revision.credential_data = service.decryptCredential(_revision.credential_data, this.parent.old_password); - _revision.credential_data = service.encryptCredential(_revision.credential_data, this.parent.new_password); - this.current++; + const decrypted_revision_credential_data = service.decryptCredential(_revision.credential_data, this.parent.old_password); + _revision.credential_data = service.encryptCredential(decrypted_revision_credential_data, this.parent.new_password); + + this.current++; this.call_progress(new progress_datatype(this.current + this.upload, this.total, 'revisions')); service.updateRevision(_revision).then((function () { From baf9a189eaf955d5bea0b12c271ff63ddbeb413b Mon Sep 17 00:00:00 2001 From: binsky Date: Sat, 22 Apr 2023 19:10:17 +0200 Subject: [PATCH 02/11] fix shared credential decryption error #737 --- js/app/controllers/edit_credential.js | 35 ++++++++------------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/js/app/controllers/edit_credential.js b/js/app/controllers/edit_credential.js index 326ba78e3..10a03fd1c 100644 --- a/js/app/controllers/edit_credential.js +++ b/js/app/controllers/edit_credential.js @@ -342,31 +342,17 @@ $scope.updateExistingListWithCredential(new_cred); }); - - } else { - - var key, _credential; - if (!$scope.storedCredential.hasOwnProperty('acl') && $scope.storedCredential.hasOwnProperty('shared_key')) { - - if ($scope.storedCredential.shared_key) { - key = EncryptService.decryptString(angular.copy($scope.storedCredential.shared_key)); - } - } - - if ($scope.storedCredential.hasOwnProperty('acl')) { - key = EncryptService.decryptString(angular.copy($scope.storedCredential.acl.shared_key)); - } + let _credential = angular.copy($scope.storedCredential); + const key = CredentialService.getSharedKeyFromCredential($scope.storedCredential); if (key) { _credential = ShareService.encryptSharedCredential($scope.storedCredential, key); - } else { - _credential = angular.copy($scope.storedCredential); } delete _credential.shared_key; - var _useKey = (key != null); - var regex = /(<([^>]+)>)/ig; + const _useKey = (key != null); + const regex = /(<([^>]+)>)/ig; if(_credential.description && _credential.description !== "") { _credential.description = _credential.description.replace(regex, ""); } @@ -386,17 +372,17 @@ if (!credential.shared_key) { credential = CredentialService.decryptCredential(credential); } else { - var enc_key = EncryptService.decryptString(credential.shared_key); + const enc_key = CredentialService.getSharedKeyFromCredential($scope.storedCredential); credential = ShareService.decryptSharedCredential(credential, enc_key); } credential.tags_raw = credential.tags; - var found=false; - var credList=$rootScope.vaultCache[$scope.active_vault.guid].credentials; - for (var i = 0; i < credList.length; i++) { + let found = false; + let credList = $rootScope.vaultCache[$scope.active_vault.guid].credentials; + for (let i = 0; i < credList.length; i++) { if (credList[i].credential_id === credential.credential_id) { - $rootScope.vaultCache[$scope.active_vault.guid].credentials[i]=credential; - found=true; + $rootScope.vaultCache[$scope.active_vault.guid].credentials[i] = credential; + found = true; } } @@ -404,7 +390,6 @@ $rootScope.vaultCache[$scope.active_vault.guid].credentials.push(credential); } $rootScope.$broadcast('push_decrypted_credential_to_list', credential); - } catch (e) { NotificationService.showNotification($translate.instant('error.decrypt'), 5000); console.log(e); From 82efcde76656113344a58a33ba6e3ece855e17fb Mon Sep 17 00:00:00 2001 From: binsky Date: Sat, 22 Apr 2023 21:15:54 +0200 Subject: [PATCH 03/11] improve permission check in share controller getFile() --- controller/sharecontroller.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/controller/sharecontroller.php b/controller/sharecontroller.php index 4b1f534e4..c934aa62f 100644 --- a/controller/sharecontroller.php +++ b/controller/sharecontroller.php @@ -476,13 +476,17 @@ public function getFile($item_guid, $file_guid) { } catch (\Exception $e) { return new NotFoundJSONResponse(); } + + // $this->userId does not exist for anonymous share link downloads $userId = ($this->userId) ? $this->userId->getUID() : null; $acl = $this->shareService->getACL($userId, $credential->getGuid()); - if (!$acl->hasPermission(SharingACL::FILES)) { - return new NotFoundJSONResponse(); - } else { - return $this->fileService->getFileByGuid($file_guid); + + if ($acl->hasPermission(SharingACL::FILES)) { + // get file by guid and check if it is owned by the owner of the shared credential + return $this->fileService->getFileByGuid($file_guid, $credential->getUserId()); } + + return new NotFoundJSONResponse(); } /** From f97567d7f62ca9898828f31d4756825fdd979802 Mon Sep 17 00:00:00 2001 From: binsky Date: Sun, 23 Apr 2023 11:44:12 +0200 Subject: [PATCH 04/11] fix/implement shared credential file upload for other users --- appinfo/routes.php | 1 + controller/sharecontroller.php | 34 +++++++++++++++++++ js/app/controllers/edit_credential.js | 49 +++++++++++---------------- js/app/services/shareservice.js | 16 ++++++++- 4 files changed, 69 insertions(+), 31 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index a04767510..d72ada96f 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -64,6 +64,7 @@ ['name' => 'share#unshareCredentialFromUser', 'url' => '/api/v2/sharing/credential/{item_guid}/{user_id}', 'verb' => 'DELETE'], ['name' => 'share#getRevisions', 'url' => '/api/v2/sharing/credential/{item_guid}/revisions', 'verb' => 'GET'], ['name' => 'share#getItemAcl', 'url' => '/api/v2/sharing/credential/{item_guid}/acl', 'verb' => 'GET'], + ['name' => 'share#uploadFile', 'url' => '/api/v2/sharing/credential/{item_guid}/file', 'verb' => 'POST'], ['name' => 'share#getFile', 'url' => '/api/v2/sharing/credential/{item_guid}/file/{file_guid}', 'verb' => 'GET'], ['name' => 'share#updateSharedCredentialACL', 'url' => '/api/v2/sharing/credential/{item_guid}/acl', 'verb' => 'PATCH'], ['name' => 'internal#getAppVersion', 'url' => '/api/v2/version', 'verb' => 'GET'], diff --git a/controller/sharecontroller.php b/controller/sharecontroller.php index c934aa62f..f7cbde023 100644 --- a/controller/sharecontroller.php +++ b/controller/sharecontroller.php @@ -489,6 +489,40 @@ public function getFile($item_guid, $file_guid) { return new NotFoundJSONResponse(); } + /** + * @param $item_guid + * @param $data + * @param $filename + * @param $mimetype + * @param $size + * @return DataResponse|NotFoundJSONResponse|JSONResponse + * @throws \Exception + * @NoAdminRequired + * @NoCSRFRequired + */ + public function uploadFile($item_guid, $data, $filename, $mimetype, $size) { + try { + $credential = $this->credentialService->getCredentialByGUID($item_guid); + } catch (\Exception $e) { + return new NotFoundJSONResponse(); + } + + $acl = $this->shareService->getACL($this->userId->getUID(), $credential->getGuid()); + if ($acl->hasPermission(SharingACL::FILES)) { + $file = array( + 'filename' => $filename, + 'size' => $size, + 'mimetype' => $mimetype, + 'file_data' => $data, + 'user_id' => $credential->getUserId() + ); + // save the file with the id of the user that owns the credential + return new JSONResponse($this->fileService->createFile($file, $credential->getUserId())); + } + + return new DataResponse(['msg' => 'Not authorized'], Http::STATUS_UNAUTHORIZED); + } + /** * @param $item_guid * @param $user_id diff --git a/js/app/controllers/edit_credential.js b/js/app/controllers/edit_credential.js index 10a03fd1c..e32e7fd66 100644 --- a/js/app/controllers/edit_credential.js +++ b/js/app/controllers/edit_credential.js @@ -161,26 +161,22 @@ $scope.selected_field_type = 'text'; _field.secret = (_field.field_type === 'password'); if(_field.field_type === 'file'){ - var key = false; - var _file = $scope.new_custom_field.value; - if (!$scope.storedCredential.hasOwnProperty('acl') && $scope.storedCredential.hasOwnProperty('shared_key')) { - - if ($scope.storedCredential.shared_key) { - key = EncryptService.decryptString(angular.copy($scope.storedCredential.shared_key)); - } - } - - if ($scope.storedCredential.hasOwnProperty('acl')) { - key = EncryptService.decryptString(angular.copy($scope.storedCredential.acl.shared_key)); - } + const key = CredentialService.getSharedKeyFromCredential($scope.storedCredential); + const file = $scope.new_custom_field.value; - FileService.uploadFile(_file, key).then(function (result) { + const callback = function (result) { delete result.file_data; result.filename = EncryptService.decryptString(result.filename, key); _field.value = result; $scope.storedCredential.custom_fields.push(_field); $scope.new_custom_field = angular.copy(_customField); - }); + }; + + if (key) { + ShareService.uploadSharedFile($scope.storedCredential, file, key).then(callback); + } else { + FileService.uploadFile(file).then(callback); + } } else { $scope.storedCredential.custom_fields.push(_field); $scope.new_custom_field = angular.copy(_customField); @@ -221,32 +217,25 @@ }; $scope.fileLoaded = function (file) { - var key; - var _file = { + const key = CredentialService.getSharedKeyFromCredential($scope.storedCredential); + const _file = { filename: file.name, size: file.size, mimetype: file.type, data: file.data }; - if (!$scope.storedCredential.hasOwnProperty('acl') && $scope.storedCredential.hasOwnProperty('shared_key')) { - - if ($scope.storedCredential.shared_key) { - key = EncryptService.decryptString(angular.copy($scope.storedCredential.shared_key)); - } - } - - if ($scope.storedCredential.hasOwnProperty('acl')) { - key = EncryptService.decryptString(angular.copy($scope.storedCredential.acl.shared_key)); - } - - - FileService.uploadFile(_file, key).then(function (result) { + const callback = function (result) { delete result.file_data; result.filename = EncryptService.decryptString(result.filename, key); $scope.storedCredential.files.push(result); - }); + }; + if (key) { + ShareService.uploadSharedFile($scope.storedCredential, _file, key).then(callback); + } else { + FileService.uploadFile(_file).then(callback); + } $scope.$digest(); }; diff --git a/js/app/services/shareservice.js b/js/app/services/shareservice.js index ff4a149cb..020c38865 100644 --- a/js/app/services/shareservice.js +++ b/js/app/services/shareservice.js @@ -160,13 +160,27 @@ }); }, downloadSharedFile: function (credential, file) { - var queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + credential.guid + '/file/' + file.guid); + const queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + credential.guid + '/file/' + file.guid); return $http.get(queryUrl).then(function (response) { if (response.data) { return response.data; } }); }, + uploadSharedFile: function (credential, file, key) { + const queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + credential.guid + '/file'); + let _file = angular.copy(file); + _file.filename = EncryptService.encryptString(_file.filename, key); + const data = EncryptService.encryptString(angular.copy(file.data), key); + _file.data = data; + return $http.post(queryUrl, _file).then(function (response) { + if (response.data) { + return response.data; + } else { + return response; + } + }); + }, encryptSharedCredential: function (credential, sharedKey) { var _credential = angular.copy(credential); _credential.shared_key = EncryptService.encryptString(sharedKey); From 2018d422d0c8deca06dc09f92f05046de925bd9c Mon Sep 17 00:00:00 2001 From: binsky Date: Sun, 23 Apr 2023 13:28:07 +0200 Subject: [PATCH 05/11] fix delete/recover credential button actions --- controller/translationcontroller.php | 1 + js/app/controllers/credential.js | 73 ++++++++++++++++------------ js/app/controllers/settings.js | 11 ++--- js/app/services/credentialservice.js | 2 +- 4 files changed, 50 insertions(+), 37 deletions(-) diff --git a/controller/translationcontroller.php b/controller/translationcontroller.php index d2a0bca13..207adc80e 100644 --- a/controller/translationcontroller.php +++ b/controller/translationcontroller.php @@ -58,6 +58,7 @@ public function getLanguageStrings() { 'credential.recovered' => $this->trans->t('Credential recovered'), 'credential.destroyed' => $this->trans->t('Credential destroyed'), 'error.loading.file.perm' => $this->trans->t('Error downloading file, you probably have insufficient permissions'), + 'error.general' => $this->trans->t('An error occurred'), // js/app/controllers/edit_credential.js 'invalid.qr' => $this->trans->t('Invalid QR code'), diff --git a/js/app/controllers/credential.js b/js/app/controllers/credential.js index 73254adbe..401dc6cdb 100644 --- a/js/app/controllers/credential.js +++ b/js/app/controllers/credential.js @@ -72,7 +72,6 @@ try { if (!_credential.shared_key) { _credential = CredentialService.decryptCredential(angular.copy(_credential)); - } else { var enc_key = EncryptService.decryptString(_credential.shared_key); _credential = ShareService.decryptSharedCredential(angular.copy(_credential), enc_key); @@ -261,54 +260,62 @@ }; var notification; - $scope.deleteCredential = function (credential) { - var _credential = angular.copy(credential); - try { - _credential = CredentialService.decryptCredential(_credential); - } catch (e) { - - } + $scope.deleteCredential = function (decrypted_credential) { + let _credential = angular.copy(decrypted_credential); _credential.delete_time = new Date().getTime() / 1000; - for (var i = 0; i < $scope.active_vault.credentials.length; i++) { - if ($scope.active_vault.credentials[i].credential_id === credential.credential_id) { - $scope.active_vault.credentials[i].delete_time = _credential.delete_time; - } - } + $scope.closeSelected(); if (notification) { NotificationService.hideNotification(notification); } - var key = CredentialService.getSharedKeyFromCredential(_credential); - CredentialService.updateCredential(_credential, false, key).then(function () { + + const key = CredentialService.getSharedKeyFromCredential(_credential); + CredentialService.updateCredential(_credential, false, key).then(function (response) { + decrypted_credential.delete_time = _credential.delete_time; + for (let i = 0; i < $scope.active_vault.credentials.length; i++) { + if ($scope.active_vault.credentials[i].credential_id === _credential.credential_id) { + $scope.active_vault.credentials[i].delete_time = _credential.delete_time; + } + } notification = NotificationService.showNotification($translate.instant('credential.deleted'), 5000); + }, function (error) { + if (error.data.msg) { + NotificationService.showNotification(error.data.msg, 5000); + } else { + NotificationService.showNotification($translate.instant('error.general'), 5000); + } }); }; - $scope.recoverCredential = function (credential) { - var _credential = angular.copy(credential); - try { - _credential = CredentialService.decryptCredential(_credential); - } catch (e) { - - } - for (var i = 0; i < $scope.active_vault.credentials.length; i++) { - if ($scope.active_vault.credentials[i].credential_id === credential.credential_id) { - $scope.active_vault.credentials[i].delete_time = 0; - } - } + $scope.recoverCredential = function (decrypted_credential) { + let _credential = angular.copy(decrypted_credential); _credential.delete_time = 0; + $scope.closeSelected(); if (notification) { NotificationService.hideNotification(notification); } - var key = CredentialService.getSharedKeyFromCredential(_credential); - CredentialService.updateCredential(_credential, false, key).then(function () { + + const key = CredentialService.getSharedKeyFromCredential(_credential); + CredentialService.updateCredential(_credential, false, key).then(function (response) { + decrypted_credential.delete_time = 0; + for (let i = 0; i < $scope.active_vault.credentials.length; i++) { + if ($scope.active_vault.credentials[i].credential_id === _credential.credential_id) { + $scope.active_vault.credentials[i].delete_time = 0; + } + } NotificationService.showNotification($translate.instant('credential.recovered'), 5000); + }, function (error) { + if (error.data.msg) { + NotificationService.showNotification(error.data.msg, 5000); + } else { + NotificationService.showNotification($translate.instant('error.general'), 5000); + } }); }; $scope.destroyCredential = function (credential) { - var _credential = angular.copy(credential); + const _credential = angular.copy(credential); CredentialService.destroyCredential(_credential.guid).then(function () { for (var i = 0; i < $scope.active_vault.credentials.length; i++) { if ($scope.active_vault.credentials[i].credential_id === credential.credential_id) { @@ -317,6 +324,12 @@ break; } } + }, function (error) { + if (error.data.msg) { + NotificationService.showNotification(error.data.msg, 5000); + } else { + NotificationService.showNotification($translate.instant('error.general'), 5000); + } }); }; diff --git a/js/app/controllers/settings.js b/js/app/controllers/settings.js index 21caea4a0..9bdad69c3 100644 --- a/js/app/controllers/settings.js +++ b/js/app/controllers/settings.js @@ -73,7 +73,7 @@ } }); - var key_strengths = [ + const key_strengths = [ 'password.poor', 'password.poor', 'password.weak', @@ -86,7 +86,7 @@ $scope.required_score = {'strength': translation}; }); - var btn_txt = $translate.instant('bookmarklet.text'); + const btn_txt = $translate.instant('bookmarklet.text'); var http = location.protocol, slashes = http.concat("//"), host = slashes.concat(window.location.hostname + ":" + window.location.port), complete = host + location.pathname; @@ -94,7 +94,7 @@ $scope.saveVaultSettings = function () { - var _vault = $scope.active_vault; + let _vault = $scope.active_vault; _vault.name = $scope.new_vault_name; _vault.vault_settings = angular.copy($scope.vault_settings); VaultService.updateVault(_vault).then(function () { @@ -236,8 +236,8 @@ done: 0, total: _selected_credentials.length }; - var changeCredential = function (index, oldVaultPass, newVaultPass) { - var usedKey = oldVaultPass; + const changeCredential = function (index, oldVaultPass, newVaultPass) { + let usedKey = oldVaultPass; if (_selected_credentials[index].hasOwnProperty('shared_key')) { if (_selected_credentials[index].shared_key) { @@ -267,7 +267,6 @@ }); }; changeCredential(0, VaultService.getActiveVault().vaultKey, newVaultPass); - }); }; diff --git a/js/app/services/credentialservice.js b/js/app/services/credentialservice.js index f8fca1196..5c0f5d97c 100644 --- a/js/app/services/credentialservice.js +++ b/js/app/services/credentialservice.js @@ -277,7 +277,7 @@ this.call_progress(new progress_datatype(this.current, this.total, 'files')); FileService.updateFile(fileData, this.parent.new_password).then((function () { - // increase due to successful re-encryption / update / upload + // increase due to successful re-encryption / update / upload this.current++; this.call_progress(new progress_datatype(this.current, this.total, 'files')); if (this.current === this.total) { From ecda82e01f524b29108f253b3d8ad315d5eca611 Mon Sep 17 00:00:00 2001 From: binsky Date: Sun, 23 Apr 2023 14:38:18 +0200 Subject: [PATCH 06/11] improve shared file upload to also support custom field files --- controller/sharecontroller.php | 28 +++++++------ js/app/services/credentialservice.js | 60 +++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/controller/sharecontroller.php b/controller/sharecontroller.php index f7cbde023..2e686cba3 100644 --- a/controller/sharecontroller.php +++ b/controller/sharecontroller.php @@ -507,20 +507,24 @@ public function uploadFile($item_guid, $data, $filename, $mimetype, $size) { return new NotFoundJSONResponse(); } - $acl = $this->shareService->getACL($this->userId->getUID(), $credential->getGuid()); - if ($acl->hasPermission(SharingACL::FILES)) { - $file = array( - 'filename' => $filename, - 'size' => $size, - 'mimetype' => $mimetype, - 'file_data' => $data, - 'user_id' => $credential->getUserId() - ); - // save the file with the id of the user that owns the credential - return new JSONResponse($this->fileService->createFile($file, $credential->getUserId())); + // only check acl, if the uploading user is not the credential owner + if ($credential->getUserId() != $this->userId->getUID()) { + $acl = $this->shareService->getACL($this->userId->getUID(), $credential->getGuid()); + if (!$acl->hasPermission(SharingACL::FILES)) { + return new DataResponse(['msg' => 'Not authorized'], Http::STATUS_UNAUTHORIZED); + } } - return new DataResponse(['msg' => 'Not authorized'], Http::STATUS_UNAUTHORIZED); + $file = array( + 'filename' => $filename, + 'size' => $size, + 'mimetype' => $mimetype, + 'file_data' => $data, + 'user_id' => $credential->getUserId() + ); + + // save the file with the id of the user that owns the credential + return new JSONResponse($this->fileService->createFile($file, $credential->getUserId())); } /** diff --git a/js/app/services/credentialservice.js b/js/app/services/credentialservice.js index 5c0f5d97c..ed4c85bdf 100644 --- a/js/app/services/credentialservice.js +++ b/js/app/services/credentialservice.js @@ -286,7 +286,53 @@ }).bind(this)); } catch (e) { console.error(e); - console.error('Failed to re-encrypt fle. It seems to be corrupt.', _file); + console.error('Failed to re-encrypt file. It seems to be corrupt.', _file); + } + }).bind(this)); + /* jshint ignore:end */ + } + }; + + var promise_custom_field_files_update = function () { + if (this.parent.plain_credential.custom_fields.length === 0) { + this.call_progress(new progress_datatype(0, 0, 'custom_field_files')); + this.call_then("No custom field files to update"); + return; + } + + // add the double total progress value of the custom fields count to be able to separate the decryption step and the re-encryption / update / upload phase + this.total = this.parent.plain_credential.custom_fields.length * 2; + this.current = 0; + + for (let i = 0; i < this.parent.plain_credential.custom_fields.length; i++) { + const custom_field = this.parent.plain_credential.custom_fields[i]; + if (custom_field.field_type !== 'file') { + continue; + } + + const _file = custom_field.value; + /* jshint ignore:start */ + FileService.getFile(_file).then((function (fileData) { + try { + //Decrypt with old key + fileData.filename = EncryptService.decryptString(fileData.filename, this.parent.old_password); + fileData.file_data = EncryptService.decryptString(fileData.file_data, this.parent.old_password); + + // increase due to successful decryption + this.current++; + this.call_progress(new progress_datatype(this.current, this.total, 'custom_field_files')); + + FileService.updateFile(fileData, this.parent.new_password).then((function () { + // increase due to successful re-encryption / update / upload + this.current++; + this.call_progress(new progress_datatype(this.current, this.total, 'custom_field_files')); + if (this.current === this.total) { + this.call_then('All files has been updated'); + } + }).bind(this)); + } catch (e) { + console.error(e); + console.error('Failed to re-encrypt custom field file. It seems to be corrupt.', _file); } }).bind(this)); /* jshint ignore:end */ @@ -365,6 +411,18 @@ } }); + master_promise.promises++; + /** global: C_Promise */ + (new C_Promise(promise_custom_field_files_update, new password_data())).progress(function (data) { + master_promise.call_progress(data); + }).then(function () { + console.warn("End custom field files update"); + master_promise.promises--; + if (master_promise.promises === 0) { + master_promise.call_then(master_promise.credential_data); + } + }); + master_promise.promises++; /** global: C_Promise */ (new C_Promise(promise_revisions_update, new password_data())).progress(function (data) { From 6660985f8867111a22eddcd60f9bc9f2a68c3c2a Mon Sep 17 00:00:00 2001 From: binsky Date: Sun, 23 Apr 2023 16:41:32 +0200 Subject: [PATCH 07/11] fix credential.sharing_key re-encryption for vault password changes --- js/app/controllers/settings.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/js/app/controllers/settings.js b/js/app/controllers/settings.js index 9bdad69c3..514866c4f 100644 --- a/js/app/controllers/settings.js +++ b/js/app/controllers/settings.js @@ -237,18 +237,8 @@ total: _selected_credentials.length }; const changeCredential = function (index, oldVaultPass, newVaultPass) { - let usedKey = oldVaultPass; - - if (_selected_credentials[index].hasOwnProperty('shared_key')) { - if (_selected_credentials[index].shared_key) { - usedKey = EncryptService.decryptString(angular.copy(_selected_credentials[index].shared_key), oldVaultPass); - } - } - - CredentialService.reencryptCredential(_selected_credentials[index].guid, usedKey, newVaultPass).progress(function (data) { - $scope.cur_state = data; - }).then(function () { - var percent = index / _selected_credentials.length * 100; + const next_credential_callback = function () { + const percent = index / _selected_credentials.length * 100; $scope.change_pw = { percent: percent, done: index + 1, @@ -264,7 +254,22 @@ NotificationService.showNotification($translate.instant('login.new.pass'), 5000); }); } - }); + }; + + if (_selected_credentials[index].shared_key) { + // only re-encrypt the shared key, if the credential is shared and not encrypted with the vault key like default credentials + CredentialService.getCredential(_selected_credentials[index].guid).then((function (credential) { + let decrypted_shared_key = EncryptService.decryptString(angular.copy(credential.shared_key), oldVaultPass); + credential.set_share_key = true; + credential.skip_revision = true; + credential.shared_key = EncryptService.encryptString(decrypted_shared_key, newVaultPass); + CredentialService.updateCredential(credential, true).then(next_credential_callback); + })); + } else { + CredentialService.reencryptCredential(_selected_credentials[index].guid, oldVaultPass, newVaultPass).progress(function (data) { + $scope.cur_state = data; + }).then(next_credential_callback); + } }; changeCredential(0, VaultService.getActiveVault().vaultKey, newVaultPass); }); From 776ffe6ea18b2f4e1673d4953cbefe58142c1dc5 Mon Sep 17 00:00:00 2001 From: binsky Date: Sun, 23 Apr 2023 18:31:50 +0200 Subject: [PATCH 08/11] implement re-encryption of the acl shared_key when changing vault password --- appinfo/routes.php | 2 ++ controller/sharecontroller.php | 28 ++++++++++++++++++++++++++++ js/app/controllers/credential.js | 3 +-- js/app/controllers/settings.js | 17 ++++++++++------- js/app/services/credentialservice.js | 2 +- js/app/services/vaultservice.js | 24 ++++++++++++++++++++++++ lib/Service/ShareService.php | 11 +++++++++++ 7 files changed, 77 insertions(+), 10 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index d72ada96f..f3d3db754 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -58,6 +58,7 @@ ['name' => 'share#getPendingRequests', 'url' => '/api/v2/sharing/pending', 'verb' => 'GET'], ['name' => 'share#deleteShareRequest', 'url' => '/api/v2/sharing/decline/{share_request_id}', 'verb' => 'DELETE'], ['name' => 'share#getVaultItems', 'url' => '/api/v2/sharing/vault/{vault_guid}/get', 'verb' => 'GET'], + ['name' => 'share#getVaultAclEntries', 'url' => '/api/v2/sharing/vault/{vault_guid}/acl', 'verb' => 'GET'], ['name' => 'share#createPublicShare', 'url' => '/api/v2/sharing/public', 'verb' => 'POST'], ['name' => 'share#getPublicCredentialData', 'url' => '/api/v2/sharing/credential/{credential_guid}/public', 'verb' => 'GET'], ['name' => 'share#unshareCredential', 'url' => '/api/v2/sharing/credential/{item_guid}', 'verb' => 'DELETE'], @@ -67,6 +68,7 @@ ['name' => 'share#uploadFile', 'url' => '/api/v2/sharing/credential/{item_guid}/file', 'verb' => 'POST'], ['name' => 'share#getFile', 'url' => '/api/v2/sharing/credential/{item_guid}/file/{file_guid}', 'verb' => 'GET'], ['name' => 'share#updateSharedCredentialACL', 'url' => '/api/v2/sharing/credential/{item_guid}/acl', 'verb' => 'PATCH'], + ['name' => 'share#updateSharedCredentialACLSharedKey', 'url' => '/api/v2/sharing/credential/{item_guid}/acl/shared_key', 'verb' => 'PATCH'], ['name' => 'internal#getAppVersion', 'url' => '/api/v2/version', 'verb' => 'GET'], //Settings diff --git a/controller/sharecontroller.php b/controller/sharecontroller.php index 2e686cba3..a8a974011 100644 --- a/controller/sharecontroller.php +++ b/controller/sharecontroller.php @@ -368,6 +368,20 @@ public function getVaultItems($vault_guid) { } } + /** + * Obtains the list of acl entries for credentials shared with this vault + * + * @NoAdminRequired + * @NoCSRFRequired + */ + public function getVaultAclEntries($vault_guid) { + try { + return new JSONResponse($this->shareService->getVaultAclList($this->userId->getUID(), $vault_guid)); + } catch (\Exception $ex) { + return new NotFoundResponse(); + } + } + /** * @param $share_request_id * @return JSONResponse @@ -557,4 +571,18 @@ public function updateSharedCredentialACL($item_guid, $user_id, $permission) { } } + + /** + * @param $item_guid + * @param $shared_key + * @return JSONResponse + * @NoAdminRequired + * @NoCSRFRequired + */ + public function updateSharedCredentialACLSharedKey($item_guid, $shared_key) { + /** @var SharingACL $acl */ + $acl = $this->shareService->getACL($this->userId->getUID(), $item_guid); + $acl->setSharedKey($shared_key); + return new JSONResponse($this->shareService->updateCredentialACL($acl)->jsonSerialize()); + } } diff --git a/js/app/controllers/credential.js b/js/app/controllers/credential.js index 401dc6cdb..a358081df 100644 --- a/js/app/controllers/credential.js +++ b/js/app/controllers/credential.js @@ -78,13 +78,12 @@ } _credential.tags_raw = _credential.tags; } catch (e) { - NotificationService.showNotification($translate.instant('error.decrypt'), 5000); + console.error(e); //$rootScope.$broadcast('logout'); //SettingsService.setSetting('defaultVaultPass', null); //.setSetting('defaultVault', null); //$location.path('/') - } _credentials[i] = _credential; } diff --git a/js/app/controllers/settings.js b/js/app/controllers/settings.js index 514866c4f..66eddbf93 100644 --- a/js/app/controllers/settings.js +++ b/js/app/controllers/settings.js @@ -250,8 +250,10 @@ vault.private_sharing_key = EncryptService.decryptString(angular.copy(vault.private_sharing_key), oldVaultPass); vault.private_sharing_key = EncryptService.encryptString(vault.private_sharing_key, newVaultPass); VaultService.updateSharingKeys(vault).then(function () { - $rootScope.$broadcast('logout'); - NotificationService.showNotification($translate.instant('login.new.pass'), 5000); + VaultService.reEncryptACLSharingKeys(vault, oldVaultPass, newVaultPass, EncryptService).then(function () { + $rootScope.$broadcast('logout'); + NotificationService.showNotification($translate.instant('login.new.pass'), 5000); + }); }); } }; @@ -259,11 +261,12 @@ if (_selected_credentials[index].shared_key) { // only re-encrypt the shared key, if the credential is shared and not encrypted with the vault key like default credentials CredentialService.getCredential(_selected_credentials[index].guid).then((function (credential) { - let decrypted_shared_key = EncryptService.decryptString(angular.copy(credential.shared_key), oldVaultPass); - credential.set_share_key = true; - credential.skip_revision = true; - credential.shared_key = EncryptService.encryptString(decrypted_shared_key, newVaultPass); - CredentialService.updateCredential(credential, true).then(next_credential_callback); + const decrypted_shared_key = EncryptService.decryptString(angular.copy(credential.shared_key), oldVaultPass); + let _credential = angular.copy(credential); + _credential.set_share_key = true; + _credential.skip_revision = true; + _credential.shared_key = EncryptService.encryptString(decrypted_shared_key, newVaultPass); + CredentialService.updateCredential(_credential, true).then(next_credential_callback); })); } else { CredentialService.reencryptCredential(_selected_credentials[index].guid, oldVaultPass, newVaultPass).progress(function (data) { diff --git a/js/app/services/credentialservice.js b/js/app/services/credentialservice.js index ed4c85bdf..9fc20a6b9 100644 --- a/js/app/services/credentialservice.js +++ b/js/app/services/credentialservice.js @@ -141,13 +141,13 @@ } } catch (e) { console.error('Error decrypting credential:', credential); + console.error('Error decrypting credential field:', field); throw e; } try { credential[field] = JSON.parse(field_decrypted_value); } catch (e) { console.warn('Field' + field + ' in ' + credential.label + ' could not be parsed! Value:' + fieldValue); - } } diff --git a/js/app/services/vaultservice.js b/js/app/services/vaultservice.js index 188f7c060..da3eb9923 100644 --- a/js/app/services/vaultservice.js +++ b/js/app/services/vaultservice.js @@ -122,6 +122,30 @@ } }); }, + reEncryptACLSharingKeys: function (vault, oldVaultPass, newVaultPass, EncryptService) { + const queryUrl = OC.generateUrl('apps/passman/api/v2/sharing/vault/' + vault.guid + '/acl'); + return $http.get(queryUrl).then(function (response) { + if (response.data) { + const updateACLSharingKey = function (index) { + let acl = response.data[index]; + const decrypted_shared_key = EncryptService.decryptString(angular.copy(acl.shared_key), oldVaultPass); + acl.shared_key = EncryptService.encryptString(decrypted_shared_key, newVaultPass); + + const patchUrl = OC.generateUrl('apps/passman/api/v2/sharing/credential/' + acl.item_guid + '/acl/shared_key'); + $http.patch(patchUrl, { + shared_key: acl.shared_key + }).then(function () { + if (index < response.data.length - 1) { + return updateACLSharingKey(index + 1); + } + }); + }; + if (response.data[0]) { + return updateACLSharingKey(0); + } + } + }); + }, deleteVault: function (vault, file_ids) { var queryUrl = OC.generateUrl('apps/passman/api/v2/vaults/' + vault.guid); var deleteFilesUrl = OC.generateUrl('apps/passman/api/v2/files/delete'); diff --git a/lib/Service/ShareService.php b/lib/Service/ShareService.php index 20a79e442..1827674cd 100644 --- a/lib/Service/ShareService.php +++ b/lib/Service/ShareService.php @@ -273,6 +273,17 @@ public function getCredentialAclList(string $item_guid) { return $this->sharingACL->getCredentialAclList($item_guid); } + /** + * Get the access control list by vault guid + * + * @param string $user_id + * @param string $vault_guid + * @return Entity[] + */ + public function getVaultAclList(string $user_id, string $vault_guid) { + return $this->sharingACL->getVaultEntries($user_id, $vault_guid); + } + /** * @param string $item_guid * @return Entity[] From e7fd74c32a249748ee5782f001801ba5440e996f Mon Sep 17 00:00:00 2001 From: binsky Date: Sun, 23 Apr 2023 18:56:31 +0200 Subject: [PATCH 09/11] use recursion in the new reencryptCredential promise functions --- js/app/services/credentialservice.js | 73 +++++++++++++++------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/js/app/services/credentialservice.js b/js/app/services/credentialservice.js index 9fc20a6b9..5361bb2d1 100644 --- a/js/app/services/credentialservice.js +++ b/js/app/services/credentialservice.js @@ -259,12 +259,22 @@ return; } - // add the double total progress value of the files count to be able to separate the decryption step and the re-encryption / update / upload phase - this.total = this.parent.plain_credential.files.length * 2; + this.total = this.parent.plain_credential.files.length; this.current = 0; - for (let i = 0; i < this.parent.plain_credential.files.length; i++) { - const _file = this.parent.plain_credential.files[i]; + const files_workload = function () { + const check_next_callback = function () { + this.current++; + this.call_progress(new progress_datatype(this.current, this.total, 'files')); + + if (this.current === this.total) { + this.call_then('All files has been updated'); + } else { + setTimeout(files_workload.bind(this), 1); + } + }; + + const _file = this.parent.plain_credential.files[this.current]; /* jshint ignore:start */ FileService.getFile(_file).then((function (fileData) { try { @@ -272,25 +282,18 @@ fileData.filename = EncryptService.decryptString(fileData.filename, this.parent.old_password); fileData.file_data = EncryptService.decryptString(fileData.file_data, this.parent.old_password); - // increase due to successful decryption - this.current++; - this.call_progress(new progress_datatype(this.current, this.total, 'files')); - FileService.updateFile(fileData, this.parent.new_password).then((function () { - // increase due to successful re-encryption / update / upload - this.current++; - this.call_progress(new progress_datatype(this.current, this.total, 'files')); - if (this.current === this.total) { - this.call_then('All files has been updated'); - } + check_next_callback.bind(this)(); }).bind(this)); } catch (e) { console.error(e); console.error('Failed to re-encrypt file. It seems to be corrupt.', _file); + check_next_callback.bind(this)(); } }).bind(this)); /* jshint ignore:end */ - } + }; + setTimeout(files_workload.bind(this), 1); }; var promise_custom_field_files_update = function () { @@ -300,17 +303,28 @@ return; } - // add the double total progress value of the custom fields count to be able to separate the decryption step and the re-encryption / update / upload phase - this.total = this.parent.plain_credential.custom_fields.length * 2; + this.total = this.parent.plain_credential.custom_fields.length; + console.log("total custom_field_files_update = " + this.total); this.current = 0; - for (let i = 0; i < this.parent.plain_credential.custom_fields.length; i++) { - const custom_field = this.parent.plain_credential.custom_fields[i]; - if (custom_field.field_type !== 'file') { - continue; + const custom_field_workload = function () { + const check_next_callback = function () { + this.current++; + this.call_progress(new progress_datatype(this.current, this.total, 'custom_field_files')); + + if (this.current === this.total) { + this.call_then('All custom field files has been updated'); + } else { + setTimeout(custom_field_workload.bind(this), 1); + } + }; + + if (this.parent.plain_credential.custom_fields[this.current].field_type !== 'file') { + check_next_callback.bind(this)(); + return; } - const _file = custom_field.value; + const _file = this.parent.plain_credential.custom_fields[this.current].value; /* jshint ignore:start */ FileService.getFile(_file).then((function (fileData) { try { @@ -318,25 +332,18 @@ fileData.filename = EncryptService.decryptString(fileData.filename, this.parent.old_password); fileData.file_data = EncryptService.decryptString(fileData.file_data, this.parent.old_password); - // increase due to successful decryption - this.current++; - this.call_progress(new progress_datatype(this.current, this.total, 'custom_field_files')); - FileService.updateFile(fileData, this.parent.new_password).then((function () { - // increase due to successful re-encryption / update / upload - this.current++; - this.call_progress(new progress_datatype(this.current, this.total, 'custom_field_files')); - if (this.current === this.total) { - this.call_then('All files has been updated'); - } + check_next_callback.bind(this)(); }).bind(this)); } catch (e) { console.error(e); console.error('Failed to re-encrypt custom field file. It seems to be corrupt.', _file); + check_next_callback.bind(this)(); } }).bind(this)); /* jshint ignore:end */ - } + }; + setTimeout(custom_field_workload.bind(this), 1); }; var promise_revisions_update = function () { From a21c2aace865f13059a2d28b8c01135558600457 Mon Sep 17 00:00:00 2001 From: binsky Date: Mon, 24 Apr 2023 18:57:13 +0200 Subject: [PATCH 10/11] fix patching files as a non admin user --- controller/filecontroller.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/controller/filecontroller.php b/controller/filecontroller.php index bd5cef402..af9b2911b 100644 --- a/controller/filecontroller.php +++ b/controller/filecontroller.php @@ -93,6 +93,10 @@ public function deleteFiles($file_ids) { return new JSONResponse(array('ok' => empty($failed_file_ids), 'failed' => $failed_file_ids)); } + /** + * @NoAdminRequired + * @NoCSRFRequired + */ public function updateFile($file_id, $file_data, $filename) { try { $file = $this->fileService->getFile($file_id, $this->userId); From 6e192fec573bece3dbd59ef37cc7780faa76706f Mon Sep 17 00:00:00 2001 From: binsky Date: Mon, 24 Apr 2023 19:34:30 +0200 Subject: [PATCH 11/11] fix deletion of a vault that contains shared credentials with files --- js/app/controllers/settings.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/js/app/controllers/settings.js b/js/app/controllers/settings.js index 66eddbf93..861fc9c49 100644 --- a/js/app/controllers/settings.js +++ b/js/app/controllers/settings.js @@ -283,18 +283,23 @@ $scope.delete_vault = function () { if ($scope.confirm_vault_delete && $scope.delete_vault_password === VaultService.getActiveVault().vaultKey) { getCurrentVaultCredentials(function (vault) { - var credentials = vault.credentials; + const credentials = vault.credentials; $scope.remove_pw = { percent: 0, done: 0, total: vault.credentials.length, }; - var file_ids = []; + const file_ids = []; for (const credential of credentials) { - var decryptedFiles = JSON.parse(EncryptService.decryptString(angular.copy(credential.files), VaultService.getActiveVault().vaultKey)); - for (const file of decryptedFiles) { - file_ids.push(file.file_id); + try { + const enc_key = CredentialService.getSharedKeyFromCredential(credential); + const decryptedFiles = JSON.parse(EncryptService.decryptString(angular.copy(credential.files), enc_key)); + for (const file of decryptedFiles) { + file_ids.push(file.file_id); + } + } catch (e) { + console.error(e); } }