diff --git a/appinfo/routes.php b/appinfo/routes.php index a04767510..f3d3db754 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -58,14 +58,17 @@ ['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'], ['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' => '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/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); diff --git a/controller/sharecontroller.php b/controller/sharecontroller.php index 4b1f534e4..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 @@ -476,13 +490,55 @@ 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)) { + + 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(); + } + + /** + * @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(); - } else { - return $this->fileService->getFileByGuid($file_guid); } + + // 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); + } + } + + $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())); } /** @@ -515,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/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 440af9942..a358081df 100644 --- a/js/app/controllers/credential.js +++ b/js/app/controllers/credential.js @@ -72,20 +72,18 @@ 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); } _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; } @@ -181,10 +179,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; @@ -261,54 +259,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 +323,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/edit_credential.js b/js/app/controllers/edit_credential.js index 326ba78e3..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')) { + const key = CredentialService.getSharedKeyFromCredential($scope.storedCredential); + const file = $scope.new_custom_field.value; - 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); _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(); }; @@ -342,31 +331,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 +361,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 +379,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); diff --git a/js/app/controllers/settings.js b/js/app/controllers/settings.js index 21caea4a0..861fc9c49 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,19 +236,9 @@ done: 0, total: _selected_credentials.length }; - var changeCredential = function (index, oldVaultPass, newVaultPass) { - var 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 changeCredential = function (index, oldVaultPass, newVaultPass) { + const next_credential_callback = function () { + const percent = index / _selected_credentials.length * 100; $scope.change_pw = { percent: percent, done: index + 1, @@ -260,14 +250,31 @@ 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); + }); }); } - }); + }; + + 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) { + 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) { + $scope.cur_state = data; + }).then(next_credential_callback); + } }; changeCredential(0, VaultService.getActiveVault().vaultKey, newVaultPass); - }); }; @@ -276,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); } } 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..5361bb2d1 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; @@ -145,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); - } } @@ -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,97 @@ }; 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; + } + + this.total = this.parent.plain_credential.files.length; this.current = 0; - for (var i = 0; i < this.parent.plain_credential.files.length; i++) { - var _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) { - //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); + 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 () { + 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 () { + 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; + } + + this.total = this.parent.plain_credential.custom_fields.length; + console.log("total custom_field_files_update = " + this.total); + this.current = 0; + + 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')); - this.call_progress(new progress_datatype(this.current, this.total, 'files')); + if (this.current === this.total) { + this.call_then('All custom field files has been updated'); + } else { + setTimeout(custom_field_workload.bind(this), 1); + } + }; - FileService.updateFile(fileData, this.parent.new_password).then((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'); - } - }).bind(this)); + if (this.parent.plain_credential.custom_fields[this.current].field_type !== 'file') { + check_next_callback.bind(this)(); + return; + } + + const _file = this.parent.plain_credential.custom_fields[this.current].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); + + FileService.updateFile(fileData, this.parent.new_password).then((function () { + 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 */ - } - if (this.parent.plain_credential.files.length === 0) { - this.call_progress(new progress_datatype(0, 0, 'files')); - this.call_then("No files to update"); - } + }; + setTimeout(custom_field_workload.bind(this), 1); }; var promise_revisions_update = function () { @@ -287,12 +360,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 () { @@ -344,6 +418,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) { 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); 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[]