Compare commits
140 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -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
|
||||
|
||||
|
||||
+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 + '/';
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -598,6 +600,10 @@
|
||||
"data-permissions": fileData.permissions || this.getDirectoryPermissions()
|
||||
});
|
||||
|
||||
if (fileData.mountType) {
|
||||
tr.attr('data-mounttype', fileData.mountType);
|
||||
}
|
||||
|
||||
if (!_.isUndefined(path)) {
|
||||
tr.attr('data-path', path);
|
||||
}
|
||||
@@ -919,7 +925,9 @@
|
||||
.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 +953,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 +983,7 @@
|
||||
}
|
||||
|
||||
this.setFiles(result.data.files);
|
||||
return true
|
||||
return true;
|
||||
},
|
||||
|
||||
updateStorageStatistics: function(force) {
|
||||
@@ -1566,7 +1581,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',
|
||||
|
||||
@@ -1933,4 +1933,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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -432,7 +444,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 +451,9 @@ class Hooks {
|
||||
isset(self::$renamedFiles[$params['oldpath']]['path'])) {
|
||||
$ownerOld = self::$renamedFiles[$params['oldpath']]['uid'];
|
||||
$pathOld = self::$renamedFiles[$params['oldpath']]['path'];
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -474,6 +486,7 @@ class Hooks {
|
||||
|
||||
// handle share keys
|
||||
if (!$view->is_dir($oldKeyfilePath)) {
|
||||
$type = 'file';
|
||||
$oldKeyfilePath .= '.key';
|
||||
$newKeyfilePath .= '.key';
|
||||
|
||||
@@ -485,6 +498,7 @@ class Hooks {
|
||||
}
|
||||
|
||||
} else {
|
||||
$type = "folder";
|
||||
// handle share-keys folders
|
||||
$view->rename($oldShareKeyPath, $newShareKeyPath);
|
||||
}
|
||||
@@ -494,14 +508,9 @@ class Hooks {
|
||||
$view->rename($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 +604,7 @@ class Hooks {
|
||||
}
|
||||
|
||||
/**
|
||||
* unmount file from yourself
|
||||
* remember files/folders which get unmounted
|
||||
*/
|
||||
public static function preUmount($params) {
|
||||
@@ -613,6 +623,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 +655,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 {
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,4 +15,5 @@
|
||||
<files>public.php</files>
|
||||
<webdav>publicwebdav.php</webdav>
|
||||
</public>
|
||||
<ocsid>166050</ocsid>
|
||||
</info>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -95,7 +95,6 @@
|
||||
},
|
||||
|
||||
reload: function() {
|
||||
var self = this;
|
||||
this.showMask();
|
||||
if (this._reloadCall) {
|
||||
this._reloadCall.abort();
|
||||
@@ -110,14 +109,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 +161,10 @@
|
||||
}
|
||||
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.icon = true;
|
||||
file.isPreviewAvailable = true;
|
||||
}
|
||||
}
|
||||
file.share = {
|
||||
id: share.id,
|
||||
@@ -185,7 +179,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 +233,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 +241,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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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']);
|
||||
}
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,6 +237,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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,7 +236,7 @@ class Storage {
|
||||
return true;
|
||||
|
||||
}else if ( $versionCreated ) {
|
||||
$users_view->unlink($version);
|
||||
self::deleteVersion($users_view, $version);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -472,7 +488,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 +502,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'];
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -241,20 +241,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; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
+14
-3
@@ -420,13 +420,13 @@ input[name='password-clone'] {
|
||||
#password-icon {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 20px;
|
||||
top: 22px;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)";
|
||||
filter: alpha(opacity=30);
|
||||
opacity: .3;
|
||||
}
|
||||
#adminpass-icon, #password-icon {
|
||||
top: 15px;
|
||||
top: 17px;
|
||||
}
|
||||
|
||||
/* General new input field look */
|
||||
@@ -474,10 +474,10 @@ label.infield {
|
||||
#body-login form input[type="checkbox"]+label {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
padding: 14px;
|
||||
padding-left: 28px;
|
||||
margin-left: -28px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
#body-login form .errors { background:#fed7d7; border:1px solid #f00; list-style-indent:inside; margin:0 0 2em; padding:1em; }
|
||||
#body-login .success { background:#d7fed7; border:1px solid #0f0; width: 35%; margin: 30px auto; padding:1em; text-align: center;}
|
||||
@@ -812,6 +812,7 @@ div.crumb {
|
||||
display: block;
|
||||
background: url('../img/breadcrumb.svg') no-repeat right center;
|
||||
height: 44px;
|
||||
background-size: auto 24px;
|
||||
}
|
||||
div.crumb.hidden {
|
||||
display: none;
|
||||
@@ -853,6 +854,16 @@ div.crumb:active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* LEGACY FIX only - do not use fieldsets for settings */
|
||||
fieldset.warning legend, fieldset.update legend {
|
||||
top: 18px;
|
||||
position: relative;
|
||||
}
|
||||
fieldset.warning legend + p, fieldset.update legend + p {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
|
||||
/* for IE10 */
|
||||
@-ms-viewport {
|
||||
width: device-width;
|
||||
|
||||
@@ -111,6 +111,13 @@
|
||||
var $buttonrow = $('<div class="oc-dialog-buttonrow" />');
|
||||
this.$buttonrow = $buttonrow.appendTo(this.$dialog);
|
||||
}
|
||||
if (value.length === 1) {
|
||||
this.$buttonrow.addClass('onebutton');
|
||||
} else if (value.length === 2) {
|
||||
this.$buttonrow.addClass('twobuttons');
|
||||
} else if (value.length === 3) {
|
||||
this.$buttonrow.addClass('threebuttons');
|
||||
}
|
||||
$.each(value, function(idx, val) {
|
||||
var $button = $('<button>').text(val.text);
|
||||
if (val.classes) {
|
||||
|
||||
+5
-3
@@ -380,7 +380,7 @@ var OC={
|
||||
* Do a search query and display the results
|
||||
* @param {string} query the search query
|
||||
*/
|
||||
search:function(query){
|
||||
search: _.debounce(function(query){
|
||||
if(query){
|
||||
OC.addStyle('search','results');
|
||||
$.getJSON(OC.filePath('search','ajax','search.php')+'?query='+encodeURIComponent(query), function(results){
|
||||
@@ -388,7 +388,7 @@ var OC={
|
||||
OC.search.showResults(results);
|
||||
});
|
||||
}
|
||||
},
|
||||
}, 500),
|
||||
dialogs:OCdialogs,
|
||||
mtime2date:function(mtime) {
|
||||
mtime = parseInt(mtime,10);
|
||||
@@ -1138,7 +1138,9 @@ function initCore() {
|
||||
if(!$app.is('a')) {
|
||||
$app = $app.closest('a');
|
||||
}
|
||||
$app.addClass('app-loading');
|
||||
if(!event.ctrlKey) {
|
||||
$app.addClass('app-loading');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+22
-24
@@ -19,7 +19,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/* global OC, t, alert, $ */
|
||||
/* global alert */
|
||||
|
||||
/**
|
||||
* this class to ease the usage of jquery dialogs
|
||||
@@ -66,7 +66,7 @@ var OCdialogs = {
|
||||
* @param modal make the dialog modal
|
||||
*/
|
||||
confirm:function(text, title, callback, modal) {
|
||||
this.message(
|
||||
return this.message(
|
||||
text,
|
||||
title,
|
||||
'notice',
|
||||
@@ -86,7 +86,7 @@ var OCdialogs = {
|
||||
* @param password whether the input should be a password input
|
||||
*/
|
||||
prompt: function (text, title, callback, modal, name, password) {
|
||||
$.when(this._getMessageTemplate()).then(function ($tmpl) {
|
||||
return $.when(this._getMessageTemplate()).then(function ($tmpl) {
|
||||
var dialogName = 'oc-dialog-' + OCdialogs.dialogsCounter + '-content';
|
||||
var dialogId = '#' + dialogName;
|
||||
var $dlg = $tmpl.octemplate({
|
||||
@@ -104,8 +104,15 @@ var OCdialogs = {
|
||||
modal = false;
|
||||
}
|
||||
$('body').append($dlg);
|
||||
var buttonlist = [
|
||||
{
|
||||
var buttonlist = [{
|
||||
text : t('core', 'No'),
|
||||
click: function () {
|
||||
if (callback !== undefined) {
|
||||
callback(false, input.val());
|
||||
}
|
||||
$(dialogId).ocdialog('close');
|
||||
}
|
||||
}, {
|
||||
text : t('core', 'Yes'),
|
||||
click : function () {
|
||||
if (callback !== undefined) {
|
||||
@@ -114,15 +121,6 @@ var OCdialogs = {
|
||||
$(dialogId).ocdialog('close');
|
||||
},
|
||||
defaultButton: true
|
||||
},
|
||||
{
|
||||
text : t('core', 'No'),
|
||||
click: function () {
|
||||
if (callback !== undefined) {
|
||||
callback(false, input.val());
|
||||
}
|
||||
$(dialogId).ocdialog('close');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -237,7 +235,7 @@ var OCdialogs = {
|
||||
* You better use a wrapper instead ...
|
||||
*/
|
||||
message:function(content, title, dialogType, buttons, callback, modal) {
|
||||
$.when(this._getMessageTemplate()).then(function($tmpl) {
|
||||
return $.when(this._getMessageTemplate()).then(function($tmpl) {
|
||||
var dialogName = 'oc-dialog-' + OCdialogs.dialogsCounter + '-content';
|
||||
var dialogId = '#' + dialogName;
|
||||
var $dlg = $tmpl.octemplate({
|
||||
@@ -254,6 +252,15 @@ var OCdialogs = {
|
||||
switch (buttons) {
|
||||
case OCdialogs.YES_NO_BUTTONS:
|
||||
buttonlist = [{
|
||||
text: t('core', 'No'),
|
||||
click: function(){
|
||||
if (callback !== undefined) {
|
||||
callback(false);
|
||||
}
|
||||
$(dialogId).ocdialog('close');
|
||||
}
|
||||
},
|
||||
{
|
||||
text: t('core', 'Yes'),
|
||||
click: function(){
|
||||
if (callback !== undefined) {
|
||||
@@ -262,15 +269,6 @@ var OCdialogs = {
|
||||
$(dialogId).ocdialog('close');
|
||||
},
|
||||
defaultButton: true
|
||||
},
|
||||
{
|
||||
text: t('core', 'No'),
|
||||
click: function(){
|
||||
if (callback !== undefined) {
|
||||
callback(false);
|
||||
}
|
||||
$(dialogId).ocdialog('close');
|
||||
}
|
||||
}];
|
||||
break;
|
||||
case OCdialogs.OK_BUTTON:
|
||||
|
||||
@@ -52,6 +52,10 @@ $(document).ready(function() {
|
||||
$(':submit', this).attr('disabled','disabled').val($(':submit', this).data('finishing'));
|
||||
$('input', this).addClass('ui-state-disabled').attr('disabled','disabled');
|
||||
$('#selectDbType').buttonset('disable');
|
||||
$('.strengthify-wrapper, .tipsy')
|
||||
.css('-ms-filter', '"progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"')
|
||||
.css('filter', 'alpha(opacity=30)')
|
||||
.css('opacity', .3);
|
||||
|
||||
// Create the form
|
||||
var form = $('<form>');
|
||||
|
||||
+2
-2
@@ -191,7 +191,7 @@ OC.Share={
|
||||
var parts = this._REMOTE_OWNER_REGEXP.exec(owner);
|
||||
if (!parts) {
|
||||
// display as is, most likely to be a simple owner name
|
||||
return t('files_sharing', 'Shared by {owner}', {owner: escapeHTML(owner)});
|
||||
return escapeHTML(owner);
|
||||
}
|
||||
|
||||
var userName = parts[1];
|
||||
@@ -211,7 +211,7 @@ OC.Share={
|
||||
html += '<span class="userDomain">@' + escapeHTML(userDomain) + '</span>';
|
||||
}
|
||||
html += '</span>';
|
||||
return t('files_sharing', 'Shared by {owner}', {owner: html});
|
||||
return html;
|
||||
},
|
||||
/**
|
||||
* Marks/unmarks a given file as shared by changing its action icon
|
||||
|
||||
+760
-543
File diff suppressed because it is too large
Load Diff
@@ -403,39 +403,39 @@ describe('OC.Share tests', function() {
|
||||
}
|
||||
|
||||
it('displays the local share owner as is', function() {
|
||||
checkOwner('User One', 'Shared by User One', null);
|
||||
checkOwner('User One', 'User One', null);
|
||||
});
|
||||
it('displays the user name part of a remote share owner', function() {
|
||||
checkOwner(
|
||||
'User One@someserver.com',
|
||||
'Shared by User One',
|
||||
'User One',
|
||||
'User One@someserver.com'
|
||||
);
|
||||
checkOwner(
|
||||
'User One@someserver.com/',
|
||||
'Shared by User One',
|
||||
'User One',
|
||||
'User One@someserver.com'
|
||||
);
|
||||
checkOwner(
|
||||
'User One@someserver.com/root/of/owncloud',
|
||||
'Shared by User One',
|
||||
'User One',
|
||||
'User One@someserver.com'
|
||||
);
|
||||
});
|
||||
it('displays the user name part with domain of a remote share owner', function() {
|
||||
checkOwner(
|
||||
'User One@example.com@someserver.com',
|
||||
'Shared by User One@example.com',
|
||||
'User One@example.com',
|
||||
'User One@example.com@someserver.com'
|
||||
);
|
||||
checkOwner(
|
||||
'User One@example.com@someserver.com/',
|
||||
'Shared by User One@example.com',
|
||||
'User One@example.com',
|
||||
'User One@example.com@someserver.com'
|
||||
);
|
||||
checkOwner(
|
||||
'User One@example.com@someserver.com/root/of/owncloud',
|
||||
'Shared by User One@example.com',
|
||||
'User One@example.com',
|
||||
'User One@example.com@someserver.com'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -41,7 +41,7 @@ class Controller {
|
||||
'dbpass' => '',
|
||||
'dbname' => '',
|
||||
'dbtablespace' => '',
|
||||
'dbhost' => '',
|
||||
'dbhost' => 'localhost',
|
||||
'dbtype' => '',
|
||||
);
|
||||
$parameters = array_merge($defaults, $post);
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
<a href="<?php print_unescaped(OC_Helper::linkToRoute('settings_apps').'?installed'); ?>" title=""
|
||||
<?php if( $_['appsmanagement_active'] ): ?> class="active"<?php endif; ?>>
|
||||
<img class="app-icon svg" alt="" src="<?php print_unescaped(OC_Helper::imagePath('settings', 'apps.svg')); ?>"/>
|
||||
<div class="icon-loading-dark" style="display:none;"></div>
|
||||
<span>
|
||||
<?php p($l->t('Apps')); ?>
|
||||
</span>
|
||||
|
||||
@@ -942,18 +942,6 @@
|
||||
<sorting>ascending</sorting>
|
||||
</field>
|
||||
</index>
|
||||
<index>
|
||||
<name>file_target_index</name>
|
||||
<field>
|
||||
<name>file_target</name>
|
||||
</field>
|
||||
<field>
|
||||
<name>uid_owner</name>
|
||||
</field>
|
||||
<field>
|
||||
<name>share_type</name>
|
||||
</field>
|
||||
</index>
|
||||
</declaration>
|
||||
|
||||
</table>
|
||||
|
||||
@@ -557,7 +557,7 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin.php:229
|
||||
msgid "Use systems cron service to call the cron.php file every 15 minutes."
|
||||
msgid "Use system's cron service to call the cron.php file every 15 minutes."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin.php:234
|
||||
|
||||
@@ -557,7 +557,7 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin.php:229
|
||||
msgid "Use systems cron service to call the cron.php file every 15 minutes."
|
||||
msgid "Use system's cron service to call the cron.php file every 15 minutes."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin.php:234
|
||||
|
||||
+1
-1
@@ -467,7 +467,7 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin.php:200
|
||||
msgid "Use systems cron service to call the cron.php file every 15 minutes."
|
||||
msgid "Use system's cron service to call the cron.php file every 15 minutes."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin.php:205
|
||||
|
||||
@@ -557,7 +557,7 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin.php:229
|
||||
msgid "Use systems cron service to call the cron.php file every 15 minutes."
|
||||
msgid "Use system's cron service to call the cron.php file every 15 minutes."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin.php:234
|
||||
|
||||
+1
-1
@@ -557,7 +557,7 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin.php:229
|
||||
msgid "Use systems cron service to call the cron.php file every 15 minutes."
|
||||
msgid "Use system's cron service to call the cron.php file every 15 minutes."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin.php:234
|
||||
|
||||
@@ -557,7 +557,7 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin.php:229
|
||||
msgid "Use systems cron service to call the cron.php file every 15 minutes."
|
||||
msgid "Use system's cron service to call the cron.php file every 15 minutes."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin.php:234
|
||||
|
||||
+1
-1
@@ -560,7 +560,7 @@ msgid ""
|
||||
msgstr "الملف cron.php تم تسجيله فى خدمه webcron لاستدعاء الملف cron.php كل 15 دقيقه"
|
||||
|
||||
#: templates/admin.php:229
|
||||
msgid "Use systems cron service to call the cron.php file every 15 minutes."
|
||||
msgid "Use system's cron service to call the cron.php file every 15 minutes."
|
||||
msgstr "استخدم نظام خدمة cron لـ استدعاء ملف cron.php كل 15 دقيقة "
|
||||
|
||||
#: templates/admin.php:234
|
||||
|
||||
@@ -559,7 +559,7 @@ msgid ""
|
||||
msgstr "cron.php rexístrase nun serviciu webcron pa llamar a cron.php cada 15 minutos al traviés de HTTP."
|
||||
|
||||
#: templates/admin.php:229
|
||||
msgid "Use systems cron service to call the cron.php file every 15 minutes."
|
||||
msgid "Use system's cron service to call the cron.php file every 15 minutes."
|
||||
msgstr "Usa'l serviciu cron del sistema pa llamar al ficheru cron.php cada 15 minutos."
|
||||
|
||||
#: templates/admin.php:234
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user