Compare commits
518 Commits
stable16
...
v7.0.3alpha1
| Author | SHA1 | Date | |
|---|---|---|---|
| 424cc31fd6 | |||
| d388ef5c0a | |||
| 7d389b26f9 | |||
| 9254073786 | |||
| bb703006da | |||
| 1371fecf26 | |||
| 1e5c786392 | |||
| 6d85cf2a0b | |||
| 3b1715c3fc | |||
| 6d747e9721 | |||
| c5db1ef3cc | |||
| 27a630b0bc | |||
| e25593db4e | |||
| 7edf912ef8 | |||
| aab44694ad | |||
| 98268078c0 | |||
| df97d8299a | |||
| 97dba2e022 | |||
| b52154b306 | |||
| 64a9315b4a | |||
| 3fb55d9d2e | |||
| 3a4ac2caa1 | |||
| 67b9edeeb7 | |||
| 25d003a52d | |||
| a47f965b87 | |||
| 0484939fe0 | |||
| 69296befa6 | |||
| bc5c608e4e | |||
| 094f5bcc78 | |||
| 79f5346105 | |||
| 02374e595a | |||
| 7eb0d6866c | |||
| 5ddacaed7f | |||
| bd74ab2886 | |||
| 5c0139fdff | |||
| c1c70b306e | |||
| 4d261d9e15 | |||
| 257dbe61e2 | |||
| d660aab116 | |||
| e37f44e56a | |||
| bdc26c6749 | |||
| 6d8fdcd7d2 | |||
| 0044c3bb6b | |||
| 1ec756d125 | |||
| c31990e936 | |||
| 4e850128e6 | |||
| 00bb0e122f | |||
| 2013404e0a | |||
| 734112a5c1 | |||
| fbcd80ac08 | |||
| 435b9c3028 | |||
| 838c849a5a | |||
| 85d7492398 | |||
| a6eb638380 | |||
| cb3bc5ad31 | |||
| d9384cd959 | |||
| bd7f6c9e18 | |||
| bb69eebde4 | |||
| cb39c55073 | |||
| 33737f9526 | |||
| b3ddd39438 | |||
| 2b297567b5 | |||
| d6276beac0 | |||
| 03bb3d2bdc | |||
| 44c7562c7b | |||
| e556698db7 | |||
| 2bdf5a8b1a | |||
| dfc6bfe131 | |||
| c0d1b6bfb2 | |||
| 387638e317 | |||
| 2d3ea08e19 | |||
| 3ebd0b8983 | |||
| 984b6a28ac | |||
| 744b20bc0d | |||
| 8905e77d60 | |||
| c16c680e32 | |||
| 13cee302f7 | |||
| 2aaad09062 | |||
| 996c68aa2e | |||
| 7f030eedde | |||
| 2e93300313 | |||
| a385a367b0 | |||
| 97b83b9098 | |||
| ce69e83b33 | |||
| a2e9056832 | |||
| 260229b027 | |||
| 0c014409e6 | |||
| 2068eaf16b | |||
| 645e7a0c9e | |||
| 0afd79a22c | |||
| 6946962609 | |||
| f8ecef2917 | |||
| 6c544b95e8 | |||
| 5637a581d2 | |||
| 83b9a89569 | |||
| 8580e2a697 | |||
| 7f45226b8e | |||
| 37632e428d | |||
| 7930440b31 | |||
| 749c519759 | |||
| 35cb14c95f | |||
| 28f462095d | |||
| 19a7aa081e | |||
| 82ccb04182 | |||
| ebbd40385a | |||
| 03f9b14a23 | |||
| 8ed1762dfb | |||
| 76ff7ab007 | |||
| 9bf1a18512 | |||
| 86400ea4c8 | |||
| ff6deb809a | |||
| 7f1416e62a | |||
| a23396452f | |||
| 1981d220c5 | |||
| 572720fb7a | |||
| 0732131cd9 | |||
| 2ffcff228c | |||
| bec08955b2 | |||
| f040529e6d | |||
| 5ac620cb04 | |||
| e26a5ff715 | |||
| b7c7ec26c7 | |||
| 7f1e32764d | |||
| 3f5cefb07f | |||
| 463ad5a50d | |||
| ec9517f98b | |||
| 36680a5a81 | |||
| 04597aedcb | |||
| 1a9e08ae4d | |||
| dedb47390b | |||
| 95eb9bf8ae | |||
| cc0a855103 | |||
| 19dd866c7e | |||
| 8e89c51e87 | |||
| 2a859f84a8 | |||
| 8869dcf1af | |||
| ca121d8438 | |||
| f5bac5fb2d | |||
| c52f66cc73 | |||
| 5e5d9e0a80 | |||
| b98c22d39b | |||
| b609d36e3c | |||
| a7968604d8 | |||
| 9b99c81a9d | |||
| 96bb1f7f45 | |||
| 85cde16feb | |||
| 1e4431ba01 | |||
| 994f36e498 | |||
| 640abbe099 | |||
| 1f2550e3da | |||
| cfca88f20e | |||
| 8e77ca6201 | |||
| 41d8157937 | |||
| 6534304196 | |||
| 7bd4f6cac5 | |||
| 69cc2044f0 | |||
| eb682a3022 | |||
| e638a9a444 | |||
| f268eee86c | |||
| 28e1480d1c | |||
| 81c4043d61 | |||
| bd5796a0d1 | |||
| 919d19c906 | |||
| 459c78106d | |||
| 444e21ab15 | |||
| b59d58fdc1 | |||
| b26dd3c76b | |||
| 3d35c7edcc | |||
| 6eb1f7a474 | |||
| f3f1d1ee73 | |||
| 11b2835cc3 | |||
| 8b86df308b | |||
| 98ac45fa78 | |||
| 617636d5a7 | |||
| 4d9fd199ef | |||
| cc820eb824 | |||
| 135d40203c | |||
| 30cc4b9293 | |||
| 04e14ab5a6 | |||
| a133833fb9 | |||
| 2b001adce0 | |||
| 5a8db83c48 | |||
| a3dcbe5bce | |||
| dac8dead07 | |||
| 09ab1f16c0 | |||
| 0a2e471163 | |||
| a70fe184e7 | |||
| e552573bed | |||
| ffdcab6cd5 | |||
| c38fcb520a | |||
| af17de9467 | |||
| 6c1e19b386 | |||
| 2307a5a6b5 | |||
| a3a97cf1f5 | |||
| 4e4b299ceb | |||
| e9497182ba | |||
| 02ac579eae | |||
| b94c88266b | |||
| 1f52e975fa | |||
| a3c6d20ccb | |||
| 9caff0be96 | |||
| c6e87acb96 | |||
| 992bd6dbf2 | |||
| f74bf923ca | |||
| 899035bfd3 | |||
| e311535ae1 | |||
| b1b745c052 | |||
| a9ce0edecb | |||
| dce2e5e3b7 | |||
| 80d3f30ada | |||
| 7842014a68 | |||
| 2facba644e | |||
| 75236b0ee6 | |||
| 4d2ab79392 | |||
| 322cd65b9e | |||
| c47a32d515 | |||
| d9008f8ae4 | |||
| 22387b8346 | |||
| 9173a661bd | |||
| c69215c115 | |||
| efca0ab4d4 | |||
| c4fd3dbd2a | |||
| 67399901dc | |||
| 23f4e7c1cb | |||
| 29e9cb51dc | |||
| 1519f018a4 | |||
| 2b449dd4fd | |||
| be7cb7298a | |||
| 86009a564e | |||
| 9d5d0cca7a | |||
| e0c62bbd64 | |||
| f82467f845 | |||
| cac56279c2 | |||
| 5e8733a9f6 | |||
| e7cea79ee7 | |||
| 1a2bae347a | |||
| ed6365af4a | |||
| 8cd5e652de | |||
| 5dd950bc0a | |||
| e3cf107e1d | |||
| 4c6c22c4e3 | |||
| 35c133143a | |||
| 34e0259fa8 | |||
| fe119300c7 | |||
| 9a83dbec76 | |||
| bc1848933d | |||
| e2f23b24f7 | |||
| 4593d9f819 | |||
| 0711ab5de7 | |||
| 1266ebdb10 | |||
| 1b4793db66 | |||
| eb68b0d5c6 | |||
| ca3d09c940 | |||
| f979a0c09d | |||
| 09ed6b5ad4 | |||
| 113049b8f2 | |||
| c8f81e6241 | |||
| 7aa35e9f24 | |||
| 9f9c18bd74 | |||
| 6af1a2d914 | |||
| 3432b2e386 | |||
| 5b27b01525 | |||
| 3bcca4f344 | |||
| 8972f74ecf | |||
| 1051460afc | |||
| 6907eb3eb0 | |||
| d1658c1d2c | |||
| cb0cdd1d74 | |||
| 5c89d9c9ee | |||
| 493b724935 | |||
| b50a4e918b | |||
| db7b245800 | |||
| b3e2f6b457 | |||
| db117bd468 | |||
| 8c892638f5 | |||
| a8f24ed408 | |||
| 41fe3f2652 | |||
| 592a2dc82f | |||
| 18c023da16 | |||
| 68ba31fd4c | |||
| 0c057bf310 | |||
| 4748923dbc | |||
| 44ae0868fd | |||
| 8f8923a714 | |||
| 9ba8f5b604 | |||
| 2213127094 | |||
| 9598efc7cd | |||
| 8c56be5722 | |||
| 34963b0d89 | |||
| 2b9c093510 | |||
| 3c68defac2 | |||
| 8417f55b01 | |||
| 1ca1e1d4d1 | |||
| 5b88d3d3fc | |||
| 12c7ddc18f | |||
| cc1d95cbe5 | |||
| ef6a0254f9 | |||
| d59b94fa4c | |||
| 079390c037 | |||
| 8a038eeeef | |||
| 7540a58ce7 | |||
| 97a62f6bc4 | |||
| cc531fc905 | |||
| 2e04c5e956 | |||
| ec79e5470a | |||
| 9b9fbc60c0 | |||
| a1656abb4c | |||
| 8f8f3d1e43 | |||
| 3ebdb3593a | |||
| 5bc562d2ce | |||
| 039b1e3715 | |||
| b21a1de07a | |||
| aba52ff02f | |||
| 3e103f5bee | |||
| 5a57d2bd42 | |||
| ed89a746e2 | |||
| 78af01393f | |||
| f359e3432e | |||
| c0f0e79b5e | |||
| 727374a6f3 | |||
| a0f024fec2 | |||
| 32cec6cae4 | |||
| 57c954a345 | |||
| 24c8774b7f | |||
| f68fa072c7 | |||
| 4f40cde66a | |||
| 19bad71da2 | |||
| 2e8f993299 | |||
| 5eca22d229 | |||
| 78ea700752 | |||
| 60b1a6e75f | |||
| 0b8135dcc4 | |||
| 8f9deb118a | |||
| dc41f63930 | |||
| 185e41afdd | |||
| eb665bf3c8 | |||
| 64094c5384 | |||
| 1cd1214b93 | |||
| d677040bdc | |||
| 72459ca50a | |||
| b4a379b7e3 | |||
| 59aa54ddb8 | |||
| 7287789588 | |||
| bfb6b7ba90 | |||
| a50c33a91f | |||
| 53c1e3d41b | |||
| 3b760141d6 | |||
| 8757a99f78 | |||
| 663e8bc5e2 | |||
| 2e28b7fff9 | |||
| 1831ae9c9b | |||
| 51cbe2a0ee | |||
| 02a61c0b6a | |||
| 7cd1a48222 | |||
| 0cabafb513 | |||
| df0d00c8c6 | |||
| 2d7379da2c | |||
| 9cd741417a | |||
| 05301825e2 | |||
| 87ec3fbf1d | |||
| c72f0e692b | |||
| d28b4caa2f | |||
| 06bcf3db8d | |||
| d6e61745c8 | |||
| f33c49e2be | |||
| c152ab4572 | |||
| f75f1b4412 | |||
| 127aa309fb | |||
| 303e504fcb | |||
| 3c0f5d02ba | |||
| faf0bfb29b | |||
| c7d7ca455a | |||
| 555fcbdd7d | |||
| 69065ceecb | |||
| 61d9967221 | |||
| a393670f7d | |||
| 9be9e777c2 | |||
| 0e732982ae | |||
| 508fd15975 | |||
| e482ba60bc | |||
| bad2d4d408 | |||
| a77044da9e | |||
| 9c6e87849b | |||
| f80f8d9cc4 | |||
| 00c0495703 | |||
| e7f7ac38c9 | |||
| 6094da6c76 | |||
| 22c957d475 | |||
| 06158966f1 | |||
| 47d2e963be | |||
| 7403476489 | |||
| 981bd7da2a | |||
| bab5de8e8f | |||
| acf80ba7cc | |||
| abe2c8ce76 | |||
| 0b8de8087b | |||
| 34cb09b777 | |||
| ef202509f3 | |||
| cce2cb578f | |||
| 8d851435b6 | |||
| b1575eda3a | |||
| fc6d1177b7 | |||
| a66ee26187 | |||
| e2f2313eb5 | |||
| 7886b900f1 | |||
| b020e6b308 | |||
| 4de6896028 | |||
| f5aa292587 | |||
| 0cde504e80 | |||
| 7e3a4b59a2 | |||
| 36039ca0c3 | |||
| 8af6b723bf | |||
| 0895324553 | |||
| e0a69d4c0a | |||
| 198a30d964 | |||
| a163b185ab | |||
| 4fa39283f6 | |||
| ad249155ec | |||
| ec30cc4f21 | |||
| ca34fb9ea0 | |||
| f40554fb2e | |||
| cca04f3514 | |||
| 8ebe6ce7bb | |||
| 292ce3f484 | |||
| eaa5c530de | |||
| ebce1e1c41 | |||
| d3b6a6333e | |||
| c5c600bd7b | |||
| 078637130e | |||
| 3113e99c90 | |||
| 9fa495cf70 | |||
| 5a36841144 | |||
| b51d54a84c | |||
| a4a0ebf9db | |||
| a58f85d3ee | |||
| 3de1d2cfff | |||
| 75d45c4e49 | |||
| 0acb76c97f | |||
| 8e8c6c9f72 | |||
| 334ace9080 | |||
| 8ec00dcb66 | |||
| b7d717e47c | |||
| 9d3336002b | |||
| c5278a421a | |||
| 36360a6e8a | |||
| 169b328d41 | |||
| 1a01debe68 | |||
| 1cc8be0701 | |||
| bae4579d60 | |||
| fae2a00922 | |||
| 9ebb037fc2 | |||
| 5bb42bedec | |||
| e3d50804f7 | |||
| 14ea0ea3bc | |||
| 66130ad336 | |||
| a310415b09 | |||
| 10bac56551 | |||
| 32c6afba90 | |||
| 31d8bce383 | |||
| 2fb022fbc3 | |||
| 5911188211 | |||
| 9ccadcfe80 | |||
| 9ee46bbe91 | |||
| bc1ff4744b | |||
| 6043a90a71 | |||
| 0e62a7dc59 | |||
| 7a6b76f96e | |||
| a78293dd3f | |||
| 1ce9ba1ebc | |||
| 2f15ae988f | |||
| c02e95ff40 | |||
| 03c13021db | |||
| 01c0601d39 | |||
| 8bd5c6e04d | |||
| 76b310de9d | |||
| ee2886331e | |||
| ed1c918d9e | |||
| 18f5f85160 | |||
| 19dedf3d61 | |||
| efadfedbaa | |||
| 60e3195600 | |||
| db72e90504 | |||
| cf982e9818 | |||
| dccab5d20f | |||
| f5e4ebf2ba | |||
| 2bfdd02c2a | |||
| 8b97073e13 | |||
| d2fd78a0c9 | |||
| 59fd9d7517 | |||
| 5344d9beab | |||
| 127ce3c5d9 | |||
| 04604dae0d | |||
| 0fe1f25a9e | |||
| a5d34b435f | |||
| 67cf1d61e1 | |||
| 5549148039 | |||
| 0319ee3894 | |||
| 2bd3aa6b21 | |||
| 36d2aab945 | |||
| 0a4e95cc07 | |||
| b429a71660 | |||
| b33c61798c | |||
| 11a2c0249d | |||
| 86545a90d0 | |||
| 71261decf1 | |||
| f390ae53ba | |||
| 195cf273f8 | |||
| 3a1c7182e6 | |||
| c272338897 | |||
| 74edbd5df0 | |||
| c28fb2de4e | |||
| b343b18cd9 | |||
| 2224d1c0d3 | |||
| b42215a3c9 | |||
| 2115a9bf1a | |||
| 843ad18bf3 | |||
| ed4ba00b77 | |||
| 4a7aaa8914 |
+1
-1
Submodule 3rdparty updated: 6ece897f44...4e13abaebe
@@ -26,7 +26,8 @@ $success = true;
|
||||
|
||||
//Now delete
|
||||
foreach ($files as $file) {
|
||||
if (($dir === '' && $file === 'Shared') || !\OC\Files\Filesystem::unlink($dir . '/' . $file)) {
|
||||
if (\OC\Files\Filesystem::file_exists($dir . '/' . $file) &&
|
||||
!\OC\Files\Filesystem::unlink($dir . '/' . $file)) {
|
||||
$filesWithError .= $file . "\n";
|
||||
$success = false;
|
||||
}
|
||||
|
||||
@@ -19,10 +19,16 @@ if(\OC\Files\Filesystem::file_exists($target . '/' . $file)) {
|
||||
if ($target != '' || strtolower($file) != 'shared') {
|
||||
$targetFile = \OC\Files\Filesystem::normalizePath($target . '/' . $file);
|
||||
$sourceFile = \OC\Files\Filesystem::normalizePath($dir . '/' . $file);
|
||||
if(\OC\Files\Filesystem::rename($sourceFile, $targetFile)) {
|
||||
OCP\JSON::success(array("data" => array( "dir" => $dir, "files" => $file )));
|
||||
} else {
|
||||
OCP\JSON::error(array("data" => array( "message" => $l->t("Could not move %s", array($file)) )));
|
||||
try {
|
||||
if(\OC\Files\Filesystem::rename($sourceFile, $targetFile)) {
|
||||
OCP\JSON::success(array("data" => array( "dir" => $dir, "files" => $file )));
|
||||
} else {
|
||||
OCP\JSON::error(array("data" => array( "message" => $l->t("Could not move %s", array($file)) )));
|
||||
}
|
||||
} catch (\OCP\Files\NotPermittedException $e) {
|
||||
OCP\JSON::error(array("data" => array( "message" => $l->t("Permission denied") )));
|
||||
} catch (\Exception $e) {
|
||||
OCP\JSON::error(array("data" => array( "message" => $e->getMessage())));
|
||||
}
|
||||
}else{
|
||||
OCP\JSON::error(array("data" => array( "message" => $l->t("Could not move %s", array($file)) )));
|
||||
|
||||
@@ -93,7 +93,8 @@ if (\OC\Files\Filesystem::file_exists($target)) {
|
||||
}
|
||||
|
||||
if($source) {
|
||||
if(substr($source, 0, 8)!='https://' and substr($source, 0, 7)!='http://') {
|
||||
$httpHelper = \OC::$server->getHTTPHelper();
|
||||
if(!$httpHelper->isHTTPURL($source)) {
|
||||
OCP\JSON::error(array('data' => array('message' => $l10n->t('Not a valid source'))));
|
||||
exit();
|
||||
}
|
||||
@@ -104,10 +105,36 @@ if($source) {
|
||||
exit();
|
||||
}
|
||||
|
||||
$ctx = stream_context_create(null, array('notification' =>'progress'));
|
||||
$source = $httpHelper->getFinalLocationOfURL($source);
|
||||
|
||||
$ctx = stream_context_create(\OC::$server->getHTTPHelper()->getDefaultContextArray(), array('notification' =>'progress'));
|
||||
|
||||
$sourceStream=@fopen($source, 'rb', false, $ctx);
|
||||
$result = 0;
|
||||
if (is_resource($sourceStream)) {
|
||||
$meta = stream_get_meta_data($sourceStream);
|
||||
if (isset($meta['wrapper_data']) && is_array($meta['wrapper_data'])) {
|
||||
//check stream size
|
||||
$storageStats = \OCA\Files\Helper::buildFileStorageStatistics($dir);
|
||||
$freeSpace = $storageStats['freeSpace'];
|
||||
|
||||
foreach($meta['wrapper_data'] as $header) {
|
||||
list($name, $value) = explode(':', $header);
|
||||
if ('content-length' === strtolower(trim($name))) {
|
||||
$length = (int) trim($value);
|
||||
|
||||
if ($length > $freeSpace) {
|
||||
$delta = $length - $freeSpace;
|
||||
$humanDelta = OCP\Util::humanFileSize($delta);
|
||||
|
||||
$eventSource->send('error', array('message' => (string)$l10n->t('The file exceeds your quota by %s', array($humanDelta))));
|
||||
$eventSource->close();
|
||||
fclose($sourceStream);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$result=\OC\Files\Filesystem::file_put_contents($target, $sourceStream);
|
||||
}
|
||||
if($result) {
|
||||
|
||||
@@ -24,6 +24,8 @@ if (empty($_POST['dirToken'])) {
|
||||
// and the upload/file transfer code needs to be refactored into a utility method
|
||||
// that could be used there
|
||||
|
||||
\OC_User::setIncognitoMode(true);
|
||||
|
||||
// return only read permissions for public upload
|
||||
$allowedPermissions = OCP\PERMISSION_READ;
|
||||
$publicDirectory = !empty($_POST['subdir']) ? $_POST['subdir'] : '/';
|
||||
@@ -175,7 +177,7 @@ if (strpos($dir, '..') === false) {
|
||||
} catch(Exception $ex) {
|
||||
$error = $ex->getMessage();
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
// file already exists
|
||||
$meta = \OC\Files\Filesystem::getFileInfo($target);
|
||||
|
||||
@@ -53,7 +53,6 @@ $server->subscribeEvent('beforeMethod', function () use ($server, $objectTree) {
|
||||
$rootDir = new OC_Connector_Sabre_Directory($view, $rootInfo);
|
||||
$objectTree->init($rootDir, $view, $mountManager);
|
||||
|
||||
$server->addPlugin(new OC_Connector_Sabre_AbortedUploadDetectionPlugin($view));
|
||||
$server->addPlugin(new OC_Connector_Sabre_QuotaPlugin($view));
|
||||
}, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request
|
||||
|
||||
|
||||
@@ -77,7 +77,11 @@ class Scan extends Command {
|
||||
if (is_object($user)) {
|
||||
$user = $user->getUID();
|
||||
}
|
||||
$this->scanFiles($user, $output);
|
||||
if ($this->userManager->userExists($user)) {
|
||||
$this->scanFiles($user, $output);
|
||||
} else {
|
||||
$output->writeln("<error>Unknown user $user</error>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-10
@@ -152,16 +152,20 @@ table th .columntitle.name {
|
||||
padding-right: 80px;
|
||||
margin-left: 50px;
|
||||
}
|
||||
/* hover effect on sortable column */
|
||||
table th a.columntitle:hover {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.sort-indicator.hidden { visibility: hidden; }
|
||||
table th .sort-indicator {
|
||||
width: 10px;
|
||||
height: 8px;
|
||||
margin-left: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
table th:hover .sort-indicator.hidden {
|
||||
width: 10px;
|
||||
height: 8px;
|
||||
margin-left: 10px;
|
||||
visibility: visible;
|
||||
}
|
||||
table th, table td { border-bottom:1px solid #ddd; text-align:left; font-weight:normal; }
|
||||
table td {
|
||||
padding: 0 15px;
|
||||
@@ -345,14 +349,13 @@ table td.filename .uploadtext {
|
||||
#fileList tr td.filename>input[type="checkbox"] + label,
|
||||
.select-all + label {
|
||||
height: 50px;
|
||||
position: absolute;
|
||||
position: relative;
|
||||
width: 50px;
|
||||
z-index: 5;
|
||||
}
|
||||
#fileList tr td.filename>input[type="checkbox"]{
|
||||
/* sometimes checkbox height is bigger (KDE/Qt), so setting to absolute
|
||||
* to prevent it to increase the height */
|
||||
position: absolute;
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
}
|
||||
#fileList tr td.filename>input[type="checkbox"] + label {
|
||||
left: 0;
|
||||
@@ -367,7 +370,6 @@ table td.filename .uploadtext {
|
||||
left: 18px;
|
||||
}
|
||||
|
||||
|
||||
#fileList tr td.filename {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@@ -432,7 +434,6 @@ a.action>img {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
|
||||
#fileList a.action {
|
||||
display: inline;
|
||||
padding: 18px 8px;
|
||||
|
||||
@@ -137,3 +137,27 @@
|
||||
.oc-dialog .oc-dialog-buttonrow .cancel {
|
||||
float:left;
|
||||
}
|
||||
|
||||
.highlightUploaded {
|
||||
-webkit-animation: highlightAnimation 2s 1;
|
||||
-moz-animation: highlightAnimation 2s 1;
|
||||
-o-animation: highlightAnimation 2s 1;
|
||||
animation: highlightAnimation 2s 1;
|
||||
}
|
||||
|
||||
@-webkit-keyframes highlightAnimation {
|
||||
0% { background-color: rgba(255, 255, 140, 1); }
|
||||
100% { background-color: rgba(0, 0, 0, 0); }
|
||||
}
|
||||
@-moz-keyframes highlightAnimation {
|
||||
0% { background-color: rgba(255, 255, 140, 1); }
|
||||
100% { background-color: rgba(0, 0, 0, 0); }
|
||||
}
|
||||
@-o-keyframes highlightAnimation {
|
||||
0% { background-color: rgba(255, 255, 140, 1); }
|
||||
100% { background-color: rgba(0, 0, 0, 0); }
|
||||
}
|
||||
@keyframes highlightAnimation {
|
||||
0% { background-color: rgba(255, 255, 140, 1); }
|
||||
100% { background-color: rgba(0, 0, 0, 0); }
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ if(!\OC\Files\Filesystem::file_exists($filename)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$ftype=\OC\Files\Filesystem::getMimeType( $filename );
|
||||
$ftype=\OC_Helper::getSecureMimeType(\OC\Files\Filesystem::getMimeType( $filename ));
|
||||
|
||||
header('Content-Type:'.$ftype);
|
||||
OCP\Response::setContentDispositionHeader(basename($filename), 'attachment');
|
||||
|
||||
+35
-5
@@ -24,6 +24,7 @@
|
||||
initialize: function() {
|
||||
this.navigation = new OCA.Files.Navigation($('#app-navigation'));
|
||||
|
||||
var urlParams = OC.Util.History.parseUrlQuery();
|
||||
var fileActions = new OCA.Files.FileActions();
|
||||
// default actions
|
||||
fileActions.registerDefaultActions();
|
||||
@@ -32,9 +33,11 @@
|
||||
// regular actions
|
||||
fileActions.merge(OCA.Files.fileActions);
|
||||
|
||||
// in case apps would decide to register file actions later,
|
||||
// replace the global object with this one
|
||||
OCA.Files.fileActions = fileActions;
|
||||
this._onActionsUpdated = _.bind(this._onActionsUpdated, this);
|
||||
OCA.Files.fileActions.on('setDefault.app-files', this._onActionsUpdated);
|
||||
OCA.Files.fileActions.on('registerAction.app-files', this._onActionsUpdated);
|
||||
window.FileActions.on('setDefault.app-files', this._onActionsUpdated);
|
||||
window.FileActions.on('registerAction.app-files', this._onActionsUpdated);
|
||||
|
||||
this.files = OCA.Files.Files;
|
||||
|
||||
@@ -45,7 +48,8 @@
|
||||
dragOptions: dragOptions,
|
||||
folderDropOptions: folderDropOptions,
|
||||
fileActions: fileActions,
|
||||
allowLegacyActions: true
|
||||
allowLegacyActions: true,
|
||||
scrollTo: urlParams.scrollto
|
||||
}
|
||||
);
|
||||
this.files.initialize();
|
||||
@@ -56,7 +60,33 @@
|
||||
|
||||
this._setupEvents();
|
||||
// trigger URL change event handlers
|
||||
this._onPopState(OC.Util.History.parseUrlQuery());
|
||||
this._onPopState(urlParams);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the app
|
||||
*/
|
||||
destroy: function() {
|
||||
this.navigation = null;
|
||||
this.fileList.destroy();
|
||||
this.fileList = null;
|
||||
this.files = null;
|
||||
OCA.Files.fileActions.off('setDefault.app-files', this._onActionsUpdated);
|
||||
OCA.Files.fileActions.off('registerAction.app-files', this._onActionsUpdated);
|
||||
window.FileActions.off('setDefault.app-files', this._onActionsUpdated);
|
||||
window.FileActions.off('registerAction.app-files', this._onActionsUpdated);
|
||||
},
|
||||
|
||||
_onActionsUpdated: function(ev, newAction) {
|
||||
// forward new action to the file list
|
||||
if (ev.action) {
|
||||
this.fileList.fileActions.registerAction(ev.action);
|
||||
} else if (ev.defaultAction) {
|
||||
this.fileList.fileActions.setDefault(
|
||||
ev.defaultAction.mime,
|
||||
ev.defaultAction.name
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -233,7 +233,8 @@ OC.Upload = {
|
||||
data.originalFiles.selection = {
|
||||
uploads: [],
|
||||
filesToUpload: data.originalFiles.length,
|
||||
totalBytes: 0
|
||||
totalBytes: 0,
|
||||
biggestFileBytes: 0
|
||||
};
|
||||
}
|
||||
var selection = data.originalFiles.selection;
|
||||
@@ -273,13 +274,15 @@ OC.Upload = {
|
||||
|
||||
// add size
|
||||
selection.totalBytes += file.size;
|
||||
// update size of biggest file
|
||||
selection.biggestFileBytes = Math.max(selection.biggestFileBytes, file.size);
|
||||
|
||||
// check PHP upload limit
|
||||
if (selection.totalBytes > $('#upload_limit').val()) {
|
||||
// check PHP upload limit against biggest file
|
||||
if (selection.biggestFileBytes > $('#upload_limit').val()) {
|
||||
data.textStatus = 'sizeexceedlimit';
|
||||
data.errorThrown = t('files',
|
||||
'Total file size {size1} exceeds upload limit {size2}', {
|
||||
'size1': humanFileSize(selection.totalBytes),
|
||||
'size1': humanFileSize(selection.biggestFileBytes),
|
||||
'size2': humanFileSize($('#upload_limit').val())
|
||||
});
|
||||
}
|
||||
@@ -424,6 +427,14 @@ OC.Upload = {
|
||||
data.textStatus = 'servererror';
|
||||
data.errorThrown = result[0].data.message; // error message has been translated on server
|
||||
fu._trigger('fail', e, data);
|
||||
} else { // Successful upload
|
||||
// Checking that the uploaded file is the last one and contained in the current directory
|
||||
if (data.files[0] === data.originalFiles[data.originalFiles.length - 1] &&
|
||||
result[0].directory === FileList.getCurrentDirectory()) {
|
||||
// Scroll to the last uploaded file and highlight all of them
|
||||
var fileList = _.pluck(data.originalFiles, 'name');
|
||||
FileList.highlightFiles(fileList);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -618,7 +629,7 @@ OC.Upload = {
|
||||
},
|
||||
function(result) {
|
||||
if (result.status === 'success') {
|
||||
FileList.add(result.data, {hidden: hidden, animate: true});
|
||||
FileList.add(result.data, {hidden: hidden, animate: true, scrollTo: true});
|
||||
} else {
|
||||
OC.dialogs.alert(result.data.message, t('core', 'Could not create file'));
|
||||
}
|
||||
@@ -634,7 +645,7 @@ OC.Upload = {
|
||||
},
|
||||
function(result) {
|
||||
if (result.status === 'success') {
|
||||
FileList.add(result.data, {hidden: hidden, animate: true});
|
||||
FileList.add(result.data, {hidden: hidden, animate: true, scrollTo: true});
|
||||
} else {
|
||||
OC.dialogs.alert(result.data.message, t('core', 'Could not create folder'));
|
||||
}
|
||||
|
||||
@@ -23,48 +23,52 @@
|
||||
icons: {},
|
||||
currentFile: null,
|
||||
|
||||
/**
|
||||
* Dummy jquery element, for events
|
||||
*/
|
||||
$el: null,
|
||||
|
||||
/**
|
||||
* List of handlers to be notified whenever a register() or
|
||||
* setDefault() was called.
|
||||
*/
|
||||
_updateListeners: [],
|
||||
_updateListeners: {},
|
||||
|
||||
initialize: function() {
|
||||
this.clear();
|
||||
// abusing jquery for events until we get a real event lib
|
||||
this.$el = $('<div class="dummy-fileactions hidden"></div>');
|
||||
$('body').append(this.$el);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an update listener to be notified whenever register()
|
||||
* or setDefault() has been called.
|
||||
* Adds an event handler
|
||||
*
|
||||
* @param {String} eventName event name
|
||||
* @param Function callback
|
||||
*/
|
||||
addUpdateListener: function(callback) {
|
||||
if (!_.isFunction(callback)) {
|
||||
throw 'Argument passed to FileActions.addUpdateListener must be a function';
|
||||
}
|
||||
this._updateListeners.push(callback);
|
||||
on: function(eventName, callback) {
|
||||
this.$el.on(eventName, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes an update listener.
|
||||
* Removes an event handler
|
||||
*
|
||||
* @param {String} eventName event name
|
||||
* @param Function callback
|
||||
*/
|
||||
removeUpdateListener: function(callback) {
|
||||
if (!_.isFunction(callback)) {
|
||||
throw 'Argument passed to FileActions.removeUpdateListener must be a function';
|
||||
}
|
||||
this._updateListeners = _.without(this._updateListeners, callback);
|
||||
off: function(eventName, callback) {
|
||||
this.$el.off(eventName, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies the registered update listeners
|
||||
* Notifies the event handlers
|
||||
*
|
||||
* @param {String} eventName event name
|
||||
* @param {Object} data data
|
||||
*/
|
||||
_notifyUpdateListeners: function() {
|
||||
for (var i = 0; i < this._updateListeners.length; i++) {
|
||||
this._updateListeners[i](this);
|
||||
}
|
||||
_notifyUpdateListeners: function(eventName, data) {
|
||||
this.$el.trigger(new $.Event(eventName, data));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -87,21 +91,44 @@
|
||||
this.defaults = _.extend(this.defaults, fileActions.defaults);
|
||||
this.icons = _.extend(this.icons, fileActions.icons);
|
||||
},
|
||||
register: function (mime, name, permissions, icon, action, displayName) {
|
||||
/**
|
||||
* @deprecated use #registerAction() instead
|
||||
*/
|
||||
register: function(mime, name, permissions, icon, action, displayName) {
|
||||
return this.registerAction({
|
||||
name: name,
|
||||
mime: mime,
|
||||
permissions: permissions,
|
||||
icon: icon,
|
||||
actionHandler: action,
|
||||
displayName: displayName
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Register action
|
||||
*
|
||||
* @param {Object} action action object
|
||||
* @param {String} action.name identifier of the action
|
||||
* @param {String} action.displayName display name of the action, defaults
|
||||
* to the name given in action.name
|
||||
* @param {String} action.mime mime type
|
||||
* @param {int} action.permissions permissions
|
||||
* @param {(Function|String)} action.icon icon
|
||||
* @param {Function} action.actionHandler function that performs the action
|
||||
*/
|
||||
registerAction: function (action) {
|
||||
var mime = action.mime;
|
||||
var name = action.name;
|
||||
if (!this.actions[mime]) {
|
||||
this.actions[mime] = {};
|
||||
}
|
||||
if (!this.actions[mime][name]) {
|
||||
this.actions[mime][name] = {};
|
||||
}
|
||||
if (!displayName) {
|
||||
displayName = t('files', name);
|
||||
}
|
||||
this.actions[mime][name]['action'] = action;
|
||||
this.actions[mime][name]['permissions'] = permissions;
|
||||
this.actions[mime][name]['displayName'] = displayName;
|
||||
this.icons[name] = icon;
|
||||
this._notifyUpdateListeners();
|
||||
this.actions[mime][name] = {
|
||||
action: action.actionHandler,
|
||||
permissions: action.permissions,
|
||||
displayName: action.displayName || t('files', name)
|
||||
};
|
||||
this.icons[name] = action.icon;
|
||||
this._notifyUpdateListeners('registerAction', {action: action});
|
||||
},
|
||||
clear: function() {
|
||||
this.actions = {};
|
||||
@@ -112,7 +139,7 @@
|
||||
},
|
||||
setDefault: function (mime, name) {
|
||||
this.defaults[mime] = name;
|
||||
this._notifyUpdateListeners();
|
||||
this._notifyUpdateListeners('setDefault', {defaultAction: {mime: mime, name: name}});
|
||||
},
|
||||
get: function (mime, type, permissions) {
|
||||
var actions = this.getActions(mime, type, permissions);
|
||||
@@ -264,14 +291,20 @@
|
||||
if (actions['Delete']) {
|
||||
var img = self.icons['Delete'];
|
||||
var html;
|
||||
var mountType = $tr.attr('data-mounttype');
|
||||
var deleteTitle = t('files', 'Delete');
|
||||
if (mountType === 'external-root') {
|
||||
deleteTitle = t('files', 'Disconnect storage');
|
||||
} else if (mountType === 'shared-root') {
|
||||
deleteTitle = t('files', 'Unshare');
|
||||
} else if (fileList.id === 'trashbin') {
|
||||
deleteTitle = t('files', 'Delete permanently');
|
||||
}
|
||||
|
||||
if (img.call) {
|
||||
img = img(file);
|
||||
}
|
||||
if (typeof trashBinApp !== 'undefined' && trashBinApp) {
|
||||
html = '<a href="#" original-title="' + t('files', 'Delete permanently') + '" class="action delete delete-icon" />';
|
||||
} else {
|
||||
html = '<a href="#" original-title="' + t('files', 'Delete') + '" class="action delete delete-icon" />';
|
||||
}
|
||||
html = '<a href="#" original-title="' + escapeHTML(deleteTitle) + '" class="action delete delete-icon" />';
|
||||
var element = $(html);
|
||||
element.data('action', actions['Delete']);
|
||||
element.on('click', {a: null, elem: parent, actionFunc: actions['Delete'].action}, actionHandler);
|
||||
@@ -314,7 +347,7 @@
|
||||
});
|
||||
|
||||
this.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
|
||||
var dir = context.fileList.getCurrentDirectory();
|
||||
var dir = context.$file.attr('data-path') || context.fileList.getCurrentDirectory();
|
||||
if (dir !== '/') {
|
||||
dir = dir + '/';
|
||||
}
|
||||
|
||||
+147
-27
@@ -18,8 +18,8 @@
|
||||
this.initialize($el, options);
|
||||
};
|
||||
FileList.prototype = {
|
||||
SORT_INDICATOR_ASC_CLASS: 'icon-triangle-s',
|
||||
SORT_INDICATOR_DESC_CLASS: 'icon-triangle-n',
|
||||
SORT_INDICATOR_ASC_CLASS: 'icon-triangle-n',
|
||||
SORT_INDICATOR_DESC_CLASS: 'icon-triangle-s',
|
||||
|
||||
id: 'files',
|
||||
appName: t('files', 'Files'),
|
||||
@@ -103,9 +103,10 @@
|
||||
* @param $el container element with existing markup for the #controls
|
||||
* and a table
|
||||
* @param options map of options, see other parameters
|
||||
* @param scrollContainer scrollable container, defaults to $(window)
|
||||
* @param dragOptions drag options, disabled by default
|
||||
* @param folderDropOptions folder drop options, disabled by default
|
||||
* @param options.scrollContainer scrollable container, defaults to $(window)
|
||||
* @param options.dragOptions drag options, disabled by default
|
||||
* @param options.folderDropOptions folder drop options, disabled by default
|
||||
* @param options.scrollTo name of file to scroll to after the first load
|
||||
*/
|
||||
initialize: function($el, options) {
|
||||
var self = this;
|
||||
@@ -165,6 +166,12 @@
|
||||
this.setupUploadEvents();
|
||||
|
||||
this.$container.on('scroll', _.bind(this._onScroll, this));
|
||||
|
||||
if (options.scrollTo) {
|
||||
this.$fileList.one('updated', function() {
|
||||
self.scrollTo(options.scrollTo);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -172,7 +179,8 @@
|
||||
*/
|
||||
destroy: function() {
|
||||
// TODO: also unregister other event handlers
|
||||
this.fileActions.removeUpdateListener(this._onFileActionsUpdated);
|
||||
this.fileActions.off('registerAction', this._onFileActionsUpdated);
|
||||
this.fileActions.off('setDefault', this._onFileActionsUpdated);
|
||||
},
|
||||
|
||||
_initFileActions: function(fileActions) {
|
||||
@@ -182,7 +190,8 @@
|
||||
this.fileActions.registerDefaultActions();
|
||||
}
|
||||
this._onFileActionsUpdated = _.debounce(_.bind(this._onFileActionsUpdated, this), 100);
|
||||
this.fileActions.addUpdateListener(this._onFileActionsUpdated);
|
||||
this.fileActions.on('registerAction', this._onFileActionsUpdated);
|
||||
this.fileActions.on('setDefault', this._onFileActionsUpdated);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -336,7 +345,6 @@
|
||||
else {
|
||||
files = _.pluck(this.getSelectedFiles(), 'name');
|
||||
}
|
||||
OC.Notification.show(t('files','Your download is being prepared. This might take some time if the files are big.'));
|
||||
OC.redirect(this.getDownloadUrl(files, dir));
|
||||
return false;
|
||||
},
|
||||
@@ -369,7 +377,12 @@
|
||||
this.setSort(sort, (this._sortDirection === 'desc')?'asc':'desc');
|
||||
}
|
||||
else {
|
||||
this.setSort(sort, 'asc');
|
||||
if ( sort === 'name' ) { //default sorting of name is opposite to size and mtime
|
||||
this.setSort(sort, 'asc');
|
||||
}
|
||||
else {
|
||||
this.setSort(sort, 'desc');
|
||||
}
|
||||
}
|
||||
this.reload();
|
||||
}
|
||||
@@ -393,7 +406,7 @@
|
||||
* This appends/renders the next page of entries when reaching the bottom.
|
||||
*/
|
||||
_onScroll: function(e) {
|
||||
if (this.$container.scrollTop() + this.$container.height() > this.$el.height() - 100) {
|
||||
if (this.$container.scrollTop() + this.$container.height() > this.$el.height() - 300) {
|
||||
this._nextPage(true);
|
||||
}
|
||||
},
|
||||
@@ -598,6 +611,10 @@
|
||||
"data-permissions": fileData.permissions || this.getDirectoryPermissions()
|
||||
});
|
||||
|
||||
if (fileData.mountType) {
|
||||
tr.attr('data-mounttype', fileData.mountType);
|
||||
}
|
||||
|
||||
if (!_.isUndefined(path)) {
|
||||
tr.attr('data-path', path);
|
||||
}
|
||||
@@ -699,8 +716,10 @@
|
||||
*
|
||||
* @param fileData map of file attributes
|
||||
* @param options map of attributes:
|
||||
* - "updateSummary": true to update the summary after adding (default), false otherwise
|
||||
* - "silent": true to prevent firing events like "fileActionsReady"
|
||||
* @param options.updateSummary true to update the summary after adding (default), false otherwise
|
||||
* @param options.silent true to prevent firing events like "fileActionsReady"
|
||||
* @param options.animate true to animate preview loading (defaults to true here)
|
||||
* @param options.scrollTo true to automatically scroll to the file's location
|
||||
* @return new tr element (not appended to the table)
|
||||
*/
|
||||
add: function(fileData, options) {
|
||||
@@ -708,7 +727,7 @@
|
||||
var $tr;
|
||||
var $rows;
|
||||
var $insertionPoint;
|
||||
options = options || {};
|
||||
options = _.extend({animate: true}, options || {});
|
||||
|
||||
// there are three situations to cover:
|
||||
// 1) insertion point is visible on the current page
|
||||
@@ -749,6 +768,10 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (options.scrollTo) {
|
||||
this.scrollTo(fileData.name);
|
||||
}
|
||||
|
||||
// defaults to true if not defined
|
||||
if (typeof(options.updateSummary) === 'undefined' || !!options.updateSummary) {
|
||||
this.fileSummary.add(fileData, true);
|
||||
@@ -766,6 +789,7 @@
|
||||
* @param options map of attributes:
|
||||
* - "index" optional index at which to insert the element
|
||||
* - "updateSummary" true to update the summary after adding (default), false otherwise
|
||||
* - "animate" true to animate the preview rendering
|
||||
* @return new tr element (not appended to the table)
|
||||
*/
|
||||
_renderRow: function(fileData, options) {
|
||||
@@ -807,7 +831,7 @@
|
||||
|
||||
if (fileData.isPreviewAvailable) {
|
||||
// lazy load / newly inserted td ?
|
||||
if (!fileData.icon) {
|
||||
if (options.animate) {
|
||||
this.lazyLoadPreview({
|
||||
path: path + '/' + fileData.name,
|
||||
mime: mime,
|
||||
@@ -908,18 +932,29 @@
|
||||
this._sort = sort;
|
||||
this._sortDirection = (direction === 'desc')?'desc':'asc';
|
||||
this._sortComparator = comparator;
|
||||
|
||||
if (direction === 'desc') {
|
||||
this._sortComparator = function(fileInfo1, fileInfo2) {
|
||||
return -comparator(fileInfo1, fileInfo2);
|
||||
};
|
||||
}
|
||||
this.$el.find('thead th .sort-indicator')
|
||||
.removeClass(this.SORT_INDICATOR_ASC_CLASS + ' ' + this.SORT_INDICATOR_DESC_CLASS);
|
||||
.removeClass(this.SORT_INDICATOR_ASC_CLASS)
|
||||
.removeClass(this.SORT_INDICATOR_DESC_CLASS)
|
||||
.toggleClass('hidden', true)
|
||||
.addClass(this.SORT_INDICATOR_DESC_CLASS);
|
||||
|
||||
this.$el.find('thead th.column-' + sort + ' .sort-indicator')
|
||||
.removeClass(this.SORT_INDICATOR_ASC_CLASS)
|
||||
.removeClass(this.SORT_INDICATOR_DESC_CLASS)
|
||||
.toggleClass('hidden', false)
|
||||
.addClass(direction === 'desc' ? this.SORT_INDICATOR_DESC_CLASS : this.SORT_INDICATOR_ASC_CLASS);
|
||||
},
|
||||
|
||||
/**
|
||||
* @brief Reloads the file list using ajax call
|
||||
* Reloads the file list using ajax call
|
||||
*
|
||||
* @return ajax call object
|
||||
*/
|
||||
reload: function() {
|
||||
this._selectedFiles = {};
|
||||
@@ -945,6 +980,13 @@
|
||||
this.hideMask();
|
||||
|
||||
if (!result || result.status === 'error') {
|
||||
// if the error is not related to folder we're trying to load, reload the page to handle logout etc
|
||||
if (result.data.error === 'authentication_error' ||
|
||||
result.data.error === 'token_expired' ||
|
||||
result.data.error === 'application_not_enabled'
|
||||
) {
|
||||
OC.redirect(OC.generateUrl('apps/files'));
|
||||
}
|
||||
OC.Notification.show(result.data.message);
|
||||
return false;
|
||||
}
|
||||
@@ -968,7 +1010,7 @@
|
||||
}
|
||||
|
||||
this.setFiles(result.data.files);
|
||||
return true
|
||||
return true;
|
||||
},
|
||||
|
||||
updateStorageStatistics: function(force) {
|
||||
@@ -1291,6 +1333,10 @@
|
||||
if (!result || result.status === 'error') {
|
||||
OC.dialogs.alert(result.data.message, t('core', 'Could not rename file'));
|
||||
fileInfo = oldFileInfo;
|
||||
if (result.data.code === 'sourcenotfound') {
|
||||
self.remove(result.data.newname, {updateSummary: true});
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
fileInfo = result.data;
|
||||
@@ -1473,16 +1519,15 @@
|
||||
this.$table.removeClass('hidden');
|
||||
},
|
||||
scrollTo:function(file) {
|
||||
//scroll to and highlight preselected file
|
||||
var $scrollToRow = this.findFileEl(file);
|
||||
if ($scrollToRow.exists()) {
|
||||
$scrollToRow.addClass('searchresult');
|
||||
$(window).scrollTop($scrollToRow.position().top);
|
||||
//remove highlight when hovered over
|
||||
$scrollToRow.one('hover', function() {
|
||||
$scrollToRow.removeClass('searchresult');
|
||||
});
|
||||
if (!_.isArray(file)) {
|
||||
file = [file];
|
||||
}
|
||||
this.highlightFiles(file, function($tr) {
|
||||
$tr.addClass('searchresult');
|
||||
$tr.one('hover', function() {
|
||||
$tr.removeClass('searchresult');
|
||||
});
|
||||
});
|
||||
},
|
||||
filter:function(query) {
|
||||
this.$fileList.find('tr').each(function(i,e) {
|
||||
@@ -1566,7 +1611,7 @@
|
||||
numMatch=base.match(/\((\d+)\)/);
|
||||
var num=2;
|
||||
if (numMatch && numMatch.length>0) {
|
||||
num=parseInt(numMatch[numMatch.length-1])+1;
|
||||
num=parseInt(numMatch[numMatch.length-1], 10)+1;
|
||||
base=base.split('(');
|
||||
base.pop();
|
||||
base=$.trim(base.join('('));
|
||||
@@ -1581,6 +1626,18 @@
|
||||
return name;
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows a "permission denied" notification
|
||||
*/
|
||||
_showPermissionDeniedNotification: function() {
|
||||
var message = t('core', 'You don’t have permission to upload or create files here');
|
||||
OC.Notification.show(message);
|
||||
//hide notification after 10 sec
|
||||
setTimeout(function() {
|
||||
OC.Notification.hide();
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
/**
|
||||
* Setup file upload events related to the file-upload plugin
|
||||
*/
|
||||
@@ -1612,6 +1669,12 @@
|
||||
// remember as context
|
||||
data.context = dropTarget;
|
||||
|
||||
// if permissions are specified, only allow if create permission is there
|
||||
var permissions = dropTarget.data('permissions');
|
||||
if (!_.isUndefined(permissions) && (permissions & OC.PERMISSION_CREATE) === 0) {
|
||||
self._showPermissionDeniedNotification();
|
||||
return false;
|
||||
}
|
||||
var dir = dropTarget.data('file');
|
||||
// if from file list, need to prepend parent dir
|
||||
if (dir) {
|
||||
@@ -1636,6 +1699,7 @@
|
||||
// cancel uploads to current dir if no permission
|
||||
var isCreatable = (self.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0;
|
||||
if (!isCreatable) {
|
||||
self._showPermissionDeniedNotification();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1806,6 +1870,62 @@
|
||||
self.updateStorageStatistics();
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Scroll to the last file of the given list
|
||||
* Highlight the list of files
|
||||
* @param files array of filenames,
|
||||
* @param {Function} [highlightFunction] optional function
|
||||
* to be called after the scrolling is finished
|
||||
*/
|
||||
highlightFiles: function(files, highlightFunction) {
|
||||
// Detection of the uploaded element
|
||||
var filename = files[files.length - 1];
|
||||
var $fileRow = this.findFileEl(filename);
|
||||
|
||||
while(!$fileRow.exists() && this._nextPage(false) !== false) { // Checking element existence
|
||||
$fileRow = this.findFileEl(filename);
|
||||
}
|
||||
|
||||
if (!$fileRow.exists()) { // Element not present in the file list
|
||||
return;
|
||||
}
|
||||
|
||||
var currentOffset = this.$container.scrollTop();
|
||||
var additionalOffset = this.$el.find("#controls").height()+this.$el.find("#controls").offset().top;
|
||||
|
||||
// Animation
|
||||
var _this = this;
|
||||
this.$container.animate({
|
||||
// Scrolling to the top of the new element
|
||||
scrollTop: currentOffset + $fileRow.offset().top - $fileRow.height() * 2 - additionalOffset
|
||||
}, {
|
||||
duration: 500,
|
||||
complete: function() {
|
||||
// Highlighting function
|
||||
var highlightRow = highlightFunction;
|
||||
|
||||
if (!highlightRow) {
|
||||
highlightRow = function($fileRow) {
|
||||
$fileRow.addClass("highlightUploaded");
|
||||
setTimeout(function() {
|
||||
$fileRow.removeClass("highlightUploaded");
|
||||
}, 2500);
|
||||
};
|
||||
}
|
||||
|
||||
// Loop over uploaded files
|
||||
for(var i=0; i<files.length; i++) {
|
||||
var $fileRow = _this.findFileEl(files[i]);
|
||||
|
||||
if($fileRow.length !== 0) { // Checking element existence
|
||||
highlightRow($fileRow);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -433,7 +433,12 @@ var folderDropOptions = {
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetPath = FileList.getCurrentDirectory() + '/' + $(this).closest('tr').data('file');
|
||||
var $tr = $(this).closest('tr');
|
||||
if (($tr.data('permissions') & OC.PERMISSION_CREATE) === 0) {
|
||||
FileList._showPermissionDeniedNotification();
|
||||
return false;
|
||||
}
|
||||
var targetPath = FileList.getCurrentDirectory() + '/' + $tr.data('file');
|
||||
|
||||
var files = FileList.getSelectedFiles();
|
||||
if (files.length === 0) {
|
||||
|
||||
+14
-4
@@ -71,15 +71,25 @@ class App {
|
||||
'data' => NULL
|
||||
);
|
||||
|
||||
$normalizedOldPath = \OC\Files\Filesystem::normalizePath($dir . '/' . $oldname);
|
||||
$normalizedNewPath = \OC\Files\Filesystem::normalizePath($dir . '/' . $newname);
|
||||
|
||||
// rename to non-existing folder is denied
|
||||
if (!$this->view->file_exists($dir)) {
|
||||
if (!$this->view->file_exists($normalizedOldPath)) {
|
||||
$result['data'] = array(
|
||||
'message' => $this->l10n->t('%s could not be renamed as it has been deleted', array($oldname)),
|
||||
'code' => 'sourcenotfound',
|
||||
'oldname' => $oldname,
|
||||
'newname' => $newname,
|
||||
);
|
||||
}else if (!$this->view->file_exists($dir)) {
|
||||
$result['data'] = array('message' => (string)$this->l10n->t(
|
||||
'The target folder has been moved or deleted.',
|
||||
array($dir)),
|
||||
'code' => 'targetnotfound'
|
||||
);
|
||||
// rename to existing file is denied
|
||||
} else if ($this->view->file_exists($dir . '/' . $newname)) {
|
||||
} else if ($this->view->file_exists($normalizedNewPath)) {
|
||||
|
||||
$result['data'] = array(
|
||||
'message' => $this->l10n->t(
|
||||
@@ -90,10 +100,10 @@ class App {
|
||||
// rename to "." is denied
|
||||
$newname !== '.' and
|
||||
// THEN try to rename
|
||||
$this->view->rename($dir . '/' . $oldname, $dir . '/' . $newname)
|
||||
$this->view->rename($normalizedOldPath, $normalizedNewPath)
|
||||
) {
|
||||
// successful rename
|
||||
$meta = $this->view->getFileInfo($dir . '/' . $newname);
|
||||
$meta = $this->view->getFileInfo($normalizedNewPath);
|
||||
$fileinfo = \OCA\Files\Helper::formatFileInfo($meta);
|
||||
$result['success'] = true;
|
||||
$result['data'] = $fileinfo;
|
||||
|
||||
@@ -37,6 +37,7 @@ class Helper
|
||||
public static function determineIcon($file) {
|
||||
if($file['type'] === 'dir') {
|
||||
$icon = \OC_Helper::mimetypeIcon('dir');
|
||||
// TODO: move this part to the client side, using mountType
|
||||
if ($file->isShared()) {
|
||||
$icon = \OC_Helper::mimetypeIcon('dir-shared');
|
||||
} elseif ($file->isMounted()) {
|
||||
@@ -125,6 +126,18 @@ class Helper
|
||||
if (isset($i['is_share_mount_point'])) {
|
||||
$entry['isShareMountPoint'] = $i['is_share_mount_point'];
|
||||
}
|
||||
$mountType = null;
|
||||
if ($i->isShared()) {
|
||||
$mountType = 'shared';
|
||||
} else if ($i->isMounted()) {
|
||||
$mountType = 'external';
|
||||
}
|
||||
if ($mountType !== null) {
|
||||
if ($i->getInternalPath() === '') {
|
||||
$mountType .= '-root';
|
||||
}
|
||||
$entry['mountType'] = $mountType;
|
||||
}
|
||||
return $entry;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,10 +73,14 @@ class Test_OC_Files_App_Rename extends \PHPUnit_Framework_TestCase {
|
||||
$oldname = 'oldname';
|
||||
$newname = 'newname';
|
||||
|
||||
$this->viewMock->expects($this->at(0))
|
||||
$this->viewMock->expects($this->any())
|
||||
->method('file_exists')
|
||||
->with('/')
|
||||
->will($this->returnValue(true));
|
||||
->with($this->anything())
|
||||
->will($this->returnValueMap(array(
|
||||
array('/', true),
|
||||
array('/oldname', true)
|
||||
)));
|
||||
|
||||
|
||||
$this->viewMock->expects($this->any())
|
||||
->method('getFileInfo')
|
||||
@@ -119,7 +123,7 @@ class Test_OC_Files_App_Rename extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
$this->viewMock->expects($this->at(0))
|
||||
->method('file_exists')
|
||||
->with('/unexist')
|
||||
->with('/unexist/oldname')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->viewMock->expects($this->any())
|
||||
@@ -136,6 +140,40 @@ class Test_OC_Files_App_Rename extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
$result = $this->files->rename($dir, $oldname, $newname);
|
||||
|
||||
$this->assertFalse($result['success']);
|
||||
$this->assertEquals('sourcenotfound', $result['data']['code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test move to a folder that doesn't exist any more
|
||||
*/
|
||||
function testRenameToNonExistingFolder() {
|
||||
$dir = '/';
|
||||
$oldname = 'oldname';
|
||||
$newname = '/unexist/newname';
|
||||
|
||||
$this->viewMock->expects($this->any())
|
||||
->method('file_exists')
|
||||
->with($this->anything())
|
||||
->will($this->returnValueMap(array(
|
||||
array('/oldname', true),
|
||||
array('/unexist', false)
|
||||
)));
|
||||
|
||||
$this->viewMock->expects($this->any())
|
||||
->method('getFileInfo')
|
||||
->will($this->returnValue(array(
|
||||
'fileid' => 123,
|
||||
'type' => 'dir',
|
||||
'mimetype' => 'httpd/unix-directory',
|
||||
'size' => 18,
|
||||
'etag' => 'abcdef',
|
||||
'directory' => '/unexist',
|
||||
'name' => 'new_name',
|
||||
)));
|
||||
|
||||
$result = $this->files->rename($dir, $oldname, $newname);
|
||||
|
||||
$this->assertFalse($result['success']);
|
||||
$this->assertEquals('targetnotfound', $result['data']['code']);
|
||||
}
|
||||
|
||||
@@ -52,9 +52,7 @@ describe('OCA.Files.App tests', function() {
|
||||
App.initialize();
|
||||
});
|
||||
afterEach(function() {
|
||||
App.navigation = null;
|
||||
App.fileList = null;
|
||||
App.files = null;
|
||||
App.destroy();
|
||||
|
||||
pushStateStub.restore();
|
||||
parseUrlQueryStub.restore();
|
||||
|
||||
@@ -193,6 +193,156 @@ describe('OCA.Files.FileActions tests', function() {
|
||||
context = actionStub.getCall(0).args[1];
|
||||
expect(context.dir).toEqual('/somepath');
|
||||
});
|
||||
describe('merging', function() {
|
||||
var $tr;
|
||||
beforeEach(function() {
|
||||
var fileData = {
|
||||
id: 18,
|
||||
type: 'file',
|
||||
name: 'testName.txt',
|
||||
path: '/anotherpath/there',
|
||||
mimetype: 'text/plain',
|
||||
size: '1234',
|
||||
etag: 'a01234c',
|
||||
mtime: '123456'
|
||||
};
|
||||
$tr = fileList.add(fileData);
|
||||
});
|
||||
afterEach(function() {
|
||||
$tr = null;
|
||||
});
|
||||
it('copies all actions to target file actions', function() {
|
||||
var actions1 = new OCA.Files.FileActions();
|
||||
var actions2 = new OCA.Files.FileActions();
|
||||
var actionStub1 = sinon.stub();
|
||||
var actionStub2 = sinon.stub();
|
||||
actions1.register(
|
||||
'all',
|
||||
'Test',
|
||||
OC.PERMISSION_READ,
|
||||
OC.imagePath('core', 'actions/test'),
|
||||
actionStub1
|
||||
);
|
||||
actions2.register(
|
||||
'all',
|
||||
'Test2',
|
||||
OC.PERMISSION_READ,
|
||||
OC.imagePath('core', 'actions/test'),
|
||||
actionStub2
|
||||
);
|
||||
actions2.merge(actions1);
|
||||
|
||||
actions2.display($tr.find('td.filename'), true, fileList);
|
||||
|
||||
expect($tr.find('.action-test').length).toEqual(1);
|
||||
expect($tr.find('.action-test2').length).toEqual(1);
|
||||
|
||||
$tr.find('.action-test').click();
|
||||
expect(actionStub1.calledOnce).toEqual(true);
|
||||
expect(actionStub2.notCalled).toEqual(true);
|
||||
|
||||
actionStub1.reset();
|
||||
|
||||
$tr.find('.action-test2').click();
|
||||
expect(actionStub1.notCalled).toEqual(true);
|
||||
expect(actionStub2.calledOnce).toEqual(true);
|
||||
});
|
||||
it('overrides existing actions on merge', function() {
|
||||
var actions1 = new OCA.Files.FileActions();
|
||||
var actions2 = new OCA.Files.FileActions();
|
||||
var actionStub1 = sinon.stub();
|
||||
var actionStub2 = sinon.stub();
|
||||
actions1.register(
|
||||
'all',
|
||||
'Test',
|
||||
OC.PERMISSION_READ,
|
||||
OC.imagePath('core', 'actions/test'),
|
||||
actionStub1
|
||||
);
|
||||
actions2.register(
|
||||
'all',
|
||||
'Test', // override
|
||||
OC.PERMISSION_READ,
|
||||
OC.imagePath('core', 'actions/test'),
|
||||
actionStub2
|
||||
);
|
||||
actions1.merge(actions2);
|
||||
|
||||
actions1.display($tr.find('td.filename'), true, fileList);
|
||||
|
||||
expect($tr.find('.action-test').length).toEqual(1);
|
||||
|
||||
$tr.find('.action-test').click();
|
||||
expect(actionStub1.notCalled).toEqual(true);
|
||||
expect(actionStub2.calledOnce).toEqual(true);
|
||||
});
|
||||
it('overrides existing action when calling register after merge', function() {
|
||||
var actions1 = new OCA.Files.FileActions();
|
||||
var actions2 = new OCA.Files.FileActions();
|
||||
var actionStub1 = sinon.stub();
|
||||
var actionStub2 = sinon.stub();
|
||||
actions1.register(
|
||||
'all',
|
||||
'Test',
|
||||
OC.PERMISSION_READ,
|
||||
OC.imagePath('core', 'actions/test'),
|
||||
actionStub1
|
||||
);
|
||||
|
||||
actions1.merge(actions2);
|
||||
|
||||
// late override
|
||||
actions1.register(
|
||||
'all',
|
||||
'Test', // override
|
||||
OC.PERMISSION_READ,
|
||||
OC.imagePath('core', 'actions/test'),
|
||||
actionStub2
|
||||
);
|
||||
|
||||
actions1.display($tr.find('td.filename'), true, fileList);
|
||||
|
||||
expect($tr.find('.action-test').length).toEqual(1);
|
||||
|
||||
$tr.find('.action-test').click();
|
||||
expect(actionStub1.notCalled).toEqual(true);
|
||||
expect(actionStub2.calledOnce).toEqual(true);
|
||||
});
|
||||
it('leaves original file actions untouched (clean copy)', function() {
|
||||
var actions1 = new OCA.Files.FileActions();
|
||||
var actions2 = new OCA.Files.FileActions();
|
||||
var actionStub1 = sinon.stub();
|
||||
var actionStub2 = sinon.stub();
|
||||
actions1.register(
|
||||
'all',
|
||||
'Test',
|
||||
OC.PERMISSION_READ,
|
||||
OC.imagePath('core', 'actions/test'),
|
||||
actionStub1
|
||||
);
|
||||
|
||||
// copy the Test action to actions2
|
||||
actions2.merge(actions1);
|
||||
|
||||
// late override
|
||||
actions2.register(
|
||||
'all',
|
||||
'Test', // override
|
||||
OC.PERMISSION_READ,
|
||||
OC.imagePath('core', 'actions/test'),
|
||||
actionStub2
|
||||
);
|
||||
|
||||
// check if original actions still call the correct handler
|
||||
actions1.display($tr.find('td.filename'), true, fileList);
|
||||
|
||||
expect($tr.find('.action-test').length).toEqual(1);
|
||||
|
||||
$tr.find('.action-test').click();
|
||||
expect(actionStub1.calledOnce).toEqual(true);
|
||||
expect(actionStub2.notCalled).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('events', function() {
|
||||
var clock;
|
||||
beforeEach(function() {
|
||||
@@ -204,7 +354,7 @@ describe('OCA.Files.FileActions tests', function() {
|
||||
it('notifies update event handlers once after multiple changes', function() {
|
||||
var actionStub = sinon.stub();
|
||||
var handler = sinon.stub();
|
||||
FileActions.addUpdateListener(handler);
|
||||
FileActions.on('registerAction', handler);
|
||||
FileActions.register(
|
||||
'all',
|
||||
'Test',
|
||||
@@ -224,8 +374,8 @@ describe('OCA.Files.FileActions tests', function() {
|
||||
it('does not notifies update event handlers after unregistering', function() {
|
||||
var actionStub = sinon.stub();
|
||||
var handler = sinon.stub();
|
||||
FileActions.addUpdateListener(handler);
|
||||
FileActions.removeUpdateListener(handler);
|
||||
FileActions.on('registerAction', handler);
|
||||
FileActions.off('registerAction', handler);
|
||||
FileActions.register(
|
||||
'all',
|
||||
'Test',
|
||||
|
||||
@@ -938,16 +938,6 @@ describe('OCA.Files.FileList tests', function() {
|
||||
describe('file previews', function() {
|
||||
var previewLoadStub;
|
||||
|
||||
function getImageUrl($el) {
|
||||
// might be slightly different cross-browser
|
||||
var url = $el.css('background-image');
|
||||
var r = url.match(/url\(['"]?([^'")]*)['"]?\)/);
|
||||
if (!r) {
|
||||
return url;
|
||||
}
|
||||
return r[1];
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
previewLoadStub = sinon.stub(OCA.Files.FileList.prototype, 'lazyLoadPreview');
|
||||
});
|
||||
@@ -961,7 +951,7 @@ describe('OCA.Files.FileList tests', function() {
|
||||
};
|
||||
var $tr = fileList.add(fileData);
|
||||
var $td = $tr.find('td.filename');
|
||||
expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
|
||||
expect(OC.TestUtil.getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
|
||||
expect(previewLoadStub.notCalled).toEqual(true);
|
||||
});
|
||||
it('renders default icon for dir when none provided and no preview is available', function() {
|
||||
@@ -971,7 +961,7 @@ describe('OCA.Files.FileList tests', function() {
|
||||
};
|
||||
var $tr = fileList.add(fileData);
|
||||
var $td = $tr.find('td.filename');
|
||||
expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/folder.svg');
|
||||
expect(OC.TestUtil.getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/folder.svg');
|
||||
expect(previewLoadStub.notCalled).toEqual(true);
|
||||
});
|
||||
it('renders provided icon for file when provided', function() {
|
||||
@@ -982,7 +972,7 @@ describe('OCA.Files.FileList tests', function() {
|
||||
};
|
||||
var $tr = fileList.add(fileData);
|
||||
var $td = $tr.find('td.filename');
|
||||
expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/application-pdf.svg');
|
||||
expect(OC.TestUtil.getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/application-pdf.svg');
|
||||
expect(previewLoadStub.notCalled).toEqual(true);
|
||||
});
|
||||
it('renders preview when no icon was provided and preview is available', function() {
|
||||
@@ -993,11 +983,11 @@ describe('OCA.Files.FileList tests', function() {
|
||||
};
|
||||
var $tr = fileList.add(fileData);
|
||||
var $td = $tr.find('td.filename');
|
||||
expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
|
||||
expect(OC.TestUtil.getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
|
||||
expect(previewLoadStub.calledOnce).toEqual(true);
|
||||
// third argument is callback
|
||||
previewLoadStub.getCall(0).args[0].callback(OC.webroot + '/somepath.png');
|
||||
expect(getImageUrl($td)).toEqual(OC.webroot + '/somepath.png');
|
||||
expect(OC.TestUtil.getImageUrl($td)).toEqual(OC.webroot + '/somepath.png');
|
||||
});
|
||||
it('renders default file type icon when no icon was provided and no preview is available', function() {
|
||||
var fileData = {
|
||||
@@ -1007,7 +997,7 @@ describe('OCA.Files.FileList tests', function() {
|
||||
};
|
||||
var $tr = fileList.add(fileData);
|
||||
var $td = $tr.find('td.filename');
|
||||
expect(getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
|
||||
expect(OC.TestUtil.getImageUrl($td)).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
|
||||
expect(previewLoadStub.notCalled).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -1696,7 +1686,7 @@ describe('OCA.Files.FileList tests', function() {
|
||||
var url = fakeServer.requests[0].url;
|
||||
var query = OC.parseQueryString(url.substr(url.indexOf('?') + 1));
|
||||
expect(query.sort).toEqual('size');
|
||||
expect(query.sortdirection).toEqual('asc');
|
||||
expect(query.sortdirection).toEqual('desc');
|
||||
});
|
||||
it('Toggles sort direction when clicking on already sorted column', function() {
|
||||
fileList.$el.find('.column-name .columntitle').click();
|
||||
@@ -1710,37 +1700,51 @@ describe('OCA.Files.FileList tests', function() {
|
||||
var ASC_CLASS = fileList.SORT_INDICATOR_ASC_CLASS;
|
||||
var DESC_CLASS = fileList.SORT_INDICATOR_DESC_CLASS;
|
||||
fileList.$el.find('.column-size .columntitle').click();
|
||||
// moves triangle to size column
|
||||
// moves triangle to size column, check indicator on name is hidden
|
||||
expect(
|
||||
fileList.$el.find('.column-name .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS)
|
||||
).toEqual(false);
|
||||
expect(
|
||||
fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS)
|
||||
fileList.$el.find('.column-name .sort-indicator').hasClass('hidden')
|
||||
).toEqual(true);
|
||||
|
||||
// click again on size column, reverses direction
|
||||
fileList.$el.find('.column-size .columntitle').click();
|
||||
// check indicator on size is visible and defaults to descending
|
||||
expect(
|
||||
fileList.$el.find('.column-size .sort-indicator').hasClass('hidden')
|
||||
).toEqual(false);
|
||||
expect(
|
||||
fileList.$el.find('.column-size .sort-indicator').hasClass(DESC_CLASS)
|
||||
).toEqual(true);
|
||||
|
||||
// click again on size column, reverses direction
|
||||
fileList.$el.find('.column-size .columntitle').click();
|
||||
expect(
|
||||
fileList.$el.find('.column-size .sort-indicator').hasClass('hidden')
|
||||
).toEqual(false);
|
||||
expect(
|
||||
fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS)
|
||||
).toEqual(true);
|
||||
|
||||
// click again on size column, reverses direction
|
||||
fileList.$el.find('.column-size .columntitle').click();
|
||||
expect(
|
||||
fileList.$el.find('.column-size .sort-indicator').hasClass('hidden')
|
||||
).toEqual(false);
|
||||
expect(
|
||||
fileList.$el.find('.column-size .sort-indicator').hasClass(DESC_CLASS)
|
||||
).toEqual(true);
|
||||
|
||||
// click on mtime column, moves indicator there
|
||||
fileList.$el.find('.column-mtime .columntitle').click();
|
||||
expect(
|
||||
fileList.$el.find('.column-size .sort-indicator').hasClass(ASC_CLASS + ' ' + DESC_CLASS)
|
||||
fileList.$el.find('.column-size .sort-indicator').hasClass('hidden')
|
||||
).toEqual(true);
|
||||
expect(
|
||||
fileList.$el.find('.column-mtime .sort-indicator').hasClass('hidden')
|
||||
).toEqual(false);
|
||||
expect(
|
||||
fileList.$el.find('.column-mtime .sort-indicator').hasClass(ASC_CLASS)
|
||||
fileList.$el.find('.column-mtime .sort-indicator').hasClass(DESC_CLASS)
|
||||
).toEqual(true);
|
||||
});
|
||||
it('Uses correct sort comparator when inserting files', function() {
|
||||
testFiles.sort(OCA.Files.FileList.Comparators.size);
|
||||
testFiles.reverse(); //default is descending
|
||||
// this will make it reload the testFiles with the correct sorting
|
||||
fileList.$el.find('.column-size .columntitle').click();
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
@@ -1764,17 +1768,16 @@ describe('OCA.Files.FileList tests', function() {
|
||||
etag: '999'
|
||||
};
|
||||
fileList.add(newFileData);
|
||||
expect(fileList.findFileEl('Three.pdf').index()).toEqual(0);
|
||||
expect(fileList.findFileEl('new file.txt').index()).toEqual(1);
|
||||
expect(fileList.findFileEl('Two.jpg').index()).toEqual(2);
|
||||
expect(fileList.findFileEl('somedir').index()).toEqual(3);
|
||||
expect(fileList.findFileEl('One.txt').index()).toEqual(4);
|
||||
expect(fileList.files.length).toEqual(5);
|
||||
expect(fileList.$fileList.find('tr').length).toEqual(5);
|
||||
expect(fileList.findFileEl('One.txt').index()).toEqual(0);
|
||||
expect(fileList.findFileEl('somedir').index()).toEqual(1);
|
||||
expect(fileList.findFileEl('Two.jpg').index()).toEqual(2);
|
||||
expect(fileList.findFileEl('new file.txt').index()).toEqual(3);
|
||||
expect(fileList.findFileEl('Three.pdf').index()).toEqual(4);
|
||||
});
|
||||
it('Uses correct reversed sort comparator when inserting files', function() {
|
||||
testFiles.sort(OCA.Files.FileList.Comparators.size);
|
||||
testFiles.reverse();
|
||||
// this will make it reload the testFiles with the correct sorting
|
||||
fileList.$el.find('.column-size .columntitle').click();
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
@@ -1811,13 +1814,13 @@ describe('OCA.Files.FileList tests', function() {
|
||||
etag: '999'
|
||||
};
|
||||
fileList.add(newFileData);
|
||||
expect(fileList.findFileEl('One.txt').index()).toEqual(0);
|
||||
expect(fileList.findFileEl('somedir').index()).toEqual(1);
|
||||
expect(fileList.findFileEl('Two.jpg').index()).toEqual(2);
|
||||
expect(fileList.findFileEl('new file.txt').index()).toEqual(3);
|
||||
expect(fileList.findFileEl('Three.pdf').index()).toEqual(4);
|
||||
expect(fileList.files.length).toEqual(5);
|
||||
expect(fileList.$fileList.find('tr').length).toEqual(5);
|
||||
expect(fileList.findFileEl('One.txt').index()).toEqual(4);
|
||||
expect(fileList.findFileEl('somedir').index()).toEqual(3);
|
||||
expect(fileList.findFileEl('Two.jpg').index()).toEqual(2);
|
||||
expect(fileList.findFileEl('new file.txt').index()).toEqual(1);
|
||||
expect(fileList.findFileEl('Three.pdf').index()).toEqual(0);
|
||||
});
|
||||
});
|
||||
/**
|
||||
@@ -1832,7 +1835,6 @@ describe('OCA.Files.FileList tests', function() {
|
||||
// but it makes it possible to simulate the event triggering to
|
||||
// test the response of the handlers
|
||||
$uploader = $('#file_upload_start');
|
||||
fileList.setupUploadEvents();
|
||||
fileList.setFiles(testFiles);
|
||||
});
|
||||
|
||||
@@ -1909,6 +1911,16 @@ describe('OCA.Files.FileList tests', function() {
|
||||
ev = dropOn(fileList.$fileList.find('th:first'));
|
||||
|
||||
expect(ev.result).toEqual(false);
|
||||
expect(notificationStub.calledOnce).toEqual(true);
|
||||
});
|
||||
it('drop on an folder does not trigger upload if no upload permission on that folder', function() {
|
||||
var $tr = fileList.findFileEl('somedir');
|
||||
var ev;
|
||||
$tr.data('permissions', OC.PERMISSION_READ);
|
||||
ev = dropOn($tr);
|
||||
|
||||
expect(ev.result).toEqual(false);
|
||||
expect(notificationStub.calledOnce).toEqual(true);
|
||||
});
|
||||
it('drop on a file row inside the table triggers upload to current folder', function() {
|
||||
var ev;
|
||||
@@ -1933,4 +1945,30 @@ describe('OCA.Files.FileList tests', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Handeling errors', function () {
|
||||
beforeEach(function () {
|
||||
redirectStub = sinon.stub(OC, 'redirect');
|
||||
|
||||
fileList = new OCA.Files.FileList($('#app-content-files'));
|
||||
});
|
||||
afterEach(function () {
|
||||
fileList = undefined;
|
||||
|
||||
redirectStub.restore();
|
||||
});
|
||||
it('reloads the page on authentication errors', function () {
|
||||
fileList.reload();
|
||||
fakeServer.requests[0].respond(
|
||||
200,
|
||||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify({
|
||||
status: 'error',
|
||||
data: {
|
||||
'error': 'authentication_error'
|
||||
}
|
||||
})
|
||||
);
|
||||
expect(redirectStub.calledWith(OC.generateUrl('apps/files'))).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,11 +35,12 @@ $encryptedRecoveryKey = $view->file_get_contents($keyPath);
|
||||
$decryptedRecoveryKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedRecoveryKey, $oldPassword);
|
||||
|
||||
if ($decryptedRecoveryKey) {
|
||||
|
||||
$encryptedRecoveryKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedRecoveryKey, $newPassword);
|
||||
$view->file_put_contents($keyPath, $encryptedRecoveryKey);
|
||||
|
||||
$return = true;
|
||||
$cipher = \OCA\Encryption\Helper::getCipher();
|
||||
$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedRecoveryKey, $newPassword, $cipher);
|
||||
if ($encryptedKey) {
|
||||
\OCA\Encryption\Keymanager::setPrivateSystemKey($encryptedKey, $keyId . '.private.key');
|
||||
$return = true;
|
||||
}
|
||||
}
|
||||
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
@@ -35,13 +35,13 @@ $encryptedKey = $view->file_get_contents($keyPath);
|
||||
$decryptedKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedKey, $oldPassword);
|
||||
|
||||
if ($decryptedKey) {
|
||||
|
||||
$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedKey, $newPassword);
|
||||
$view->file_put_contents($keyPath, $encryptedKey);
|
||||
|
||||
$session->setPrivateKey($decryptedKey);
|
||||
|
||||
$return = true;
|
||||
$cipher = \OCA\Encryption\Helper::getCipher();
|
||||
$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedKey, $newPassword, $cipher);
|
||||
if ($encryptedKey) {
|
||||
\OCA\Encryption\Keymanager::setPrivateKey($encryptedKey, $user);
|
||||
$session->setPrivateKey($decryptedKey);
|
||||
$return = true;
|
||||
}
|
||||
}
|
||||
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
@@ -10,6 +10,10 @@ OC::$CLASSPATH['OCA\Encryption\Session'] = 'files_encryption/lib/session.php';
|
||||
OC::$CLASSPATH['OCA\Encryption\Capabilities'] = 'files_encryption/lib/capabilities.php';
|
||||
OC::$CLASSPATH['OCA\Encryption\Helper'] = 'files_encryption/lib/helper.php';
|
||||
|
||||
// Exceptions
|
||||
OC::$CLASSPATH['OCA\Encryption\Exceptions\MultiKeyEncryptException'] = 'files_encryption/lib/exceptions.php';
|
||||
OC::$CLASSPATH['OCA\Encryption\Exceptions\MultiKeyDecryptException'] = 'files_encryption/lib/exceptions.php';
|
||||
|
||||
\OCP\Util::addscript('files_encryption', 'encryption');
|
||||
\OCP\Util::addscript('files_encryption', 'detect-migration');
|
||||
|
||||
@@ -31,22 +35,6 @@ if (!OC_Config::getValue('maintenance', false)) {
|
||||
if(!in_array('crypt', stream_get_wrappers())) {
|
||||
stream_wrapper_register('crypt', 'OCA\Encryption\Stream');
|
||||
}
|
||||
|
||||
// check if we are logged in
|
||||
if (OCP\User::isLoggedIn()) {
|
||||
|
||||
// ensure filesystem is loaded
|
||||
if (!\OC\Files\Filesystem::$loaded) {
|
||||
\OC_Util::setupFS();
|
||||
}
|
||||
|
||||
$view = new OC\Files\View('/');
|
||||
|
||||
$sessionReady = OCA\Encryption\Helper::checkRequirements();
|
||||
if($sessionReady) {
|
||||
$session = new \OCA\Encryption\Session($view);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// logout user if we are in maintenance to force re-login
|
||||
OCP\User::logout();
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
<requiremin>4</requiremin>
|
||||
<shipped>true</shipped>
|
||||
<documentation>
|
||||
<user>http://doc.owncloud.org/server/6.0/user_manual/files/encryption.html</user>
|
||||
<admin>http://doc.owncloud.org/server/6.0/admin_manual/configuration/configuration_encryption.html</admin>
|
||||
<user>http://doc.owncloud.org/server/7.0/user_manual/files/encryption.html</user>
|
||||
<admin>http://doc.owncloud.org/server/7.0/admin_manual/configuration/configuration_encryption.html</admin>
|
||||
</documentation>
|
||||
<rememberlogin>false</rememberlogin>
|
||||
<types>
|
||||
<filesystem/>
|
||||
</types>
|
||||
<ocsid>166047</ocsid>
|
||||
</info>
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.6
|
||||
0.6.1
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
if (!isset($_)) { //also provide standalone error page
|
||||
require_once __DIR__ . '/../../../lib/base.php';
|
||||
require_once __DIR__ . '/../lib/crypt.php';
|
||||
|
||||
$l = OC_L10N::get('files_encryption');
|
||||
|
||||
|
||||
@@ -136,6 +136,14 @@ class Hooks {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* remove keys from session during logout
|
||||
*/
|
||||
public static function logout() {
|
||||
$session = new \OCA\Encryption\Session(new \OC\Files\View());
|
||||
$session->removeKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* setup encryption backend upon user created
|
||||
* @note This method should never be called for users using client side encryption
|
||||
@@ -187,7 +195,6 @@ class Hooks {
|
||||
* @param array $params keys: uid, password
|
||||
*/
|
||||
public static function setPassphrase($params) {
|
||||
|
||||
if (\OCP\App::isEnabled('files_encryption') === false) {
|
||||
return true;
|
||||
}
|
||||
@@ -198,19 +205,22 @@ class Hooks {
|
||||
if (Crypt::mode() === 'server') {
|
||||
|
||||
$view = new \OC\Files\View('/');
|
||||
$session = new \OCA\Encryption\Session($view);
|
||||
|
||||
if ($params['uid'] === \OCP\User::getUser()) {
|
||||
// Get existing decrypted private key
|
||||
$privateKey = $session->getPrivateKey();
|
||||
|
||||
$session = new \OCA\Encryption\Session($view);
|
||||
|
||||
// Get existing decrypted private key
|
||||
$privateKey = $session->getPrivateKey();
|
||||
if ($params['uid'] === \OCP\User::getUser() && $privateKey) {
|
||||
|
||||
// Encrypt private key with new user pwd as passphrase
|
||||
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($privateKey, $params['password']);
|
||||
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($privateKey, $params['password'], Helper::getCipher());
|
||||
|
||||
// Save private key
|
||||
Keymanager::setPrivateKey($encryptedPrivateKey);
|
||||
if ($encryptedPrivateKey) {
|
||||
Keymanager::setPrivateKey($encryptedPrivateKey, \OCP\User::getUser());
|
||||
} else {
|
||||
\OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
|
||||
}
|
||||
|
||||
// NOTE: Session does not need to be updated as the
|
||||
// private key has not changed, only the passphrase
|
||||
@@ -231,6 +241,9 @@ class Hooks {
|
||||
|| !$util->userKeysExists()
|
||||
|| !$view->file_exists($user . '/files')) {
|
||||
|
||||
// backup old keys
|
||||
$util->backupAllKeys('recovery');
|
||||
|
||||
$newUserPassword = $params['password'];
|
||||
|
||||
// make sure that the users home is mounted
|
||||
@@ -245,16 +258,17 @@ class Hooks {
|
||||
// Save public key
|
||||
$view->file_put_contents('/public-keys/' . $user . '.public.key', $keypair['publicKey']);
|
||||
|
||||
// Encrypt private key empty passphrase
|
||||
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword);
|
||||
// Encrypt private key with new password
|
||||
$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword, Helper::getCipher());
|
||||
if ($encryptedKey) {
|
||||
Keymanager::setPrivateKey($encryptedKey, $user);
|
||||
|
||||
// Save private key
|
||||
$view->file_put_contents(
|
||||
'/' . $user . '/files_encryption/' . $user . '.private.key', $encryptedPrivateKey);
|
||||
|
||||
if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
|
||||
$util = new Util($view, $user);
|
||||
$util->recoverUsersFiles($recoveryPassword);
|
||||
if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
|
||||
$util = new Util($view, $user);
|
||||
$util->recoverUsersFiles($recoveryPassword);
|
||||
}
|
||||
} else {
|
||||
\OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
|
||||
}
|
||||
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
@@ -303,7 +317,7 @@ class Hooks {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* update share keys if a file was shared
|
||||
*/
|
||||
public static function postShared($params) {
|
||||
|
||||
@@ -313,34 +327,44 @@ class Hooks {
|
||||
|
||||
if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
|
||||
|
||||
$view = new \OC\Files\View('/');
|
||||
$session = new \OCA\Encryption\Session($view);
|
||||
$userId = \OCP\User::getUser();
|
||||
$util = new Util($view, $userId);
|
||||
$path = \OC\Files\Filesystem::getPath($params['fileSource']);
|
||||
|
||||
$sharingEnabled = \OCP\Share::isEnabled();
|
||||
|
||||
$mountManager = \OC\Files\Filesystem::getMountManager();
|
||||
$mount = $mountManager->find('/' . $userId . '/files' . $path);
|
||||
$mountPoint = $mount->getMountPoint();
|
||||
|
||||
// if a folder was shared, get a list of all (sub-)folders
|
||||
if ($params['itemType'] === 'folder') {
|
||||
$allFiles = $util->getAllFiles($path, $mountPoint);
|
||||
} else {
|
||||
$allFiles = array($path);
|
||||
}
|
||||
|
||||
foreach ($allFiles as $path) {
|
||||
$usersSharing = $util->getSharingUsersArray($sharingEnabled, $path);
|
||||
$util->setSharedFileKeyfiles($session, $usersSharing, $path);
|
||||
}
|
||||
self::updateKeyfiles($path, $params['itemType']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* update keyfiles and share keys recursively
|
||||
*
|
||||
* @param string $path to the file/folder
|
||||
* @param string $type 'file' or 'folder'
|
||||
*/
|
||||
private static function updateKeyfiles($path, $type) {
|
||||
$view = new \OC\Files\View('/');
|
||||
$userId = \OCP\User::getUser();
|
||||
$session = new \OCA\Encryption\Session($view);
|
||||
$util = new Util($view, $userId);
|
||||
$sharingEnabled = \OCP\Share::isEnabled();
|
||||
|
||||
$mountManager = \OC\Files\Filesystem::getMountManager();
|
||||
$mount = $mountManager->find('/' . $userId . '/files' . $path);
|
||||
$mountPoint = $mount->getMountPoint();
|
||||
|
||||
// if a folder was shared, get a list of all (sub-)folders
|
||||
if ($type === 'folder') {
|
||||
$allFiles = $util->getAllFiles($path, $mountPoint);
|
||||
} else {
|
||||
$allFiles = array($path);
|
||||
}
|
||||
|
||||
foreach ($allFiles as $path) {
|
||||
$usersSharing = $util->getSharingUsersArray($sharingEnabled, $path);
|
||||
$util->setSharedFileKeyfiles($session, $usersSharing, $path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* unshare file/folder from a user with whom you shared the file before
|
||||
*/
|
||||
public static function postUnshare($params) {
|
||||
|
||||
@@ -385,8 +409,10 @@ class Hooks {
|
||||
// Unshare every user who no longer has access to the file
|
||||
$delUsers = array_diff($userIds, $sharingUsers);
|
||||
|
||||
list($owner, $ownerPath) = $util->getUidAndFilename($path);
|
||||
|
||||
// delete share key
|
||||
Keymanager::delShareKey($view, $delUsers, $path);
|
||||
Keymanager::delShareKey($view, $delUsers, $ownerPath, $owner);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -407,21 +433,51 @@ class Hooks {
|
||||
$mp1 = $view->getMountPoint('/' . $user . '/files/' . $params['oldpath']);
|
||||
$mp2 = $view->getMountPoint('/' . $user . '/files/' . $params['newpath']);
|
||||
|
||||
$type = $view->is_dir('/' . $user . '/files/' . $params['oldpath']) ? 'folder' : 'file';
|
||||
|
||||
if ($mp1 === $mp2) {
|
||||
self::$renamedFiles[$params['oldpath']] = array(
|
||||
'uid' => $ownerOld,
|
||||
'path' => $pathOld);
|
||||
'path' => $pathOld,
|
||||
'type' => $type,
|
||||
'operation' => 'rename',
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* after a file is renamed, rename its keyfile and share-keys also fix the file size and fix also the sharing
|
||||
* @param array $params array with oldpath and newpath
|
||||
*
|
||||
* This function is connected to the rename signal of OC_Filesystem and adjust the name and location
|
||||
* of the stored versions along the actual file
|
||||
* mark file as renamed so that we know the original source after the file was renamed
|
||||
* @param array $params with the old path and the new path
|
||||
*/
|
||||
public static function postRename($params) {
|
||||
public static function preCopy($params) {
|
||||
$user = \OCP\User::getUser();
|
||||
$view = new \OC\Files\View('/');
|
||||
$util = new Util($view, $user);
|
||||
list($ownerOld, $pathOld) = $util->getUidAndFilename($params['oldpath']);
|
||||
|
||||
// we only need to rename the keys if the rename happens on the same mountpoint
|
||||
// otherwise we perform a stream copy, so we get a new set of keys
|
||||
$mp1 = $view->getMountPoint('/' . $user . '/files/' . $params['oldpath']);
|
||||
$mp2 = $view->getMountPoint('/' . $user . '/files/' . $params['newpath']);
|
||||
|
||||
$type = $view->is_dir('/' . $user . '/files/' . $params['oldpath']) ? 'folder' : 'file';
|
||||
|
||||
if ($mp1 === $mp2) {
|
||||
self::$renamedFiles[$params['oldpath']] = array(
|
||||
'uid' => $ownerOld,
|
||||
'path' => $pathOld,
|
||||
'type' => $type,
|
||||
'operation' => 'copy');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* after a file is renamed/copied, rename/copy its keyfile and share-keys also fix the file size and fix also the sharing
|
||||
*
|
||||
* @param array $params array with oldpath and newpath
|
||||
*/
|
||||
public static function postRenameOrCopy($params) {
|
||||
|
||||
if (\OCP\App::isEnabled('files_encryption') === false) {
|
||||
return true;
|
||||
@@ -432,7 +488,6 @@ class Hooks {
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
$view = new \OC\Files\View('/');
|
||||
$session = new \OCA\Encryption\Session($view);
|
||||
$userId = \OCP\User::getUser();
|
||||
$util = new Util($view, $userId);
|
||||
|
||||
@@ -440,8 +495,12 @@ class Hooks {
|
||||
isset(self::$renamedFiles[$params['oldpath']]['path'])) {
|
||||
$ownerOld = self::$renamedFiles[$params['oldpath']]['uid'];
|
||||
$pathOld = self::$renamedFiles[$params['oldpath']]['path'];
|
||||
$type = self::$renamedFiles[$params['oldpath']]['type'];
|
||||
$operation = self::$renamedFiles[$params['oldpath']]['operation'];
|
||||
unset(self::$renamedFiles[$params['oldpath']]);
|
||||
} else {
|
||||
\OCP\Util::writeLog('Encryption library', "can't get path and owner from the file before it was renamed", \OCP\Util::ERROR);
|
||||
\OCP\Util::writeLog('Encryption library', "can't get path and owner from the file before it was renamed", \OCP\Util::DEBUG);
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -473,35 +532,36 @@ class Hooks {
|
||||
}
|
||||
|
||||
// handle share keys
|
||||
if (!$view->is_dir($oldKeyfilePath)) {
|
||||
if ($type === 'file') {
|
||||
$oldKeyfilePath .= '.key';
|
||||
$newKeyfilePath .= '.key';
|
||||
|
||||
// handle share-keys
|
||||
$matches = Helper::findShareKeys($oldShareKeyPath, $view);
|
||||
$matches = Helper::findShareKeys($pathOld, $oldShareKeyPath, $view);
|
||||
if (count($matches) === 0) {
|
||||
\OC_Log::write(
|
||||
'Encryption library', 'No share keys found for "' . $pathOld . '"',
|
||||
\OC_Log::WARN
|
||||
);
|
||||
}
|
||||
foreach ($matches as $src) {
|
||||
$dst = \OC\Files\Filesystem::normalizePath(str_replace($pathOld, $pathNew, $src));
|
||||
$view->rename($src, $dst);
|
||||
$view->$operation($src, $dst);
|
||||
}
|
||||
|
||||
} else {
|
||||
// handle share-keys folders
|
||||
$view->rename($oldShareKeyPath, $newShareKeyPath);
|
||||
$view->$operation($oldShareKeyPath, $newShareKeyPath);
|
||||
}
|
||||
|
||||
// Rename keyfile so it isn't orphaned
|
||||
if ($view->file_exists($oldKeyfilePath)) {
|
||||
$view->rename($oldKeyfilePath, $newKeyfilePath);
|
||||
$view->$operation($oldKeyfilePath, $newKeyfilePath);
|
||||
}
|
||||
|
||||
// update share keys
|
||||
$sharingEnabled = \OCP\Share::isEnabled();
|
||||
|
||||
// get users
|
||||
$usersSharing = $util->getSharingUsersArray($sharingEnabled, $pathNew);
|
||||
|
||||
// update sharing-keys
|
||||
$util->setSharedFileKeyfiles($session, $usersSharing, $pathNew);
|
||||
self::updateKeyfiles($params['newpath'], $type);
|
||||
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
}
|
||||
@@ -595,6 +655,7 @@ class Hooks {
|
||||
}
|
||||
|
||||
/**
|
||||
* unmount file from yourself
|
||||
* remember files/folders which get unmounted
|
||||
*/
|
||||
public static function preUmount($params) {
|
||||
@@ -613,6 +674,9 @@ class Hooks {
|
||||
'itemType' => $itemType);
|
||||
}
|
||||
|
||||
/**
|
||||
* unmount file from yourself
|
||||
*/
|
||||
public static function postUmount($params) {
|
||||
|
||||
if (!isset(self::$umountedFiles[$params[\OC\Files\Filesystem::signal_param_path]])) {
|
||||
@@ -642,7 +706,7 @@ class Hooks {
|
||||
// check if the user still has access to the file, otherwise delete share key
|
||||
$sharingUsers = \OCP\Share::getUsersSharingFile($path, $user);
|
||||
if (!in_array(\OCP\User::getUser(), $sharingUsers['users'])) {
|
||||
Keymanager::delShareKey($view, array(\OCP\User::getUser()), $path);
|
||||
Keymanager::delShareKey($view, array(\OCP\User::getUser()), $path, $user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,11 @@ class Crypt {
|
||||
const ENCRYPTION_PRIVATE_KEY_NOT_VALID_ERROR = 2;
|
||||
const ENCRYPTION_NO_SHARE_KEY_FOUND = 3;
|
||||
|
||||
const BLOCKSIZE = 8192; // block size will always be 8192 for a PHP stream https://bugs.php.net/bug.php?id=21641
|
||||
const DEFAULT_CIPHER = 'AES-256-CFB';
|
||||
|
||||
const HEADERSTART = 'HBEGIN';
|
||||
const HEADEREND = 'HEND';
|
||||
|
||||
/**
|
||||
* return encryption mode client or server side encryption
|
||||
@@ -213,19 +218,22 @@ class Crypt {
|
||||
* @param string $plainContent
|
||||
* @param string $iv
|
||||
* @param string $passphrase
|
||||
* @param string $cypher used for encryption, currently we support AES-128-CFB and AES-256-CFB
|
||||
* @return string encrypted file content
|
||||
* @throws \OCA\Encryption\Exceptions\EncryptionException
|
||||
*/
|
||||
private static function encrypt($plainContent, $iv, $passphrase = '') {
|
||||
private static function encrypt($plainContent, $iv, $passphrase = '', $cipher = Crypt::DEFAULT_CIPHER) {
|
||||
|
||||
if ($encryptedContent = openssl_encrypt($plainContent, 'AES-128-CFB', $passphrase, false, $iv)) {
|
||||
return $encryptedContent;
|
||||
} else {
|
||||
\OCP\Util::writeLog('Encryption library', 'Encryption (symmetric) of content failed', \OCP\Util::ERROR);
|
||||
\OCP\Util::writeLog('Encryption library', openssl_error_string(), \OCP\Util::ERROR);
|
||||
return false;
|
||||
$encryptedContent = openssl_encrypt($plainContent, $cipher, $passphrase, false, $iv);
|
||||
|
||||
if (!$encryptedContent) {
|
||||
$error = "Encryption (symmetric) of content failed: " . openssl_error_string();
|
||||
\OCP\Util::writeLog('Encryption library', $error, \OCP\Util::ERROR);
|
||||
throw new Exceptions\EncryptionException($error, 50);
|
||||
}
|
||||
|
||||
return $encryptedContent;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,19 +241,18 @@ class Crypt {
|
||||
* @param string $encryptedContent
|
||||
* @param string $iv
|
||||
* @param string $passphrase
|
||||
* @param string $cipher cipher user for decryption, currently we support aes128 and aes256
|
||||
* @throws \Exception
|
||||
* @return string decrypted file content
|
||||
*/
|
||||
private static function decrypt($encryptedContent, $iv, $passphrase) {
|
||||
private static function decrypt($encryptedContent, $iv, $passphrase, $cipher = Crypt::DEFAULT_CIPHER) {
|
||||
|
||||
if ($plainContent = openssl_decrypt($encryptedContent, 'AES-128-CFB', $passphrase, false, $iv)) {
|
||||
$plainContent = openssl_decrypt($encryptedContent, $cipher, $passphrase, false, $iv);
|
||||
|
||||
if ($plainContent) {
|
||||
return $plainContent;
|
||||
|
||||
} else {
|
||||
|
||||
throw new \Exception('Encryption library: Decryption (symmetric) of content failed');
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -293,11 +300,12 @@ class Crypt {
|
||||
* Symmetrically encrypts a string and returns keyfile content
|
||||
* @param string $plainContent content to be encrypted in keyfile
|
||||
* @param string $passphrase
|
||||
* @param string $cypher used for encryption, currently we support AES-128-CFB and AES-256-CFB
|
||||
* @return false|string encrypted content combined with IV
|
||||
* @note IV need not be specified, as it will be stored in the returned keyfile
|
||||
* and remain accessible therein.
|
||||
*/
|
||||
public static function symmetricEncryptFileContent($plainContent, $passphrase = '') {
|
||||
public static function symmetricEncryptFileContent($plainContent, $passphrase = '', $cipher = Crypt::DEFAULT_CIPHER) {
|
||||
|
||||
if (!$plainContent) {
|
||||
\OCP\Util::writeLog('Encryption library', 'symmetrically encryption failed, no content given.', \OCP\Util::ERROR);
|
||||
@@ -306,15 +314,16 @@ class Crypt {
|
||||
|
||||
$iv = self::generateIv();
|
||||
|
||||
if ($encryptedContent = self::encrypt($plainContent, $iv, $passphrase)) {
|
||||
try {
|
||||
$encryptedContent = self::encrypt($plainContent, $iv, $passphrase, $cipher);
|
||||
// Combine content to encrypt with IV identifier and actual IV
|
||||
$catfile = self::concatIv($encryptedContent, $iv);
|
||||
$padded = self::addPadding($catfile);
|
||||
|
||||
return $padded;
|
||||
|
||||
} else {
|
||||
\OCP\Util::writeLog('Encryption library', 'Encryption (symmetric) of keyfile content failed', \OCP\Util::ERROR);
|
||||
} catch (OCA\Encryption\Exceptions\EncryptionException $e) {
|
||||
$message = 'Could not encrypt file content (code: ' . $e->getCode . '): ';
|
||||
\OCP\Util::writeLog('files_encryption', $message . $e->getMessage, \OCP\Util::ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -325,6 +334,7 @@ class Crypt {
|
||||
* Symmetrically decrypts keyfile content
|
||||
* @param string $keyfileContent
|
||||
* @param string $passphrase
|
||||
* @param string $cipher cipher used for decryption, currently aes128 and aes256 is supported.
|
||||
* @throws \Exception
|
||||
* @return string|false
|
||||
* @internal param string $source
|
||||
@@ -334,7 +344,7 @@ class Crypt {
|
||||
*
|
||||
* This function decrypts a file
|
||||
*/
|
||||
public static function symmetricDecryptFileContent($keyfileContent, $passphrase = '') {
|
||||
public static function symmetricDecryptFileContent($keyfileContent, $passphrase = '', $cipher = Crypt::DEFAULT_CIPHER) {
|
||||
|
||||
if (!$keyfileContent) {
|
||||
|
||||
@@ -348,7 +358,7 @@ class Crypt {
|
||||
// Split into enc data and catfile
|
||||
$catfile = self::splitIv($noPadding);
|
||||
|
||||
if ($plainContent = self::decrypt($catfile['encrypted'], $catfile['iv'], $passphrase)) {
|
||||
if ($plainContent = self::decrypt($catfile['encrypted'], $catfile['iv'], $passphrase, $cipher)) {
|
||||
|
||||
return $plainContent;
|
||||
|
||||
@@ -360,6 +370,7 @@ class Crypt {
|
||||
|
||||
/**
|
||||
* Decrypt private key and check if the result is a valid keyfile
|
||||
*
|
||||
* @param string $encryptedKey encrypted keyfile
|
||||
* @param string $passphrase to decrypt keyfile
|
||||
* @return string|false encrypted private key or false
|
||||
@@ -368,7 +379,15 @@ class Crypt {
|
||||
*/
|
||||
public static function decryptPrivateKey($encryptedKey, $passphrase) {
|
||||
|
||||
$plainKey = self::symmetricDecryptFileContent($encryptedKey, $passphrase);
|
||||
$header = self::parseHeader($encryptedKey);
|
||||
$cipher = self::getCipher($header);
|
||||
|
||||
// if we found a header we need to remove it from the key we want to decrypt
|
||||
if (!empty($header)) {
|
||||
$encryptedKey = substr($encryptedKey, strpos($encryptedKey, self::HEADEREND) + strlen(self::HEADEREND));
|
||||
}
|
||||
|
||||
$plainKey = self::symmetricDecryptFileContent($encryptedKey, $passphrase, $cipher);
|
||||
|
||||
// check if this a valid private key
|
||||
$res = openssl_pkey_get_private($plainKey);
|
||||
@@ -390,6 +409,7 @@ class Crypt {
|
||||
* @param string $plainContent content to be encrypted
|
||||
* @param array $publicKeys array keys must be the userId of corresponding user
|
||||
* @return array keys: keys (array, key = userId), data
|
||||
* @throws \OCA\Encryption\Exceptions\\MultiKeyEncryptException if encryption failed
|
||||
* @note symmetricDecryptFileContent() can decrypt files created using this method
|
||||
*/
|
||||
public static function multiKeyEncrypt($plainContent, array $publicKeys) {
|
||||
@@ -397,9 +417,7 @@ class Crypt {
|
||||
// openssl_seal returns false without errors if $plainContent
|
||||
// is empty, so trigger our own error
|
||||
if (empty($plainContent)) {
|
||||
|
||||
throw new \Exception('Cannot mutliKeyEncrypt empty plain content');
|
||||
|
||||
throw new Exceptions\MultiKeyEncryptException('Cannot mutliKeyEncrypt empty plain content', 10);
|
||||
}
|
||||
|
||||
// Set empty vars to be set by openssl by reference
|
||||
@@ -426,9 +444,7 @@ class Crypt {
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
return false;
|
||||
|
||||
throw new Exceptions\MultiKeyEncryptException('multi key encryption failed: ' . openssl_error_string(), 20);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -438,8 +454,8 @@ class Crypt {
|
||||
* @param string $encryptedContent
|
||||
* @param string $shareKey
|
||||
* @param mixed $privateKey
|
||||
* @return false|string
|
||||
* @internal param string $plainContent content to be encrypted
|
||||
* @throws \OCA\Encryption\Exceptions\\MultiKeyDecryptException if decryption failed
|
||||
* @internal param string $plainContent contains decrypted content
|
||||
* @return string $plainContent decrypted string
|
||||
* @note symmetricDecryptFileContent() can be used to decrypt files created using this method
|
||||
*
|
||||
@@ -448,9 +464,7 @@ class Crypt {
|
||||
public static function multiKeyDecrypt($encryptedContent, $shareKey, $privateKey) {
|
||||
|
||||
if (!$encryptedContent) {
|
||||
|
||||
return false;
|
||||
|
||||
throw new Exceptions\MultiKeyDecryptException('Cannot mutliKeyDecrypt empty plain content', 10);
|
||||
}
|
||||
|
||||
if (openssl_open($encryptedContent, $plainContent, $shareKey, $privateKey)) {
|
||||
@@ -458,11 +472,7 @@ class Crypt {
|
||||
return $plainContent;
|
||||
|
||||
} else {
|
||||
|
||||
\OCP\Util::writeLog('Encryption library', 'Decryption (asymmetric) of sealed content with share-key "'.$shareKey.'" failed', \OCP\Util::ERROR);
|
||||
|
||||
return false;
|
||||
|
||||
throw new Exceptions\MultiKeyDecryptException('multiKeyDecrypt with share-key' . $shareKey . 'failed: ' . openssl_error_string(), 20);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -580,4 +590,76 @@ class Crypt {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* read header into array
|
||||
*
|
||||
* @param string $data
|
||||
* @return array
|
||||
*/
|
||||
public static function parseHeader($data) {
|
||||
|
||||
$result = array();
|
||||
|
||||
if (substr($data, 0, strlen(self::HEADERSTART)) === self::HEADERSTART) {
|
||||
$endAt = strpos($data, self::HEADEREND);
|
||||
$header = substr($data, 0, $endAt + strlen(self::HEADEREND));
|
||||
|
||||
// +1 to not start with an ':' which would result in empty element at the beginning
|
||||
$exploded = explode(':', substr($header, strlen(self::HEADERSTART)+1));
|
||||
|
||||
$element = array_shift($exploded);
|
||||
while ($element !== self::HEADEREND) {
|
||||
|
||||
$result[$element] = array_shift($exploded);
|
||||
|
||||
$element = array_shift($exploded);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if data block is the header
|
||||
*
|
||||
* @param string $data
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isHeader($data) {
|
||||
|
||||
if (substr($data, 0, strlen(self::HEADERSTART)) === self::HEADERSTART) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* get chiper from header
|
||||
*
|
||||
* @param array $header
|
||||
* @throws \OCA\Encryption\Exceptions\EncryptionException
|
||||
*/
|
||||
public static function getCipher($header) {
|
||||
$cipher = isset($header['cipher']) ? $header['cipher'] : 'AES-128-CFB';
|
||||
|
||||
if ($cipher !== 'AES-256-CFB' && $cipher !== 'AES-128-CFB') {
|
||||
|
||||
throw new \OCA\Encryption\Exceptions\EncryptionException('file header broken, no supported cipher defined', 40);
|
||||
}
|
||||
|
||||
return $cipher;
|
||||
}
|
||||
|
||||
/**
|
||||
* generate header for encrypted file
|
||||
*/
|
||||
public static function generateHeader() {
|
||||
$cipher = Helper::getCipher();
|
||||
$header = self::HEADERSTART . ':cipher:' . $cipher . ':' . self::HEADEREND;
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Bjoern Schiessle
|
||||
* @copyright 2014 Bjoern Schiessle <schiessle@owncloud.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Encryption\Exceptions;
|
||||
|
||||
/**
|
||||
* General encryption exception
|
||||
* Possible Error Codes:
|
||||
* 10 - unexpected end of encryption header
|
||||
* 20 - unexpected blog size
|
||||
* 30 - encryption header to large
|
||||
* 40 - unknown cipher
|
||||
* 50 - encryption failed
|
||||
*/
|
||||
class EncryptionException extends \Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw this exception if multi key encrytion fails
|
||||
*
|
||||
* Possible error codes:
|
||||
* 10 - empty plain content was given
|
||||
* 20 - openssl_seal failed
|
||||
*/
|
||||
class MultiKeyEncryptException extends EncryptionException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw this encryption if multi key decryption failed
|
||||
*
|
||||
* Possible error codes:
|
||||
* 10 - empty encrypted content was given
|
||||
* 20 - openssl_open failed
|
||||
*/
|
||||
class MultiKeyDecryptException extends EncryptionException {
|
||||
}
|
||||
@@ -49,6 +49,7 @@ class Helper {
|
||||
public static function registerUserHooks() {
|
||||
|
||||
\OCP\Util::connectHook('OC_User', 'post_login', 'OCA\Encryption\Hooks', 'login');
|
||||
\OCP\Util::connectHook('OC_User', 'logout', 'OCA\Encryption\Hooks', 'logout');
|
||||
\OCP\Util::connectHook('OC_User', 'post_setPassword', 'OCA\Encryption\Hooks', 'setPassphrase');
|
||||
\OCP\Util::connectHook('OC_User', 'pre_setPassword', 'OCA\Encryption\Hooks', 'preSetPassphrase');
|
||||
\OCP\Util::connectHook('OC_User', 'post_createUser', 'OCA\Encryption\Hooks', 'postCreateUser');
|
||||
@@ -62,7 +63,9 @@ class Helper {
|
||||
public static function registerFilesystemHooks() {
|
||||
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Encryption\Hooks', 'preRename');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Encryption\Hooks', 'postRename');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Encryption\Hooks', 'postRenameOrCopy');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'copy', 'OCA\Encryption\Hooks', 'preCopy');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_copy', 'OCA\Encryption\Hooks', 'postRenameOrCopy');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_delete', 'OCA\Encryption\Hooks', 'postDelete');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Encryption\Hooks', 'preDelete');
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_umount', 'OCA\Encryption\Hooks', 'postUmount');
|
||||
@@ -141,19 +144,17 @@ class Helper {
|
||||
|
||||
$view->file_put_contents('/public-keys/' . $recoveryKeyId . '.public.key', $keypair['publicKey']);
|
||||
|
||||
// Encrypt private key empty passphrase
|
||||
$encryptedPrivateKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], $recoveryPassword);
|
||||
|
||||
// Save private key
|
||||
$view->file_put_contents('/owncloud_private_key/' . $recoveryKeyId . '.private.key', $encryptedPrivateKey);
|
||||
$cipher = \OCA\Encryption\Helper::getCipher();
|
||||
$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], $recoveryPassword, $cipher);
|
||||
if ($encryptedKey) {
|
||||
Keymanager::setPrivateSystemKey($encryptedKey, $recoveryKeyId . '.private.key');
|
||||
// Set recoveryAdmin as enabled
|
||||
$appConfig->setValue('files_encryption', 'recoveryAdminEnabled', 1);
|
||||
$return = true;
|
||||
}
|
||||
|
||||
\OC_FileProxy::$enabled = true;
|
||||
|
||||
// Set recoveryAdmin as enabled
|
||||
$appConfig->setValue('files_encryption', 'recoveryAdminEnabled', 1);
|
||||
|
||||
$return = true;
|
||||
|
||||
} else { // get recovery key and check the password
|
||||
$util = new \OCA\Encryption\Util(new \OC\Files\View('/'), \OCP\User::getUser());
|
||||
$return = $util->checkRecoveryPassword($recoveryPassword);
|
||||
@@ -227,7 +228,6 @@ class Helper {
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* checks if access is public/anonymous user
|
||||
* @return bool
|
||||
@@ -431,24 +431,40 @@ class Helper {
|
||||
|
||||
/**
|
||||
* find all share keys for a given file
|
||||
* @param string $path to the file
|
||||
* @param \OC\Files\View $view view, relative to data/
|
||||
* @return array list of files, path relative to data/
|
||||
*
|
||||
* @param string $filePath path to the file name relative to the user's files dir
|
||||
* for example "subdir/filename.txt"
|
||||
* @param string $shareKeyPath share key prefix path relative to the user's data dir
|
||||
* for example "user1/files_encryption/share-keys/subdir/filename.txt"
|
||||
* @param \OC\Files\View $rootView root view, relative to data/
|
||||
* @return array list of share key files, path relative to data/$user
|
||||
*/
|
||||
public static function findShareKeys($path, $view) {
|
||||
public static function findShareKeys($filePath, $shareKeyPath, $rootView) {
|
||||
$result = array();
|
||||
$pathinfo = pathinfo($path);
|
||||
$dirContent = $view->opendir($pathinfo['dirname']);
|
||||
|
||||
if (is_resource($dirContent)) {
|
||||
while (($file = readdir($dirContent)) !== false) {
|
||||
if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
|
||||
if (preg_match("/" . $pathinfo['filename'] . ".(.*).shareKey/", $file)) {
|
||||
$result[] = $pathinfo['dirname'] . '/' . $file;
|
||||
}
|
||||
}
|
||||
$user = \OCP\User::getUser();
|
||||
$util = new Util($rootView, $user);
|
||||
// get current sharing state
|
||||
$sharingEnabled = \OCP\Share::isEnabled();
|
||||
|
||||
// get users sharing this file
|
||||
$usersSharing = $util->getSharingUsersArray($sharingEnabled, $filePath);
|
||||
|
||||
$pathinfo = pathinfo($shareKeyPath);
|
||||
|
||||
$baseDir = $pathinfo['dirname'] . '/';
|
||||
$fileName = $pathinfo['basename'];
|
||||
foreach ($usersSharing as $user) {
|
||||
$keyName = $fileName . '.' . $user . '.shareKey';
|
||||
if ($rootView->file_exists($baseDir . $keyName)) {
|
||||
$result[] = $baseDir . $keyName;
|
||||
} else {
|
||||
\OC_Log::write(
|
||||
'Encryption library',
|
||||
'No share key found for user "' . $user . '" for file "' . $pathOld . '"',
|
||||
\OC_Log::WARN
|
||||
);
|
||||
}
|
||||
closedir($dirContent);
|
||||
}
|
||||
|
||||
return $result;
|
||||
@@ -475,5 +491,25 @@ class Helper {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* read the cipher used for encryption from the config.php
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getCipher() {
|
||||
|
||||
$cipher = \OCP\Config::getSystemValue('cipher', Crypt::DEFAULT_CIPHER);
|
||||
|
||||
if ($cipher !== 'AES-256-CFB' && $cipher !== 'AES-128-CFB') {
|
||||
\OCP\Util::writeLog('files_encryption',
|
||||
'wrong cipher defined in config.php, only AES-128-CFB and AES-256-CFB is supported. Fall back ' . Crypt::DEFAULT_CIPHER,
|
||||
\OCP\Util::WARN);
|
||||
|
||||
$cipher = Crypt::DEFAULT_CIPHER;
|
||||
}
|
||||
|
||||
return $cipher;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -258,9 +258,13 @@ class Keymanager {
|
||||
* @note Encryption of the private key must be performed by client code
|
||||
* as no encryption takes place here
|
||||
*/
|
||||
public static function setPrivateKey($key) {
|
||||
public static function setPrivateKey($key, $user = '') {
|
||||
|
||||
$user = \OCP\User::getUser();
|
||||
if ($user === '') {
|
||||
$user = \OCP\User::getUser();
|
||||
}
|
||||
|
||||
$header = Crypt::generateHeader();
|
||||
|
||||
$view = new \OC\Files\View('/' . $user . '/files_encryption');
|
||||
|
||||
@@ -271,7 +275,7 @@ class Keymanager {
|
||||
$view->mkdir('');
|
||||
}
|
||||
|
||||
$result = $view->file_put_contents($user . '.private.key', $key);
|
||||
$result = $view->file_put_contents($user . '.private.key', $header . $key);
|
||||
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
@@ -279,6 +283,33 @@ class Keymanager {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* write private system key (recovery and public share key) to disk
|
||||
*
|
||||
* @param string $key encrypted key
|
||||
* @param string $keyName name of the key file
|
||||
* @return boolean
|
||||
*/
|
||||
public static function setPrivateSystemKey($key, $keyName) {
|
||||
|
||||
$header = Crypt::generateHeader();
|
||||
|
||||
$view = new \OC\Files\View('/owncloud_private_key');
|
||||
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
if (!$view->file_exists('')) {
|
||||
$view->mkdir('');
|
||||
}
|
||||
|
||||
$result = $view->file_put_contents($keyName, $header . $key);
|
||||
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* store share key
|
||||
*
|
||||
@@ -428,13 +459,17 @@ class Keymanager {
|
||||
\OCP\Util::writeLog('files_encryption', 'delAllShareKeys: delete share keys: ' . $baseDir . $filePath, \OCP\Util::DEBUG);
|
||||
$result = $view->unlink($baseDir . $filePath);
|
||||
} else {
|
||||
$parentDir = dirname($baseDir . $filePath);
|
||||
$filename = pathinfo($filePath, PATHINFO_BASENAME);
|
||||
foreach($view->getDirectoryContent($parentDir) as $content) {
|
||||
$path = $content['path'];
|
||||
if (self::getFilenameFromShareKey($content['name']) === $filename) {
|
||||
\OCP\Util::writeLog('files_encryption', 'dellAllShareKeys: delete share keys: ' . '/' . $userId . '/' . $path, \OCP\Util::DEBUG);
|
||||
$result &= $view->unlink('/' . $userId . '/' . $path);
|
||||
$sharingEnabled = \OCP\Share::isEnabled();
|
||||
$users = $util->getSharingUsersArray($sharingEnabled, $filePath);
|
||||
foreach($users as $user) {
|
||||
$keyName = $baseDir . $filePath . '.' . $user . '.shareKey';
|
||||
if ($view->file_exists($keyName)) {
|
||||
\OCP\Util::writeLog(
|
||||
'files_encryption',
|
||||
'dellAllShareKeys: delete share keys: "' . $keyName . '"',
|
||||
\OCP\Util::DEBUG
|
||||
);
|
||||
$result &= $view->unlink($keyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -444,17 +479,18 @@ class Keymanager {
|
||||
|
||||
/**
|
||||
* Delete a single user's shareKey for a single file
|
||||
*
|
||||
* @param \OC\Files\View $view relative to data/
|
||||
* @param array $userIds list of users we want to remove
|
||||
* @param string $filename the owners name of the file for which we want to remove the users relative to data/user/files
|
||||
* @param string $owner owner of the file
|
||||
*/
|
||||
public static function delShareKey(\OC\Files\View $view, $userIds, $filePath) {
|
||||
public static function delShareKey($view, $userIds, $filename, $owner) {
|
||||
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
$userId = Helper::getUser($filePath);
|
||||
|
||||
$util = new Util($view, $userId);
|
||||
|
||||
list($owner, $filename) = $util->getUidAndFilename($filePath);
|
||||
$util = new Util($view, $owner);
|
||||
|
||||
if ($util->isSystemWideMountPoint($filename)) {
|
||||
$shareKeyPath = \OC\Files\Filesystem::normalizePath('/files_encryption/share-keys/' . $filename);
|
||||
@@ -507,17 +543,20 @@ class Keymanager {
|
||||
if ($view->is_dir($dir . '/' . $file)) {
|
||||
self::recursiveDelShareKeys($dir . '/' . $file, $userIds, $owner, $view);
|
||||
} else {
|
||||
$realFile = $realFileDir . self::getFilenameFromShareKey($file);
|
||||
foreach ($userIds as $userId) {
|
||||
if (preg_match("/(.*)." . $userId . ".shareKey/", $file)) {
|
||||
if ($userId === $owner &&
|
||||
$view->file_exists($realFile)) {
|
||||
\OCP\Util::writeLog('files_encryption', 'original file still exists, keep owners share key!', \OCP\Util::ERROR);
|
||||
continue;
|
||||
}
|
||||
\OCP\Util::writeLog('files_encryption', 'recursiveDelShareKey: delete share key: ' . $file, \OCP\Util::DEBUG);
|
||||
$view->unlink($dir . '/' . $file);
|
||||
$fileNameFromShareKey = self::getFilenameFromShareKey($file, $userId);
|
||||
if (!$fileNameFromShareKey) {
|
||||
continue;
|
||||
}
|
||||
$realFile = $realFileDir . $fileNameFromShareKey;
|
||||
|
||||
if ($userId === $owner &&
|
||||
$view->file_exists($realFile)) {
|
||||
\OCP\Util::writeLog('files_encryption', 'original file still exists, keep owners share key!', \OCP\Util::ERROR);
|
||||
continue;
|
||||
}
|
||||
\OCP\Util::writeLog('files_encryption', 'recursiveDelShareKey: delete share key: ' . $file, \OCP\Util::DEBUG);
|
||||
$view->unlink($dir . '/' . $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -559,16 +598,19 @@ class Keymanager {
|
||||
/**
|
||||
* extract filename from share key name
|
||||
* @param string $shareKey (filename.userid.sharekey)
|
||||
* @param string $userId
|
||||
* @return string|false filename or false
|
||||
*/
|
||||
protected static function getFilenameFromShareKey($shareKey) {
|
||||
$parts = explode('.', $shareKey);
|
||||
protected static function getFilenameFromShareKey($shareKey, $userId) {
|
||||
$expectedSuffix = '.' . $userId . '.' . 'shareKey';
|
||||
$suffixLen = strlen($expectedSuffix);
|
||||
|
||||
$filename = false;
|
||||
if(count($parts) > 2) {
|
||||
$filename = implode('.', array_slice($parts, 0, count($parts)-2));
|
||||
$suffix = substr($shareKey, -$suffixLen);
|
||||
|
||||
if ($suffix !== $expectedSuffix) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $filename;
|
||||
return substr($shareKey, 0, -$suffixLen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ class Proxy extends \OC_FileProxy {
|
||||
$view = new \OC\Files\View();
|
||||
|
||||
// files outside of the files-folder are excluded
|
||||
if(strpos($path, '/' . $uid . '/files') !== 0) {
|
||||
if(strpos($path, '/' . $uid . '/files/') !== 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ class Proxy extends \OC_FileProxy {
|
||||
* @param resource $result
|
||||
* @return resource
|
||||
*/
|
||||
public function postFopen($path, &$result) {
|
||||
public function postFopen($path, $result) {
|
||||
|
||||
$path = \OC\Files\Filesystem::normalizePath($path);
|
||||
|
||||
|
||||
@@ -80,11 +80,13 @@ class Session {
|
||||
$this->view->file_put_contents('/public-keys/' . $publicShareKeyId . '.public.key', $keypair['publicKey']);
|
||||
|
||||
// Encrypt private key empty passphrase
|
||||
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], '');
|
||||
|
||||
// Save private key
|
||||
$this->view->file_put_contents(
|
||||
'/owncloud_private_key/' . $publicShareKeyId . '.private.key', $encryptedPrivateKey);
|
||||
$cipher = \OCA\Encryption\Helper::getCipher();
|
||||
$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], '', $cipher);
|
||||
if ($encryptedKey) {
|
||||
Keymanager::setPrivateSystemKey($encryptedKey, $publicShareKeyId . '.private.key');
|
||||
} else {
|
||||
\OCP\Util::writeLog('files_encryption', 'Could not create public share keys', \OCP\Util::ERROR);
|
||||
}
|
||||
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
@@ -121,6 +123,14 @@ class Session {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* remove keys from session
|
||||
*/
|
||||
public function removeKeys() {
|
||||
\OC::$session->remove('publicSharePrivateKey');
|
||||
\OC::$session->remove('privateKey');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets status of encryption app
|
||||
* @param string $init INIT_SUCCESSFUL, INIT_EXECUTED, NOT_INITIALIZED
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Robin Appelman
|
||||
* @copyright 2012 Sam Tuke <samtuke@owncloud.com>, 2011 Robin Appelman
|
||||
* <icewind1991@gmail.com>
|
||||
* @author Bjoern Schiessle, Robin Appelman
|
||||
* @copyright 2014 Bjoern Schiessle <schiessle@owncloud.com>
|
||||
* 2012 Sam Tuke <samtuke@owncloud.com>,
|
||||
* 2011 Robin Appelman <icewind1991@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
@@ -49,9 +50,11 @@ namespace OCA\Encryption;
|
||||
* encryption proxies are used and keyfiles deleted.
|
||||
*/
|
||||
class Stream {
|
||||
|
||||
const PADDING_CHAR = '-';
|
||||
|
||||
private $plainKey;
|
||||
private $encKeyfiles;
|
||||
|
||||
private $rawPath; // The raw path relative to the data dir
|
||||
private $relPath; // rel path to users file dir
|
||||
private $userId;
|
||||
@@ -66,6 +69,9 @@ class Stream {
|
||||
private $newFile; // helper var, we only need to write the keyfile for new files
|
||||
private $isLocalTmpFile = false; // do we operate on a local tmp file
|
||||
private $localTmpFile; // path of local tmp file
|
||||
private $headerWritten = false;
|
||||
private $containHeader = false; // the file contain a header
|
||||
private $cipher; // cipher used for encryption/decryption
|
||||
|
||||
/**
|
||||
* @var \OC\Files\View
|
||||
@@ -87,6 +93,9 @@ class Stream {
|
||||
*/
|
||||
public function stream_open($path, $mode, $options, &$opened_path) {
|
||||
|
||||
// read default cipher from config
|
||||
$this->cipher = Helper::getCipher();
|
||||
|
||||
// assume that the file already exist before we decide it finally in getKey()
|
||||
$this->newFile = false;
|
||||
|
||||
@@ -150,6 +159,9 @@ class Stream {
|
||||
}
|
||||
|
||||
$this->size = $this->rootView->filesize($this->rawPath);
|
||||
|
||||
$this->readHeader();
|
||||
|
||||
}
|
||||
|
||||
if ($this->isLocalTmpFile) {
|
||||
@@ -178,6 +190,29 @@ class Stream {
|
||||
|
||||
}
|
||||
|
||||
private function readHeader() {
|
||||
|
||||
if ($this->isLocalTmpFile) {
|
||||
$handle = fopen($this->localTmpFile, 'r');
|
||||
} else {
|
||||
$handle = $this->rootView->fopen($this->rawPath, 'r');
|
||||
}
|
||||
|
||||
if (is_resource($handle)) {
|
||||
$data = fread($handle, Crypt::BLOCKSIZE);
|
||||
|
||||
$header = Crypt::parseHeader($data);
|
||||
$this->cipher = Crypt::getCipher($header);
|
||||
|
||||
// remeber that we found a header
|
||||
if (!empty($header)) {
|
||||
$this->containHeader = true;
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current position of the file pointer
|
||||
* @return int position of the file pointer
|
||||
@@ -195,6 +230,11 @@ class Stream {
|
||||
|
||||
$this->flush();
|
||||
|
||||
// ignore the header and just overstep it
|
||||
if ($this->containHeader) {
|
||||
$offset += Crypt::BLOCKSIZE;
|
||||
}
|
||||
|
||||
// this wrapper needs to return "true" for success.
|
||||
// the fseek call itself returns 0 on succeess
|
||||
return !fseek($this->handle, $offset, $whence);
|
||||
@@ -204,25 +244,25 @@ class Stream {
|
||||
/**
|
||||
* @param int $count
|
||||
* @return bool|string
|
||||
* @throws \Exception
|
||||
* @throws \OCA\Encryption\Exceptions\EncryptionException
|
||||
*/
|
||||
public function stream_read($count) {
|
||||
|
||||
$this->writeCache = '';
|
||||
|
||||
if ($count !== 8192) {
|
||||
|
||||
// $count will always be 8192 https://bugs.php.net/bug.php?id=21641
|
||||
// This makes this function a lot simpler, but will break this class if the above 'bug' gets 'fixed'
|
||||
if ($count !== Crypt::BLOCKSIZE) {
|
||||
\OCP\Util::writeLog('Encryption library', 'PHP "bug" 21641 no longer holds, decryption system requires refactoring', \OCP\Util::FATAL);
|
||||
|
||||
die();
|
||||
|
||||
throw new \OCA\Encryption\Exceptions\EncryptionException('expected a blog size of 8192 byte', 20);
|
||||
}
|
||||
|
||||
// Get the data from the file handle
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
// if this block contained the header we move on to the next block
|
||||
if (Crypt::isHeader($data)) {
|
||||
$data = fread($this->handle, $count);
|
||||
}
|
||||
|
||||
$result = null;
|
||||
|
||||
if (strlen($data)) {
|
||||
@@ -236,7 +276,7 @@ class Stream {
|
||||
} else {
|
||||
|
||||
// Decrypt data
|
||||
$result = Crypt::symmetricDecryptFileContent($data, $this->plainKey);
|
||||
$result = Crypt::symmetricDecryptFileContent($data, $this->plainKey, $this->cipher);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -254,7 +294,7 @@ class Stream {
|
||||
public function preWriteEncrypt($plainData, $key) {
|
||||
|
||||
// Encrypt data to 'catfile', which includes IV
|
||||
if ($encrypted = Crypt::symmetricEncryptFileContent($plainData, $key)) {
|
||||
if ($encrypted = Crypt::symmetricEncryptFileContent($plainData, $key, $this->cipher)) {
|
||||
|
||||
return $encrypted;
|
||||
|
||||
@@ -317,6 +357,25 @@ class Stream {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* write header at beginning of encrypted file
|
||||
*
|
||||
* @throws Exceptions\EncryptionException
|
||||
*/
|
||||
private function writeHeader() {
|
||||
|
||||
$header = Crypt::generateHeader();
|
||||
|
||||
if (strlen($header) > Crypt::BLOCKSIZE) {
|
||||
throw new Exceptions\EncryptionException('max header size exceeded', 30);
|
||||
}
|
||||
|
||||
$paddedHeader = str_pad($header, Crypt::BLOCKSIZE, self::PADDING_CHAR, STR_PAD_RIGHT);
|
||||
|
||||
fwrite($this->handle, $paddedHeader);
|
||||
$this->headerWritten = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle plain data from the stream, and write it in 8192 byte blocks
|
||||
* @param string $data data to be written to disk
|
||||
@@ -334,6 +393,10 @@ class Stream {
|
||||
return strlen($data);
|
||||
}
|
||||
|
||||
if ($this->headerWritten === false) {
|
||||
$this->writeHeader();
|
||||
}
|
||||
|
||||
// Disable the file proxies so that encryption is not
|
||||
// automatically attempted when the file is written to disk -
|
||||
// we are handling that separately here and we don't want to
|
||||
|
||||
@@ -167,11 +167,12 @@ class Util {
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
// Encrypt private key with user pwd as passphrase
|
||||
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $passphrase);
|
||||
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $passphrase, Helper::getCipher());
|
||||
|
||||
// Save key-pair
|
||||
if ($encryptedPrivateKey) {
|
||||
$this->view->file_put_contents($this->privateKeyPath, $encryptedPrivateKey);
|
||||
$header = crypt::generateHeader();
|
||||
$this->view->file_put_contents($this->privateKeyPath, $header . $encryptedPrivateKey);
|
||||
$this->view->file_put_contents($this->publicKeyPath, $keypair['publicKey']);
|
||||
}
|
||||
|
||||
@@ -394,8 +395,14 @@ class Util {
|
||||
&& $this->isEncryptedPath($path)
|
||||
) {
|
||||
|
||||
// get the size from filesystem
|
||||
$size = $this->view->filesize($path);
|
||||
$offset = 0;
|
||||
if ($this->containHeader($path)) {
|
||||
$offset = Crypt::BLOCKSIZE;
|
||||
}
|
||||
|
||||
// get the size from filesystem if the file contains a encryption header we
|
||||
// we substract it
|
||||
$size = $this->view->filesize($path) - $offset;
|
||||
|
||||
// fast path, else the calculation for $lastChunkNr is bogus
|
||||
if ($size === 0) {
|
||||
@@ -406,15 +413,15 @@ class Util {
|
||||
// calculate last chunk nr
|
||||
// next highest is end of chunks, one subtracted is last one
|
||||
// we have to read the last chunk, we can't just calculate it (because of padding etc)
|
||||
$lastChunkNr = ceil($size/ 8192) - 1;
|
||||
$lastChunkSize = $size - ($lastChunkNr * 8192);
|
||||
$lastChunkNr = ceil($size/ Crypt::BLOCKSIZE) - 1;
|
||||
$lastChunkSize = $size - ($lastChunkNr * Crypt::BLOCKSIZE);
|
||||
|
||||
// open stream
|
||||
$stream = fopen('crypt://' . $path, "r");
|
||||
|
||||
if (is_resource($stream)) {
|
||||
// calculate last chunk position
|
||||
$lastChunckPos = ($lastChunkNr * 8192);
|
||||
$lastChunckPos = ($lastChunkNr * Crypt::BLOCKSIZE);
|
||||
|
||||
// seek to end
|
||||
if (@fseek($stream, $lastChunckPos) === -1) {
|
||||
@@ -448,6 +455,30 @@ class Util {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if encrypted file contain a encryption header
|
||||
*
|
||||
* @param string $path
|
||||
* @return boolean
|
||||
*/
|
||||
private function containHeader($path) {
|
||||
// Disable encryption proxy to read the raw data
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
$isHeader = false;
|
||||
$handle = $this->view->fopen($path, 'r');
|
||||
|
||||
if (is_resource($handle)) {
|
||||
$firstBlock = fread($handle, Crypt::BLOCKSIZE);
|
||||
$isHeader = Crypt::isHeader($firstBlock);
|
||||
}
|
||||
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
return $isHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* fix the file size of the encrypted file
|
||||
* @param string $path absolute path
|
||||
@@ -956,19 +987,26 @@ class Util {
|
||||
// Get the current users's private key for decrypting existing keyfile
|
||||
$privateKey = $session->getPrivateKey();
|
||||
|
||||
$fileOwner = \OC\Files\Filesystem::getOwner($filePath);
|
||||
|
||||
// Decrypt keyfile
|
||||
$plainKeyfile = $this->decryptKeyfile($filePath, $privateKey);
|
||||
|
||||
// Re-enc keyfile to (additional) sharekeys
|
||||
$multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys);
|
||||
try {
|
||||
// Decrypt keyfile
|
||||
$plainKeyfile = $this->decryptKeyfile($filePath, $privateKey);
|
||||
// Re-enc keyfile to (additional) sharekeys
|
||||
$multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys);
|
||||
} catch (Exceptions\EncryptionException $e) {
|
||||
$msg = 'set shareFileKeyFailed (code: ' . $e->getCode() . '): ' . $e->getMessage();
|
||||
\OCP\Util::writeLog('files_encryption', $msg, \OCP\Util::FATAL);
|
||||
return false;
|
||||
} catch (\Exception $e) {
|
||||
$msg = 'set shareFileKeyFailed (unknown error): ' . $e->getMessage();
|
||||
\OCP\Util::writeLog('files_encryption', $msg, \OCP\Util::FATAL);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save the recrypted key to it's owner's keyfiles directory
|
||||
// Save new sharekeys to all necessary user directory
|
||||
if (
|
||||
!Keymanager::setFileKey($this->view, $this, $filePath, $multiEncKey['data'])
|
||||
|| !Keymanager::setShareKeys($this->view, $this, $filePath, $multiEncKey['keys'])
|
||||
!Keymanager::setFileKey($this->view, $this, $filePath, $multiEncKey['data'])
|
||||
|| !Keymanager::setShareKeys($this->view, $this, $filePath, $multiEncKey['keys'])
|
||||
) {
|
||||
|
||||
\OCP\Util::writeLog('Encryption library',
|
||||
@@ -1034,10 +1072,10 @@ class Util {
|
||||
|
||||
// check if it is a group mount
|
||||
if (\OCP\App::isEnabled("files_external")) {
|
||||
$mount = \OC_Mount_Config::getSystemMountPoints();
|
||||
foreach ($mount as $mountPoint => $data) {
|
||||
if ($mountPoint == substr($ownerPath, 1, strlen($mountPoint))) {
|
||||
$userIds = array_merge($userIds, $this->getUserWithAccessToMountPoint($data['applicable']['users'], $data['applicable']['groups']));
|
||||
$mounts = \OC_Mount_Config::getSystemMountPoints();
|
||||
foreach ($mounts as $mount) {
|
||||
if ($mount['mountpoint'] == substr($ownerPath, 1, strlen($mount['mountpoint']))) {
|
||||
$userIds = array_merge($userIds, $this->getUserWithAccessToMountPoint($mount['applicable']['users'], $mount['applicable']['groups']));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1487,6 +1525,22 @@ class Util {
|
||||
$this->recoverAllFiles('/', $privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a backup of all keys from the user
|
||||
*
|
||||
* @param string $purpose (optional) define the purpose of the backup, will be part of the backup folder
|
||||
*/
|
||||
public function backupAllKeys($purpose = '') {
|
||||
$this->userId;
|
||||
$backupDir = $this->encryptionDir . '/backup.';
|
||||
$backupDir .= ($purpose === '') ? date("Y-m-d_H-i-s") . '/' : $purpose . '.' . date("Y-m-d_H-i-s") . '/';
|
||||
$this->view->mkdir($backupDir);
|
||||
$this->view->copy($this->shareKeysPath, $backupDir . 'share-keys/');
|
||||
$this->view->copy($this->keyfilesPath, $backupDir . 'keyfiles/');
|
||||
$this->view->copy($this->privateKeyPath, $backupDir . $this->userId . '.private.key');
|
||||
$this->view->copy($this->publicKeyPath, $backupDir . $this->userId . '.public.key');
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the file is stored on a system wide mount point
|
||||
* @param string $path relative to /data/user with leading '/'
|
||||
@@ -1495,16 +1549,41 @@ class Util {
|
||||
public function isSystemWideMountPoint($path) {
|
||||
$normalizedPath = ltrim($path, '/');
|
||||
if (\OCP\App::isEnabled("files_external")) {
|
||||
$mount = \OC_Mount_Config::getSystemMountPoints();
|
||||
foreach ($mount as $mountPoint => $data) {
|
||||
if ($mountPoint == substr($normalizedPath, 0, strlen($mountPoint))) {
|
||||
return true;
|
||||
$mounts = \OC_Mount_Config::getSystemMountPoints();
|
||||
foreach ($mounts as $mount) {
|
||||
if ($mount['mountpoint'] == substr($normalizedPath, 0, strlen($mount['mountpoint']))) {
|
||||
if ($this->isMountPointApplicableToUser($mount)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if mount point is applicable to user
|
||||
*
|
||||
* @param array $mount contains $mount['applicable']['users'], $mount['applicable']['groups']
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isMountPointApplicableToUser($mount) {
|
||||
$uid = \OCP\User::getUser();
|
||||
$acceptedUids = array('all', $uid);
|
||||
// check if mount point is applicable for the user
|
||||
$intersection = array_intersect($acceptedUids, $mount['applicable']['users']);
|
||||
if (!empty($intersection)) {
|
||||
return true;
|
||||
}
|
||||
// check if mount point is applicable for group where the user is a member
|
||||
foreach ($mount['applicable']['groups'] as $gid) {
|
||||
if (\OC_Group::inGroup($uid, $gid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* decrypt private key and add it to the current session
|
||||
* @param array $params with 'uid' and 'password'
|
||||
|
||||
@@ -12,8 +12,11 @@ $tmpl = new OCP\Template('files_encryption', 'settings-admin');
|
||||
|
||||
// Check if an adminRecovery account is enabled for recovering files after lost pwd
|
||||
$recoveryAdminEnabled = \OC::$server->getAppConfig()->getValue('files_encryption', 'recoveryAdminEnabled', '0');
|
||||
$session = new \OCA\Encryption\Session(new \OC\Files\View('/'));
|
||||
$initStatus = $session->getInitialized();
|
||||
|
||||
$tmpl->assign('recoveryEnabled', $recoveryAdminEnabled);
|
||||
$tmpl->assign('initStatus', $initStatus);
|
||||
|
||||
\OCP\Util::addscript('files_encryption', 'settings-admin');
|
||||
\OCP\Util::addscript('core', 'multiselect');
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<form id="encryption" class="section">
|
||||
<h2><?php p($l->t('Encryption')); ?></h2>
|
||||
|
||||
<?php if($_["initStatus"] === \OCA\Encryption\Session::NOT_INITIALIZED): ?>
|
||||
<?php p($l->t("Encryption App is enabled but your keys are not initialized, please log-out and log-in again")); ?>
|
||||
<?php else: ?>
|
||||
<p>
|
||||
<?php p($l->t("Enable recovery key (allow to recover users files in case of password loss):")); ?>
|
||||
<br/>
|
||||
@@ -57,4 +60,5 @@
|
||||
</button>
|
||||
<span class="msg"></span>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
<form id="encryption" class="section">
|
||||
<h2><?php p( $l->t( 'Encryption' ) ); ?></h2>
|
||||
|
||||
<?php if ( $_["initialized"] === '1' ): ?>
|
||||
<?php if ( $_["initialized"] === \OCA\Encryption\Session::NOT_INITIALIZED ): ?>
|
||||
|
||||
<?php p($l->t("Encryption App is enabled but your keys are not initialized, please log-out and log-in again")); ?>
|
||||
|
||||
<?php elseif ( $_["initialized"] === \OCA\Encryption\Session::INIT_EXECUTED ): ?>
|
||||
<p>
|
||||
<a name="changePKPasswd" />
|
||||
<label for="changePrivateKeyPasswd">
|
||||
<?php p( $l->t( "Your private key password no longer match your log-in password:" ) ); ?>
|
||||
<em><?php p( $l->t( "Your private key password no longer match your log-in password." ) ); ?></em>
|
||||
</label>
|
||||
<br />
|
||||
<em><?php p( $l->t( "Set your old private key password to your current log-in password." ) ); ?>
|
||||
<?php p( $l->t( "Set your old private key password to your current log-in password:" ) ); ?>
|
||||
<?php if ( $_["recoveryEnabledForUser"] ):
|
||||
p( $l->t( " If you don't remember your old password you can ask your administrator to recover your files." ) );
|
||||
endif; ?>
|
||||
</em>
|
||||
<br />
|
||||
<input
|
||||
type="password"
|
||||
@@ -33,9 +36,8 @@
|
||||
</button>
|
||||
<span class="msg"></span>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( $_["recoveryEnabled"] && $_["privateKeySet"] ): ?>
|
||||
<?php elseif ( $_["recoveryEnabled"] && $_["privateKeySet"] && $_["initialized"] === \OCA\Encryption\Session::INIT_SUCCESSFUL ): ?>
|
||||
<br />
|
||||
<p>
|
||||
<label for="userEnableRecovery"><?php p( $l->t( "Enable password recovery:" ) ); ?></label>
|
||||
|
||||
@@ -95,6 +95,9 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
} else {
|
||||
OC_App::disable('files_trashbin');
|
||||
}
|
||||
|
||||
$this->assertTrue(\OC_FileProxy::$enabled);
|
||||
\OCP\Config::deleteSystemValue('cipher');
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass() {
|
||||
@@ -120,7 +123,9 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
// test successful decrypt
|
||||
$crypted = Encryption\Crypt::symmetricEncryptFileContent($this->genPrivateKey, 'hat');
|
||||
|
||||
$decrypted = Encryption\Crypt::decryptPrivateKey($crypted, 'hat');
|
||||
$header = Encryption\Crypt::generateHeader();
|
||||
|
||||
$decrypted = Encryption\Crypt::decryptPrivateKey($header . $crypted, 'hat');
|
||||
|
||||
$this->assertEquals($this->genPrivateKey, $decrypted);
|
||||
|
||||
@@ -150,6 +155,24 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
*/
|
||||
function testSymmetricEncryptFileContentAes128() {
|
||||
|
||||
# TODO: search in keyfile for actual content as IV will ensure this test always passes
|
||||
|
||||
$crypted = Encryption\Crypt::symmetricEncryptFileContent($this->dataShort, 'hat', 'AES-128-CFB');
|
||||
|
||||
$this->assertNotEquals($this->dataShort, $crypted);
|
||||
|
||||
|
||||
$decrypt = Encryption\Crypt::symmetricDecryptFileContent($crypted, 'hat', 'AES-128-CFB');
|
||||
|
||||
$this->assertEquals($this->dataShort, $decrypt);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
*/
|
||||
@@ -157,8 +180,6 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
$filename = 'tmp-' . uniqid() . '.test';
|
||||
|
||||
$util = new Encryption\Util(new \OC\Files\View(), $this->userId);
|
||||
|
||||
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/'. $filename, $this->dataShort);
|
||||
|
||||
// Test that data was successfully written
|
||||
@@ -177,26 +198,52 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
// Check that the file was encrypted before being written to disk
|
||||
$this->assertNotEquals($this->dataShort, $retreivedCryptedFile);
|
||||
|
||||
// Get the encrypted keyfile
|
||||
$encKeyfile = Encryption\Keymanager::getFileKey($this->view, $util, $filename);
|
||||
|
||||
// Attempt to fetch the user's shareKey
|
||||
$shareKey = Encryption\Keymanager::getShareKey($this->view, $this->userId, $util, $filename);
|
||||
|
||||
// get session
|
||||
$session = new \OCA\Encryption\Session($this->view);
|
||||
|
||||
// get private key
|
||||
$privateKey = $session->getPrivateKey($this->userId);
|
||||
|
||||
// Decrypt keyfile with shareKey
|
||||
$plainKeyfile = Encryption\Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey);
|
||||
|
||||
// Manually decrypt
|
||||
$manualDecrypt = Encryption\Crypt::symmetricDecryptFileContent($retreivedCryptedFile, $plainKeyfile);
|
||||
// Get file contents with the encryption wrapper
|
||||
$decrypted = file_get_contents('crypt:///' . $this->userId . '/files/'. $filename);
|
||||
|
||||
// Check that decrypted data matches
|
||||
$this->assertEquals($this->dataShort, $manualDecrypt);
|
||||
$this->assertEquals($this->dataShort, $decrypted);
|
||||
|
||||
// Teardown
|
||||
$this->view->unlink($this->userId . '/files/' . $filename);
|
||||
|
||||
Encryption\Keymanager::deleteFileKey($this->view, $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
*/
|
||||
function testSymmetricStreamEncryptShortFileContentAes128() {
|
||||
|
||||
$filename = 'tmp-' . uniqid() . '.test';
|
||||
|
||||
\OCP\Config::setSystemValue('cipher', 'AES-128-CFB');
|
||||
|
||||
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/'. $filename, $this->dataShort);
|
||||
|
||||
// Test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
|
||||
\OCP\Config::deleteSystemValue('cipher');
|
||||
|
||||
// Disable encryption proxy to prevent recursive calls
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
// Get file contents without using any wrapper to get it's actual contents on disk
|
||||
$retreivedCryptedFile = $this->view->file_get_contents($this->userId . '/files/' . $filename);
|
||||
|
||||
// Re-enable proxy - our work is done
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
// Check that the file was encrypted before being written to disk
|
||||
$this->assertNotEquals($this->dataShort, $retreivedCryptedFile);
|
||||
|
||||
// Get file contents with the encryption wrapper
|
||||
$decrypted = file_get_contents('crypt:///' . $this->userId . '/files/'. $filename);
|
||||
|
||||
// Check that decrypted data matches
|
||||
$this->assertEquals($this->dataShort, $decrypted);
|
||||
|
||||
// Teardown
|
||||
$this->view->unlink($this->userId . '/files/' . $filename);
|
||||
@@ -216,8 +263,6 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
// Generate a a random filename
|
||||
$filename = 'tmp-' . uniqid() . '.test';
|
||||
|
||||
$util = new Encryption\Util(new \OC\Files\View(), $this->userId);
|
||||
|
||||
// Save long data as encrypted file using stream wrapper
|
||||
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/' . $filename, $this->dataLong . $this->dataLong);
|
||||
|
||||
@@ -238,50 +283,9 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
// Check that the file was encrypted before being written to disk
|
||||
$this->assertNotEquals($this->dataLong . $this->dataLong, $retreivedCryptedFile);
|
||||
|
||||
// Manuallly split saved file into separate IVs and encrypted chunks
|
||||
$r = preg_split('/(00iv00.{16,18})/', $retreivedCryptedFile, NULL, PREG_SPLIT_DELIM_CAPTURE);
|
||||
$decrypted = file_get_contents('crypt:///' . $this->userId . '/files/'. $filename);
|
||||
|
||||
//print_r($r);
|
||||
|
||||
// Join IVs and their respective data chunks
|
||||
$e = array();
|
||||
$i = 0;
|
||||
while ($i < count($r)-1) {
|
||||
$e[] = $r[$i] . $r[$i+1];
|
||||
$i = $i + 2;
|
||||
}
|
||||
|
||||
//print_r($e);
|
||||
|
||||
// Get the encrypted keyfile
|
||||
$encKeyfile = Encryption\Keymanager::getFileKey($this->view, $util, $filename);
|
||||
|
||||
// Attempt to fetch the user's shareKey
|
||||
$shareKey = Encryption\Keymanager::getShareKey($this->view, $this->userId, $util, $filename);
|
||||
|
||||
// get session
|
||||
$session = new \OCA\Encryption\Session($this->view);
|
||||
|
||||
// get private key
|
||||
$privateKey = $session->getPrivateKey($this->userId);
|
||||
|
||||
// Decrypt keyfile with shareKey
|
||||
$plainKeyfile = Encryption\Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey);
|
||||
|
||||
// Set var for reassembling decrypted content
|
||||
$decrypt = '';
|
||||
|
||||
// Manually decrypt chunk
|
||||
foreach ($e as $chunk) {
|
||||
|
||||
$chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent($chunk, $plainKeyfile);
|
||||
|
||||
// Assemble decrypted chunks
|
||||
$decrypt .= $chunkDecrypt;
|
||||
|
||||
}
|
||||
|
||||
$this->assertEquals($this->dataLong . $this->dataLong, $decrypt);
|
||||
$this->assertEquals($this->dataLong . $this->dataLong, $decrypted);
|
||||
|
||||
// Teardown
|
||||
|
||||
@@ -293,14 +297,20 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
/**
|
||||
* @medium
|
||||
* Test that data that is read by the crypto stream wrapper
|
||||
* Test that data that is written by the crypto stream wrapper with AES 128
|
||||
* @note Encrypted data is manually prepared and decrypted here to avoid dependency on success of stream_read
|
||||
* @note If this test fails with truncate content, check that enough array slices are being rejoined to form $e, as the crypt.php file may have gotten longer and broken the manual
|
||||
* reassembly of its data
|
||||
*/
|
||||
function testSymmetricStreamDecryptShortFileContent() {
|
||||
function testSymmetricStreamEncryptLongFileContentAes128() {
|
||||
|
||||
$filename = 'tmp-' . uniqid();
|
||||
// Generate a a random filename
|
||||
$filename = 'tmp-' . uniqid() . '.test';
|
||||
|
||||
\OCP\Config::setSystemValue('cipher', 'AES-128-CFB');
|
||||
|
||||
// Save long data as encrypted file using stream wrapper
|
||||
$cryptedFile = file_put_contents('crypt:///'. $this->userId . '/files/' . $filename, $this->dataShort);
|
||||
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/' . $filename, $this->dataLong . $this->dataLong);
|
||||
|
||||
// Test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
@@ -309,39 +319,80 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
$this->assertTrue(Encryption\Crypt::isEncryptedMeta($filename));
|
||||
\OCP\Config::deleteSystemValue('cipher');
|
||||
|
||||
// Get file contents without using any wrapper to get it's actual contents on disk
|
||||
$retreivedCryptedFile = $this->view->file_get_contents($this->userId . '/files/' . $filename);
|
||||
|
||||
// Re-enable proxy - our work is done
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
// Get file decrypted contents
|
||||
$decrypt = file_get_contents('crypt:///' . $this->userId . '/files/' . $filename);
|
||||
|
||||
$this->assertEquals($this->dataShort, $decrypt);
|
||||
// Check that the file was encrypted before being written to disk
|
||||
$this->assertNotEquals($this->dataLong . $this->dataLong, $retreivedCryptedFile);
|
||||
|
||||
$decrypted = file_get_contents('crypt:///' . $this->userId . '/files/'. $filename);
|
||||
|
||||
$this->assertEquals($this->dataLong . $this->dataLong, $decrypted);
|
||||
|
||||
// Teardown
|
||||
|
||||
// tear down
|
||||
$this->view->unlink($this->userId . '/files/' . $filename);
|
||||
|
||||
Encryption\Keymanager::deleteFileKey($this->view, $filename);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
* Test that data that is written by the crypto stream wrapper with AES 128
|
||||
* @note Encrypted data is manually prepared and decrypted here to avoid dependency on success of stream_read
|
||||
* @note If this test fails with truncate content, check that enough array slices are being rejoined to form $e, as the crypt.php file may have gotten longer and broken the manual
|
||||
* reassembly of its data
|
||||
*/
|
||||
function testSymmetricStreamDecryptLongFileContent() {
|
||||
function testStreamDecryptLongFileContentWithoutHeader() {
|
||||
|
||||
$filename = 'tmp-' . uniqid();
|
||||
// Generate a a random filename
|
||||
$filename = 'tmp-' . uniqid() . '.test';
|
||||
|
||||
\OCP\Config::setSystemValue('cipher', 'AES-128-CFB');
|
||||
|
||||
// Save long data as encrypted file using stream wrapper
|
||||
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/' . $filename, $this->dataLong);
|
||||
$cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/' . $filename, $this->dataLong . $this->dataLong);
|
||||
|
||||
\OCP\Config::deleteSystemValue('cipher');
|
||||
|
||||
// Test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
|
||||
// Get file decrypted contents
|
||||
$decrypt = file_get_contents('crypt:///' . $this->userId . '/files/' . $filename);
|
||||
// Disable encryption proxy to prevent recursive calls
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
$this->assertEquals($this->dataLong, $decrypt);
|
||||
// Get file contents without using any wrapper to get it's actual contents on disk
|
||||
$retreivedCryptedFile = $this->view->file_get_contents($this->userId . '/files/' . $filename);
|
||||
|
||||
// Check that the file was encrypted before being written to disk
|
||||
$this->assertNotEquals($this->dataLong . $this->dataLong, $retreivedCryptedFile);
|
||||
|
||||
// remove the header to check if we can also decrypt old files without a header,
|
||||
// this files should fall back to AES-128
|
||||
$cryptedWithoutHeader = substr($retreivedCryptedFile, Encryption\Crypt::BLOCKSIZE);
|
||||
$this->view->file_put_contents($this->userId . '/files/' . $filename, $cryptedWithoutHeader);
|
||||
|
||||
// Re-enable proxy - our work is done
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
$decrypted = file_get_contents('crypt:///' . $this->userId . '/files/'. $filename);
|
||||
|
||||
$this->assertEquals($this->dataLong . $this->dataLong, $decrypted);
|
||||
|
||||
// Teardown
|
||||
|
||||
// tear down
|
||||
$this->view->unlink($this->userId . '/files/' . $filename);
|
||||
|
||||
Encryption\Keymanager::deleteFileKey($this->view, $filename);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -353,7 +404,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
$this->assertFalse(Encryption\Crypt::isCatfileContent($this->legacyEncryptedData));
|
||||
|
||||
$keyfileContent = Encryption\Crypt::symmetricEncryptFileContent($this->dataUrl, 'hat');
|
||||
$keyfileContent = Encryption\Crypt::symmetricEncryptFileContent($this->dataUrl, 'hat', 'AES-128-CFB');
|
||||
|
||||
$this->assertTrue(Encryption\Crypt::isCatfileContent($keyfileContent));
|
||||
|
||||
|
||||
@@ -108,4 +108,60 @@ class Test_Encryption_Helper extends \PHPUnit_Framework_TestCase {
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Helper::TEST_ENCRYPTION_HELPER_USER1);
|
||||
}
|
||||
|
||||
function userNamesProvider() {
|
||||
return array(
|
||||
array('testuser' . uniqid()),
|
||||
array('user.name.with.dots'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether share keys can be found
|
||||
*
|
||||
* @dataProvider userNamesProvider
|
||||
*/
|
||||
function testFindShareKeys($userName) {
|
||||
// note: not using dataProvider as we want to make
|
||||
// sure that the correct keys are match and not any
|
||||
// other ones that might happen to have similar names
|
||||
\Test_Encryption_Util::setupHooks();
|
||||
\Test_Encryption_Util::loginHelper($userName, true);
|
||||
$testDir = 'testFindShareKeys' . uniqid() . '/';
|
||||
$baseDir = $userName . '/files/' . $testDir;
|
||||
$fileList = array(
|
||||
't est.txt',
|
||||
't est_.txt',
|
||||
't est.doc.txt',
|
||||
't est(.*).txt', // make sure the regexp is escaped
|
||||
'multiple.dots.can.happen.too.txt',
|
||||
't est.' . $userName . '.txt',
|
||||
't est_.' . $userName . '.shareKey.txt',
|
||||
'who would upload their.shareKey',
|
||||
'user ones file.txt',
|
||||
'user ones file.txt.backup',
|
||||
'.t est.txt'
|
||||
);
|
||||
|
||||
$rootView = new \OC\Files\View('/');
|
||||
$rootView->mkdir($baseDir);
|
||||
foreach ($fileList as $fileName) {
|
||||
$rootView->file_put_contents($baseDir . $fileName, 'dummy');
|
||||
}
|
||||
|
||||
$shareKeysDir = $userName . '/files_encryption/share-keys/' . $testDir;
|
||||
foreach ($fileList as $fileName) {
|
||||
// make sure that every file only gets its correct respective keys
|
||||
$result = Encryption\Helper::findShareKeys($baseDir . $fileName, $shareKeysDir . $fileName, $rootView);
|
||||
$this->assertEquals(
|
||||
array($shareKeysDir . $fileName . '.' . $userName . '.shareKey'),
|
||||
$result
|
||||
);
|
||||
}
|
||||
|
||||
// clean up
|
||||
$rootView->unlink($baseDir);
|
||||
\Test_Encryption_Util::logoutHelper();
|
||||
\OC_User::deleteUser($userName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,8 +36,8 @@ use OCA\Encryption;
|
||||
*/
|
||||
class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
const TEST_ENCRYPTION_HOOKS_USER1 = "test-encryption-hooks-user1";
|
||||
const TEST_ENCRYPTION_HOOKS_USER2 = "test-encryption-hooks-user2";
|
||||
const TEST_ENCRYPTION_HOOKS_USER1 = "test-encryption-hooks-user1.dot";
|
||||
const TEST_ENCRYPTION_HOOKS_USER2 = "test-encryption-hooks-user2.dot";
|
||||
|
||||
/**
|
||||
* @var \OC\Files\View
|
||||
@@ -49,7 +49,26 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase {
|
||||
public $filename;
|
||||
public $folder;
|
||||
|
||||
private static $testFiles;
|
||||
|
||||
public static function setUpBeforeClass() {
|
||||
// note: not using a data provider because these
|
||||
// files all need to coexist to make sure the
|
||||
// share keys are found properly (pattern matching)
|
||||
self::$testFiles = array(
|
||||
't est.txt',
|
||||
't est_.txt',
|
||||
't est.doc.txt',
|
||||
't est(.*).txt', // make sure the regexp is escaped
|
||||
'multiple.dots.can.happen.too.txt',
|
||||
't est.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.txt',
|
||||
't est_.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey.txt',
|
||||
'who would upload their.shareKey',
|
||||
'user ones file.txt',
|
||||
'user ones file.txt.backup',
|
||||
'.t est.txt'
|
||||
);
|
||||
|
||||
// reset backend
|
||||
\OC_User::clearBackends();
|
||||
\OC_User::useBackend('database');
|
||||
@@ -281,25 +300,33 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
function testRenameHook() {
|
||||
// create all files to make sure all keys can coexist properly
|
||||
foreach (self::$testFiles as $file) {
|
||||
// save file with content
|
||||
$cryptedFile = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $file, $this->data);
|
||||
|
||||
// test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
}
|
||||
|
||||
foreach (self::$testFiles as $file) {
|
||||
$this->doTestRenameHook($file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* test rename operation
|
||||
*/
|
||||
function testRenameHook() {
|
||||
|
||||
// save file with content
|
||||
$cryptedFile = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->filename, $this->data);
|
||||
|
||||
// test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
|
||||
function doTestRenameHook($filename) {
|
||||
// check if keys exists
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
|
||||
. $this->filename . '.key'));
|
||||
. $filename . '.key'));
|
||||
|
||||
// make subfolder and sub-subfolder
|
||||
$this->rootView->mkdir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
|
||||
@@ -310,31 +337,91 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase {
|
||||
// move the file to the sub-subfolder
|
||||
$root = $this->rootView->getRoot();
|
||||
$this->rootView->chroot('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/');
|
||||
$this->rootView->rename($this->filename, '/' . $this->folder . '/' . $this->folder . '/' . $this->filename);
|
||||
$this->rootView->rename($filename, '/' . $this->folder . '/' . $this->folder . '/' . $filename);
|
||||
$this->rootView->chroot($root);
|
||||
|
||||
$this->assertFalse($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->filename));
|
||||
$this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder . '/' . $this->filename));
|
||||
$this->assertFalse($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $filename));
|
||||
$this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder . '/' . $filename));
|
||||
|
||||
// keys should be renamed too
|
||||
$this->assertFalse($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
$this->assertFalse($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
|
||||
. $this->filename . '.key'));
|
||||
. $filename . '.key'));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/' . $this->folder . '/' . $this->folder . '/'
|
||||
. $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' . $this->folder . '/' . $this->folder . '/'
|
||||
. $this->filename . '.key'));
|
||||
. $filename . '.key'));
|
||||
|
||||
// cleanup
|
||||
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
|
||||
}
|
||||
|
||||
function testCopyHook() {
|
||||
// create all files to make sure all keys can coexist properly
|
||||
foreach (self::$testFiles as $file) {
|
||||
// save file with content
|
||||
$cryptedFile = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $file, $this->data);
|
||||
|
||||
// test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
}
|
||||
|
||||
foreach (self::$testFiles as $file) {
|
||||
$this->doTestCopyHook($file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* test rename operation
|
||||
*/
|
||||
function doTestCopyHook($filename) {
|
||||
// check if keys exists
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
|
||||
. $filename . '.key'));
|
||||
|
||||
// make subfolder and sub-subfolder
|
||||
$this->rootView->mkdir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
|
||||
$this->rootView->mkdir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder);
|
||||
|
||||
$this->assertTrue($this->rootView->is_dir('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder));
|
||||
|
||||
// copy the file to the sub-subfolder
|
||||
\OC\Files\Filesystem::copy($filename, '/' . $this->folder . '/' . $this->folder . '/' . $filename);
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $filename));
|
||||
$this->assertTrue($this->rootView->file_exists('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder . '/' . $this->folder . '/' . $filename));
|
||||
|
||||
// keys should be copied too
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
|
||||
. $filename . '.key'));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/' . $this->folder . '/' . $this->folder . '/'
|
||||
. $filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' . $this->folder . '/' . $this->folder . '/'
|
||||
. $filename . '.key'));
|
||||
|
||||
// cleanup
|
||||
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
|
||||
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief replacing encryption keys during password change should be allowed
|
||||
* until the user logged in for the first time
|
||||
|
||||
@@ -23,7 +23,7 @@ use OCA\Encryption;
|
||||
*/
|
||||
class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
const TEST_USER = "test-keymanager-user";
|
||||
const TEST_USER = "test-keymanager-user.dot";
|
||||
|
||||
public $userId;
|
||||
public $pass;
|
||||
@@ -107,7 +107,7 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
$key = Encryption\Keymanager::getPrivateKey($this->view, $this->userId);
|
||||
|
||||
$privateKey = Encryption\Crypt::symmetricDecryptFileContent($key, $this->pass);
|
||||
$privateKey = Encryption\Crypt::decryptPrivateKey($key, $this->pass);
|
||||
|
||||
$res = openssl_pkey_get_private($privateKey);
|
||||
|
||||
@@ -135,15 +135,25 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
$this->assertArrayHasKey('key', $sslInfo);
|
||||
}
|
||||
|
||||
function fileNameFromShareKeyProvider() {
|
||||
return array(
|
||||
array('file.user.shareKey', 'user', 'file'),
|
||||
array('file.name.with.dots.user.shareKey', 'user', 'file.name.with.dots'),
|
||||
array('file.name.user.with.dots.shareKey', 'user.with.dots', 'file.name'),
|
||||
array('file.txt', 'user', false),
|
||||
array('user.shareKey', 'user', false),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @small
|
||||
*
|
||||
* @dataProvider fileNameFromShareKeyProvider
|
||||
*/
|
||||
function testGetFilenameFromShareKey() {
|
||||
$this->assertEquals("file",
|
||||
\TestProtectedKeymanagerMethods::testGetFilenameFromShareKey("file.user.shareKey"));
|
||||
$this->assertEquals("file.name.with.dots",
|
||||
\TestProtectedKeymanagerMethods::testGetFilenameFromShareKey("file.name.with.dots.user.shareKey"));
|
||||
$this->assertFalse(\TestProtectedKeymanagerMethods::testGetFilenameFromShareKey("file.txt"));
|
||||
function testGetFilenameFromShareKey($fileName, $user, $expectedFileName) {
|
||||
$this->assertEquals($expectedFileName,
|
||||
\TestProtectedKeymanagerMethods::testGetFilenameFromShareKey($fileName, $user)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,6 +184,38 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
*/
|
||||
function testSetPrivateKey() {
|
||||
|
||||
$key = "dummy key";
|
||||
|
||||
Encryption\Keymanager::setPrivateKey($key, 'dummyUser');
|
||||
|
||||
$this->assertTrue($this->view->file_exists('/dummyUser/files_encryption/dummyUser.private.key'));
|
||||
|
||||
//clean up
|
||||
$this->view->deleteAll('/dummyUser');
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
*/
|
||||
function testSetPrivateSystemKey() {
|
||||
|
||||
$key = "dummy key";
|
||||
$keyName = "myDummyKey.private.key";
|
||||
|
||||
Encryption\Keymanager::setPrivateSystemKey($key, $keyName);
|
||||
|
||||
$this->assertTrue($this->view->file_exists('/owncloud_private_key/' . $keyName));
|
||||
|
||||
// clean up
|
||||
$this->view->unlink('/owncloud_private_key/' . $keyName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @medium
|
||||
*/
|
||||
@@ -189,7 +231,7 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
$this->assertArrayHasKey('key', $sslInfoPublic);
|
||||
|
||||
$privateKey = Encryption\Crypt::symmetricDecryptFileContent($keys['privateKey'], $this->pass);
|
||||
$privateKey = Encryption\Crypt::decryptPrivateKey($keys['privateKey'], $this->pass);
|
||||
|
||||
$resPrivate = openssl_pkey_get_private($privateKey);
|
||||
|
||||
@@ -217,6 +259,12 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.user1.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.test.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.test-keymanager-userxdot.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.userx.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.' . Test_Encryption_Keymanager::TEST_USER . '.userx.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.' . Test_Encryption_Keymanager::TEST_USER . '.user1.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file2.user2.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file2.user3.shareKey', 'data');
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder/file2.user3.shareKey', 'data');
|
||||
@@ -225,7 +273,7 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder/subsubfolder/file2.user3.shareKey', 'data');
|
||||
|
||||
// recursive delete share keys from user1 and user2
|
||||
Encryption\Keymanager::delShareKey($this->view, array('user1', 'user2', Test_Encryption_Keymanager::TEST_USER), '/folder1/');
|
||||
Encryption\Keymanager::delShareKey($this->view, array('user1', 'user2', Test_Encryption_Keymanager::TEST_USER), '/folder1/', Test_Encryption_Keymanager::TEST_USER);
|
||||
|
||||
// check if share keys from user1 and user2 are deleted
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
@@ -247,6 +295,23 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/subfolder/file2.user3.shareKey'));
|
||||
|
||||
// check if share keys for user or file with similar name
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.test.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.test-keymanager-userxdot.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.' . Test_Encryption_Keymanager::TEST_USER . '.userx.shareKey'));
|
||||
// FIXME: this case currently cannot be distinguished, needs further fixing
|
||||
/*
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.userx.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.user1.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/file1.' . Test_Encryption_Keymanager::TEST_USER . '.user1.shareKey'));
|
||||
*/
|
||||
|
||||
// owner key from existing file should still exists because the file is still there
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey'));
|
||||
@@ -274,7 +339,7 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
$this->view->file_put_contents('/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey', 'data');
|
||||
|
||||
// recursive delete share keys from user1 and user2
|
||||
Encryption\Keymanager::delShareKey($this->view, array('user1', 'user2', Test_Encryption_Keymanager::TEST_USER), '/folder1/existingFile.txt');
|
||||
Encryption\Keymanager::delShareKey($this->view, array('user1', 'user2', Test_Encryption_Keymanager::TEST_USER), '/folder1/existingFile.txt', Test_Encryption_Keymanager::TEST_USER);
|
||||
|
||||
// check if share keys from user1 and user2 are deleted
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
@@ -400,18 +465,19 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/existingFile.txt.user3.shareKey'));
|
||||
|
||||
// try to del all share keys froma file, should fail because the file still exists
|
||||
// try to del all share keys from file, should succeed because the does not exist any more
|
||||
$result2 = Encryption\Keymanager::delAllShareKeys($this->view, Test_Encryption_Keymanager::TEST_USER, 'folder1/nonexistingFile.txt');
|
||||
$this->assertTrue($result2);
|
||||
|
||||
// check if share keys are really gone
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.' . Test_Encryption_Keymanager::TEST_USER . '.shareKey'));
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
// check that it only deleted keys or users who had access, others remain
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.user1.shareKey'));
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.user2.shareKey'));
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/'.Test_Encryption_Keymanager::TEST_USER.'/files_encryption/share-keys/folder1/nonexistingFile.txt.user3.shareKey'));
|
||||
|
||||
// cleanup
|
||||
@@ -447,8 +513,8 @@ class TestProtectedKeymanagerMethods extends \OCA\Encryption\Keymanager {
|
||||
/**
|
||||
* @param string $sharekey
|
||||
*/
|
||||
public static function testGetFilenameFromShareKey($sharekey) {
|
||||
return self::getFilenameFromShareKey($sharekey);
|
||||
public static function testGetFilenameFromShareKey($sharekey, $user) {
|
||||
return self::getFilenameFromShareKey($sharekey, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -50,10 +50,44 @@ class Test_Migration extends PHPUnit_Framework_TestCase {
|
||||
|
||||
}
|
||||
|
||||
public function testDataMigration() {
|
||||
public function checkLastIndexId() {
|
||||
$query = \OC_DB::prepare('INSERT INTO `*PREFIX*share` ('
|
||||
.' `item_type`, `item_source`, `item_target`, `share_type`,'
|
||||
.' `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`,'
|
||||
.' `file_target`, `token`, `parent`, `expiration`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)');
|
||||
$query->bindValue(1, 'file');
|
||||
$query->bindValue(2, 949);
|
||||
$query->bindValue(3, '/949');
|
||||
$query->bindValue(4, 0);
|
||||
$query->bindValue(5, 'migrate-test-user');
|
||||
$query->bindValue(6, 'migrate-test-owner');
|
||||
$query->bindValue(7, 23);
|
||||
$query->bindValue(8, 1402493312);
|
||||
$query->bindValue(9, 0);
|
||||
$query->bindValue(10, '/migration.txt');
|
||||
$query->bindValue(11, null);
|
||||
$query->bindValue(12, null);
|
||||
$query->bindValue(13, null);
|
||||
$this->assertEquals(1, $query->execute());
|
||||
|
||||
//FIXME fix this test so that we can enable it again
|
||||
$this->markTestIncomplete('Disabled, because of this tests a lot of other tests fail at the moment');
|
||||
$this->assertNotEquals('0', \OC_DB::insertid('*PREFIX*share'));
|
||||
|
||||
// cleanup
|
||||
$query = \OC_DB::prepare('DELETE FROM `*PREFIX*share` WHERE `file_target` = ?');
|
||||
$query->bindValue(1, '/migration.txt');
|
||||
$this->assertEquals(1, $query->execute());
|
||||
|
||||
}
|
||||
|
||||
public function testBrokenLastIndexId() {
|
||||
|
||||
// create test table
|
||||
$this->checkLastIndexId();
|
||||
OC_DB::createDbFromStructure(__DIR__ . '/encryption_table.xml');
|
||||
$this->checkLastIndexId();
|
||||
}
|
||||
|
||||
public function testDataMigration() {
|
||||
|
||||
$this->assertTableNotExist('encryption_test');
|
||||
|
||||
@@ -80,9 +114,6 @@ class Test_Migration extends PHPUnit_Framework_TestCase {
|
||||
|
||||
public function testDuplicateDataMigration() {
|
||||
|
||||
//FIXME fix this test so that we can enable it again
|
||||
$this->markTestIncomplete('Disabled, because of this tests a lot of other tests fail at the moment');
|
||||
|
||||
// create test table
|
||||
OC_DB::createDbFromStructure(__DIR__ . '/encryption_table.xml');
|
||||
|
||||
|
||||
@@ -541,9 +541,9 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
. $this->filename . '.' . $publicShareKeyId . '.shareKey'));
|
||||
|
||||
// some hacking to simulate public link
|
||||
$GLOBALS['app'] = 'files_sharing';
|
||||
$GLOBALS['fileOwner'] = \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1;
|
||||
\OC_User::setUserId(false);
|
||||
//$GLOBALS['app'] = 'files_sharing';
|
||||
//$GLOBALS['fileOwner'] = \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1;
|
||||
\Test_Encryption_Util::logoutHelper();
|
||||
|
||||
// get file contents
|
||||
$retrievedCryptedFile = file_get_contents('crypt:///' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename);
|
||||
@@ -1016,4 +1016,109 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase {
|
||||
$this->view->unlink('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* test if additional share keys are added if we move a folder to a shared parent
|
||||
* @medium
|
||||
*/
|
||||
function testMoveFolder() {
|
||||
|
||||
$view = new \OC\Files\View('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1);
|
||||
|
||||
$filename = '/tmp-' . uniqid();
|
||||
$folder = '/folder' . uniqid();
|
||||
|
||||
\OC\Files\Filesystem::mkdir($folder);
|
||||
|
||||
// Save long data as encrypted file using stream wrapper
|
||||
$cryptedFile = \OC\Files\Filesystem::file_put_contents($folder . $filename, $this->dataShort);
|
||||
|
||||
// Test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
|
||||
// Get file decrypted contents
|
||||
$decrypt = \OC\Files\Filesystem::file_get_contents($folder . $filename);
|
||||
|
||||
$this->assertEquals($this->dataShort, $decrypt);
|
||||
|
||||
$newFolder = '/newfolder/subfolder' . uniqid();
|
||||
\OC\Files\Filesystem::mkdir('/newfolder');
|
||||
|
||||
// get the file info from previous created file
|
||||
$fileInfo = \OC\Files\Filesystem::getFileInfo('/newfolder');
|
||||
$this->assertTrue($fileInfo instanceof \OC\Files\FileInfo);
|
||||
|
||||
// share the folder
|
||||
\OCP\Share::shareItem('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, OCP\PERMISSION_ALL);
|
||||
|
||||
\OC\Files\Filesystem::rename($folder, $newFolder);
|
||||
|
||||
// Get file decrypted contents
|
||||
$newDecrypt = \OC\Files\Filesystem::file_get_contents($newFolder . $filename);
|
||||
$this->assertEquals($this->dataShort, $newDecrypt);
|
||||
|
||||
// check if additional share key for user2 exists
|
||||
$this->assertTrue($view->file_exists('files_encryption/share-keys' . $newFolder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
|
||||
|
||||
// check that old keys were removed/moved properly
|
||||
$this->assertFalse($view->file_exists('files_encryption/share-keys' . $folder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
|
||||
|
||||
// tear down
|
||||
\OC\Files\Filesystem::unlink($newFolder);
|
||||
\OC\Files\Filesystem::unlink('/newfolder');
|
||||
}
|
||||
|
||||
function testMoveFileToFolder() {
|
||||
|
||||
$view = new \OC\Files\View('/' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1);
|
||||
|
||||
$filename = '/tmp-' . uniqid();
|
||||
$folder = '/folder' . uniqid();
|
||||
|
||||
\OC\Files\Filesystem::mkdir($folder);
|
||||
|
||||
// Save long data as encrypted file using stream wrapper
|
||||
$cryptedFile = \OC\Files\Filesystem::file_put_contents($folder . $filename, $this->dataShort);
|
||||
|
||||
// Test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
|
||||
// Get file decrypted contents
|
||||
$decrypt = \OC\Files\Filesystem::file_get_contents($folder . $filename);
|
||||
|
||||
$this->assertEquals($this->dataShort, $decrypt);
|
||||
|
||||
$subFolder = $folder . '/subfolder' . uniqid();
|
||||
\OC\Files\Filesystem::mkdir($subFolder);
|
||||
|
||||
// get the file info from previous created file
|
||||
$fileInfo = \OC\Files\Filesystem::getFileInfo($folder);
|
||||
$this->assertTrue($fileInfo instanceof \OC\Files\FileInfo);
|
||||
|
||||
// share the folder
|
||||
\OCP\Share::shareItem('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2, OCP\PERMISSION_ALL);
|
||||
|
||||
// check that the share keys exist
|
||||
$this->assertTrue($view->file_exists('files_encryption/share-keys' . $folder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey'));
|
||||
$this->assertTrue($view->file_exists('files_encryption/share-keys' . $folder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
|
||||
|
||||
// move the file into the subfolder
|
||||
\OC\Files\Filesystem::rename($folder . $filename, $subFolder . $filename);
|
||||
|
||||
// Get file decrypted contents
|
||||
$newDecrypt = \OC\Files\Filesystem::file_get_contents($subFolder . $filename);
|
||||
$this->assertEquals($this->dataShort, $newDecrypt);
|
||||
|
||||
// check if additional share key for user2 exists
|
||||
$this->assertTrue($view->file_exists('files_encryption/share-keys' . $subFolder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey'));
|
||||
$this->assertTrue($view->file_exists('files_encryption/share-keys' . $subFolder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
|
||||
|
||||
// check that old keys were removed/moved properly
|
||||
$this->assertFalse($view->file_exists('files_encryption/share-keys' . $folder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey'));
|
||||
$this->assertFalse($view->file_exists('files_encryption/share-keys' . $folder . '/' . $filename . '.' . \Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
|
||||
|
||||
// tear down
|
||||
\OC\Files\Filesystem::unlink($subFolder);
|
||||
\OC\Files\Filesystem::unlink($folder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -120,24 +120,33 @@ class Test_Encryption_Trashbin extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
// generate filename
|
||||
$filename = 'tmp-' . uniqid() . '.txt';
|
||||
$filename2 = $filename . '.backup'; // a second file with similar name
|
||||
|
||||
// save file with content
|
||||
$cryptedFile = file_put_contents('crypt:///' .\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1. '/files/'. $filename, $this->dataShort);
|
||||
$cryptedFile2 = file_put_contents('crypt:///' .\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1. '/files/'. $filename2, $this->dataShort);
|
||||
|
||||
// test that data was successfully written
|
||||
$this->assertTrue(is_int($cryptedFile));
|
||||
$this->assertTrue(is_int($cryptedFile2));
|
||||
|
||||
// check if key for admin exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/' . $filename
|
||||
. '.key'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/' . $filename2
|
||||
. '.key'));
|
||||
|
||||
// check if share key for admin exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename2 . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
|
||||
// delete file
|
||||
// delete first file
|
||||
\OC\FIles\Filesystem::unlink($filename);
|
||||
|
||||
// check if file not exists
|
||||
@@ -154,6 +163,20 @@ class Test_Encryption_Trashbin extends \PHPUnit_Framework_TestCase {
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
|
||||
// check that second file still exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files/' . $filename2));
|
||||
|
||||
// check that key for second file still exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/' . $filename2
|
||||
. '.key'));
|
||||
|
||||
// check that share key for second file still exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename2 . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
|
||||
// get files
|
||||
$trashFiles = $this->view->getDirectoryContent(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/files/');
|
||||
@@ -179,41 +202,75 @@ class Test_Encryption_Trashbin extends \PHPUnit_Framework_TestCase {
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/share-keys/' . $filename
|
||||
. '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey.' . $trashFileSuffix));
|
||||
|
||||
// return filename for next test
|
||||
return $filename . '.' . $trashFileSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
* test restore file
|
||||
*
|
||||
* @depends testDeleteFile
|
||||
*/
|
||||
function testRestoreFile($filename) {
|
||||
function testRestoreFile() {
|
||||
// generate filename
|
||||
$filename = 'tmp-' . uniqid() . '.txt';
|
||||
$filename2 = $filename . '.backup'; // a second file with similar name
|
||||
|
||||
// save file with content
|
||||
$cryptedFile = file_put_contents('crypt:///' .\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1. '/files/'. $filename, $this->dataShort);
|
||||
$cryptedFile2 = file_put_contents('crypt:///' .\Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1. '/files/'. $filename2, $this->dataShort);
|
||||
|
||||
// delete both files
|
||||
\OC\Files\Filesystem::unlink($filename);
|
||||
\OC\Files\Filesystem::unlink($filename2);
|
||||
|
||||
$trashFiles = $this->view->getDirectoryContent(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_trashbin/files/');
|
||||
|
||||
$trashFileSuffix = null;
|
||||
$trashFileSuffix2 = null;
|
||||
// find created file with timestamp
|
||||
foreach ($trashFiles as $file) {
|
||||
if (strncmp($file['path'], $filename, strlen($filename))) {
|
||||
$path_parts = pathinfo($file['name']);
|
||||
$trashFileSuffix = $path_parts['extension'];
|
||||
}
|
||||
if (strncmp($file['path'], $filename2, strlen($filename2))) {
|
||||
$path_parts = pathinfo($file['name']);
|
||||
$trashFileSuffix2 = $path_parts['extension'];
|
||||
}
|
||||
}
|
||||
|
||||
// prepare file information
|
||||
$path_parts = pathinfo($filename);
|
||||
$trashFileSuffix = $path_parts['extension'];
|
||||
$timestamp = str_replace('d', '', $trashFileSuffix);
|
||||
$fileNameWithoutSuffix = str_replace('.' . $trashFileSuffix, '', $filename);
|
||||
|
||||
// restore file
|
||||
$this->assertTrue(\OCA\Files_Trashbin\Trashbin::restore($filename, $fileNameWithoutSuffix, $timestamp));
|
||||
// restore first file
|
||||
$this->assertTrue(\OCA\Files_Trashbin\Trashbin::restore($filename . '.' . $trashFileSuffix, $filename, $timestamp));
|
||||
|
||||
// check if file exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files/' . $fileNameWithoutSuffix));
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files/' . $filename));
|
||||
|
||||
// check if key for admin exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/'
|
||||
. $fileNameWithoutSuffix . '.key'));
|
||||
. $filename . '.key'));
|
||||
|
||||
// check if share key for admin exists
|
||||
$this->assertTrue($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
|
||||
. $fileNameWithoutSuffix . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
. $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
|
||||
// check that second file was NOT restored
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files/' . $filename2));
|
||||
|
||||
// check if key for admin exists
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/keyfiles/'
|
||||
. $filename2 . '.key'));
|
||||
|
||||
// check if share key for admin exists
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
'/' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '/files_encryption/share-keys/'
|
||||
. $filename2 . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,7 +299,7 @@ class Test_Encryption_Trashbin extends \PHPUnit_Framework_TestCase {
|
||||
. $filename . '.' . \Test_Encryption_Trashbin::TEST_ENCRYPTION_TRASHBIN_USER1 . '.shareKey'));
|
||||
|
||||
// delete file
|
||||
\OC\FIles\Filesystem::unlink($filename);
|
||||
\OC\Files\Filesystem::unlink($filename);
|
||||
|
||||
// check if file not exists
|
||||
$this->assertFalse($this->view->file_exists(
|
||||
|
||||
@@ -22,6 +22,9 @@ use OCA\Encryption;
|
||||
class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
const TEST_ENCRYPTION_UTIL_USER1 = "test-util-user1";
|
||||
const TEST_ENCRYPTION_UTIL_USER2 = "test-util-user2";
|
||||
const TEST_ENCRYPTION_UTIL_GROUP1 = "test-util-group1";
|
||||
const TEST_ENCRYPTION_UTIL_GROUP2 = "test-util-group2";
|
||||
const TEST_ENCRYPTION_UTIL_LEGACY_USER = "test-legacy-user";
|
||||
|
||||
public $userId;
|
||||
@@ -50,16 +53,19 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
\OC_User::clearBackends();
|
||||
\OC_User::useBackend('database');
|
||||
|
||||
// Filesystem related hooks
|
||||
\OCA\Encryption\Helper::registerFilesystemHooks();
|
||||
|
||||
// clear and register hooks
|
||||
\OC_FileProxy::clearProxies();
|
||||
\OC_FileProxy::register(new OCA\Encryption\Proxy());
|
||||
self::setupHooks();
|
||||
|
||||
// create test user
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1, true);
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER2, true);
|
||||
\Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER, true);
|
||||
|
||||
// create groups
|
||||
\OC_Group::createGroup(self::TEST_ENCRYPTION_UTIL_GROUP1);
|
||||
\OC_Group::createGroup(self::TEST_ENCRYPTION_UTIL_GROUP2);
|
||||
|
||||
// add user 1 to group1
|
||||
\OC_Group::addToGroup(self::TEST_ENCRYPTION_UTIL_USER1, self::TEST_ENCRYPTION_UTIL_GROUP1);
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +122,20 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
public static function tearDownAfterClass() {
|
||||
// cleanup test user
|
||||
\OC_User::deleteUser(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1);
|
||||
\OC_User::deleteUser(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER2);
|
||||
\OC_User::deleteUser(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER);
|
||||
//cleanup groups
|
||||
\OC_Group::deleteGroup(self::TEST_ENCRYPTION_UTIL_GROUP1);
|
||||
\OC_Group::deleteGroup(self::TEST_ENCRYPTION_UTIL_GROUP2);
|
||||
}
|
||||
|
||||
public static function setupHooks() {
|
||||
// Filesystem related hooks
|
||||
\OCA\Encryption\Helper::registerFilesystemHooks();
|
||||
|
||||
// clear and register hooks
|
||||
\OC_FileProxy::clearProxies();
|
||||
\OC_FileProxy::register(new OCA\Encryption\Proxy());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -411,6 +430,48 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* test if all keys get moved to the backup folder correctly
|
||||
*/
|
||||
function testBackupAllKeys() {
|
||||
self::loginHelper(self::TEST_ENCRYPTION_UTIL_USER1);
|
||||
|
||||
// create some dummy key files
|
||||
$encPath = '/' . self::TEST_ENCRYPTION_UTIL_USER1 . '/files_encryption';
|
||||
$this->view->file_put_contents($encPath . '/keyfiles/foo.key', 'key');
|
||||
$this->view->file_put_contents($encPath . '/share-keys/foo.user1.shareKey', 'share key');
|
||||
|
||||
$util = new \OCA\Encryption\Util($this->view, self::TEST_ENCRYPTION_UTIL_USER1);
|
||||
|
||||
$util->backupAllKeys('testing');
|
||||
|
||||
$encFolderContent = $this->view->getDirectoryContent($encPath);
|
||||
|
||||
$backupPath = '';
|
||||
foreach ($encFolderContent as $c) {
|
||||
$name = $c['name'];
|
||||
if (substr($name, 0, strlen('backup')) === 'backup') {
|
||||
$backupPath = $encPath . '/'. $c['name'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertTrue($backupPath !== '');
|
||||
|
||||
// check backupDir Content
|
||||
$this->assertTrue($this->view->is_dir($backupPath . '/keyfiles'));
|
||||
$this->assertTrue($this->view->is_dir($backupPath . '/share-keys'));
|
||||
$this->assertTrue($this->view->file_exists($backupPath . '/keyfiles/foo.key'));
|
||||
$this->assertTrue($this->view->file_exists($backupPath . '/share-keys/foo.user1.shareKey'));
|
||||
$this->assertTrue($this->view->file_exists($backupPath . '/' . self::TEST_ENCRYPTION_UTIL_USER1 . '.private.key'));
|
||||
$this->assertTrue($this->view->file_exists($backupPath . '/' . self::TEST_ENCRYPTION_UTIL_USER1 . '.public.key'));
|
||||
|
||||
//cleanup
|
||||
$this->view->deleteAll($backupPath);
|
||||
$this->view->unlink($encPath . '/keyfiles/foo.key', 'key');
|
||||
$this->view->unlink($encPath . '/share-keys/foo.user1.shareKey', 'share key');
|
||||
}
|
||||
|
||||
|
||||
function testDescryptAllWithBrokenFiles() {
|
||||
|
||||
@@ -537,6 +598,29 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
$this->assertTrue($found);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderFortestIsMountPointApplicableToUser
|
||||
*/
|
||||
function testIsMountPointApplicableToUser($mount, $expectedResult) {
|
||||
self::loginHelper(self::TEST_ENCRYPTION_UTIL_USER1);
|
||||
$dummyClass = new DummyUtilClass($this->view, self::TEST_ENCRYPTION_UTIL_USER1);
|
||||
$result = $dummyClass->testIsMountPointApplicableToUser($mount);
|
||||
|
||||
$this->assertSame($expectedResult, $result);
|
||||
}
|
||||
|
||||
function dataProviderFortestIsMountPointApplicableToUser() {
|
||||
return array(
|
||||
array(array('applicable' => array('groups' => array(), 'users' => array(self::TEST_ENCRYPTION_UTIL_USER1))), true),
|
||||
array(array('applicable' => array('groups' => array(), 'users' => array(self::TEST_ENCRYPTION_UTIL_USER2))), false),
|
||||
array(array('applicable' => array('groups' => array(self::TEST_ENCRYPTION_UTIL_GROUP1), 'users' => array())), true),
|
||||
array(array('applicable' => array('groups' => array(self::TEST_ENCRYPTION_UTIL_GROUP1), 'users' => array(self::TEST_ENCRYPTION_UTIL_USER2))), true),
|
||||
array(array('applicable' => array('groups' => array(self::TEST_ENCRYPTION_UTIL_GROUP2), 'users' => array(self::TEST_ENCRYPTION_UTIL_USER2))), false),
|
||||
array(array('applicable' => array('groups' => array(self::TEST_ENCRYPTION_UTIL_GROUP2), 'users' => array(self::TEST_ENCRYPTION_UTIL_USER2, 'all'))), true),
|
||||
array(array('applicable' => array('groups' => array(self::TEST_ENCRYPTION_UTIL_GROUP2), 'users' => array('all'))), true),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param bool $create
|
||||
@@ -570,7 +654,7 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
public static function logoutHelper() {
|
||||
\OC_Util::tearDownFS();
|
||||
\OC_User::setUserId('');
|
||||
\OC_User::setUserId(false);
|
||||
\OC\Files\Filesystem::tearDown();
|
||||
}
|
||||
|
||||
@@ -587,3 +671,12 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* dummy class extends \OCA\Encryption\Util to access protected methods for testing
|
||||
*/
|
||||
class DummyUtilClass extends \OCA\Encryption\Util {
|
||||
public function testIsMountPointApplicableToUser($mount) {
|
||||
return $this->isMountPointApplicableToUser($mount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,17 @@ if ($_POST['isPersonal'] == 'true') {
|
||||
OCP\JSON::checkAdminUser();
|
||||
$isPersonal = false;
|
||||
}
|
||||
$status = OC_Mount_Config::addMountPoint($_POST['mountPoint'],
|
||||
$_POST['class'],
|
||||
$_POST['classOptions'],
|
||||
$_POST['mountType'],
|
||||
$_POST['applicable'],
|
||||
$isPersonal);
|
||||
|
||||
$mountPoint = $_POST['mountPoint'];
|
||||
$oldMountPoint = $_POST['oldMountPoint'];
|
||||
$class = $_POST['class'];
|
||||
$options = $_POST['classOptions'];
|
||||
$type = $_POST['mountType'];
|
||||
$applicable = $_POST['applicable'];
|
||||
|
||||
if ($oldMountPoint and $oldMountPoint !== $mountPoint) {
|
||||
OC_Mount_Config::removeMountPoint($oldMountPoint, $type, $applicable, $isPersonal);
|
||||
}
|
||||
|
||||
$status = OC_Mount_Config::addMountPoint($mountPoint, $class, $options, $type, $applicable, $isPersonal);
|
||||
OCP\JSON::success(array('data' => array('message' => $status)));
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
OCP\JSON::checkAppEnabled('files_external');
|
||||
OCP\JSON::callCheck();
|
||||
|
||||
OCP\JSON::checkAdminUser();
|
||||
|
||||
$pattern = '';
|
||||
$limit = null;
|
||||
$offset = null;
|
||||
if (isset($_GET['pattern'])) {
|
||||
$pattern = $_GET['pattern'];
|
||||
}
|
||||
if (isset($_GET['limit'])) {
|
||||
$limit = $_GET['limit'];
|
||||
}
|
||||
if (isset($_GET['offset'])) {
|
||||
$offset = $_GET['offset'];
|
||||
}
|
||||
|
||||
$groups = \OC_Group::getGroups($pattern, $limit, $offset);
|
||||
$users = \OCP\User::getDisplayNames($pattern, $limit, $offset);
|
||||
|
||||
$results = array('groups' => $groups, 'users' => $users);
|
||||
|
||||
\OCP\JSON::success($results);
|
||||
@@ -10,4 +10,5 @@
|
||||
<types>
|
||||
<filesystem/>
|
||||
</types>
|
||||
<ocsid>166048</ocsid>
|
||||
</info>
|
||||
|
||||
@@ -20,8 +20,10 @@
|
||||
*
|
||||
*/
|
||||
|
||||
OC_API::register('get',
|
||||
'/apps/files_external/api/v1/mounts',
|
||||
array('\OCA\Files\External\Api', 'getUserMounts'),
|
||||
'files_external');
|
||||
$this->create('files_external_list_applicable', '/applicable')->actionInclude('files_external/ajax/applicable.php');
|
||||
|
||||
OC_API::register('get',
|
||||
'/apps/files_external/api/v1/mounts',
|
||||
array('\OCA\Files\External\Api', 'getUserMounts'),
|
||||
'files_external');
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.2
|
||||
0.2.1
|
||||
@@ -10,7 +10,20 @@ td.remove>img { visibility:hidden; padding-top:7px; }
|
||||
tr:hover>td.remove>img { visibility:visible; cursor:pointer; }
|
||||
#addMountPoint>td { border:none; }
|
||||
#addMountPoint>td.applicable { visibility:hidden; }
|
||||
#selectBackend { margin-left:-10px; }
|
||||
|
||||
#selectBackend {
|
||||
margin-left: -10px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
#externalStorage td.configuration,
|
||||
#externalStorage td.backend {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
#externalStorage td.configuration input.added {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
#externalStorage label > input[type="checkbox"] {
|
||||
margin-right: 3px;
|
||||
@@ -29,3 +42,19 @@ tr:hover>td.remove>img { visibility:visible; cursor:pointer; }
|
||||
#userMountingBackends {
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
#body-settings .select2-results .select2-result-label {
|
||||
height: 32px;
|
||||
padding: 3px;
|
||||
}
|
||||
.select2-results .select2-result-label .avatardiv {
|
||||
display:inline-block;
|
||||
}
|
||||
.select2-results .select2-result-label .avatardiv + span {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.select2-results .select2-result-label .avatardiv[data-type="group"] + span {
|
||||
vertical-align: top;
|
||||
top: 6px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,6 @@
|
||||
},
|
||||
|
||||
reload: function() {
|
||||
var self = this;
|
||||
this.showMask();
|
||||
if (this._reloadCall) {
|
||||
this._reloadCall.abort();
|
||||
@@ -78,14 +77,10 @@
|
||||
type: 'GET',
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader('OCS-APIREQUEST', 'true');
|
||||
},
|
||||
error: function(result) {
|
||||
self.reloadCallback(result);
|
||||
},
|
||||
success: function(result) {
|
||||
self.reloadCallback(result);
|
||||
}
|
||||
});
|
||||
var callBack = this.reloadCallback.bind(this);
|
||||
return this._reloadCall.then(callBack, callBack);
|
||||
},
|
||||
|
||||
reloadCallback: function(result) {
|
||||
@@ -109,6 +104,7 @@
|
||||
_makeFiles: function(data) {
|
||||
var files = _.map(data, function(fileData) {
|
||||
fileData.icon = OC.imagePath('core', 'filetypes/folder-external');
|
||||
fileData.mountType = 'external';
|
||||
return fileData;
|
||||
});
|
||||
|
||||
|
||||
@@ -11,9 +11,18 @@ function updateStatus(statusEl, result){
|
||||
}
|
||||
}
|
||||
|
||||
function getSelection($row) {
|
||||
var values = $row.find('.applicableUsers').select2('val');
|
||||
if (!values || values.length === 0) {
|
||||
values = ['all'];
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
OC.MountConfig={
|
||||
saveStorage:function(tr, callback) {
|
||||
var mountPoint = $(tr).find('.mountPoint input').val();
|
||||
var oldMountPoint = $(tr).find('.mountPoint input').data('mountpoint');
|
||||
if (mountPoint == '') {
|
||||
return false;
|
||||
}
|
||||
@@ -41,10 +50,7 @@ OC.MountConfig={
|
||||
}
|
||||
});
|
||||
if ($('#externalStorage').data('admin') === true) {
|
||||
var multiselect = $(tr).find('.chzn-select').val();
|
||||
if (multiselect == null) {
|
||||
return false;
|
||||
}
|
||||
var multiselect = getSelection($(tr));
|
||||
}
|
||||
if (addMountPoint) {
|
||||
var status = false;
|
||||
@@ -80,9 +86,11 @@ OC.MountConfig={
|
||||
classOptions: classOptions,
|
||||
mountType: mountType,
|
||||
applicable: applicable,
|
||||
isPersonal: isPersonal
|
||||
isPersonal: isPersonal,
|
||||
oldMountPoint: oldMountPoint
|
||||
},
|
||||
success: function(result) {
|
||||
$(tr).find('.mountPoint input').data('mountpoint', mountPoint);
|
||||
status = updateStatus(statusSpan, result);
|
||||
if (callback) {
|
||||
callback(status);
|
||||
@@ -139,9 +147,11 @@ OC.MountConfig={
|
||||
classOptions: classOptions,
|
||||
mountType: mountType,
|
||||
applicable: applicable,
|
||||
isPersonal: isPersonal
|
||||
isPersonal: isPersonal,
|
||||
oldMountPoint: oldMountPoint
|
||||
},
|
||||
success: function(result) {
|
||||
$(tr).find('.mountPoint input').data('mountpoint', mountPoint);
|
||||
status = updateStatus(statusSpan, result);
|
||||
if (callback) {
|
||||
callback(status);
|
||||
@@ -161,8 +171,139 @@ OC.MountConfig={
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
$('.chzn-select').chosen();
|
||||
//initialize hidden input field with list of users and groups
|
||||
$('#externalStorage').find('tr:not(#addMountPoint)').each(function(i,tr) {
|
||||
var applicable = $(tr).find('.applicable');
|
||||
if (applicable.length > 0) {
|
||||
var groups = applicable.data('applicable-groups');
|
||||
var groupsId = [];
|
||||
$.each(groups, function () {
|
||||
groupsId.push(this+"(group)");
|
||||
});
|
||||
var users = applicable.data('applicable-users');
|
||||
if (users.indexOf('all') > -1) {
|
||||
$(tr).find('.applicableUsers').val('');
|
||||
} else {
|
||||
$(tr).find('.applicableUsers').val(groupsId.concat(users).join(','));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var userListLimit = 30;
|
||||
function addSelect2 ($elements) {
|
||||
if ($elements.length > 0) {
|
||||
$elements.select2({
|
||||
placeholder: t('files_external', 'All users. Type to select user or group.'),
|
||||
allowClear: true,
|
||||
multiple: true,
|
||||
//minimumInputLength: 1,
|
||||
ajax: {
|
||||
url: OC.generateUrl('apps/files_external/applicable'),
|
||||
dataType: 'json',
|
||||
quietMillis: 100,
|
||||
data: function (term, page) { // page is the one-based page number tracked by Select2
|
||||
return {
|
||||
pattern: term, //search term
|
||||
limit: userListLimit, // page size
|
||||
offset: userListLimit*(page-1) // page number starts with 0
|
||||
};
|
||||
},
|
||||
results: function (data, page) {
|
||||
if (data.status === "success") {
|
||||
|
||||
var results = [];
|
||||
var userCount = 0; // users is an object
|
||||
|
||||
// add groups
|
||||
$.each(data.groups, function(i, group) {
|
||||
results.push({name:group+'(group)', displayname:group, type:'group' });
|
||||
});
|
||||
// add users
|
||||
$.each(data.users, function(id, user) {
|
||||
userCount++;
|
||||
results.push({name:id, displayname:user, type:'user' });
|
||||
});
|
||||
|
||||
|
||||
var more = (userCount >= userListLimit) || (data.groups.length >= userListLimit);
|
||||
return {results: results, more: more};
|
||||
} else {
|
||||
//FIXME add error handling
|
||||
}
|
||||
}
|
||||
},
|
||||
initSelection: function(element, callback) {
|
||||
|
||||
var promises = [];
|
||||
|
||||
var results = [];
|
||||
|
||||
$(element.val().split(",")).each(function (i,userId) {
|
||||
var def = new $.Deferred();
|
||||
promises.push(def.promise());
|
||||
|
||||
var pos = userId.indexOf('(group)');
|
||||
if (pos !== -1) {
|
||||
//add as group
|
||||
results.push({name:userId, displayname:userId.substr(0, pos), type:'group'});
|
||||
def.resolve();
|
||||
} else {
|
||||
$.ajax(OC.generateUrl('apps/files_external/applicable'), {
|
||||
data: {
|
||||
pattern: userId
|
||||
},
|
||||
dataType: "json"
|
||||
}).done(function(data) {
|
||||
if (data.status === "success") {
|
||||
if (data.users[userId]) {
|
||||
results.push({name:userId, displayname:data.users[userId], type:'user'});
|
||||
}
|
||||
def.resolve();
|
||||
} else {
|
||||
//FIXME add error handling
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
$.when.apply(undefined, promises).then(function(){
|
||||
callback(results);
|
||||
});
|
||||
},
|
||||
id: function(element) {
|
||||
return element.name;
|
||||
},
|
||||
formatResult: function (element) {
|
||||
var $result = $('<span><div class="avatardiv"/><span>'+escapeHTML(element.displayname)+'</span></span>');
|
||||
var $div = $result.find('.avatardiv')
|
||||
.attr('data-type', element.type)
|
||||
.attr('data-name', element.name)
|
||||
.attr('data-displayname', element.displayname);
|
||||
if (element.type === 'group') {
|
||||
var url = OC.imagePath('core','places/contacts-dark'); // TODO better group icon
|
||||
$div.html('<img width="32" height="32" src="'+url+'">');
|
||||
}
|
||||
return $result.get(0).outerHTML;
|
||||
},
|
||||
formatSelection: function (element) {
|
||||
if (element.type === 'group') {
|
||||
return '<span title="'+escapeHTML(element.name)+'" class="group">'+escapeHTML(element.displayname+' '+t('files_external', '(group)'))+'</span>';
|
||||
} else {
|
||||
return '<span title="'+escapeHTML(element.name)+'" class="user">'+escapeHTML(element.displayname)+'</span>';
|
||||
}
|
||||
},
|
||||
escapeMarkup: function (m) { return m; } // we escape the markup in formatResult and formatSelection
|
||||
}).on("select2-loaded", function() {
|
||||
$.each($(".avatardiv"), function(i, div) {
|
||||
$div = $(div);
|
||||
if ($div.data('type') === 'user') {
|
||||
$div.avatar($div.data('name'),32);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
addSelect2($('tr:not(#addMountPoint) .applicableUsers'));
|
||||
|
||||
$('#externalStorage').on('change', '#selectBackend', function() {
|
||||
var tr = $(this).parent().parent();
|
||||
$('#externalStorage tbody').append($(tr).clone());
|
||||
@@ -187,15 +328,15 @@ $(document).ready(function() {
|
||||
placeholder = placeholder.substring(1);
|
||||
}
|
||||
if (placeholder.indexOf('*') === 0) {
|
||||
var class_string = is_optional ? ' class="optional"' : '';
|
||||
td.append('<input type="password"' + class_string + ' data-parameter="'+parameter+'" placeholder="'+placeholder.substring(1)+'" />');
|
||||
var class_string = is_optional ? ' optional' : '';
|
||||
td.append('<input type="password" class="added' + class_string + '" data-parameter="'+parameter+'" placeholder="'+placeholder.substring(1)+'" />');
|
||||
} else if (placeholder.indexOf('!') === 0) {
|
||||
td.append('<label><input type="checkbox" data-parameter="'+parameter+'" />'+placeholder.substring(1)+'</label>');
|
||||
td.append('<label><input type="checkbox" class="added" data-parameter="'+parameter+'" />'+placeholder.substring(1)+'</label>');
|
||||
} else if (placeholder.indexOf('#') === 0) {
|
||||
td.append('<input type="hidden" data-parameter="'+parameter+'" />');
|
||||
td.append('<input type="hidden" class="added" data-parameter="'+parameter+'" />');
|
||||
} else {
|
||||
var class_string = is_optional ? ' class="optional"' : '';
|
||||
td.append('<input type="text"' + class_string + ' data-parameter="'+parameter+'" placeholder="'+placeholder+'" />');
|
||||
var class_string = is_optional ? ' optional' : '';
|
||||
td.append('<input type="text" class="added' + class_string + '" data-parameter="'+parameter+'" placeholder="'+placeholder+'" />');
|
||||
}
|
||||
});
|
||||
if (parameters['custom'] && $('#externalStorage tbody tr.'+backendClass.replace(/\\/g, '\\\\')).length == 1) {
|
||||
@@ -204,15 +345,11 @@ $(document).ready(function() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// Reset chosen
|
||||
var chosen = $(tr).find('.applicable select');
|
||||
chosen.parent().find('div').remove();
|
||||
chosen.removeAttr('id').removeClass('chzn-done').css({display:'inline-block'});
|
||||
chosen.chosen();
|
||||
$(tr).find('td').last().attr('class', 'remove');
|
||||
$(tr).find('td').last().removeAttr('style');
|
||||
$(tr).removeAttr('id');
|
||||
$(this).remove();
|
||||
addSelect2($('tr:not(#addMountPoint) .applicableUsers'));
|
||||
});
|
||||
|
||||
function suggestMountPoint(defaultMountPoint) {
|
||||
@@ -265,8 +402,8 @@ $(document).ready(function() {
|
||||
OC.MountConfig.saveStorage($(this).parent().parent().parent());
|
||||
});
|
||||
|
||||
$('#externalStorage').on('change', '.applicable .chzn-select', function() {
|
||||
OC.MountConfig.saveStorage($(this).parent().parent());
|
||||
$('#externalStorage').on('change', '.applicable', function() {
|
||||
OC.MountConfig.saveStorage($(this).parent());
|
||||
});
|
||||
|
||||
$('#sslCertificate').on('click', 'td.remove>img', function() {
|
||||
@@ -283,20 +420,18 @@ $(document).ready(function() {
|
||||
|
||||
if ($('#externalStorage').data('admin') === true) {
|
||||
var isPersonal = false;
|
||||
var multiselect = $(tr).find('.chzn-select').val();
|
||||
if (multiselect != null) {
|
||||
$.each(multiselect, function(index, value) {
|
||||
var pos = value.indexOf('(group)');
|
||||
if (pos != -1) {
|
||||
var mountType = 'group';
|
||||
var applicable = value.substr(0, pos);
|
||||
} else {
|
||||
var mountType = 'user';
|
||||
var applicable = value;
|
||||
}
|
||||
$.post(OC.filePath('files_external', 'ajax', 'removeMountPoint.php'), { mountPoint: mountPoint, mountType: mountType, applicable: applicable, isPersonal: isPersonal });
|
||||
});
|
||||
}
|
||||
var multiselect = getSelection($(tr));
|
||||
$.each(multiselect, function(index, value) {
|
||||
var pos = value.indexOf('(group)');
|
||||
if (pos != -1) {
|
||||
var mountType = 'group';
|
||||
var applicable = value.substr(0, pos);
|
||||
} else {
|
||||
var mountType = 'user';
|
||||
var applicable = value;
|
||||
}
|
||||
$.post(OC.filePath('files_external', 'ajax', 'removeMountPoint.php'), { mountPoint: mountPoint, mountType: mountType, applicable: applicable, isPersonal: isPersonal });
|
||||
});
|
||||
} else {
|
||||
var mountType = 'user';
|
||||
var applicable = OC.currentUser;
|
||||
|
||||
@@ -56,6 +56,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return string correctly encoded path
|
||||
*/
|
||||
private function normalizePath($path) {
|
||||
$path = trim($path, '/');
|
||||
@@ -73,6 +74,13 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanKey($path) {
|
||||
if ($path === '.') {
|
||||
return '/';
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function __construct($params) {
|
||||
if (!isset($params['key']) || !isset($params['secret']) || !isset($params['bucket'])) {
|
||||
throw new \Exception("Access Key, Secret and Bucket have to be configured.");
|
||||
@@ -83,13 +91,13 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
$this->bucket = $params['bucket'];
|
||||
$scheme = ($params['use_ssl'] === 'false') ? 'http' : 'https';
|
||||
$this->test = isset($params['test']);
|
||||
$this->timeout = ( ! isset($params['timeout'])) ? 15 : $params['timeout'];
|
||||
$params['region'] = ( ! isset($params['region']) || $params['region'] === '' ) ? 'eu-west-1' : $params['region'];
|
||||
$params['hostname'] = ( !isset($params['hostname']) || $params['hostname'] === '' ) ? 's3.amazonaws.com' : $params['hostname'];
|
||||
$this->timeout = (!isset($params['timeout'])) ? 15 : $params['timeout'];
|
||||
$params['region'] = (!isset($params['region']) || $params['region'] === '') ? 'eu-west-1' : $params['region'];
|
||||
$params['hostname'] = (!isset($params['hostname']) || $params['hostname'] === '') ? 's3.amazonaws.com' : $params['hostname'];
|
||||
if (!isset($params['port']) || $params['port'] === '') {
|
||||
$params['port'] = ($params['use_ssl'] === 'false') ? 80 : 443;
|
||||
}
|
||||
$base_url = $scheme.'://'.$params['hostname'].':'.$params['port'].'/';
|
||||
$base_url = $scheme . '://' . $params['hostname'] . ':' . $params['port'] . '/';
|
||||
|
||||
$this->connection = S3Client::factory(array(
|
||||
'key' => $params['key'],
|
||||
@@ -112,18 +120,17 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
'waiter.interval' => 1,
|
||||
'waiter.max_attempts' => 15
|
||||
));
|
||||
$this->testTimeout();
|
||||
$this->testTimeout();
|
||||
} catch (S3Exception $e) {
|
||||
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
|
||||
throw new \Exception("Creation of bucket failed.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->file_exists('.')) {
|
||||
$result = $this->connection->putObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => '.',
|
||||
'Body' => '',
|
||||
'Key' => $this->cleanKey('.'),
|
||||
'Body' => '',
|
||||
'ContentType' => 'httpd/unix-directory',
|
||||
'ContentLength' => 0
|
||||
));
|
||||
@@ -139,10 +146,10 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->connection->putObject(array(
|
||||
$this->connection->putObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $path . '/',
|
||||
'Body' => '',
|
||||
'Key' => $path . '/',
|
||||
'Body' => '',
|
||||
'ContentType' => 'httpd/unix-directory',
|
||||
'ContentLength' => 0
|
||||
));
|
||||
@@ -167,7 +174,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
try {
|
||||
$result = $this->connection->doesObjectExist(
|
||||
$this->bucket,
|
||||
$path
|
||||
$this->cleanKey($path)
|
||||
);
|
||||
} catch (S3Exception $e) {
|
||||
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
|
||||
@@ -181,30 +188,25 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
public function rmdir($path) {
|
||||
$path = $this->normalizePath($path);
|
||||
|
||||
if ($path === '.') {
|
||||
return $this->clearBucket();
|
||||
}
|
||||
|
||||
if (!$this->file_exists($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dh = $this->opendir($path);
|
||||
|
||||
if(is_resource($dh)) {
|
||||
while (($file = readdir($dh)) !== false) {
|
||||
if ($file === '.' || $file === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->is_dir($path . '/' . $file)) {
|
||||
$this->rmdir($path . '/' . $file);
|
||||
} else {
|
||||
$this->unlink($path . '/' . $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Since there are no real directories on S3, we need
|
||||
// to delete all objects prefixed with the path.
|
||||
$objects = $this->connection->listObjects(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Prefix' => $path . '/'
|
||||
));
|
||||
|
||||
try {
|
||||
$result = $this->connection->deleteObject(array(
|
||||
$this->connection->deleteObjects(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $path . '/'
|
||||
'Objects' => $objects['Contents']
|
||||
));
|
||||
$this->testTimeout();
|
||||
} catch (S3Exception $e) {
|
||||
@@ -215,6 +217,28 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function clearBucket() {
|
||||
try {
|
||||
$this->connection->clearBucket($this->bucket);
|
||||
// clearBucket() is not working with Ceph, so if it fails we try the slower approach
|
||||
} catch (\Exception $e) {
|
||||
try {
|
||||
$iterator = $this->connection->getIterator('ListObjects', array(
|
||||
'Bucket' => $this->bucket
|
||||
));
|
||||
|
||||
foreach ($iterator as $object) {
|
||||
$this->connection->deleteObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $object['Key']
|
||||
));
|
||||
}
|
||||
} catch (S3Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function opendir($path) {
|
||||
$path = $this->normalizePath($path);
|
||||
|
||||
@@ -261,7 +285,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
|
||||
$result = $this->connection->headObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $path
|
||||
'Key' => $this->cleanKey($path)
|
||||
));
|
||||
|
||||
$stat = array();
|
||||
@@ -274,7 +298,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
$stat['atime'] = time();
|
||||
|
||||
return $stat;
|
||||
} catch(S3Exception $e) {
|
||||
} catch (S3Exception $e) {
|
||||
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
|
||||
return false;
|
||||
}
|
||||
@@ -291,8 +315,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
if ($path != '.') {
|
||||
$path .= '/';
|
||||
}
|
||||
|
||||
if ($this->connection->doesObjectExist($this->bucket, $path)) {
|
||||
if ($this->connection->doesObjectExist($this->bucket, $this->cleanKey($path))) {
|
||||
return 'dir';
|
||||
}
|
||||
} catch (S3Exception $e) {
|
||||
@@ -306,10 +329,14 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
public function unlink($path) {
|
||||
$path = $this->normalizePath($path);
|
||||
|
||||
if ($this->is_dir($path)) {
|
||||
return $this->rmdir($path);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->connection->deleteObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $path
|
||||
'Key' => $this->cleanKey($path)
|
||||
));
|
||||
$this->testTimeout();
|
||||
} catch (S3Exception $e) {
|
||||
@@ -332,7 +359,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
try {
|
||||
$result = $this->connection->getObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $path,
|
||||
'Key' => $this->cleanKey($path),
|
||||
'SaveAs' => $tmpFile
|
||||
));
|
||||
} catch (S3Exception $e) {
|
||||
@@ -380,7 +407,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
try {
|
||||
$result = $this->connection->headObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $path
|
||||
'Key' => $this->cleanKey($path)
|
||||
));
|
||||
} catch (S3Exception $e) {
|
||||
\OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
|
||||
@@ -407,7 +434,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
}
|
||||
$result = $this->connection->copyObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $path,
|
||||
'Key' => $this->cleanKey($path),
|
||||
'Metadata' => $metadata,
|
||||
'CopySource' => $this->bucket . '/' . $path
|
||||
));
|
||||
@@ -415,7 +442,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
} else {
|
||||
$result = $this->connection->putObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $path,
|
||||
'Key' => $this->cleanKey($path),
|
||||
'Metadata' => $metadata
|
||||
));
|
||||
$this->testTimeout();
|
||||
@@ -436,8 +463,8 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
try {
|
||||
$result = $this->connection->copyObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $path2,
|
||||
'CopySource' => $this->bucket . '/' . $path1
|
||||
'Key' => $this->cleanKey($path2),
|
||||
'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1)
|
||||
));
|
||||
$this->testTimeout();
|
||||
} catch (S3Exception $e) {
|
||||
@@ -445,15 +472,17 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if ($this->file_exists($path2)) {
|
||||
return false;
|
||||
if ($this->is_dir($path2)) {
|
||||
$this->rmdir($path2);
|
||||
} else if ($this->file_exists($path2)) {
|
||||
$this->unlink($path2);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->connection->copyObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $path2 . '/',
|
||||
'CopySource' => $this->bucket . '/' . $path1 . '/'
|
||||
'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/')
|
||||
));
|
||||
$this->testTimeout();
|
||||
} catch (S3Exception $e) {
|
||||
@@ -462,7 +491,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
}
|
||||
|
||||
$dh = $this->opendir($path1);
|
||||
if(is_resource($dh)) {
|
||||
if (is_resource($dh)) {
|
||||
while (($file = readdir($dh)) !== false) {
|
||||
if ($file === '.' || $file === '..') {
|
||||
continue;
|
||||
@@ -483,6 +512,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
$path2 = $this->normalizePath($path2);
|
||||
|
||||
if ($this->is_file($path1)) {
|
||||
|
||||
if ($this->copy($path1, $path2) === false) {
|
||||
return false;
|
||||
}
|
||||
@@ -492,9 +522,6 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if ($this->file_exists($path2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->copy($path1, $path2) === false) {
|
||||
return false;
|
||||
@@ -533,9 +560,9 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
}
|
||||
|
||||
try {
|
||||
$result= $this->connection->putObject(array(
|
||||
$result = $this->connection->putObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => self::$tmpFiles[$tmpFile],
|
||||
'Key' => $this->cleanKey(self::$tmpFiles[$tmpFile]),
|
||||
'SourceFile' => $tmpFile,
|
||||
'ContentType' => \OC_Helper::getMimeType($tmpFile),
|
||||
'ContentLength' => filesize($tmpFile)
|
||||
|
||||
@@ -169,6 +169,7 @@ class OC_Mount_Config {
|
||||
foreach ($options as &$option) {
|
||||
$option = self::setUserVars($user, $option);
|
||||
}
|
||||
$options['personal'] = false;
|
||||
$options['options'] = self::decryptPasswords($options['options']);
|
||||
if (!isset($options['priority'])) {
|
||||
$options['priority'] = $backends[$options['class']]['priority'];
|
||||
|
||||
@@ -7,17 +7,35 @@
|
||||
*/
|
||||
namespace OC\Files\Storage;
|
||||
|
||||
/**
|
||||
* Uses phpseclib's Net_SFTP class and the Net_SFTP_Stream stream wrapper to
|
||||
* provide access to SFTP servers.
|
||||
*/
|
||||
class SFTP extends \OC\Files\Storage\Common {
|
||||
private $host;
|
||||
private $user;
|
||||
private $password;
|
||||
private $root;
|
||||
|
||||
/**
|
||||
* @var \Net_SFTP
|
||||
*/
|
||||
private $client;
|
||||
|
||||
private static $tempFiles = array();
|
||||
|
||||
public function __construct($params) {
|
||||
// The sftp:// scheme has to be manually registered via inclusion of
|
||||
// the 'Net/SFTP/Stream.php' file which registers the Net_SFTP_Stream
|
||||
// stream wrapper as a side effect.
|
||||
// A slightly better way to register the stream wrapper is available
|
||||
// since phpseclib 0.3.7 in the form of a static call to
|
||||
// Net_SFTP_Stream::register() which will trigger autoloading if
|
||||
// necessary.
|
||||
// TODO: Call Net_SFTP_Stream::register() instead when phpseclib is
|
||||
// updated to 0.3.7 or higher.
|
||||
require_once 'Net/SFTP/Stream.php';
|
||||
|
||||
$this->host = $params['host'];
|
||||
$proto = strpos($this->host, '://');
|
||||
if ($proto != false) {
|
||||
@@ -39,12 +57,8 @@ class SFTP extends \OC\Files\Storage\Common {
|
||||
$hostKeys = $this->readHostKeys();
|
||||
$this->client = new \Net_SFTP($this->host);
|
||||
|
||||
if (!$this->client->login($this->user, $this->password)) {
|
||||
throw new \Exception('Login failed');
|
||||
}
|
||||
|
||||
// The SSH Host Key MUST be verified before login().
|
||||
$currentHostKey = $this->client->getServerPublicHostKey();
|
||||
|
||||
if (array_key_exists($this->host, $hostKeys)) {
|
||||
if ($hostKeys[$this->host] != $currentHostKey) {
|
||||
throw new \Exception('Host public key does not match known key');
|
||||
@@ -53,6 +67,10 @@ class SFTP extends \OC\Files\Storage\Common {
|
||||
$hostKeys[$this->host] = $currentHostKey;
|
||||
$this->writeHostKeys($hostKeys);
|
||||
}
|
||||
|
||||
if (!$this->client->login($this->user, $this->password)) {
|
||||
throw new \Exception('Login failed');
|
||||
}
|
||||
}
|
||||
|
||||
public function test() {
|
||||
@@ -216,8 +234,8 @@ class SFTP extends \OC\Files\Storage\Common {
|
||||
case 'x+':
|
||||
case 'c':
|
||||
case 'c+':
|
||||
// FIXME: make client login lazy to prevent it when using fopen()
|
||||
return fopen($this->constructUrl($path), $mode);
|
||||
$context = stream_context_create(array('sftp' => array('session' => $this->client)));
|
||||
return fopen($this->constructUrl($path), $mode, false, $context);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
@@ -279,7 +297,10 @@ class SFTP extends \OC\Files\Storage\Common {
|
||||
* @param string $path
|
||||
*/
|
||||
public function constructUrl($path) {
|
||||
$url = 'sftp://'.$this->user.':'.$this->password.'@'.$this->host.$this->root.$path;
|
||||
// Do not pass the password here. We want to use the Net_SFTP object
|
||||
// supplied via stream context or fail. We only supply username and
|
||||
// hostname because this might show up in logs (they are not used).
|
||||
$url = 'sftp://'.$this->user.'@'.$this->host.$this->root.$path;
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,10 @@
|
||||
OC_Util::checkAdminUser();
|
||||
|
||||
OCP\Util::addScript('files_external', 'settings');
|
||||
OCP\Util::addscript('3rdparty', 'chosen/chosen.jquery.min');
|
||||
OCP\Util::addStyle('files_external', 'settings');
|
||||
OCP\Util::addStyle('3rdparty', 'chosen/chosen');
|
||||
|
||||
OCP\Util::addScript('core', 'select2/select2');
|
||||
OCP\Util::addStyle('core', 'select2/select2');
|
||||
|
||||
$backends = OC_Mount_Config::getBackends();
|
||||
$personal_backends = array();
|
||||
@@ -46,9 +47,6 @@ $tmpl->assign('isAdminPage', true);
|
||||
$tmpl->assign('mounts', OC_Mount_Config::getSystemMountPoints());
|
||||
$tmpl->assign('backends', $backends);
|
||||
$tmpl->assign('personal_backends', $personal_backends);
|
||||
$tmpl->assign('groups', OC_Group::getGroups());
|
||||
$tmpl->assign('users', OCP\User::getUsers());
|
||||
$tmpl->assign('userDisplayNames', OC_User::getDisplayNames());
|
||||
$tmpl->assign('dependencies', OC_Mount_Config::checkDependencies());
|
||||
$tmpl->assign('allowUserMounting', OCP\Config::getAppValue('files_external', 'allow_user_mounting', 'yes'));
|
||||
return $tmpl->fetchPage();
|
||||
|
||||
@@ -8,12 +8,11 @@
|
||||
<th><?php p($l->t('Folder name')); ?></th>
|
||||
<th><?php p($l->t('External storage')); ?></th>
|
||||
<th><?php p($l->t('Configuration')); ?></th>
|
||||
<!--<th><?php p($l->t('Options')); ?></th> -->
|
||||
<?php if ($_['isAdminPage']) print_unescaped('<th>'.$l->t('Available for').'</th>'); ?>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody width="100%">
|
||||
<tbody>
|
||||
<?php $_['mounts'] = array_merge($_['mounts'], array('' => array())); ?>
|
||||
<?php foreach ($_['mounts'] as $mount): ?>
|
||||
<tr <?php print_unescaped(isset($mount['mountpoint']) ? 'class="'.OC_Util::sanitizeHTML($mount['class']).'"' : 'id="addMountPoint"'); ?>>
|
||||
@@ -24,7 +23,9 @@
|
||||
</td>
|
||||
<td class="mountPoint"><input type="text" name="mountPoint"
|
||||
value="<?php p(isset($mount['mountpoint']) ? $mount['mountpoint'] : ''); ?>"
|
||||
placeholder="<?php p($l->t('Folder name')); ?>" /></td>
|
||||
data-mountpoint="<?php p(isset($mount['mountpoint']) ? $mount['mountpoint'] : ''); ?>"
|
||||
placeholder="<?php p($l->t('Folder name')); ?>" />
|
||||
</td>
|
||||
<?php if (!isset($mount['mountpoint'])): ?>
|
||||
<td class="backend">
|
||||
<select id="selectBackend" data-configurations='<?php p(json_encode($_['backends'])); ?>'>
|
||||
@@ -36,10 +37,10 @@
|
||||
</select>
|
||||
</td>
|
||||
<?php else: ?>
|
||||
<td class="backend"
|
||||
data-class="<?php p($mount['class']); ?>"><?php p($mount['backend']); ?></td>
|
||||
<td class="backend" data-class="<?php p($mount['class']); ?>"><?php p($mount['backend']); ?>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
<td class ="configuration" width="100%">
|
||||
<td class ="configuration">
|
||||
<?php if (isset($mount['options'])): ?>
|
||||
<?php foreach ($mount['options'] as $parameter => $value): ?>
|
||||
<?php if (isset($_['backends'][$mount['class']]['configuration'][$parameter])): ?>
|
||||
@@ -87,31 +88,8 @@
|
||||
print_unescaped(json_encode($mount['applicable']['groups'])); ?>'
|
||||
data-applicable-users='<?php if (isset($mount['applicable']['users']))
|
||||
print_unescaped(json_encode($mount['applicable']['users'])); ?>'>
|
||||
<select class="chzn-select"
|
||||
multiple style="width:20em;"
|
||||
data-placeholder="<?php p($l->t('No user or group')); ?>">
|
||||
<option value="all"
|
||||
<?php if (empty($mount['class']) || (isset($mount['applicable']['users']) && in_array('all', $mount['applicable']['users']))) print_unescaped('selected="selected"');?> >
|
||||
<?php p($l->t('All Users')); ?>
|
||||
</option>
|
||||
<optgroup label="<?php p($l->t('Groups')); ?>">
|
||||
<?php foreach ($_['groups'] as $group): ?>
|
||||
<option value="<?php p($group); ?>(group)"
|
||||
<?php if (isset($mount['applicable']['groups']) && in_array($group, $mount['applicable']['groups'])): ?>
|
||||
selected="selected"
|
||||
<?php endif; ?>><?php p($group); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</optgroup>
|
||||
<optgroup label="<?php p($l->t('Users')); ?>">
|
||||
<?php foreach ($_['users'] as $user): ?>
|
||||
<option value="<?php p($user); ?>"
|
||||
<?php if (isset($mount['applicable']['users']) && in_array($user, $mount['applicable']['users'])): ?>
|
||||
selected="selected"
|
||||
<?php endif; ?>><?php p($_['userDisplayNames'][$user]); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</optgroup>
|
||||
</select>
|
||||
</td>
|
||||
<input type="hidden" class="applicableUsers" style="width:20em;" value=""/>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
<td <?php if (isset($mount['mountpoint'])): ?>class="remove"
|
||||
<?php else: ?>style="visibility:hidden;"
|
||||
@@ -149,7 +127,7 @@
|
||||
action="<?php p(OCP\Util::linkTo('files_external', 'ajax/addRootCertificate.php')); ?>">
|
||||
<h2><?php p($l->t('SSL root certificates'));?></h2>
|
||||
<table id="sslCertificate" data-admin='<?php print_unescaped(json_encode($_['isAdminPage'])); ?>'>
|
||||
<tbody width="100%">
|
||||
<tbody>
|
||||
<?php foreach ($_['certs'] as $rootCert): ?>
|
||||
<tr id="<?php p($rootCert) ?>">
|
||||
<td class="rootCert"><?php p($rootCert) ?></td>
|
||||
|
||||
@@ -38,29 +38,11 @@ class AmazonS3 extends Storage {
|
||||
|
||||
public function tearDown() {
|
||||
if ($this->instance) {
|
||||
$connection = $this->instance->getConnection();
|
||||
|
||||
try {
|
||||
// NOTE(berendt): clearBucket() is not working with Ceph
|
||||
$iterator = $connection->getIterator('ListObjects', array(
|
||||
'Bucket' => $this->config['amazons3']['bucket']
|
||||
));
|
||||
|
||||
foreach ($iterator as $object) {
|
||||
$connection->deleteObject(array(
|
||||
'Bucket' => $this->config['amazons3']['bucket'],
|
||||
'Key' => $object['Key']
|
||||
));
|
||||
}
|
||||
} catch (S3Exception $e) {
|
||||
}
|
||||
|
||||
$connection->deleteBucket(array(
|
||||
'Bucket' => $this->config['amazons3']['bucket']
|
||||
));
|
||||
|
||||
//wait some seconds for completing the replication
|
||||
sleep(30);
|
||||
$this->instance->rmdir('');
|
||||
}
|
||||
}
|
||||
|
||||
public function testStat() {
|
||||
$this->markTestSkipped('S3 doesn\'t update the parents folder mtime');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../../lib/base.php';
|
||||
|
||||
class Test_Mount_Config_Dummy_Storage {
|
||||
public function test() {
|
||||
return true;
|
||||
@@ -800,4 +798,41 @@ class Test_Mount_Config extends \PHPUnit_Framework_TestCase {
|
||||
$this->assertEquals($priority,
|
||||
$mountPoints['/'.self::TEST_USER1.'/files/ext']['priority']);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test for correct personal configuration loading in file sharing scenarios
|
||||
*/
|
||||
public function testMultiUserPersonalConfigLoading() {
|
||||
$mountConfig = array(
|
||||
'host' => 'somehost',
|
||||
'user' => 'someuser',
|
||||
'password' => 'somepassword',
|
||||
'root' => 'someroot'
|
||||
);
|
||||
|
||||
// Create personal mount point
|
||||
$this->assertTrue(
|
||||
OC_Mount_Config::addMountPoint(
|
||||
'/ext',
|
||||
'\OC\Files\Storage\SMB',
|
||||
$mountConfig,
|
||||
OC_Mount_Config::MOUNT_TYPE_USER,
|
||||
self::TEST_USER1,
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
// Ensure other user can read mount points
|
||||
\OC_User::setUserId(self::TEST_USER2);
|
||||
$mountPointsMe = OC_Mount_Config::getAbsoluteMountPoints(self::TEST_USER2);
|
||||
$mountPointsOther = OC_Mount_Config::getAbsoluteMountPoints(self::TEST_USER1);
|
||||
|
||||
$this->assertEquals(0, count($mountPointsMe));
|
||||
$this->assertEquals(1, count($mountPointsOther));
|
||||
$this->assertTrue(isset($mountPointsOther['/'.self::TEST_USER1.'/files/ext']));
|
||||
$this->assertEquals('\OC\Files\Storage\SMB',
|
||||
$mountPointsOther['/'.self::TEST_USER1.'/files/ext']['class']);
|
||||
$this->assertEquals($mountConfig,
|
||||
$mountPointsOther['/'.self::TEST_USER1.'/files/ext']['options']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,12 @@ $owner = $_POST['owner'];
|
||||
$name = $_POST['name'];
|
||||
$password = $_POST['password'];
|
||||
|
||||
// Check for invalid name
|
||||
if(!\OCP\Util::isValidFileName($name)) {
|
||||
\OCP\JSON::error(array('data' => array('message' => $l->t('The mountpoint name contains invalid characters.'))));
|
||||
exit();
|
||||
}
|
||||
|
||||
$externalManager = new \OCA\Files_Sharing\External\Manager(
|
||||
\OC::$server->getDatabaseConnection(),
|
||||
\OC\Files\Filesystem::getMountManager(),
|
||||
|
||||
@@ -70,10 +70,6 @@ if(substr($path, 0, 1) === '/') {
|
||||
$path = substr($path, 1);
|
||||
}
|
||||
|
||||
if ($keepAspect === true) {
|
||||
$maxY = $maxX;
|
||||
}
|
||||
|
||||
if($maxX === 0 || $maxY === 0) {
|
||||
\OC_Response::setStatus(\OC_Response::STATUS_BAD_REQUEST);
|
||||
\OC_Log::write('core-preview', 'x and/or y set to 0', \OC_Log::DEBUG);
|
||||
|
||||
@@ -12,6 +12,9 @@ OC::$CLASSPATH['OCA\Files\Share\Api'] = 'files_sharing/lib/api.php';
|
||||
OC::$CLASSPATH['OCA\Files\Share\Maintainer'] = 'files_sharing/lib/maintainer.php';
|
||||
OC::$CLASSPATH['OCA\Files\Share\Proxy'] = 'files_sharing/lib/proxy.php';
|
||||
|
||||
// Exceptions
|
||||
OC::$CLASSPATH['OCA\Files_Sharing\Exceptions\BrokenPath'] = 'files_sharing/lib/exceptions.php';
|
||||
|
||||
\OCP\App::registerAdmin('files_sharing', 'settings-admin');
|
||||
|
||||
\OCA\Files_Sharing\Helper::registerHooks();
|
||||
@@ -24,30 +27,38 @@ OCP\Util::addScript('files_sharing', 'external');
|
||||
|
||||
OC_FileProxy::register(new OCA\Files\Share\Proxy());
|
||||
|
||||
\OCA\Files\App::getNavigationManager()->add(
|
||||
array(
|
||||
"id" => 'sharingin',
|
||||
"appname" => 'files_sharing',
|
||||
"script" => 'list.php',
|
||||
"order" => 10,
|
||||
"name" => $l->t('Shared with you')
|
||||
)
|
||||
);
|
||||
\OCA\Files\App::getNavigationManager()->add(
|
||||
array(
|
||||
"id" => 'sharingout',
|
||||
"appname" => 'files_sharing',
|
||||
"script" => 'list.php',
|
||||
"order" => 15,
|
||||
"name" => $l->t('Shared with others')
|
||||
)
|
||||
);
|
||||
\OCA\Files\App::getNavigationManager()->add(
|
||||
array(
|
||||
"id" => 'sharinglinks',
|
||||
"appname" => 'files_sharing',
|
||||
"script" => 'list.php',
|
||||
"order" => 20,
|
||||
"name" => $l->t('Shared by link')
|
||||
)
|
||||
);
|
||||
$config = \OC::$server->getConfig();
|
||||
if ($config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes') {
|
||||
|
||||
\OCA\Files\App::getNavigationManager()->add(
|
||||
array(
|
||||
"id" => 'sharingin',
|
||||
"appname" => 'files_sharing',
|
||||
"script" => 'list.php',
|
||||
"order" => 10,
|
||||
"name" => $l->t('Shared with you')
|
||||
)
|
||||
);
|
||||
|
||||
if (\OCP\Util::isSharingDisabledForUser() === false) {
|
||||
|
||||
\OCA\Files\App::getNavigationManager()->add(
|
||||
array(
|
||||
"id" => 'sharingout',
|
||||
"appname" => 'files_sharing',
|
||||
"script" => 'list.php',
|
||||
"order" => 15,
|
||||
"name" => $l->t('Shared with others')
|
||||
)
|
||||
);
|
||||
\OCA\Files\App::getNavigationManager()->add(
|
||||
array(
|
||||
"id" => 'sharinglinks',
|
||||
"appname" => 'files_sharing',
|
||||
"script" => 'list.php',
|
||||
"order" => 20,
|
||||
"name" => $l->t('Shared by link')
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,4 +15,5 @@
|
||||
<files>public.php</files>
|
||||
<webdav>publicwebdav.php</webdav>
|
||||
</public>
|
||||
<ocsid>166050</ocsid>
|
||||
</info>
|
||||
|
||||
@@ -32,6 +32,7 @@ function updateFilePermissions($chunkSize = 99) {
|
||||
}
|
||||
}
|
||||
|
||||
$connection = \OC_DB::getConnection();
|
||||
$chunkedPermissionList = array_chunk($updatedRows, $chunkSize, true);
|
||||
|
||||
foreach ($chunkedPermissionList as $subList) {
|
||||
@@ -39,7 +40,7 @@ function updateFilePermissions($chunkSize = 99) {
|
||||
//update share table
|
||||
$ids = implode(',', array_keys($subList));
|
||||
foreach ($subList as $id => $permission) {
|
||||
$statement .= "WHEN " . $id . " THEN " . $permission . " ";
|
||||
$statement .= "WHEN " . $connection->quote($id, \PDO::PARAM_INT) . " THEN " . $permission . " ";
|
||||
}
|
||||
$statement .= ' END WHERE `id` IN (' . $ids . ')';
|
||||
|
||||
@@ -95,6 +96,7 @@ function removeSharedFolder($mkdirs = true, $chunkSize = 99) {
|
||||
}
|
||||
|
||||
$chunkedShareList = array_chunk($shares, $chunkSize, true);
|
||||
$connection = \OC_DB::getConnection();
|
||||
|
||||
foreach ($chunkedShareList as $subList) {
|
||||
|
||||
@@ -102,7 +104,7 @@ function removeSharedFolder($mkdirs = true, $chunkSize = 99) {
|
||||
//update share table
|
||||
$ids = implode(',', array_keys($subList));
|
||||
foreach ($subList as $id => $target) {
|
||||
$statement .= "WHEN " . $id . " THEN '/Shared" . $target . "' ";
|
||||
$statement .= "WHEN " . $connection->quote($id, \PDO::PARAM_INT) . " THEN " . $connection->quote('/Shared' . $target, \PDO::PARAM_STR);
|
||||
}
|
||||
$statement .= ' END WHERE `id` IN (' . $ids . ')';
|
||||
|
||||
@@ -111,5 +113,8 @@ function removeSharedFolder($mkdirs = true, $chunkSize = 99) {
|
||||
$query->execute(array());
|
||||
}
|
||||
|
||||
// set config to keep the Shared folder as the default location for new shares
|
||||
\OCA\Files_Sharing\Helper::setShareFolder('/Shared');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.5.2
|
||||
0.5.3
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
margin: 45px auto 0;
|
||||
min-height: 150px;
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
#preview .notCreatable {
|
||||
@@ -44,8 +44,9 @@ p.info a {
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
/* fix multiselect bar offset on shared page */
|
||||
thead {
|
||||
padding-left: 0 !important; /* fixes multiselect bar offset on shared page */
|
||||
left: 0 !important;
|
||||
}
|
||||
|
||||
#data-upload-form {
|
||||
@@ -89,21 +90,48 @@ thead {
|
||||
}
|
||||
|
||||
/* within #save */
|
||||
#remote_address {
|
||||
margin: 0;
|
||||
height: 14px;
|
||||
padding: 6px;
|
||||
#save .save-form {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#save button {
|
||||
#remote_address {
|
||||
margin: 0;
|
||||
width: 130px;
|
||||
height: 14px;
|
||||
padding: 6px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
.ie8 #remote_address {
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
#save #save-button,
|
||||
#save #save-button-confirm {
|
||||
margin: 0 5px;
|
||||
height: 28px;
|
||||
padding-bottom: 4px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
#save .save-form [type="submit"] {
|
||||
margin: 0 5px;
|
||||
height: 28px;
|
||||
padding-bottom: 4px;
|
||||
#save-button-confirm {
|
||||
position: absolute;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
margin: 2px 4px !important;
|
||||
right: 0;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
|
||||
filter: alpha(opacity=50);
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.ie8 #save-button-confirm {
|
||||
margin: 2px 0 !important;
|
||||
}
|
||||
|
||||
#save-button-confirm:hover,
|
||||
#save-button-confirm:focus {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
|
||||
filter: alpha(opacity=100);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -92,6 +92,21 @@ OCA.Sharing.App = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the app
|
||||
*/
|
||||
destroy: function() {
|
||||
OCA.Files.fileActions.off('setDefault.app-sharing', this._onActionsUpdated);
|
||||
OCA.Files.fileActions.off('registerAction.app-sharing', this._onActionsUpdated);
|
||||
this.removeSharingIn();
|
||||
this.removeSharingOut();
|
||||
this.removeSharingLinks();
|
||||
this._inFileList = null;
|
||||
this._outFileList = null;
|
||||
this._linkFileList = null;
|
||||
delete this._globalActionsInitialized;
|
||||
},
|
||||
|
||||
_createFileActions: function() {
|
||||
// inherit file actions from the files app
|
||||
var fileActions = new OCA.Files.FileActions();
|
||||
@@ -100,6 +115,14 @@ OCA.Sharing.App = {
|
||||
fileActions.registerDefaultActions();
|
||||
fileActions.merge(OCA.Files.fileActions);
|
||||
|
||||
if (!this._globalActionsInitialized) {
|
||||
// in case actions are registered later
|
||||
this._onActionsUpdated = _.bind(this._onActionsUpdated, this);
|
||||
OCA.Files.fileActions.on('setDefault.app-sharing', this._onActionsUpdated);
|
||||
OCA.Files.fileActions.on('registerAction.app-sharing', this._onActionsUpdated);
|
||||
this._globalActionsInitialized = true;
|
||||
}
|
||||
|
||||
// when the user clicks on a folder, redirect to the corresponding
|
||||
// folder in the files app instead of opening it directly
|
||||
fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
|
||||
@@ -110,6 +133,23 @@ OCA.Sharing.App = {
|
||||
return fileActions;
|
||||
},
|
||||
|
||||
_onActionsUpdated: function(ev) {
|
||||
_.each([this._inFileList, this._outFileList, this._linkFileList], function(list) {
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.action) {
|
||||
list.fileActions.registerAction(ev.action);
|
||||
} else if (ev.defaultAction) {
|
||||
list.fileActions.setDefault(
|
||||
ev.defaultAction.mime,
|
||||
ev.defaultAction.name
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_extendFileList: function(fileList) {
|
||||
// remove size column from summary
|
||||
fileList.fileSummary.$el.find('.filesize').remove();
|
||||
|
||||
@@ -42,13 +42,40 @@
|
||||
}
|
||||
};
|
||||
if (!passwordProtected) {
|
||||
OC.dialogs.confirm(t('files_sharing', 'Add {name} from {owner}@{remote}', {name: name, owner: owner, remote: remoteClean})
|
||||
, t('files_sharing','Add Share'), callback, true);
|
||||
OC.dialogs.confirm(
|
||||
t(
|
||||
'files_sharing',
|
||||
'Do you want to add the remote share {name} from {owner}@{remote}?',
|
||||
{name: name, owner: owner, remote: remoteClean}
|
||||
),
|
||||
t('files_sharing','Remote share'),
|
||||
callback,
|
||||
true
|
||||
).then(this._adjustDialog);
|
||||
} else {
|
||||
OC.dialogs.prompt(t('files_sharing', 'Add {name} from {owner}@{remote}', {name: name, owner: owner, remote: remoteClean})
|
||||
, t('files_sharing','Add Share'), callback, true, t('files_sharing','Password'), true);
|
||||
OC.dialogs.prompt(
|
||||
t(
|
||||
'files_sharing',
|
||||
'Do you want to add the remote share {name} from {owner}@{remote}?',
|
||||
{name: name, owner: owner, remote: remoteClean}
|
||||
),
|
||||
t('files_sharing','Remote share'),
|
||||
callback,
|
||||
true,
|
||||
t('files_sharing','Remote share password'),
|
||||
true
|
||||
).then(this._adjustDialog);
|
||||
}
|
||||
};
|
||||
|
||||
OCA.Sharing._adjustDialog = function() {
|
||||
var $dialog = $('.oc-dialog:visible');
|
||||
var $buttons = $dialog.find('button');
|
||||
// hack the buttons
|
||||
$dialog.find('.ui-icon').remove();
|
||||
$buttons.eq(0).text(t('core', 'Cancel'));
|
||||
$buttons.eq(1).text(t('files_sharing', 'Add remote share'));
|
||||
};
|
||||
})();
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
@@ -56,6 +56,11 @@ OCA.Sharing.PublicApp = {
|
||||
}
|
||||
|
||||
var mimetype = $('#mimetype').val();
|
||||
var mimetypeIcon = $('#mimetypeIcon').val();
|
||||
mimetypeIcon = mimetypeIcon.substring(0, mimetypeIcon.length - 3);
|
||||
mimetypeIcon = mimetypeIcon + 'svg';
|
||||
|
||||
var previewSupported = $('#previewSupported').val();
|
||||
|
||||
if (typeof FileActions !== 'undefined') {
|
||||
// Show file preview if previewer is available, images are already handled by the template
|
||||
@@ -68,20 +73,25 @@ OCA.Sharing.PublicApp = {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// dynamically load image previews
|
||||
if (mimetype.substr(0, mimetype.indexOf('/')) === 'image') {
|
||||
var params = {
|
||||
x: $(document).width() * window.devicePixelRatio,
|
||||
y: $(document).height() * window.devicePixelRatio,
|
||||
a: 'true',
|
||||
file: encodeURIComponent(this.initialDir + $('#filename').val()),
|
||||
t: $('#sharingToken').val(),
|
||||
scalingup: 0
|
||||
};
|
||||
|
||||
var params = {
|
||||
x: $(document).width() * window.devicePixelRatio,
|
||||
a: 'true',
|
||||
file: encodeURIComponent(this.initialDir + $('#filename').val()),
|
||||
t: $('#sharingToken').val(),
|
||||
scalingup: 0
|
||||
};
|
||||
|
||||
var img = $('<img class="publicpreview">');
|
||||
var img = $('<img class="publicpreview">');
|
||||
if (previewSupported === 'true' || mimetype.substr(0, mimetype.indexOf('/')) === 'image') {
|
||||
img.attr('src', OC.filePath('files_sharing', 'ajax', 'publicpreview.php') + '?' + OC.buildQueryString(params));
|
||||
img.appendTo('#imgframe');
|
||||
} else if (mimetype.substr(0, mimetype.indexOf('/')) !== 'video') {
|
||||
img.attr('src', OC.Util.replaceSVGIcon(mimetypeIcon));
|
||||
img.attr('width', 128);
|
||||
img.appendTo('#imgframe');
|
||||
}
|
||||
|
||||
if (this.fileList) {
|
||||
@@ -163,7 +173,7 @@ OCA.Sharing.PublicApp = {
|
||||
OCA.Sharing.PublicApp._saveToOwnCloud(remote, token, owner, name, isProtected);
|
||||
});
|
||||
|
||||
$('#save > button').click(function () {
|
||||
$('#save #save-button').click(function () {
|
||||
$(this).hide();
|
||||
$('.save-form').css('display', 'inline');
|
||||
$('#remote_address').focus();
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
' data-action="Share-Notification" href="#" original-title="">' +
|
||||
' <img class="svg" src="' + OC.imagePath('core', 'actions/share') + '"></img>';
|
||||
$tr.find('.fileactions').append(function() {
|
||||
var shareBy = t('files_sharing', 'Shared by {owner}', {owner: escapeHTML($tr.attr('data-share-owner'))});
|
||||
var shareBy = escapeHTML($tr.attr('data-share-owner'));
|
||||
var $result = $(shareNotification + '<span> ' + shareBy + '</span></span>');
|
||||
$result.on('click', function() {
|
||||
return false;
|
||||
|
||||
@@ -59,6 +59,9 @@
|
||||
$tr.attr('data-share-id', _.pluck(fileData.shares, 'id').join(','));
|
||||
if (this._sharedWithUser) {
|
||||
$tr.attr('data-share-owner', fileData.shareOwner);
|
||||
$tr.attr('data-mounttype', 'shared-root');
|
||||
var permission = parseInt($tr.attr('data-permissions')) | OC.PERMISSION_DELETE;
|
||||
$tr.attr('data-permissions', permission);
|
||||
}
|
||||
return $tr;
|
||||
},
|
||||
@@ -95,7 +98,6 @@
|
||||
},
|
||||
|
||||
reload: function() {
|
||||
var self = this;
|
||||
this.showMask();
|
||||
if (this._reloadCall) {
|
||||
this._reloadCall.abort();
|
||||
@@ -110,14 +112,10 @@
|
||||
type: 'GET',
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader('OCS-APIREQUEST', 'true');
|
||||
},
|
||||
error: function(result) {
|
||||
self.reloadCallback(result);
|
||||
},
|
||||
success: function(result) {
|
||||
self.reloadCallback(result);
|
||||
}
|
||||
});
|
||||
var callBack = this.reloadCallback.bind(this);
|
||||
return this._reloadCall.then(callBack, callBack);
|
||||
},
|
||||
|
||||
reloadCallback: function(result) {
|
||||
@@ -166,11 +164,9 @@
|
||||
}
|
||||
else {
|
||||
file.type = 'file';
|
||||
// force preview retrieval as we don't have mime types,
|
||||
// the preview endpoint will fall back to the mime type
|
||||
// icon if no preview exists
|
||||
file.isPreviewAvailable = true;
|
||||
file.icon = true;
|
||||
if (share.isPreviewAvailable) {
|
||||
file.isPreviewAvailable = true;
|
||||
}
|
||||
}
|
||||
file.share = {
|
||||
id: share.id,
|
||||
@@ -185,7 +181,9 @@
|
||||
file.permissions = share.permissions;
|
||||
}
|
||||
else {
|
||||
file.share.targetDisplayName = share.share_with_displayname;
|
||||
if (share.share_type !== OC.Share.SHARE_TYPE_LINK) {
|
||||
file.share.targetDisplayName = share.share_with_displayname;
|
||||
}
|
||||
file.name = OC.basename(share.path);
|
||||
file.path = OC.dirname(share.path);
|
||||
file.permissions = OC.PERMISSION_ALL;
|
||||
@@ -237,6 +235,7 @@
|
||||
.each(function(data) {
|
||||
// convert the recipients map to a flat
|
||||
// array of sorted names
|
||||
data.mountType = 'shared';
|
||||
data.recipients = _.keys(data.recipients);
|
||||
data.recipientsDisplayName = OCA.Sharing.Util.formatRecipients(
|
||||
data.recipients,
|
||||
@@ -244,12 +243,11 @@
|
||||
);
|
||||
delete data.recipientsCount;
|
||||
})
|
||||
// Sort by expected sort comparator
|
||||
.sortBy(this._sortComparator)
|
||||
// Finish the chain by getting the result
|
||||
.value();
|
||||
|
||||
return files;
|
||||
// Sort by expected sort comparator
|
||||
return files.sort(this._sortComparator);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
$TRANSLATIONS = array(
|
||||
"Server to server sharing is not enabled on this server" => "La compartición sirvidor a sirvidor nun ta habilitada nesti sirvidor",
|
||||
"Couldn't add remote share" => "Nun pudo amestase una compartición remota",
|
||||
"Shared with you" => "Compartío contigo",
|
||||
"Shared with others" => "Compartío con otros",
|
||||
"Shared by link" => "Compartíu por enllaz",
|
||||
"Shared with you" => "Compartíos contigo",
|
||||
"Shared with others" => "Compartíos con otros",
|
||||
"Shared by link" => "Compartíos per enllaz",
|
||||
"No files have been shared with you yet." => "Entá nun se compartieron ficheros contigo.",
|
||||
"You haven't shared any files yet." => "Entá nun compartiesti dengún ficheru.",
|
||||
"You haven't shared any files by link yet." => "Entá nun compartiesti nengún ficheru por enllaz.",
|
||||
@@ -30,6 +30,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Enllaz direutu",
|
||||
"Remote Shares" => "Comparticiones remotes",
|
||||
"Allow other instances to mount public links shared from this server" => "Permitir a otres instancies montar enllaces compartíos públicos d'esti sirvidor",
|
||||
"Allow users to mount public link shares" => "Permitir a los usuarios montar enllaces compartíos públicos"
|
||||
"Allow users to mount public link shares" => "Permitir a los usuarios montar enllaces compartíos públicos",
|
||||
"Remote share" => "Compartición remota",
|
||||
"Remote share password" => "Contraseña de compartición remota",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Quies amestar compartición remota {name} de {owner}@{remote}?",
|
||||
"Add remote share" => "Amestar compartición remota",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -4,6 +4,12 @@ $TRANSLATIONS = array(
|
||||
"Shared by" => "Споделено от",
|
||||
"Name" => "Име",
|
||||
"Save" => "Запис",
|
||||
"Download" => "Изтегляне"
|
||||
"Download" => "Изтегляне",
|
||||
"Remote share" => "Прикачена Папка",
|
||||
"Remote share password" => "Парола за прикачена папка",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Желаеш ли да добавиш като прикачената папка {name} от {owner}@{remote}?",
|
||||
"Couldn't add remote share" => "Неуспешно добавяне на отдалечена споделена директория.",
|
||||
"Add remote share" => "Добави прикачена папка",
|
||||
"Remote Shares" => "Прикачени Папки",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -3,6 +3,9 @@ $TRANSLATIONS = array(
|
||||
"Password" => "কূটশব্দ",
|
||||
"Name" => "রাম",
|
||||
"Save" => "সংরক্ষণ",
|
||||
"Download" => "ডাউনলোড"
|
||||
"Download" => "ডাউনলোড",
|
||||
"Remote share" => "দুরবর্তী ভাগাভাগি",
|
||||
"Couldn't add remote share" => "দুরবর্তী ভাগাভাগি যোগ করা গেলনা",
|
||||
"Remote Shares" => "দুরবর্তী ভাগাভাগি",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Enllaç directe",
|
||||
"Remote Shares" => "Compartició remota",
|
||||
"Allow other instances to mount public links shared from this server" => "Permet que altres instàncies muntin enllaços públics compartits des d'aqeust servidor",
|
||||
"Allow users to mount public link shares" => "Permet que usuaris muntin compartits amb enllaços públics"
|
||||
"Allow users to mount public link shares" => "Permet que usuaris muntin compartits amb enllaços públics",
|
||||
"Remote share" => "Compartició remota",
|
||||
"Remote share password" => "Contrasenya de compartició remota",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Voleu afegir la compartició remota {nom} des de {owner}@{remote}?",
|
||||
"Add remote share" => "Afegeix compartició remota",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Přímý odkaz",
|
||||
"Remote Shares" => "Vzdálená úložiště",
|
||||
"Allow other instances to mount public links shared from this server" => "Povolit připojování veřejně sdílených odkazů z tohoto serveru",
|
||||
"Allow users to mount public link shares" => "Povolit uživatelům připojovat veřejně sdílené odkazy"
|
||||
"Allow users to mount public link shares" => "Povolit uživatelům připojovat veřejně sdílené odkazy",
|
||||
"Remote share" => "Vzdálené úložiště",
|
||||
"Remote share password" => "Heslo ke vzdálenému úložišti",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Chcete přidat vzdálené úložiště {name} uživatele {owner}@{remote}?",
|
||||
"Add remote share" => "Přidat vzdálené úložiště",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;";
|
||||
|
||||
@@ -22,6 +22,12 @@ $TRANSLATIONS = array(
|
||||
"Save" => "Gem",
|
||||
"Download" => "Download",
|
||||
"Download %s" => "Download %s",
|
||||
"Direct link" => "Direkte link"
|
||||
"Direct link" => "Direkte link",
|
||||
"Remote share" => "Ekstern deling",
|
||||
"Remote share password" => "Adgangskode for ekstern deling",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Vil du tilføje den eksterne deling {name} fra {owner}@{remote}?",
|
||||
"Couldn't add remote share" => "Kunne ikke tliføje den delte ekstern ressource",
|
||||
"Add remote share" => "Tilføj ekstern deling",
|
||||
"Remote Shares" => "Eksterne delinger",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Direkter Link",
|
||||
"Remote Shares" => "Entfernte Freigaben",
|
||||
"Allow other instances to mount public links shared from this server" => "Andere Instanzen zum Hinzufügen von öffentlichen Links, die über diesen Server Freigegeben werden, erlauben",
|
||||
"Allow users to mount public link shares" => "Erlaube Nutzern das Hinzufügen von freigegebenen öffentlichen Links"
|
||||
"Allow users to mount public link shares" => "Erlaube Nutzern das Hinzufügen von freigegebenen öffentlichen Links",
|
||||
"Remote share" => "Entfernte Freigabe",
|
||||
"Remote share password" => "Passwort für die entfernte Freigabe",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Möchtest Du die entfernte Freigabe {name} von {owner}@{remote} hinzufügen?",
|
||||
"Add remote share" => "Entfernte Freigabe hinzufügen",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Direkte Verlinkung",
|
||||
"Remote Shares" => "Entfernte Freigaben",
|
||||
"Allow other instances to mount public links shared from this server" => "Andere Instanzen zum Hinzufügen von öffentlichen Links, die über diesen Server Freigegeben werden, erlauben",
|
||||
"Allow users to mount public link shares" => "Erlaube Nutzern das Hinzufügen von freigegebenen öffentlichen Links"
|
||||
"Allow users to mount public link shares" => "Erlaube Nutzern das Hinzufügen von freigegebenen öffentlichen Links",
|
||||
"Remote share" => "Entfernte Freigabe",
|
||||
"Remote share password" => "Passwort für die entfernte Freigabe",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Möchten Sie die entfernte Freigabe {name} von {owner}@{remote} hinzufügen?",
|
||||
"Add remote share" => "Entfernte Freigabe hinzufügen",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Άμεσος σύνδεσμος",
|
||||
"Remote Shares" => "Απομακρυσμένοι Κοινόχρηστοι Φάκελοι",
|
||||
"Allow other instances to mount public links shared from this server" => "Να επιτρέπεται σε άλλες εγκαταστάσεις να επιθέτουν δημόσιους συνδέσμους που έχουν διαμοιραστεί από αυτόν το διακομιστή",
|
||||
"Allow users to mount public link shares" => "Να επιτρέπεται στους χρήστες να επιθέτουν κοινόχρηστους φακέλους με δημόσιους συνδέσμους"
|
||||
"Allow users to mount public link shares" => "Να επιτρέπεται στους χρήστες να επιθέτουν κοινόχρηστους φακέλους με δημόσιους συνδέσμους",
|
||||
"Remote share" => "Απομακρυσμένος κοινόχρηστος φάκελος",
|
||||
"Remote share password" => "Κωδικός πρόσβασης απομακρυσμένου κοινόχρηστου φακέλου",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Θέλετε να προσθέσουμε τον απομακρυσμένο κοινόχρηστο φάκελο {name} από {owner}@{remote}?",
|
||||
"Add remote share" => "Προσθήκη απομακρυσμένου κοινόχρηστου φακέλου",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Direct link",
|
||||
"Remote Shares" => "Remote Shares",
|
||||
"Allow other instances to mount public links shared from this server" => "Allow other instances to mount public links shared from this server",
|
||||
"Allow users to mount public link shares" => "Allow users to mount public link shares"
|
||||
"Allow users to mount public link shares" => "Allow users to mount public link shares",
|
||||
"Remote share" => "Remote share",
|
||||
"Remote share password" => "Remote share password",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Do you want to add the remote share {name} from {owner}@{remote}?",
|
||||
"Add remote share" => "Add remote share",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Enlace directo",
|
||||
"Remote Shares" => "Almacenamiento compartido remoto",
|
||||
"Allow other instances to mount public links shared from this server" => "Permitir a otros montar enlaces publicos compartidos de este servidor",
|
||||
"Allow users to mount public link shares" => "Permitir a los usuarios montar enlaces publicos compartidos"
|
||||
"Allow users to mount public link shares" => "Permitir a los usuarios montar enlaces publicos compartidos",
|
||||
"Remote share" => "Recurso compartido remoto",
|
||||
"Remote share password" => "Contraseña del compartido remoto",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "¿Desea añadir el recurso compartido remoto {name} de {owner}@{remote}?",
|
||||
"Add remote share" => "Añadir recurso compartido remoto",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -30,6 +30,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Otsene link",
|
||||
"Remote Shares" => "Eemalolevad jagamised",
|
||||
"Allow other instances to mount public links shared from this server" => "Luba teistel instantsidel ühendada sellest serverist jagatud avalikke linke",
|
||||
"Allow users to mount public link shares" => "Luba kasutajatel ühendada jagatud avalikke linke"
|
||||
"Allow users to mount public link shares" => "Luba kasutajatel ühendada jagatud avalikke linke",
|
||||
"Remote share" => "Kaugjagamine",
|
||||
"Remote share password" => "Kaugjagamise parool",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Soovid lisata kaugjagamise {name} asukohast {owner}@{remote}?",
|
||||
"Add remote share" => "Lisa kaugjagamine",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Lotura zuzena",
|
||||
"Remote Shares" => "Hurruneko Elkarbanaketak",
|
||||
"Allow other instances to mount public links shared from this server" => "Baimendu beste instantziak zerbitzari honetatik elkarbanatutako lotura publikoak kargatzen",
|
||||
"Allow users to mount public link shares" => "Baimendu erabiltzaileak lotura publiko bidezko elkarbanaketak kargatzen"
|
||||
"Allow users to mount public link shares" => "Baimendu erabiltzaileak lotura publiko bidezko elkarbanaketak kargatzen",
|
||||
"Remote share" => "Urrutiko parte hartzea",
|
||||
"Remote share password" => "Urrutiko parte hartzeen pasahitza",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Nahi duzu gehitzea {name} urrutiko partekatzea honengandik {owner}@{remote}?",
|
||||
"Add remote share" => "Gehitu urrutiko parte hartzea",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -15,6 +15,12 @@ $TRANSLATIONS = array(
|
||||
"Save" => "ذخیره",
|
||||
"Download" => "دانلود",
|
||||
"Download %s" => "دانلود %s",
|
||||
"Direct link" => "پیوند مستقیم"
|
||||
"Direct link" => "پیوند مستقیم",
|
||||
"Remote share" => "اشتراک از راه دور",
|
||||
"Remote share password" => "رمز عبور اشتراک از راه دور",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "آیا مایل به افزودن اشتراک از راه دور {name} از {owner}@{remote} هستید.",
|
||||
"Couldn't add remote share" => "امکان افزودن اشتراک گذاری از راه دور وجود ندارد",
|
||||
"Add remote share" => "افزودن اشتراک از راه دور",
|
||||
"Remote Shares" => "اشتراک های از راه دور",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=1; plural=0;";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Suora linkki",
|
||||
"Remote Shares" => "Etäjaot",
|
||||
"Allow other instances to mount public links shared from this server" => "Salli muiden instanssien liittää tältä palvelimelta jaettuja julkisia linkkejä",
|
||||
"Allow users to mount public link shares" => "Salli käyttäjien liittää julkisia linkkijakoja"
|
||||
"Allow users to mount public link shares" => "Salli käyttäjien liittää julkisia linkkijakoja",
|
||||
"Remote share" => "Etäjako",
|
||||
"Remote share password" => "Etäjaon salasana",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Haluatko lisätä etäjaon {name} kohteesta {owner}@{remote}?",
|
||||
"Add remote share" => "Lisää etäjako",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Lien direct",
|
||||
"Remote Shares" => "Partages Distants",
|
||||
"Allow other instances to mount public links shared from this server" => "Autorise d'autres instances à monter des liens publiques partagés depuis ce serveur",
|
||||
"Allow users to mount public link shares" => "Autorise les utilisateurs à monter des liens de partages publiques"
|
||||
"Allow users to mount public link shares" => "Autorise les utilisateurs à monter des liens de partages publiques",
|
||||
"Remote share" => "Partage distant",
|
||||
"Remote share password" => "Mot de passe du partage distant",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Voulez-vous ajouter le partage distant {name} de {owner}@{remote} ?",
|
||||
"Add remote share" => "Ajouter un partage distant",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n > 1);";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Ligazón directa",
|
||||
"Remote Shares" => "Comparticións remotas",
|
||||
"Allow other instances to mount public links shared from this server" => "Permitir que outras instancias monten ligazóns públicas compartidas desde este servidor",
|
||||
"Allow users to mount public link shares" => "Permitirlle aos usuarios montar ligazóns públicas compartidas"
|
||||
"Allow users to mount public link shares" => "Permitirlle aos usuarios montar ligazóns públicas compartidas",
|
||||
"Remote share" => "Compartición remota",
|
||||
"Remote share password" => "Contrasinal da compartición remota",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Quere engadir a compartición remota {name} desde {owner}@{remote}?",
|
||||
"Add remote share" => "Engadir unha compartición remota",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -3,6 +3,12 @@ $TRANSLATIONS = array(
|
||||
"Password" => "Lozinka",
|
||||
"Name" => "Ime",
|
||||
"Save" => "Snimi",
|
||||
"Download" => "Preuzimanje"
|
||||
"Download" => "Preuzimanje",
|
||||
"Remote share" => "Udaljeni zajednički resurs (za raspodjelu)",
|
||||
"Remote share password" => "Lozinka za udaljeni zajednički resurs",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Želite li dodati udaljeni zajednički resurs {name} od {owner}@{remote}?",
|
||||
"Couldn't add remote share" => "Udaljeni zajednički resurs nije moguće dodati",
|
||||
"Add remote share" => "Dodajte udaljeni zajednički resurs",
|
||||
"Remote Shares" => "Udaljeni zajednički resursi (za raspodjelu)",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;";
|
||||
|
||||
@@ -14,6 +14,12 @@ $TRANSLATIONS = array(
|
||||
"For more info, please ask the person who sent this link." => "További információért forduljon ahhoz, aki ezt a linket küldte Önnek!",
|
||||
"Save" => "Mentés",
|
||||
"Download" => "Letöltés",
|
||||
"Direct link" => "Közvetlen link"
|
||||
"Direct link" => "Közvetlen link",
|
||||
"Remote share" => "Távoli megosztás",
|
||||
"Remote share password" => "Jelszó a távoli megosztáshoz",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Használatba kívánja venni a {name} távoli megosztást, amit a {owner}@{remote} címről kapott?",
|
||||
"Couldn't add remote share" => "A távoli megosztás nem hozható létre",
|
||||
"Add remote share" => "Távoli megosztás létrehozása",
|
||||
"Remote Shares" => "Távoli megosztások",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Collegamento diretto",
|
||||
"Remote Shares" => "Condivisioni remote",
|
||||
"Allow other instances to mount public links shared from this server" => "Permetti ad altre istanze di montare collegamenti pubblici condivisi da questo server",
|
||||
"Allow users to mount public link shares" => "Permetti agli utenti di montare condivisioni con collegamento pubblico"
|
||||
"Allow users to mount public link shares" => "Permetti agli utenti di montare condivisioni con collegamento pubblico",
|
||||
"Remote share" => "Condivisione remota",
|
||||
"Remote share password" => "Password della condivisione remota",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Vuoi aggiungere la condivisione remota {name} da {owner}@{remote}?",
|
||||
"Add remote share" => "Aggiungi condivisione remota",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "リンク",
|
||||
"Remote Shares" => "リモート共有",
|
||||
"Allow other instances to mount public links shared from this server" => "このサーバにおけるURLでの共有を他のインスタンスからマウントできるようにする",
|
||||
"Allow users to mount public link shares" => "ユーザーがURLでの共有をマウントできるようにする"
|
||||
"Allow users to mount public link shares" => "ユーザーがURLでの共有をマウントできるようにする",
|
||||
"Remote share" => "リモート共有",
|
||||
"Remote share password" => "リモート共有のパスワード",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "{owner}@{remote} からリモート共有 {name} を追加してもよろしいですか?",
|
||||
"Add remote share" => "リモート共有を追加",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=1; plural=0;";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Direkte lenke",
|
||||
"Remote Shares" => "Ekstern deling",
|
||||
"Allow other instances to mount public links shared from this server" => "Tillat at andre servere kobler opp offentlige lenker som er delt fra denne serveren",
|
||||
"Allow users to mount public link shares" => "Tillat at brukere kobler opp offentlige lenke-delinger"
|
||||
"Allow users to mount public link shares" => "Tillat at brukere kobler opp offentlige lenke-delinger",
|
||||
"Remote share" => "Ekstern deling",
|
||||
"Remote share password" => "Passord for ekstern deling",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Ønsker du å legge til ekstern deling {name} fra {owner}@{remote}?",
|
||||
"Add remote share" => "Legg til ekstern deling",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -30,7 +30,11 @@ $TRANSLATIONS = array(
|
||||
"Download %s" => "Download %s",
|
||||
"Direct link" => "Directe link",
|
||||
"Remote Shares" => "Externe shares",
|
||||
"Allow other instances to mount public links shared from this server" => "Toestaan dat andere oanClouds openbaar gedeelde links mounten vanaf deze server",
|
||||
"Allow users to mount public link shares" => "Toestaan dat gebruikers openbaar gedeelde links mounten"
|
||||
"Allow other instances to mount public links shared from this server" => "Toestaan dat andere ownClouds openbaar gedeelde links mounten vanaf deze server",
|
||||
"Allow users to mount public link shares" => "Toestaan dat gebruikers openbaar gedeelde links mounten",
|
||||
"Remote share" => "Externe share",
|
||||
"Remote share password" => "Wachtwoord externe share",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Wilt u de externe share {name} van {owner}@{remote} toevoegen?",
|
||||
"Add remote share" => "Toevoegen externe share",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -27,6 +27,11 @@ $TRANSLATIONS = array(
|
||||
"Download" => "Pobierz",
|
||||
"Download %s" => "Pobierz %s",
|
||||
"Direct link" => "Bezpośredni link",
|
||||
"Allow users to mount public link shares" => "Zezwalaj użytkownikom na montowanie publicznych linków"
|
||||
"Allow users to mount public link shares" => "Zezwalaj użytkownikom na montowanie publicznych linków",
|
||||
"Remote share" => "Zdalny zasób",
|
||||
"Remote share password" => "Hasło do zdalnego zasobu",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Czy chcesz dodać udział zdalny {name} od {owner}@{remote}?",
|
||||
"Add remote share" => "Dodaj zdalny zasób",
|
||||
"Remote Shares" => "Udziały zdalne",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Link direto",
|
||||
"Remote Shares" => "Compartilhamentos Remoto",
|
||||
"Allow other instances to mount public links shared from this server" => "Permitir que outras instâncias montem links de compartilhamentos públicos a partir desde servidor",
|
||||
"Allow users to mount public link shares" => "Permitir aos usuários montar links públicos de compartilhamentos"
|
||||
"Allow users to mount public link shares" => "Permitir aos usuários montar links públicos de compartilhamentos",
|
||||
"Remote share" => "Compartilhamento remoto",
|
||||
"Remote share password" => "Senha do compartilhamento remoto",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Você quer adicionar o compartilhamento remoto {name} de {owner}@{remote}?",
|
||||
"Add remote share" => "Adicionar compartilhamento remoto",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n > 1);";
|
||||
|
||||
@@ -31,6 +31,10 @@ $TRANSLATIONS = array(
|
||||
"Direct link" => "Hiperligação direta",
|
||||
"Remote Shares" => "Partilhas Remotas",
|
||||
"Allow other instances to mount public links shared from this server" => "Permitir que outras instâncias mapeiem endereços partilhados deste servidor",
|
||||
"Allow users to mount public link shares" => "Permitir mapeamentos de endereços partilhados aos utilizadores"
|
||||
"Allow users to mount public link shares" => "Permitir mapeamentos de endereços partilhados aos utilizadores",
|
||||
"Remote share" => "Partilha remota",
|
||||
"Remote share password" => "Password da partilha remota",
|
||||
"Do you want to add the remote share {name} from {owner}@{remote}?" => "Deseja adicionar a partilha remota {nome} de {proprietário}@{remoto}?",
|
||||
"Add remote share" => "Adicionar partilha remota",
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user