diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a79fcc08d6..2360a082a2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,10 @@ If you have questions about how to install or use ownCloud, please direct these ### Guidelines * Please search the existing issues first, it's likely that your issue was already reported or even fixed. -* This repository is *only* for issues within the ownCloud core code. This also includes the apps: files, encryption, external storage, sharing, deleted files, versions, LDAP, and WebDAV Auth + - Go to one of the repositories, click "issues" and type any word in the top search/command bar. + - You can also filter by appending e. g. "state:open" to the search string. + - More info on [search syntax within github](https://help.github.com/articles/searching-issues) +* This repository ([core](https://github.com/owncloud/core/issues)) is *only* for issues within the ownCloud core code. This also includes the apps: files, encryption, external storage, sharing, deleted files, versions, LDAP, and WebDAV Auth * The issues in other components should be reported in their respective repositories: - [Android client](https://github.com/owncloud/android/issues) - [iOS client](https://github.com/owncloud/ios-issues/issues) diff --git a/apps/files/ajax/newfile.php b/apps/files/ajax/newfile.php index 76c03c87a5..d1183089b4 100644 --- a/apps/files/ajax/newfile.php +++ b/apps/files/ajax/newfile.php @@ -20,15 +20,6 @@ if($source) { OC_JSON::callCheck(); } -if($filename == '') { - OCP\JSON::error(array("data" => array( "message" => "Empty Filename" ))); - exit(); -} -if(strpos($filename, '/')!==false) { - OCP\JSON::error(array("data" => array( "message" => "Invalid Filename" ))); - exit(); -} - function progress($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { static $filesize = 0; static $lastsize = 0; @@ -44,7 +35,7 @@ function progress($notification_code, $severity, $message, $message_code, $bytes if (!isset($filesize)) { } else { $progress = (int)(($bytes_transferred/$filesize)*100); - if($progress>$lastsize) {//limit the number or messages send + if($progress>$lastsize) { //limit the number or messages send $eventSource->send('progress', $progress); } $lastsize=$progress; @@ -54,11 +45,40 @@ function progress($notification_code, $severity, $message, $message_code, $bytes } } +$l10n = \OC_L10n::get('files'); + +$result = array( + 'success' => false, + 'data' => NULL + ); + +if(trim($filename) === '') { + $result['data'] = array('message' => $l10n->t('File name cannot not be empty.')); + OCP\JSON::error($result); + exit(); +} + +if(strpos($filename, '/') !== false) { + $result['data'] = array('message' => $l10n->t('File name must not contain "/". Please choose a different name.')); + OCP\JSON::error($result); + exit(); +} + +//TODO why is stripslashes used on foldername in newfolder.php but not here? $target = $dir.'/'.$filename; +if (\OC\Files\Filesystem::file_exists($target)) { + $result['data'] = array('message' => $l10n->t( + 'The name %s is already used in the folder %s. Please choose a different name.', + array($filename, $dir)) + ); + OCP\JSON::error($result); + exit(); +} + if($source) { if(substr($source, 0, 8)!='https://' and substr($source, 0, 7)!='http://') { - OCP\JSON::error(array("data" => array( "message" => "Not a valid source" ))); + OCP\JSON::error(array('data' => array( 'message' => $l10n->t('Not a valid source') ))); exit(); } @@ -71,7 +91,7 @@ if($source) { $id = $meta['fileid']; $eventSource->send('success', array('mime'=>$mime, 'size'=>\OC\Files\Filesystem::filesize($target), 'id' => $id)); } else { - $eventSource->send('error', "Error while downloading ".$source. ' to '.$target); + $eventSource->send('error', $l10n->t('Error while downloading %s to %s', array($source, $target))); } $eventSource->close(); exit(); @@ -104,4 +124,4 @@ if($source) { } } -OCP\JSON::error(array("data" => array( "message" => "Error when creating the file" ))); +OCP\JSON::error(array('data' => array( 'message' => $l10n->t('Error when creating the file') ))); diff --git a/apps/files/ajax/newfolder.php b/apps/files/ajax/newfolder.php index e26e1238bc..1fd5359896 100644 --- a/apps/files/ajax/newfolder.php +++ b/apps/files/ajax/newfolder.php @@ -10,25 +10,47 @@ OCP\JSON::callCheck(); $dir = isset( $_POST['dir'] ) ? stripslashes($_POST['dir']) : ''; $foldername = isset( $_POST['foldername'] ) ? stripslashes($_POST['foldername']) : ''; -if(trim($foldername) == '') { - OCP\JSON::error(array("data" => array( "message" => "Empty Foldername" ))); - exit(); -} -if(strpos($foldername, '/')!==false) { - OCP\JSON::error(array("data" => array( "message" => "Invalid Foldername" ))); +$l10n = \OC_L10n::get('files'); + +$result = array( + 'success' => false, + 'data' => NULL + ); + +if(trim($foldername) === '') { + $result['data'] = array('message' => $l10n->t('Folder name cannot not be empty.')); + OCP\JSON::error($result); exit(); } -if(\OC\Files\Filesystem::mkdir($dir . '/' . stripslashes($foldername))) { - if ( $dir != '/') { +if(strpos($foldername, '/') !== false) { + $result['data'] = array('message' => $l10n->t('Folder name must not contain "/". Please choose a different name.')); + OCP\JSON::error($result); + exit(); +} + +//TODO why is stripslashes used on foldername here but not in newfile.php? +$target = $dir . '/' . stripslashes($foldername); + +if (\OC\Files\Filesystem::file_exists($target)) { + $result['data'] = array('message' => $l10n->t( + 'The name %s is already used in the folder %s. Please choose a different name.', + array($foldername, $dir)) + ); + OCP\JSON::error($result); + exit(); +} + +if(\OC\Files\Filesystem::mkdir($target)) { + if ( $dir !== '/') { $path = $dir.'/'.$foldername; } else { $path = '/'.$foldername; } $meta = \OC\Files\Filesystem::getFileInfo($path); $id = $meta['fileid']; - OCP\JSON::success(array("data" => array('id'=>$id))); + OCP\JSON::success(array('data' => array('id' => $id))); exit(); } -OCP\JSON::error(array("data" => array( "message" => "Error when creating the folder" ))); +OCP\JSON::error(array('data' => array( 'message' => $l10n->t('Error when creating the folder') ))); diff --git a/apps/files/appinfo/remote.php b/apps/files/appinfo/remote.php index 75c80cd49f..9f29079620 100644 --- a/apps/files/appinfo/remote.php +++ b/apps/files/appinfo/remote.php @@ -48,6 +48,7 @@ $defaults = new OC_Defaults(); $server->addPlugin(new Sabre_DAV_Auth_Plugin($authBackend, $defaults->getName())); $server->addPlugin(new Sabre_DAV_Locks_Plugin($lockBackend)); $server->addPlugin(new Sabre_DAV_Browser_Plugin(false)); // Show something in the Browser, but no upload +$server->addPlugin(new OC_Connector_Sabre_FilesPlugin()); $server->addPlugin(new OC_Connector_Sabre_AbortedUploadDetectionPlugin()); $server->addPlugin(new OC_Connector_Sabre_QuotaPlugin()); $server->addPlugin(new OC_Connector_Sabre_MaintenancePlugin()); diff --git a/apps/files/css/files.css b/apps/files/css/files.css index ac2a243f2b..af8597192f 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -49,7 +49,13 @@ background-repeat:no-repeat; cursor:pointer; } #new>ul>li>p { cursor:pointer; padding-top: 7px; padding-bottom: 7px;} - +#new .error, #fileList .error { + color: #e9322d; + border-color: #e9322d; + -webkit-box-shadow: 0 0 6px #f8b9b7; + -moz-box-shadow: 0 0 6px #f8b9b7; + box-shadow: 0 0 6px #f8b9b7; +} /* FILE TABLE */ diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index fefb06a8ac..5bf4f5c098 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -21,13 +21,13 @@ function supportAjaxUploadWithProgress() { var fi = document.createElement('INPUT'); fi.type = 'file'; return 'files' in fi; - }; + } // Are progress events supported? function supportAjaxUploadProgressEvents() { var xhr = new XMLHttpRequest(); return !! (xhr && ('upload' in xhr) && ('onprogress' in xhr.upload)); - }; + } // Is FormData supported? function supportFormData() { @@ -53,12 +53,12 @@ OC.Upload = { */ cancelUploads:function() { this.log('canceling uploads'); - jQuery.each(this._uploads,function(i, jqXHR){ + jQuery.each(this._uploads,function(i, jqXHR) { jqXHR.abort(); }); this._uploads = []; }, - rememberUpload:function(jqXHR){ + rememberUpload:function(jqXHR) { if (jqXHR) { this._uploads.push(jqXHR); } @@ -68,10 +68,10 @@ OC.Upload = { * returns true if any hxr has the state 'pending' * @returns {boolean} */ - isProcessing:function(){ + isProcessing:function() { var count = 0; - jQuery.each(this._uploads,function(i, data){ + jQuery.each(this._uploads,function(i, data) { if (data.state() === 'pending') { count++; } @@ -114,7 +114,7 @@ OC.Upload = { * handle skipping an upload * @param {object} data */ - onSkip:function(data){ + onSkip:function(data) { this.log('skip', null, data); this.deleteUpload(data); }, @@ -122,12 +122,12 @@ OC.Upload = { * handle replacing a file on the server with an uploaded file * @param {object} data */ - onReplace:function(data){ + onReplace:function(data) { this.log('replace', null, data); - if (data.data){ + if (data.data) { data.data.append('resolution', 'replace'); } else { - data.formData.push({name:'resolution',value:'replace'}); //hack for ie8 + data.formData.push({name:'resolution', value:'replace'}); //hack for ie8 } data.submit(); }, @@ -135,12 +135,12 @@ OC.Upload = { * handle uploading a file and letting the server decide a new name * @param {object} data */ - onAutorename:function(data){ + onAutorename:function(data) { this.log('autorename', null, data); if (data.data) { data.data.append('resolution', 'autorename'); } else { - data.formData.push({name:'resolution',value:'autorename'}); //hack for ie8 + data.formData.push({name:'resolution', value:'autorename'}); //hack for ie8 } data.submit(); }, @@ -162,7 +162,7 @@ OC.Upload = { * @param {function} callbacks.onChooseConflicts * @param {function} callbacks.onCancel */ - checkExistingFiles: function (selection, callbacks){ + checkExistingFiles: function (selection, callbacks) { // TODO check filelist before uploading and show dialog on conflicts, use callbacks callbacks.onNoConflicts(selection); } @@ -215,7 +215,7 @@ $(document).ready(function() { var selection = data.originalFiles.selection; // add uploads - if ( selection.uploads.length < selection.filesToUpload ){ + if ( selection.uploads.length < selection.filesToUpload ) { // remember upload selection.uploads.push(data); } @@ -335,7 +335,7 @@ $(document).ready(function() { delete data.jqXHR; - if(typeof result[0] === 'undefined') { + if (typeof result[0] === 'undefined') { data.textStatus = 'servererror'; data.errorThrown = t('files', 'Could not get result from server.'); var fu = $(this).data('blueimp-fileupload') || $(this).data('fileupload'); @@ -368,13 +368,13 @@ $(document).ready(function() { var fileupload = $('#file_upload_start').fileupload(file_upload_param); window.file_upload_param = fileupload; - if(supportAjaxUploadWithProgress()) { + if (supportAjaxUploadWithProgress()) { // add progress handlers fileupload.on('fileuploadadd', function(e, data) { OC.Upload.log('progress handle fileuploadadd', e, data); //show cancel button - //if(data.dataType !== 'iframe') { //FIXME when is iframe used? only for ie? + //if (data.dataType !== 'iframe') { //FIXME when is iframe used? only for ie? // $('#uploadprogresswrapper input.stop').show(); //} }); @@ -419,7 +419,9 @@ $(document).ready(function() { // http://stackoverflow.com/a/6700/11236 var size = 0, key; for (key in obj) { - if (obj.hasOwnProperty(key)) size++; + if (obj.hasOwnProperty(key)) { + size++; + } } return size; }; @@ -432,56 +434,61 @@ $(document).ready(function() { }); //add multiply file upload attribute to all browsers except konqueror (which crashes when it's used) - if(navigator.userAgent.search(/konqueror/i)==-1){ - $('#file_upload_start').attr('multiple','multiple'); + if (navigator.userAgent.search(/konqueror/i) === -1) { + $('#file_upload_start').attr('multiple', 'multiple'); } //if the breadcrumb is to long, start by replacing foldernames with '...' except for the current folder var crumb=$('div.crumb').first(); - while($('div.controls').height()>40 && crumb.next('div.crumb').length>0){ + while($('div.controls').height() > 40 && crumb.next('div.crumb').length > 0) { crumb.children('a').text('...'); - crumb=crumb.next('div.crumb'); + crumb = crumb.next('div.crumb'); } //if that isn't enough, start removing items from the breacrumb except for the current folder and it's parent - var crumb=$('div.crumb').first(); - var next=crumb.next('div.crumb'); - while($('div.controls').height()>40 && next.next('div.crumb').length>0){ + var crumb = $('div.crumb').first(); + var next = crumb.next('div.crumb'); + while($('div.controls').height()>40 && next.next('div.crumb').length > 0) { crumb.remove(); - crumb=next; - next=crumb.next('div.crumb'); + crumb = next; + next = crumb.next('div.crumb'); } //still not enough, start shorting down the current folder name var crumb=$('div.crumb>a').last(); - while($('div.controls').height()>40 && crumb.text().length>6){ - var text=crumb.text() - text=text.substr(0,text.length-6)+'...'; + while($('div.controls').height() > 40 && crumb.text().length > 6) { + var text=crumb.text(); + text = text.substr(0,text.length-6)+'...'; crumb.text(text); } - $(document).click(function(){ + $(document).click(function() { $('#new>ul').hide(); $('#new').removeClass('active'); - $('#new li').each(function(i,element){ - if($(element).children('p').length==0){ + if ($('#new .error').length > 0) { + $('#new .error').tipsy('hide'); + } + $('#new li').each(function(i,element) { + if ($(element).children('p').length === 0) { $(element).children('form').remove(); $(element).append('

'+$(element).data('text')+'

'); } }); }); - $('#new').click(function(event){ + $('#new').click(function(event) { event.stopPropagation(); }); - $('#new>a').click(function(){ + $('#new>a').click(function() { $('#new>ul').toggle(); $('#new').toggleClass('active'); }); - $('#new li').click(function(){ - if($(this).children('p').length==0){ + $('#new li').click(function() { + if ($(this).children('p').length === 0) { return; } + + $('#new .error').tipsy('hide'); - $('#new li').each(function(i,element){ - if($(element).children('p').length==0){ + $('#new li').each(function(i,element) { + if ($(element).children('p').length === 0) { $(element).children('form').remove(); $(element).append('

'+$(element).data('text')+'

'); } @@ -491,132 +498,164 @@ $(document).ready(function() { var text=$(this).children('p').text(); $(this).data('text',text); $(this).children('p').remove(); + + // add input field var form=$('
'); var input=$(''); form.append(input); $(this).append(form); + + var checkInput = function () { + var filename = input.val(); + if (type === 'web' && filename.length === 0) { + throw t('files', 'URL cannot be empty'); + } else if (type !== 'web' && !Files.isFileNameValid(filename)) { + // Files.isFileNameValid(filename) throws an exception itself + } else if ($('#dir').val() === '/' && filename === 'Shared') { + throw t('files', 'In the home folder \'Shared\' is a reserved filename'); + } else if (FileList.inList(filename)) { + throw t('files', '{new_name} already exists', {new_name: filename}); + } else { + return true; + } + }; + + // verify filename on typing + input.keyup(function(event) { + try { + checkInput(); + input.tipsy('hide'); + input.removeClass('error'); + } catch (error) { + input.attr('title', error); + input.tipsy({gravity: 'w', trigger: 'manual'}); + input.tipsy('show'); + input.addClass('error'); + } + }); + input.focus(); - form.submit(function(event){ + form.submit(function(event) { event.stopPropagation(); event.preventDefault(); - var newname=input.val(); - if(type == 'web' && newname.length == 0) { - OC.Notification.show(t('files', 'URL cannot be empty.')); - return false; - } else if (type != 'web' && !Files.isFileNameValid(newname)) { - return false; - } else if( type == 'folder' && $('#dir').val() == '/' && newname == 'Shared') { - OC.Notification.show(t('files','Invalid folder name. Usage of \'Shared\' is reserved by ownCloud')); - return false; - } - if (FileList.lastAction) { - FileList.lastAction(); - } - var name = getUniqueName(newname); - if (newname != name) { - FileList.checkName(name, newname, true); - var hidden = true; - } else { - var hidden = false; - } - switch(type){ - case 'file': - $.post( - OC.filePath('files','ajax','newfile.php'), - {dir:$('#dir').val(),filename:name}, - function(result){ - if (result.status == 'success') { - var date=new Date(); - // TODO: ideally addFile should be able to receive - // all attributes and set them automatically, - // and also auto-load the preview - var tr = FileList.addFile(name,0,date,false,hidden); - tr.attr('data-size',result.data.size); - tr.attr('data-mime',result.data.mime); - tr.attr('data-id', result.data.id); - tr.find('.filesize').text(humanFileSize(result.data.size)); - var path = getPathForPreview(name); - lazyLoadPreview(path, result.data.mime, function(previewpath){ - tr.find('td.filename').attr('style','background-image:url('+previewpath+')'); - }); - FileActions.display(tr.find('td.filename'), true); - } else { - OC.dialogs.alert(result.data.message, t('core', 'Error')); + try { + checkInput(); + var newname = input.val(); + if (FileList.lastAction) { + FileList.lastAction(); + } + var name = getUniqueName(newname); + if (newname !== name) { + FileList.checkName(name, newname, true); + var hidden = true; + } else { + var hidden = false; + } + switch(type) { + case 'file': + $.post( + OC.filePath('files', 'ajax', 'newfile.php'), + {dir:$('#dir').val(), filename:name}, + function(result) { + if (result.status === 'success') { + var date = new Date(); + // TODO: ideally addFile should be able to receive + // all attributes and set them automatically, + // and also auto-load the preview + var tr = FileList.addFile(name, 0, date, false, hidden); + tr.attr('data-size', result.data.size); + tr.attr('data-mime', result.data.mime); + tr.attr('data-id', result.data.id); + tr.find('.filesize').text(humanFileSize(result.data.size)); + var path = getPathForPreview(name); + lazyLoadPreview(path, result.data.mime, function(previewpath) { + tr.find('td.filename').attr('style','background-image:url('+previewpath+')'); + }); + FileActions.display(tr.find('td.filename'), true); + } else { + OC.dialogs.alert(result.data.message, t('core', 'Could not create file')); + } } - } - ); - break; - case 'folder': - $.post( - OC.filePath('files','ajax','newfolder.php'), - {dir:$('#dir').val(),foldername:name}, - function(result){ - if (result.status == 'success') { - var date=new Date(); - FileList.addDir(name,0,date,hidden); - var tr=$('tr').filterAttr('data-file',name); - tr.attr('data-id', result.data.id); - } else { - OC.dialogs.alert(result.data.message, t('core', 'Error')); + ); + break; + case 'folder': + $.post( + OC.filePath('files','ajax','newfolder.php'), + {dir:$('#dir').val(), foldername:name}, + function(result) { + if (result.status === 'success') { + var date=new Date(); + FileList.addDir(name, 0, date, hidden); + var tr=$('tr[data-file="'+name+'"]'); + tr.attr('data-id', result.data.id); + } else { + OC.dialogs.alert(result.data.message, t('core', 'Could not create folder')); + } } + ); + break; + case 'web': + if (name.substr(0,8) !== 'https://' && name.substr(0,7) !== 'http://') { + name = 'http://' + name; } - ); - break; - case 'web': - if(name.substr(0,8)!='https://' && name.substr(0,7)!='http://'){ - name='http://'+name; - } - var localName=name; - if(localName.substr(localName.length-1,1)=='/'){//strip / - localName=localName.substr(0,localName.length-1) - } - if(localName.indexOf('/')){//use last part of url - localName=localName.split('/').pop(); - } else { //or the domain - localName=(localName.match(/:\/\/(.[^\/]+)/)[1]).replace('www.',''); - } - localName = getUniqueName(localName); - //IE < 10 does not fire the necessary events for the progress bar. - if($('html.lte9').length === 0) { - $('#uploadprogressbar').progressbar({value:0}); - $('#uploadprogressbar').fadeIn(); - } - - var eventSource=new OC.EventSource(OC.filePath('files','ajax','newfile.php'),{dir:$('#dir').val(),source:name,filename:localName}); - eventSource.listen('progress',function(progress){ + var localName=name; + if (localName.substr(localName.length-1,1)==='/') {//strip / + localName=localName.substr(0,localName.length-1); + } + if (localName.indexOf('/')) {//use last part of url + localName=localName.split('/').pop(); + } else { //or the domain + localName=(localName.match(/:\/\/(.[^\/]+)/)[1]).replace('www.',''); + } + localName = getUniqueName(localName); //IE < 10 does not fire the necessary events for the progress bar. - if($('html.lte9').length === 0) { - $('#uploadprogressbar').progressbar('value',progress); + if ($('html.lte9').length === 0) { + $('#uploadprogressbar').progressbar({value:0}); + $('#uploadprogressbar').fadeIn(); } - }); - eventSource.listen('success',function(data){ - var mime=data.mime; - var size=data.size; - var id=data.id; - $('#uploadprogressbar').fadeOut(); - var date=new Date(); - FileList.addFile(localName,size,date,false,hidden); - var tr=$('tr').filterAttr('data-file',localName); - tr.data('mime',mime).data('id',id); - tr.attr('data-id', id); - var path = $('#dir').val()+'/'+localName; - lazyLoadPreview(path, mime, function(previewpath){ - tr.find('td.filename').attr('style','background-image:url('+previewpath+')'); + + var eventSource=new OC.EventSource(OC.filePath('files','ajax','newfile.php'),{dir:$('#dir').val(),source:name,filename:localName}); + eventSource.listen('progress',function(progress) { + //IE < 10 does not fire the necessary events for the progress bar. + if ($('html.lte9').length === 0) { + $('#uploadprogressbar').progressbar('value',progress); + } }); - }); - eventSource.listen('error',function(error){ - $('#uploadprogressbar').fadeOut(); - alert(error); - }); - break; + eventSource.listen('success',function(data) { + var mime = data.mime; + var size = data.size; + var id = data.id; + $('#uploadprogressbar').fadeOut(); + var date = new Date(); + FileList.addFile(localName, size, date, false, hidden); + var tr = $('tr[data-file="'+localName+'"]'); + tr.data('mime', mime).data('id', id); + tr.attr('data-id', id); + var path = $('#dir').val()+'/'+localName; + lazyLoadPreview(path, mime, function(previewpath) { + tr.find('td.filename').attr('style', 'background-image:url('+previewpath+')'); + }); + FileActions.display(tr.find('td.filename'), true); + }); + eventSource.listen('error',function(error) { + $('#uploadprogressbar').fadeOut(); + alert(error); + }); + break; + } + var li=form.parent(); + form.remove(); + /* workaround for IE 9&10 click event trap, 2 lines: */ + $('input').first().focus(); + $('#content').focus(); + li.append('

'+li.data('text')+'

'); + $('#new>a').click(); + } catch (error) { + input.attr('title', error); + input.tipsy({gravity: 'w', trigger: 'manual'}); + input.tipsy('show'); + input.addClass('error'); } - var li=form.parent(); - form.remove(); - /* workaround for IE 9&10 click event trap, 2 lines: */ - $('input').first().focus(); - $('#content').focus(); - li.append('

'+li.data('text')+'

