Compare commits
318 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f52e975fa | |||
| a3c6d20ccb | |||
| 9caff0be96 | |||
| c6e87acb96 | |||
| 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...b958a1e609
@@ -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;
|
||||
}
|
||||
|
||||
@@ -108,6 +108,29 @@ if($source) {
|
||||
$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
|
||||
|
||||
|
||||
+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;
|
||||
|
||||
+31
-3
@@ -32,9 +32,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;
|
||||
|
||||
@@ -59,6 +61,32 @@
|
||||
this._onPopState(OC.Util.History.parseUrlQuery());
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the container of the currently visible app.
|
||||
*
|
||||
|
||||
@@ -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())
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 + '/';
|
||||
}
|
||||
|
||||
+47
-13
@@ -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'),
|
||||
@@ -172,7 +172,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 +183,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 +338,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 +370,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 +399,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 +604,10 @@
|
||||
"data-permissions": fileData.permissions || this.getDirectoryPermissions()
|
||||
});
|
||||
|
||||
if (fileData.mountType) {
|
||||
tr.attr('data-mounttype', fileData.mountType);
|
||||
}
|
||||
|
||||
if (!_.isUndefined(path)) {
|
||||
tr.attr('data-path', path);
|
||||
}
|
||||
@@ -701,6 +711,7 @@
|
||||
* @param options map of attributes:
|
||||
* - "updateSummary": true to update the summary after adding (default), false otherwise
|
||||
* - "silent": true to prevent firing events like "fileActionsReady"
|
||||
* - "animate": true to animate preview loading (defaults to true here)
|
||||
* @return new tr element (not appended to the table)
|
||||
*/
|
||||
add: function(fileData, options) {
|
||||
@@ -708,7 +719,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
|
||||
@@ -766,6 +777,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 +819,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 +920,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 +968,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 +998,7 @@
|
||||
}
|
||||
|
||||
this.setFiles(result.data.files);
|
||||
return true
|
||||
return true;
|
||||
},
|
||||
|
||||
updateStorageStatistics: function(force) {
|
||||
@@ -1291,6 +1321,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;
|
||||
@@ -1566,7 +1600,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('('));
|
||||
|
||||
+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);
|
||||
});
|
||||
});
|
||||
/**
|
||||
@@ -1933,4 +1936,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');
|
||||
|
||||
|
||||
@@ -15,4 +15,5 @@
|
||||
<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;
|
||||
}
|
||||
@@ -207,10 +214,14 @@ class Hooks {
|
||||
$privateKey = $session->getPrivateKey();
|
||||
|
||||
// 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
|
||||
@@ -245,16 +256,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 +315,7 @@ class Hooks {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* update share keys if a file was shared
|
||||
*/
|
||||
public static function postShared($params) {
|
||||
|
||||
@@ -313,34 +325,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 +407,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 +431,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 +486,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 +493,11 @@ 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);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -473,7 +529,7 @@ class Hooks {
|
||||
}
|
||||
|
||||
// handle share keys
|
||||
if (!$view->is_dir($oldKeyfilePath)) {
|
||||
if ($type === 'file') {
|
||||
$oldKeyfilePath .= '.key';
|
||||
$newKeyfilePath .= '.key';
|
||||
|
||||
@@ -481,27 +537,22 @@ class Hooks {
|
||||
$matches = Helper::findShareKeys($oldShareKeyPath, $view);
|
||||
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 +646,7 @@ class Hooks {
|
||||
}
|
||||
|
||||
/**
|
||||
* unmount file from yourself
|
||||
* remember files/folders which get unmounted
|
||||
*/
|
||||
public static function preUmount($params) {
|
||||
@@ -613,6 +665,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 +697,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
|
||||
@@ -475,5 +475,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
|
||||
*
|
||||
@@ -444,17 +475,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);
|
||||
|
||||
@@ -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']));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1495,16 +1533,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));
|
||||
|
||||
|
||||
@@ -335,6 +335,58 @@ class Test_Encryption_Hooks extends \PHPUnit_Framework_TestCase {
|
||||
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* test rename operation
|
||||
*/
|
||||
function testCopyHook() {
|
||||
|
||||
// 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));
|
||||
|
||||
// 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'));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
|
||||
. $this->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($this->filename, '/' . $this->folder . '/' . $this->folder . '/' . $this->filename);
|
||||
|
||||
$this->assertTrue($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));
|
||||
|
||||
// keys should be copied too
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/share-keys/'
|
||||
. $this->filename . '.' . self::TEST_ENCRYPTION_HOOKS_USER1 . '.shareKey'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/'
|
||||
. $this->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'));
|
||||
$this->assertTrue($this->rootView->file_exists(
|
||||
'/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files_encryption/keyfiles/' . $this->folder . '/' . $this->folder . '/'
|
||||
. $this->filename . '.key'));
|
||||
|
||||
// cleanup
|
||||
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->folder);
|
||||
$this->rootView->unlink('/' . self::TEST_ENCRYPTION_HOOKS_USER1 . '/files/' . $this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief replacing encryption keys during password change should be allowed
|
||||
* until the user logged in for the first time
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -174,6 +174,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 +221,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);
|
||||
|
||||
@@ -225,7 +257,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(
|
||||
@@ -274,7 +306,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(
|
||||
|
||||
@@ -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,52 @@ 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'));
|
||||
|
||||
// tear down
|
||||
\OC\Files\Filesystem::unlink($newFolder);
|
||||
\OC\Files\Filesystem::unlink('/newfolder');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -59,7 +62,15 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
// 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 +127,11 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -537,6 +552,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 +608,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 +625,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)));
|
||||
|
||||
@@ -10,4 +10,5 @@
|
||||
<types>
|
||||
<filesystem/>
|
||||
</types>
|
||||
<ocsid>166048</ocsid>
|
||||
</info>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ function updateStatus(statusEl, result){
|
||||
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;
|
||||
}
|
||||
@@ -80,9 +81,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 +142,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);
|
||||
@@ -187,15 +192,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) {
|
||||
|
||||
@@ -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, '/');
|
||||
@@ -72,6 +73,12 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
sleep($this->timeout);
|
||||
}
|
||||
}
|
||||
private function cleanKey($path) {
|
||||
if ($path === '.') {
|
||||
return '/';
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function __construct($params) {
|
||||
if (!isset($params['key']) || !isset($params['secret']) || !isset($params['bucket'])) {
|
||||
@@ -118,11 +125,10 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
throw new \Exception("Creation of bucket failed.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->file_exists('.')) {
|
||||
$result = $this->connection->putObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => '.',
|
||||
'Key' => $this->cleanKey('.'),
|
||||
'Body' => '',
|
||||
'ContentType' => 'httpd/unix-directory',
|
||||
'ContentLength' => 0
|
||||
@@ -167,7 +173,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);
|
||||
@@ -261,7 +267,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
|
||||
$result = $this->connection->headObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $path
|
||||
'Key' => $this->cleanKey($path)
|
||||
));
|
||||
|
||||
$stat = array();
|
||||
@@ -291,8 +297,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) {
|
||||
@@ -309,7 +314,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
try {
|
||||
$result = $this->connection->deleteObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $path
|
||||
'Key' => $this->cleanKey($path)
|
||||
));
|
||||
$this->testTimeout();
|
||||
} catch (S3Exception $e) {
|
||||
@@ -332,7 +337,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 +385,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 +412,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 +420,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 +441,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) {
|
||||
@@ -453,7 +458,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
$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) {
|
||||
@@ -535,7 +540,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
try {
|
||||
$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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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])): ?>
|
||||
@@ -149,7 +150,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>
|
||||
|
||||
@@ -800,4 +800,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']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -24,30 +24,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('core', 'Add remote share'));
|
||||
};
|
||||
})();
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
@@ -73,6 +73,7 @@ OCA.Sharing.PublicApp = {
|
||||
|
||||
var params = {
|
||||
x: $(document).width() * window.devicePixelRatio,
|
||||
y: $(document).height() * window.devicePixelRatio,
|
||||
a: 'true',
|
||||
file: encodeURIComponent(this.initialDir + $('#filename').val()),
|
||||
t: $('#sharingToken').val(),
|
||||
@@ -163,7 +164,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,7 +30,7 @@ $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 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"
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
|
||||
@@ -60,8 +60,10 @@ class Api {
|
||||
foreach ($shares as &$share) {
|
||||
if ($share['item_type'] === 'file' && isset($share['path'])) {
|
||||
$share['mimetype'] = \OC_Helper::getFileNameMimeType($share['path']);
|
||||
if (\OC::$server->getPreviewManager()->isMimeSupported($share['mimetype'])) {
|
||||
$share['isPreviewAvailable'] = true;
|
||||
}
|
||||
}
|
||||
$newShares[] = $share;
|
||||
}
|
||||
return new \OC_OCS_Result($shares);
|
||||
}
|
||||
@@ -214,6 +216,9 @@ class Api {
|
||||
foreach ($shares as &$share) {
|
||||
if ($share['item_type'] === 'file') {
|
||||
$share['mimetype'] = \OC_Helper::getFileNameMimeType($share['file_target']);
|
||||
if (\OC::$server->getPreviewManager()->isMimeSupported($share['mimetype'])) {
|
||||
$share['isPreviewAvailable'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$result = new \OC_OCS_Result($shares);
|
||||
@@ -333,6 +338,8 @@ class Api {
|
||||
return self::updatePassword($share, $params);
|
||||
} elseif (isset($params['_put']['publicUpload'])) {
|
||||
return self::updatePublicUpload($share, $params);
|
||||
} elseif (isset($params['_put']['expireDate'])) {
|
||||
return self::updateExpireDate($share, $params);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@@ -403,7 +410,7 @@ class Api {
|
||||
|
||||
if ($share['item_type'] !== 'folder' ||
|
||||
(int)$share['share_type'] !== \OCP\Share::SHARE_TYPE_LINK ) {
|
||||
return new \OC_OCS_Result(null, 404, "public upload is only possible for public shared folders");
|
||||
return new \OC_OCS_Result(null, 400, "public upload is only possible for public shared folders");
|
||||
}
|
||||
|
||||
// read, create, update (7) if public upload is enabled or
|
||||
@@ -414,6 +421,29 @@ class Api {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* set expire date for public link share
|
||||
* @param array $share information about the share
|
||||
* @param array $params contains 'expireDate' which needs to be a well formated date string, e.g DD-MM-YYYY
|
||||
* @return \OC_OCS_Result
|
||||
*/
|
||||
private static function updateExpireDate($share, $params) {
|
||||
// only public links can have a expire date
|
||||
if ((int)$share['share_type'] !== \OCP\Share::SHARE_TYPE_LINK ) {
|
||||
return new \OC_OCS_Result(null, 400, "expire date only exists for public link shares");
|
||||
}
|
||||
|
||||
try {
|
||||
$expireDateSet = \OCP\Share::setExpirationDate($share['item_type'], $share['item_source'], $params['_put']['expireDate'], (int)$share['stime']);
|
||||
$result = ($expireDateSet) ? new \OC_OCS_Result() : new \OC_OCS_Result(null, 404, "couldn't set expire date");
|
||||
} catch (\Exception $e) {
|
||||
$result = new \OC_OCS_Result(null, 404, $e->getMessage());
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* update password for public link share
|
||||
* @param array $share information about the share
|
||||
@@ -549,7 +579,7 @@ class Api {
|
||||
* @return array with: item_source, share_type, share_with, item_type, permissions
|
||||
*/
|
||||
private static function getShareFromId($shareID) {
|
||||
$sql = 'SELECT `file_source`, `item_source`, `share_type`, `share_with`, `item_type`, `permissions` FROM `*PREFIX*share` WHERE `id` = ?';
|
||||
$sql = 'SELECT `file_source`, `item_source`, `share_type`, `share_with`, `item_type`, `permissions`, `stime` FROM `*PREFIX*share` WHERE `id` = ?';
|
||||
$args = array($shareID);
|
||||
$query = \OCP\DB::prepare($sql);
|
||||
$result = $query->execute($args);
|
||||
|
||||
@@ -43,6 +43,7 @@ class Shared_Cache extends Cache {
|
||||
|
||||
/**
|
||||
* Get the source cache of a shared file or folder
|
||||
*
|
||||
* @param string $target Shared target file path
|
||||
* @return \OC\Files\Cache\Cache
|
||||
*/
|
||||
@@ -162,7 +163,7 @@ class Shared_Cache extends Cache {
|
||||
foreach ($sourceFolderContent as $key => $c) {
|
||||
$sourceFolderContent[$key]['path'] = $dir . $c['name'];
|
||||
$sourceFolderContent[$key]['uid_owner'] = $parent['uid_owner'];
|
||||
$sourceFolderContent[$key]['displayname_owner'] = $parent['uid_owner'];
|
||||
$sourceFolderContent[$key]['displayname_owner'] = \OC_User::getDisplayName($parent['uid_owner']);
|
||||
$sourceFolderContent[$key]['permissions'] = $sourceFolderContent[$key]['permissions'] & $this->storage->getPermissions($dir . $c['name']);
|
||||
}
|
||||
|
||||
@@ -275,12 +276,34 @@ class Shared_Cache extends Cache {
|
||||
*/
|
||||
public function search($pattern) {
|
||||
|
||||
$where = '`name` LIKE ? AND ';
|
||||
$pattern = trim($pattern,'%');
|
||||
|
||||
// normalize pattern
|
||||
$value = $this->normalize($pattern);
|
||||
$normalizedPattern = $this->normalize($pattern);
|
||||
|
||||
return $this->searchWithWhere($where, $value);
|
||||
$result = array();
|
||||
$exploreDirs = array('');
|
||||
while (count($exploreDirs) > 0) {
|
||||
$dir = array_pop($exploreDirs);
|
||||
$files = $this->getFolderContents($dir);
|
||||
// no results?
|
||||
if (!$files) {
|
||||
// maybe it's a single shared file
|
||||
$file = $this->get('');
|
||||
if ($normalizedPattern === '' || stristr($file['name'], $normalizedPattern) !== false) {
|
||||
$result[] = $file;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
if ($normalizedPattern === '' || stristr($file['name'], $normalizedPattern) !== false) {
|
||||
$result[] = $file;
|
||||
}
|
||||
if ($file['mimetype'] === 'httpd/unix-directory') {
|
||||
$exploreDirs[] = ltrim($dir . '/' . $file['name'], '/');
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
@@ -297,10 +320,6 @@ class Shared_Cache extends Cache {
|
||||
$mimetype = null;
|
||||
}
|
||||
|
||||
// note: searchWithWhere is currently broken as it doesn't
|
||||
// recurse into subdirs nor returns the correct
|
||||
// file paths, so using getFolderContents() for now
|
||||
|
||||
$result = array();
|
||||
$exploreDirs = array('');
|
||||
while (count($exploreDirs) > 0) {
|
||||
@@ -326,57 +345,6 @@ class Shared_Cache extends Cache {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of placeholders that can be used in an SQL query.
|
||||
* Value MUST be <= 1000 for oracle:
|
||||
* see ORA-01795 maximum number of expressions in a list is 1000
|
||||
* FIXME we should get this from doctrine as other DBs allow a lot more placeholders
|
||||
*/
|
||||
const MAX_SQL_CHUNK_SIZE = 1000;
|
||||
|
||||
/**
|
||||
* search for files with a custom where clause and value
|
||||
* the $wherevalue will be array_merge()d with the file id chunks
|
||||
*
|
||||
* @param string $sqlwhere
|
||||
* @param string $wherevalue
|
||||
* @return array
|
||||
*/
|
||||
private function searchWithWhere($sqlwhere, $wherevalue, $chunksize = self::MAX_SQL_CHUNK_SIZE) {
|
||||
|
||||
$ids = $this->getAll();
|
||||
|
||||
$files = array();
|
||||
|
||||
// divide into chunks
|
||||
$chunks = array_chunk($ids, $chunksize);
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
$placeholders = join(',', array_fill(0, count($chunk), '?'));
|
||||
$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
|
||||
`encrypted`, `unencrypted_size`, `etag`
|
||||
FROM `*PREFIX*filecache` WHERE ' . $sqlwhere . ' `fileid` IN (' . $placeholders . ')';
|
||||
|
||||
$stmt = \OC_DB::prepare($sql);
|
||||
|
||||
$result = $stmt->execute(array_merge(array($wherevalue), $chunk));
|
||||
|
||||
while ($row = $result->fetchRow()) {
|
||||
if (substr($row['path'], 0, 6) === 'files/') {
|
||||
$row['path'] = substr($row['path'], 6); // remove 'files/' from path as it's relative to '/Shared'
|
||||
}
|
||||
$row['mimetype'] = $this->getMimetype($row['mimetype']);
|
||||
$row['mimepart'] = $this->getMimetype($row['mimepart']);
|
||||
if ($row['encrypted'] or ($row['unencrypted_size'] > 0 and $row['mimetype'] === 'httpd/unix-directory')) {
|
||||
$row['encrypted_size'] = $row['size'];
|
||||
$row['size'] = $row['unencrypted_size'];
|
||||
}
|
||||
$files[] = $row;
|
||||
}
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the size of a folder and set it in the cache
|
||||
*
|
||||
@@ -436,8 +404,7 @@ class Shared_Cache extends Cache {
|
||||
*/
|
||||
public function getPathById($id, $pathEnd = '') {
|
||||
// direct shares are easy
|
||||
$path = $this->getShareById($id);
|
||||
if (is_string($path)) {
|
||||
if ($id === $this->storage->getSourceId()) {
|
||||
return ltrim($pathEnd, '/');
|
||||
} else {
|
||||
// if the item is a direct share we try and get the path of the parent and append the name of the item to it
|
||||
@@ -452,28 +419,14 @@ class Shared_Cache extends Cache {
|
||||
|
||||
/**
|
||||
* @param integer $id
|
||||
*/
|
||||
private function getShareById($id) {
|
||||
$item = \OCP\Share::getItemSharedWithBySource('file', $id);
|
||||
if ($item) {
|
||||
return trim($item['file_target'], '/');
|
||||
}
|
||||
$item = \OCP\Share::getItemSharedWithBySource('folder', $id);
|
||||
if ($item) {
|
||||
return trim($item['file_target'], '/');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $id
|
||||
* @return array
|
||||
*/
|
||||
private function getParentInfo($id) {
|
||||
$sql = 'SELECT `parent`, `name` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
|
||||
$query = \OC_DB::prepare($sql);
|
||||
$result = $query->execute(array($id));
|
||||
if ($row = $result->fetchRow()) {
|
||||
return array($row['parent'], $row['name']);
|
||||
return array((int)$row['parent'], $row['name']);
|
||||
} else {
|
||||
return array(-1, '');
|
||||
}
|
||||
|
||||
+7
-2
@@ -49,7 +49,12 @@ class Storage extends DAV implements ISharedStorage {
|
||||
$this->remote = $options['remote'];
|
||||
$this->remoteUser = $options['owner'];
|
||||
list($protocol, $remote) = explode('://', $this->remote);
|
||||
list($host, $root) = explode('/', $remote, 2);
|
||||
if (strpos($remote, '/')) {
|
||||
list($host, $root) = explode('/', $remote, 2);
|
||||
} else {
|
||||
$host = $remote;
|
||||
$root = '';
|
||||
}
|
||||
$secure = $protocol === 'https';
|
||||
$root = rtrim($root, '/') . '/public.php/webdav';
|
||||
$this->mountPoint = $options['mountpoint'];
|
||||
@@ -148,7 +153,7 @@ class Storage extends DAV implements ISharedStorage {
|
||||
// ownCloud instance is gone, likely to be a temporary server configuration error
|
||||
throw $e;
|
||||
}
|
||||
} catch(\Exception $shareException) {
|
||||
} catch (\Exception $shareException) {
|
||||
// todo, maybe handle 403 better and ask the user for a new password
|
||||
throw $e;
|
||||
}
|
||||
|
||||
@@ -237,4 +237,24 @@ class Helper {
|
||||
return ($result === 'yes') ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* get default share folder
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getShareFolder() {
|
||||
$shareFolder = \OCP\Config::getSystemValue('share_folder', '/');
|
||||
|
||||
return \OC\Files\Filesystem::normalizePath($shareFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* set default share folder
|
||||
*
|
||||
* @param string $shareFolder
|
||||
*/
|
||||
public static function setShareFolder($shareFolder) {
|
||||
\OCP\Config::setSystemValue('share_folder', $shareFolder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,47 +21,43 @@
|
||||
*/
|
||||
|
||||
namespace OCA\Files\Share;
|
||||
use OCA\Files_Sharing\Helper;
|
||||
|
||||
class Proxy extends \OC_FileProxy {
|
||||
|
||||
/**
|
||||
* check if the deleted folder contains share mount points and move them
|
||||
* up to the parent
|
||||
* check if the deleted folder contains share mount points and unshare them
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
public function preUnlink($path) {
|
||||
$this->moveMountPointsUp($path);
|
||||
$this->unshareChildren($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the deleted folder contains share mount points and move them
|
||||
* up to the parent
|
||||
* check if the deleted folder contains share mount points and unshare them
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
public function preRmdir($path) {
|
||||
$this->moveMountPointsUp($path);
|
||||
$this->unshareChildren($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* move share mount points up to the parent
|
||||
* unshare shared items below the deleted folder
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
private function moveMountPointsUp($path) {
|
||||
private function unshareChildren($path) {
|
||||
$view = new \OC\Files\View('/');
|
||||
|
||||
// find share mount points within $path and move them up to the parent folder
|
||||
// before we delete $path
|
||||
// find share mount points within $path and unmount them
|
||||
$mountManager = \OC\Files\Filesystem::getMountManager();
|
||||
$mountedShares = $mountManager->findIn($path);
|
||||
foreach ($mountedShares as $mount) {
|
||||
if ($mount->getStorage()->instanceOfStorage('\OC\Files\Storage\Shared')) {
|
||||
if ($mount->getStorage()->instanceOfStorage('OCA\Files_Sharing\ISharedStorage')) {
|
||||
$mountPoint = $mount->getMountPoint();
|
||||
$mountPointName = $mount->getMountPointName();
|
||||
$target = \OCA\Files_Sharing\Helper::generateUniqueTarget(dirname($path) . '/' . $mountPointName, array(), $view);
|
||||
$view->rename($mountPoint, $target);
|
||||
$view->unlink($mountPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,8 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent {
|
||||
* @return string
|
||||
*/
|
||||
public function generateTarget($filePath, $shareWith, $exclude = null) {
|
||||
$target = '/'.basename($filePath);
|
||||
$shareFolder = \OCA\Files_Sharing\Helper::getShareFolder();
|
||||
$target = \OC\Files\Filesystem::normalizePath($shareFolder . '/' . basename($filePath));
|
||||
|
||||
// for group shares we return the target right away
|
||||
if ($shareWith === false) {
|
||||
@@ -70,6 +71,18 @@ class OC_Share_Backend_File implements OCP\Share_Backend_File_Dependent {
|
||||
|
||||
\OC\Files\Filesystem::initMountPoints($shareWith);
|
||||
$view = new \OC\Files\View('/' . $shareWith . '/files');
|
||||
|
||||
if (!$view->is_dir($shareFolder)) {
|
||||
$dir = '';
|
||||
$subdirs = explode('/', $shareFolder);
|
||||
foreach ($subdirs as $subdir) {
|
||||
$dir = $dir . '/' . $subdir;
|
||||
if (!$view->is_dir($dir)) {
|
||||
$view->mkdir($dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$excludeList = \OCP\Share::getItemsSharedWithUser('file', $shareWith, self::FORMAT_TARGET_NAMES);
|
||||
if (is_array($exclude)) {
|
||||
$excludeList = array_merge($excludeList, $exclude);
|
||||
|
||||
@@ -143,8 +143,10 @@ class SharedMount extends Mount implements MoveableMount {
|
||||
* @return bool
|
||||
*/
|
||||
public function removeMount() {
|
||||
$mountManager = \OC\Files\Filesystem::getMountManager();
|
||||
$storage = $this->getStorage();
|
||||
$result = \OCP\Share::unshareFromSelf($storage->getItemType(), $storage->getMountPoint());
|
||||
$mountManager->removeMount($this->mountPoint);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -48,10 +48,10 @@ class Shared extends \OC\Files\Storage\Common implements ISharedStorage {
|
||||
|
||||
/**
|
||||
* get file cache of the shared item source
|
||||
* @return string
|
||||
* @return int
|
||||
*/
|
||||
public function getSourceId() {
|
||||
return $this->share['file_source'];
|
||||
return (int) $this->share['file_source'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -204,8 +204,8 @@ class Shared_Updater {
|
||||
static public function fixBrokenSharesOnAppUpdate() {
|
||||
// delete all shares where the original file no longer exists
|
||||
$findAndRemoveShares = \OC_DB::prepare('DELETE FROM `*PREFIX*share` ' .
|
||||
'WHERE `file_source` NOT IN ( ' .
|
||||
'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `item_type` IN (\'file\', \'folder\'))'
|
||||
'WHERE `item_type` IN (\'file\', \'folder\') ' .
|
||||
'AND `file_source` NOT IN (SELECT `fileid` FROM `*PREFIX*filecache`)'
|
||||
);
|
||||
$findAndRemoveShares->execute(array());
|
||||
}
|
||||
|
||||
@@ -100,6 +100,10 @@ if (isset($path)) {
|
||||
$file = basename($path);
|
||||
// Download the file
|
||||
if (isset($_GET['download'])) {
|
||||
if (!\OCP\App::isEnabled('files_encryption')) {
|
||||
// encryption app requires the session to store the keys in
|
||||
\OC::$server->getSession()->close();
|
||||
}
|
||||
if (isset($_GET['files'])) { // download selected files
|
||||
$files = urldecode($_GET['files']);
|
||||
$files_list = json_decode($files);
|
||||
|
||||
@@ -67,7 +67,6 @@ $server->subscribeEvent('beforeMethod', function () use ($server, $objectTree, $
|
||||
$mountManager = \OC\Files\Filesystem::getMountManager();
|
||||
$objectTree->init($root, $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
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
<?php /** @var $l OC_L10N */ ?>
|
||||
<?php $thumbSize=1024; ?>
|
||||
<?php if ( \OC\Preview::isMimeSupported($_['mimetype'])): /* This enables preview images for links (e.g. on Facebook, Google+, ...)*/?>
|
||||
<link rel="image_src" href="<?php p(OCP\Util::linkToRoute( 'core_ajax_public_preview', array('x' => $thumbSize, 'y' => $thumbSize, 'file' => $_['directory_path'], 't' => $_['dirToken']))); ?>" />
|
||||
<?php endif; ?>
|
||||
|
||||
<div id="notification-container">
|
||||
<div id="notification" style="display: none;"></div>
|
||||
</div>
|
||||
@@ -19,10 +24,10 @@
|
||||
<div class="header-right">
|
||||
<span id="details">
|
||||
<span id="save" data-protected="<?php p($_['protected'])?>" data-owner="<?php p($_['displayName'])?>" data-name="<?php p($_['filename'])?>">
|
||||
<button><?php p($l->t('Add to your ownCloud')) ?></button>
|
||||
<button id="save-button"><?php p($l->t('Add to your ownCloud')) ?></button>
|
||||
<form class="save-form hidden" action="#">
|
||||
<input type="text" id="remote_address" placeholder="example.com/owncloud"/>
|
||||
<input type="submit" value="<?php p($l->t('Save')) ?>"/>
|
||||
<button id="save-button-confirm" class="icon-confirm svg"></button>
|
||||
</form>
|
||||
</span>
|
||||
<a href="<?php p($_['downloadURL']); ?>" id="download" class="button">
|
||||
@@ -42,7 +47,7 @@
|
||||
</div>
|
||||
<?php elseif (substr($_['mimetype'], 0, strpos($_['mimetype'], '/')) == 'video'): ?>
|
||||
<div id="imgframe">
|
||||
<video tabindex="0" controls="" autoplay="">
|
||||
<video tabindex="0" controls="" preload="none">
|
||||
<source src="<?php p($_['downloadURL']); ?>" type="<?php p($_['mimetype']); ?>" />
|
||||
</video>
|
||||
</div>
|
||||
|
||||
@@ -938,6 +938,78 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
*/
|
||||
function testUpdateShareExpireDate() {
|
||||
|
||||
$fileInfo = $this->view->getFileInfo($this->folder);
|
||||
|
||||
// enforce expire date, by default 7 days after the file was shared
|
||||
\OCP\Config::setAppValue('core', 'shareapi_default_expire_date', 'yes');
|
||||
\OCP\Config::setAppValue('core', 'shareapi_enforce_expire_date', 'yes');
|
||||
|
||||
$dateWithinRange = new \DateTime();
|
||||
$dateWithinRange->add(new \DateInterval('P5D'));
|
||||
$dateOutOfRange = new \DateTime();
|
||||
$dateOutOfRange->add(new \DateInterval('P8D'));
|
||||
|
||||
$result = \OCP\Share::shareItem('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK,
|
||||
null, 1);
|
||||
|
||||
// share was successful?
|
||||
$this->assertTrue(is_string($result));
|
||||
|
||||
$items = \OCP\Share::getItemShared('file', null);
|
||||
|
||||
// make sure that we found a link share
|
||||
$this->assertEquals(1, count($items));
|
||||
|
||||
$linkShare = reset($items);
|
||||
|
||||
// update expire date to a valid value
|
||||
$params = array();
|
||||
$params['id'] = $linkShare['id'];
|
||||
$params['_put'] = array();
|
||||
$params['_put']['expireDate'] = $dateWithinRange->format('Y-m-d');
|
||||
|
||||
$result = Share\Api::updateShare($params);
|
||||
|
||||
$this->assertTrue($result->succeeded());
|
||||
|
||||
$items = \OCP\Share::getItemShared('file', $linkShare['file_source']);
|
||||
|
||||
$updatedLinkShare = reset($items);
|
||||
|
||||
// date should be changed
|
||||
$this->assertTrue(is_array($updatedLinkShare));
|
||||
$this->assertEquals($dateWithinRange->format('Y-m-d') . ' 00:00:00', $updatedLinkShare['expiration']);
|
||||
|
||||
// update expire date to a value out of range
|
||||
$params = array();
|
||||
$params['id'] = $linkShare['id'];
|
||||
$params['_put'] = array();
|
||||
$params['_put']['expireDate'] = $dateOutOfRange->format('Y-m-d');
|
||||
|
||||
$result = Share\Api::updateShare($params);
|
||||
|
||||
$this->assertFalse($result->succeeded());
|
||||
|
||||
$items = \OCP\Share::getItemShared('file', $linkShare['file_source']);
|
||||
|
||||
$updatedLinkShare = reset($items);
|
||||
|
||||
// date shouldn't be changed
|
||||
$this->assertTrue(is_array($updatedLinkShare));
|
||||
$this->assertEquals($dateWithinRange->format('Y-m-d') . ' 00:00:00', $updatedLinkShare['expiration']);
|
||||
|
||||
// cleanup
|
||||
\OCP\Config::setAppValue('core', 'shareapi_default_expire_date', 'no');
|
||||
\OCP\Config::setAppValue('core', 'shareapi_enforce_expire_date', 'no');
|
||||
\OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, null);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
* @depends testCreateShare
|
||||
@@ -1158,7 +1230,7 @@ class Test_Files_Sharing_Api extends Test_Files_Sharing_Base {
|
||||
$result = \OCP\Share::shareItem('file', $info->getId(), \OCP\Share::SHARE_TYPE_USER, \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31);
|
||||
$this->assertTrue($result);
|
||||
|
||||
$result = \OCP\Share::setExpirationDate('file', $info->getId() , $expireDate);
|
||||
$result = \OCP\Share::setExpirationDate('file', $info->getId() , $expireDate, $now);
|
||||
$this->assertTrue($result);
|
||||
|
||||
//manipulate stime so that both shares are older then the default expire date
|
||||
|
||||
@@ -32,6 +32,9 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base {
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
\OC_User::setDisplayName(self::TEST_FILES_SHARING_API_USER1, 'User One');
|
||||
\OC_User::setDisplayName(self::TEST_FILES_SHARING_API_USER2, 'User Two');
|
||||
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
|
||||
|
||||
$this->user2View = new \OC\Files\View('/'. self::TEST_FILES_SHARING_API_USER2 . '/files');
|
||||
@@ -92,6 +95,80 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base {
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
function searchDataProvider() {
|
||||
return array(
|
||||
array('%another%',
|
||||
array(
|
||||
array('name' => 'another too.txt', 'path' => 'subdir/another too.txt'),
|
||||
array('name' => 'another.txt', 'path' => 'subdir/another.txt'),
|
||||
)
|
||||
),
|
||||
array('%Another%',
|
||||
array(
|
||||
array('name' => 'another too.txt', 'path' => 'subdir/another too.txt'),
|
||||
array('name' => 'another.txt', 'path' => 'subdir/another.txt'),
|
||||
)
|
||||
),
|
||||
array('%dir%',
|
||||
array(
|
||||
array('name' => 'emptydir', 'path' => 'emptydir'),
|
||||
array('name' => 'subdir', 'path' => 'subdir'),
|
||||
array('name' => 'shareddir', 'path' => ''),
|
||||
)
|
||||
),
|
||||
array('%Dir%',
|
||||
array(
|
||||
array('name' => 'emptydir', 'path' => 'emptydir'),
|
||||
array('name' => 'subdir', 'path' => 'subdir'),
|
||||
array('name' => 'shareddir', 'path' => ''),
|
||||
)
|
||||
),
|
||||
array('%txt%',
|
||||
array(
|
||||
array('name' => 'bar.txt', 'path' => 'bar.txt'),
|
||||
array('name' => 'another too.txt', 'path' => 'subdir/another too.txt'),
|
||||
array('name' => 'another.txt', 'path' => 'subdir/another.txt'),
|
||||
)
|
||||
),
|
||||
array('%Txt%',
|
||||
array(
|
||||
array('name' => 'bar.txt', 'path' => 'bar.txt'),
|
||||
array('name' => 'another too.txt', 'path' => 'subdir/another too.txt'),
|
||||
array('name' => 'another.txt', 'path' => 'subdir/another.txt'),
|
||||
)
|
||||
),
|
||||
array('%',
|
||||
array(
|
||||
array('name' => 'bar.txt', 'path' => 'bar.txt'),
|
||||
array('name' => 'emptydir', 'path' => 'emptydir'),
|
||||
array('name' => 'subdir', 'path' => 'subdir'),
|
||||
array('name' => 'another too.txt', 'path' => 'subdir/another too.txt'),
|
||||
array('name' => 'another.txt', 'path' => 'subdir/another.txt'),
|
||||
array('name' => 'not a text file.xml', 'path' => 'subdir/not a text file.xml'),
|
||||
array('name' => 'shareddir', 'path' => ''),
|
||||
)
|
||||
),
|
||||
array('%nonexistant%',
|
||||
array(
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* we cannot use a dataProvider because that would cause the stray hook detection to remove the hooks
|
||||
* that were added in setUpBeforeClass.
|
||||
*/
|
||||
function testSearch() {
|
||||
foreach ($this->searchDataProvider() as $data) {
|
||||
list($pattern, $expectedFiles) = $data;
|
||||
|
||||
$results = $this->sharedStorage->getCache()->search($pattern);
|
||||
|
||||
$this->verifyFiles($expectedFiles, $results);
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Test searching by mime type
|
||||
*/
|
||||
@@ -112,8 +189,6 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base {
|
||||
),
|
||||
);
|
||||
$this->verifyFiles($check, $results);
|
||||
|
||||
$this->verifyFiles($check, $results);
|
||||
}
|
||||
|
||||
function testGetFolderContentsInRoot() {
|
||||
@@ -133,11 +208,15 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base {
|
||||
'name' => 'shareddir',
|
||||
'path' => 'files/shareddir',
|
||||
'mimetype' => 'httpd/unix-directory',
|
||||
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
|
||||
'displayname_owner' => 'User One',
|
||||
),
|
||||
array(
|
||||
'name' => 'shared single file.txt',
|
||||
'path' => 'files/shared single file.txt',
|
||||
'mimetype' => 'text/plain',
|
||||
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
|
||||
'displayname_owner' => 'User One',
|
||||
),
|
||||
),
|
||||
$results
|
||||
@@ -153,16 +232,22 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base {
|
||||
'name' => 'bar.txt',
|
||||
'path' => 'bar.txt',
|
||||
'mimetype' => 'text/plain',
|
||||
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
|
||||
'displayname_owner' => 'User One',
|
||||
),
|
||||
array(
|
||||
'name' => 'emptydir',
|
||||
'path' => 'emptydir',
|
||||
'mimetype' => 'httpd/unix-directory',
|
||||
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
|
||||
'displayname_owner' => 'User One',
|
||||
),
|
||||
array(
|
||||
'name' => 'subdir',
|
||||
'path' => 'subdir',
|
||||
'mimetype' => 'httpd/unix-directory',
|
||||
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
|
||||
'displayname_owner' => 'User One',
|
||||
),
|
||||
),
|
||||
$results
|
||||
@@ -187,16 +272,22 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base {
|
||||
'name' => 'another too.txt',
|
||||
'path' => 'another too.txt',
|
||||
'mimetype' => 'text/plain',
|
||||
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
|
||||
'displayname_owner' => 'User One',
|
||||
),
|
||||
array(
|
||||
'name' => 'another.txt',
|
||||
'path' => 'another.txt',
|
||||
'mimetype' => 'text/plain',
|
||||
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
|
||||
'displayname_owner' => 'User One',
|
||||
),
|
||||
array(
|
||||
'name' => 'not a text file.xml',
|
||||
'path' => 'not a text file.xml',
|
||||
'mimetype' => 'application/xml',
|
||||
'uid_owner' => self::TEST_FILES_SHARING_API_USER1,
|
||||
'displayname_owner' => 'User One',
|
||||
),
|
||||
),
|
||||
$results
|
||||
@@ -226,7 +317,7 @@ class Test_Files_Sharing_Cache extends Test_Files_Sharing_Base {
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->assertTrue(empty($results));
|
||||
$this->assertEquals(array(), $results);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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/>.
|
||||
*
|
||||
*/
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
class Test_Files_Sharing_Helper extends Test_Files_Sharing_Base {
|
||||
|
||||
/**
|
||||
* test set and get share folder
|
||||
*/
|
||||
function testSetGetShareFolder() {
|
||||
$this->assertSame('/', \OCA\Files_Sharing\Helper::getShareFolder());
|
||||
|
||||
\OCA\Files_Sharing\Helper::setShareFolder('/Shared');
|
||||
|
||||
$this->assertSame('/Shared', \OCA\Files_Sharing\Helper::getShareFolder());
|
||||
|
||||
// cleanup
|
||||
\OCP\Config::deleteSystemValue('share_folder');
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -45,12 +45,7 @@ describe('OCA.Sharing.App tests', function() {
|
||||
fileListOut = App.initSharingOut($('#app-content-sharingout'));
|
||||
});
|
||||
afterEach(function() {
|
||||
App._inFileList = null;
|
||||
App._outFileList = null;
|
||||
fileListIn.destroy();
|
||||
fileListOut.destroy();
|
||||
fileListIn = null;
|
||||
fileListOut = null;
|
||||
App.destroy();
|
||||
});
|
||||
|
||||
describe('initialization', function() {
|
||||
|
||||
@@ -165,7 +165,7 @@ describe('OCA.Sharing.Util tests', function() {
|
||||
$tr = fileList.$el.find('tbody tr:first');
|
||||
$action = $tr.find('.action-share');
|
||||
expect($action.hasClass('permanent')).toEqual(true);
|
||||
expect($action.find('>span').text()).toEqual('Shared by User One');
|
||||
expect($action.find('>span').text()).toEqual('User One');
|
||||
expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg');
|
||||
expect(OC.basename(getImageUrl($tr.find('.filename')))).toEqual('folder-shared.svg');
|
||||
});
|
||||
@@ -207,7 +207,7 @@ describe('OCA.Sharing.Util tests', function() {
|
||||
expect($tr.find('.action-share').length).toEqual(0);
|
||||
$action = $tr.find('.action-share-notification');
|
||||
expect($action.hasClass('permanent')).toEqual(true);
|
||||
expect($action.find('>span').text().trim()).toEqual('Shared by User One');
|
||||
expect($action.find('>span').text().trim()).toEqual('User One');
|
||||
expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg');
|
||||
expect(OC.basename(getImageUrl($tr.find('.filename')))).toEqual('folder-shared.svg');
|
||||
expect($action.find('img').length).toEqual(1);
|
||||
@@ -369,7 +369,7 @@ describe('OCA.Sharing.Util tests', function() {
|
||||
OC.Share.updateIcon('file', 1);
|
||||
|
||||
expect($action.hasClass('permanent')).toEqual(true);
|
||||
expect($action.find('>span').text()).toEqual('Shared by User One');
|
||||
expect($action.find('>span').text()).toEqual('User One');
|
||||
expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg');
|
||||
});
|
||||
it('keep share text after unsharing reshare', function() {
|
||||
@@ -405,7 +405,7 @@ describe('OCA.Sharing.Util tests', function() {
|
||||
OC.Share.updateIcon('file', 1);
|
||||
|
||||
expect($action.hasClass('permanent')).toEqual(true);
|
||||
expect($action.find('>span').text()).toEqual('Shared by User One');
|
||||
expect($action.find('>span').text()).toEqual('User One');
|
||||
expect(OC.basename($action.find('img').attr('src'))).toEqual('share.svg');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -499,6 +499,48 @@ describe('OCA.Sharing.FileList tests', function() {
|
||||
expect($tr.attr('data-permissions')).toEqual('31'); // read and delete
|
||||
expect($tr.attr('data-mime')).toEqual('text/plain');
|
||||
expect($tr.attr('data-mtime')).toEqual('11111000');
|
||||
expect($tr.attr('data-share-recipients')).not.toBeDefined();
|
||||
expect($tr.attr('data-share-owner')).not.toBeDefined();
|
||||
expect($tr.attr('data-share-id')).toEqual('7');
|
||||
expect($tr.find('a.name').attr('href')).toEqual(
|
||||
OC.webroot +
|
||||
'/index.php/apps/files/ajax/download.php' +
|
||||
'?dir=%2Flocal%20path&files=local%20name.txt');
|
||||
|
||||
expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
|
||||
});
|
||||
it('does not show virtual token recipient as recipient when password was set', function() {
|
||||
/* jshint camelcase: false */
|
||||
var request;
|
||||
// when a password is set, share_with contains an auth token
|
||||
ocsResponse.ocs.data[0].share_with = 'abc01234/01234abc';
|
||||
ocsResponse.ocs.data[0].share_with_displayname = 'abc01234/01234abc';
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
request = fakeServer.requests[0];
|
||||
expect(request.url).toEqual(
|
||||
OC.linkToOCS('apps/files_sharing/api/v1') +
|
||||
'shares?format=json&shared_with_me=false'
|
||||
);
|
||||
|
||||
fakeServer.requests[0].respond(
|
||||
200,
|
||||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify(ocsResponse)
|
||||
);
|
||||
|
||||
// only renders the link share entry
|
||||
var $rows = fileList.$el.find('tbody tr');
|
||||
var $tr = $rows.eq(0);
|
||||
expect($rows.length).toEqual(1);
|
||||
expect($tr.attr('data-id')).toEqual('49');
|
||||
expect($tr.attr('data-type')).toEqual('file');
|
||||
expect($tr.attr('data-file')).toEqual('local name.txt');
|
||||
expect($tr.attr('data-path')).toEqual('/local path');
|
||||
expect($tr.attr('data-size')).not.toBeDefined();
|
||||
expect($tr.attr('data-permissions')).toEqual('31'); // read and delete
|
||||
expect($tr.attr('data-mime')).toEqual('text/plain');
|
||||
expect($tr.attr('data-mtime')).toEqual('11111000');
|
||||
expect($tr.attr('data-share-recipients')).not.toBeDefined();
|
||||
expect($tr.attr('data-share-owner')).not.toBeDefined();
|
||||
expect($tr.attr('data-share-id')).toEqual('7');
|
||||
expect($tr.find('a.name').attr('href')).toEqual(
|
||||
|
||||
@@ -47,7 +47,6 @@ class Test_Files_Sharing_Proxy extends Test_Files_Sharing_Base {
|
||||
$this->filename = '/share-api-test';
|
||||
|
||||
// save file with content
|
||||
$this->view->file_put_contents($this->filename, $this->data);
|
||||
$this->view->mkdir($this->folder);
|
||||
$this->view->mkdir($this->folder . $this->subfolder);
|
||||
$this->view->mkdir($this->folder . $this->subfolder . $this->subsubfolder);
|
||||
@@ -56,7 +55,6 @@ class Test_Files_Sharing_Proxy extends Test_Files_Sharing_Base {
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
$this->view->unlink($this->filename);
|
||||
$this->view->deleteAll($this->folder);
|
||||
|
||||
self::$tempStorage = null;
|
||||
@@ -69,30 +67,33 @@ class Test_Files_Sharing_Proxy extends Test_Files_Sharing_Base {
|
||||
*/
|
||||
function testpreUnlink() {
|
||||
|
||||
$fileInfo1 = \OC\Files\Filesystem::getFileInfo($this->filename);
|
||||
$fileInfo2 = \OC\Files\Filesystem::getFileInfo($this->folder);
|
||||
|
||||
$result = \OCP\Share::shareItem('file', $fileInfo1->getId(), \OCP\Share::SHARE_TYPE_USER, self::TEST_FILES_SHARING_API_USER2, 31);
|
||||
$this->assertTrue($result);
|
||||
|
||||
$result = \OCP\Share::shareItem('folder', $fileInfo2->getId(), \OCP\Share::SHARE_TYPE_USER, self::TEST_FILES_SHARING_API_USER2, 31);
|
||||
$this->assertTrue($result);
|
||||
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
|
||||
|
||||
// move shared folder to 'localDir' and rename it, so that it uses the same
|
||||
// name as the shared file
|
||||
// one folder should be shared with the user
|
||||
$sharedFolders = \OCP\Share::getItemsSharedWith('folder');
|
||||
$this->assertSame(1, count($sharedFolders));
|
||||
|
||||
// move shared folder to 'localDir'
|
||||
\OC\Files\Filesystem::mkdir('localDir');
|
||||
$result = \OC\Files\Filesystem::rename($this->folder, '/localDir/' . $this->filename);
|
||||
$result = \OC\Files\Filesystem::rename($this->folder, '/localDir/' . $this->folder);
|
||||
$this->assertTrue($result);
|
||||
|
||||
\OC\Files\Filesystem::unlink('localDir');
|
||||
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
|
||||
|
||||
// after we deleted 'localDir' the share should be moved up to the root and be
|
||||
// renamed to "filename (2)"
|
||||
$this->assertTrue(\OC\Files\Filesystem::file_exists($this->filename));
|
||||
$this->assertTrue(\OC\Files\Filesystem::file_exists($this->filename . ' (2)' ));
|
||||
// after the parent directory was deleted the share should be unshared
|
||||
$sharedFolders = \OCP\Share::getItemsSharedWith('folder');
|
||||
$this->assertTrue(empty($sharedFolders));
|
||||
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
|
||||
|
||||
// the folder for the owner should still exists
|
||||
$this->assertTrue(\OC\Files\Filesystem::file_exists($this->folder));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,36 @@ class Test_Files_Sharing extends Test_Files_Sharing_Base {
|
||||
|
||||
}
|
||||
|
||||
function testShareWithDifferentShareFolder() {
|
||||
|
||||
$fileinfo = $this->view->getFileInfo($this->filename);
|
||||
$folderinfo = $this->view->getFileInfo($this->folder);
|
||||
|
||||
$fileShare = \OCP\Share::shareItem('file', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
|
||||
self::TEST_FILES_SHARING_API_USER2, 31);
|
||||
$this->assertTrue($fileShare);
|
||||
|
||||
\OCA\Files_Sharing\Helper::setShareFolder('/Shared/subfolder');
|
||||
|
||||
$folderShare = \OCP\Share::shareItem('folder', $folderinfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
|
||||
self::TEST_FILES_SHARING_API_USER2, 31);
|
||||
$this->assertTrue($folderShare);
|
||||
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
|
||||
|
||||
$this->assertTrue(\OC\Files\Filesystem::file_exists($this->filename));
|
||||
$this->assertTrue(\OC\Files\Filesystem::file_exists('/Shared/subfolder/' . $this->folder));
|
||||
|
||||
//cleanup
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
|
||||
\OCP\Share::unshare('file', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
|
||||
self::TEST_FILES_SHARING_API_USER2);
|
||||
\OCP\Share::unshare('folder', $folderinfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
|
||||
self::TEST_FILES_SHARING_API_USER2);
|
||||
|
||||
\OCP\Config::deleteSystemValue('share_folder');
|
||||
}
|
||||
|
||||
/**
|
||||
* shared files should never have delete permissions
|
||||
* @dataProvider DataProviderTestFileSharePermissions
|
||||
|
||||
@@ -52,9 +52,11 @@ class Test_Files_Sharing_Storage extends Test_Files_Sharing_Base {
|
||||
}
|
||||
|
||||
/**
|
||||
* if the parent of the mount point is gone then the mount point should move up
|
||||
*
|
||||
* @medium
|
||||
*/
|
||||
function testDeleteParentOfMountPoint() {
|
||||
function testParentOfMountPointIsGone() {
|
||||
|
||||
// share to user
|
||||
$fileinfo = $this->view->getFileInfo($this->folder);
|
||||
@@ -79,8 +81,8 @@ class Test_Files_Sharing_Storage extends Test_Files_Sharing_Base {
|
||||
$this->assertFalse($user2View->is_dir($this->folder));
|
||||
|
||||
// delete the local folder
|
||||
$result = $user2View->unlink('/localfolder');
|
||||
$this->assertTrue($result);
|
||||
$fullPath = \OC_Config::getValue('datadirectory') . '/' . self::TEST_FILES_SHARING_API_USER2 . '/files/localfolder';
|
||||
rmdir($fullPath);
|
||||
|
||||
//enforce reload of the mount points
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
|
||||
|
||||
@@ -87,13 +87,18 @@ class Test_Files_Sharing_Update_Routine extends Test_Files_Sharing_Base {
|
||||
/**
|
||||
* @medium
|
||||
*/
|
||||
function testRemoveBrokenShares() {
|
||||
function testRemoveBrokenFileShares() {
|
||||
|
||||
$this->prepareFileCache();
|
||||
|
||||
// check if there are just 3 shares (see setUp - precondition: empty table)
|
||||
$countShares = \OC_DB::prepare('SELECT COUNT(`id`) FROM `*PREFIX*share`');
|
||||
$result = $countShares->execute()->fetchOne();
|
||||
// check if there are just 4 shares (see setUp - precondition: empty table)
|
||||
$countAllShares = \OC_DB::prepare('SELECT COUNT(`id`) FROM `*PREFIX*share`');
|
||||
$result = $countAllShares->execute()->fetchOne();
|
||||
$this->assertEquals(4, $result);
|
||||
|
||||
// check if there are just 3 file shares (see setUp - precondition: empty table)
|
||||
$countFileShares = \OC_DB::prepare('SELECT COUNT(`id`) FROM `*PREFIX*share` WHERE `item_type` IN (\'file\', \'folder\')');
|
||||
$result = $countFileShares->execute()->fetchOne();
|
||||
$this->assertEquals(3, $result);
|
||||
|
||||
// check if there are just 2 items (see setUp - precondition: empty table)
|
||||
@@ -105,19 +110,23 @@ class Test_Files_Sharing_Update_Routine extends Test_Files_Sharing_Base {
|
||||
\OC\Files\Cache\Shared_Updater::fixBrokenSharesOnAppUpdate();
|
||||
|
||||
// check if there are just 2 shares (one gets killed by the code as there is no filecache entry for this)
|
||||
$countShares = \OC_DB::prepare('SELECT COUNT(`id`) FROM `*PREFIX*share`');
|
||||
$result = $countShares->execute()->fetchOne();
|
||||
$result = $countFileShares->execute()->fetchOne();
|
||||
$this->assertEquals(2, $result);
|
||||
|
||||
// check if the share of file '200' is removed as there is no entry for this in filecache table
|
||||
$countShares = \OC_DB::prepare('SELECT COUNT(`id`) FROM `*PREFIX*share` WHERE `file_source` = 200');
|
||||
$result = $countShares->execute()->fetchOne();
|
||||
$countFileShares = \OC_DB::prepare('SELECT COUNT(`id`) FROM `*PREFIX*share` WHERE `file_source` = 200');
|
||||
$result = $countFileShares->execute()->fetchOne();
|
||||
$this->assertEquals(0, $result);
|
||||
|
||||
// check if there are just 2 items
|
||||
$countItems = \OC_DB::prepare('SELECT COUNT(`fileid`) FROM `*PREFIX*filecache`');
|
||||
$result = $countItems->execute()->fetchOne();
|
||||
$this->assertEquals(2, $result);
|
||||
|
||||
// the calendar share survived
|
||||
$countOtherShares = \OC_DB::prepare('SELECT COUNT(`id`) FROM `*PREFIX*share` WHERE `item_source` = \'999\'');
|
||||
$result = $countOtherShares->execute()->fetchOne();
|
||||
$this->assertEquals(1, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,8 +152,13 @@ class Test_Files_Sharing_Update_Routine extends Test_Files_Sharing_Base {
|
||||
}
|
||||
}
|
||||
|
||||
$shareFolder = \OCP\Config::getSystemValue('share_folder', '/');
|
||||
|
||||
$this->assertSame('/Shared', $shareFolder);
|
||||
|
||||
// cleanup
|
||||
$this->cleanupSharedTable();
|
||||
\OCP\Config::deleteSystemValue('share_folder');
|
||||
|
||||
}
|
||||
|
||||
@@ -167,6 +181,7 @@ class Test_Files_Sharing_Update_Routine extends Test_Files_Sharing_Base {
|
||||
array(\OCP\Share::SHARE_TYPE_USER, 'folder', 'user2', 'admin', '/foo2'),
|
||||
array(\OCP\Share::SHARE_TYPE_USER, 'file', 'user3', 'admin', '/foo3'),
|
||||
array(\OCP\Share::SHARE_TYPE_USER, 'folder', 'user4', 'admin', '/foo4'),
|
||||
array(\OCP\Share::SHARE_TYPE_USER, 'folder', 'user4', 'admin', "/foo'4"),
|
||||
array(\OCP\Share::SHARE_TYPE_LINK, 'file', 'user1', 'admin', '/ShouldNotChange'),
|
||||
array(\OCP\Share::SHARE_TYPE_CONTACT, 'contact', 'admin', 'user1', '/ShouldNotChange'),
|
||||
|
||||
@@ -228,6 +243,11 @@ class Test_Files_Sharing_Update_Routine extends Test_Files_Sharing_Base {
|
||||
$addShares->execute(array($fileIds[0]));
|
||||
$addShares->execute(array(200)); // id of "deleted" file
|
||||
$addShares->execute(array($fileIds[1]));
|
||||
|
||||
// add a few unrelated shares, calendar share that must be left untouched
|
||||
$addShares = \OC_DB::prepare('INSERT INTO `*PREFIX*share` (`item_source`, `item_type`, `uid_owner`) VALUES (?, \'calendar\', 1)');
|
||||
// the number is used as item_source
|
||||
$addShares->execute(array(999));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ class Test_Files_Sharing_Updater extends Test_Files_Sharing_Base {
|
||||
|
||||
/**
|
||||
* test deletion of a folder which contains share mount points. Share mount
|
||||
* points should move up to the parent before the folder gets deleted so
|
||||
* points should be unshared before the folder gets deleted so
|
||||
* that the mount point doesn't end up at the trash bin
|
||||
*/
|
||||
function testDeleteParentFolder() {
|
||||
@@ -78,6 +78,9 @@ class Test_Files_Sharing_Updater extends Test_Files_Sharing_Base {
|
||||
// check if user2 can see the shared folder
|
||||
$this->assertTrue($view->file_exists($this->folder));
|
||||
|
||||
$foldersShared = \OCP\Share::getItemsSharedWith('folder');
|
||||
$this->assertSame(1, count($foldersShared));
|
||||
|
||||
$view->mkdir("localFolder");
|
||||
$view->file_put_contents("localFolder/localFile.txt", "local file");
|
||||
|
||||
@@ -91,8 +94,9 @@ class Test_Files_Sharing_Updater extends Test_Files_Sharing_Base {
|
||||
|
||||
$this->loginHelper(self::TEST_FILES_SHARING_API_USER2);
|
||||
|
||||
// mount point should move up again
|
||||
$this->assertTrue($view->file_exists($this->folder));
|
||||
// shared folder should be unshared
|
||||
$foldersShared = \OCP\Share::getItemsSharedWith('folder');
|
||||
$this->assertTrue(empty($foldersShared));
|
||||
|
||||
// trashbin should contain the local file but not the mount point
|
||||
$rootView = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2);
|
||||
@@ -109,10 +113,6 @@ class Test_Files_Sharing_Updater extends Test_Files_Sharing_Base {
|
||||
if ($status === false) {
|
||||
\OC_App::disable('files_trashbin');
|
||||
}
|
||||
// cleanup
|
||||
$this->loginHelper(self::TEST_FILES_SHARING_API_USER1);
|
||||
$result = \OCP\Share::unshare('folder', $fileinfo->getId(), \OCP\Share::SHARE_TYPE_USER, self::TEST_FILES_SHARING_API_USER2);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
$l = OC_L10N::get('files_trashbin');
|
||||
|
||||
OC::$CLASSPATH['OCA\Files_Trashbin\Exceptions\CopyRecursiveException'] = 'files_trashbin/lib/exceptions.php';
|
||||
|
||||
// register hooks
|
||||
\OCA\Files_Trashbin\Trashbin::registerHooks();
|
||||
|
||||
|
||||
@@ -24,4 +24,5 @@
|
||||
<types>
|
||||
<filesystem/>
|
||||
</types>
|
||||
<ocsid>166052</ocsid>
|
||||
</info>
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.6.1
|
||||
0.6.2
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* ownCloud - trash bin
|
||||
*
|
||||
* @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\Files_Trashbin\Exceptions;
|
||||
|
||||
class CopyRecursiveException extends \Exception {
|
||||
}
|
||||
@@ -117,10 +117,18 @@ class Trashbin {
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
\OC_FileProxy::$enabled = false;
|
||||
$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
|
||||
$sizeOfAddedFiles = self::copy_recursive('/files/' . $file_path, $trashPath, $view);
|
||||
try {
|
||||
$sizeOfAddedFiles = self::copy_recursive('/files/'.$file_path, $trashPath, $view);
|
||||
} catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
|
||||
$sizeOfAddedFiles = false;
|
||||
if ($view->file_exists($trashPath)) {
|
||||
$view->deleteAll($trashPath);
|
||||
}
|
||||
\OC_Log::write('files_trashbin', 'Couldn\'t move ' . $file_path . ' to the trash bin', \OC_log::ERROR);
|
||||
}
|
||||
\OC_FileProxy::$enabled = $proxyStatus;
|
||||
|
||||
if ($view->file_exists('files_trashbin/files/' . $filename . '.d' . $timestamp)) {
|
||||
if ($sizeOfAddedFiles !== false) {
|
||||
$size = $sizeOfAddedFiles;
|
||||
$query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
|
||||
$result = $query->execute(array($filename, $timestamp, $location, $user));
|
||||
@@ -137,8 +145,6 @@ class Trashbin {
|
||||
if ($user !== $owner) {
|
||||
self::copyFilesToOwner($file_path, $owner, $ownerPath, $timestamp);
|
||||
}
|
||||
} else {
|
||||
\OC_Log::write('files_trashbin', 'Couldn\'t move ' . $file_path . ' to the trash bin', \OC_log::ERROR);
|
||||
}
|
||||
|
||||
$userTrashSize += $size;
|
||||
@@ -823,13 +829,19 @@ class Trashbin {
|
||||
$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
|
||||
} else {
|
||||
$size += $view->filesize($pathDir);
|
||||
$view->copy($pathDir, $destination . '/' . $i['name']);
|
||||
$result = $view->copy($pathDir, $destination . '/' . $i['name']);
|
||||
if (!$result) {
|
||||
throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
|
||||
}
|
||||
$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$size += $view->filesize($source);
|
||||
$view->copy($source, $destination);
|
||||
$result = $view->copy($source, $destination);
|
||||
if (!$result) {
|
||||
throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
|
||||
}
|
||||
$view->touch($destination, $view->filemtime($source));
|
||||
}
|
||||
return $size;
|
||||
|
||||
@@ -8,9 +8,4 @@ OC::$CLASSPATH['OCA\Files_Versions\Capabilities'] = 'files_versions/lib/capabili
|
||||
OCP\Util::addscript('files_versions', 'versions');
|
||||
OCP\Util::addStyle('files_versions', 'versions');
|
||||
|
||||
// Listen to write signals
|
||||
OCP\Util::connectHook('OC_Filesystem', 'write', "OCA\Files_Versions\Hooks", "write_hook");
|
||||
// Listen to delete and rename signals
|
||||
OCP\Util::connectHook('OC_Filesystem', 'post_delete', "OCA\Files_Versions\Hooks", "remove_hook");
|
||||
OCP\Util::connectHook('OC_Filesystem', 'delete', "OCA\Files_Versions\Hooks", "pre_remove_hook");
|
||||
OCP\Util::connectHook('OC_Filesystem', 'rename', "OCA\Files_Versions\Hooks", "rename_hook");
|
||||
\OCA\Files_Versions\Hooks::connectHooks();
|
||||
|
||||
@@ -29,4 +29,5 @@
|
||||
<filesystem/>
|
||||
</types>
|
||||
<default_enable/>
|
||||
<ocsid>166053</ocsid>
|
||||
</info>
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
// Register with the capabilities API
|
||||
OC_API::register('get', '/cloud/capabilities', array('OCA\Files_Versions\Capabilities', 'getCapabilities'), 'files_versions', OC_API::USER_AUTH);
|
||||
|
||||
/** @var $this \OCP\Route\IRouter */
|
||||
$this->create('core_ajax_versions_preview', '/preview')->action(
|
||||
function() {
|
||||
require_once __DIR__ . '/../ajax/preview.php';
|
||||
});
|
||||
|
||||
// Register with the capabilities API
|
||||
OC_API::register('get', '/cloud/capabilities', array('OCA\Files_Versions\Capabilities', 'getCapabilities'), 'files_versions', OC_API::USER_AUTH);
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.0.4
|
||||
1.0.5
|
||||
|
||||
@@ -14,6 +14,16 @@ namespace OCA\Files_Versions;
|
||||
|
||||
class Hooks {
|
||||
|
||||
public static function connectHooks() {
|
||||
// Listen to write signals
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'write', "OCA\Files_Versions\Hooks", "write_hook");
|
||||
// Listen to delete and rename signals
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_delete', "OCA\Files_Versions\Hooks", "remove_hook");
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'delete', "OCA\Files_Versions\Hooks", "pre_remove_hook");
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'rename', "OCA\Files_Versions\Hooks", "rename_hook");
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'copy', "OCA\Files_Versions\Hooks", "copy_hook");
|
||||
}
|
||||
|
||||
/**
|
||||
* listen to write event.
|
||||
*/
|
||||
@@ -69,7 +79,25 @@ class Hooks {
|
||||
$oldpath = $params['oldpath'];
|
||||
$newpath = $params['newpath'];
|
||||
if($oldpath<>'' && $newpath<>'') {
|
||||
Storage::rename( $oldpath, $newpath );
|
||||
Storage::renameOrCopy($oldpath, $newpath, 'rename');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* copy versions of copied files
|
||||
* @param array $params array with oldpath and newpath
|
||||
*
|
||||
* This function is connected to the copy signal of OC_Filesystem and copies the
|
||||
* the stored versions to the new location
|
||||
*/
|
||||
public static function copy_hook($params) {
|
||||
|
||||
if (\OCP\App::isEnabled('files_versions')) {
|
||||
$oldpath = $params['oldpath'];
|
||||
$newpath = $params['newpath'];
|
||||
if($oldpath<>'' && $newpath<>'') {
|
||||
Storage::renameOrCopy($oldpath, $newpath, 'copy');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,23 @@ class Storage {
|
||||
'filename' => $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* delete the version from the storage and cache
|
||||
*
|
||||
* @param \OC\Files\View $view
|
||||
* @param string $path
|
||||
*/
|
||||
protected static function deleteVersion($view, $path) {
|
||||
$view->unlink($path);
|
||||
/**
|
||||
* @var \OC\Files\Storage\Storage $storage
|
||||
* @var string $internalPath
|
||||
*/
|
||||
list($storage, $internalPath) = $view->resolvePath($path);
|
||||
$cache = $storage->getCache($internalPath);
|
||||
$cache->remove($internalPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete versions of a file
|
||||
*/
|
||||
@@ -142,15 +159,14 @@ class Storage {
|
||||
|
||||
if (!\OC\Files\Filesystem::file_exists($path)) {
|
||||
|
||||
$versions_fileview = new \OC\Files\View('/' . $uid . '/files_versions');
|
||||
$view = new \OC\Files\View('/' . $uid . '/files_versions');
|
||||
|
||||
$abs_path = $versions_fileview->getLocalFile($filename . '.v');
|
||||
$versions = self::getVersions($uid, $filename);
|
||||
if (!empty($versions)) {
|
||||
foreach ($versions as $v) {
|
||||
\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $abs_path . $v['version']));
|
||||
unlink($abs_path . $v['version']);
|
||||
\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $abs_path . $v['version']));
|
||||
\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path . $v['version']));
|
||||
self::deleteVersion($view, $filename . '.v' . $v['version']);
|
||||
\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path . $v['version']));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,9 +174,12 @@ class Storage {
|
||||
}
|
||||
|
||||
/**
|
||||
* rename versions of a file
|
||||
* rename or copy versions of a file
|
||||
* @param string $old_path
|
||||
* @param string $new_path
|
||||
* @param string $operation can be 'copy' or 'rename'
|
||||
*/
|
||||
public static function rename($old_path, $new_path) {
|
||||
public static function renameOrCopy($old_path, $new_path, $operation) {
|
||||
list($uid, $oldpath) = self::getUidAndFilename($old_path);
|
||||
list($uidn, $newpath) = self::getUidAndFilename($new_path);
|
||||
$versions_view = new \OC\Files\View('/'.$uid .'/files_versions');
|
||||
@@ -172,18 +191,21 @@ class Storage {
|
||||
return self::store($new_path);
|
||||
}
|
||||
|
||||
self::expire($newpath);
|
||||
|
||||
if ( $files_view->is_dir($oldpath) && $versions_view->is_dir($oldpath) ) {
|
||||
$versions_view->rename($oldpath, $newpath);
|
||||
$versions_view->$operation($oldpath, $newpath);
|
||||
} else if ( ($versions = Storage::getVersions($uid, $oldpath)) ) {
|
||||
// create missing dirs if necessary
|
||||
self::createMissingDirectories($newpath, new \OC\Files\View('/'. $uidn));
|
||||
|
||||
foreach ($versions as $v) {
|
||||
$versions_view->rename($oldpath.'.v'.$v['version'], $newpath.'.v'.$v['version']);
|
||||
$versions_view->$operation($oldpath.'.v'.$v['version'], $newpath.'.v'.$v['version']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$files_view->is_dir($newpath)) {
|
||||
self::expire($newpath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,7 +242,7 @@ class Storage {
|
||||
return true;
|
||||
|
||||
}else if ( $versionCreated ) {
|
||||
$users_view->unlink($version);
|
||||
self::deleteVersion($users_view, $version);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -238,34 +260,46 @@ class Storage {
|
||||
public static function getVersions($uid, $filename, $userFullPath = '') {
|
||||
$versions = array();
|
||||
// fetch for old versions
|
||||
$view = new \OC\Files\View('/' . $uid . '/' . self::VERSIONS_ROOT);
|
||||
$view = new \OC\Files\View('/' . $uid . '/');
|
||||
|
||||
$pathinfo = pathinfo($filename);
|
||||
|
||||
$files = $view->getDirectoryContent($pathinfo['dirname']);
|
||||
|
||||
$versionedFile = $pathinfo['basename'];
|
||||
|
||||
foreach ($files as $file) {
|
||||
if ($file['type'] === 'file') {
|
||||
$pos = strrpos($file['path'], '.v');
|
||||
$currentFile = substr($file['name'], 0, strrpos($file['name'], '.v'));
|
||||
if ($currentFile === $versionedFile) {
|
||||
$version = substr($file['path'], $pos + 2);
|
||||
$key = $version . '#' . $filename;
|
||||
$versions[$key]['cur'] = 0;
|
||||
$versions[$key]['version'] = $version;
|
||||
$versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($version);
|
||||
if (empty($userFullPath)) {
|
||||
$versions[$key]['preview'] = '';
|
||||
} else {
|
||||
$versions[$key]['preview'] = \OCP\Util::linkToRoute('core_ajax_versions_preview', array('file' => $userFullPath, 'version' => $version));
|
||||
$dir = \OC\Files\Filesystem::normalizePath(self::VERSIONS_ROOT . '/' . $pathinfo['dirname']);
|
||||
|
||||
$dirContent = false;
|
||||
if ($view->is_dir($dir)) {
|
||||
$dirContent = $view->opendir($dir);
|
||||
}
|
||||
|
||||
if ($dirContent === false) {
|
||||
return $versions;
|
||||
}
|
||||
|
||||
if (is_resource($dirContent)) {
|
||||
while (($entryName = readdir($dirContent)) !== false) {
|
||||
if (!\OC\Files\Filesystem::isIgnoredDir($entryName)) {
|
||||
$pathparts = pathinfo($entryName);
|
||||
$filename = $pathparts['filename'];
|
||||
if ($filename === $versionedFile) {
|
||||
$pathparts = pathinfo($entryName);
|
||||
$timestamp = substr($pathparts['extension'], 1);
|
||||
$filename = $pathparts['filename'];
|
||||
$key = $timestamp . '#' . $filename;
|
||||
$versions[$key]['version'] = $timestamp;
|
||||
$versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
|
||||
if (empty($userFullPath)) {
|
||||
$versions[$key]['preview'] = '';
|
||||
} else {
|
||||
$versions[$key]['preview'] = \OCP\Util::linkToRoute('core_ajax_versions_preview', array('file' => $userFullPath, 'version' => $timestamp));
|
||||
}
|
||||
$versions[$key]['path'] = $pathinfo['dirname'] . '/' . $filename;
|
||||
$versions[$key]['name'] = $versionedFile;
|
||||
$versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
|
||||
}
|
||||
$versions[$key]['path'] = $filename;
|
||||
$versions[$key]['name'] = $versionedFile;
|
||||
$versions[$key]['size'] = $file['size'];
|
||||
}
|
||||
}
|
||||
closedir($dirContent);
|
||||
}
|
||||
|
||||
// sort with newest version first
|
||||
@@ -472,7 +506,7 @@ class Storage {
|
||||
|
||||
foreach($toDelete as $key => $path) {
|
||||
\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path));
|
||||
$versionsFileview->unlink($path);
|
||||
self::deleteVersion($versionsFileview, $path);
|
||||
\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path));
|
||||
unset($allVersions[$key]); // update array with the versions we keep
|
||||
\OCP\Util::writeLog('files_versions', "Expire: " . $path, \OCP\Util::DEBUG);
|
||||
@@ -486,7 +520,7 @@ class Storage {
|
||||
while ($availableSpace < 0 && $i < $numOfVersions) {
|
||||
$version = current($allVersions);
|
||||
\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version']));
|
||||
$versionsFileview->unlink($version['path'].'.v'.$version['version']);
|
||||
self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
|
||||
\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version']));
|
||||
\OCP\Util::writeLog('files_versions', 'running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'] , \OCP\Util::DEBUG);
|
||||
$versionsSize -= $version['size'];
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../appinfo/app.php';
|
||||
require_once __DIR__ . '/../lib/versions.php';
|
||||
|
||||
/**
|
||||
@@ -28,6 +29,32 @@ require_once __DIR__ . '/../lib/versions.php';
|
||||
*/
|
||||
class Test_Files_Versioning extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
const TEST_VERSIONS_USER = 'test-versions-user';
|
||||
const USERS_VERSIONS_ROOT = '/test-versions-user/files_versions';
|
||||
|
||||
private $rootView;
|
||||
|
||||
public static function setUpBeforeClass() {
|
||||
// create test user
|
||||
self::loginHelper(self::TEST_VERSIONS_USER, true);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass() {
|
||||
// cleanup test user
|
||||
\OC_User::deleteUser(self::TEST_VERSIONS_USER);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
self::loginHelper(self::TEST_VERSIONS_USER);
|
||||
$this->rootView = new \OC\Files\View();
|
||||
if (!$this->rootView->file_exists(self::USERS_VERSIONS_ROOT)) {
|
||||
$this->rootView->mkdir(self::USERS_VERSIONS_ROOT);
|
||||
}
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
$this->rootView->deleteAll(self::USERS_VERSIONS_ROOT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
@@ -176,6 +203,121 @@ class Test_Files_Versioning extends \PHPUnit_Framework_TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
function testRename() {
|
||||
|
||||
\OC\Files\Filesystem::file_put_contents("test.txt", "test file");
|
||||
|
||||
$t1 = time();
|
||||
// second version is two weeks older, this way we make sure that no
|
||||
// version will be expired
|
||||
$t2 = $t1 - 60 * 60 * 24 * 14;
|
||||
|
||||
// create some versions
|
||||
$v1 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t1;
|
||||
$v2 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t2;
|
||||
$v1Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t1;
|
||||
$v2Renamed = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t2;
|
||||
|
||||
$this->rootView->file_put_contents($v1, 'version1');
|
||||
$this->rootView->file_put_contents($v2, 'version2');
|
||||
|
||||
// execute rename hook of versions app
|
||||
\OCA\Files_Versions\Storage::renameOrCopy("test.txt", "test2.txt", 'rename');
|
||||
|
||||
$this->assertFalse($this->rootView->file_exists($v1));
|
||||
$this->assertFalse($this->rootView->file_exists($v2));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists($v1Renamed));
|
||||
$this->assertTrue($this->rootView->file_exists($v2Renamed));
|
||||
|
||||
//cleanup
|
||||
\OC\Files\Filesystem::unlink('test2.txt');
|
||||
}
|
||||
|
||||
function testCopy() {
|
||||
|
||||
\OC\Files\Filesystem::file_put_contents("test.txt", "test file");
|
||||
|
||||
$t1 = time();
|
||||
// second version is two weeks older, this way we make sure that no
|
||||
// version will be expired
|
||||
$t2 = $t1 - 60 * 60 * 24 * 14;
|
||||
|
||||
// create some versions
|
||||
$v1 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t1;
|
||||
$v2 = self::USERS_VERSIONS_ROOT . '/test.txt.v' . $t2;
|
||||
$v1Copied = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t1;
|
||||
$v2Copied = self::USERS_VERSIONS_ROOT . '/test2.txt.v' . $t2;
|
||||
|
||||
$this->rootView->file_put_contents($v1, 'version1');
|
||||
$this->rootView->file_put_contents($v2, 'version2');
|
||||
|
||||
// execute copy hook of versions app
|
||||
\OCA\Files_Versions\Storage::renameOrCopy("test.txt", "test2.txt", 'copy');
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists($v1));
|
||||
$this->assertTrue($this->rootView->file_exists($v2));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists($v1Copied));
|
||||
$this->assertTrue($this->rootView->file_exists($v2Copied));
|
||||
|
||||
//cleanup
|
||||
\OC\Files\Filesystem::unlink('test.txt');
|
||||
\OC\Files\Filesystem::unlink('test2.txt');
|
||||
}
|
||||
|
||||
/**
|
||||
* test if we find all versions and if the versions array contain
|
||||
* the correct 'path' and 'name'
|
||||
*/
|
||||
public function testGetVersions() {
|
||||
|
||||
$t1 = time();
|
||||
// second version is two weeks older, this way we make sure that no
|
||||
// version will be expired
|
||||
$t2 = $t1 - 60 * 60 * 24 * 14;
|
||||
|
||||
// create some versions
|
||||
$v1 = self::USERS_VERSIONS_ROOT . '/subfolder/test.txt.v' . $t1;
|
||||
$v2 = self::USERS_VERSIONS_ROOT . '/subfolder/test.txt.v' . $t2;
|
||||
|
||||
$this->rootView->mkdir(self::USERS_VERSIONS_ROOT . '/subfolder/');
|
||||
|
||||
$this->rootView->file_put_contents($v1, 'version1');
|
||||
$this->rootView->file_put_contents($v2, 'version2');
|
||||
|
||||
// execute copy hook of versions app
|
||||
$versions = \OCA\Files_Versions\Storage::getVersions(self::TEST_VERSIONS_USER, '/subfolder/test.txt');
|
||||
|
||||
$this->assertSame(2, count($versions));
|
||||
|
||||
foreach ($versions as $version) {
|
||||
$this->assertSame('/subfolder/test.txt', $version['path']);
|
||||
$this->assertSame('test.txt', $version['name']);
|
||||
}
|
||||
|
||||
//cleanup
|
||||
$this->rootView->deleteAll(self::USERS_VERSIONS_ROOT . '/subfolder');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param bool $create
|
||||
* @param bool $password
|
||||
*/
|
||||
public static function loginHelper($user, $create = false) {
|
||||
|
||||
if ($create) {
|
||||
\OC_User::createUser($user, $user);
|
||||
}
|
||||
|
||||
\OC_Util::tearDownFS();
|
||||
\OC_User::setUserId('');
|
||||
\OC\Files\Filesystem::tearDown();
|
||||
\OC_User::setUserId($user);
|
||||
\OC_Util::setupFS($user);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// extend the original class to make it possible to test protected methods
|
||||
|
||||
@@ -17,4 +17,5 @@
|
||||
<documentation>
|
||||
<admin>http://doc.owncloud.org/server/6.0/go.php?to=admin-ldap</admin>
|
||||
</documentation>
|
||||
<ocsid>166061</ocsid>
|
||||
</info>
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.4.2
|
||||
0.4.3
|
||||
|
||||
@@ -127,3 +127,8 @@ select[multiple=multiple] + button {
|
||||
.ldap_grey {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.outoftheway {
|
||||
position: absolute;
|
||||
left: -2000px;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user