Compare commits
204 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -367,7 +371,6 @@ table td.filename .uploadtext {
|
||||
left: 18px;
|
||||
}
|
||||
|
||||
|
||||
#fileList tr td.filename {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@@ -432,7 +435,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.
|
||||
*
|
||||
|
||||
@@ -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 + '/';
|
||||
}
|
||||
|
||||
+42
-11
@@ -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);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -369,7 +371,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();
|
||||
}
|
||||
@@ -598,6 +605,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 +712,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 +720,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 +778,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 +820,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 +921,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 +969,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 +999,7 @@
|
||||
}
|
||||
|
||||
this.setFiles(result.data.files);
|
||||
return true
|
||||
return true;
|
||||
},
|
||||
|
||||
updateStorageStatistics: function(force) {
|
||||
@@ -1566,7 +1597,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('('));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -1696,7 +1696,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 +1710,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 +1778,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 +1824,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 +1946,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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -303,7 +303,7 @@ class Hooks {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* update share keys if a file was shared
|
||||
*/
|
||||
public static function postShared($params) {
|
||||
|
||||
@@ -313,34 +313,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 +395,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 +419,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 +474,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 +481,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 +517,7 @@ class Hooks {
|
||||
}
|
||||
|
||||
// handle share keys
|
||||
if (!$view->is_dir($oldKeyfilePath)) {
|
||||
if ($type === 'file') {
|
||||
$oldKeyfilePath .= '.key';
|
||||
$newKeyfilePath .= '.key';
|
||||
|
||||
@@ -481,27 +525,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 +634,7 @@ class Hooks {
|
||||
}
|
||||
|
||||
/**
|
||||
* unmount file from yourself
|
||||
* remember files/folders which get unmounted
|
||||
*/
|
||||
public static function preUmount($params) {
|
||||
@@ -613,6 +653,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 +685,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,6 +390,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 +398,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 +425,7 @@ class Crypt {
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
return false;
|
||||
|
||||
throw new Exceptions\MultiKeyEncryptException('multi key encryption failed: ' . openssl_error_string(), 20);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -438,8 +435,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 +445,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 +453,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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?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;
|
||||
|
||||
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 {
|
||||
}
|
||||
@@ -62,7 +62,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');
|
||||
|
||||
@@ -444,17 +444,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);
|
||||
|
||||
@@ -956,19 +956,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',
|
||||
|
||||
@@ -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,7 +1,11 @@
|
||||
<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">
|
||||
@@ -33,9 +37,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,8 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
} else {
|
||||
OC_App::disable('files_trashbin');
|
||||
}
|
||||
|
||||
$this->assertTrue(\OC_FileProxy::$enabled);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -225,7 +225,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 +274,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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -72,6 +72,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 +124,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 +172,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 +266,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 +296,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 +313,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 +336,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 +384,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 +411,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 +419,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,7 +440,7 @@ class AmazonS3 extends \OC\Files\Storage\Common {
|
||||
try {
|
||||
$result = $this->connection->copyObject(array(
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $path2,
|
||||
'Key' => $this->cleanKey($path2),
|
||||
'CopySource' => $this->bucket . '/' . $path1
|
||||
));
|
||||
$this->testTimeout();
|
||||
@@ -535,7 +539,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);
|
||||
|
||||
@@ -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 . ')';
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.5.2
|
||||
0.5.3
|
||||
|
||||
@@ -89,21 +89,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 () {
|
||||
|
||||
@@ -163,7 +163,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,6 +60,9 @@ 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;
|
||||
}
|
||||
@@ -214,6 +217,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 +339,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 +411,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 +422,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 +580,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);
|
||||
|
||||
@@ -162,7 +162,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']);
|
||||
}
|
||||
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -19,10 +19,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 +42,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');
|
||||
@@ -133,11 +136,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 +160,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 +200,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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,6 +176,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 +238,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 = 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'] = $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,87 @@ 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');
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
||||
+198
-37
@@ -50,20 +50,29 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
if(!$this->enabled) {
|
||||
return false;
|
||||
}
|
||||
if($this->access->connection->isCached('inGroup'.$uid.':'.$gid)) {
|
||||
return $this->access->connection->getFromCache('inGroup'.$uid.':'.$gid);
|
||||
$cacheKey = 'inGroup'.$uid.':'.$gid;
|
||||
if($this->access->connection->isCached($cacheKey)) {
|
||||
return $this->access->connection->getFromCache($cacheKey);
|
||||
}
|
||||
$dn_user = $this->access->username2dn($uid);
|
||||
$dn_group = $this->access->groupname2dn($gid);
|
||||
|
||||
$userDN = $this->access->username2dn($uid);
|
||||
$groupDN = $this->access->groupname2dn($gid);
|
||||
// just in case
|
||||
if(!$dn_group || !$dn_user) {
|
||||
$this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, false);
|
||||
if(!$groupDN || !$userDN) {
|
||||
$this->access->connection->writeToCache($cacheKey, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
//check primary group first
|
||||
if($gid === $this->getUserPrimaryGroup($userDN)) {
|
||||
$this->access->connection->writeToCache($cacheKey, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
//usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
|
||||
$members = array_keys($this->_groupMembers($dn_group));
|
||||
$members = array_keys($this->_groupMembers($groupDN));
|
||||
if(!$members) {
|
||||
$this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, false);
|
||||
$this->access->connection->writeToCache($cacheKey, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -82,8 +91,8 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
$members = $dns;
|
||||
}
|
||||
|
||||
$isInGroup = in_array($dn_user, $members);
|
||||
$this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, $isInGroup);
|
||||
$isInGroup = in_array($userDN, $members);
|
||||
$this->access->connection->writeToCache($cacheKey, $isInGroup);
|
||||
|
||||
return $isInGroup;
|
||||
}
|
||||
@@ -91,6 +100,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
/**
|
||||
* @param string $dnGroup
|
||||
* @param array|null &$seen
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
private function _groupMembers($dnGroup, &$seen = null) {
|
||||
if ($seen === null) {
|
||||
@@ -125,6 +135,125 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
return $allMembers;
|
||||
}
|
||||
|
||||
/**
|
||||
* translates a primary group ID into an ownCloud internal name
|
||||
* @param string $gid as given by primaryGroupID on AD
|
||||
* @param string $dn a DN that belongs to the same domain as the group
|
||||
* @return string|bool
|
||||
*/
|
||||
public function primaryGroupID2Name($gid, $dn) {
|
||||
$cacheKey = 'primaryGroupIDtoName';
|
||||
if($this->access->connection->isCached($cacheKey)) {
|
||||
$groupNames = $this->access->connection->getFromCache($cacheKey);
|
||||
if(isset($groupNames[$gid])) {
|
||||
return $groupNames[$gid];
|
||||
}
|
||||
}
|
||||
|
||||
$domainObjectSid = $this->access->getSID($dn);
|
||||
if($domainObjectSid === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//we need to get the DN from LDAP
|
||||
$filter = $this->access->combineFilterWithAnd(array(
|
||||
$this->access->connection->ldapGroupFilter,
|
||||
'objectsid=' . $domainObjectSid . '-' . $gid
|
||||
));
|
||||
$result = $this->access->searchGroups($filter, array('dn'), 1);
|
||||
if(empty($result)) {
|
||||
return false;
|
||||
}
|
||||
$dn = $result[0];
|
||||
|
||||
//and now the group name
|
||||
//NOTE once we have separate ownCloud group IDs and group names we can
|
||||
//directly read the display name attribute instead of the DN
|
||||
$name = $this->access->dn2groupname($dn);
|
||||
|
||||
$this->access->connection->writeToCache($cacheKey, $name);
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the entry's primary group ID
|
||||
* @param string $dn
|
||||
* @param string $attribute
|
||||
* @return string|bool
|
||||
*/
|
||||
private function getEntryGroupID($dn, $attribute) {
|
||||
$value = $this->access->readAttribute($dn, $attribute);
|
||||
if(is_array($value) && !empty($value)) {
|
||||
return $value[0];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the group's primary ID
|
||||
* @param string $dn
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getGroupPrimaryGroupID($dn) {
|
||||
return $this->getEntryGroupID($dn, 'primaryGroupToken');
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the user's primary group ID
|
||||
* @param string $dn
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getUserPrimaryGroupIDs($dn) {
|
||||
return $this->getEntryGroupID($dn, 'primaryGroupID');
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a list of users that have the given group as primary group
|
||||
*
|
||||
* @param string $groupDN
|
||||
* @param $limit
|
||||
* @param int $offset
|
||||
* @return string[]
|
||||
*/
|
||||
public function getUsersInPrimaryGroup($groupDN, $limit = -1, $offset = 0) {
|
||||
$groupID = $this->getGroupPrimaryGroupID($groupDN);
|
||||
if($groupID === false) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$filter = $this->access->combineFilterWithAnd(array(
|
||||
$this->access->connection->ldapUserFilter,
|
||||
'primaryGroupID=' . $groupID
|
||||
));
|
||||
|
||||
$users = $this->access->fetchListOfUsers(
|
||||
$filter,
|
||||
array($this->access->connection->ldapUserDisplayName, 'dn'),
|
||||
$limit,
|
||||
$offset
|
||||
);
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the primary group of a user
|
||||
* @param string $dn
|
||||
* @return string
|
||||
*/
|
||||
public function getUserPrimaryGroup($dn) {
|
||||
$groupID = $this->getUserPrimaryGroupIDs($dn);
|
||||
if($groupID !== false) {
|
||||
$groupName = $this->primaryGroupID2Name($groupID, $dn);
|
||||
if($groupName !== false) {
|
||||
return $groupName;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all groups a user belongs to
|
||||
* @param string $uid Name of the user
|
||||
@@ -161,7 +290,14 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
}
|
||||
|
||||
$groups = array_values($this->getGroupsByMember($uid));
|
||||
$groups = array_unique($this->access->ownCloudGroupNames($groups), SORT_LOCALE_STRING);
|
||||
$groups = $this->access->ownCloudGroupNames($groups);
|
||||
|
||||
$primaryGroup = $this->getUserPrimaryGroup($userDN);
|
||||
if($primaryGroup !== false) {
|
||||
$groups[] = $primaryGroup;
|
||||
}
|
||||
|
||||
$groups = array_unique($groups, SORT_LOCALE_STRING);
|
||||
$this->access->connection->writeToCache($cacheKey, $groups);
|
||||
|
||||
return $groups;
|
||||
@@ -170,6 +306,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
/**
|
||||
* @param string $dn
|
||||
* @param array|null &$seen
|
||||
* @return array
|
||||
*/
|
||||
private function getGroupsByMember($dn, &$seen = null) {
|
||||
if ($seen === null) {
|
||||
@@ -205,6 +342,11 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
|
||||
/**
|
||||
* get a list of all users in a group
|
||||
*
|
||||
* @param string $gid
|
||||
* @param string $search
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
* @return array with user ids
|
||||
*/
|
||||
public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
|
||||
@@ -214,9 +356,9 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
if(!$this->groupExists($gid)) {
|
||||
return array();
|
||||
}
|
||||
$cachekey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
|
||||
$cacheKey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
|
||||
// check for cache of the exact query
|
||||
$groupUsers = $this->access->connection->getFromCache($cachekey);
|
||||
$groupUsers = $this->access->connection->getFromCache($cacheKey);
|
||||
if(!is_null($groupUsers)) {
|
||||
return $groupUsers;
|
||||
}
|
||||
@@ -225,7 +367,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
$groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search);
|
||||
if(!is_null($groupUsers)) {
|
||||
$groupUsers = array_slice($groupUsers, $offset, $limit);
|
||||
$this->access->connection->writeToCache($cachekey, $groupUsers);
|
||||
$this->access->connection->writeToCache($cacheKey, $groupUsers);
|
||||
return $groupUsers;
|
||||
}
|
||||
|
||||
@@ -235,14 +377,14 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
$groupDN = $this->access->groupname2dn($gid);
|
||||
if(!$groupDN) {
|
||||
// group couldn't be found, return empty resultset
|
||||
$this->access->connection->writeToCache($cachekey, array());
|
||||
$this->access->connection->writeToCache($cacheKey, array());
|
||||
return array();
|
||||
}
|
||||
|
||||
$members = array_keys($this->_groupMembers($groupDN));
|
||||
if(!$members) {
|
||||
//in case users could not be retrieved, return empty resultset
|
||||
$this->access->connection->writeToCache($cachekey, array());
|
||||
//in case users could not be retrieved, return empty result set
|
||||
$this->access->connection->writeToCache($cacheKey, array());
|
||||
return array();
|
||||
}
|
||||
|
||||
@@ -250,7 +392,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
$isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid');
|
||||
foreach($members as $member) {
|
||||
if($isMemberUid) {
|
||||
//we got uids, need to get their DNs to 'tranlsate' them to usernames
|
||||
//we got uids, need to get their DNs to 'translate' them to user names
|
||||
$filter = $this->access->combineFilterWithAnd(array(
|
||||
\OCP\Util::mb_str_replace('%uid', $member,
|
||||
$this->access->connection->ldapLoginFilter, 'UTF-8'),
|
||||
@@ -276,10 +418,16 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
natsort($groupUsers);
|
||||
$this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers);
|
||||
$groupUsers = array_slice($groupUsers, $offset, $limit);
|
||||
$this->access->connection->writeToCache($cachekey, $groupUsers);
|
||||
|
||||
//and get users that have the group as primary
|
||||
$primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $limit, $offset);
|
||||
$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers));
|
||||
|
||||
$this->access->connection->writeToCache($cacheKey, $groupUsers);
|
||||
|
||||
return $groupUsers;
|
||||
}
|
||||
@@ -291,32 +439,32 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
* @return int|bool
|
||||
*/
|
||||
public function countUsersInGroup($gid, $search = '') {
|
||||
$cachekey = 'countUsersInGroup-'.$gid.'-'.$search;
|
||||
$cacheKey = 'countUsersInGroup-'.$gid.'-'.$search;
|
||||
if(!$this->enabled || !$this->groupExists($gid)) {
|
||||
return false;
|
||||
}
|
||||
$groupUsers = $this->access->connection->getFromCache($cachekey);
|
||||
$groupUsers = $this->access->connection->getFromCache($cacheKey);
|
||||
if(!is_null($groupUsers)) {
|
||||
return $groupUsers;
|
||||
}
|
||||
|
||||
$groupDN = $this->access->groupname2dn($gid);
|
||||
if(!$groupDN) {
|
||||
// group couldn't be found, return empty resultset
|
||||
$this->access->connection->writeToCache($cachekey, false);
|
||||
// group couldn't be found, return empty result set
|
||||
$this->access->connection->writeToCache($cacheKey, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
$members = array_keys($this->_groupMembers($groupDN));
|
||||
if(!$members) {
|
||||
//in case users could not be retrieved, return empty resultset
|
||||
$this->access->connection->writeToCache($cachekey, false);
|
||||
//in case users could not be retrieved, return empty result set
|
||||
$this->access->connection->writeToCache($cacheKey, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(empty($search)) {
|
||||
$groupUsers = count($members);
|
||||
$this->access->connection->writeToCache($cachekey, $groupUsers);
|
||||
$this->access->connection->writeToCache($cacheKey, $groupUsers);
|
||||
return $groupUsers;
|
||||
}
|
||||
$isMemberUid =
|
||||
@@ -334,7 +482,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
$groupUsers = array();
|
||||
foreach($members as $member) {
|
||||
if($isMemberUid) {
|
||||
//we got uids, need to get their DNs to 'tranlsate' them to usernames
|
||||
//we got uids, need to get their DNs to 'translate' them to user names
|
||||
$filter = $this->access->combineFilterWithAnd(array(
|
||||
\OCP\Util::mb_str_replace('%uid', $member,
|
||||
$this->access->connection->ldapLoginFilter, 'UTF-8'),
|
||||
@@ -359,11 +507,19 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
}
|
||||
}
|
||||
|
||||
//and get users that have the group as primary
|
||||
$primaryUsers = $this->getUsersInPrimaryGroup($groupDN);
|
||||
$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers));
|
||||
|
||||
return count($groupUsers);
|
||||
}
|
||||
|
||||
/**
|
||||
* get a list of all groups
|
||||
*
|
||||
* @param string $search
|
||||
* @param $limit
|
||||
* @param int $offset
|
||||
* @return array with group names
|
||||
*
|
||||
* Returns a list with all groups (used by getGroups)
|
||||
@@ -372,11 +528,11 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
if(!$this->enabled) {
|
||||
return array();
|
||||
}
|
||||
$cachekey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
|
||||
$cacheKey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
|
||||
|
||||
//Check cache before driving unnecessary searches
|
||||
\OCP\Util::writeLog('user_ldap', 'getGroups '.$cachekey, \OCP\Util::DEBUG);
|
||||
$ldap_groups = $this->access->connection->getFromCache($cachekey);
|
||||
\OCP\Util::writeLog('user_ldap', 'getGroups '.$cacheKey, \OCP\Util::DEBUG);
|
||||
$ldap_groups = $this->access->connection->getFromCache($cacheKey);
|
||||
if(!is_null($ldap_groups)) {
|
||||
return $ldap_groups;
|
||||
}
|
||||
@@ -397,26 +553,30 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
$offset);
|
||||
$ldap_groups = $this->access->ownCloudGroupNames($ldap_groups);
|
||||
|
||||
$this->access->connection->writeToCache($cachekey, $ldap_groups);
|
||||
$this->access->connection->writeToCache($cacheKey, $ldap_groups);
|
||||
return $ldap_groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* get a list of all groups using a paged search
|
||||
*
|
||||
* @param string $search
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
* @return array with group names
|
||||
*
|
||||
* Returns a list with all groups
|
||||
* Uses a paged search if available to override a
|
||||
* server side search limit.
|
||||
* (active directory has a limit of 1000 by default)
|
||||
* Uses a paged search if available to override a
|
||||
* server side search limit.
|
||||
* (active directory has a limit of 1000 by default)
|
||||
*/
|
||||
public function getGroups($search = '', $limit = -1, $offset = 0) {
|
||||
if(!$this->enabled) {
|
||||
return array();
|
||||
}
|
||||
$pagingsize = $this->access->connection->ldapPagingSize;
|
||||
$pagingSize = $this->access->connection->ldapPagingSize;
|
||||
if ((! $this->access->connection->hasPagedResultSupport)
|
||||
|| empty($pagingsize)) {
|
||||
|| empty($pagingSize)) {
|
||||
return $this->getGroupsChunk($search, $limit, $offset);
|
||||
}
|
||||
$maxGroups = 100000; // limit max results (just for safety reasons)
|
||||
@@ -428,7 +588,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
$chunkOffset = $offset;
|
||||
$allGroups = array();
|
||||
while ($chunkOffset < $overallLimit) {
|
||||
$chunkLimit = min($pagingsize, $overallLimit - $chunkOffset);
|
||||
$chunkLimit = min($pagingSize, $overallLimit - $chunkOffset);
|
||||
$ldapGroups = $this->getGroupsChunk($search, $chunkLimit, $chunkOffset);
|
||||
$nread = count($ldapGroups);
|
||||
\OCP\Util::writeLog('user_ldap', 'getGroups('.$search.'): read '.$nread.' at offset '.$chunkOffset.' (limit: '.$chunkLimit.')', \OCP\Util::DEBUG);
|
||||
@@ -445,6 +605,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
|
||||
/**
|
||||
* @param string $group
|
||||
* @return bool
|
||||
*/
|
||||
public function groupMatchesFilter($group) {
|
||||
return (strripos($group, $this->groupSearch) !== false);
|
||||
|
||||
@@ -28,6 +28,9 @@ namespace OCA\user_ldap\lib;
|
||||
* @package OCA\user_ldap\lib
|
||||
*/
|
||||
class Access extends LDAPUtility implements user\IUserTools {
|
||||
/**
|
||||
* @var \OCA\user_ldap\lib\Connection
|
||||
*/
|
||||
public $connection;
|
||||
public $userManager;
|
||||
//never ever check this var directly, always use getPagedSearchResultState
|
||||
@@ -61,8 +64,8 @@ class Access extends LDAPUtility implements user\IUserTools {
|
||||
|
||||
/**
|
||||
* reads a given attribute for an LDAP record identified by a DN
|
||||
* @param $dn the record in question
|
||||
* @param $attr the attribute that shall be retrieved
|
||||
* @param string $dn the record in question
|
||||
* @param string $attr the attribute that shall be retrieved
|
||||
* if empty, just check the record's existence
|
||||
* @param string $filter
|
||||
* @return array|false an array of values on success or an empty
|
||||
@@ -180,6 +183,33 @@ class Access extends LDAPUtility implements user\IUserTools {
|
||||
return $dn;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a DN-string that is cleaned from not domain parts, e.g.
|
||||
* cn=foo,cn=bar,dc=foobar,dc=server,dc=org
|
||||
* becomes dc=foobar,dc=server,dc=org
|
||||
* @param string $dn
|
||||
* @return string
|
||||
*/
|
||||
public function getDomainDNFromDN($dn) {
|
||||
$allParts = $this->ldap->explodeDN($dn, 0);
|
||||
if($allParts === false) {
|
||||
//not a valid DN
|
||||
return '';
|
||||
}
|
||||
$domainParts = array();
|
||||
$dcFound = false;
|
||||
foreach($allParts as $part) {
|
||||
if(!$dcFound && strpos($part, 'dc=') === 0) {
|
||||
$dcFound = true;
|
||||
}
|
||||
if($dcFound) {
|
||||
$domainParts[] = $part;
|
||||
}
|
||||
}
|
||||
$domainDN = implode(',', $domainParts);
|
||||
return $domainDN;
|
||||
}
|
||||
|
||||
/**
|
||||
* gives back the database table for the query
|
||||
* @param bool $isUser
|
||||
@@ -534,7 +564,7 @@ class Access extends LDAPUtility implements user\IUserTools {
|
||||
if(!\OC_Group::groupExists($altName)) {
|
||||
return $altName;
|
||||
}
|
||||
$altName = $name . '_' . $lastNo + $attempts;
|
||||
$altName = $name . '_' . ($lastNo + $attempts);
|
||||
$attempts++;
|
||||
}
|
||||
return false;
|
||||
@@ -581,6 +611,7 @@ class Access extends LDAPUtility implements user\IUserTools {
|
||||
|
||||
/**
|
||||
* @param boolean $isUsers
|
||||
* @return array
|
||||
*/
|
||||
private function mappedComponents($isUsers) {
|
||||
$table = $this->getMapTable($isUsers);
|
||||
@@ -834,7 +865,7 @@ class Access extends LDAPUtility implements user\IUserTools {
|
||||
private function count($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
|
||||
\OCP\Util::writeLog('user_ldap', 'Count filter: '.print_r($filter, true), \OCP\Util::DEBUG);
|
||||
|
||||
if(is_null($limit)) {
|
||||
if(is_null($limit) || $limit <= 0) {
|
||||
$limit = intval($this->connection->ldapPagingSize);
|
||||
}
|
||||
|
||||
@@ -894,6 +925,10 @@ class Access extends LDAPUtility implements user\IUserTools {
|
||||
* @return array with the search result
|
||||
*/
|
||||
private function search($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
|
||||
if($limit <= 0) {
|
||||
//otherwise search will fail
|
||||
$limit = null;
|
||||
}
|
||||
$search = $this->executeSearch($filter, $base, $attr, $limit, $offset);
|
||||
if($search === false) {
|
||||
return array();
|
||||
@@ -908,7 +943,7 @@ class Access extends LDAPUtility implements user\IUserTools {
|
||||
$this->processPagedSearchStatus($sr, $filter, $base, 1, $limit,
|
||||
$offset, $pagedSearchOK,
|
||||
$skipHandling);
|
||||
return;
|
||||
return array();
|
||||
}
|
||||
|
||||
// Do the server-side sorting
|
||||
@@ -1232,6 +1267,61 @@ class Access extends LDAPUtility implements user\IUserTools {
|
||||
return strtoupper($hex_guid_to_guid_str);
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a SID of the domain of the given dn
|
||||
* @param string $dn
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getSID($dn) {
|
||||
$domainDN = $this->getDomainDNFromDN($dn);
|
||||
$cacheKey = 'getSID-'.$domainDN;
|
||||
if($this->connection->isCached($cacheKey)) {
|
||||
return $this->connection->getFromCache($cacheKey);
|
||||
}
|
||||
|
||||
$objectSid = $this->readAttribute($domainDN, 'objectsid');
|
||||
if(!is_array($objectSid) || empty($objectSid)) {
|
||||
$this->connection->writeToCache($cacheKey, false);
|
||||
return false;
|
||||
}
|
||||
$domainObjectSid = $this->convertSID2Str($objectSid[0]);
|
||||
$this->connection->writeToCache($cacheKey, $domainObjectSid);
|
||||
|
||||
return $domainObjectSid;
|
||||
}
|
||||
|
||||
/**
|
||||
* converts a binary SID into a string representation
|
||||
* @param string $sid
|
||||
* @return string
|
||||
* @link http://blogs.freebsdish.org/tmclaugh/2010/07/21/finding-a-users-primary-group-in-ad/#comment-2855
|
||||
*/
|
||||
public function convertSID2Str($sid) {
|
||||
try {
|
||||
if(!function_exists('bcadd')) {
|
||||
\OCP\Util::writeLog('user_ldap',
|
||||
'You need to install bcmath module for PHP to have support ' .
|
||||
'for AD primary groups', \OCP\Util::WARN);
|
||||
throw new \Exception('missing bcmath module');
|
||||
}
|
||||
$srl = ord($sid[0]);
|
||||
$numberSubID = ord($sid[1]);
|
||||
$x = substr($sid, 2, 6);
|
||||
$h = unpack('N', "\x0\x0" . substr($x,0,2));
|
||||
$l = unpack('N', substr($x,2,6));
|
||||
$iav = bcadd(bcmul($h[1], bcpow(2,32)), $l[1]);
|
||||
$subIDs = array();
|
||||
for ($i=0; $i<$numberSubID; $i++) {
|
||||
$subID = unpack('V', substr($sid, 8+4*$i, 4));
|
||||
$subIDs[] = $subID[1];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return sprintf('S-%d-%d-%s', $srl, $iav, implode('-', $subIDs));
|
||||
}
|
||||
|
||||
/**
|
||||
* converts a stored DN so it can be used as base parameter for LDAP queries, internally we store them for usage in LDAP filters
|
||||
* @param string $dn the DN
|
||||
|
||||
@@ -23,6 +23,13 @@
|
||||
|
||||
namespace OCA\user_ldap\lib;
|
||||
|
||||
//magic properties (incomplete)
|
||||
/**
|
||||
* responsible for LDAP connections in context with the provided configuration
|
||||
* @property string ldapUserFilter
|
||||
* @property string ldapUserDisplayName
|
||||
* @property boolean hasPagedResultSupport
|
||||
*/
|
||||
class Connection extends LDAPUtility {
|
||||
private $ldapConnectionRes = null;
|
||||
private $configPrefix;
|
||||
|
||||
@@ -89,6 +89,15 @@ interface ILDAPWrapper {
|
||||
*/
|
||||
public function error($link);
|
||||
|
||||
/**
|
||||
* Splits DN into its component parts
|
||||
* @param string $dn
|
||||
* @param int @withAttrib
|
||||
* @return array|false
|
||||
* @link http://www.php.net/manual/en/function.ldap-explode-dn.php
|
||||
*/
|
||||
public function explodeDN($dn, $withAttrib);
|
||||
|
||||
/**
|
||||
* Return first result id
|
||||
* @param resource $link LDAP link resource
|
||||
|
||||
@@ -98,6 +98,17 @@ class LDAP implements ILDAPWrapper {
|
||||
return $this->invokeLDAPMethod('error', $link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits DN into its component parts
|
||||
* @param string $dn
|
||||
* @param int @withAttrib
|
||||
* @return array|false
|
||||
* @link http://www.php.net/manual/en/function.ldap-explode-dn.php
|
||||
*/
|
||||
public function explodeDN($dn, $withAttrib) {
|
||||
return $this->invokeLDAPMethod('ldap_explode_dn', $dn, $withAttrib);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LDAP $link
|
||||
* @param LDAP $result
|
||||
|
||||
@@ -143,7 +143,8 @@ class Manager {
|
||||
return $this->users['byUid'][$id];
|
||||
}
|
||||
|
||||
if(strpos(mb_strtolower($id, 'UTF-8'), 'dc=') === false) {
|
||||
if(strpos(mb_strtolower($id, 'UTF-8'), 'dc=') === false
|
||||
&& strpos(mb_strtolower($id, 'UTF-8'), 'uid=') === false ) {
|
||||
//most likely a uid
|
||||
$dn = $this->access->username2dn($id);
|
||||
if($dn !== false) {
|
||||
|
||||
@@ -77,4 +77,83 @@ class Test_Access extends \PHPUnit_Framework_TestCase {
|
||||
$expected = 'foo\\\\*bar';
|
||||
$this->assertTrue($expected === $access->escapeFilterPart($input));
|
||||
}
|
||||
}
|
||||
|
||||
public function testConvertSID2StrSuccess() {
|
||||
list($lw, $con, $um) = $this->getConnecterAndLdapMock();
|
||||
$access = new Access($con, $lw, $um);
|
||||
|
||||
if(!function_exists('\bcadd')) {
|
||||
$this->markTestSkipped('bcmath not available');
|
||||
}
|
||||
|
||||
$sidBinary = file_get_contents(__DIR__ . '/data/sid.dat');
|
||||
$sidExpected = 'S-1-5-21-249921958-728525901-1594176202';
|
||||
|
||||
$this->assertSame($sidExpected, $access->convertSID2Str($sidBinary));
|
||||
}
|
||||
|
||||
public function testConvertSID2StrInputError() {
|
||||
list($lw, $con, $um) = $this->getConnecterAndLdapMock();
|
||||
$access = new Access($con, $lw, $um);
|
||||
|
||||
if(!function_exists('\bcadd')) {
|
||||
$this->markTestSkipped('bcmath not available');
|
||||
}
|
||||
|
||||
$sidIllegal = 'foobar';
|
||||
$sidExpected = '';
|
||||
|
||||
$this->assertSame($sidExpected, $access->convertSID2Str($sidIllegal));
|
||||
}
|
||||
|
||||
public function testConvertSID2StrNoBCMath() {
|
||||
if(function_exists('\bcadd')) {
|
||||
$removed = false;
|
||||
if(function_exists('runkit_function_remove')) {
|
||||
$removed = !runkit_function_remove('\bcadd');
|
||||
}
|
||||
if(!$removed) {
|
||||
$this->markTestSkipped('bcadd could not be removed for ' .
|
||||
'testing without bcmath');
|
||||
}
|
||||
}
|
||||
|
||||
list($lw, $con, $um) = $this->getConnecterAndLdapMock();
|
||||
$access = new Access($con, $lw, $um);
|
||||
|
||||
$sidBinary = file_get_contents(__DIR__ . '/data/sid.dat');
|
||||
$sidExpected = '';
|
||||
|
||||
$this->assertSame($sidExpected, $access->convertSID2Str($sidBinary));
|
||||
}
|
||||
|
||||
public function testGetDomainDNFromDNSuccess() {
|
||||
list($lw, $con, $um) = $this->getConnecterAndLdapMock();
|
||||
$access = new Access($con, $lw, $um);
|
||||
|
||||
$inputDN = 'uid=zaphod,cn=foobar,dc=my,dc=server,dc=com';
|
||||
$domainDN = 'dc=my,dc=server,dc=com';
|
||||
|
||||
$lw->expects($this->once())
|
||||
->method('explodeDN')
|
||||
->with($inputDN, 0)
|
||||
->will($this->returnValue(explode(',', $inputDN)));
|
||||
|
||||
$this->assertSame($domainDN, $access->getDomainDNFromDN($inputDN));
|
||||
}
|
||||
|
||||
public function testGetDomainDNFromDNError() {
|
||||
list($lw, $con, $um) = $this->getConnecterAndLdapMock();
|
||||
$access = new Access($con, $lw, $um);
|
||||
|
||||
$inputDN = 'foobar';
|
||||
$expected = '';
|
||||
|
||||
$lw->expects($this->once())
|
||||
->method('explodeDN')
|
||||
->with($inputDN, 0)
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->assertSame($expected, $access->getDomainDNFromDN($inputDN));
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -95,6 +95,10 @@ class Test_Group_Ldap extends \PHPUnit_Framework_TestCase {
|
||||
->method('groupname2dn')
|
||||
->will($this->returnValue('cn=group,dc=foo,dc=bar'));
|
||||
|
||||
$access->expects($this->any())
|
||||
->method('fetchListOfUsers')
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$access->expects($this->any())
|
||||
->method('readAttribute')
|
||||
->will($this->returnCallback(function($name) {
|
||||
@@ -111,7 +115,9 @@ class Test_Group_Ldap extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
$access->expects($this->any())
|
||||
->method('dn2username')
|
||||
->will($this->returnValue('foobar'));
|
||||
->will($this->returnCallback(function() {
|
||||
return 'foobar' . \OCP\Util::generateRandomBytes(7);
|
||||
}));
|
||||
|
||||
$groupBackend = new GroupLDAP($access);
|
||||
$users = $groupBackend->countUsersInGroup('group', '3');
|
||||
@@ -119,4 +125,148 @@ class Test_Group_Ldap extends \PHPUnit_Framework_TestCase {
|
||||
$this->assertSame(2, $users);
|
||||
}
|
||||
|
||||
public function testPrimaryGroupID2NameSuccess() {
|
||||
$access = $this->getAccessMock();
|
||||
$this->enableGroups($access);
|
||||
|
||||
$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('getSID')
|
||||
->with($userDN)
|
||||
->will($this->returnValue('S-1-5-21-249921958-728525901-1594176202'));
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('searchGroups')
|
||||
->will($this->returnValue(array('cn=foo,dc=barfoo,dc=bar')));
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('dn2groupname')
|
||||
->with('cn=foo,dc=barfoo,dc=bar')
|
||||
->will($this->returnValue('MyGroup'));
|
||||
|
||||
$groupBackend = new GroupLDAP($access);
|
||||
|
||||
$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
|
||||
|
||||
$this->assertSame('MyGroup', $group);
|
||||
}
|
||||
|
||||
public function testPrimaryGroupID2NameNoSID() {
|
||||
$access = $this->getAccessMock();
|
||||
$this->enableGroups($access);
|
||||
|
||||
$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('getSID')
|
||||
->with($userDN)
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$access->expects($this->never())
|
||||
->method('searchGroups');
|
||||
|
||||
$access->expects($this->never())
|
||||
->method('dn2groupname');
|
||||
|
||||
$groupBackend = new GroupLDAP($access);
|
||||
|
||||
$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
|
||||
|
||||
$this->assertSame(false, $group);
|
||||
}
|
||||
|
||||
public function testPrimaryGroupID2NameNoGroup() {
|
||||
$access = $this->getAccessMock();
|
||||
$this->enableGroups($access);
|
||||
|
||||
$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('getSID')
|
||||
->with($userDN)
|
||||
->will($this->returnValue('S-1-5-21-249921958-728525901-1594176202'));
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('searchGroups')
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$access->expects($this->never())
|
||||
->method('dn2groupname');
|
||||
|
||||
$groupBackend = new GroupLDAP($access);
|
||||
|
||||
$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
|
||||
|
||||
$this->assertSame(false, $group);
|
||||
}
|
||||
|
||||
public function testPrimaryGroupID2NameNoName() {
|
||||
$access = $this->getAccessMock();
|
||||
$this->enableGroups($access);
|
||||
|
||||
$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('getSID')
|
||||
->with($userDN)
|
||||
->will($this->returnValue('S-1-5-21-249921958-728525901-1594176202'));
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('searchGroups')
|
||||
->will($this->returnValue(array('cn=foo,dc=barfoo,dc=bar')));
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('dn2groupname')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$groupBackend = new GroupLDAP($access);
|
||||
|
||||
$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
|
||||
|
||||
$this->assertSame(false, $group);
|
||||
}
|
||||
|
||||
public function testGetEntryGroupIDValue() {
|
||||
//tests getEntryGroupID via getGroupPrimaryGroupID
|
||||
//which is basically identical to getUserPrimaryGroupIDs
|
||||
$access = $this->getAccessMock();
|
||||
$this->enableGroups($access);
|
||||
|
||||
$dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar';
|
||||
$attr = 'primaryGroupToken';
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('readAttribute')
|
||||
->with($dn, $attr)
|
||||
->will($this->returnValue(array('3117')));
|
||||
|
||||
$groupBackend = new GroupLDAP($access);
|
||||
|
||||
$gid = $groupBackend->getGroupPrimaryGroupID($dn);
|
||||
|
||||
$this->assertSame('3117', $gid);
|
||||
}
|
||||
|
||||
public function testGetEntryGroupIDNoValue() {
|
||||
//tests getEntryGroupID via getGroupPrimaryGroupID
|
||||
//which is basically identical to getUserPrimaryGroupIDs
|
||||
$access = $this->getAccessMock();
|
||||
$this->enableGroups($access);
|
||||
|
||||
$dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar';
|
||||
$attr = 'primaryGroupToken';
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('readAttribute')
|
||||
->with($dn, $attr)
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$groupBackend = new GroupLDAP($access);
|
||||
|
||||
$gid = $groupBackend->getGroupPrimaryGroupID($dn);
|
||||
|
||||
$this->assertSame(false, $gid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Arthur Schiwon
|
||||
* @copyright 2014 Arthur Schiwon blizzz@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\user_ldap\tests;
|
||||
|
||||
use OCA\user_ldap\lib\user\Manager;
|
||||
|
||||
class Test_User_Manager extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
private function getTestInstances() {
|
||||
$access = $this->getMock('\OCA\user_ldap\lib\user\IUserTools');
|
||||
$config = $this->getMock('\OCP\IConfig');
|
||||
$filesys = $this->getMock('\OCA\user_ldap\lib\FilesystemHelper');
|
||||
$log = $this->getMock('\OCA\user_ldap\lib\LogWrapper');
|
||||
$avaMgr = $this->getMock('\OCP\IAvatarManager');
|
||||
$image = $this->getMock('\OCP\Image');
|
||||
|
||||
return array($access, $config, $filesys, $image, $log, $avaMgr);
|
||||
}
|
||||
|
||||
public function testGetByDNExisting() {
|
||||
list($access, $config, $filesys, $image, $log, $avaMgr) =
|
||||
$this->getTestInstances();
|
||||
|
||||
$inputDN = 'cn=foo,dc=foobar,dc=bar';
|
||||
$uid = '563418fc-423b-1033-8d1c-ad5f418ee02e';
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('dn2username')
|
||||
->with($this->equalTo($inputDN))
|
||||
->will($this->returnValue($uid));
|
||||
|
||||
$access->expects($this->never())
|
||||
->method('username2dn');
|
||||
|
||||
$manager = new Manager($config, $filesys, $log, $avaMgr, $image);
|
||||
$manager->setLdapAccess($access);
|
||||
$user = $manager->get($inputDN);
|
||||
|
||||
$this->assertInstanceOf('\OCA\user_ldap\lib\user\User', $user);
|
||||
}
|
||||
|
||||
public function testGetByEDirectoryDN() {
|
||||
list($access, $config, $filesys, $image, $log, $avaMgr) =
|
||||
$this->getTestInstances();
|
||||
|
||||
$inputDN = 'uid=foo,o=foobar,c=bar';
|
||||
$uid = '563418fc-423b-1033-8d1c-ad5f418ee02e';
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('dn2username')
|
||||
->with($this->equalTo($inputDN))
|
||||
->will($this->returnValue($uid));
|
||||
|
||||
$access->expects($this->never())
|
||||
->method('username2dn');
|
||||
|
||||
$manager = new Manager($config, $filesys, $log, $avaMgr, $image);
|
||||
$manager->setLdapAccess($access);
|
||||
$user = $manager->get($inputDN);
|
||||
|
||||
$this->assertInstanceOf('\OCA\user_ldap\lib\user\User', $user);
|
||||
}
|
||||
|
||||
public function testGetByDNNotExisting() {
|
||||
list($access, $config, $filesys, $image, $log, $avaMgr) =
|
||||
$this->getTestInstances();
|
||||
|
||||
$inputDN = 'cn=gone,dc=foobar,dc=bar';
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('dn2username')
|
||||
->with($this->equalTo($inputDN))
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('username2dn')
|
||||
->with($this->equalTo($inputDN))
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$manager = new Manager($config, $filesys, $log, $avaMgr, $image);
|
||||
$manager->setLdapAccess($access);
|
||||
$user = $manager->get($inputDN);
|
||||
|
||||
$this->assertNull($user);
|
||||
}
|
||||
|
||||
public function testGetByUidExisting() {
|
||||
list($access, $config, $filesys, $image, $log, $avaMgr) =
|
||||
$this->getTestInstances();
|
||||
|
||||
$dn = 'cn=foo,dc=foobar,dc=bar';
|
||||
$uid = '563418fc-423b-1033-8d1c-ad5f418ee02e';
|
||||
|
||||
$access->expects($this->never())
|
||||
->method('dn2username');
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('username2dn')
|
||||
->with($this->equalTo($uid))
|
||||
->will($this->returnValue($dn));
|
||||
|
||||
$manager = new Manager($config, $filesys, $log, $avaMgr, $image);
|
||||
$manager->setLdapAccess($access);
|
||||
$user = $manager->get($uid);
|
||||
|
||||
$this->assertInstanceOf('\OCA\user_ldap\lib\user\User', $user);
|
||||
}
|
||||
|
||||
public function testGetByUidNotExisting() {
|
||||
list($access, $config, $filesys, $image, $log, $avaMgr) =
|
||||
$this->getTestInstances();
|
||||
|
||||
$dn = 'cn=foo,dc=foobar,dc=bar';
|
||||
$uid = 'gone';
|
||||
|
||||
$access->expects($this->never())
|
||||
->method('dn2username');
|
||||
|
||||
$access->expects($this->exactly(2))
|
||||
->method('username2dn')
|
||||
->with($this->equalTo($uid))
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$manager = new Manager($config, $filesys, $log, $avaMgr, $image);
|
||||
$manager->setLdapAccess($access);
|
||||
$user = $manager->get($uid);
|
||||
|
||||
$this->assertNull($user);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -73,7 +73,6 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
|
||||
}
|
||||
|
||||
$user->markLogin();
|
||||
$user->update();
|
||||
|
||||
return $user->getUsername();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<name>WebDAV user backend</name>
|
||||
<description>Authenticate users by a WebDAV call. You can use any WebDAV server, ownCloud server or other webserver to authenticate. It should return http 200 for right credentials and http 401 for wrong ones.
|
||||
|
||||
This app is not compatible with the LDAP user and group backend.</description>
|
||||
Attention: This app is not compatible with the LDAP user and group backend. This app is not the webdav interface of ownCloud, if you don't understand what it does then do not enable it.</description>
|
||||
<licence>AGPL</licence>
|
||||
<author>Frank Karlitschek</author>
|
||||
<requiremin>4.93</requiremin>
|
||||
@@ -12,4 +12,5 @@
|
||||
<types>
|
||||
<authentication/>
|
||||
</types>
|
||||
<ocsid>166062</ocsid>
|
||||
</info>
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.1.0.0
|
||||
1.1.0.1
|
||||
|
||||
@@ -41,16 +41,16 @@ $CONFIG = array(
|
||||
/* Blacklist a specific file and disallow the upload of files with this name - WARNING: USE THIS ONLY IF YOU KNOW WHAT YOU ARE DOING. */
|
||||
"blacklisted_files" => array('.htaccess'),
|
||||
|
||||
/* The automatic hostname detection of ownCloud can fail in certain reverse proxy situations. This option allows to manually override the automatic detection. You can also add a port. For example "www.example.com:88" */
|
||||
/* The automatic hostname detection of ownCloud can fail in certain reverse proxy and CLI/cron situations. This option allows to manually override the automatic detection. You can also add a port. For example "www.example.com:88" */
|
||||
"overwritehost" => "",
|
||||
|
||||
/* The automatic protocol detection of ownCloud can fail in certain reverse proxy situations. This option allows to manually override the protocol detection. For example "https" */
|
||||
/* The automatic protocol detection of ownCloud can fail in certain reverse proxy and CLI/cron situations. This option allows to manually override the protocol detection. For example "https" */
|
||||
"overwriteprotocol" => "",
|
||||
|
||||
/* The automatic webroot detection of ownCloud can fail in certain reverse proxy situations. This option allows to manually override the automatic detection. For example "/domain.tld/ownCloud". The value "/" can be used to remove the root. */
|
||||
/* The automatic webroot detection of ownCloud can fail in certain reverse proxy and CLI/cron situations. This option allows to manually override the automatic detection. For example "/domain.tld/ownCloud". The value "/" can be used to remove the root. */
|
||||
"overwritewebroot" => "",
|
||||
|
||||
/* The automatic detection of ownCloud can fail in certain reverse proxy situations. This option allows to define a manually override condition as regular expression for the remote ip address. For example "^10\.0\.0\.[1-3]$" */
|
||||
/* The automatic detection of ownCloud can fail in certain reverse proxy and CLI/cron situations. This option allows to define a manually override condition as regular expression for the remote ip address. For example "^10\.0\.0\.[1-3]$" */
|
||||
"overwritecondaddr" => "",
|
||||
|
||||
/* A proxy to use to connect to the internet. For example "myproxy.org:88" */
|
||||
@@ -88,7 +88,7 @@ $CONFIG = array(
|
||||
"appstoreenabled" => true,
|
||||
|
||||
/* URL of the appstore to use, server should understand OCS */
|
||||
"appstoreurl" => "http://api.apps.owncloud.com/v1",
|
||||
"appstoreurl" => "https://api.owncloud.com/v1",
|
||||
|
||||
/* Domain name used by ownCloud for the sender mail address, e.g. no-reply@example.com */
|
||||
"mail_domain" => "example.com",
|
||||
|
||||
@@ -21,10 +21,6 @@ if ($file === '') {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($keepAspect === true) {
|
||||
$maxY = $maxX;
|
||||
}
|
||||
|
||||
if ($maxX === 0 || $maxY === 0) {
|
||||
//400 Bad Request
|
||||
\OC_Response::setStatus(400);
|
||||
|
||||
+5
-11
@@ -80,18 +80,12 @@ if (isset($_POST['action']) && isset($_POST['itemType']) && isset($_POST['itemSo
|
||||
break;
|
||||
case 'setExpirationDate':
|
||||
if (isset($_POST['date'])) {
|
||||
$l = OC_L10N::get('core');
|
||||
$date = new \DateTime($_POST['date']);
|
||||
$today = new \DateTime('now');
|
||||
|
||||
|
||||
|
||||
if ($date < $today) {
|
||||
OC_JSON::error(array('data' => array('message' => $l->t('Expiration date is in the past.'))));
|
||||
return;
|
||||
try {
|
||||
$return = OCP\Share::setExpirationDate($_POST['itemType'], $_POST['itemSource'], $_POST['date']);
|
||||
($return) ? OC_JSON::success() : OC_JSON::error();
|
||||
} catch (\Exception $e) {
|
||||
OC_JSON::error(array('data' => array('message' => $e->getMessage())));
|
||||
}
|
||||
$return = OCP\Share::setExpirationDate($_POST['itemType'], $_POST['itemSource'], $_POST['date']);
|
||||
($return) ? OC_JSON::success() : OC_JSON::error();
|
||||
}
|
||||
break;
|
||||
case 'informRecipients':
|
||||
|
||||
@@ -190,6 +190,8 @@
|
||||
#navigation .app-icon {
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
max-height: 32px;
|
||||
max-width: 32px;
|
||||
}
|
||||
|
||||
/* Apps management */
|
||||
@@ -241,20 +243,26 @@
|
||||
float: left;
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#header .avatardiv img {
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#settings {
|
||||
float: right;
|
||||
color: #bbb;
|
||||
cursor: pointer;
|
||||
}
|
||||
#expand {
|
||||
display: block;
|
||||
padding: 7px 12px 6px 7px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#expand * {
|
||||
cursor: pointer;
|
||||
}
|
||||
#expand:hover, #expand:focus, #expand:active { color:#fff; }
|
||||
#expand img { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=70)"; filter:alpha(opacity=70); opacity:.7; margin-bottom:-2px; }
|
||||
#expand:hover img, #expand:focus img, #expand:active img { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; filter:alpha(opacity=100); opacity:1; }
|
||||
|
||||
@@ -136,6 +136,9 @@
|
||||
.icon-search {
|
||||
background-image: url('../img/actions/search.svg');
|
||||
}
|
||||
.icon-search-white {
|
||||
background-image: url('../img/actions/search-white.svg');
|
||||
}
|
||||
|
||||
.icon-settings {
|
||||
background-image: url('../img/actions/settings.svg');
|
||||
|
||||
@@ -31,6 +31,17 @@
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
/* align primary button to right, other buttons to left */
|
||||
.oc-dialog-buttonrow.twobuttons button:nth-child(1) {
|
||||
float: left;
|
||||
}
|
||||
.oc-dialog-buttonrow.twobuttons button:nth-child(2) {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.oc-dialog-buttonrow.onebutton button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.oc-dialog-close {
|
||||
position:absolute;
|
||||
|
||||
+11
-5
@@ -31,16 +31,22 @@
|
||||
|
||||
/* compress search box on mobile, expand when focused */
|
||||
.searchbox input[type="search"] {
|
||||
width: 15%;
|
||||
-webkit-transition: width 100ms;
|
||||
-moz-transition: width 100ms;
|
||||
-o-transition: width 100ms;
|
||||
transition: width 100ms;
|
||||
width: 0;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
background-image: url('../img/actions/search-white.svg');
|
||||
-webkit-transition: all 100ms;
|
||||
-moz-transition: all 100ms;
|
||||
-o-transition: all 100ms;
|
||||
transition: all 100ms;
|
||||
}
|
||||
.searchbox input[type="search"]:focus,
|
||||
.searchbox input[type="search"]:active {
|
||||
width: 155px;
|
||||
max-width: 50%;
|
||||
cursor: text;
|
||||
background-color: #fff;
|
||||
background-image: url('../img/actions/search.svg');
|
||||
}
|
||||
|
||||
/* do not show display name on mobile when profile picture is present */
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user