'); - $('#new>a').click(); }); }); window.file_upload_param = file_upload_param; diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 85bdd50971..c33a06bbdc 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -1,7 +1,7 @@ var FileList={ useUndo:true, - postProcessList: function(){ - $('#fileList tr').each(function(){ + postProcessList: function() { + $('#fileList tr').each(function() { //little hack to set unescape filenames in attribute $(this).attr('data-file',decodeURIComponent($(this).attr('data-file'))); }); @@ -16,13 +16,13 @@ var FileList={ $fileList.trigger(jQuery.Event("fileActionsReady")); FileList.postProcessList(); // "Files" might not be loaded in extending apps - if (window.Files){ + if (window.Files) { Files.setupDragAndDrop(); } FileList.updateFileSummary(); $fileList.trigger(jQuery.Event("updated")); }, - createRow:function(type, name, iconurl, linktarget, size, lastModified, permissions){ + createRow:function(type, name, iconurl, linktarget, size, lastModified, permissions) { var td, simpleSize, basename, extension; //containing tr var tr = $('').attr({ @@ -43,7 +43,7 @@ var FileList={ "href": linktarget }); //split extension from filename for non dirs - if (type != 'dir' && name.indexOf('.')!=-1) { + if (type !== 'dir' && name.indexOf('.') !== -1) { basename=name.substr(0,name.lastIndexOf('.')); extension=name.substr(name.lastIndexOf('.')); } else { @@ -52,11 +52,11 @@ var FileList={ } var name_span=$('').addClass('nametext').text(basename); link_elem.append(name_span); - if(extension){ + if (extension) { name_span.append($('').addClass('extension').text(extension)); } //dirs can show the number of uploaded files - if (type == 'dir') { + if (type === 'dir') { link_elem.append($('').attr({ 'class': 'uploadtext', 'currentUploads': 0 @@ -66,9 +66,9 @@ var FileList={ tr.append(td); //size column - if(size!=t('files', 'Pending')){ + if (size !== t('files', 'Pending')) { simpleSize = humanFileSize(size); - }else{ + } else { simpleSize=t('files', 'Pending'); } var sizeColor = Math.round(160-Math.pow((size/(1024*1024)),2)); @@ -90,7 +90,7 @@ var FileList={ tr.append(td); return tr; }, - addFile:function(name,size,lastModified,loading,hidden,param){ + addFile:function(name, size, lastModified, loading, hidden, param) { var imgurl; if (!param) { @@ -120,9 +120,9 @@ var FileList={ ); FileList.insertElement(name, 'file', tr); - if(loading){ - tr.data('loading',true); - }else{ + if (loading) { + tr.data('loading', true); + } else { tr.find('td.filename').draggable(dragOptions); } if (hidden) { @@ -130,7 +130,7 @@ var FileList={ } return tr; }, - addDir:function(name,size,lastModified,hidden){ + addDir:function(name, size, lastModified, hidden) { var tr = this.createRow( 'dir', @@ -142,7 +142,7 @@ var FileList={ $('#permissions').val() ); - FileList.insertElement(name,'dir',tr); + FileList.insertElement(name, 'dir', tr); var td = tr.find('td.filename'); td.draggable(dragOptions); td.droppable(folderDropOptions); @@ -156,25 +156,26 @@ var FileList={ * @brief Changes the current directory and reload the file list. * @param targetDir target directory (non URL encoded) * @param changeUrl false if the URL must not be changed (defaults to true) + * @param {boolean} force set to true to force changing directory */ - changeDirectory: function(targetDir, changeUrl, force){ + changeDirectory: function(targetDir, changeUrl, force) { var $dir = $('#dir'), url, currentDir = $dir.val() || '/'; targetDir = targetDir || '/'; - if (!force && currentDir === targetDir){ + if (!force && currentDir === targetDir) { return; } FileList.setCurrentDir(targetDir, changeUrl); FileList.reload(); }, - linkTo: function(dir){ + linkTo: function(dir) { return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); }, - setCurrentDir: function(targetDir, changeUrl){ + setCurrentDir: function(targetDir, changeUrl) { $('#dir').val(targetDir); - if (changeUrl !== false){ - if (window.history.pushState && changeUrl !== false){ + if (changeUrl !== false) { + if (window.history.pushState && changeUrl !== false) { url = FileList.linkTo(targetDir); window.history.pushState({dir: targetDir}, '', url); } @@ -187,9 +188,9 @@ var FileList={ /** * @brief Reloads the file list using ajax call */ - reload: function(){ + reload: function() { FileList.showMask(); - if (FileList._reloadCall){ + if (FileList._reloadCall) { FileList._reloadCall.abort(); } FileList._reloadCall = $.ajax({ @@ -198,7 +199,7 @@ var FileList={ dir : $('#dir').val(), breadcrumb: true }, - error: function(result){ + error: function(result) { FileList.reloadCallback(result); }, success: function(result) { @@ -206,7 +207,7 @@ var FileList={ } }); }, - reloadCallback: function(result){ + reloadCallback: function(result) { var $controls = $('#controls'); delete FileList._reloadCall; @@ -217,17 +218,17 @@ var FileList={ return; } - if (result.status === 404){ + if (result.status === 404) { // go back home FileList.changeDirectory('/'); return; } - if (result.data.permissions){ + if (result.data.permissions) { FileList.setDirectoryPermissions(result.data.permissions); } - if(typeof(result.data.breadcrumb) != 'undefined'){ + if (typeof(result.data.breadcrumb) !== 'undefined') { $controls.find('.crumb').remove(); $controls.prepend(result.data.breadcrumb); @@ -236,14 +237,14 @@ var FileList={ Files.resizeBreadcrumbs(width, true); // in case svg is not supported by the browser we need to execute the fallback mechanism - if(!SVGSupport()) { + if (!SVGSupport()) { replaceSVG(); } } FileList.update(result.data.files); }, - setDirectoryPermissions: function(permissions){ + setDirectoryPermissions: function(permissions) { var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; $('#permissions').val(permissions); $('.creatable').toggleClass('hidden', !isCreatable); @@ -278,66 +279,68 @@ var FileList={ $('tr').filterAttr('data-file',name).find('td.filename').draggable('destroy'); $('tr').filterAttr('data-file',name).remove(); FileList.updateFileSummary(); - if($('tr[data-file]').length==0){ + if ( ! $('tr[data-file]').exists() ) { $('#emptycontent').removeClass('hidden'); $('#filescontent th').addClass('hidden'); } }, - insertElement:function(name,type,element){ + insertElement:function(name, type, element) { //find the correct spot to insert the file or folder var pos, fileElements=$('tr[data-file][data-type="'+type+'"]:visible'); - if(name.localeCompare($(fileElements[0]).attr('data-file'))<0){ - pos=-1; - }else if(name.localeCompare($(fileElements[fileElements.length-1]).attr('data-file'))>0){ - pos=fileElements.length-1; - }else{ - for(pos=0;pos0 && name.localeCompare($(fileElements[pos+1]).attr('data-file'))<0){ + if (name.localeCompare($(fileElements[0]).attr('data-file')) < 0) { + pos = -1; + } else if (name.localeCompare($(fileElements[fileElements.length-1]).attr('data-file')) > 0) { + pos = fileElements.length - 1; + } else { + for(pos = 0; pos 0 + && name.localeCompare($(fileElements[pos+1]).attr('data-file')) < 0) + { break; } } } - if(fileElements.length){ - if(pos==-1){ + if (fileElements.exists()) { + if (pos === -1) { $(fileElements[0]).before(element); - }else{ + } else { $(fileElements[pos]).after(element); } - }else if(type=='dir' && $('tr[data-file]').length>0){ + } else if (type === 'dir' && $('tr[data-file]').exists()) { $('tr[data-file]').first().before(element); - } else if(type=='file' && $('tr[data-file]').length>0) { + } else if (type === 'file' && $('tr[data-file]').exists()) { $('tr[data-file]').last().before(element); - }else{ + } else { $('#fileList').append(element); } $('#emptycontent').addClass('hidden'); $('#filestable th').removeClass('hidden'); FileList.updateFileSummary(); }, - loadingDone:function(name, id){ - var mime, tr=$('tr').filterAttr('data-file',name); - tr.data('loading',false); - mime=tr.data('mime'); - tr.attr('data-mime',mime); - if (id != null) { + loadingDone:function(name, id) { + var mime, tr = $('tr[data-file="'+name+'"]'); + tr.data('loading', false); + mime = tr.data('mime'); + tr.attr('data-mime', mime); + if (id) { tr.attr('data-id', id); } var path = getPathForPreview(name); - lazyLoadPreview(path, mime, function(previewpath){ + lazyLoadPreview(path, mime, function(previewpath) { tr.find('td.filename').attr('style','background-image:url('+previewpath+')'); }); tr.find('td.filename').draggable(dragOptions); }, - isLoading:function(name){ - return $('tr').filterAttr('data-file',name).data('loading'); + isLoading:function(name) { + return $('tr[data-file="'+name+'"]').data('loading'); }, - rename:function(name){ + rename:function(oldname) { var tr, td, input, form; - tr=$('tr').filterAttr('data-file',name); + tr = $('tr[data-file="'+oldname+'"]'); tr.data('renaming',true); - td=tr.children('td.filename'); - input=$('').val(name); - form=$('
'); + td = tr.children('td.filename'); + input = $('').val(oldname); + form = $('
'); form.append(input); td.children('a.name').hide(); td.append(form); @@ -347,18 +350,29 @@ var FileList={ if (len === -1) { len = input.val().length; } - input.selectRange(0,len); + input.selectRange(0, len); - form.submit(function(event){ + var checkInput = function () { + var filename = input.val(); + if (filename !== oldname) { + if (!Files.isFileNameValid(filename)) { + // Files.isFileNameValid(filename) throws an exception itself + } else if($('#dir').val() === '/' && filename === 'Shared') { + throw t('files','In the home folder \'Shared\' is a reserved filename'); + } else if (FileList.inList(filename)) { + throw t('files', '{new_name} already exists', {new_name: filename}); + } + } + return true; + }; + + form.submit(function(event) { event.stopPropagation(); event.preventDefault(); - var newname=input.val(); - if (!Files.isFileNameValid(newname)) { - return false; - } else if (newname != name) { - if (FileList.checkName(name, newname, false)) { - newname = name; - } else { + try { + var newname = input.val(); + if (newname !== oldname) { + checkInput(); // save background image, because it's replaced by a spinner while async request var oldBackgroundImage = td.css('background-image'); // mark as loading @@ -368,16 +382,16 @@ var FileList={ data: { dir : $('#dir').val(), newname: newname, - file: name + file: oldname }, success: function(result) { if (!result || result.status === 'error') { - OC.Notification.show(result.data.message); - newname = name; + OC.dialogs.alert(result.data.message, t('core', 'Could not rename file')); // revert changes + newname = oldname; tr.attr('data-file', newname); var path = td.children('a.name').attr('href'); - td.children('a.name').attr('href', path.replace(encodeURIComponent(name), encodeURIComponent(newname))); + td.children('a.name').attr('href', path.replace(encodeURIComponent(oldname), encodeURIComponent(newname))); if (newname.indexOf('.') > 0 && tr.data('type') !== 'dir') { var basename=newname.substr(0,newname.lastIndexOf('.')); } else { @@ -385,7 +399,7 @@ var FileList={ } td.find('a.name span.nametext').text(basename); if (newname.indexOf('.') > 0 && tr.data('type') !== 'dir') { - if (td.find('a.name span.extension').length === 0 ) { + if ( ! td.find('a.name span.extension').exists() ) { td.find('a.name span.nametext').append(''); } td.find('a.name span.extension').text(newname.substr(newname.lastIndexOf('.'))); @@ -393,70 +407,76 @@ var FileList={ tr.find('.fileactions').effect('highlight', {}, 5000); tr.effect('highlight', {}, 5000); } + // reinsert row + tr.detach(); + FileList.insertElement( tr.attr('data-file'), tr.attr('data-type'),tr ); // remove loading mark and recover old image td.css('background-image', oldBackgroundImage); } }); } - } - tr.data('renaming',false); - tr.attr('data-file', newname); - var path = td.children('a.name').attr('href'); - td.children('a.name').attr('href', path.replace(encodeURIComponent(name), encodeURIComponent(newname))); - if (newname.indexOf('.') > 0 && tr.data('type') != 'dir') { - var basename=newname.substr(0,newname.lastIndexOf('.')); - } else { - var basename=newname; - } - td.find('a.name span.nametext').text(basename); - if (newname.indexOf('.') > 0 && tr.data('type') != 'dir') { - if (td.find('a.name span.extension').length == 0 ) { - td.find('a.name span.nametext').append(''); + input.tipsy('hide'); + tr.data('renaming',false); + tr.attr('data-file', newname); + var path = td.children('a.name').attr('href'); + // FIXME this will fail if the path contains the filename. + td.children('a.name').attr('href', path.replace(encodeURIComponent(oldname), encodeURIComponent(newname))); + var basename = newname; + if (newname.indexOf('.') > 0 && tr.data('type') !== 'dir') { + basename = newname.substr(0, newname.lastIndexOf('.')); + } + td.find('a.name span.nametext').text(basename); + if (newname.indexOf('.') > 0 && tr.data('type') !== 'dir') { + if ( ! td.find('a.name span.extension').exists() ) { + td.find('a.name span.nametext').append(''); + } + td.find('a.name span.extension').text(newname.substr(newname.lastIndexOf('.'))); } - td.find('a.name span.extension').text(newname.substr(newname.lastIndexOf('.'))); + form.remove(); + td.children('a.name').show(); + } catch (error) { + input.attr('title', error); + input.tipsy({gravity: 'w', trigger: 'manual'}); + input.tipsy('show'); + input.addClass('error'); } - form.remove(); - td.children('a.name').show(); return false; }); - input.keyup(function(event){ - if (event.keyCode == 27) { + input.keyup(function(event) { + // verify filename on typing + try { + checkInput(); + input.tipsy('hide'); + input.removeClass('error'); + } catch (error) { + input.attr('title', error); + input.tipsy({gravity: 'w', trigger: 'manual'}); + input.tipsy('show'); + input.addClass('error'); + } + if (event.keyCode === 27) { + input.tipsy('hide'); tr.data('renaming',false); form.remove(); td.children('a.name').show(); } }); - input.click(function(event){ + input.click(function(event) { event.stopPropagation(); event.preventDefault(); }); - input.blur(function(){ + input.blur(function() { form.trigger('submit'); }); }, - checkName:function(oldName, newName, isNewFile) { - if (isNewFile || $('tr').filterAttr('data-file', newName).length > 0) { - var html; - if(isNewFile){ - html = t('files', '{new_name} already exists', {new_name: escapeHTML(newName)})+''+t('files', 'replace')+''+t('files', 'suggest name')+' '+t('files', 'cancel')+''; - }else{ - html = t('files', '{new_name} already exists', {new_name: escapeHTML(newName)})+''+t('files', 'replace')+''+t('files', 'cancel')+''; - } - html = $('' + html + ''); - html.attr('data-oldName', oldName); - html.attr('data-newName', newName); - html.attr('data-isNewFile', isNewFile); - OC.Notification.showHtml(html); - return true; - } else { - return false; - } + inList:function(filename) { + return $('#fileList tr[data-file="'+filename+'"]').length; }, replace:function(oldName, newName, isNewFile) { // Finish any existing actions - $('tr').filterAttr('data-file', oldName).hide(); - $('tr').filterAttr('data-file', newName).hide(); - var tr = $('tr').filterAttr('data-file', oldName).clone(); + $('tr[data-file="'+oldName+'"]').hide(); + $('tr[data-file="'+newName+'"]').hide(); + var tr = $('tr[data-file="'+oldName+'"]').clone(); tr.attr('data-replace', 'true'); tr.attr('data-file', newName); var td = tr.children('td.filename'); @@ -485,14 +505,14 @@ var FileList={ FileList.finishReplace(); }; if (!isNewFile) { - OC.Notification.showHtml(t('files', 'replaced {new_name} with {old_name}', {new_name: newName}, {old_name: oldName})+''+t('files', 'undo')+''); + OC.Notification.showHtml(t('files', 'replaced {new_name} with {old_name}', {new_name: newName}, {old_name: oldName})+''+t('files', 'undo')+''); } }, finishReplace:function() { if (!FileList.replaceCanceled && FileList.replaceOldName && FileList.replaceNewName) { $.ajax({url: OC.filePath('files', 'ajax', 'rename.php'), async: false, data: { dir: $('#dir').val(), newname: FileList.replaceNewName, file: FileList.replaceOldName }, success: function(result) { - if (result && result.status == 'success') { - $('tr').filterAttr('data-replace', 'true').removeAttr('data-replace'); + if (result && result.status === 'success') { + $('tr[data-replace="true"').removeAttr('data-replace'); } else { OC.dialogs.alert(result.data.message, 'Error moving file'); } @@ -503,12 +523,12 @@ var FileList={ }}); } }, - do_delete:function(files){ - if(files.substr){ + do_delete:function(files) { + if (files.substr) { files=[files]; } for (var i=0; i 0 ) { + if ( $('#fileList tr').exists() ) { var totalDirs = 0; var totalFiles = 0; var totalSize = 0; @@ -562,7 +582,7 @@ var FileList={ var infoVars = { dirs: ''+directoryInfo+'', files: ''+fileInfo+'' - } + }; var info = t('files', '{dirs} and {files}', infoVars); @@ -644,17 +664,17 @@ var FileList={ } } }, - updateEmptyContent: function(){ + updateEmptyContent: function() { var $fileList = $('#fileList'); var permissions = $('#permissions').val(); var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0; - $('#emptycontent').toggleClass('hidden', !isCreatable || $fileList.find('tr').length > 0); - $('#filestable th').toggleClass('hidden', $fileList.find('tr').length === 0); + $('#emptycontent').toggleClass('hidden', !isCreatable || $fileList.find('tr').exists()); + $('#filestable th').toggleClass('hidden', $fileList.find('tr').exists() === false); }, - showMask: function(){ + showMask: function() { // in case one was shown before var $mask = $('#content .mask'); - if ($mask.length){ + if ($mask.exists()) { return; } @@ -665,31 +685,31 @@ var FileList={ $('#content').append($mask); // block UI, but only make visible in case loading takes longer - FileList._maskTimeout = window.setTimeout(function(){ + FileList._maskTimeout = window.setTimeout(function() { // reset opacity $mask.removeClass('transparent'); }, 250); }, - hideMask: function(){ + hideMask: function() { var $mask = $('#content .mask').remove(); - if (FileList._maskTimeout){ + if (FileList._maskTimeout) { window.clearTimeout(FileList._maskTimeout); } }, scrollTo:function(file) { //scroll to and highlight preselected file - var scrolltorow = $('tr[data-file="'+file+'"]'); - if (scrolltorow.length > 0) { - scrolltorow.addClass('searchresult'); - $(window).scrollTop(scrolltorow.position().top); + var $scrolltorow = $('tr[data-file="'+file+'"]'); + if ($scrolltorow.exists()) { + $scrolltorow.addClass('searchresult'); + $(window).scrollTop($scrolltorow.position().top); //remove highlight when hovered over - scrolltorow.one('hover', function(){ - scrolltorow.removeClass('searchresult'); + $scrolltorow.one('hover', function() { + $scrolltorow.removeClass('searchresult'); }); } }, - filter:function(query){ - $('#fileList tr:not(.summary)').each(function(i,e){ + filter:function(query) { + $('#fileList tr:not(.summary)').each(function(i,e) { if ($(e).data('file').toLowerCase().indexOf(query.toLowerCase()) !== -1) { $(e).addClass("searchresult"); } else { @@ -698,18 +718,18 @@ var FileList={ }); //do not use scrollto to prevent removing searchresult css class var first = $('#fileList tr.searchresult').first(); - if (first.length !== 0) { + if (first.exists()) { $(window).scrollTop(first.position().top); } }, - unfilter:function(){ - $('#fileList tr.searchresult').each(function(i,e){ + unfilter:function() { + $('#fileList tr.searchresult').each(function(i,e) { $(e).removeClass("searchresult"); }); } }; -$(document).ready(function(){ +$(document).ready(function() { var isPublic = !!$('#isPublic').val(); // handle upload events @@ -719,16 +739,16 @@ $(document).ready(function(){ OC.Upload.log('filelist handle fileuploaddrop', e, data); var dropTarget = $(e.originalEvent.target).closest('tr, .crumb'); - if(dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder + if (dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder // remember as context data.context = dropTarget; var dir = dropTarget.data('file'); // if from file list, need to prepend parent dir - if (dir){ + if (dir) { var parentDir = $('#dir').val() || '/'; - if (parentDir[parentDir.length - 1] != '/'){ + if (parentDir[parentDir.length - 1] !== '/') { parentDir += '/'; } dir = parentDir + dir; @@ -752,12 +772,12 @@ $(document).ready(function(){ OC.Upload.log('filelist handle fileuploadadd', e, data); //finish delete if we are uploading a deleted file - if(FileList.deleteFiles && FileList.deleteFiles.indexOf(data.files[0].name)!==-1){ + if (FileList.deleteFiles && FileList.deleteFiles.indexOf(data.files[0].name)!==-1) { FileList.finishDelete(null, true); //delete file before continuing } // add ui visualization to existing folder - if(data.context && data.context.data('type') === 'dir') { + if (data.context && data.context.data('type') === 'dir') { // add to existing folder // update upload counter ui @@ -767,7 +787,7 @@ $(document).ready(function(){ uploadtext.attr('currentUploads', currentUploads); var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads); - if(currentUploads === 1) { + if (currentUploads === 1) { var img = OC.imagePath('core', 'loading.gif'); data.context.find('td.filename').attr('style','background-image:url('+img+')'); uploadtext.text(translatedText); @@ -794,7 +814,7 @@ $(document).ready(function(){ } var result=$.parseJSON(response); - if(typeof result[0] !== 'undefined' && result[0].status === 'success') { + if (typeof result[0] !== 'undefined' && result[0].status === 'success') { var file = result[0]; if (data.context && data.context.data('type') === 'dir') { @@ -805,7 +825,7 @@ $(document).ready(function(){ currentUploads -= 1; uploadtext.attr('currentUploads', currentUploads); var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads); - if(currentUploads === 0) { + if (currentUploads === 0) { var img = OC.imagePath('core', 'filetypes/folder.png'); data.context.find('td.filename').attr('style','background-image:url('+img+')'); uploadtext.text(translatedText); @@ -822,18 +842,18 @@ $(document).ready(function(){ } else { // only append new file if dragged onto current dir's crumb (last) - if (data.context && data.context.hasClass('crumb') && !data.context.hasClass('last')){ + if (data.context && data.context.hasClass('crumb') && !data.context.hasClass('last')) { return; } // add as stand-alone row to filelist var size=t('files', 'Pending'); - if (data.files[0].size>=0){ + if (data.files[0].size>=0) { size=data.files[0].size; } var date=new Date(); var param = {}; - if ($('#publicUploadRequestToken').length) { + if ($('#publicUploadRequestToken').exists()) { param.download_url = document.location.href + '&download&path=/' + $('#dir').val() + '/' + file.name; } //should the file exist in the list remove it @@ -846,14 +866,14 @@ $(document).ready(function(){ data.context.attr('data-mime',file.mime).attr('data-id',file.id); var permissions = data.context.data('permissions'); - if(permissions !== file.permissions) { + if (permissions !== file.permissions) { data.context.attr('data-permissions', file.permissions); data.context.data('permissions', file.permissions); } FileActions.display(data.context.find('td.filename'), true); var path = getPathForPreview(file.name); - lazyLoadPreview(path, file.mime, function(previewpath){ + lazyLoadPreview(path, file.mime, function(previewpath) { data.context.find('td.filename').attr('style','background-image:url('+previewpath+')'); }); } @@ -887,10 +907,10 @@ $(document).ready(function(){ }); $('#notification').hide(); - $('#notification').on('click', '.undo', function(){ + $('#notification').on('click', '.undo', function() { if (FileList.deleteFiles) { - $.each(FileList.deleteFiles,function(index,file){ - $('tr').filterAttr('data-file',file).show(); + $.each(FileList.deleteFiles,function(index,file) { + $('tr[data-file="'+file+'"]').show(); }); FileList.deleteCanceled=true; FileList.deleteFiles=null; @@ -900,10 +920,10 @@ $(document).ready(function(){ FileList.deleteCanceled = false; FileList.deleteFiles = [FileList.replaceOldName]; } else { - $('tr').filterAttr('data-file', FileList.replaceOldName).show(); + $('tr[data-file="'+FileList.replaceOldName+'"]').show(); } - $('tr').filterAttr('data-replace', 'true').remove(); - $('tr').filterAttr('data-file', FileList.replaceNewName).show(); + $('tr[data-replace="true"').remove(); + $('tr[data-file="'+FileList.replaceNewName+'"]').show(); FileList.replaceCanceled = true; FileList.replaceOldName = null; FileList.replaceNewName = null; @@ -918,7 +938,7 @@ $(document).ready(function(){ }); }); $('#notification:first-child').on('click', '.suggest', function() { - $('tr').filterAttr('data-file', $('#notification > span').attr('data-oldName')).show(); + $('tr[data-file="'+$('#notification > span').attr('data-oldName')+'"]').show(); OC.Notification.hide(); }); $('#notification:first-child').on('click', '.cancel', function() { @@ -928,67 +948,67 @@ $(document).ready(function(){ } }); FileList.useUndo=(window.onbeforeunload)?true:false; - $(window).bind('beforeunload', function (){ + $(window).bind('beforeunload', function () { if (FileList.lastAction) { FileList.lastAction(); } }); - $(window).unload(function (){ + $(window).unload(function () { $(window).trigger('beforeunload'); }); - function decodeQuery(query){ + function decodeQuery(query) { return query.replace(/\+/g, ' '); } - function parseHashQuery(){ + function parseHashQuery() { var hash = window.location.hash, pos = hash.indexOf('?'), query; - if (pos >= 0){ + if (pos >= 0) { return hash.substr(pos + 1); } return ''; } - function parseCurrentDirFromUrl(){ + function parseCurrentDirFromUrl() { var query = parseHashQuery(), params, dir = '/'; // try and parse from URL hash first - if (query){ + if (query) { params = OC.parseQueryString(decodeQuery(query)); } // else read from query attributes - if (!params){ + if (!params) { params = OC.parseQueryString(decodeQuery(location.search)); } return (params && params.dir) || '/'; } // disable ajax/history API for public app (TODO: until it gets ported) - if (!isPublic){ + if (!isPublic) { // fallback to hashchange when no history support - if (!window.history.pushState){ - $(window).on('hashchange', function(){ + if (!window.history.pushState) { + $(window).on('hashchange', function() { FileList.changeDirectory(parseCurrentDirFromUrl(), false); }); } - window.onpopstate = function(e){ + window.onpopstate = function(e) { var targetDir; - if (e.state && e.state.dir){ + if (e.state && e.state.dir) { targetDir = e.state.dir; } else{ // read from URL targetDir = parseCurrentDirFromUrl(); } - if (targetDir){ + if (targetDir) { FileList.changeDirectory(targetDir, false); } - } + }; - if (parseInt($('#ajaxLoad').val(), 10) === 1){ + if (parseInt($('#ajaxLoad').val(), 10) === 1) { // need to initially switch the dir to the one from the hash (IE8) FileList.changeDirectory(parseCurrentDirFromUrl(), false, true); } diff --git a/apps/files/js/files.js b/apps/files/js/files.js index ec2dc7c62e..389bf1bf19 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -1,18 +1,18 @@ Files={ updateMaxUploadFilesize:function(response) { - if(response == undefined) { + if (response === undefined) { return; } - if(response.data !== undefined && response.data.uploadMaxFilesize !== undefined) { + if (response.data !== undefined && response.data.uploadMaxFilesize !== undefined) { $('#max_upload').val(response.data.uploadMaxFilesize); $('#upload.button').attr('original-title', response.data.maxHumanFilesize); $('#usedSpacePercent').val(response.data.usedSpacePercent); Files.displayStorageWarnings(); } - if(response[0] == undefined) { + if (response[0] === undefined) { return; } - if(response[0].uploadMaxFilesize !== undefined) { + if (response[0].uploadMaxFilesize !== undefined) { $('#max_upload').val(response[0].uploadMaxFilesize); $('#upload.button').attr('original-title', response[0].maxHumanFilesize); $('#usedSpacePercent').val(response[0].usedSpacePercent); @@ -22,23 +22,18 @@ Files={ }, isFileNameValid:function (name) { if (name === '.') { - OC.Notification.show(t('files', '\'.\' is an invalid file name.')); - return false; - } - if (name.length == 0) { - OC.Notification.show(t('files', 'File name cannot be empty.')); - return false; + throw t('files', '\'.\' is an invalid file name.'); + } else if (name.length === 0) { + throw t('files', 'File name cannot be empty.'); } // check for invalid characters var invalid_characters = ['\\', '/', '<', '>', ':', '"', '|', '?', '*']; for (var i = 0; i < invalid_characters.length; i++) { - if (name.indexOf(invalid_characters[i]) != -1) { - OC.Notification.show(t('files', "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed.")); - return false; + if (name.indexOf(invalid_characters[i]) !== -1) { + throw t('files', "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed."); } } - OC.Notification.hide(); return true; }, displayStorageWarnings: function() { @@ -78,18 +73,18 @@ Files={ } }, - setupDragAndDrop: function(){ + setupDragAndDrop: function() { var $fileList = $('#fileList'); //drag/drop of files - $fileList.find('tr td.filename').each(function(i,e){ + $fileList.find('tr td.filename').each(function(i,e) { if ($(e).parent().data('permissions') & OC.PERMISSION_DELETE) { $(e).draggable(dragOptions); } }); - $fileList.find('tr[data-type="dir"] td.filename').each(function(i,e){ - if ($(e).parent().data('permissions') & OC.PERMISSION_CREATE){ + $fileList.find('tr[data-type="dir"] td.filename').each(function(i,e) { + if ($(e).parent().data('permissions') & OC.PERMISSION_CREATE) { $(e).droppable(folderDropOptions); } }); @@ -127,9 +122,9 @@ Files={ }, resizeBreadcrumbs: function (width, firstRun) { - if (width != Files.lastWidth) { + if (width !== Files.lastWidth) { if ((width < Files.lastWidth || firstRun) && width < Files.breadcrumbsWidth) { - if (Files.hiddenBreadcrumbs == 0) { + if (Files.hiddenBreadcrumbs === 0) { Files.breadcrumbsWidth -= $(Files.breadcrumbs[1]).get(0).offsetWidth; $(Files.breadcrumbs[1]).find('a').hide(); $(Files.breadcrumbs[1]).append('...'); @@ -141,12 +136,12 @@ Files={ Files.breadcrumbsWidth -= $(Files.breadcrumbs[i]).get(0).offsetWidth; $(Files.breadcrumbs[i]).hide(); Files.hiddenBreadcrumbs = i; - i++ + i++; } } else if (width > Files.lastWidth && Files.hiddenBreadcrumbs > 0) { var i = Files.hiddenBreadcrumbs; while (width > Files.breadcrumbsWidth && i > 0) { - if (Files.hiddenBreadcrumbs == 1) { + if (Files.hiddenBreadcrumbs === 1) { Files.breadcrumbsWidth -= $(Files.breadcrumbs[1]).get(0).offsetWidth; $(Files.breadcrumbs[1]).find('span').remove(); $(Files.breadcrumbs[1]).find('a').show(); @@ -170,7 +165,7 @@ Files={ }; $(document).ready(function() { // FIXME: workaround for trashbin app - if (window.trashBinApp){ + if (window.trashBinApp) { return; } Files.displayEncryptionWarning(); @@ -215,7 +210,7 @@ $(document).ready(function() { var rows = $(this).parent().parent().parent().children('tr'); for (var i = start; i < end; i++) { $(rows).each(function(index) { - if (index == i) { + if (index === i) { var checkbox = $(this).children().children('input:checkbox'); $(checkbox).attr('checked', 'checked'); $(checkbox).parent().parent().addClass('selected'); @@ -232,23 +227,23 @@ $(document).ready(function() { } else { $(checkbox).attr('checked', 'checked'); $(checkbox).parent().parent().toggleClass('selected'); - var selectedCount=$('td.filename input:checkbox:checked').length; - if (selectedCount == $('td.filename input:checkbox').length) { + var selectedCount = $('td.filename input:checkbox:checked').length; + if (selectedCount === $('td.filename input:checkbox').length) { $('#select_all').attr('checked', 'checked'); } } procesSelection(); } else { var filename=$(this).parent().parent().attr('data-file'); - var tr=$('tr').filterAttr('data-file',filename); + var tr=$('tr[data-file="'+filename+'"]'); var renaming=tr.data('renaming'); - if(!renaming && !FileList.isLoading(filename)){ + if (!renaming && !FileList.isLoading(filename)) { FileActions.currentFile = $(this).parent(); var mime=FileActions.getCurrentMimeType(); var type=FileActions.getCurrentType(); var permissions = FileActions.getCurrentPermissions(); var action=FileActions.getDefault(mime,type, permissions); - if(action){ + if (action) { event.preventDefault(); action(filename); } @@ -259,11 +254,11 @@ $(document).ready(function() { // Sets the select_all checkbox behaviour : $('#select_all').click(function() { - if($(this).attr('checked')){ + if ($(this).attr('checked')) { // Check all $('td.filename input:checkbox').attr('checked', true); $('td.filename input:checkbox').parent().parent().addClass('selected'); - }else{ + } else { // Uncheck all $('td.filename input:checkbox').attr('checked', false); $('td.filename input:checkbox').parent().parent().removeClass('selected'); @@ -280,7 +275,7 @@ $(document).ready(function() { var rows = $(this).parent().parent().parent().children('tr'); for (var i = start; i < end; i++) { $(rows).each(function(index) { - if (index == i) { + if (index === i) { var checkbox = $(this).children().children('input:checkbox'); $(checkbox).attr('checked', 'checked'); $(checkbox).parent().parent().addClass('selected'); @@ -290,10 +285,10 @@ $(document).ready(function() { } var selectedCount=$('td.filename input:checkbox:checked').length; $(this).parent().parent().toggleClass('selected'); - if(!$(this).attr('checked')){ + if (!$(this).attr('checked')) { $('#select_all').attr('checked',false); - }else{ - if(selectedCount==$('td.filename input:checkbox').length){ + } else { + if (selectedCount===$('td.filename input:checkbox').length) { $('#select_all').attr('checked',true); } } @@ -306,10 +301,11 @@ $(document).ready(function() { var dir=$('#dir').val()||'/'; OC.Notification.show(t('files','Your download is being prepared. This might take some time if the files are big.')); // use special download URL if provided, e.g. for public shared files - if ( (downloadURL = document.getElementById("downloadURL")) ) { - window.location=downloadURL.value+"&download&files="+encodeURIComponent(fileslist); + var downloadURL = document.getElementById("downloadURL"); + if ( downloadURL ) { + window.location = downloadURL.value+"&download&files=" + encodeURIComponent(fileslist); } else { - window.location=OC.filePath('files', 'ajax', 'download.php') + '?'+ $.param({ dir: dir, files: fileslist }); + window.location = OC.filePath('files', 'ajax', 'download.php') + '?'+ $.param({ dir: dir, files: fileslist }); } return false; }); @@ -376,12 +372,12 @@ $(document).ready(function() { } }); -function scanFiles(force, dir, users){ +function scanFiles(force, dir, users) { if (!OC.currentUser) { return; } - if(!dir){ + if (!dir) { dir = ''; } force = !!force; //cast to bool @@ -399,17 +395,17 @@ function scanFiles(force, dir, users){ scannerEventSource = new OC.EventSource(OC.filePath('files','ajax','scan.php'),{force: force,dir: dir}); } scanFiles.cancel = scannerEventSource.close.bind(scannerEventSource); - scannerEventSource.listen('count',function(count){ - console.log(count + ' files scanned') + scannerEventSource.listen('count',function(count) { + console.log(count + ' files scanned'); }); - scannerEventSource.listen('folder',function(path){ - console.log('now scanning ' + path) + scannerEventSource.listen('folder',function(path) { + console.log('now scanning ' + path); }); - scannerEventSource.listen('done',function(count){ + scannerEventSource.listen('done',function(count) { scanFiles.scanning=false; console.log('done after ' + count + ' files'); }); - scannerEventSource.listen('user',function(user){ + scannerEventSource.listen('user',function(user) { console.log('scanning files for ' + user); }); } @@ -418,14 +414,14 @@ scanFiles.scanning=false; function boolOperationFinished(data, callback) { result = jQuery.parseJSON(data.responseText); Files.updateMaxUploadFilesize(result); - if(result.status == 'success'){ + if (result.status === 'success') { callback.call(); } else { alert(result.data.message); } } -var createDragShadow = function(event){ +var createDragShadow = function(event) { //select dragged file var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked'); if (!isDragSelected) { @@ -435,7 +431,7 @@ var createDragShadow = function(event){ var selectedFiles = getSelectedFilesTrash(); - if (!isDragSelected && selectedFiles.length == 1) { + if (!isDragSelected && selectedFiles.length === 1) { //revert the selection $(event.target).parents('tr').find('td input:first').prop('checked',false); } @@ -452,7 +448,7 @@ var createDragShadow = function(event){ var dir=$('#dir').val(); - $(selectedFiles).each(function(i,elem){ + $(selectedFiles).each(function(i,elem) { var newtr = $('').attr('data-dir', dir).attr('data-filename', elem.name); newtr.append($('').addClass('filename').text(elem.name)); newtr.append($('').addClass('size').text(humanFileSize(elem.size))); @@ -461,14 +457,14 @@ var createDragShadow = function(event){ newtr.find('td.filename').attr('style','background-image:url('+OC.imagePath('core', 'filetypes/folder.png')+')'); } else { var path = getPathForPreview(elem.name); - lazyLoadPreview(path, elem.mime, function(previewpath){ + lazyLoadPreview(path, elem.mime, function(previewpath) { newtr.find('td.filename').attr('style','background-image:url('+previewpath+')'); }); } }); return dragshadow; -} +}; //options for file drag/drop var dragOptions={ @@ -478,7 +474,7 @@ var dragOptions={ stop: function(event, ui) { $('#fileList tr td.filename').addClass('ui-draggable'); } -} +}; // sane browsers support using the distance option if ( $('html.ie').length === 0) { dragOptions['distance'] = 20; @@ -491,20 +487,20 @@ var folderDropOptions={ return false; } - var target=$.trim($(this).find('.nametext').text()); + var target = $.trim($(this).find('.nametext').text()); var files = ui.helper.find('tr'); - $(files).each(function(i,row){ + $(files).each(function(i,row) { var dir = $(row).data('dir'); var file = $(row).data('filename'); $.post(OC.filePath('files', 'ajax', 'move.php'), { dir: dir, file: file, target: dir+'/'+target }, function(result) { if (result) { if (result.status === 'success') { //recalculate folder size - var oldSize = $('#fileList tr').filterAttr('data-file',target).data('size'); - var newSize = oldSize + $('#fileList tr').filterAttr('data-file',file).data('size'); - $('#fileList tr').filterAttr('data-file',target).data('size', newSize); - $('#fileList tr').filterAttr('data-file',target).find('td.filesize').text(humanFileSize(newSize)); + var oldSize = $('#fileList tr[data-file="'+target+'"]').data('size'); + var newSize = oldSize + $('#fileList tr[data-file="'+file+'"]').data('size'); + $('#fileList tr[data-file="'+target+'"]').data('size', newSize); + $('#fileList tr[data-file="'+target+'"]').find('td.filesize').text(humanFileSize(newSize)); FileList.remove(file); procesSelection(); @@ -521,24 +517,24 @@ var folderDropOptions={ }); }, tolerance: 'pointer' -} +}; var crumbDropOptions={ drop: function( event, ui ) { var target=$(this).data('dir'); - var dir=$('#dir').val(); - while(dir.substr(0,1)=='/'){//remove extra leading /'s + var dir = $('#dir').val(); + while(dir.substr(0,1) === '/') {//remove extra leading /'s dir=dir.substr(1); } - dir='/'+dir; - if(dir.substr(-1,1)!='/'){ - dir=dir+'/'; + dir = '/' + dir; + if (dir.substr(-1,1) !== '/') { + dir = dir + '/'; } - if(target==dir || target+'/'==dir){ + if (target === dir || target+'/' === dir) { return; } var files = ui.helper.find('tr'); - $(files).each(function(i,row){ + $(files).each(function(i,row) { var dir = $(row).data('dir'); var file = $(row).data('filename'); $.post(OC.filePath('files', 'ajax', 'move.php'), { dir: dir, file: file, target: target }, function(result) { @@ -559,13 +555,17 @@ var crumbDropOptions={ }); }, tolerance: 'pointer' -} +}; -function procesSelection(){ - var selected=getSelectedFilesTrash(); - var selectedFiles=selected.filter(function(el){return el.type=='file'}); - var selectedFolders=selected.filter(function(el){return el.type=='dir'}); - if(selectedFiles.length==0 && selectedFolders.length==0) { +function procesSelection() { + var selected = getSelectedFilesTrash(); + var selectedFiles = selected.filter(function(el) { + return el.type==='file'; + }); + var selectedFolders = selected.filter(function(el) { + return el.type==='dir'; + }); + if (selectedFiles.length === 0 && selectedFolders.length === 0) { $('#headerName>span.name').text(t('files','Name')); $('#headerSize').text(t('files','Size')); $('#modified').text(t('files','Modified')); @@ -574,22 +574,22 @@ function procesSelection(){ } else { $('.selectedActions').show(); - var totalSize=0; - for(var i=0;i0){ + var selection = ''; + if (selectedFolders.length > 0) { selection += n('files', '%n folder', '%n folders', selectedFolders.length); - if(selectedFiles.length>0){ - selection+=' & '; + if (selectedFiles.length > 0) { + selection += ' & '; } } - if(selectedFiles.length>0){ + if (selectedFiles.length>0) { selection += n('files', '%n file', '%n files', selectedFiles.length); } $('#headerName>span.name').text(selection); @@ -600,37 +600,37 @@ function procesSelection(){ /** * @brief get a list of selected files - * @param string property (option) the property of the file requested - * @return array + * @param {string} property (option) the property of the file requested + * @return {array} * * possible values for property: name, mime, size and type * if property is set, an array with that property for each file is returnd * if it's ommited an array of objects with all properties is returned */ -function getSelectedFilesTrash(property){ +function getSelectedFilesTrash(property) { var elements=$('td.filename input:checkbox:checked').parent().parent(); var files=[]; - elements.each(function(i,element){ + elements.each(function(i,element) { var file={ name:$(element).attr('data-file'), mime:$(element).data('mime'), type:$(element).data('type'), size:$(element).data('size') }; - if(property){ + if (property) { files.push(file[property]); - }else{ + } else { files.push(file); } }); return files; } -function getMimeIcon(mime, ready){ - if(getMimeIcon.cache[mime]){ +function getMimeIcon(mime, ready) { + if (getMimeIcon.cache[mime]) { ready(getMimeIcon.cache[mime]); - }else{ - $.get( OC.filePath('files','ajax','mimeicon.php'), {mime: mime}, function(path){ + } else { + $.get( OC.filePath('files','ajax','mimeicon.php'), {mime: mime}, function(path) { getMimeIcon.cache[mime]=path; ready(getMimeIcon.cache[mime]); }); @@ -655,7 +655,7 @@ function lazyLoadPreview(path, mime, ready, width, height) { if ( ! height ) { height = $('#filestable').data('preview-y'); } - if( $('#publicUploadButtonMock').length ) { + if ( $('#publicUploadButtonMock').length ) { var previewURL = OC.Router.generate('core_ajax_public_preview', {file: path, x:width, y:height, t:$('#dirToken').val()}); } else { var previewURL = OC.Router.generate('core_ajax_preview', {file: path, x:width, y:height}); @@ -677,8 +677,8 @@ function lazyLoadPreview(path, mime, ready, width, height) { }); } -function getUniqueName(name){ - if($('tr').filterAttr('data-file',name).length>0){ +function getUniqueName(name) { + if ($('tr[data-file="'+name+'"]').exists()) { var parts=name.split('.'); var extension = ""; if (parts.length > 1) { @@ -687,9 +687,9 @@ function getUniqueName(name){ var base=parts.join('.'); numMatch=base.match(/\((\d+)\)/); var num=2; - if(numMatch && numMatch.length>0){ + if (numMatch && numMatch.length>0) { num=parseInt(numMatch[numMatch.length-1])+1; - base=base.split('(') + base=base.split('('); base.pop(); base=$.trim(base.join('(')); } @@ -703,19 +703,19 @@ function getUniqueName(name){ } function checkTrashStatus() { - $.post(OC.filePath('files_trashbin', 'ajax', 'isEmpty.php'), function(result){ + $.post(OC.filePath('files_trashbin', 'ajax', 'isEmpty.php'), function(result) { if (result.data.isEmpty === false) { $("input[type=button][id=trash]").removeAttr("disabled"); } }); } -function onClickBreadcrumb(e){ +function onClickBreadcrumb(e) { var $el = $(e.target).closest('.crumb'), $targetDir = $el.data('dir'); isPublic = !!$('#isPublic').val(); - if ($targetDir !== undefined && !isPublic){ + if ($targetDir !== undefined && !isPublic) { e.preventDefault(); FileList.changeDirectory(decodeURIComponent($targetDir)); } diff --git a/apps/files/lib/app.php b/apps/files/lib/app.php index 579e8676cf..810a9fdd8d 100644 --- a/apps/files/lib/app.php +++ b/apps/files/lib/app.php @@ -25,7 +25,14 @@ namespace OCA\Files; class App { + /** + * @var \OC_L10N + */ private $l10n; + + /** + * @var \OC\Files\View + */ private $view; public function __construct($view, $l10n) { @@ -52,7 +59,15 @@ class App { $result['data'] = array( 'message' => $this->l10n->t("Invalid folder name. Usage of 'Shared' is reserved by ownCloud") ); - } elseif( + // rename to existing file is denied + } else if ($this->view->file_exists($dir . '/' . $newname)) { + + $result['data'] = array( + 'message' => $this->l10n->t( + "The name %s is already used in the folder %s. Please choose a different name.", + array($newname, $dir)) + ); + } else if ( // rename to "." is denied $newname !== '.' and // rename of "/Shared" is denied diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php index 2e88bf2dbb..6dd28532cc 100644 --- a/apps/files/templates/index.php +++ b/apps/files/templates/index.php @@ -37,7 +37,7 @@
-
+ t('You don’t have permission to upload or create files here'))?>
diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index b9592a32cb..0d34af043a 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -508,11 +508,18 @@ class Util { ) { // get the size from filesystem - $fullPath = $this->view->getLocalFile($path); $size = $this->view->filesize($path); + // fast path, else the calculation for $lastChunkNr is bogus + if ($size === 0) { + \OC_FileProxy::$enabled = $proxyStatus; + return 0; + } + // calculate last chunk nr - $lastChunkNr = floor($size / 8192); + // next highest is end of chunks, one subtracted is last one + // we have to read the last chunk, we can't just calculate it (because of padding etc) + $lastChunkNr = ceil($size/ 8192) - 1; $lastChunkSize = $size - ($lastChunkNr * 8192); // open stream diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php index eddc4c6b3f..1b93bc36c8 100755 --- a/apps/files_encryption/tests/util.php +++ b/apps/files_encryption/tests/util.php @@ -241,6 +241,34 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase { $this->view->unlink($this->userId . '/files/' . $filename); } + /** +< * @brief Test that data that is read by the crypto stream wrapper + */ + function testGetFileSize() { + \Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1); + + $filename = 'tmp-' . time(); + $externalFilename = '/' . $this->userId . '/files/' . $filename; + + // Test for 0 byte files + $problematicFileSizeData = ""; + $cryptedFile = $this->view->file_put_contents($externalFilename, $problematicFileSizeData); + $this->assertTrue(is_int($cryptedFile)); + $this->assertEquals($this->util->getFileSize($externalFilename), 0); + $decrypt = $this->view->file_get_contents($externalFilename); + $this->assertEquals($problematicFileSizeData, $decrypt); + $this->view->unlink($this->userId . '/files/' . $filename); + + // Test a file with 18377 bytes as in https://github.com/owncloud/mirall/issues/1009 + $problematicFileSizeData = str_pad("", 18377, "abc"); + $cryptedFile = $this->view->file_put_contents($externalFilename, $problematicFileSizeData); + $this->assertTrue(is_int($cryptedFile)); + $this->assertEquals($this->util->getFileSize($externalFilename), 18377); + $decrypt = $this->view->file_get_contents($externalFilename); + $this->assertEquals($problematicFileSizeData, $decrypt); + $this->view->unlink($this->userId . '/files/' . $filename); + } + /** * @medium */ @@ -333,7 +361,7 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase { /** * helper function to set migration status to the right value * to be able to test the migration path - * + * * @param $status needed migration status for test * @param $user for which user the status should be set * @return boolean diff --git a/apps/files_sharing/lib/updater.php b/apps/files_sharing/lib/updater.php index 08aaa62e25..3381f75f16 100644 --- a/apps/files_sharing/lib/updater.php +++ b/apps/files_sharing/lib/updater.php @@ -32,17 +32,19 @@ class Shared_Updater { $uid = \OCP\User::getUser(); $uidOwner = \OC\Files\Filesystem::getOwner($target); $info = \OC\Files\Filesystem::getFileInfo($target); + $checkedUser = array($uidOwner); // Correct Shared folders of other users shared with $users = \OCP\Share::getUsersItemShared('file', $info['fileid'], $uidOwner, true); if (!empty($users)) { while (!empty($users)) { $reshareUsers = array(); foreach ($users as $user) { - if ( $user !== $uidOwner ) { + if ( !in_array($user, $checkedUser) ) { $etag = \OC\Files\Filesystem::getETag(''); \OCP\Config::setUserValue($user, 'files_sharing', 'etag', $etag); // Look for reshares $reshareUsers = array_merge($reshareUsers, \OCP\Share::getUsersItemShared('file', $info['fileid'], $user, true)); + $checkedUser[] = $user; } } $users = $reshareUsers; diff --git a/apps/user_ldap/ajax/wizard.php b/apps/user_ldap/ajax/wizard.php new file mode 100644 index 0000000000..e580c09786 --- /dev/null +++ b/apps/user_ldap/ajax/wizard.php @@ -0,0 +1,96 @@ +. + * + */ + +// Check user and app status +OCP\JSON::checkAdminUser(); +OCP\JSON::checkAppEnabled('user_ldap'); +OCP\JSON::callCheck(); + +$l=OC_L10N::get('user_ldap'); + +if(!isset($_POST['action'])) { + \OCP\JSON::error(array('message' => $l->t('No action specified'))); +} +$action = $_POST['action']; + + +if(!isset($_POST['ldap_serverconfig_chooser'])) { + \OCP\JSON::error(array('message' => $l->t('No configuration specified'))); +} +$prefix = $_POST['ldap_serverconfig_chooser']; + +$ldapWrapper = new OCA\user_ldap\lib\LDAP(); +$configuration = new \OCA\user_ldap\lib\Configuration($prefix); +$wizard = new \OCA\user_ldap\lib\Wizard($configuration, $ldapWrapper); + +switch($action) { + case 'guessPortAndTLS': + case 'guessBaseDN': + case 'determineGroupMemberAssoc': + case 'determineUserObjectClasses': + case 'determineGroupObjectClasses': + case 'determineGroupsForUsers': + case 'determineGroupsForGroups': + case 'determineAttributes': + case 'getUserListFilter': + case 'getUserLoginFilter': + case 'getGroupFilter': + case 'countUsers': + case 'countGroups': + try { + $result = $wizard->$action(); + if($result !== false) { + OCP\JSON::success($result->getResultArray()); + exit; + } + } catch (\Exception $e) { + \OCP\JSON::error(array('message' => $e->getMessage())); + exit; + } + \OCP\JSON::error(); + exit; + break; + + case 'save': + $key = isset($_POST['cfgkey']) ? $_POST['cfgkey'] : false; + $val = isset($_POST['cfgval']) ? $_POST['cfgval'] : null; + if($key === false || is_null($val)) { + \OCP\JSON::error(array('message' => $l->t('No data specified'))); + exit; + } + $cfg = array($key => $val); + $setParameters = array(); + $configuration->setConfiguration($cfg, $setParameters); + if(!in_array($key, $setParameters)) { + \OCP\JSON::error(array('message' => $l->t($key. + ' Could not set configuration %s', $setParameters[0]))); + exit; + } + $configuration->saveConfiguration(); + OCP\JSON::success(); + break; + default: + //TODO: return 4xx error + break; +} + diff --git a/apps/user_ldap/appinfo/app.php b/apps/user_ldap/appinfo/app.php index 9d6327181a..c2cd295523 100644 --- a/apps/user_ldap/appinfo/app.php +++ b/apps/user_ldap/appinfo/app.php @@ -30,7 +30,7 @@ if(count($configPrefixes) === 1) { $ldapAccess = new OCA\user_ldap\lib\Access($connector, $ldapWrapper); $userBackend = new OCA\user_ldap\USER_LDAP($ldapAccess); $groupBackend = new OCA\user_ldap\GROUP_LDAP($ldapAccess); -} else { +} else if(count($configPrefixes) > 1) { $userBackend = new OCA\user_ldap\User_Proxy($configPrefixes, $ldapWrapper); $groupBackend = new OCA\user_ldap\Group_Proxy($configPrefixes, $ldapWrapper); } diff --git a/apps/user_ldap/css/settings.css b/apps/user_ldap/css/settings.css index 6086c7b74e..65bff3aadb 100644 --- a/apps/user_ldap/css/settings.css +++ b/apps/user_ldap/css/settings.css @@ -1,3 +1,85 @@ +.table { + display: table; + width: 60%; +} + +.tablecell { + display: table-cell !important; + white-space: nowrap; +} + +.tablerow { + display: table-row; +} + +.tablerow input, .tablerow textarea { + width: 100% !important; +} + +.tablerow textarea { + height: 15px; +} + +.invisible { + visibility: hidden; +} + +.ldapSettingsTabs { + float: right !important; +} + +.ldapWizardControls { + width: 60%; + text-align: right; +} + +.ldapWizardInfo { + width: 100% !important; + height: 50px; + background-color: lightyellow; + border-radius: 0.5em; + padding: 0.6em 0.5em 0.4em !important; + margin-bottom: 0.3em; +} + +#ldapWizard1 .hostPortCombinator { + width: 60%; + display: table; +} + +#ldapWizard1 .hostPortCombinator div span { + width: 7%; + display: table-cell; + text-align: right; +} + +#ldapWizard1 .host { + width: 96.5% !important; +} + +.tableCellInput { + margin-left: -40%; + width: 100%; +} + +.tableCellLabel { + text-align: right; + padding-right: 25%; +} + +.ldapIndent { + margin-left: 50px; +} + +.ldapwarning { + margin-left: 1.4em; + color: #FF3B3B; +} + +.wizSpinner { + height: 15px; +} + #ldap fieldset p label { width: 20%; max-width: 200px; @@ -9,7 +91,7 @@ } #ldap fieldset input, #ldap fieldset textarea { - width: 60%; + width: 60%; display: inline-block; } @@ -17,11 +99,9 @@ vertical-align: bottom; } -.ldapIndent { - margin-left: 50px; -} - -.ldapwarning { - margin-left: 1.4em; - color: #FF3B3B; -} +select[multiple=multiple] + button { + height: 28px; + padding-top: 6px !important; + min-width: 40%; + max-width: 40%; +} \ No newline at end of file diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index 20d6f76dcd..faef477420 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -30,6 +30,7 @@ var LdapConfiguration = { // assign the value $('#'+configkey).val(configvalue); }); + LdapWizard.init(); } } ); @@ -91,6 +92,7 @@ var LdapConfiguration = { $('#ldap_serverconfig_chooser option:selected').removeAttr('selected'); var html = ''; $('#ldap_serverconfig_chooser option:last').before(html); + LdapWizard.init(); } else { OC.dialogs.alert( result.message, @@ -122,13 +124,546 @@ var LdapConfiguration = { } }; +var LdapWizard = { + checkPortInfoShown: false, + saveBlacklist: {}, + userFilterGroupSelectState: 'enable', + spinner: '', + + ajax: function(param, fnOnSuccess, fnOnError) { + $.post( + OC.filePath('user_ldap','ajax','wizard.php'), + param, + function(result) { + if(result.status == 'success') { + fnOnSuccess(result); + } else { + fnOnError(result); + } + } + ); + }, + + applyChanges: function (result) { + for (id in result.changes) { + if(!$.isArray(result.changes[id])) { + //no need to blacklist multiselect + LdapWizard.saveBlacklist[id] = true; + } + if(id.indexOf('count') > 0) { + $('#'+id).text(result.changes[id]); + } else { + $('#'+id).val(result.changes[id]); + } + } + LdapWizard.functionalityCheck(); + + if($('#ldapSettings').tabs('option', 'active') == 0) { + LdapWizard.basicStatusCheck(); + } + }, + + basicStatusCheck: function() { + //criterias to continue from the first tab + // - host, port, user filter, agent dn, password, base dn + host = $('#ldap_host').val(); + port = $('#ldap_port').val(); + agent = $('#ldap_dn').val(); + pwd = $('#ldap_agent_password').val(); + base = $('#ldap_base').val(); + + if(host && port && agent && pwd && base) { + $('.ldap_action_continue').removeAttr('disabled'); + $('#ldapSettings').tabs('option', 'disabled', []); + } else { + $('.ldap_action_continue').attr('disabled', 'disabled'); + $('#ldapSettings').tabs('option', 'disabled', [1, 2, 3, 4, 5]); + } + }, + + checkBaseDN: function() { + host = $('#ldap_host').val(); + port = $('#ldap_port').val(); + user = $('#ldap_dn').val(); + pass = $('#ldap_agent_password').val(); + + if(host && port && user && pass) { + param = 'action=guessBaseDN'+ + '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + + LdapWizard.showSpinner('#ldap_base'); + LdapWizard.ajax(param, + function(result) { + LdapWizard.applyChanges(result); + LdapWizard.hideSpinner('#ldap_base'); + if($('#ldap_base').val()) { + $('#ldap_base').removeClass('invisible'); + LdapWizard.hideInfoBox(); + } + }, + function (result) { + LdapWizard.hideSpinner('#ldap_base'); + $('#ldap_base').removeClass('invisible'); + LdapWizard.showInfoBox('Please specify a port'); + } + ); + } + }, + + checkPort: function() { + host = $('#ldap_host').val(); + user = $('#ldap_dn').val(); + pass = $('#ldap_agent_password').val(); + + if(host && user && pass) { + param = 'action=guessPortAndTLS'+ + '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + + LdapWizard.showSpinner('#ldap_port'); + LdapWizard.ajax(param, + function(result) { + LdapWizard.applyChanges(result); + LdapWizard.hideSpinner('#ldap_port'); + if($('#ldap_port').val()) { + LdapWizard.checkBaseDN(); + $('#ldap_port').removeClass('invisible'); + LdapWizard.hideInfoBox(); + } + }, + function (result) { + LdapWizard.hideSpinner('#ldap_port'); + $('#ldap_port').removeClass('invisible'); + LdapWizard.showInfoBox('Please specify the BaseDN'); + } + ); + } + }, + + composeFilter: function(type) { + if(type == 'user') { + action = 'getUserListFilter'; + } else if(type == 'login') { + action = 'getUserLoginFilter'; + } else if(type == 'group') { + action = 'getGroupFilter'; + } + + param = 'action='+action+ + '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + + LdapWizard.ajax(param, + function(result) { + LdapWizard.applyChanges(result); + if(type == 'user') { + LdapWizard.countUsers(); + } else if(type == 'group') { + LdapWizard.countGroups(); + LdapWizard.detectGroupMemberAssoc(); + } + }, + function (result) { + // error handling + } + ); + }, + + controlBack: function() { + curTabIndex = $('#ldapSettings').tabs('option', 'active'); + if(curTabIndex == 0) { + return; + } + if(curTabIndex == 1) { + $('.ldap_action_back').addClass('invisible'); + } + $('#ldapSettings').tabs('option', 'active', curTabIndex - 1); + if(curTabIndex == 3) { + $('.ldap_action_continue').removeClass('invisible'); + } + }, + + controlContinue: function() { + curTabIndex = $('#ldapSettings').tabs('option', 'active'); + if(curTabIndex == 3) { + return; + } + $('#ldapSettings').tabs('option', 'active', 1 + curTabIndex); + if(curTabIndex == 2) { + //now last tab + $('.ldap_action_continue').addClass('invisible'); + } + if(curTabIndex == 0) { + $('.ldap_action_back').removeClass('invisible'); + } + }, + + _countThings: function(method) { + param = 'action='+method+ + '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + + LdapWizard.ajax(param, + function(result) { + LdapWizard.applyChanges(result); + }, + function (result) { + // error handling + } + ); + }, + + countGroups: function() { + LdapWizard._countThings('countGroups'); + }, + + countUsers: function() { + LdapWizard._countThings('countUsers'); + }, + + detectGroupMemberAssoc: function() { + param = 'action=determineGroupMemberAssoc'+ + '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + + LdapWizard.ajax(param, + function(result) { + //pure background story + }, + function (result) { + // error handling + } + ); + }, + + findAttributes: function() { + param = 'action=determineAttributes'+ + '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + + LdapWizard.showSpinner('#ldap_loginfilter_attributes'); + LdapWizard.ajax(param, + function(result) { + $('#ldap_loginfilter_attributes').find('option').remove(); + for (i in result.options['ldap_loginfilter_attributes']) { + //FIXME: move HTML into template + attr = result.options['ldap_loginfilter_attributes'][i]; + $('#ldap_loginfilter_attributes').append( + ""); + } + LdapWizard.hideSpinner('#ldap_loginfilter_attributes'); + LdapWizard.applyChanges(result); + $('#ldap_loginfilter_attributes').multiselect('refresh'); + $('#ldap_loginfilter_attributes').multiselect('enable'); + }, + function (result) { + //deactivate if no attributes found + $('#ldap_loginfilter_attributes').multiselect( + {noneSelectedText : 'No attributes found'}); + $('#ldap_loginfilter_attributes').multiselect('disable'); + LdapWizard.hideSpinner('#ldap_loginfilter_attributes'); + } + ); + }, + + findAvailableGroups: function(multisel, type) { + if(type != 'Users' && type != 'Groups') { + return false; + } + param = 'action=determineGroupsFor'+type+ + '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + + LdapWizard.showSpinner('#'+multisel); + LdapWizard.ajax(param, + function(result) { + $('#'+multisel).find('option').remove(); + for (i in result.options[multisel]) { + //FIXME: move HTML into template + objc = result.options[multisel][i]; + $('#'+multisel).append(""); + } + LdapWizard.hideSpinner('#'+multisel); + LdapWizard.applyChanges(result); + $('#'+multisel).multiselect('refresh'); + $('#'+multisel).multiselect('enable'); + }, + function (result) { + LdapWizard.hideSpinner('#'+multisel); + $('#'+multisel).multiselect('disable'); + } + ); + }, + + findObjectClasses: function(multisel, type) { + if(type != 'User' && type != 'Group') { + return false; + } + param = 'action=determine'+type+'ObjectClasses'+ + '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + + LdapWizard.showSpinner('#'+multisel); + LdapWizard.ajax(param, + function(result) { + $('#'+multisel).find('option').remove(); + for (i in result.options[multisel]) { + //FIXME: move HTML into template + objc = result.options[multisel][i]; + $('#'+multisel).append(""); + } + LdapWizard.hideSpinner('#'+multisel); + LdapWizard.applyChanges(result); + $('#'+multisel).multiselect('refresh'); + }, + function (result) { + LdapWizard.hideSpinner('#'+multisel); + //TODO: error handling + } + ); + }, + + functionalityCheck: function() { + //criterias to enable the connection: + // - host, port, user filter, login filter + host = $('#ldap_host').val(); + port = $('#ldap_port').val(); + userfilter = $('#ldap_dn').val(); + loginfilter = $('#ldap_agent_password').val(); + + //FIXME: activates a manually deactivated configuration. + if(host && port && userfilter && loginfilter) { + if($('#ldap_configuration_active').is(':checked')) { + return; + } + $('#ldap_configuration_active').prop('checked', true); + LdapWizard.save($('#ldap_configuration_active')[0]); + } else { + if($('#ldap_configuration_active').is(':checked')) { + $('#ldap_configuration_active').prop('checked', false); + LdapWizard.save($('#ldap_configuration_active')[0]); + } + } + }, + + hideInfoBox: function() { + if(LdapWizard.checkInfoShown) { + $('#ldapWizard1 .ldapWizardInfo').addClass('invisible'); + LdapWizard.checkInfoShown = false; + } + }, + + hideSpinner: function(id) { + $(id+' + .wizSpinner').remove(); + $(id + " + button").css('display', 'inline'); + }, + + init: function() { + if($('#ldap_port').val()) { + $('#ldap_port').removeClass('invisible'); + } + if($('#ldap_base').val()) { + $('#ldap_base').removeClass('invisible'); + } + LdapWizard.basicStatusCheck(); + }, + + initGroupFilter: function() { + LdapWizard.findObjectClasses('ldap_groupfilter_objectclass', 'Group'); + LdapWizard.findAvailableGroups('ldap_groupfilter_groups', 'Groups'); + LdapWizard.composeFilter('group'); + LdapWizard.countGroups(); + }, + + initLoginFilter: function() { + LdapWizard.findAttributes(); + LdapWizard.composeFilter('login'); + }, + + initMultiSelect: function(object, id, caption) { + object.multiselect({ + header: false, + selectedList: 9, + noneSelectedText: caption, + click: function(event, ui) { + LdapWizard.saveMultiSelect(id, + $('#'+id).multiselect("getChecked")); + } + }); + }, + + initUserFilter: function() { + LdapWizard.findObjectClasses('ldap_userfilter_objectclass', 'User'); + LdapWizard.findAvailableGroups('ldap_userfilter_groups', 'Users'); + LdapWizard.composeFilter('user'); + LdapWizard.countUsers(); + }, + + onTabChange: function(event, ui) { + if(ui.newTab[0].id === '#ldapWizard2') { + LdapWizard.initUserFilter(); + } else if(ui.newTab[0].id === '#ldapWizard3') { + LdapWizard.initLoginFilter(); + } else if(ui.newTab[0].id === '#ldapWizard4') { + LdapWizard.initGroupFilter(); + } + }, + + processChanges: function(triggerObj) { + if(triggerObj.id == 'ldap_host' + || triggerObj.id == 'ldap_port' + || triggerObj.id == 'ldap_dn' + || triggerObj.id == 'ldap_agent_password') { + LdapWizard.checkPort(); + if($('#ldap_port').val()) { + //if Port is already set, check BaseDN + LdapWizard.checkBaseDN(); + } + } + + if(triggerObj.id == 'ldap_userlist_filter') { + LdapWizard.countUsers(); + } else if(triggerObj.id == 'ldap_group_filter') { + LdapWizard.countGroups(); + LdapWizard.detectGroupMemberAssoc(); + } + + if(triggerObj.id == 'ldap_loginfilter_username' + || triggerObj.id == 'ldap_loginfilter_email') { + LdapWizard.composeFilter('login'); + } + + if($('#ldapSettings').tabs('option', 'active') == 0) { + LdapWizard.basicStatusCheck(); + } + }, + + save: function(inputObj) { + if(LdapWizard.saveBlacklist.hasOwnProperty(inputObj.id)) { + delete LdapWizard.saveBlacklist[inputObj.id]; + return; + } + if($(inputObj).is('input[type=checkbox]') + && !$(inputObj).is(':checked')) { + val = 0; + } else { + val = $(inputObj).val(); + } + LdapWizard._save(inputObj, val); + }, + + saveMultiSelect: function(originalObj, resultObj) { + values = ''; + for(i = 0; i < resultObj.length; i++) { + values = values + "\n" + resultObj[i].value; + } + LdapWizard._save($('#'+originalObj)[0], $.trim(values)); + if(originalObj == 'ldap_userfilter_objectclass' + || originalObj == 'ldap_userfilter_groups') { + LdapWizard.composeFilter('user'); + //when user filter is changed afterwards, login filter needs to + //be adjusted, too + LdapWizard.composeFilter('login'); + } else if(originalObj == 'ldap_loginfilter_attributes') { + LdapWizard.composeFilter('login'); + } else if(originalObj == 'ldap_groupfilter_objectclass' + || originalObj == 'ldap_groupfilter_groups') { + LdapWizard.composeFilter('group'); + } + }, + + _save: function(object, value) { + param = 'cfgkey='+object.id+ + '&cfgval='+value+ + '&action=save'+ + '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + + $.post( + OC.filePath('user_ldap','ajax','wizard.php'), + param, + function(result) { + if(result.status == 'success') { + LdapWizard.processChanges(object); + } else { +// alert('Oooooooooooh :('); + } + } + ); + }, + + showInfoBox: function(text) { + $('#ldapWizard1 .ldapWizardInfo').text(t('user_ldap', text)); + $('#ldapWizard1 .ldapWizardInfo').removeClass('invisible'); + LdapWizard.checkInfoShown = true; + }, + + showSpinner: function(id) { + if($(id + ' + .wizSpinner').length == 0) { + $(LdapWizard.spinner).insertAfter($(id)); + $(id + " + img + button").css('display', 'none'); + } + }, + + toggleRawFilter: function(container, moc, mg, stateVar) { + if($(container).hasClass('invisible')) { + $(container).removeClass('invisible'); + $(moc).multiselect('disable'); + if($(mg).multiselect().attr('disabled') == 'disabled') { + LdapWizard[stateVar] = 'disable'; + } else { + LdapWizard[stateVar] = 'enable'; + } + $(mg).multiselect('disable'); + } else { + $(container).addClass('invisible'); + $(mg).multiselect(LdapWizard[stateVar]); + $(moc).multiselect('enable'); + } + }, + + toggleRawGroupFilter: function() { + LdapWizard.toggleRawFilter('#rawGroupFilterContainer', + '#ldap_groupfilter_objectclass', + '#ldap_groupfilter_groups', + 'groupFilterGroupSelectState' + ); + }, + + toggleRawUserFilter: function() { + LdapWizard.toggleRawFilter('#rawUserFilterContainer', + '#ldap_userfilter_objectclass', + '#ldap_userfilter_groups', + 'userFilterGroupSelectState' + ); + } +}; + $(document).ready(function() { $('#ldapAdvancedAccordion').accordion({ heightStyle: 'content', animate: 'easeInOutCirc'}); - $('#ldapSettings').tabs(); + $('#ldapSettings').tabs({ beforeActivate: LdapWizard.onTabChange }); $('#ldap_submit').button(); $('#ldap_action_test_connection').button(); $('#ldap_action_delete_configuration').button(); + LdapWizard.initMultiSelect($('#ldap_userfilter_groups'), + 'ldap_userfilter_groups', + t('user_ldap', 'Select groups')); + LdapWizard.initMultiSelect($('#ldap_userfilter_objectclass'), + 'ldap_userfilter_objectclass', + t('user_ldap', 'Select object classes')); + LdapWizard.initMultiSelect($('#ldap_loginfilter_attributes'), + 'ldap_loginfilter_attributes', + t('user_ldap', 'Select attributes')); + LdapWizard.initMultiSelect($('#ldap_groupfilter_groups'), + 'ldap_groupfilter_groups', + t('user_ldap', 'Select groups')); + LdapWizard.initMultiSelect($('#ldap_groupfilter_objectclass'), + 'ldap_groupfilter_objectclass', + t('user_ldap', 'Select object classes')); + $('.lwautosave').change(function() { LdapWizard.save(this); }); + $('#toggleRawUserFilter').click(LdapWizard.toggleRawUserFilter); + $('#toggleRawGroupFilter').click(LdapWizard.toggleRawGroupFilter); LdapConfiguration.refreshConfig(); + $('.ldap_action_continue').click(function(event) { + event.preventDefault(); + LdapWizard.controlContinue(); + }); + $('.ldap_action_back').click(function(event) { + event.preventDefault(); + LdapWizard.controlBack(); + }); $('#ldap_action_test_connection').click(function(event){ event.preventDefault(); $.post( diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index 0d6cc7cfd2..0d4b09bac7 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -831,7 +831,7 @@ class Access extends LDAPUtility { private function combineFilter($filters, $operator) { $combinedFilter = '('.$operator; foreach($filters as $filter) { - if($filter[0] !== '(') { + if(!empty($filter) && $filter[0] !== '(') { $filter = '('.$filter.')'; } $combinedFilter.=$filter; diff --git a/apps/user_ldap/lib/configuration.php b/apps/user_ldap/lib/configuration.php new file mode 100644 index 0000000000..c8bf1c0948 --- /dev/null +++ b/apps/user_ldap/lib/configuration.php @@ -0,0 +1,384 @@ +. + * + */ + +namespace OCA\user_ldap\lib; + +class Configuration { + + protected $configPrefix = null; + protected $configRead = false; + + //settings + protected $config = array( + 'ldapHost' => null, + 'ldapPort' => null, + 'ldapBackupHost' => null, + 'ldapBackupPort' => null, + 'ldapBase' => null, + 'ldapBaseUsers' => null, + 'ldapBaseGroups' => null, + 'ldapAgentName' => null, + 'ldapAgentPassword' => null, + 'ldapTLS' => null, + 'ldapNoCase' => null, + 'turnOffCertCheck' => null, + 'ldapIgnoreNamingRules' => null, + 'ldapUserDisplayName' => null, + 'ldapUserFilterObjectclass' => null, + 'ldapUserFilterGroups' => null, + 'ldapUserFilter' => null, + 'ldapGroupFilter' => null, + 'ldapGroupFilterObjectclass' => null, + 'ldapGroupFilterGroups' => null, + 'ldapGroupDisplayName' => null, + 'ldapGroupMemberAssocAttr' => null, + 'ldapLoginFilter' => null, + 'ldapLoginFilterEmail' => null, + 'ldapLoginFilterUsername' => null, + 'ldapLoginFilterAttributes' => null, + 'ldapQuotaAttribute' => null, + 'ldapQuotaDefault' => null, + 'ldapEmailAttribute' => null, + 'ldapCacheTTL' => null, + 'ldapUuidUserAttribute' => 'auto', + 'ldapUuidGroupAttribute' => 'auto', + 'ldapOverrideMainServer' => false, + 'ldapConfigurationActive' => false, + 'ldapAttributesForUserSearch' => null, + 'ldapAttributesForGroupSearch' => null, + 'homeFolderNamingRule' => null, + 'hasPagedResultSupport' => false, + 'hasMemberOfFilterSupport' => false, + 'ldapExpertUsernameAttr' => null, + 'ldapExpertUUIDUserAttr' => null, + 'ldapExpertUUIDGroupAttr' => null, + ); + + public function __construct($configPrefix, $autoread = true) { + $this->configPrefix = $configPrefix; + if($autoread) { + $this->readConfiguration(); + } + } + + public function __get($name) { + if(isset($this->config[$name])) { + return $this->config[$name]; + } + } + + public function __set($name, $value) { + $this->setConfiguration(array($name => $value)); + } + + public function getConfiguration() { + return $this->config; + } + + /** + * @brief set LDAP configuration with values delivered by an array, not read + * from configuration. It does not save the configuration! To do so, you + * must call saveConfiguration afterwards. + * @param $config array that holds the config parameters in an associated + * array + * @param &$applied optional; array where the set fields will be given to + * @return null + */ + public function setConfiguration($config, &$applied = null) { + if(!is_array($config)) { + return false; + } + + $cta = $this->getConfigTranslationArray(); + foreach($config as $inputkey => $val) { + if(strpos($inputkey, '_') !== false && isset($cta[$inputkey])) { + $key = $cta[$inputkey]; + } elseif(isset($this->config[$inputkey])) { + $key = $inputkey; + } else { + continue; + } + + $setMethod = 'setValue'; + switch($key) { + case 'homeFolderNamingRule': + if(!empty($val) && strpos($val, 'attr:') === false) { + $val = 'attr:'.$val; + } + case 'ldapBase': + case 'ldapBaseUsers': + case 'ldapBaseGroups': + case 'ldapAttributesForUserSearch': + case 'ldapAttributesForGroupSearch': + case 'ldapUserFilterObjectclass': + case 'ldapUserFilterGroups': + case 'ldapGroupFilterObjectclass': + case 'ldapGroupFilterGroups': + case 'ldapLoginFilterAttributes': + $setMethod = 'setMultiLine'; + default: + $this->$setMethod($key, $val); + if(is_array($applied)) { + $applied[] = $inputkey; + } + } + } + + } + + public function readConfiguration() { + if(!$this->configRead && !is_null($this->configPrefix)) { + $cta = array_flip($this->getConfigTranslationArray()); + foreach($this->config as $key => $val) { + if(!isset($cta[$key])) { + //some are determined + continue; + } + $dbkey = $cta[$key]; + switch($key) { + case 'ldapBase': + case 'ldapBaseUsers': + case 'ldapBaseGroups': + case 'ldapAttributesForUserSearch': + case 'ldapAttributesForGroupSearch': + case 'ldapUserFilterObjectclass': + case 'ldapUserFilterGroups': + case 'ldapGroupFilterObjectclass': + case 'ldapGroupFilterGroups': + case 'ldapLoginFilterAttributes': + $readMethod = 'getMultiLine'; + break; + case 'ldapIgnoreNamingRules': + $readMethod = 'getSystemValue'; + $dbkey = $key; + break; + case 'ldapAgentPassword': + $readMethod = 'getPwd'; + break; + case 'ldapUserDisplayName': + case 'ldapGroupDisplayName': + $readMethod = 'getLcValue'; + break; + default: + $readMethod = 'getValue'; + break; + } + $this->config[$key] = $this->$readMethod($dbkey); + } + $this->configRead = true; + } + } + + /** + * @brief saves the current Configuration in the database + */ + public function saveConfiguration() { + $cta = array_flip($this->getConfigTranslationArray()); + foreach($this->config as $key => $value) { + switch ($key) { + case 'ldapAgentPassword': + $value = base64_encode($value); + break; + case 'ldapBase': + case 'ldapBaseUsers': + case 'ldapBaseGroups': + case 'ldapAttributesForUserSearch': + case 'ldapAttributesForGroupSearch': + case 'ldapUserFilterObjectclass': + case 'ldapUserFilterGroups': + case 'ldapGroupFilterObjectclass': + case 'ldapGroupFilterGroups': + case 'ldapLoginFilterAttributes': + if(is_array($value)) { + $value = implode("\n", $value); + } + break; + //following options are not stored but detected, skip them + case 'ldapIgnoreNamingRules': + case 'hasPagedResultSupport': + case 'ldapUuidUserAttribute': + case 'ldapUuidGroupAttribute': + continue 2; + } + if(is_null($value)) { + $value = ''; + } + $this->saveValue($cta[$key], $value); + } + } + + protected function getMultiLine($varname) { + $value = $this->getValue($varname); + if(empty($value)) { + $value = ''; + } else { + $value = preg_split('/\r\n|\r|\n/', $value); + } + + return $value; + } + + protected function setMultiLine($varname, $value) { + if(empty($value)) { + $value = ''; + } else { + $value = preg_split('/\r\n|\r|\n/', $value); + if($value === false) { + $value = ''; + } + } + + $this->setValue($varname, $value); + } + + protected function getPwd($varname) { + return base64_decode($this->getValue($varname)); + } + + protected function getLcValue($varname) { + return mb_strtolower($this->getValue($varname), 'UTF-8'); + } + + protected function getSystemValue($varname) { + //FIXME: if another system value is added, softcode the default value + return \OCP\Config::getSystemValue($varname, false); + } + + protected function getValue($varname) { + static $defaults; + if(is_null($defaults)) { + $defaults = $this->getDefaults(); + } + return \OCP\Config::getAppValue('user_ldap', + $this->configPrefix.$varname, + $defaults[$varname]); + } + + protected function setValue($varname, $value) { + $this->config[$varname] = $value; + } + + protected function saveValue($varname, $value) { + return \OCP\Config::setAppValue('user_ldap', + $this->configPrefix.$varname, + $value); + } + + /** + * @returns an associative array with the default values. Keys are correspond + * to config-value entries in the database table + */ + public function getDefaults() { + return array( + 'ldap_host' => '', + 'ldap_port' => '', + 'ldap_backup_host' => '', + 'ldap_backup_port' => '', + 'ldap_override_main_server' => '', + 'ldap_dn' => '', + 'ldap_agent_password' => '', + 'ldap_base' => '', + 'ldap_base_users' => '', + 'ldap_base_groups' => '', + 'ldap_userlist_filter' => '', + 'ldap_userfilter_objectclass' => '', + 'ldap_userfilter_groups' => '', + 'ldap_login_filter' => 'uid=%uid', + 'ldap_loginfilter_email' => 0, + 'ldap_loginfilter_username' => 1, + 'ldap_loginfilter_attributes' => '', + 'ldap_group_filter' => '', + 'ldap_groupfilter_objectclass' => '', + 'ldap_groupfilter_groups' => '', + 'ldap_display_name' => 'displayName', + 'ldap_group_display_name' => 'cn', + 'ldap_tls' => 1, + 'ldap_nocase' => 0, + 'ldap_quota_def' => '', + 'ldap_quota_attr' => '', + 'ldap_email_attr' => '', + 'ldap_group_member_assoc_attribute' => 'uniqueMember', + 'ldap_cache_ttl' => 600, + 'ldap_uuid_user_attribute' => 'auto', + 'ldap_uuid_group_attribute' => 'auto', + 'home_folder_naming_rule' => '', + 'ldap_turn_off_cert_check' => 0, + 'ldap_configuration_active' => 0, + 'ldap_attributes_for_user_search' => '', + 'ldap_attributes_for_group_search' => '', + 'ldap_expert_username_attr' => '', + 'ldap_expert_uuid_user_attr' => '', + 'ldap_expert_uuid_group_attr' => '', + 'has_memberof_filter_support' => 0, + ); + } + + /** + * @return returns an array that maps internal variable names to database fields + */ + public function getConfigTranslationArray() { + //TODO: merge them into one representation + static $array = array( + 'ldap_host' => 'ldapHost', + 'ldap_port' => 'ldapPort', + 'ldap_backup_host' => 'ldapBackupHost', + 'ldap_backup_port' => 'ldapBackupPort', + 'ldap_override_main_server' => 'ldapOverrideMainServer', + 'ldap_dn' => 'ldapAgentName', + 'ldap_agent_password' => 'ldapAgentPassword', + 'ldap_base' => 'ldapBase', + 'ldap_base_users' => 'ldapBaseUsers', + 'ldap_base_groups' => 'ldapBaseGroups', + 'ldap_userfilter_objectclass' => 'ldapUserFilterObjectclass', + 'ldap_userfilter_groups' => 'ldapUserFilterGroups', + 'ldap_userlist_filter' => 'ldapUserFilter', + 'ldap_login_filter' => 'ldapLoginFilter', + 'ldap_loginfilter_email' => 'ldapLoginFilterEmail', + 'ldap_loginfilter_username' => 'ldapLoginFilterUsername', + 'ldap_loginfilter_attributes' => 'ldapLoginFilterAttributes', + 'ldap_group_filter' => 'ldapGroupFilter', + 'ldap_groupfilter_objectclass' => 'ldapGroupFilterObjectclass', + 'ldap_groupfilter_groups' => 'ldapGroupFilterGroups', + 'ldap_display_name' => 'ldapUserDisplayName', + 'ldap_group_display_name' => 'ldapGroupDisplayName', + 'ldap_tls' => 'ldapTLS', + 'ldap_nocase' => 'ldapNoCase', + 'ldap_quota_def' => 'ldapQuotaDefault', + 'ldap_quota_attr' => 'ldapQuotaAttribute', + 'ldap_email_attr' => 'ldapEmailAttribute', + 'ldap_group_member_assoc_attribute' => 'ldapGroupMemberAssocAttr', + 'ldap_cache_ttl' => 'ldapCacheTTL', + 'home_folder_naming_rule' => 'homeFolderNamingRule', + 'ldap_turn_off_cert_check' => 'turnOffCertCheck', + 'ldap_configuration_active' => 'ldapConfigurationActive', + 'ldap_attributes_for_user_search' => 'ldapAttributesForUserSearch', + 'ldap_attributes_for_group_search' => 'ldapAttributesForGroupSearch', + 'ldap_expert_username_attr' => 'ldapExpertUsernameAttr', + 'ldap_expert_uuid_user_attr' => 'ldapExpertUUIUserDAttr', + 'ldap_expert_uuid_group_attr' => 'ldapExpertUUIDGroupAttr', + 'has_memberof_filter_support' => 'hasMemberOfFilterSupport', + ); + return $array; + } + +} \ No newline at end of file diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index 93efdb4c9c..8d34fb2f41 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -1,7 +1,7 @@ null, - 'ldapPort' => null, - 'ldapBackupHost' => null, - 'ldapBackupPort' => null, - 'ldapBase' => null, - 'ldapBaseUsers' => null, - 'ldapBaseGroups' => null, - 'ldapAgentName' => null, - 'ldapAgentPassword' => null, - 'ldapTLS' => null, - 'ldapNoCase' => null, - 'turnOffCertCheck' => null, - 'ldapIgnoreNamingRules' => null, - 'ldapUserDisplayName' => null, - 'ldapUserFilter' => null, - 'ldapGroupFilter' => null, - 'ldapGroupDisplayName' => null, - 'ldapGroupMemberAssocAttr' => null, - 'ldapLoginFilter' => null, - 'ldapQuotaAttribute' => null, - 'ldapQuotaDefault' => null, - 'ldapEmailAttribute' => null, - 'ldapCacheTTL' => null, - 'ldapUuidUserAttribute' => 'auto', - 'ldapUuidGroupAttribute' => 'auto', - 'ldapOverrideUuidAttribute' => null, - 'ldapOverrideMainServer' => false, - 'ldapConfigurationActive' => false, - 'ldapAttributesForUserSearch' => null, - 'ldapAttributesForGroupSearch' => null, - 'homeFolderNamingRule' => null, - 'hasPagedResultSupport' => false, - 'ldapExpertUsernameAttr' => null, - 'ldapExpertUUIDUserAttr' => null, - 'ldapExpertUUIDGroupAttr' => null, - ); + //settings handler + protected $configuration; /** * @brief Constructor @@ -83,13 +48,14 @@ class Connection extends LDAPUtility { parent::__construct($ldap); $this->configPrefix = $configPrefix; $this->configID = $configID; + $this->configuration = new Configuration($configPrefix); $memcache = new \OC\Memcache\Factory(); if($memcache->isAvailable()) { $this->cache = $memcache->create(); } else { $this->cache = \OC_Cache::getGlobalCache(); } - $this->config['hasPagedResultSupport'] = + $this->hasPagedResultSupport = $this->ldap->hasPagedResultSupport(); } @@ -114,23 +80,21 @@ class Connection extends LDAPUtility { $this->readConfiguration(); } - if(isset($this->config[$name])) { - return $this->config[$name]; + if($name === 'hasPagedResultSupport') { + return $this->hasPagedResultSupport; } + + return $this->configuration->$name; } public function __set($name, $value) { - $changed = false; - //only few options are writable - if($name === 'ldapUuidUserAttribute' || $name === 'ldapUuidGroupAttribute') { - \OCP\Util::writeLog('user_ldap', 'Set config '.$name.' to '.$value, \OCP\Util::DEBUG); - $this->config[$name] = $value; + $before = $this->configuration->$name; + $this->configuration->$name = $value; + $after = $this->configuration->$name; + if($before !== $after) { if(!empty($this->configID)) { - \OCP\Config::setAppValue($this->configID, $this->configPrefix.$name, $value); + $this->configuration->saveConfiguration(); } - $changed = true; - } - if($changed) { $this->validateConfiguration(); } } @@ -174,7 +138,7 @@ class Connection extends LDAPUtility { if(!$this->configured) { $this->readConfiguration(); } - if(!$this->config['ldapCacheTTL']) { + if(!$this->configuration->ldapCacheTTL) { return null; } if(!$this->isCached($key)) { @@ -190,7 +154,7 @@ class Connection extends LDAPUtility { if(!$this->configured) { $this->readConfiguration(); } - if(!$this->config['ldapCacheTTL']) { + if(!$this->configuration->ldapCacheTTL) { return false; } $key = $this->getCacheKey($key); @@ -201,156 +165,32 @@ class Connection extends LDAPUtility { if(!$this->configured) { $this->readConfiguration(); } - if(!$this->config['ldapCacheTTL'] - || !$this->config['ldapConfigurationActive']) { + if(!$this->configuration->ldapCacheTTL + || !$this->configuration->ldapConfigurationActive) { return null; } $key = $this->getCacheKey($key); $value = base64_encode(serialize($value)); - $this->cache->set($key, $value, $this->config['ldapCacheTTL']); + $this->cache->set($key, $value, $this->configuration->ldapCacheTTL); } public function clearCache() { $this->cache->clear($this->getCacheKey(null)); } - private function getValue($varname) { - static $defaults; - if(is_null($defaults)) { - $defaults = $this->getDefaults(); - } - return \OCP\Config::getAppValue($this->configID, - $this->configPrefix.$varname, - $defaults[$varname]); - } - - private function setValue($varname, $value) { - \OCP\Config::setAppValue($this->configID, - $this->configPrefix.$varname, - $value); - } - /** - * Special handling for reading Base Configuration - * - * @param $base the internal name of the config key - * @param $value the value stored for the base - */ - private function readBase($base, $value) { - if(empty($value)) { - $value = ''; - } else { - $value = preg_split('/\r\n|\r|\n/', $value); - } - - $this->config[$base] = $value; - } - - /** - * Caches the general LDAP configuration. + * @brief Caches the general LDAP configuration. + * @param $force optional. true, if the re-read should be forced. defaults + * to false. + * @return null */ private function readConfiguration($force = false) { if((!$this->configured || $force) && !is_null($this->configID)) { - $v = 'getValue'; - $this->config['ldapHost'] = $this->$v('ldap_host'); - $this->config['ldapBackupHost'] = $this->$v('ldap_backup_host'); - $this->config['ldapPort'] = $this->$v('ldap_port'); - $this->config['ldapBackupPort'] = $this->$v('ldap_backup_port'); - $this->config['ldapOverrideMainServer'] - = $this->$v('ldap_override_main_server'); - $this->config['ldapAgentName'] = $this->$v('ldap_dn'); - $this->config['ldapAgentPassword'] - = base64_decode($this->$v('ldap_agent_password')); - $this->readBase('ldapBase', $this->$v('ldap_base')); - $this->readBase('ldapBaseUsers', $this->$v('ldap_base_users')); - $this->readBase('ldapBaseGroups', $this->$v('ldap_base_groups')); - $this->config['ldapTLS'] = $this->$v('ldap_tls'); - $this->config['ldapNoCase'] = $this->$v('ldap_nocase'); - $this->config['turnOffCertCheck'] - = $this->$v('ldap_turn_off_cert_check'); - $this->config['ldapUserDisplayName'] - = mb_strtolower($this->$v('ldap_display_name'), 'UTF-8'); - $this->config['ldapUserFilter'] - = $this->$v('ldap_userlist_filter'); - $this->config['ldapGroupFilter'] = $this->$v('ldap_group_filter'); - $this->config['ldapLoginFilter'] = $this->$v('ldap_login_filter'); - $this->config['ldapGroupDisplayName'] - = mb_strtolower($this->$v('ldap_group_display_name'), 'UTF-8'); - $this->config['ldapQuotaAttribute'] - = $this->$v('ldap_quota_attr'); - $this->config['ldapQuotaDefault'] - = $this->$v('ldap_quota_def'); - $this->config['ldapEmailAttribute'] - = $this->$v('ldap_email_attr'); - $this->config['ldapGroupMemberAssocAttr'] - = $this->$v('ldap_group_member_assoc_attribute'); - $this->config['ldapIgnoreNamingRules'] - = \OCP\Config::getSystemValue('ldapIgnoreNamingRules', false); - $this->config['ldapCacheTTL'] = $this->$v('ldap_cache_ttl'); - $this->config['ldapUuidUserAttribute'] - = $this->$v('ldap_uuid_user_attribute'); - $this->config['ldapUuidGroupAttribute'] - = $this->$v('ldap_uuid_group_attribute'); - $this->config['ldapOverrideUuidAttribute'] - = $this->$v('ldap_override_uuid_attribute'); - $this->config['homeFolderNamingRule'] - = $this->$v('home_folder_naming_rule'); - $this->config['ldapConfigurationActive'] - = $this->$v('ldap_configuration_active'); - $this->config['ldapAttributesForUserSearch'] - = preg_split('/\r\n|\r|\n/', $this->$v('ldap_attributes_for_user_search')); - $this->config['ldapAttributesForGroupSearch'] - = preg_split('/\r\n|\r|\n/', $this->$v('ldap_attributes_for_group_search')); - $this->config['ldapExpertUsernameAttr'] - = $this->$v('ldap_expert_username_attr'); - $this->config['ldapExpertUUIDUserAttr'] - = $this->$v('ldap_expert_uuid_user_attr'); - $this->config['ldapExpertUUIDGroupAttr'] - = $this->$v('ldap_expert_uuid_group_attr'); - + $this->configuration->readConfiguration(); $this->configured = $this->validateConfiguration(); } } - /** - * @return returns an array that maps internal variable names to database fields - */ - private function getConfigTranslationArray() { - static $array = array( - 'ldap_host'=>'ldapHost', - 'ldap_port'=>'ldapPort', - 'ldap_backup_host'=>'ldapBackupHost', - 'ldap_backup_port'=>'ldapBackupPort', - 'ldap_override_main_server' => 'ldapOverrideMainServer', - 'ldap_dn'=>'ldapAgentName', - 'ldap_agent_password'=>'ldapAgentPassword', - 'ldap_base'=>'ldapBase', - 'ldap_base_users'=>'ldapBaseUsers', - 'ldap_base_groups'=>'ldapBaseGroups', - 'ldap_userlist_filter'=>'ldapUserFilter', - 'ldap_login_filter'=>'ldapLoginFilter', - 'ldap_group_filter'=>'ldapGroupFilter', - 'ldap_display_name'=>'ldapUserDisplayName', - 'ldap_group_display_name'=>'ldapGroupDisplayName', - 'ldap_tls'=>'ldapTLS', - 'ldap_nocase'=>'ldapNoCase', - 'ldap_quota_def'=>'ldapQuotaDefault', - 'ldap_quota_attr'=>'ldapQuotaAttribute', - 'ldap_email_attr'=>'ldapEmailAttribute', - 'ldap_group_member_assoc_attribute'=>'ldapGroupMemberAssocAttr', - 'ldap_cache_ttl'=>'ldapCacheTTL', - 'home_folder_naming_rule' => 'homeFolderNamingRule', - 'ldap_turn_off_cert_check' => 'turnOffCertCheck', - 'ldap_configuration_active' => 'ldapConfigurationActive', - 'ldap_attributes_for_user_search' => 'ldapAttributesForUserSearch', - 'ldap_attributes_for_group_search' => 'ldapAttributesForGroupSearch', - 'ldap_expert_username_attr' => 'ldapExpertUsernameAttr', - 'ldap_expert_uuid_user_attr' => 'ldapExpertUUIDUserAttr', - 'ldap_expert_uuid_group_attr' => 'ldapExpertUUIDGroupAttr', - ); - return $array; - } - /** * @brief set LDAP configuration with values delivered by an array, not read from configuration * @param $config array that holds the config parameters in an associated array @@ -358,79 +198,24 @@ class Connection extends LDAPUtility { * @return true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters */ public function setConfiguration($config, &$setParameters = null) { - if(!is_array($config)) { - return false; + if(is_null($setParameters)) { + $setParameters = array(); } - - $params = $this->getConfigTranslationArray(); - - foreach($config as $parameter => $value) { - if(($parameter === 'homeFolderNamingRule' - || (isset($params[$parameter]) - && $params[$parameter] === 'homeFolderNamingRule')) - && !empty($value)) { - $value = 'attr:'.$value; - } else if (strpos($parameter, 'ldapBase') !== false - || (isset($params[$parameter]) - && strpos($params[$parameter], 'ldapBase') !== false)) { - $this->readBase($params[$parameter], $value); - if(is_array($setParameters)) { - $setParameters[] = $parameter; - } - continue; - } - if(isset($this->config[$parameter])) { - $this->config[$parameter] = $value; - if(is_array($setParameters)) { - $setParameters[] = $parameter; - } - } else if(isset($params[$parameter])) { - $this->config[$params[$parameter]] = $value; - if(is_array($setParameters)) { - $setParameters[] = $params[$parameter]; - } - } + $this->configuration->setConfiguration($config, $setParameters); + if(count($setParameters) > 0) { + $this->configured = $this->validateConfiguration(); } - $this->configured = $this->validateConfiguration(); - return $this->configured; } /** - * @brief saves the current Configuration in the database + * @brief saves the current Configuration in the database and empties the + * cache + * @return null */ public function saveConfiguration() { - $trans = array_flip($this->getConfigTranslationArray()); - foreach($this->config as $key => $value) { - \OCP\Util::writeLog('user_ldap', 'LDAP: storing key '.$key. - ' value '.print_r($value, true), \OCP\Util::DEBUG); - switch ($key) { - case 'ldapAgentPassword': - $value = base64_encode($value); - break; - case 'ldapBase': - case 'ldapBaseUsers': - case 'ldapBaseGroups': - case 'ldapAttributesForUserSearch': - case 'ldapAttributesForGroupSearch': - if(is_array($value)) { - $value = implode("\n", $value); - } - break; - case 'ldapIgnoreNamingRules': - case 'ldapOverrideUuidAttribute': - case 'ldapUuidUserAttribute': - case 'ldapUuidGroupAttribute': - case 'hasPagedResultSupport': - continue 2; - } - if(is_null($value)) { - $value = ''; - } - - $this->setValue($trans[$key], $value); - } + $this->configuration->saveConfiguration(); $this->clearCache(); } @@ -440,26 +225,175 @@ class Connection extends LDAPUtility { */ public function getConfiguration() { $this->readConfiguration(); - $trans = $this->getConfigTranslationArray(); - $config = array(); - foreach($trans as $dbKey => $classKey) { - if($classKey === 'homeFolderNamingRule') { - if(strpos($this->config[$classKey], 'attr:') === 0) { - $config[$dbKey] = substr($this->config[$classKey], 5); - } else { - $config[$dbKey] = ''; - } - continue; - } else if((strpos($classKey, 'ldapBase') !== false - || strpos($classKey, 'ldapAttributes') !== false) - && is_array($this->config[$classKey])) { - $config[$dbKey] = implode("\n", $this->config[$classKey]); - continue; + $config = $this->configuration->getConfiguration(); + $cta = $this->configuration->getConfigTranslationArray(); + $result = array(); + foreach($cta as $dbkey => $configkey) { + switch($configkey) { + case 'homeFolderNamingRule': + if(strpos($config[$configkey], 'attr:') === 0) { + $result[$dbkey] = substr($config[$configkey], 5); + } else { + $result[$dbkey] = ''; + } + break; + case 'ldapBase': + case 'ldapBaseUsers': + case 'ldapBaseGroups': + case 'ldapAttributesForUserSearch': + case 'ldapAttributesForGroupSearch': + if(is_array($config[$configkey])) { + $result[$dbkey] = implode("\n", $config[$configkey]); + break; + } //else follows default + default: + $result[$dbkey] = $config[$configkey]; + } + } + return $result; + } + + private function doSoftValidation() { + //if User or Group Base are not set, take over Base DN setting + foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) { + $val = $this->configuration->$keyBase; + if(empty($val)) { + $obj = strpos('Users', $keyBase) !== false ? 'Users' : 'Groups'; + \OCP\Util::writeLog('user_ldap', + 'Base tree for '.$obj. + ' is empty, using Base DN', + \OCP\Util::INFO); + $this->configuration->$keyBase = $this->configuration->ldapBase; } - $config[$dbKey] = $this->config[$classKey]; } - return $config; + $groupFilter = $this->configuration->ldapGroupFilter; + if(empty($groupFilter)) { + \OCP\Util::writeLog('user_ldap', + 'No group filter is specified, LDAP group '. + 'feature will not be used.', + \OCP\Util::INFO); + } + + foreach(array('ldapExpertUUIDUserAttr' => 'ldapUuidUserAttribute', + 'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute') + as $expertSetting => $effectiveSetting) { + $uuidOverride = $this->configuration->$expertSetting; + if(!empty($uuidOverride)) { + $this->configuration->$effectiveSetting = $uuidOverride; + } else { + $uuidAttributes = array('auto', 'entryuuid', 'nsuniqueid', + 'objectguid', 'guid'); + if(!in_array($this->configuration->$effectiveSetting, + $uuidAttributes) + && (!is_null($this->configID))) { + $this->configuration->$effectiveSetting = 'auto'; + $this->configuration->saveConfiguration(); + \OCP\Util::writeLog('user_ldap', + 'Illegal value for the '. + $effectiveSetting.', '.'reset to '. + 'autodetect.', \OCP\Util::INFO); + } + + } + } + + $backupPort = $this->configuration->ldapBackupPort; + if(empty($backupPort)) { + $this->configuration->backupPort = $this->configuration->ldapPort; + } + + //make sure empty search attributes are saved as simple, empty array + $sakeys = array('ldapAttributesForUserSearch', + 'ldapAttributesForGroupSearch'); + foreach($sakeys as $key) { + $val = $this->configuration->$key; + if(is_array($val) && count($val) === 1 && empty($val[0])) { + $this->configuration->$key = array(); + } + } + + if((stripos($this->configuration->ldapHost, 'ldaps://') === 0) + && $this->configuration->ldapTLS) { + $this->configuration->ldapTLS = false; + \OCP\Util::writeLog('user_ldap', + 'LDAPS (already using secure connection) and '. + 'TLS do not work together. Switched off TLS.', + \OCP\Util::INFO); + } + } + + private function doCriticalValidation() { + $configurationOK = true; + $errorStr = 'Configuration Error (prefix '. + strval($this->configPrefix).'): '; + + //options that shall not be empty + $options = array('ldapHost', 'ldapPort', 'ldapUserDisplayName', + 'ldapGroupDisplayName', 'ldapLoginFilter'); + foreach($options as $key) { + $val = $this->configuration->$key; + if(empty($val)) { + switch($key) { + case 'ldapHost': + $subj = 'LDAP Host'; + break; + case 'ldapPort': + $subj = 'LDAP Port'; + break; + case 'ldapUserDisplayName': + $subj = 'LDAP User Display Name'; + break; + case 'ldapGroupDisplayName': + $subj = 'LDAP Group Display Name'; + break; + case 'ldapLoginFilter': + $subj = 'LDAP Login Filter'; + break; + default: + $subj = $key; + break; + } + $configurationOK = false; + \OCP\Util::writeLog('user_ldap', + $errorStr.'No '.$subj.' given!', + \OCP\Util::WARN); + } + } + + //combinations + $agent = $this->configuration->ldapAgentName; + $pwd = $this->configuration->ldapAgentPassword; + if((empty($agent) && !empty($pwd)) || (!empty($agent) && empty($pwd))) { + \OCP\Util::writeLog('user_ldap', + $errorStr.'either no password is given for the'. + 'user agent or a password is given, but not an'. + 'LDAP agent.', + \OCP\Util::WARN); + $configurationOK = false; + } + + $base = $this->configuration->ldapBase; + $baseUsers = $this->configuration->ldapBaseUsers; + $baseGroups = $this->configuration->ldapBaseGroups; + + if(empty($base) && empty($baseUsers) && empty($baseGroups)) { + \OCP\Util::writeLog('user_ldap', + $errorStr.'Not a single Base DN given.', + \OCP\Util::WARN); + $configurationOK = false; + } + + if(mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8') + === false) { + \OCP\Util::writeLog('user_ldap', + $errorStr.'login filter does not contain %uid '. + 'place holder.', + \OCP\Util::WARN); + $configurationOK = false; + } + + return $configurationOK; } /** @@ -469,162 +403,19 @@ class Connection extends LDAPUtility { private function validateConfiguration() { // first step: "soft" checks: settings that are not really // necessary, but advisable. If left empty, give an info message - if(empty($this->config['ldapBaseUsers'])) { - \OCP\Util::writeLog('user_ldap', 'Base tree for Users is empty, using Base DN', \OCP\Util::INFO); - $this->config['ldapBaseUsers'] = $this->config['ldapBase']; - } - if(empty($this->config['ldapBaseGroups'])) { - \OCP\Util::writeLog('user_ldap', 'Base tree for Groups is empty, using Base DN', \OCP\Util::INFO); - $this->config['ldapBaseGroups'] = $this->config['ldapBase']; - } - if(empty($this->config['ldapGroupFilter']) && empty($this->config['ldapGroupMemberAssocAttr'])) { - \OCP\Util::writeLog('user_ldap', - 'No group filter is specified, LDAP group feature will not be used.', - \OCP\Util::INFO); - } - $uuidAttributes = array( - 'auto', 'entryuuid', 'nsuniqueid', 'objectguid', 'guid'); - $uuidSettings = array( - 'ldapUuidUserAttribute' => 'ldapExpertUUIDUserAttr', - 'ldapUuidGroupAttribute' => 'ldapExpertUUIDGroupAttr'); - $cta = array_flip($this->getConfigTranslationArray()); - foreach($uuidSettings as $defaultKey => $overrideKey) { - if( !in_array($this->config[$defaultKey], $uuidAttributes) - && is_null($this->config[$overrideKey]) - && !is_null($this->configID)) { - \OCP\Config::setAppValue($this->configID, - $this->configPrefix.$cta[$defaultKey], - 'auto'); - \OCP\Util::writeLog('user_ldap', - 'Illegal value for'.$defaultKey.', reset to autodetect.', - \OCP\Util::DEBUG); - } - } + $this->doSoftValidation(); - if(empty($this->config['ldapBackupPort'])) { - //force default - $this->config['ldapBackupPort'] = $this->config['ldapPort']; - } - foreach(array('ldapAttributesForUserSearch', 'ldapAttributesForGroupSearch') as $key) { - if(is_array($this->config[$key]) - && count($this->config[$key]) === 1 - && empty($this->config[$key][0])) { - $this->config[$key] = array(); - } - } - if((strpos($this->config['ldapHost'], 'ldaps') === 0) - && $this->config['ldapTLS']) { - $this->config['ldapTLS'] = false; - \OCP\Util::writeLog('user_ldap', - 'LDAPS (already using secure connection) and TLS do not work together. Switched off TLS.', - \OCP\Util::INFO); - } - - //second step: critical checks. If left empty or filled wrong, set as unconfigured and give a warning. - $configurationOK = true; - if(empty($this->config['ldapHost'])) { - \OCP\Util::writeLog('user_ldap', 'No LDAP host given, won`t connect.', \OCP\Util::WARN); - $configurationOK = false; - } - if(empty($this->config['ldapPort'])) { - \OCP\Util::writeLog('user_ldap', 'No LDAP Port given, won`t connect.', \OCP\Util::WARN); - $configurationOK = false; - } - if((empty($this->config['ldapAgentName']) && !empty($this->config['ldapAgentPassword'])) - || (!empty($this->config['ldapAgentName']) && empty($this->config['ldapAgentPassword']))) { - \OCP\Util::writeLog('user_ldap', - 'Either no password given for the user agent or a password is given, but no LDAP agent; won`t connect.', - \OCP\Util::WARN); - $configurationOK = false; - } - //TODO: check if ldapAgentName is in DN form - if(empty($this->config['ldapBase']) - && (empty($this->config['ldapBaseUsers']) - && empty($this->config['ldapBaseGroups']))) { - \OCP\Util::writeLog('user_ldap', 'No Base DN given, won`t connect.', \OCP\Util::WARN); - $configurationOK = false; - } - if(empty($this->config['ldapUserDisplayName'])) { - \OCP\Util::writeLog('user_ldap', - 'No user display name attribute specified, won`t connect.', - \OCP\Util::WARN); - $configurationOK = false; - } - if(empty($this->config['ldapGroupDisplayName'])) { - \OCP\Util::writeLog('user_ldap', - 'No group display name attribute specified, won`t connect.', - \OCP\Util::WARN); - $configurationOK = false; - } - if(empty($this->config['ldapLoginFilter'])) { - \OCP\Util::writeLog('user_ldap', 'No login filter specified, won`t connect.', \OCP\Util::WARN); - $configurationOK = false; - } - if(mb_strpos($this->config['ldapLoginFilter'], '%uid', 0, 'UTF-8') === false) { - \OCP\Util::writeLog('user_ldap', - 'Login filter does not contain %uid place holder, won`t connect.', - \OCP\Util::WARN); - \OCP\Util::writeLog('user_ldap', 'Login filter was ' . $this->config['ldapLoginFilter'], \OCP\Util::DEBUG); - $configurationOK = false; - } - - if(!empty($this->config['ldapExpertUUIDUserAttr'])) { - $this->config['ldapUuidUserAttribute'] = $this->config['ldapExpertUUIDUserAttr']; - } - if(!empty($this->config['ldapExpertUUIDGroupAttr'])) { - $this->config['ldapUuidGroupAttribute'] = $this->config['ldapExpertUUIDGroupAttr']; - } - - return $configurationOK; + //second step: critical checks. If left empty or filled wrong, set as + //unconfigured and give a warning. + return $this->doCriticalValidation(); } - /** - * @returns an associative array with the default values. Keys are correspond - * to config-value entries in the database table - */ - static public function getDefaults() { - return array( - 'ldap_host' => '', - 'ldap_port' => '389', - 'ldap_backup_host' => '', - 'ldap_backup_port' => '', - 'ldap_override_main_server' => '', - 'ldap_dn' => '', - 'ldap_agent_password' => '', - 'ldap_base' => '', - 'ldap_base_users' => '', - 'ldap_base_groups' => '', - 'ldap_userlist_filter' => 'objectClass=person', - 'ldap_login_filter' => 'uid=%uid', - 'ldap_group_filter' => 'objectClass=posixGroup', - 'ldap_display_name' => 'cn', - 'ldap_group_display_name' => 'cn', - 'ldap_tls' => 1, - 'ldap_nocase' => 0, - 'ldap_quota_def' => '', - 'ldap_quota_attr' => '', - 'ldap_email_attr' => '', - 'ldap_group_member_assoc_attribute' => 'uniqueMember', - 'ldap_cache_ttl' => 600, - 'ldap_uuid_user_attribute' => 'auto', - 'ldap_uuid_group_attribute' => 'auto', - 'ldap_override_uuid_attribute' => 0, - 'home_folder_naming_rule' => '', - 'ldap_turn_off_cert_check' => 0, - 'ldap_configuration_active' => 1, - 'ldap_attributes_for_user_search' => '', - 'ldap_attributes_for_group_search' => '', - 'ldap_expert_username_attr' => '', - 'ldap_expert_uuid_user_attr' => '', - 'ldap_expert_uuid_group_attr' => '', - ); - } /** * Connects and Binds to LDAP */ private function establishConnection() { - if(!$this->config['ldapConfigurationActive']) { + if(!$this->configuration->ldapConfigurationActive) { return null; } static $phpLDAPinstalled = true; @@ -632,29 +423,36 @@ class Connection extends LDAPUtility { return false; } if(!$this->configured) { - \OCP\Util::writeLog('user_ldap', 'Configuration is invalid, cannot connect', \OCP\Util::WARN); + \OCP\Util::writeLog('user_ldap', + 'Configuration is invalid, cannot connect', + \OCP\Util::WARN); return false; } if(!$this->ldapConnectionRes) { if(!$this->ldap->areLDAPFunctionsAvailable()) { $phpLDAPinstalled = false; \OCP\Util::writeLog('user_ldap', - 'function ldap_connect is not available. Make sure that the PHP ldap module is installed.', - \OCP\Util::ERROR); + 'function ldap_connect is not available. Make '. + 'sure that the PHP ldap module is installed.', + \OCP\Util::ERROR); return false; } - if($this->config['turnOffCertCheck']) { + if($this->configuration->turnOffCertCheck) { if(putenv('LDAPTLS_REQCERT=never')) { \OCP\Util::writeLog('user_ldap', 'Turned off SSL certificate validation successfully.', \OCP\Util::WARN); } else { - \OCP\Util::writeLog('user_ldap', 'Could not turn off SSL certificate validation.', \OCP\Util::WARN); + \OCP\Util::writeLog('user_ldap', + 'Could not turn off SSL certificate validation.', + \OCP\Util::WARN); } } - if(!$this->config['ldapOverrideMainServer'] && !$this->getFromCache('overrideMainServer')) { - $this->doConnect($this->config['ldapHost'], $this->config['ldapPort']); + if(!$this->configuration->ldapOverrideMainServer + && !$this->getFromCache('overrideMainServer')) { + $this->doConnect($this->configuration->ldapHost, + $this->configuration->ldapPort); $bindStatus = $this->bind(); $error = $this->ldap->isResource($this->ldapConnectionRes) ? $this->ldap->errno($this->ldapConnectionRes) : -1; @@ -665,9 +463,10 @@ class Connection extends LDAPUtility { //if LDAP server is not reachable, try the Backup (Replica!) Server if((!$bindStatus && ($error !== 0)) - || $this->config['ldapOverrideMainServer'] + || $this->configuration->ldapOverrideMainServer || $this->getFromCache('overrideMainServer')) { - $this->doConnect($this->config['ldapBackupHost'], $this->config['ldapBackupPort']); + $this->doConnect($this->configuration->ldapBackupHost, + $this->configuration->ldapBackupPort); $bindStatus = $this->bind(); if(!$bindStatus && $error === -1) { //when bind to backup server succeeded and failed to main server, @@ -690,7 +489,7 @@ class Connection extends LDAPUtility { $this->ldapConnectionRes = $this->ldap->connect($host, $port); if($this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) { if($this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) { - if($this->config['ldapTLS']) { + if($this->configuration->ldapTLS) { $this->ldap->startTls($this->ldapConnectionRes); } } @@ -702,7 +501,7 @@ class Connection extends LDAPUtility { */ public function bind() { static $getConnectionResourceAttempt = false; - if(!$this->config['ldapConfigurationActive']) { + if(!$this->configuration->ldapConfigurationActive) { return false; } if($getConnectionResourceAttempt) { @@ -716,8 +515,8 @@ class Connection extends LDAPUtility { return false; } $ldapLogin = @$this->ldap->bind($cr, - $this->config['ldapAgentName'], - $this->config['ldapAgentPassword']); + $this->configuration->ldapAgentName, + $this->configuration->ldapAgentPassword); if(!$ldapLogin) { \OCP\Util::writeLog('user_ldap', 'Bind failed: ' . $this->ldap->errno($cr) . ': ' . $this->ldap->error($cr), diff --git a/apps/user_ldap/lib/helper.php b/apps/user_ldap/lib/helper.php index 4c9dd07a12..09f646921e 100644 --- a/apps/user_ldap/lib/helper.php +++ b/apps/user_ldap/lib/helper.php @@ -161,4 +161,25 @@ class Helper { return true; } + + /** + * @brief extractsthe domain from a given URL + * @param $url the URL + * @return mixed, domain as string on success, false otherwise + */ + static public function getDomainFromURL($url) { + $uinfo = parse_url($url); + if(!is_array($uinfo)) { + return false; + } + + $domain = false; + if(isset($uinfo['host'])) { + $domain = $uinfo['host']; + } else if(isset($uinfo['path'])) { + $domain = $uinfo['path']; + } + + return $domain; + } } diff --git a/apps/user_ldap/lib/ildapwrapper.php b/apps/user_ldap/lib/ildapwrapper.php index 9e6bd56ef2..20587cba7d 100644 --- a/apps/user_ldap/lib/ildapwrapper.php +++ b/apps/user_ldap/lib/ildapwrapper.php @@ -67,6 +67,14 @@ interface ILDAPWrapper { */ public function controlPagedResultResponse($link, $result, &$cookie); + /** + * @brief Count the number of entries in a search + * @param $link LDAP link resource + * @param $result LDAP result resource + * @return mixed, number of results on success, false otherwise + */ + public function countEntries($link, $result); + /** * @brief Return the LDAP error number of the last LDAP command * @param $link LDAP link resource @@ -97,6 +105,14 @@ interface ILDAPWrapper { * */ public function getAttributes($link, $result); + /** + * @brief Get the DN of a result entry + * @param $link LDAP link resource + * @param $result LDAP result resource + * @return string containing the DN, false on error + */ + public function getDN($link, $result); + /** * @brief Get all result entries * @param $link LDAP link resource @@ -105,6 +121,14 @@ interface ILDAPWrapper { */ public function getEntries($link, $result); + /** + * @brief Return next result id + * @param $link LDAP link resource + * @param $result LDAP entry result resource + * @return an LDAP search result resource + * */ + public function nextEntry($link, $result); + /** * @brief Read an entry * @param $link LDAP link resource diff --git a/apps/user_ldap/lib/ldap.php b/apps/user_ldap/lib/ldap.php index b63e969912..bc96319172 100644 --- a/apps/user_ldap/lib/ldap.php +++ b/apps/user_ldap/lib/ldap.php @@ -49,6 +49,10 @@ class LDAP implements ILDAPWrapper { $isCritical, $cookie); } + public function countEntries($link, $result) { + return $this->invokeLDAPMethod('count_entries', $link, $result); + } + public function errno($link) { return $this->invokeLDAPMethod('errno', $link); } @@ -65,10 +69,18 @@ class LDAP implements ILDAPWrapper { return $this->invokeLDAPMethod('get_attributes', $link, $result); } + public function getDN($link, $result) { + return $this->invokeLDAPMethod('get_dn', $link, $result); + } + public function getEntries($link, $result) { return $this->invokeLDAPMethod('get_entries', $link, $result); } + public function nextEntry($link, $result) { + return $this->invokeLDAPMethod('next_entry', $link, $result); + } + public function read($link, $baseDN, $filter, $attr) { return $this->invokeLDAPMethod('read', $link, $baseDN, $filter, $attr); } diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php new file mode 100644 index 0000000000..9b84c3d5a4 --- /dev/null +++ b/apps/user_ldap/lib/wizard.php @@ -0,0 +1,1024 @@ +. + * + */ + +namespace OCA\user_ldap\lib; + +class Wizard extends LDAPUtility { + static protected $l; + protected $cr; + protected $configuration; + protected $result; + protected $resultCache = array(); + + const LRESULT_PROCESSED_OK = 2; + const LRESULT_PROCESSED_INVALID = 3; + const LRESULT_PROCESSED_SKIP = 4; + + const LFILTER_LOGIN = 2; + const LFILTER_USER_LIST = 3; + const LFILTER_GROUP_LIST = 4; + + const LDAP_NW_TIMEOUT = 4; + + /** + * @brief Constructor + * @param $configuration an instance of Configuration + * @param $ldap an instance of ILDAPWrapper + */ + public function __construct(Configuration $configuration, ILDAPWrapper $ldap) { + parent::__construct($ldap); + $this->configuration = $configuration; + if(is_null(Wizard::$l)) { + Wizard::$l = \OC_L10N::get('user_ldap'); + } + $this->result = new WizardResult; + } + + public function __destruct() { + if($this->result->hasChanges()) { + $this->configuration->saveConfiguration(); + } + } + + public function countGroups() { + if(!$this->checkRequirements(array('ldapHost', + 'ldapPort', + 'ldapAgentName', + 'ldapAgentPassword', + 'ldapBase', + ))) { + return false; + } + + $base = $this->configuration->ldapBase[0]; + $filter = $this->configuration->ldapGroupFilter; + \OCP\Util::writeLog('user_ldap', 'Wiz: g filter '. print_r($filter, true), \OCP\Util::DEBUG); + $l = \OC_L10N::get('user_ldap'); + if(empty($filter)) { + $output = $l->n('%s group found', '%s groups found', 0, array(0)); + $this->result->addChange('ldap_group_count', $output); + return $this->result; + } + $cr = $this->getConnection(); + if(!$cr) { + throw new \Excpetion('Could not connect to LDAP'); + } + $rr = $this->ldap->search($cr, $base, $filter, array('dn')); + if(!$this->ldap->isResource($rr)) { + return false; + } + $entries = $this->ldap->countEntries($cr, $rr); + $entries = ($entries !== false) ? $entries : 0; + $output = $l->n('%s group found', '%s groups found', $entries, $entries); + $this->result->addChange('ldap_group_count', $output); + + return $this->result; + } + + public function countUsers() { + if(!$this->checkRequirements(array('ldapHost', + 'ldapPort', + 'ldapAgentName', + 'ldapAgentPassword', + 'ldapBase', + 'ldapUserFilter', + ))) { + return false; + } + + $cr = $this->getConnection(); + if(!$cr) { + throw new \Excpetion('Could not connect to LDAP'); + } + + $base = $this->configuration->ldapBase[0]; + $filter = $this->configuration->ldapUserFilter; + $rr = $this->ldap->search($cr, $base, $filter, array('dn')); + if(!$this->ldap->isResource($rr)) { + return false; + } + $entries = $this->ldap->countEntries($cr, $rr); + $entries = ($entries !== false) ? $entries : 0; + $l = \OC_L10N::get('user_ldap'); + $output = $l->n('%s user found', '%s users found', $entries, $entries); + $this->result->addChange('ldap_user_count', $output); + + return $this->result; + } + + + public function determineAttributes() { + if(!$this->checkRequirements(array('ldapHost', + 'ldapPort', + 'ldapAgentName', + 'ldapAgentPassword', + 'ldapBase', + 'ldapUserFilter', + ))) { + return false; + } + + $attributes = $this->getUserAttributes(); + + natcasesort($attributes); + $attributes = array_values($attributes); + + $this->result->addOptions('ldap_loginfilter_attributes', $attributes); + + $selected = $this->configuration->ldapLoginFilterAttributes; + if(is_array($selected) && !empty($selected)) { + $this->result->addChange('ldap_loginfilter_attributes', $selected); + } + + return $this->result; + } + + /** + * @brief detects the available LDAP attributes + * @returns the instance's WizardResult instance + */ + private function getUserAttributes() { + if(!$this->checkRequirements(array('ldapHost', + 'ldapPort', + 'ldapAgentName', + 'ldapAgentPassword', + 'ldapBase', + 'ldapUserFilter', + ))) { + return false; + } + $cr = $this->getConnection(); + if(!$cr) { + throw new \Excpetion('Could not connect to LDAP'); + } + + $base = $this->configuration->ldapBase[0]; + $filter = $this->configuration->ldapUserFilter; + $rr = $this->ldap->search($cr, $base, $filter, array(), 1, 1); + if(!$this->ldap->isResource($rr)) { + return false; + } + $er = $this->ldap->firstEntry($cr, $rr); + $attributes = $this->ldap->getAttributes($cr, $er); + $pureAttributes = array(); + for($i = 0; $i < $attributes['count']; $i++) { + $pureAttributes[] = $attributes[$i]; + } + + return $pureAttributes; + } + + /** + * @brief detects the available LDAP groups + * @returns the instance's WizardResult instance + */ + public function determineGroupsForGroups() { + return $this->determineGroups('ldap_groupfilter_groups', + 'ldapGroupFilterGroups', + false); + } + + /** + * @brief detects the available LDAP groups + * @returns the instance's WizardResult instance + */ + public function determineGroupsForUsers() { + return $this->determineGroups('ldap_userfilter_groups', + 'ldapUserFilterGroups'); + } + + /** + * @brief detects the available LDAP groups + * @returns the instance's WizardResult instance + */ + private function determineGroups($dbkey, $confkey, $testMemberOf = true) { + if(!$this->checkRequirements(array('ldapHost', + 'ldapPort', + 'ldapAgentName', + 'ldapAgentPassword', + 'ldapBase', + ))) { + return false; + } + $cr = $this->getConnection(); + if(!$cr) { + throw new \Excpetion('Could not connect to LDAP'); + } + + $obclasses = array('posixGroup', 'group', '*'); + $this->determineFeature($obclasses, 'cn', $dbkey, $confkey); + + if($testMemberOf) { + $this->configuration->hasMemberOfFilterSupport = $this->testMemberOf(); + $this->result->markChange(); + if(!$this->configuration->hasMemberOfFilterSupport) { + throw new \Exception('memberOf is not supported by the server'); + } + } + + return $this->result; + } + + public function determineGroupMemberAssoc() { + if(!$this->checkRequirements(array('ldapHost', + 'ldapPort', + 'ldapAgentName', + 'ldapAgentPassword', + 'ldapGroupFilter', + ))) { + return false; + } + $attribute = $this->detectGroupMemberAssoc(); + if($attribute === false) { + return false; + } + $this->configuration->setConfiguration(array('ldapGroupMemberAssocAttr' => $attribute)); + //so it will be saved on destruct + $this->result->markChange(); + + return $this->result; + } + + /** + * @brief detects the available object classes + * @returns the instance's WizardResult instance + */ + public function determineGroupObjectClasses() { + if(!$this->checkRequirements(array('ldapHost', + 'ldapPort', + 'ldapAgentName', + 'ldapAgentPassword', + 'ldapBase', + ))) { + return false; + } + $cr = $this->getConnection(); + if(!$cr) { + throw new \Excpetion('Could not connect to LDAP'); + } + + $obclasses = array('group', 'posixGroup', '*'); + $this->determineFeature($obclasses, + 'objectclass', + 'ldap_groupfilter_objectclass', + 'ldapGroupFilterObjectclass', + false); + + return $this->result; + } + + /** + * @brief detects the available object classes + * @returns the instance's WizardResult instance + */ + public function determineUserObjectClasses() { + if(!$this->checkRequirements(array('ldapHost', + 'ldapPort', + 'ldapAgentName', + 'ldapAgentPassword', + 'ldapBase', + ))) { + return false; + } + $cr = $this->getConnection(); + if(!$cr) { + throw new \Excpetion('Could not connect to LDAP'); + } + + $obclasses = array('inetOrgPerson', 'person', 'organizationalPerson', + 'user', 'posixAccount', '*'); + $filter = $this->configuration->ldapUserFilter; + //if filter is empty, it is probably the first time the wizard is called + //then, apply suggestions. + $this->determineFeature($obclasses, + 'objectclass', + 'ldap_userfilter_objectclass', + 'ldapUserFilterObjectclass', + empty($filter)); + + return $this->result; + } + + public function getGroupFilter() { + if(!$this->checkRequirements(array('ldapHost', + 'ldapPort', + 'ldapAgentName', + 'ldapAgentPassword', + 'ldapBase', + ))) { + return false; + } + $filter = $this->composeLdapFilter(self::LFILTER_GROUP_LIST); + + $this->applyFind('ldap_group_filter', $filter); + return $this->result; + } + + public function getUserListFilter() { + if(!$this->checkRequirements(array('ldapHost', + 'ldapPort', + 'ldapAgentName', + 'ldapAgentPassword', + 'ldapBase', + ))) { + return false; + } + $filter = $this->composeLdapFilter(self::LFILTER_USER_LIST); + if(!$filter) { + throw new \Exception('Cannot create filter'); + } + + $this->applyFind('ldap_userlist_filter', $filter); + return $this->result; + } + + public function getUserLoginFilter() { + if(!$this->checkRequirements(array('ldapHost', + 'ldapPort', + 'ldapAgentName', + 'ldapAgentPassword', + 'ldapBase', + 'ldapUserFilter', + ))) { + return false; + } + $filter = $this->composeLdapFilter(self::LFILTER_LOGIN); + if(!$filter) { + throw new \Exception('Cannot create filter'); + } + + $this->applyFind('ldap_login_filter', $filter); + return $this->result; + } + + /** + * Tries to determine the port, requires given Host, User DN and Password + * @returns mixed WizardResult on success, false otherwise + */ + public function guessPortAndTLS() { + if(!$this->checkRequirements(array('ldapHost', + 'ldapAgentName', + 'ldapAgentPassword' + ))) { + return false; + } + $this->checkHost(); + $portSettings = $this->getPortSettingsToTry(); + + if(!is_array($portSettings)) { + throw new \Exception(print_r($portSettings, true)); + } + + //proceed from the best configuration and return on first success + foreach($portSettings as $setting) { + $p = $setting['port']; + $t = $setting['tls']; + \OCP\Util::writeLog('user_ldap', 'Wiz: trying port '. $p . ', TLS '. $t, \OCP\Util::DEBUG); + //connectAndBind may throw Exception, it needs to be catched by the + //callee of this method + if($this->connectAndBind($p, $t) === true) { + $config = array('ldapPort' => $p, + 'ldapTLS' => intval($t) + ); + $this->configuration->setConfiguration($config); + \OCP\Util::writeLog('user_ldap', 'Wiz: detected Port '. $p, \OCP\Util::DEBUG); + $this->result->addChange('ldap_port', $p); + $this->result->addChange('ldap_tls', intval($t)); + return $this->result; + } + } + + //custom port, undetected (we do not brute force) + return false; + } + + /** + * @brief tries to determine a base dn from User DN or LDAP Host + * @returns mixed WizardResult on success, false otherwise + */ + public function guessBaseDN() { + if(!$this->checkRequirements(array('ldapHost', + 'ldapAgentName', + 'ldapAgentPassword', + 'ldapPort', + ))) { + return false; + } + + //check whether a DN is given in the agent name (99.9% of all cases) + $base = null; + $i = stripos($this->configuration->ldapAgentName, 'dc='); + if($i !== false) { + $base = substr($this->configuration->ldapAgentName, $i); + if($this->testBaseDN($base)) { + $this->applyFind('ldap_base', $base); + return $this->result; + } + } + + //this did not help :( + //Let's see whether we can parse the Host URL and convert the domain to + //a base DN + $domain = Helper::getDomainFromURL($this->configuration->ldapHost); + if(!$domain) { + return false; + } + + $dparts = explode('.', $domain); + $base2 = implode('dc=', $dparts); + if($base !== $base2 && $this->testBaseDN($base2)) { + $this->applyFind('ldap_base', $base2); + return $this->result; + } + + return false; + } + + /** + * @brief sets the found value for the configuration key in the WizardResult + * as well as in the Configuration instance + * @param $key the configuration key + * @param $value the (detected) value + * @return null + * + */ + private function applyFind($key, $value) { + $this->result->addChange($key, $value); + $this->configuration->setConfiguration(array($key => $value)); + } + + /** + * @brief Checks, whether a port was entered in the Host configuration + * field. In this case the port will be stripped off, but also stored as + * setting. + */ + private function checkHost() { + $host = $this->configuration->ldapHost; + $hostInfo = parse_url($host); + + //removes Port from Host + if(is_array($hostInfo) && isset($hostInfo['port'])) { + $port = $hostInfo['port']; + $host = str_replace(':'.$port, '', $host); + $this->applyFind('ldap_host', $host); + $this->applyFind('ldap_port', $port); + } + } + + /** + * @brief tries to detect the group member association attribute which is + * one of 'uniqueMember', 'memberUid', 'member' + * @return mixed, string with the attribute name, false on error + */ + private function detectGroupMemberAssoc() { + $possibleAttrs = array('uniqueMember', 'memberUid', 'member', 'unfugasdfasdfdfa'); + $filter = $this->configuration->ldapGroupFilter; + if(empty($filter)) { + return false; + } + $cr = $this->getConnection(); + if(!$cr) { + throw new \Excpetion('Could not connect to LDAP'); + } + $base = $this->configuration->ldapBase[0]; + $rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs); + if(!$this->ldap->isResource($rr)) { + return false; + } + $er = $this->ldap->firstEntry($cr, $rr); + while(is_resource($er)) { + $dn = $this->ldap->getDN($cr, $er); + $attrs = $this->ldap->getAttributes($cr, $er); + $result = array(); + for($i = 0; $i < count($possibleAttrs); $i++) { + if(isset($attrs[$possibleAttrs[$i]])) { + $result[$possibleAttrs[$i]] = $attrs[$possibleAttrs[$i]]['count']; + } + } + if(!empty($result)) { + natsort($result); + return key($result); + } + + $er = $this->ldap->nextEntry($cr, $er); + } + + return false; + } + + /** + * @brief Checks whether for a given BaseDN results will be returned + * @param $base the BaseDN to test + * @return bool true on success, false otherwise + */ + private function testBaseDN($base) { + $cr = $this->getConnection(); + if(!$cr) { + throw new \Excpetion('Could not connect to LDAP'); + } + + //base is there, let's validate it. If we search for anything, we should + //get a result set > 0 on a proper base + $rr = $this->ldap->search($cr, $base, 'objectClass=*', array('dn'), 0, 1); + if(!$this->ldap->isResource($rr)) { + return false; + } + $entries = $this->ldap->countEntries($cr, $rr); + return ($entries !== false) && ($entries > 0); + } + + /** + * @brief Checks whether the server supports memberOf in LDAP Filter. + * Requires that groups are determined, thus internally called from within + * determineGroups() + * @return bool, true if it does, false otherwise + */ + private function testMemberOf() { + $cr = $this->getConnection(); + if(!$cr) { + throw new \Excpetion('Could not connect to LDAP'); + } + if(!is_array($this->configuration->ldapBase) + || !isset($this->configuration->ldapBase[0])) { + return false; + } + $base = $this->configuration->ldapBase[0]; + $filterPrefix = '(&(objectclass=*)(memberOf='; + $filterSuffix = '))'; + + foreach($this->resultCache as $dn => $properties) { + if(!isset($properties['cn'])) { + //assuming only groups have their cn cached :) + continue; + } + $filter = strtolower($filterPrefix . $dn . $filterSuffix); + $rr = $this->ldap->search($cr, $base, $filter, array('dn')); + if(!$this->ldap->isResource($rr)) { + continue; + } + $entries = $this->ldap->countEntries($cr, $rr); + //we do not know which groups are empty, so test any and return + //success on the first match that returns at least one user + if(($entries !== false) && ($entries > 0)) { + return true; + } + } + + return false; + } + + /** + * @brief creates an LDAP Filter from given configuration + * @param $filterType int, for which use case the filter shall be created + * can be any of self::LFILTER_USER_LIST, self::LFILTER_LOGIN or + * self::LFILTER_GROUP_LIST + * @return mixed, string with the filter on success, false otherwise + */ + private function composeLdapFilter($filterType) { + $filter = ''; + $parts = 0; + switch ($filterType) { + case self::LFILTER_USER_LIST: + $objcs = $this->configuration->ldapUserFilterObjectclass; + //glue objectclasses + if(is_array($objcs) && count($objcs) > 0) { + $filter .= '(|'; + foreach($objcs as $objc) { + $filter .= '(objectclass=' . $objc . ')'; + } + $filter .= ')'; + $parts++; + } + //glue group memberships + if($this->configuration->hasMemberOfFilterSupport) { + $cns = $this->configuration->ldapUserFilterGroups; + if(is_array($cns) && count($cns) > 0) { + $filter .= '(|'; + $cr = $this->getConnection(); + if(!$cr) { + throw new \Excpetion('Could not connect to LDAP'); + } + $base = $this->configuration->ldapBase[0]; + foreach($cns as $cn) { + $rr = $this->ldap->search($cr, $base, 'cn=' . $cn, array('dn')); + if(!$this->ldap->isResource($rr)) { + continue; + } + $er = $this->ldap->firstEntry($cr, $rr); + $dn = $this->ldap->getDN($cr, $er); + $filter .= '(memberof=' . $dn . ')'; + } + $filter .= ')'; + } + $parts++; + } + //wrap parts in AND condition + if($parts > 1) { + $filter = '(&' . $filter . ')'; + } + if(empty($filter)) { + $filter = '(objectclass=*)'; + } + break; + + case self::LFILTER_GROUP_LIST: + $objcs = $this->configuration->ldapGroupFilterObjectclass; + //glue objectclasses + if(is_array($objcs) && count($objcs) > 0) { + $filter .= '(|'; + foreach($objcs as $objc) { + $filter .= '(objectclass=' . $objc . ')'; + } + $filter .= ')'; + $parts++; + } + //glue group memberships + $cns = $this->configuration->ldapGroupFilterGroups; + if(is_array($cns) && count($cns) > 0) { + $filter .= '(|'; + $base = $this->configuration->ldapBase[0]; + foreach($cns as $cn) { + $filter .= '(cn=' . $cn . ')'; + } + $filter .= ')'; + } + $parts++; + //wrap parts in AND condition + if($parts > 1) { + $filter = '(&' . $filter . ')'; + } + break; + + case self::LFILTER_LOGIN: + $ulf = $this->configuration->ldapUserFilter; + $loginpart = '=%uid'; + $filterUsername = ''; + $userAttributes = $this->getUserAttributes(); + $userAttributes = array_change_key_case(array_flip($userAttributes)); + $parts = 0; + + $x = $this->configuration->ldapLoginFilterUsername; + if($this->configuration->ldapLoginFilterUsername === '1') { + $attr = ''; + if(isset($userAttributes['uid'])) { + $attr = 'uid'; + } else if(isset($userAttributes['samaccountname'])) { + $attr = 'samaccountname'; + } else if(isset($userAttributes['cn'])) { + //fallback + $attr = 'cn'; + } + if(!empty($attr)) { + $filterUsername = '(' . $attr . $loginpart . ')'; + $parts++; + } + } + + $filterEmail = ''; + if($this->configuration->ldapLoginFilterEmail === '1') { + $filterEmail = '(|(mailPrimaryAddress=%uid)(mail=%uid))'; + $parts++; + } + + $filterAttributes = ''; + $attrsToFilter = $this->configuration->ldapLoginFilterAttributes; + if(is_array($attrsToFilter) && count($attrsToFilter) > 0) { + $filterAttributes = '(|'; + foreach($attrsToFilter as $attribute) { + $filterAttributes .= '(' . $attribute . $loginpart . ')'; + } + $filterAttributes .= ')'; + $parts++; + } + + $filterLogin = ''; + if($parts > 1) { + $filterLogin = '(|'; + } + $filterLogin .= $filterUsername; + $filterLogin .= $filterEmail; + $filterLogin .= $filterAttributes; + if($parts > 1) { + $filterLogin .= ')'; + } + + $filter = '(&'.$ulf.$filterLogin.')'; + break; + } + + \OCP\Util::writeLog('user_ldap', 'Wiz: Final filter '.$filter, \OCP\Util::DEBUG); + + return $filter; + } + + /** + * Connects and Binds to an LDAP Server + * @param $port the port to connect with + * @param $tls whether startTLS is to be used + * @return + */ + private function connectAndBind($port = 389, $tls = false, $ncc = false) { + if($ncc) { + //No certificate check + //FIXME: undo afterwards + putenv('LDAPTLS_REQCERT=never'); + } + + //connect, does not really trigger any server communication + \OCP\Util::writeLog('user_ldap', 'Wiz: Checking Host Info ', \OCP\Util::DEBUG); + $host = $this->configuration->ldapHost; + $hostInfo = parse_url($host); + if(!$hostInfo) { + throw new \Exception($this->l->t('Invalid Host')); + } + if(isset($hostInfo['scheme'])) { + if(isset($hostInfo['port'])) { + //problem + } else { + $host .= ':' . $port; + } + } + \OCP\Util::writeLog('user_ldap', 'Wiz: Attempting to connect ', \OCP\Util::DEBUG); + $cr = $this->ldap->connect($host, $port); + if(!is_resource($cr)) { + throw new \Exception($this->l->t('Invalid Host')); + } + + \OCP\Util::writeLog('user_ldap', 'Wiz: Setting LDAP Options ', \OCP\Util::DEBUG); + //set LDAP options + $a = $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3); + $c = $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT); + if($tls) { + $this->ldap->startTls($cr); + } + + \OCP\Util::writeLog('user_ldap', 'Wiz: Attemping to Bind ', \OCP\Util::DEBUG); + //interesting part: do the bind! + $login = $this->ldap->bind($cr, + $this->configuration->ldapAgentName, + $this->configuration->ldapAgentPassword); + + if($login === true) { + $this->ldap->unbind($cr); + if($ncc) { + throw new \Exception('Certificate cannot be validated.'); + } + \OCP\Util::writeLog('user_ldap', 'Wiz: Bind succesfull with Port '. $port, \OCP\Util::DEBUG); + return true; + } + + $errno = $this->ldap->errno($cr); + $error = ldap_error($cr); + $this->ldap->unbind($cr); + if($errno === -1 || ($errno === 2 && $ncc)) { + //host, port or TLS wrong + return false; + } else if ($errno === 2) { + return $this->connectAndBind($port, $tls, true); + } + throw new \Exception($error); + } + + private function checkRequirements($reqs) { + foreach($reqs as $option) { + $value = $this->configuration->$option; + if(empty($value)) { + return false; + } + } + return true; + } + + /** + * @brief does a cumulativeSearch on LDAP to get different values of a + * specified attribute + * @param $filters array, the filters that shall be used in the search + * @param $attr the attribute of which a list of values shall be returned + * @param $lfw bool, whether the last filter is a wildcard which shall not + * be processed if there were already findings, defaults to true + * @param $maxF string. if not null, this variable will have the filter that + * yields most result entries + * @return mixed, an array with the values on success, false otherwise + * + */ + private function cumulativeSearchOnAttribute($filters, $attr, $lfw = true, &$maxF = null) { + $dnRead = array(); + $foundItems = array(); + $maxEntries = 0; + if(!is_array($this->configuration->ldapBase) + || !isset($this->configuration->ldapBase[0])) { + return false; + } + $base = $this->configuration->ldapBase[0]; + $cr = $this->getConnection(); + if(!is_resource($cr)) { + return false; + } + foreach($filters as $filter) { + if($lfw && count($foundItems) > 0) { + continue; + } + $rr = $this->ldap->search($cr, $base, $filter, array($attr)); + if(!$this->ldap->isResource($rr)) { + continue; + } + $entries = $this->ldap->countEntries($cr, $rr); + $getEntryFunc = 'firstEntry'; + if(($entries !== false) && ($entries > 0)) { + if(!is_null($maxF) && $entries > $maxEntries) { + $maxEntries = $entries; + $maxF = $filter; + } + do { + $entry = $this->ldap->$getEntryFunc($cr, $rr); + if(!$this->ldap->isResource($entry)) { + continue 2; + } + $attributes = $this->ldap->getAttributes($cr, $entry); + $dn = $this->ldap->getDN($cr, $entry); + if($dn === false || in_array($dn, $dnRead)) { + continue; + } + $newItems = array(); + $state = $this->getAttributeValuesFromEntry($attributes, + $attr, + $newItems); + $foundItems = array_merge($foundItems, $newItems); + $this->resultCache[$dn][$attr] = $newItems; + $dnRead[] = $dn; + $getEntryFunc = 'nextEntry'; + $rr = $entry; //will be expected by nextEntry next round + } while($state === self::LRESULT_PROCESSED_SKIP + || $this->ldap->isResource($entry)); + } + } + + return array_unique($foundItems); + } + + /** + * @brief determines if and which $attr are available on the LDAP server + * @param $objectclasses the objectclasses to use as search filter + * @param $attr the attribute to look for + * @param $dbkey the dbkey of the setting the feature is connected to + * @param $confkey the confkey counterpart for the $dbkey as used in the + * Configuration class + * @param $po boolean, whether the objectClass with most result entries + * shall be pre-selected via the result + * @returns array, list of found items. + */ + private function determineFeature($objectclasses, $attr, $dbkey, $confkey, $po = false) { + $cr = $this->getConnection(); + if(!$cr) { + throw new \Excpetion('Could not connect to LDAP'); + } + $p = 'objectclass='; + foreach($objectclasses as $key => $value) { + $objectclasses[$key] = $p.$value; + } + $maxEntryObjC = ''; + $availableFeatures = + $this->cumulativeSearchOnAttribute($objectclasses, $attr, + true, $maxEntryObjC); + if(is_array($availableFeatures) + && count($availableFeatures) > 0) { + natcasesort($availableFeatures); + //natcasesort keeps indices, but we must get rid of them for proper + //sorting in the web UI. Therefore: array_values + $this->result->addOptions($dbkey, array_values($availableFeatures)); + } else { + throw new \Exception(self::$l->t('Could not find the desired feature')); + } + + $setFeatures = $this->configuration->$confkey; + if(is_array($setFeatures) && !empty($setFeatures)) { + //something is already configured? pre-select it. + $this->result->addChange($dbkey, $setFeatures); + } else if($po && !empty($maxEntryObjC)) { + //pre-select objectclass with most result entries + $maxEntryObjC = str_replace($p, '', $maxEntryObjC); + $this->applyFind($dbkey, $maxEntryObjC); + $this->result->addChange($dbkey, $maxEntryObjC); + } + + return $availableFeatures; + } + + /** + * @brief appends a list of values fr + * @param $result resource, the return value from ldap_get_attributes + * @param $attribute string, the attribute values to look for + * @param &$known array, new values will be appended here + * @return int, state on of the class constants LRESULT_PROCESSED_OK, + * LRESULT_PROCESSED_INVALID or LRESULT_PROCESSED_SKIP + */ + private function getAttributeValuesFromEntry($result, $attribute, &$known) { + if(!is_array($result) + || !isset($result['count']) + || !$result['count'] > 0) { + return self::LRESULT_PROCESSED_INVALID; + } + + //strtolower on all keys for proper comparison + $result = \OCP\Util::mb_array_change_key_case($result); + $attribute = strtolower($attribute); + if(isset($result[$attribute])) { + foreach($result[$attribute] as $key => $val) { + if($key === 'count') { + continue; + } + if(!in_array($val, $known)) { + $known[] = $val; + } + } + return self::LRESULT_PROCESSED_OK; + } else { + return self::LRESULT_PROCESSED_SKIP; + } + } + + private function getConnection() { + if(!is_null($this->cr)) { + return $cr; + } + $cr = $this->ldap->connect( + $this->configuration->ldapHost.':'.$this->configuration->ldapPort, + $this->configuration->ldapPort); + + $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3); + $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT); + if($this->configuration->ldapTLS === 1) { + $this->ldap->startTls($cr); + } + + $lo = @$this->ldap->bind($cr, + $this->configuration->ldapAgentName, + $this->configuration->ldapAgentPassword); + if($lo === true) { + $this->$cr = $cr; + return $cr; + } + + return false; + } + + private function getDefaultLdapPortSettings() { + static $settings = array( + array('port' => 7636, 'tls' => false), + array('port' => 636, 'tls' => false), + array('port' => 7389, 'tls' => true), + array('port' => 389, 'tls' => true), + array('port' => 7389, 'tls' => false), + array('port' => 389, 'tls' => false), + ); + return $settings; + } + + private function getPortSettingsToTry() { + //389 ← LDAP / Unencrypted or StartTLS + //636 ← LDAPS / SSL + //7xxx ← UCS. need to be checked first, because both ports may be open + $host = $this->configuration->ldapHost; + $port = intval($this->configuration->ldapPort); + $portSettings = array(); + + //In case the port is already provided, we will check this first + if($port > 0) { + $hostInfo = parse_url($host); + if(is_array($hostInfo) + && isset($hostInfo['scheme']) + && stripos($hostInfo['scheme'], 'ldaps') === false) { + $portSettings[] = array('port' => $port, 'tls' => true); + } + $portSettings[] =array('port' => $port, 'tls' => false); + } + + //default ports + $portSettings = array_merge($portSettings, + $this->getDefaultLdapPortSettings()); + + return $portSettings; + } + + +} \ No newline at end of file diff --git a/apps/user_ldap/lib/wizardresult.php b/apps/user_ldap/lib/wizardresult.php new file mode 100644 index 0000000000..542f106cad --- /dev/null +++ b/apps/user_ldap/lib/wizardresult.php @@ -0,0 +1,58 @@ +. + * + */ + +namespace OCA\user_ldap\lib; + +class WizardResult { + protected $changes = array(); + protected $options = array(); + protected $markedChange = false; + + public function addChange($key, $value) { + $this->changes[$key] = $value; + } + + public function markChange() { + $this->markedChange = true; + } + + public function addOptions($key, $values) { + if(!is_array($values)) { + $values = array($values); + } + $this->options[$key] = $values; + } + + public function hasChanges() { + return (count($this->changes) > 0 || $this->markedChange); + } + + public function getResultArray() { + $result = array(); + $result['changes'] = $this->changes; + if(count($this->options) > 0) { + $result['options'] = $this->options; + } + return $result; + } +} \ No newline at end of file diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php index f20bc19118..d077eafdde 100644 --- a/apps/user_ldap/settings.php +++ b/apps/user_ldap/settings.php @@ -25,19 +25,50 @@ OC_Util::checkAdminUser(); -OCP\Util::addscript('user_ldap', 'settings'); -OCP\Util::addstyle('user_ldap', 'settings'); +OCP\Util::addScript('user_ldap', 'settings'); +OCP\Util::addScript('core', 'jquery.multiselect'); +OCP\Util::addStyle('user_ldap', 'settings'); +OCP\Util::addStyle('core', 'jquery.multiselect'); +OCP\Util::addStyle('core', 'jquery-ui-1.10.0.custom'); // fill template $tmpl = new OCP\Template('user_ldap', 'settings'); $prefixes = \OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes(); $hosts = \OCA\user_ldap\lib\Helper::getServerConfigurationHosts(); -$tmpl->assign('serverConfigurationPrefixes', $prefixes); -$tmpl->assign('serverConfigurationHosts', $hosts); + +$wizardHtml = ''; +$toc = array(); + +$wControls = new OCP\Template('user_ldap', 'part.wizardcontrols'); +$wControls = $wControls->fetchPage(); +$sControls = new OCP\Template('user_ldap', 'part.settingcontrols'); +$sControls = $sControls->fetchPage(); + +$wizTabs = array(); +$wizTabs[] = array('tpl' => 'part.wizard-server', 'cap' => 'Server'); +$wizTabs[] = array('tpl' => 'part.wizard-userfilter', 'cap' => 'User Filter'); +$wizTabs[] = array('tpl' => 'part.wizard-loginfilter', 'cap' => 'Login Filter'); +$wizTabs[] = array('tpl' => 'part.wizard-groupfilter', 'cap' => 'Group Filter'); + +for($i = 0; $i < count($wizTabs); $i++) { + $tab = new OCP\Template('user_ldap', $wizTabs[$i]['tpl']); + if($i === 0) { + $tab->assign('serverConfigurationPrefixes', $prefixes); + $tab->assign('serverConfigurationHosts', $hosts); + } + $tab->assign('wizardControls', $wControls); + $wizardHtml .= $tab->fetchPage(); + $toc['#ldapWizard'.($i+1)] = $wizTabs[$i]['cap']; +} + +$tmpl->assign('tabs', $wizardHtml); +$tmpl->assign('toc', $toc); +$tmpl->assign('settingControls', $sControls); // assign default values -$defaults = \OCA\user_ldap\lib\Connection::getDefaults(); +$config = new \OCA\user_ldap\lib\Configuration('', false); +$defaults = $config->getDefaults(); foreach($defaults as $key => $default) { $tmpl->assign($key.'_default', $default); } diff --git a/apps/user_ldap/templates/part.settingcontrols.php b/apps/user_ldap/templates/part.settingcontrols.php new file mode 100644 index 0000000000..017f21c8b1 --- /dev/null +++ b/apps/user_ldap/templates/part.settingcontrols.php @@ -0,0 +1,12 @@ +
+ + + + + t('Help'));?> + +
\ No newline at end of file diff --git a/apps/user_ldap/templates/part.wizard-groupfilter.php b/apps/user_ldap/templates/part.wizard-groupfilter.php new file mode 100644 index 0000000000..0cc4dfa572 --- /dev/null +++ b/apps/user_ldap/templates/part.wizard-groupfilter.php @@ -0,0 +1,42 @@ +
+
+

+ t('Limit the access to %s to groups meeting this criteria:', $theme->getName()));?> +

+

+ + + +

+

+ + + +

+

+ +

+ +

+

+

+

+ 0 t('groups found'));?> +

+ +
+
\ No newline at end of file diff --git a/apps/user_ldap/templates/part.wizard-loginfilter.php b/apps/user_ldap/templates/part.wizard-loginfilter.php new file mode 100644 index 0000000000..d4a36eb0cb --- /dev/null +++ b/apps/user_ldap/templates/part.wizard-loginfilter.php @@ -0,0 +1,37 @@ +
+
+

+ t('What attribute shall be used as login name:'));?> +

+

+ + + +

+

+ + + +

+

+ + + +

+

+

+

+ + +
+
\ No newline at end of file diff --git a/apps/user_ldap/templates/part.wizard-server.php b/apps/user_ldap/templates/part.wizard-server.php new file mode 100644 index 0000000000..01dd8d0fcb --- /dev/null +++ b/apps/user_ldap/templates/part.wizard-server.php @@ -0,0 +1,71 @@ +
+

+ + +

+ +
+
+
+
+ + + + +
+
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
\ No newline at end of file diff --git a/apps/user_ldap/templates/part.wizard-userfilter.php b/apps/user_ldap/templates/part.wizard-userfilter.php new file mode 100644 index 0000000000..c1d522ce2a --- /dev/null +++ b/apps/user_ldap/templates/part.wizard-userfilter.php @@ -0,0 +1,42 @@ +
+
+

+ t('Limit the access to %s to users meeting this criteria:', $theme->getName()));?> +

+

+ + + +

+

+ + + +

+

+ +

+ +

+

+

+

+ 0 t('users found'));?> +

+ +
+
\ No newline at end of file diff --git a/apps/user_ldap/templates/part.wizardcontrols.php b/apps/user_ldap/templates/part.wizardcontrols.php new file mode 100644 index 0000000000..db99145d51 --- /dev/null +++ b/apps/user_ldap/templates/part.wizardcontrols.php @@ -0,0 +1,15 @@ +
+ + + + + t('Help'));?> + +
\ No newline at end of file diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php index 2530d9c04c..22aab0186f 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -1,9 +1,11 @@
'.$l->t('Warning: Apps user_ldap and user_webdavauth are incompatible. You may experience unexpected behavior. Please ask your system administrator to disable one of them.').'

'); @@ -12,65 +14,19 @@ print_unescaped('

'.$l->t('Warning: The PHP LDAP module is not installed, the backend will not work. Please ask your system administrator to install it.').'

'); } ?> +
-

- - -

-

-

-

-

-

-

-

-

-

- " />

-

- " />

-

- " />

-
-

t('Connection Settings'));?>

-

+

+ " />

-

>


@@ -93,8 +49,9 @@

+
-
+

t('Internal Username'));?>

t('By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [ a-zA-Z0-9_.@- ]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder. It is also a part of remote URLs, for instance for all *DAV services. With this setting, the default behavior can be overridden. To achieve a similar behavior as before ownCloud 5 enter the user display name attribute in the following field. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users.'));?>

@@ -105,8 +62,8 @@

t('Username-LDAP User Mapping'));?>

t('Usernames are used to store and assign (meta) data. In order to precisely identify and recognize users, each LDAP user will have a internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage.'));?>


+
- t('Help'));?>
diff --git a/core/css/share.css b/core/css/share.css index 2a21dc6edf..d8140242e0 100644 --- a/core/css/share.css +++ b/core/css/share.css @@ -21,28 +21,41 @@ padding:.5em; } -#shareWithList li { - padding-top:.1em; -} + #shareWithList li { + padding-top: 10px; + padding-bottom: 10px; + font-weight: bold; + line-height: 21px; + white-space: normal; + } -#shareWithList li:first-child { - white-space:normal; -} - -#shareWithList .cruds { - margin-left:-10px; -} - -#shareWithList .unshare img, #shareWithList .showCruds img { - vertical-align:text-bottom; /* properly align icons */ -} + #shareWithList .unshare img, #shareWithList .showCruds img { + vertical-align:text-bottom; /* properly align icons */ + } + #shareWithList label input[type=checkbox]{ + margin-left: 0; + } + #shareWithList .username{ + padding-right: .5em; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 254px; + display: inline-block; + overflow: hidden; + vertical-align: middle; + } + #shareWithList li label{ + margin-right: .5em; + } #dropdown label { font-weight:400; + white-space: nowrap; } #dropdown input[type="checkbox"] { margin:0 .2em 0 .5em; + vertical-align: middle; } a.showCruds { @@ -99,3 +112,9 @@ a.showCruds:hover,a.unshare:hover { overflow-y:auto; overflow-x:hidden; } + +.notCreatable { + padding-left: 12px; + padding-top: 12px; + color: #999; +} diff --git a/core/css/styles.css b/core/css/styles.css index 62ee0e56ca..868829b1c5 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -202,7 +202,7 @@ input[type="submit"].enabled { -moz-box-sizing:border-box; box-sizing:border-box; } #leftcontent, .leftcontent { - position:relative; overflow:auto; width:20em; height:100%; + position:relative; overflow:auto; width:256px; height:100%; background:#f8f8f8; border-right:1px solid #ddd; -moz-box-sizing:border-box; box-sizing:border-box; } @@ -211,7 +211,11 @@ input[type="submit"].enabled { #leftcontent li.active, .leftcontent li.active { font-weight:bold; } #leftcontent li:hover, .leftcontent li:hover { color:#333; background:#ddd; } #leftcontent a { height:100%; display:block; margin:0; padding:0 1em 0 0; float:left; } -#rightcontent, .rightcontent { position:fixed; top:6.4em; left:24.5em; overflow:auto } +#rightcontent, .rightcontent { position:fixed; top:89px; left: 336px; overflow:auto } + +#controls + .leftcontent{ + top: 44px; +} #emptycontent { font-size: 1.5em; @@ -441,6 +445,11 @@ label.infield { cursor:text !important; top:1.05em; left:.85em; } cursor: default; } +#body-login .update { + text-align: center; + color: #ccc; +} + #body-user .warning, #body-settings .warning { margin-top: 8px; padding: 5px; diff --git a/core/js/jquery.avatar.js b/core/js/jquery.avatar.js index 0006810172..dbab032b97 100644 --- a/core/js/jquery.avatar.js +++ b/core/js/jquery.avatar.js @@ -60,7 +60,7 @@ if (typeof(this.data('user')) !== 'undefined') { user = this.data('user'); } else { - this.placeholder('x'); + this.imageplaceholder('x'); return; } } @@ -76,9 +76,9 @@ if (typeof(result) === 'object') { if (!hidedefault) { if (result.data && result.data.displayname) { - $div.placeholder(user, result.data.displayname); + $div.imageplaceholder(user, result.data.displayname); } else { - $div.placeholder(user); + $div.imageplaceholder(user); } } else { $div.hide(); diff --git a/core/js/jquery.placeholder.js b/core/js/jquery.placeholder.js new file mode 100644 index 0000000000..689462582b --- /dev/null +++ b/core/js/jquery.placeholder.js @@ -0,0 +1,216 @@ +/* + jQuery placeholder plugin + by Andrey Kuzmin, @unsoundscapes + + Based on existing plugin http://mths.be/placeholder by @mathias + and this demo http://robertnyman.com/2011/05/02/ by @robertnyman + + Adopted to toggle placeholder on user input instead of focus + + Released under the MIT license +*/ + +(function (factory) { + 'use strict'; + + if (typeof define === 'function' && define.amd) { + // AMD. Register as anonymous module. + define(['jquery'], factory) + } else { + // Browser globals. + factory(jQuery) + } +}(function ($) { + 'use strict'; + + var isInputSupported = 'placeholder' in document.createElement('input') + , isTextareaSupported = 'placeholder' in document.createElement('textarea') + , $placeholders = $() + + function getAttributes (element) { + // Return an object of element attributes + var newAttrs = {} + , rinlinejQuery = /^jQuery\d+$/ + + $.each(element.attributes, function () { + if (this.specified && !rinlinejQuery.test(this.name)) { + newAttrs[this.name] = this.value + } + }) + return newAttrs + } + + function setCaretTo (element, index) { + // Set caret to specified @index + if (element.createTextRange) { + var range = element.createTextRange() + range.move('character', index) + range.select() + } else if (element.selectionStart !== null) { + element.focus() + element.setSelectionRange(index, index) + } + } + + + function Placeholder (element, options) { + this.options = options || {} + this.$replacement = this.$element = $(element) + this.initialize.apply(this, arguments) + // Cache all elements with placeholders + $placeholders = $placeholders.add(element) + } + + Placeholder.prototype = { + + initialize: function () { + this.isHidden = true + this.placeholderAttr = this.$element.attr('placeholder') + // do not mess with default behavior + this.$element.removeAttr('placeholder') + this.isPassword = this.$element.is('[type=password]') + if (this.isPassword) this.makeReplacement() + this.$replacement.on({ + 'keydown.placeholder': $.proxy(this.hide, this) + , 'focus.placeholder drop.placeholder click.placeholder': $.proxy(this.setCaret, this) + }) + this.$element.on({ + 'blur.placeholder keyup.placeholder': $.proxy(this.show, this) + }) + this.show() + } + + // Set or get input value + // Setting value toggles placeholder + , val: function (value) { + if (value === undefined) { + return this.isHidden ? this.$element[0].value : ''; + } + if (value === '') { + if (this.isHidden) { + this.$element[0].value = value + this.show() + } + } else { + if (!this.isHidden) this.hide() + this.$element[0].value = value + } + return this + } + + // Hide placeholder at user input + , hide: function (e) { + var isActiveElement = this.$replacement.is(':focus') + if (this.isHidden) return; + if (!e || !(e.shiftKey && e.keyCode === 16) && e.keyCode !== 9) { + this.isHidden = true + if (this.isPassword) { + this.$replacement.before(this.$element.show()).hide() + if (isActiveElement) this.$element.focus() + } else { + this.$element[0].value = '' + this.$element.removeClass(this.options.className) + } + } + } + + // Show placeholder on blur and keyup + , show: function (e) { + var isActiveElement = this.$element.is(':focus') + if (!this.isHidden) return; + if (this.$element[0].value === '') { + this.isHidden = false + if (this.isPassword) { + this.$element.before(this.$replacement.show()).hide() + if (isActiveElement) this.$replacement.focus() + } else { + this.$element[0].value = this.placeholderAttr + this.$element.addClass(this.options.className) + if (isActiveElement) this.setCaret(e) + } + } + } + + // Set caret at the beginning of the input + , setCaret: function (e) { + if (e && !this.isHidden) { + setCaretTo(this.$replacement[0], 0) + e.preventDefault() + } + } + + // Make and return replacement element + , makeReplacement: function () { + // we can't use $.fn.clone because ie <= 8 doesn't allow type change + var replacementAttributes = + $.extend( + getAttributes(this.$element[0]) + , { 'type': 'text' + , 'value': this.placeholderAttr + } + ) + + // replacement should not have input name + delete replacementAttributes.name + + this.$replacement = $('', replacementAttributes) + .data('placeholder', this) + .addClass(this.options.className) + + return this.$replacement; + } + + } + + + // Override jQuery val and prop hooks + $.valHooks.input = $.valHooks.textarea = $.propHooks.value = { + get: function (element) { + var placeholder = $(element).data('placeholder') + return placeholder ? placeholder.val() : element.value; + } + , set: function (element, value) { + var placeholder = $(element).data('placeholder') + return placeholder ? placeholder.val(value) : element.value = value; + } + } + + + // Plugin definition + $.fn.placeholder = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('placeholder') + , options = $.extend({}, $.fn.placeholder.defaults, typeof option === 'object' && option) + + if (!data && $this.is('[placeholder]') && (options.force || + !isInputSupported && $this.is('input') || + !isTextareaSupported && $this.is('textarea'))) { + $this.data('placeholder', data = new Placeholder(this, options)) + } + + if (data && typeof option === 'string') data[option]() + }) + } + $.fn.placeholder.defaults = { + force: false + , className: 'placeholder' + } + $.fn.placeholder.Constructor = Placeholder + + + // Events + $(document).on('submit.placeholder', 'form', function () { + // Clear the placeholder values so they don't get submitted + $placeholders.placeholder('hide') + // And then restore them back + setTimeout(function () { $placeholders.placeholder('show') }, 10) + }) + $(window).on('beforeunload.placeholder', function () { + // Clear placeholders upon page reload + $placeholders.placeholder('hide') + }) + + return Placeholder + +})); diff --git a/core/js/js.js b/core/js/js.js index c17e3fa295..f5991cfc9d 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -933,7 +933,7 @@ jQuery.fn.selectRange = function(start, end) { */ jQuery.fn.exists = function(){ return this.length > 0; -} +}; /** * Calls the server periodically every 15 mins to ensure that session doesnt diff --git a/core/js/octemplate.js b/core/js/octemplate.js index 46ffa97657..aab705059d 100644 --- a/core/js/octemplate.js +++ b/core/js/octemplate.js @@ -82,7 +82,7 @@ } ); } catch(e) { - console.error(e, 'data:', data) + console.error(e, 'data:', data); } }, options: { diff --git a/core/js/placeholder.js b/core/js/placeholder.js index ee2a8ce84c..47cff780d2 100644 --- a/core/js/placeholder.js +++ b/core/js/placeholder.js @@ -30,7 +30,7 @@ * * And call this from Javascript: * - * $('#albumart').placeholder('The Album Title'); + * $('#albumart').imageplaceholder('The Album Title'); * * Which will result in: * @@ -38,7 +38,7 @@ * * You may also call it like this, to have a different background, than the seed: * - * $('#albumart').placeholder('The Album Title', 'Album Title'); + * $('#albumart').imageplaceholder('The Album Title', 'Album Title'); * * Resulting in: * @@ -47,7 +47,7 @@ */ (function ($) { - $.fn.placeholder = function(seed, text) { + $.fn.imageplaceholder = function(seed, text) { // set optional argument "text" to value of "seed" if undefined text = text || seed; diff --git a/core/js/share.js b/core/js/share.js index 281cccaaef..ff557652b6 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -200,13 +200,13 @@ OC.Share={ } }); - html += ''; + html += ''; html += '
    '; html += '
'; var linksAllowed = $('#allowShareWithLink').val() === 'yes'; if (link && linksAllowed) { html += '