diff options
author | Bjoern Schiessle <schiessle@owncloud.com> | 2013-10-07 11:00:56 +0200 |
---|---|---|
committer | Bjoern Schiessle <schiessle@owncloud.com> | 2013-10-07 11:00:56 +0200 |
commit | 4f67b7a6c34d8e4ff36540ba155c75fb8e44eb7e (patch) | |
tree | 1257c3979e44c421adbf45bfbcc0aa1c4402e698 /apps | |
parent | 0346167bb2ab3c26fed26415470d52c21110d70e (diff) | |
parent | fd34c969d233724399651f6481f32d5c7c1ee18b (diff) | |
download | nextcloud-server-4f67b7a6c34d8e4ff36540ba155c75fb8e44eb7e.tar.gz nextcloud-server-4f67b7a6c34d8e4ff36540ba155c75fb8e44eb7e.zip |
Merge branch 'master' into remove_deleted_shares_from_db
Diffstat (limited to 'apps')
51 files changed, 2422 insertions, 400 deletions
diff --git a/apps/files/ajax/rawlist.php b/apps/files/ajax/rawlist.php index 531481a84c0..40da32b223a 100644 --- a/apps/files/ajax/rawlist.php +++ b/apps/files/ajax/rawlist.php @@ -3,10 +3,6 @@ // only need filesystem apps $RUNTIME_APPTYPES=array('filesystem'); -// Init owncloud - -require_once 'lib/template.php'; - OCP\JSON::checkLoggedIn(); // Load the files diff --git a/apps/files/css/files.css b/apps/files/css/files.css index b3ecd1dab93..138b15db04f 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -7,68 +7,54 @@ .actions input, .actions button, .actions .button { margin:0; float:left; } .actions .button a { color: #555; } .actions .button a:hover, .actions .button a:active { color: #333; } -#new { - height:17px; margin:0 0 0 1em; z-index:1010; float:left; +#new, #trash { + z-index: 1010; + float: left; + padding: 0 !important; /* override default control bar button padding */ +} +#trash { + margin: 0 1em; + float: right; +} +#new>a, #trash>a { + padding: 14px 10px; + position: relative; + top: 7px; +} +#new.active { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom: none; } -#new.active { border-bottom-left-radius:0; border-bottom-right-radius:0; border-bottom:none; } -#new>a { padding:.5em 1.2em .3em; } #new>ul { - display:none; position:fixed; min-width:7em; z-index:10; - padding:.5em; padding-bottom:0; margin-top:.075em; margin-left:-.5em; + display: none; + position: fixed; + min-width: 7em; + z-index: 10; + padding: .5em; + padding-bottom: 0; + margin-top: 14px; + margin-left: -1px; text-align:left; - background:#f8f8f8; border:1px solid #ddd; border-radius:10px; border-top-left-radius:0; + background: #f8f8f8; + border: 1px solid #ddd; + border-radius: 5px; + border-top-left-radius: 0; box-shadow:0 2px 7px rgba(170,170,170,.4); } #new>ul>li { height:36px; margin:.3em; padding-left:3em; padding-bottom:0.1em; background-repeat:no-repeat; cursor:pointer; } #new>ul>li>p { cursor:pointer; padding-top: 7px; padding-bottom: 7px;} -#new>ul>li>form>input { - padding: 5px; - margin: 2px 0; -} -#trash { margin: 0 1em; z-index:1010; float: right; } - -#upload { - height:27px; padding:0; margin-left:0.2em; overflow:hidden; -} -#upload a { - position:relative; display:block; width:100%; height:27px; - cursor:pointer; z-index:10; - background-image:url('%webroot%/core/img/actions/upload.svg'); - background-repeat:no-repeat; - background-position:7px 6px; - opacity:0.65; -} -.file_upload_target { display:none; } -.file_upload_form { display:inline; float:left; margin:0; padding:0; cursor:pointer; overflow:visible; } -#file_upload_start { - left:0; top:0; width:28px; height:27px; padding:0; - font-size:1em; - -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter:alpha(opacity=0); opacity:0; - z-index:20; position:relative; cursor:pointer; overflow:hidden; -} - -#uploadprogresswrapper { - position: relative; - display: inline; -} -#uploadprogressbar { - position:relative; - float: left; - margin-left: 12px; - width: 130px; - height: 26px; - display:inline-block; -} -#uploadprogressbar + stop { - font-size: 13px; -} /* FILE TABLE */ -#filestable { position: relative; top:37px; width:100%; } +#filestable { + position: relative; + top: 44px; + width: 100%; +} #filestable tbody tr { background-color:#fff; height:2.5em; } #filestable tbody tr:hover, tbody tr:active { background-color: rgb(240,240,240); @@ -122,9 +108,18 @@ table th#headerDate, table td.date { /* Multiselect bar */ #filestable.multiselect { - top: 88px; + top: 95px; +} +table.multiselect thead { + position: fixed; + top: 89px; + z-index: 1; + -moz-box-sizing: border-box; + box-sizing: border-box; + left: 0; + padding-left: 80px; + width: 100%; } -table.multiselect thead { position:fixed; top:82px; z-index:1; -moz-box-sizing: border-box; box-sizing: border-box; left: 0; padding-left: 80px; width:100%; } table.multiselect thead th { background-color: rgba(210,210,210,.7); @@ -228,6 +223,12 @@ table td.filename form { font-size:.85em; margin-left:3em; margin-right:3em; } -webkit-transition:background-image 500ms; -moz-transition:background-image 500ms; -o-transition:background-image 500ms; transition:background-image 500ms; } +#fileList tr td.filename a.name label { + position: absolute; + width: 100%; + height: 50px; +} + #uploadsize-message,#delete-confirm { display:none; } /* File actions */ @@ -319,8 +320,6 @@ a.action>img { max-height:16px; max-width:16px; vertical-align:text-bottom; } #scanning-message{ top:40%; left:40%; position:absolute; display:none; } -div.crumb a{ padding:0.9em 0 0.7em 0; color:#555; } - table.dragshadow { width:auto; } diff --git a/apps/files/css/upload.css b/apps/files/css/upload.css index 2d11e41ba88..ef043569094 100644 --- a/apps/files/css/upload.css +++ b/apps/files/css/upload.css @@ -1,38 +1,63 @@ - #upload { - height:27px; padding:0; margin-left:0.2em; overflow:hidden; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + height: 36px; + width: 39px; + padding: 0 !important; /* override default control bar button padding */ + margin-left: .2em; + overflow: hidden; vertical-align: top; } #upload a { - position:relative; display:block; width:100%; height:27px; - cursor:pointer; z-index:10; - background-image:url('%webroot%/core/img/actions/upload.svg'); - background-repeat:no-repeat; - background-position:7px 6px; - opacity:0.65; + position: relative; + display: block; + width: 100%; + height: 44px; + width: 44px; + margin: -5px -3px; + cursor: pointer; + z-index: 10; + background-image: url('%webroot%/core/img/actions/upload.svg'); + background-repeat: no-repeat; + background-position: center; + opacity: .65; } .file_upload_target { display:none; } .file_upload_form { display:inline; float:left; margin:0; padding:0; cursor:pointer; overflow:visible; } #file_upload_start { - float: left; - left:0; top:0; width:28px; height:27px; padding:0; - font-size:1em; + position: relative; + left: 0; + top: 0; + width: 44px; + height: 44px; + margin: -5px -3px; + padding: 0; + font-size: 1em; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter:alpha(opacity=0); opacity:0; - z-index:20; position:relative; cursor:pointer; overflow:hidden; + z-index: 20; + cursor: pointer; + overflow: hidden; } #uploadprogresswrapper { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; display: inline-block; vertical-align: top; - margin:0.3em; - height: 29px; + height: 36px; + box-sizing: border-box; +} +#uploadprogresswrapper > input[type='button'] { + height: 36px; } #uploadprogressbar { position:relative; float: left; margin-left: 12px; width: 130px; - height: 26px; + height: 36px; display:inline-block; } #uploadprogressbar + stop { diff --git a/apps/files/index.php b/apps/files/index.php index 6f22fdfdc19..8d877be8ac9 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -104,8 +104,12 @@ if ($needUpgrade) { $storageInfo=OC_Helper::getStorageInfo($dir); $maxUploadFilesize=OCP\Util::maxUploadFilesize($dir); $publicUploadEnabled = \OC_Appconfig::getValue('core', 'shareapi_allow_public_upload', 'yes'); + // if the encryption app is disabled, than everything is fine (INIT_SUCCESSFUL status code) + $encryptionInitStatus = 2; if (OC_App::isEnabled('files_encryption')) { $publicUploadEnabled = 'no'; + $session = new \OCA\Encryption\Session(new \OC\Files\View('/')); + $encryptionInitStatus = $session->getInitialized(); } $trashEnabled = \OCP\App::isEnabled('files_trashbin'); @@ -113,7 +117,7 @@ if ($needUpgrade) { if ($trashEnabled) { $trashEmpty = \OCA\Files_Trashbin\Trashbin::isEmpty($user); } - + OCP\Util::addscript('files', 'fileactions'); OCP\Util::addscript('files', 'files'); OCP\Util::addscript('files', 'keyboardshortcuts'); @@ -133,7 +137,10 @@ if ($needUpgrade) { $tmpl->assign('isPublic', false); $tmpl->assign('publicUploadEnabled', $publicUploadEnabled); $tmpl->assign("encryptedFiles", \OCP\Util::encryptedFiles()); + $tmpl->assign("mailNotificationEnabled", \OC_Appconfig::getValue('core', 'shareapi_allow_mail_notification', 'yes')); + $tmpl->assign("encryptionInitStatus", $encryptionInitStatus); $tmpl->assign('disableSharing', false); $tmpl->assign('ajaxLoad', $ajaxLoad); + $tmpl->printPage(); } diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 4fc1b95a0ab..3c99e3876c7 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -895,6 +895,10 @@ $(document).ready(function(){ $(window).trigger('beforeunload'); }); + function decodeQuery(query){ + return query.replace(/\+/g, ' '); + } + function parseHashQuery(){ var hash = window.location.hash, pos = hash.indexOf('?'), @@ -911,11 +915,11 @@ $(document).ready(function(){ dir = '/'; // try and parse from URL hash first if (query){ - params = OC.parseQueryString(query); + params = OC.parseQueryString(decodeQuery(query)); } // else read from query attributes if (!params){ - params = OC.parseQueryString(location.search); + params = OC.parseQueryString(decodeQuery(location.search)); } return (params && params.dir) || '/'; } diff --git a/apps/files/js/files.js b/apps/files/js/files.js index ec688eaf63e..899bc6469e5 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -63,6 +63,15 @@ Files={ } var encryptedFiles = $('#encryptedFiles').val(); + var initStatus = $('#encryptionInitStatus').val(); + if (initStatus === '0') { // enc not initialized, but should be + OC.Notification.show(t('files_encryption', 'Encryption App is enabled but your keys are not initialized, please log-out and log-in again')); + return; + } + if (initStatus === '1') { // encryption tried to init but failed + OC.Notification.showHtml(t('files_encryption', 'Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files.')); + return; + } if (encryptedFiles === '1') { OC.Notification.show(t('files_encryption', 'Encryption was disabled but your files are still encrypted. Please go to your personal settings to decrypt your files.')); return; diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php index 96a80738989..7067b854f50 100644 --- a/apps/files/templates/index.php +++ b/apps/files/templates/index.php @@ -9,7 +9,7 @@ data-type='file'><p><?php p($l->t('Text file'));?></p></li> <li style="background-image:url('<?php p(OCP\mimetype_icon('dir')) ?>')" data-type='folder'><p><?php p($l->t('Folder'));?></p></li> - <li style="background-image:url('<?php p(OCP\image_path('core', 'filetypes/web.svg')) ?>')" + <li style="background-image:url('<?php p(OCP\image_path('core', 'places/link.svg')) ?>')" data-type='web'><p><?php p($l->t('From link'));?></p></li> </ul> </div> @@ -116,3 +116,5 @@ <input type="hidden" name="allowZipDownload" id="allowZipDownload" value="<?php p($_['allowZipDownload']); ?>" /> <input type="hidden" name="usedSpacePercent" id="usedSpacePercent" value="<?php p($_['usedSpacePercent']); ?>" /> <input type="hidden" name="encryptedFiles" id="encryptedFiles" value="<?php $_['encryptedFiles'] ? p('1') : p('0'); ?>" /> +<input type="hidden" name="encryptedInitStatus" id="encryptionInitStatus" value="<?php p($_['encryptionInitStatus']) ?>" /> +<input type="hidden" name="mailNotificationEnabled" id="mailNotificationEnabled" value="<?php p($_['mailNotificationEnabled']) ?>" /> diff --git a/apps/files/templates/part.list.php b/apps/files/templates/part.list.php index 1e4d4d11c98..0679da334de 100644 --- a/apps/files/templates/part.list.php +++ b/apps/files/templates/part.list.php @@ -30,16 +30,15 @@ $totalsize = 0; ?> <?php endif; ?> <?php if($file['type'] == 'dir'): ?> <a class="name" href="<?php p(rtrim($_['baseURL'],'/').'/'.trim($directory,'/').'/'.$name); ?>" title=""> + <span class="nametext"> + <?php print_unescaped(htmlspecialchars($file['name']));?> + </span> <?php else: ?> - <a class="name" href="<?php p(rtrim($_['downloadURL'],'/').'/'.trim($directory,'/').'/'.$name); ?>" title=""> + <a class="name" href="<?php p(rtrim($_['downloadURL'],'/').'/'.trim($directory,'/').'/'.$name); ?>"> + <label class="filetext" title="" for="select-<?php p($file['fileid']); ?>"></label> + <span class="nametext"><?php print_unescaped(htmlspecialchars($file['basename']));?><span class='extension'><?php p($file['extension']);?></span> + </a> <?php endif; ?> - <span class="nametext"> - <?php if($file['type'] == 'dir'):?> - <?php print_unescaped(htmlspecialchars($file['name']));?> - <?php else:?> - <?php print_unescaped(htmlspecialchars($file['basename']));?><span class='extension'><?php p($file['extension']);?></span> - <?php endif;?> - </span> <?php if($file['type'] == 'dir'):?> <span class="uploadtext" currentUploads="0"> </span> diff --git a/apps/files_encryption/ajax/updatePrivateKeyPassword.php b/apps/files_encryption/ajax/updatePrivateKeyPassword.php index 1e6644da576..29c72952ae9 100644 --- a/apps/files_encryption/ajax/updatePrivateKeyPassword.php +++ b/apps/files_encryption/ajax/updatePrivateKeyPassword.php @@ -48,6 +48,7 @@ if ($decryptedKey) { // success or failure if ($return) { + $session->setInitialized(\OCA\Encryption\Session::INIT_SUCCESSFUL); \OCP\JSON::success(array('data' => array('message' => $l->t('Private key password successfully updated.')))); } else { \OCP\JSON::error(array('data' => array('message' => $l->t('Could not update the private key password. Maybe the old password was not correct.')))); diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 5b62b84e223..c930ac9eca8 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -43,23 +43,6 @@ if (!OC_Config::getValue('maintenance', false)) { if($sessionReady) { $session = new \OCA\Encryption\Session($view); } - - $user = \OCP\USER::getUser(); - // check if user has a private key - if ($sessionReady === false - || (!$view->file_exists('/' . $user . '/files_encryption/' . $user . '.private.key') - && OCA\Encryption\Crypt::mode() === 'server') - ) { - - // Force the user to log-in again if the encryption key isn't unlocked - // (happens when a user is logged in before the encryption app is - // enabled) - OCP\User::logout(); - - header("Location: " . OC::$WEBROOT . '/'); - - exit(); - } } } else { // logout user if we are in maintenance to force re-login diff --git a/apps/files_encryption/appinfo/version b/apps/files_encryption/appinfo/version index bd73f47072b..2eb3c4fe4ee 100644 --- a/apps/files_encryption/appinfo/version +++ b/apps/files_encryption/appinfo/version @@ -1 +1 @@ -0.4 +0.5 diff --git a/apps/files_encryption/files/error.php b/apps/files_encryption/files/error.php index 2dd27257abe..ac0c0269164 100644 --- a/apps/files_encryption/files/error.php +++ b/apps/files_encryption/files/error.php @@ -1,23 +1,33 @@ <?php + if (!isset($_)) { //also provide standalone error page require_once __DIR__ . '/../../../lib/base.php'; $l = OC_L10N::get('files_encryption'); - $errorMsg = $l->t('Your private key is not valid! Likely your password was changed outside the ownCloud system (e.g. your corporate directory). You can update your private key password in your personal settings to recover access to your encrypted files.'); + if (isset($_GET['i']) && $_GET['i'] === '0') { + $errorMsg = $l->t('Encryption app not initialized! Maybe the encryption app was re-enabled during your session. Please try to log out and log back in to initialize the encryption app.'); + $init = '0'; + } else { + $errorMsg = $l->t('Your private key is not valid! Likely your password was changed outside the ownCloud system (e.g. your corporate directory). You can update your private key password in your personal settings to recover access to your encrypted files.'); + $init = '1'; + } - if(isset($_GET['p']) && $_GET['p'] === '1') { + if (isset($_GET['p']) && $_GET['p'] === '1') { header('HTTP/1.0 404 ' . $errorMsg); } - // check if ajax request - if(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') { +// check if ajax request + if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') { \OCP\JSON::error(array('data' => array('message' => $errorMsg))); } else { header('HTTP/1.0 404 ' . $errorMsg); $tmpl = new OC_Template('files_encryption', 'invalid_private_key', 'guest'); + $tmpl->assign('message', $errorMsg); + $tmpl->assign('init', $init); $tmpl->printPage(); } exit; } + diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index d9221c6e828..2df860a8e57 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -159,7 +159,6 @@ class Hooks { * @param array $params keys: uid, password
*/
public static function setPassphrase($params) {
-
// Only attempt to change passphrase if server-side encryption
// is in use (client-side encryption does not have access to
// the necessary keys)
@@ -543,14 +542,18 @@ class Hooks { }
/**
- * set migration status back to '0' so that all new files get encrypted
+ * set migration status and the init status back to '0' so that all new files get encrypted
* if the app gets enabled again
* @param array $params contains the app ID
*/
public static function preDisable($params) {
if ($params['app'] === 'files_encryption') {
- $query = \OC_DB::prepare('UPDATE `*PREFIX*encryption` SET `migration_status`=0');
- $query->execute();
+
+ $setMigrationStatus = \OC_DB::prepare('UPDATE `*PREFIX*encryption` SET `migration_status`=0');
+ $setMigrationStatus->execute();
+
+ $session = new \OCA\Encryption\Session(new \OC\Files\View('/'));
+ $session->setInitialized(\OCA\Encryption\Session::NOT_INITIALIZED);
}
}
diff --git a/apps/files_encryption/js/settings-admin.js b/apps/files_encryption/js/settings-admin.js index 6647c621e7b..c2140a6f1eb 100644 --- a/apps/files_encryption/js/settings-admin.js +++ b/apps/files_encryption/js/settings-admin.js @@ -1,6 +1,8 @@ /** - * Copyright (c) 2013, Sam Tuke <samtuke@owncloud.com>, Robin Appelman - * <icewind1991@gmail.com> + * Copyright (c) 2013 + * Sam Tuke <samtuke@owncloud.com> + * Robin Appelman <icewind1991@gmail.com> + * Bjoern Schiessle <schiessle@owncloud.com> * This file is licensed under the Affero General Public License version 3 or later. * See the COPYING-README file. */ @@ -31,22 +33,23 @@ $(document).ready(function(){ // Trigger ajax on recoveryAdmin status change var enabledStatus = $('#adminEnableRecovery').val(); - $('input:password[name="recoveryPassword"]').keyup(function(event) { - var recoveryPassword = $( '#recoveryPassword' ).val(); + $('input:password[name="encryptionRecoveryPassword"]').keyup(function(event) { + var recoveryPassword = $( '#encryptionRecoveryPassword' ).val(); + var recoveryPasswordRepeated = $( '#repeatEncryptionRecoveryPassword' ).val(); var checkedButton = $('input:radio[name="adminEnableRecovery"]:checked').val(); var uncheckedValue = (1+parseInt(checkedButton)) % 2; - if (recoveryPassword != '' ) { + if (recoveryPassword !== '' && recoveryPassword === recoveryPasswordRepeated) { $('input:radio[name="adminEnableRecovery"][value="'+uncheckedValue.toString()+'"]').removeAttr("disabled"); } else { $('input:radio[name="adminEnableRecovery"][value="'+uncheckedValue.toString()+'"]').attr("disabled", "true"); } }); - $( 'input:radio[name="adminEnableRecovery"]' ).change( + $( 'input:radio[name="adminEnableRecovery"]' ).change( function() { var recoveryStatus = $( this ).val(); var oldStatus = (1+parseInt(recoveryStatus)) % 2; - var recoveryPassword = $( '#recoveryPassword' ).val(); + var recoveryPassword = $( '#encryptionRecoveryPassword' ).val(); $.post( OC.filePath( 'files_encryption', 'ajax', 'adminrecovery.php' ) , { adminEnableRecovery: recoveryStatus, recoveryPassword: recoveryPassword } @@ -57,11 +60,10 @@ $(document).ready(function(){ } else { OC.Notification.hide(); if (recoveryStatus === "0") { - $('button:button[name="submitChangeRecoveryKey"]').attr("disabled", "true"); - $('input:password[name="changeRecoveryPassword"]').attr("disabled", "true"); - $('input:password[name="changeRecoveryPassword"]').val(""); + $('p[name="changeRecoveryPasswordBlock"]').addClass("hidden"); } else { - $('input:password[name="changeRecoveryPassword"]').removeAttr("disabled"); + $('input:password[name="changeRecoveryPassword"]').val(""); + $('p[name="changeRecoveryPasswordBlock"]').removeClass("hidden"); } } } @@ -72,9 +74,11 @@ $(document).ready(function(){ // change recovery password $('input:password[name="changeRecoveryPassword"]').keyup(function(event) { - var oldRecoveryPassword = $('input:password[id="oldRecoveryPassword"]').val(); - var newRecoveryPassword = $('input:password[id="newRecoveryPassword"]').val(); - if (newRecoveryPassword != '' && oldRecoveryPassword != '' ) { + var oldRecoveryPassword = $('#oldEncryptionRecoveryPassword').val(); + var newRecoveryPassword = $('#newEncryptionRecoveryPassword').val(); + var newRecoveryPasswordRepeated = $('#repeatedNewEncryptionRecoveryPassword').val(); + + if (newRecoveryPassword !== '' && oldRecoveryPassword !== '' && newRecoveryPassword === newRecoveryPasswordRepeated) { $('button:button[name="submitChangeRecoveryKey"]').removeAttr("disabled"); } else { $('button:button[name="submitChangeRecoveryKey"]').attr("disabled", "true"); @@ -83,8 +87,8 @@ $(document).ready(function(){ $('button:button[name="submitChangeRecoveryKey"]').click(function() { - var oldRecoveryPassword = $('input:password[id="oldRecoveryPassword"]').val(); - var newRecoveryPassword = $('input:password[id="newRecoveryPassword"]').val(); + var oldRecoveryPassword = $('#oldEncryptionRecoveryPassword').val(); + var newRecoveryPassword = $('#newEncryptionRecoveryPassword').val(); OC.msg.startSaving('#encryption .msg'); $.post( OC.filePath( 'files_encryption', 'ajax', 'changeRecoveryPassword.php' ) @@ -98,5 +102,5 @@ $(document).ready(function(){ } ); }); - + }); diff --git a/apps/files_encryption/lib/helper.php b/apps/files_encryption/lib/helper.php index 445d7ff8ca7..ebfc00157f7 100755 --- a/apps/files_encryption/lib/helper.php +++ b/apps/files_encryption/lib/helper.php @@ -199,12 +199,12 @@ class Helper { public static function stripUserFilesPath($path) { $trimmed = ltrim($path, '/'); $split = explode('/', $trimmed); - + // it is not a file relative to data/user/files if (count($split) < 3 || $split[1] !== 'files') { return false; } - + $sliced = array_slice($split, 2); $relPath = implode('/', $sliced); @@ -219,30 +219,33 @@ class Helper { public static function getPathToRealFile($path) { $trimmed = ltrim($path, '/'); $split = explode('/', $trimmed); - + if (count($split) < 3 || $split[1] !== "files_versions") { return false; } - + $sliced = array_slice($split, 2); $realPath = implode('/', $sliced); //remove the last .v $realPath = substr($realPath, 0, strrpos($realPath, '.v')); return $realPath; - } - + } + /** * @brief redirect to a error page */ - public static function redirectToErrorPage() { + public static function redirectToErrorPage($session) { + + $init = $session->getInitialized(); + $location = \OC_Helper::linkToAbsolute('apps/files_encryption/files', 'error.php'); $post = 0; if(count($_POST) > 0) { $post = 1; - } - header('Location: ' . $location . '?p=' . $post); - exit(); + } + header('Location: ' . $location . '?p=' . $post . '&i=' . $init); + exit(); } /** @@ -259,7 +262,7 @@ class Helper { return (bool) $result; } - + /** * check some common errors if the server isn't configured properly for encryption * @return bool true if configuration seems to be OK diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 4ec810a5199..6f630c83a3f 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -317,7 +317,7 @@ class Proxy extends \OC_FileProxy { public function postGetFileInfo($path, $data) { // if path is a folder do nothing - if (is_array($data) && array_key_exists('size', $data)) { + if (\OCP\App::isEnabled('files_encryption') && is_array($data) && array_key_exists('size', $data)) { // Disable encryption proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php index 1911386cd12..25f2198181f 100644 --- a/apps/files_encryption/lib/session.php +++ b/apps/files_encryption/lib/session.php @@ -30,6 +30,11 @@ class Session { private $view; + const NOT_INITIALIZED = '0'; + const INIT_EXECUTED = '1'; + const INIT_SUCCESSFUL = '2'; + + /** * @brief if session is started, check if ownCloud key pair is set up, if not create it * @param \OC_FilesystemView $view @@ -113,6 +118,36 @@ class Session { } /** + * @brief Sets status of encryption app + * @param string $init INIT_SUCCESSFUL, INIT_EXECUTED, NOT_INOITIALIZED + * @return bool + * + * @note this doesn not indicate of the init was successful, we just remeber the try! + */ + public function setInitialized($init) { + + \OC::$session->set('encryptionInitialized', $init); + + return true; + + } + + + /** + * @brief Gets status if we already tried to initialize the encryption app + * @returns init status INIT_SUCCESSFUL, INIT_EXECUTED, NOT_INOITIALIZED + * + * @note this doesn not indicate of the init was successful, we just remeber the try! + */ + public function getInitialized() { + if (!is_null(\OC::$session->get('encryptionInitialized'))) { + return \OC::$session->get('encryptionInitialized'); + } else { + return self::NOT_INITIALIZED; + } + } + + /** * @brief Gets user or public share private key from session * @returns string $privateKey The user's plaintext private key * diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 083b33c03cb..02955bb064e 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -131,7 +131,7 @@ class Stream { if($this->privateKey === false) { // if private key is not valid redirect user to a error page - \OCA\Encryption\Helper::redirectToErrorPage(); + \OCA\Encryption\Helper::redirectToErrorPage($this->session); } $this->size = $this->rootView->filesize($this->rawPath, $mode); diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index df4d35cab0b..53d58fbf40d 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -37,7 +37,6 @@ class Util { const MIGRATION_IN_PROGRESS = -1; // migration is running const MIGRATION_OPEN = 0; // user still needs to be migrated - private $view; // OC_FilesystemView object for filesystem operations private $userId; // ID of the currently logged-in user private $client; // Client side encryption mode flag @@ -1752,6 +1751,11 @@ class Util { */ public function initEncryption($params) { + $session = new \OCA\Encryption\Session($this->view); + + // we tried to initialize the encryption app for this session + $session->setInitialized(\OCA\Encryption\Session::INIT_EXECUTED); + $encryptedKey = Keymanager::getPrivateKey($this->view, $params['uid']); $privateKey = Crypt::decryptPrivateKey($encryptedKey, $params['password']); @@ -1762,9 +1766,8 @@ class Util { return false; } - $session = new \OCA\Encryption\Session($this->view); - $session->setPrivateKey($privateKey); + $session->setInitialized(\OCA\Encryption\Session::INIT_SUCCESSFUL); return $session; } diff --git a/apps/files_encryption/settings-personal.php b/apps/files_encryption/settings-personal.php index 589219f32ad..ffcb99602e2 100644 --- a/apps/files_encryption/settings-personal.php +++ b/apps/files_encryption/settings-personal.php @@ -16,7 +16,9 @@ $view = new \OC_FilesystemView('/'); $util = new \OCA\Encryption\Util($view, $user);
$session = new \OCA\Encryption\Session($view);
-$privateKeySet = $session->getPrivateKey() !== false; +$privateKeySet = $session->getPrivateKey() !== false;
+// did we tried to initialize the keys for this session?
+$initialized = $session->getInitialized();
$recoveryAdminEnabled = OC_Appconfig::getValue('files_encryption', 'recoveryAdminEnabled');
$recoveryEnabledForUser = $util->recoveryEnabledForUser();
@@ -31,6 +33,7 @@ if ($recoveryAdminEnabled || !$privateKeySet) { $tmpl->assign('recoveryEnabled', $recoveryAdminEnabled);
$tmpl->assign('recoveryEnabledForUser', $recoveryEnabledForUser);
$tmpl->assign('privateKeySet', $privateKeySet);
+ $tmpl->assign('initialized', $initialized);
$result = $tmpl->fetchPage();
}
diff --git a/apps/files_encryption/templates/invalid_private_key.php b/apps/files_encryption/templates/invalid_private_key.php index 5c086d6514c..9af65f831b4 100644 --- a/apps/files_encryption/templates/invalid_private_key.php +++ b/apps/files_encryption/templates/invalid_private_key.php @@ -2,9 +2,11 @@ <li class='error'> <?php $location = \OC_Helper::linkToRoute( "settings_personal" ).'#changePKPasswd' ?> - <?php p($l->t('Your private key is not valid! Maybe the your password was changed from outside.')); ?> + <?php p($_['message']); ?> <br/> - <?php p($l->t('You can unlock your private key in your ')); ?> <a href="<?php echo $location?>"><?php p($l->t('personal settings')); ?>.</a> + <?php if($_['init']): ?> + <?php>p($l->t('Go directly to your ')); ?> <a href="<?php echo $location?>"><?php p($l->t('personal settings')); ?>.</a> + <?php endif; ?> <br/> </li> </ul> diff --git a/apps/files_encryption/templates/settings-admin.php b/apps/files_encryption/templates/settings-admin.php index f5f7582c2a6..3a6adc09f4b 100644 --- a/apps/files_encryption/templates/settings-admin.php +++ b/apps/files_encryption/templates/settings-admin.php @@ -10,14 +10,17 @@ <?php p($l->t("Enable recovery key (allow to recover users files in case of password loss):")); ?> <br/> <br/> - <input type="password" name="recoveryPassword" id="recoveryPassword"/> + <input type="password" name="encryptionRecoveryPassword" id="encryptionRecoveryPassword"/> <label for="recoveryPassword"><?php p($l->t("Recovery key password")); ?></label> <br/> + <input type="password" name="encryptionRecoveryPassword" id="repeatEncryptionRecoveryPassword"/> + <label for="repeatEncryptionRecoveryPassword"><?php p($l->t("Repeat Recovery key password")); ?></label> + <br/> <input type='radio' name='adminEnableRecovery' value='1' - <?php echo($_["recoveryEnabled"] == 1 ? 'checked="checked"' : 'disabled'); ?> /> + <?php echo($_["recoveryEnabled"] === '1' ? 'checked="checked"' : 'disabled'); ?> /> <?php p($l->t("Enabled")); ?> <br/> @@ -25,27 +28,32 @@ type='radio' name='adminEnableRecovery' value='0' - <?php echo($_["recoveryEnabled"] == 0 ? 'checked="checked"' : 'disabled'); ?> /> + <?php echo($_["recoveryEnabled"] === '0' ? 'checked="checked"' : 'disabled'); ?> /> <?php p($l->t("Disabled")); ?> </p> <br/><br/> - <p> + <p name="changeRecoveryPasswordBlock" <?php if ($_['recoveryEnabled'] === '0') print_unescaped('class="hidden"');?>> <strong><?php p($l->t("Change recovery key password:")); ?></strong> <br/><br/> <input type="password" name="changeRecoveryPassword" - id="oldRecoveryPassword" - <?php echo($_["recoveryEnabled"] == 0 ? 'disabled' : ''); ?> /> - <label for="oldRecoveryPassword"><?php p($l->t("Old Recovery key password")); ?></label> + id="oldEncryptionRecoveryPassword" + <label for="oldEncryptionRecoveryPassword"><?php p($l->t("Old Recovery key password")); ?></label> + <br/> + <br/> + <input + type="password" + name="changeRecoveryPassword" + id="newEncryptionRecoveryPassword" + <label for="newEncryptionRecoveryPassword"><?php p($l->t("New Recovery key password")); ?></label> <br/> <input type="password" name="changeRecoveryPassword" - id="newRecoveryPassword" - <?php echo($_["recoveryEnabled"] == 0 ? 'disabled' : ''); ?> /> - <label for="newRecoveryPassword"><?php p($l->t("New Recovery key password")); ?></label> + id="repeatedNewEncryptionRecoveryPassword" + <label for="repeatEncryptionRecoveryPassword"><?php p($l->t("Repeat New Recovery key password")); ?></label> <br/> <button type="button" diff --git a/apps/files_encryption/templates/settings-personal.php b/apps/files_encryption/templates/settings-personal.php index 38512453207..ff04556dd53 100644 --- a/apps/files_encryption/templates/settings-personal.php +++ b/apps/files_encryption/templates/settings-personal.php @@ -4,7 +4,7 @@ <?php p( $l->t( 'Encryption' ) ); ?>
</legend>
- <?php if ( ! $_["privateKeySet"] ): ?>
+ <?php if ( ! $_["privateKeySet"] && $_["initialized"] ): ?>
<p>
<a name="changePKPasswd" />
<label for="changePrivateKeyPasswd">
@@ -39,22 +39,22 @@ <?php endif; ?>
<br />
-
+
<?php if ( $_["recoveryEnabled"] && $_["privateKeySet"] ): ?>
<p>
<label for="userEnableRecovery"><?php p( $l->t( "Enable password recovery:" ) ); ?></label>
<br />
<em><?php p( $l->t( "Enabling this option will allow you to reobtain access to your encrypted files in case of password loss" ) ); ?></em>
<br />
- <input
+ <input
type='radio'
name='userEnableRecovery'
value='1'
<?php echo ( $_["recoveryEnabledForUser"] == 1 ? 'checked="checked"' : '' ); ?> />
<?php p( $l->t( "Enabled" ) ); ?>
<br />
-
- <input
+
+ <input
type='radio'
name='userEnableRecovery'
value='0'
diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index 895d446a336..ffdcbf05109 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -7,6 +7,7 @@ OC::$CLASSPATH['OC\Files\Cache\Shared_Cache'] = 'files_sharing/lib/cache.php'; OC::$CLASSPATH['OC\Files\Cache\Shared_Permissions'] = 'files_sharing/lib/permissions.php'; OC::$CLASSPATH['OC\Files\Cache\Shared_Updater'] = 'files_sharing/lib/updater.php'; OC::$CLASSPATH['OC\Files\Cache\Shared_Watcher'] = 'files_sharing/lib/watcher.php'; +OC::$CLASSPATH['OCA\Files\Share\Api'] = 'files_sharing/lib/api.php'; OCP\Util::connectHook('OC_Filesystem', 'setup', '\OC\Files\Storage\Shared', 'setup'); OCP\Share::registerBackend('file', 'OC_Share_Backend_File'); OCP\Share::registerBackend('folder', 'OC_Share_Backend_Folder', 'file'); diff --git a/apps/files_sharing/appinfo/routes.php b/apps/files_sharing/appinfo/routes.php index 02815b5eb42..3469829b6f7 100644 --- a/apps/files_sharing/appinfo/routes.php +++ b/apps/files_sharing/appinfo/routes.php @@ -2,4 +2,33 @@ $this->create('core_ajax_public_preview', '/publicpreview.png')->action( function() { require_once __DIR__ . '/../ajax/publicpreview.php'; -});
\ No newline at end of file +}); + +// OCS API + +//TODO: SET: mail notification, waiting for PR #4689 to be accepted + +OC_API::register('get', + '/apps/files_sharing/api/v1/shares', + array('\OCA\Files\Share\Api', 'getAllShares'), + 'files_sharing'); + +OC_API::register('post', + '/apps/files_sharing/api/v1/shares', + array('\OCA\Files\Share\Api', 'createShare'), + 'files_sharing'); + +OC_API::register('get', + '/apps/files_sharing/api/v1/shares/{id}', + array('\OCA\Files\Share\Api', 'getShare'), + 'files_sharing'); + +OC_API::register('put', + '/apps/files_sharing/api/v1/shares/{id}', + array('\OCA\Files\Share\Api', 'updateShare'), + 'files_sharing'); + +OC_API::register('delete', + '/apps/files_sharing/api/v1/shares/{id}', + array('\OCA\Files\Share\Api', 'deleteShare'), + 'files_sharing'); diff --git a/apps/files_sharing/lib/api.php b/apps/files_sharing/lib/api.php new file mode 100644 index 00000000000..e6624624898 --- /dev/null +++ b/apps/files_sharing/lib/api.php @@ -0,0 +1,467 @@ +<?php +/** + * ownCloud + * + * @author Bjoern Schiessle + * @copyright 2013 Bjoern Schiessle schiessle@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files\Share; + +class Api { + + /** + * @brief get all shares + * + * @param array $params option 'file' to limit the result to a specific file/folder + * @return \OC_OCS_Result share information + */ + public static function getAllShares($params) { + + // if a file is specified, get the share for this file + if (isset($_GET['path'])) { + $params['itemSource'] = self::getFileId($_GET['path']); + $params['path'] = $_GET['path']; + if (isset($_GET['subfiles']) && $_GET['subfiles'] === 'true') { + return self::getSharesFromFolder($params); + } + return self::getShare($params); + } + + $share = \OCP\Share::getItemShared('file', null); + + if ($share === false) { + return new \OC_OCS_Result(null, 404, 'could not get shares'); + } else { + return new \OC_OCS_Result($share); + } + + } + + /** + * @brief get share information for a given share + * + * @param array $params which contains a 'id' + * @return \OC_OCS_Result share information + */ + public static function getShare($params) { + + // either the $params already contains a itemSource if we come from + // getAllShare() or we need to translate the shareID to a itemSource + if(isset($params['itemSource'])) { + $itemSource = $params['itemSource']; + $getSpecificShare = true; + } else { + $s = self::getShareFromId($params['id']); + $itemSource = $s['item_source']; + $getSpecificShare = false; + } + + if ($itemSource !== null) { + $shares = \OCP\Share::getItemShared('file', $itemSource); + // if a specific share was specified only return this one + if ($getSpecificShare === false) { + foreach ($shares as $share) { + if ($share['id'] === (int)$params['id']) { + $shares = array('element' => $share); + break; + } + } + } + } else { + $shares = null; + } + + if ($shares === null || empty($shares)) { + return new \OC_OCS_Result(null, 404, 'share doesn\'t exist'); + } else { + return new \OC_OCS_Result($shares); + } + } + + /** + * @brief get share from all files in a given folder (non-recursive) + * @param array $params contains 'path' to the folder + * @return \OC_OCS_Result + */ + private static function getSharesFromFolder($params) { + $path = $params['path']; + $view = new \OC\Files\View('/'.\OCP\User::getUser().'/files'); + + if(!$view->is_dir($path)) { + return new \OC_OCS_Result(null, 404, "not a directory"); + } + + $content = $view->getDirectoryContent($path); + + $result = array(); + foreach ($content as $file) { + $share = \OCP\Share::getItemShared('file', $file['fileid']); + if ($share) { + $share['filename'] = $file['name']; + $result[] = $share; + } + } + + return new \OC_OCS_Result($result); + } + + /** + * @breif create a new share + * @param array $params + * @return \OC_OCS_Result + */ + public static function createShare($params) { + + $path = isset($_POST['path']) ? $_POST['path'] : null; + + if($path === null) { + return new \OC_OCS_Result(null, 400, "please specify a file or folder path"); + } + + $itemSource = self::getFileId($path); + $itemType = self::getItemType($path); + + if($itemSource === null) { + return new \OC_OCS_Result(null, 404, "wrong path, file/folder doesn't exist."); + } + + $shareWith = isset($_POST['shareWith']) ? $_POST['shareWith'] : null; + $shareType = isset($_POST['shareType']) ? (int)$_POST['shareType'] : null; + + switch($shareType) { + case \OCP\Share::SHARE_TYPE_USER: + $permissions = isset($_POST['permissions']) ? (int)$_POST['permissions'] : 31; + break; + case \OCP\Share::SHARE_TYPE_GROUP: + $permissions = isset($_POST['permissions']) ? (int)$_POST['permissions'] : 31; + break; + case \OCP\Share::SHARE_TYPE_LINK: + //allow password protection + $shareWith = isset($_POST['password']) ? $_POST['password'] : null; + //check public link share + $publicUploadEnabled = \OC_Appconfig::getValue('core', 'shareapi_allow_public_upload', 'yes'); + $encryptionEnabled = \OC_App::isEnabled('files_encryption'); + if(isset($_POST['publicUpload']) && + ($encryptionEnabled || $publicUploadEnabled !== 'yes')) { + return new \OC_OCS_Result(null, 404, "public upload disabled by the administrator"); + } + $publicUpload = isset($_POST['publicUpload']) ? $_POST['publicUpload'] : 'false'; + // read, create, update (7) if public upload is enabled or + // read (1) if public upload is disabled + $permissions = $publicUpload === 'true' ? 7 : 1; + break; + default: + return new \OC_OCS_Result(null, 404, "unknown share type"); + } + + try { + $token = \OCP\Share::shareItem( + $itemType, + $itemSource, + $shareType, + $shareWith, + $permissions + ); + } catch (\Exception $e) { + return new \OC_OCS_Result(null, 404, $e->getMessage()); + } + + if ($token) { + $data = array(); + $data['id'] = 'unknown'; + $shares = \OCP\Share::getItemShared('file', $itemSource); + if(is_string($token)) { //public link share + foreach ($shares as $share) { + if ($share['token'] === $token) { + $data['id'] = $share['id']; + break; + } + } + $url = \OCP\Util::linkToPublic('files&t='.$token); + $data['url'] = $url; // '&' gets encoded to $amp; + $data['token'] = $token; + + } else { + foreach ($shares as $share) { + if ($share['share_with'] === $shareWith && $share['share_type'] === $shareType) { + $data['id'] = $share['id']; + break; + } + } + } + return new \OC_OCS_Result($data); + } else { + return new \OC_OCS_Result(null, 404, "couldn't share file"); + } + } + + /** + * update shares, e.g. password, permissions, etc + * @param array $params shareId 'id' and the parameter we want to update + * currently supported: permissions, password, publicUpload + * @return \OC_OCS_Result + */ + public static function updateShare($params) { + + $share = self::getShareFromId($params['id']); + $itemSource = isset($share['item_source']) ? $share['item_source'] : null; + + if($itemSource === null) { + return new \OC_OCS_Result(null, 404, "wrong share Id, share doesn't exist."); + } + + try { + if(isset($params['_put']['permissions'])) { + return self::updatePermissions($share, $params); + } elseif (isset($params['_put']['password'])) { + return self::updatePassword($share, $params); + } elseif (isset($params['_put']['publicUpload'])) { + return self::updatePublicUpload($share, $params); + } + } catch (\Exception $e) { + return new \OC_OCS_Result(null, 400, $e->getMessage()); + } + + return new \OC_OCS_Result(null, 400, "Wrong or no update parameter given"); + + } + + /** + * @brief update permissions for a share + * @param array $share information about the share + * @param array $params contains 'permissions' + * @return \OC_OCS_Result + */ + private static function updatePermissions($share, $params) { + + $itemSource = $share['item_source']; + $itemType = $share['item_type']; + $shareWith = $share['share_with']; + $shareType = $share['share_type']; + $permissions = isset($params['_put']['permissions']) ? (int)$params['_put']['permissions'] : null; + + $publicUploadStatus = \OC_Appconfig::getValue('core', 'shareapi_allow_public_upload', 'yes'); + $encryptionEnabled = \OC_App::isEnabled('files_encryption'); + $publicUploadEnabled = false; + if(!$encryptionEnabled && $publicUploadStatus === 'yes') { + $publicUploadEnabled = true; + } + + // only change permissions for public shares if public upload is enabled + // and we want to set permissions to 1 (read only) or 7 (allow upload) + if ( (int)$shareType === \OCP\Share::SHARE_TYPE_LINK ) { + if ($publicUploadEnabled === false || ($permissions !== 7 && $permissions !== 1)) { + return new \OC_OCS_Result(null, 400, "can't change permission for public link share"); + } + } + + try { + $return = \OCP\Share::setPermissions( + $itemType, + $itemSource, + $shareType, + $shareWith, + $permissions + ); + } catch (\Exception $e) { + return new \OC_OCS_Result(null, 404, $e->getMessage()); + } + + if ($return) { + return new \OC_OCS_Result(); + } else { + return new \OC_OCS_Result(null, 404, "couldn't set permissions"); + } + } + + /** + * @brief enable/disable public upload + * @param array $share information about the share + * @param array $params contains 'publicUpload' which can be 'yes' or 'no' + * @return \OC_OCS_Result + */ + private static function updatePublicUpload($share, $params) { + + $publicUploadEnabled = \OC_Appconfig::getValue('core', 'shareapi_allow_public_upload', 'yes'); + $encryptionEnabled = \OC_App::isEnabled('files_encryption'); + if($encryptionEnabled || $publicUploadEnabled !== 'yes') { + return new \OC_OCS_Result(null, 404, "public upload disabled by the administrator"); + } + + if ($share['item_type'] !== 'folder' || + (int)$share['share_type'] !== \OCP\Share::SHARE_TYPE_LINK ) { + return new \OC_OCS_Result(null, 404, "public upload is only possible for public shared folders"); + } + + // read, create, update (7) if public upload is enabled or + // read (1) if public upload is disabled + $params['_put']['permissions'] = $params['_put']['publicUpload'] === 'true' ? 7 : 1; + + return self::updatePermissions($share, $params); + + } + + /** + * @brief update password for public link share + * @param array $share information about the share + * @param type $params 'password' + * @return \OC_OCS_Result + */ + private static function updatePassword($share, $params) { + + $itemSource = $share['item_source']; + $itemType = $share['item_type']; + + if( (int)$share['share_type'] !== \OCP\Share::SHARE_TYPE_LINK) { + return new \OC_OCS_Result(null, 400, "password protection is only supported for public shares"); + } + + $shareWith = isset($params['_put']['password']) ? $params['_put']['password'] : null; + + if($shareWith === '') { + $shareWith = null; + } + + $items = \OCP\Share::getItemShared($itemType, $itemSource); + + $checkExists = false; + foreach ($items as $item) { + if($item['share_type'] === \OCP\Share::SHARE_TYPE_LINK) { + $checkExists = true; + $permissions = $item['permissions']; + } + } + + if (!$checkExists) { + return new \OC_OCS_Result(null, 404, "share doesn't exists, can't change password"); + } + + $result = \OCP\Share::shareItem( + $itemType, + $itemSource, + \OCP\Share::SHARE_TYPE_LINK, + $shareWith, + $permissions + ); + if($result) { + return new \OC_OCS_Result(); + } + + return new \OC_OCS_Result(null, 404, "couldn't set password"); + } + + /** + * @brief unshare a file/folder + * @param array $params contains the shareID 'id' which should be unshared + * @return \OC_OCS_Result + */ + public static function deleteShare($params) { + + $share = self::getShareFromId($params['id']); + $itemSource = isset($share['item_source']) ? $share['item_source'] : null; + $itemType = isset($share['item_type']) ? $share['item_type'] : null;; + + if($itemSource === null) { + return new \OC_OCS_Result(null, 404, "wrong share ID, share doesn't exist."); + } + + $shareWith = isset($share['share_with']) ? $share['share_with'] : null; + $shareType = isset($share['share_type']) ? (int)$share['share_type'] : null; + + if( $shareType === \OCP\Share::SHARE_TYPE_LINK) { + $shareWith = null; + } + + try { + $return = \OCP\Share::unshare( + $itemType, + $itemSource, + $shareType, + $shareWith); + } catch (\Exception $e) { + return new \OC_OCS_Result(null, 404, $e->getMessage()); + } + + if ($return) { + return new \OC_OCS_Result(); + } else { + $msg = "Unshare Failed"; + return new \OC_OCS_Result(null, 404, $msg); + } + } + + /** + * @brief get file ID from a given path + * @param string $path + * @return string fileID or null + */ + private static function getFileId($path) { + + $view = new \OC\Files\View('/'.\OCP\User::getUser().'/files'); + $fileId = null; + + $fileInfo = $view->getFileInfo($path); + if ($fileInfo) { + $fileId = $fileInfo['fileid']; + } + + return $fileId; + } + + /** + * @brief get itemType + * @param string $path + * @return string type 'file', 'folder' or null of file/folder doesn't exists + */ + private static function getItemType($path) { + $view = new \OC\Files\View('/'.\OCP\User::getUser().'/files'); + $itemType = null; + + if ($view->is_dir($path)) { + $itemType = "folder"; + } elseif ($view->is_file($path)) { + $itemType = "file"; + } + + return $itemType; + } + + /** + * @brief get some information from a given share + * @param int $shareID + * @return array with: item_source, share_type, share_with, item_type, permissions + */ + private static function getShareFromId($shareID) { + $sql = 'SELECT `item_source`, `share_type`, `share_with`, `item_type`, `permissions` FROM `*PREFIX*share` WHERE `id` = ?'; + $args = array($shareID); + $query = \OCP\DB::prepare($sql); + $result = $query->execute($args); + + if (\OCP\DB::isError($result)) { + \OCP\Util::writeLog('files_sharing', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + return null; + } + if ($share = $result->fetchRow()) { + return $share; + } + + return null; + + } + +} diff --git a/apps/files_sharing/tests/api.php b/apps/files_sharing/tests/api.php new file mode 100644 index 00000000000..c55c186f089 --- /dev/null +++ b/apps/files_sharing/tests/api.php @@ -0,0 +1,537 @@ +<?php +/** + * ownCloud + * + * @author Bjoern Schiessle + * @copyright 2013 Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +require_once __DIR__ . '/../../../lib/base.php'; + +use OCA\Files\Share; + +/** + * Class Test_Files_Sharing_Api + */ +class Test_Files_Sharing_Api extends \PHPUnit_Framework_TestCase { + + const TEST_FILES_SHARING_API_USER1 = "test-share-user1"; + const TEST_FILES_SHARING_API_USER2 = "test-share-user2"; + + public $stateFilesEncryption; + public $filename; + public $data; + /** + * @var OC_FilesystemView + */ + public $view; + public $folder; + + public static function setUpBeforeClass() { + // reset backend + \OC_User::clearBackends(); + \OC_User::useBackend('database'); + + // clear share hooks + \OC_Hook::clear('OCP\\Share'); + \OC::registerShareHooks(); + \OCP\Util::connectHook('OC_Filesystem', 'setup', '\OC\Files\Storage\Shared', 'setup'); + + // create users + self::loginHelper(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER1, true); + self::loginHelper(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, true); + + } + + function setUp() { + $this->data = 'foobar'; + $this->view = new \OC_FilesystemView('/' . \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER1 . '/files'); + + $this->folder = '/folder_share_api_test'; + + $this->filename = 'share-api-test.txt'; + + // remember files_encryption state + $this->stateFilesEncryption = \OC_App::isEnabled('files_encryption'); + + //we don't want to tests with app files_encryption enabled + \OC_App::disable('files_encryption'); + + + $this->assertTrue(!\OC_App::isEnabled('files_encryption')); + + // save file with content + $this->view->file_put_contents($this->filename, $this->data); + $this->view->mkdir($this->folder); + $this->view->file_put_contents($this->folder.'/'.$this->filename, $this->data); + + } + + function tearDown() { + $this->view->unlink($this->filename); + $this->view->deleteAll($this->folder); + // reset app files_encryption + if ($this->stateFilesEncryption) { + \OC_App::enable('files_encryption'); + } else { + \OC_App::disable('files_encryption'); + } + } + + public static function tearDownAfterClass() { + + // cleanup users + \OC_User::deleteUser(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER1); + \OC_User::deleteUser(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + } + + /** + * @medium + */ + function testCreateShare() { + + //login as user1 + \Test_Files_Sharing_Api::loginHelper(\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER1); + + // share to user + + // simulate a post request + $_POST['path'] = $this->filename; + $_POST['shareWith'] = \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2; + $_POST['shareType'] = \OCP\Share::SHARE_TYPE_USER; + + $result = Share\Api::createShare(array()); + + $this->assertTrue($result->succeeded()); + $data = $result->getData(); + + $share = $this->getShareFromId($data['id']); + + $items = \OCP\Share::getItemShared('file', $share['item_source']); + + $this->assertTrue(!empty($items)); + + // share link + + // simulate a post request + $_POST['path'] = $this->folder; + $_POST['shareType'] = \OCP\Share::SHARE_TYPE_LINK; + + $result = Share\Api::createShare(array()); + + // check if API call was successful + $this->assertTrue($result->succeeded()); + + $data = $result->getData(); + + // check if we have a token + $this->assertTrue(is_string($data['token'])); + + $share = $this->getShareFromId($data['id']); + + $items = \OCP\Share::getItemShared('file', $share['item_source']); + + $this->assertTrue(!empty($items)); + + $fileinfo = $this->view->getFileInfo($this->filename); + + \OCP\Share::unshare('file', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + $fileinfo = $this->view->getFileInfo($this->folder); + + \OCP\Share::unshare('folder', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, null); + + + + } + + /** + * @medium + * @depends testCreateShare + */ + function testGetAllShares() { + + $fileinfo = $this->view->getFileInfo($this->filename); + + \OCP\Share::shareItem('file', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); + + $result = Share\Api::getAllShares(array()); + + $this->assertTrue($result->succeeded()); + + // test should return two shares created from testCreateShare() + $this->assertTrue(count($result->getData()) === 1); + + \OCP\Share::unshare('file', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + } + + /** + * @medium + * @depends testCreateShare + */ + function testGetShareFromSource() { + + $fileInfo = $this->view->getFileInfo($this->filename); + + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); + + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, + null, 1); + + $params = array('itemSource' => $fileInfo['fileid']); + + $result = Share\Api::getShare($params); + + $this->assertTrue($result->succeeded()); + + // test should return one share created from testCreateShare() + $this->assertTrue(count($result->getData()) === 2); + + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, null); + + } + + /** + * @medium + * @depends testCreateShare + */ + function testGetShareFromId() { + + $fileInfo = $this->view->getFileInfo($this->filename); + + $result = \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); + + // share was successful? + $this->assertTrue($result); + + // get item to determine share ID + $result = \OCP\Share::getItemShared('file', $fileInfo['fileid']); + + $this->assertEquals(1, count($result)); + + // get first element + $share = reset($result); + + // call getShare() with share ID + $params = array('id' => $share['id']); + $result = Share\Api::getShare($params); + + $this->assertTrue($result->succeeded()); + + // test should return one share created from testCreateShare() + $this->assertEquals(1, count($result->getData())); + + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + } + + /** + * @medium + */ + function testGetShareFromFolder() { + + $fileInfo1 = $this->view->getFileInfo($this->filename); + $fileInfo2 = $this->view->getFileInfo($this->folder.'/'.$this->filename); + + $result = \OCP\Share::shareItem('file', $fileInfo1['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); + + // share was successful? + $this->assertTrue($result); + + $result = \OCP\Share::shareItem('folder', $fileInfo2['fileid'], \OCP\Share::SHARE_TYPE_LINK, + null, 1); + + // share was successful? + $this->assertTrue(is_string($result)); + + $_GET['path'] = $this->folder; + $_GET['subfiles'] = 'true'; + + $result = Share\Api::getAllShares(array()); + + $this->assertTrue($result->succeeded()); + + // test should return one share within $this->folder + $this->assertTrue(count($result->getData()) === 1); + + \OCP\Share::unshare('file', $fileInfo1['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + \OCP\Share::unshare('folder', $fileInfo2['fileid'], \OCP\Share::SHARE_TYPE_LINK, null); + + } + + /** + * @medium + */ + function testGetShareFromUnknownId() { + + $params = array('id' => 0); + + $result = Share\Api::getShare($params); + + $this->assertEquals(404, $result->getStatusCode()); + $this->assertEquals('share doesn\'t exist', $result->getMeta()['message']); + + } + + /** + * @medium + * @depends testCreateShare + */ + function testUpdateShare() { + + $fileInfo = $this->view->getFileInfo($this->filename); + + $result = \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); + + // share was successful? + $this->assertTrue($result); + + $result = \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, + null, 1); + + // share was successful? + $this->assertTrue(is_string($result)); + + $items = \OCP\Share::getItemShared('file', null); + + // make sure that we found a link share and a user share + $this->assertEquals(count($items), 2); + + $linkShare = null; + $userShare = null; + + foreach ($items as $item) { + if ($item['share_type'] === \OCP\Share::SHARE_TYPE_LINK) { + $linkShare = $item; + } + if ($item['share_type'] === \OCP\Share::SHARE_TYPE_USER) { + $userShare = $item; + } + } + + // make sure that we found a link share and a user share + $this->assertTrue(is_array($linkShare)); + $this->assertTrue(is_array($userShare)); + + // update permissions + + $this->assertEquals('31', $userShare['permissions']); + + $params = array(); + $params['id'] = $userShare['id']; + $params['_put'] = array(); + $params['_put']['permissions'] = 1; + + $result = Share\Api::updateShare($params); + + $this->assertTrue($result->succeeded(), $result->getMeta()['message']); + + $items = \OCP\Share::getItemShared('file', $userShare['file_source']); + + $newUserShare = null; + foreach ($items as $item) { + if ($item['share_with'] === \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2) { + $newUserShare = $item; + break; + } + } + + $this->assertTrue(is_array($newUserShare)); + + $this->assertEquals('1', $newUserShare['permissions']); + + // update password for link share + + $this->assertTrue(empty($linkShare['share_with'])); + + $params = array(); + $params['id'] = $linkShare['id']; + $params['_put'] = array(); + $params['_put']['password'] = 'foo'; + + $result = Share\Api::updateShare($params); + + $this->assertTrue($result->succeeded()); + + $items = \OCP\Share::getItemShared('file', $linkShare['file_source']); + + $newLinkShare = null; + foreach ($items as $item) { + if ($item['share_type'] === \OCP\Share::SHARE_TYPE_LINK) { + $newLinkShare = $item; + break; + } + } + + $this->assertTrue(is_array($newLinkShare)); + $this->assertTrue(!empty($newLinkShare['share_with'])); + + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2); + + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, null); + + } + + /** + * @medium + */ + function testUpdateShareUpload() { + + $fileInfo = $this->view->getFileInfo($this->folder); + + $result = \OCP\Share::shareItem('folder', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, + null, 1); + + // share was successful? + $this->assertTrue(is_string($result)); + + $items = \OCP\Share::getItemShared('file', null); + + // make sure that we found a link share and a user share + $this->assertEquals(count($items), 1); + + $linkShare = null; + + foreach ($items as $item) { + if ($item['share_type'] === \OCP\Share::SHARE_TYPE_LINK) { + $linkShare = $item; + } + } + + // make sure that we found a link share + $this->assertTrue(is_array($linkShare)); + + // update public upload + + $params = array(); + $params['id'] = $linkShare['id']; + $params['_put'] = array(); + $params['_put']['publicUpload'] = 'true'; + + $result = Share\Api::updateShare($params); + + $this->assertTrue($result->succeeded()); + + $items = \OCP\Share::getItemShared('file', $linkShare['file_source']); + + $updatedLinkShare = null; + foreach ($items as $item) { + if ($item['share_type'] === \OCP\Share::SHARE_TYPE_LINK) { + $updatedLinkShare = $item; + break; + } + } + + $this->assertTrue(is_array($updatedLinkShare)); + $this->assertEquals(7, $updatedLinkShare['permissions']); + + // cleanup + + \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, null); + + } + + /** + * @medium + * @depends testCreateShare + */ + function testDeleteShare() { + + $fileInfo = $this->view->getFileInfo($this->filename); + + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + \Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2, 31); + + \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_LINK, + null, 1); + + $items = \OCP\Share::getItemShared('file', null); + + $this->assertEquals(2, count($items)); + + foreach ($items as $item) { + $result = Share\Api::deleteShare(array('id' => $item['id'])); + + $this->assertTrue($result->succeeded()); + } + + $itemsAfterDelete = \OCP\Share::getItemShared('file', null); + + $this->assertTrue(empty($itemsAfterDelete)); + + } + + /** + * @param $user + * @param bool $create + * @param bool $password + */ + private static function loginHelper($user, $create = false, $password = false) { + if ($create) { + \OC_User::createUser($user, $user); + } + + if ($password === false) { + $password = $user; + } + + \OC_Util::tearDownFS(); + \OC_User::setUserId(''); + \OC\Files\Filesystem::tearDown(); + \OC_Util::setupFS($user); + \OC_User::setUserId($user); + + $params['uid'] = $user; + $params['password'] = $password; + } + + /** + * @brief get some information from a given share + * @param int $shareID + * @return array with: item_source, share_type, share_with, item_type, permissions + */ + private function getShareFromId($shareID) { + $sql = 'SELECT `item_source`, `share_type`, `share_with`, `item_type`, `permissions` FROM `*PREFIX*share` WHERE `id` = ?'; + $args = array($shareID); + $query = \OCP\DB::prepare($sql); + $result = $query->execute($args); + + $share = Null; + + if ($result && $result->numRows() > 0) { + $share = $result->fetchRow(); + } + + return $share; + + } + +} diff --git a/apps/files_versions/ajax/preview.php b/apps/files_versions/ajax/preview.php new file mode 100644 index 00000000000..c24134df534 --- /dev/null +++ b/apps/files_versions/ajax/preview.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +\OC_Util::checkLoggedIn(); + +if(!\OC_App::isEnabled('files_versions')){ + exit; +} + +$file = array_key_exists('file', $_GET) ? (string) urldecode($_GET['file']) : ''; +$maxX = array_key_exists('x', $_GET) ? (int) $_GET['x'] : 44; +$maxY = array_key_exists('y', $_GET) ? (int) $_GET['y'] : 44; +$version = array_key_exists('version', $_GET) ? $_GET['version'] : ''; +$scalingUp = array_key_exists('scalingup', $_GET) ? (bool) $_GET['scalingup'] : true; + +if($file === '' && $version === '') { + \OC_Response::setStatus(400); //400 Bad Request + \OC_Log::write('core-preview', 'No file parameter was passed', \OC_Log::DEBUG); + exit; +} + +if($maxX === 0 || $maxY === 0) { + \OC_Response::setStatus(400); //400 Bad Request + \OC_Log::write('core-preview', 'x and/or y set to 0', \OC_Log::DEBUG); + exit; +} + +try{ + $preview = new \OC\Preview(\OC_User::getUser(), 'files_versions'); + $preview->setFile($file.'.v'.$version); + $preview->setMaxX($maxX); + $preview->setMaxY($maxY); + $preview->setScalingUp($scalingUp); + + $preview->showPreview(); +}catch(\Exception $e) { + \OC_Response::setStatus(500); + \OC_Log::write('core', $e->getmessage(), \OC_Log::DEBUG); +} diff --git a/apps/files_versions/appinfo/routes.php b/apps/files_versions/appinfo/routes.php index 38c288adf9d..8d2abaa89e5 100644 --- a/apps/files_versions/appinfo/routes.php +++ b/apps/files_versions/appinfo/routes.php @@ -7,3 +7,8 @@ // Register with the capabilities API OC_API::register('get', '/cloud/capabilities', array('OCA\Files_Versions\Capabilities', 'getCapabilities'), 'files_versions', OC_API::USER_AUTH); + +$this->create('core_ajax_versions_preview', '/preview.png')->action( +function() { + require_once __DIR__ . '/../ajax/preview.php'; +}); diff --git a/apps/files_versions/css/versions.css b/apps/files_versions/css/versions.css index 6a9b3a95698..c53935711c7 100644 --- a/apps/files_versions/css/versions.css +++ b/apps/files_versions/css/versions.css @@ -1,11 +1,11 @@ #dropdown.drop-versions { - width:22em; + width:24em; } #found_versions li { width: 100%; cursor: default; - height: 36px; + height: 56px; float: left; border-bottom: 1px solid rgba(100,100,100,.1); } @@ -21,6 +21,12 @@ filter: alpha(opacity=50); opacity: .5; } + +#found_versions li > a, +#found_versions li > span { + padding: 17px 7px; +} + #found_versions li > *:hover, #found_versions li > *:focus { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; @@ -33,6 +39,11 @@ padding-right: 4px; } +#found_versions img.preview { + cursor: default; + opacity: 1; +} + #found_versions .versionDate { min-width: 100px; vertical-align: text-bottom; diff --git a/apps/files_versions/js/versions.js b/apps/files_versions/js/versions.js index f57e931bad9..3f56a3eb698 100644 --- a/apps/files_versions/js/versions.js +++ b/apps/files_versions/js/versions.js @@ -129,6 +129,8 @@ function createVersionsDropdown(filename, files) { var path = OC.filePath('files_versions', '', 'download.php'); + var preview = '<img class="preview" src="'+revision.preview+'"/>'; + var download ='<a href="' + path + "?file=" + files + '&revision=' + revision.version + '">'; download+='<img'; download+=' src="' + OC.imagePath('core', 'actions/download') + '"'; @@ -146,7 +148,7 @@ function createVersionsDropdown(filename, files) { var version=$('<li/>'); version.attr('value', revision.version); - version.html(download + revert); + version.html(preview + download + revert); version.appendTo('#found_versions'); } diff --git a/apps/files_versions/lib/versions.php b/apps/files_versions/lib/versions.php index 0b4699dc5c0..fc8d0365c71 100644 --- a/apps/files_versions/lib/versions.php +++ b/apps/files_versions/lib/versions.php @@ -266,6 +266,7 @@ class Storage { $versions[$key]['version'] = $version; $versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($version); $versions[$key]['path'] = $filename; + $versions[$key]['preview'] = \OCP\Util::linkToRoute('core_ajax_versions_preview', array('file' => $filename, 'version' => $version)); $versions[$key]['size'] = $versions_fileview->filesize($filename.'.v'.$version); // if file with modified date exists, flag it in array as currently enabled version diff --git a/apps/user_ldap/ajax/getConfiguration.php b/apps/user_ldap/ajax/getConfiguration.php index baca588976f..fc51b459a25 100644 --- a/apps/user_ldap/ajax/getConfiguration.php +++ b/apps/user_ldap/ajax/getConfiguration.php @@ -27,5 +27,6 @@ OCP\JSON::checkAppEnabled('user_ldap'); OCP\JSON::callCheck(); $prefix = $_POST['ldap_serverconfig_chooser']; -$connection = new \OCA\user_ldap\lib\Connection($prefix); +$ldapWrapper = new OCA\user_ldap\lib\LDAP(); +$connection = new \OCA\user_ldap\lib\Connection($ldapWrapper, $prefix); OCP\JSON::success(array('configuration' => $connection->getConfiguration())); diff --git a/apps/user_ldap/ajax/setConfiguration.php b/apps/user_ldap/ajax/setConfiguration.php index d850bda2470..94de8835fbc 100644 --- a/apps/user_ldap/ajax/setConfiguration.php +++ b/apps/user_ldap/ajax/setConfiguration.php @@ -27,7 +27,8 @@ OCP\JSON::checkAppEnabled('user_ldap'); OCP\JSON::callCheck(); $prefix = $_POST['ldap_serverconfig_chooser']; -$connection = new \OCA\user_ldap\lib\Connection($prefix); +$ldapWrapper = new OCA\user_ldap\lib\LDAP(); +$connection = new \OCA\user_ldap\lib\Connection($ldapWrapper, $prefix); $connection->setConfiguration($_POST); $connection->saveConfiguration(); OCP\JSON::success(); diff --git a/apps/user_ldap/ajax/testConfiguration.php b/apps/user_ldap/ajax/testConfiguration.php index 7ce1258a796..0b8e4ccfe20 100644 --- a/apps/user_ldap/ajax/testConfiguration.php +++ b/apps/user_ldap/ajax/testConfiguration.php @@ -28,7 +28,8 @@ OCP\JSON::callCheck(); $l=OC_L10N::get('user_ldap'); -$connection = new \OCA\user_ldap\lib\Connection('', null); +$ldapWrapper = new OCA\user_ldap\lib\LDAP(); +$connection = new \OCA\user_ldap\lib\Connection($ldapWrapper, '', null); if($connection->setConfiguration($_POST)) { //Configuration is okay if($connection->bind()) { diff --git a/apps/user_ldap/appinfo/app.php b/apps/user_ldap/appinfo/app.php index 593e846bc03..9d6327181af 100644 --- a/apps/user_ldap/appinfo/app.php +++ b/apps/user_ldap/appinfo/app.php @@ -24,15 +24,15 @@ OCP\App::registerAdmin('user_ldap', 'settings'); $configPrefixes = OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes(true); +$ldapWrapper = new OCA\user_ldap\lib\LDAP(); if(count($configPrefixes) === 1) { - $connector = new OCA\user_ldap\lib\Connection($configPrefixes[0]); - $userBackend = new OCA\user_ldap\USER_LDAP(); - $userBackend->setConnector($connector); - $groupBackend = new OCA\user_ldap\GROUP_LDAP(); - $groupBackend->setConnector($connector); + $connector = new OCA\user_ldap\lib\Connection($ldapWrapper, $configPrefixes[0]); + $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 { - $userBackend = new OCA\user_ldap\User_Proxy($configPrefixes); - $groupBackend = new OCA\user_ldap\Group_Proxy($configPrefixes); + $userBackend = new OCA\user_ldap\User_Proxy($configPrefixes, $ldapWrapper); + $groupBackend = new OCA\user_ldap\Group_Proxy($configPrefixes, $ldapWrapper); } if(count($configPrefixes) > 0) { diff --git a/apps/user_ldap/group_ldap.php b/apps/user_ldap/group_ldap.php index 04ff392f920..32e2cec5960 100644 --- a/apps/user_ldap/group_ldap.php +++ b/apps/user_ldap/group_ldap.php @@ -23,13 +23,16 @@ namespace OCA\user_ldap; -class GROUP_LDAP extends lib\Access implements \OCP\GroupInterface { +use OCA\user_ldap\lib\Access; +use OCA\user_ldap\lib\BackendUtility; + +class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { protected $enabled = false; - public function setConnector(lib\Connection &$connection) { - parent::setConnector($connection); - $filter = $this->connection->ldapGroupFilter; - $gassoc = $this->connection->ldapGroupMemberAssocAttr; + public function __construct(Access $access) { + parent::__construct($access); + $filter = $this->access->connection->ldapGroupFilter; + $gassoc = $this->access->connection->ldapGroupMemberAssocAttr; if(!empty($filter) && !empty($gassoc)) { $this->enabled = true; } @@ -47,30 +50,31 @@ class GROUP_LDAP extends lib\Access implements \OCP\GroupInterface { if(!$this->enabled) { return false; } - if($this->connection->isCached('inGroup'.$uid.':'.$gid)) { - return $this->connection->getFromCache('inGroup'.$uid.':'.$gid); + if($this->access->connection->isCached('inGroup'.$uid.':'.$gid)) { + return $this->access->connection->getFromCache('inGroup'.$uid.':'.$gid); } - $dn_user = $this->username2dn($uid); - $dn_group = $this->groupname2dn($gid); + $dn_user = $this->access->username2dn($uid); + $dn_group = $this->access->groupname2dn($gid); // just in case if(!$dn_group || !$dn_user) { - $this->connection->writeToCache('inGroup'.$uid.':'.$gid, false); + $this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, false); return false; } //usually, LDAP attributes are said to be case insensitive. But there are exceptions of course. - $members = $this->readAttribute($dn_group, $this->connection->ldapGroupMemberAssocAttr); + $members = $this->access->readAttribute($dn_group, + $this->access->connection->ldapGroupMemberAssocAttr); if(!$members) { - $this->connection->writeToCache('inGroup'.$uid.':'.$gid, false); + $this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, false); return false; } //extra work if we don't get back user DNs //TODO: this can be done with one LDAP query - if(strtolower($this->connection->ldapGroupMemberAssocAttr) === 'memberuid') { + if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') { $dns = array(); foreach($members as $mid) { - $filter = str_replace('%uid', $mid, $this->connection->ldapLoginFilter); - $ldap_users = $this->fetchListOfUsers($filter, 'dn'); + $filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter); + $ldap_users = $this->access->fetchListOfUsers($filter, 'dn'); if(count($ldap_users) < 1) { continue; } @@ -80,7 +84,7 @@ class GROUP_LDAP extends lib\Access implements \OCP\GroupInterface { } $isInGroup = in_array($dn_user, $members); - $this->connection->writeToCache('inGroup'.$uid.':'.$gid, $isInGroup); + $this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, $isInGroup); return $isInGroup; } @@ -98,35 +102,36 @@ class GROUP_LDAP extends lib\Access implements \OCP\GroupInterface { return array(); } $cacheKey = 'getUserGroups'.$uid; - if($this->connection->isCached($cacheKey)) { - return $this->connection->getFromCache($cacheKey); + if($this->access->connection->isCached($cacheKey)) { + return $this->access->connection->getFromCache($cacheKey); } - $userDN = $this->username2dn($uid); + $userDN = $this->access->username2dn($uid); if(!$userDN) { - $this->connection->writeToCache($cacheKey, array()); + $this->access->connection->writeToCache($cacheKey, array()); return array(); } //uniqueMember takes DN, memberuid the uid, so we need to distinguish - if((strtolower($this->connection->ldapGroupMemberAssocAttr) === 'uniquemember') - || (strtolower($this->connection->ldapGroupMemberAssocAttr) === 'member') + if((strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'uniquemember') + || (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'member') ) { $uid = $userDN; - } else if(strtolower($this->connection->ldapGroupMemberAssocAttr) === 'memberuid') { - $result = $this->readAttribute($userDN, 'uid'); + } else if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') { + $result = $this->access->readAttribute($userDN, 'uid'); $uid = $result[0]; } else { // just in case $uid = $userDN; } - $filter = $this->combineFilterWithAnd(array( - $this->connection->ldapGroupFilter, - $this->connection->ldapGroupMemberAssocAttr.'='.$uid + $filter = $this->access->combineFilterWithAnd(array( + $this->access->connection->ldapGroupFilter, + $this->access->connection->ldapGroupMemberAssocAttr.'='.$uid )); - $groups = $this->fetchListOfGroups($filter, array($this->connection->ldapGroupDisplayName, 'dn')); - $groups = array_unique($this->ownCloudGroupNames($groups), SORT_LOCALE_STRING); - $this->connection->writeToCache($cacheKey, $groups); + $groups = $this->access->fetchListOfGroups($filter, + array($this->access->connection->ldapGroupDisplayName, 'dn')); + $groups = array_unique($this->access->ownCloudGroupNames($groups), SORT_LOCALE_STRING); + $this->access->connection->writeToCache($cacheKey, $groups); return $groups; } @@ -144,70 +149,71 @@ class GROUP_LDAP extends lib\Access implements \OCP\GroupInterface { } $cachekey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset; // check for cache of the exact query - $groupUsers = $this->connection->getFromCache($cachekey); + $groupUsers = $this->access->connection->getFromCache($cachekey); if(!is_null($groupUsers)) { return $groupUsers; } // check for cache of the query without limit and offset - $groupUsers = $this->connection->getFromCache('usersInGroup-'.$gid.'-'.$search); + $groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search); if(!is_null($groupUsers)) { $groupUsers = array_slice($groupUsers, $offset, $limit); - $this->connection->writeToCache($cachekey, $groupUsers); + $this->access->connection->writeToCache($cachekey, $groupUsers); return $groupUsers; } if($limit === -1) { $limit = null; } - $groupDN = $this->groupname2dn($gid); + $groupDN = $this->access->groupname2dn($gid); if(!$groupDN) { // group couldn't be found, return empty resultset - $this->connection->writeToCache($cachekey, array()); + $this->access->connection->writeToCache($cachekey, array()); return array(); } - $members = $this->readAttribute($groupDN, $this->connection->ldapGroupMemberAssocAttr); + $members = $this->access->readAttribute($groupDN, + $this->access->connection->ldapGroupMemberAssocAttr); if(!$members) { //in case users could not be retrieved, return empty resultset - $this->connection->writeToCache($cachekey, array()); + $this->access->connection->writeToCache($cachekey, array()); return array(); } $groupUsers = array(); - $isMemberUid = (strtolower($this->connection->ldapGroupMemberAssocAttr) === 'memberuid'); + $isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid'); foreach($members as $member) { if($isMemberUid) { //we got uids, need to get their DNs to 'tranlsate' them to usernames - $filter = $this->combineFilterWithAnd(array( + $filter = $this->access->combineFilterWithAnd(array( \OCP\Util::mb_str_replace('%uid', $member, - $this->connection->ldapLoginFilter, 'UTF-8'), - $this->getFilterPartForUserSearch($search) + $this->access->connection->ldapLoginFilter, 'UTF-8'), + $this->access->getFilterPartForUserSearch($search) )); - $ldap_users = $this->fetchListOfUsers($filter, 'dn'); + $ldap_users = $this->access->fetchListOfUsers($filter, 'dn'); if(count($ldap_users) < 1) { continue; } - $groupUsers[] = $this->dn2username($ldap_users[0]); + $groupUsers[] = $this->access->dn2username($ldap_users[0]); } else { //we got DNs, check if we need to filter by search or we can give back all of them if(!empty($search)) { - if(!$this->readAttribute($member, - $this->connection->ldapUserDisplayName, - $this->getFilterPartForUserSearch($search))) { + if(!$this->access->readAttribute($member, + $this->access->connection->ldapUserDisplayName, + $this->access->getFilterPartForUserSearch($search))) { continue; } } // dn2username will also check if the users belong to the allowed base - if($ocname = $this->dn2username($member)) { + if($ocname = $this->access->dn2username($member)) { $groupUsers[] = $ocname; } } } natsort($groupUsers); - $this->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers); + $this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers); $groupUsers = array_slice($groupUsers, $offset, $limit); - $this->connection->writeToCache($cachekey, $groupUsers); + $this->access->connection->writeToCache($cachekey, $groupUsers); return $groupUsers; } @@ -245,7 +251,7 @@ class GROUP_LDAP extends lib\Access implements \OCP\GroupInterface { //Check cache before driving unnecessary searches \OCP\Util::writeLog('user_ldap', 'getGroups '.$cachekey, \OCP\Util::DEBUG); - $ldap_groups = $this->connection->getFromCache($cachekey); + $ldap_groups = $this->access->connection->getFromCache($cachekey); if(!is_null($ldap_groups)) { return $ldap_groups; } @@ -255,16 +261,18 @@ class GROUP_LDAP extends lib\Access implements \OCP\GroupInterface { if($limit <= 0) { $limit = null; } - $filter = $this->combineFilterWithAnd(array( - $this->connection->ldapGroupFilter, - $this->getFilterPartForGroupSearch($search) + $filter = $this->access->combineFilterWithAnd(array( + $this->access->connection->ldapGroupFilter, + $this->access->getFilterPartForGroupSearch($search) )); \OCP\Util::writeLog('user_ldap', 'getGroups Filter '.$filter, \OCP\Util::DEBUG); - $ldap_groups = $this->fetchListOfGroups($filter, array($this->connection->ldapGroupDisplayName, 'dn'), - $limit, $offset); - $ldap_groups = $this->ownCloudGroupNames($ldap_groups); + $ldap_groups = $this->access->fetchListOfGroups($filter, + array($this->access->connection->ldapGroupDisplayName, 'dn'), + $limit, + $offset); + $ldap_groups = $this->access->ownCloudGroupNames($ldap_groups); - $this->connection->writeToCache($cachekey, $ldap_groups); + $this->access->connection->writeToCache($cachekey, $ldap_groups); return $ldap_groups; } @@ -278,25 +286,26 @@ class GROUP_LDAP extends lib\Access implements \OCP\GroupInterface { * @return bool */ public function groupExists($gid) { - if($this->connection->isCached('groupExists'.$gid)) { - return $this->connection->getFromCache('groupExists'.$gid); + if($this->access->connection->isCached('groupExists'.$gid)) { + return $this->access->connection->getFromCache('groupExists'.$gid); } - //getting dn, if false the group does not exist. If dn, it may be mapped only, requires more checking. - $dn = $this->groupname2dn($gid); + //getting dn, if false the group does not exist. If dn, it may be mapped + //only, requires more checking. + $dn = $this->access->groupname2dn($gid); if(!$dn) { - $this->connection->writeToCache('groupExists'.$gid, false); + $this->access->connection->writeToCache('groupExists'.$gid, false); return false; } //if group really still exists, we will be able to read its objectclass - $objcs = $this->readAttribute($dn, 'objectclass'); + $objcs = $this->access->readAttribute($dn, 'objectclass'); if(!$objcs || empty($objcs)) { - $this->connection->writeToCache('groupExists'.$gid, false); + $this->access->connection->writeToCache('groupExists'.$gid, false); return false; } - $this->connection->writeToCache('groupExists'.$gid, true); + $this->access->connection->writeToCache('groupExists'.$gid, true); return true; } diff --git a/apps/user_ldap/group_proxy.php b/apps/user_ldap/group_proxy.php index eb6f176c58c..acc563c9532 100644 --- a/apps/user_ldap/group_proxy.php +++ b/apps/user_ldap/group_proxy.php @@ -23,6 +23,8 @@ namespace OCA\user_ldap; +use OCA\user_ldap\lib\ILDAPWrapper; + class Group_Proxy extends lib\Proxy implements \OCP\GroupInterface { private $backends = array(); private $refBackend = null; @@ -31,12 +33,11 @@ class Group_Proxy extends lib\Proxy implements \OCP\GroupInterface { * @brief Constructor * @param $serverConfigPrefixes array containing the config Prefixes */ - public function __construct($serverConfigPrefixes) { - parent::__construct(); + public function __construct($serverConfigPrefixes, ILDAPWrapper $ldap) { + parent::__construct($ldap); foreach($serverConfigPrefixes as $configPrefix) { - $this->backends[$configPrefix] = new \OCA\user_ldap\GROUP_LDAP(); - $connector = $this->getConnector($configPrefix); - $this->backends[$configPrefix]->setConnector($connector); + $this->backends[$configPrefix] = + new \OCA\user_ldap\GROUP_LDAP($this->getAccess($configPrefix)); if(is_null($this->refBackend)) { $this->refBackend = &$this->backends[$configPrefix]; } diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index 52aa39012fd..fdf9c24612d 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -23,12 +23,13 @@ namespace OCA\user_ldap\lib; -abstract class Access { - protected $connection; +class Access extends LDAPUtility { + public $connection; //never ever check this var directly, always use getPagedSearchResultState protected $pagedSearchedSuccessful; - public function setConnector(Connection &$connection) { + public function __construct(Connection $connection, ILDAPWrapper $ldap) { + parent::__construct($ldap); $this->connection = $connection; } @@ -54,14 +55,14 @@ abstract class Access { return false; } $cr = $this->connection->getConnectionResource(); - if(!is_resource($cr)) { + if(!$this->ldap->isResource($cr)) { //LDAP not available \OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', \OCP\Util::DEBUG); return false; } $dn = $this->DNasBaseParameter($dn); - $rr = @ldap_read($cr, $dn, $filter, array($attr)); - if(!is_resource($rr)) { + $rr = @$this->ldap->read($cr, $dn, $filter, array($attr)); + if(!$this->ldap->isResource($rr)) { if(!empty($attr)) { //do not throw this message on userExists check, irritates \OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN '.$dn, \OCP\Util::DEBUG); @@ -73,13 +74,14 @@ abstract class Access { \OCP\Util::writeLog('user_ldap', 'readAttribute: '.$dn.' found', \OCP\Util::DEBUG); return array(); } - $er = ldap_first_entry($cr, $rr); - if(!is_resource($er)) { + $er = $this->ldap->firstEntry($cr, $rr); + if(!$this->ldap->isResource($er)) { //did not match the filter, return false return false; } //LDAP attributes are not case sensitive - $result = \OCP\Util::mb_array_change_key_case(ldap_get_attributes($cr, $er), MB_CASE_LOWER, 'UTF-8'); + $result = \OCP\Util::mb_array_change_key_case( + $this->ldap->getAttributes($cr, $er), MB_CASE_LOWER, 'UTF-8'); $attr = mb_strtolower($attr, 'UTF-8'); if(isset($result[$attr]) && $result[$attr]['count'] > 0) { @@ -653,7 +655,7 @@ abstract class Access { // See if we have a resource, in case not cancel with message $link_resource = $this->connection->getConnectionResource(); - if(!is_resource($link_resource)) { + if(!$this->ldap->isResource($link_resource)) { // Seems like we didn't find any resource. // Return an empty array just like before. \OCP\Util::writeLog('user_ldap', 'Could not search, because resource is missing.', \OCP\Util::DEBUG); @@ -664,11 +666,12 @@ abstract class Access { $pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, $limit, $offset); $linkResources = array_pad(array(), count($base), $link_resource); - $sr = ldap_search($linkResources, $base, $filter, $attr); - $error = ldap_errno($link_resource); + $sr = $this->ldap->search($linkResources, $base, $filter, $attr); + $error = $this->ldap->errno($link_resource); if(!is_array($sr) || $error !== 0) { \OCP\Util::writeLog('user_ldap', - 'Error when searching: '.ldap_error($link_resource).' code '.ldap_errno($link_resource), + 'Error when searching: '.$this->ldap->error($link_resource). + ' code '.$this->ldap->errno($link_resource), \OCP\Util::ERROR); \OCP\Util::writeLog('user_ldap', 'Attempt for Paging? '.print_r($pagedSearchOK, true), \OCP\Util::ERROR); return array(); @@ -677,19 +680,19 @@ abstract class Access { // Do the server-side sorting foreach(array_reverse($attr) as $sortAttr){ foreach($sr as $searchResource) { - ldap_sort($link_resource, $searchResource, $sortAttr); + $this->ldap->sort($link_resource, $searchResource, $sortAttr); } } $findings = array(); foreach($sr as $key => $res) { - $findings = array_merge($findings, ldap_get_entries($link_resource, $res )); + $findings = array_merge($findings, $this->ldap->getEntries($link_resource, $res )); } if($pagedSearchOK) { \OCP\Util::writeLog('user_ldap', 'Paged search successful', \OCP\Util::INFO); foreach($sr as $key => $res) { $cookie = null; - if(ldap_control_paged_result_response($link_resource, $res, $cookie)) { + if($this->ldap->controlPagedResultResponse($link_resource, $res, $cookie)) { \OCP\Util::writeLog('user_ldap', 'Set paged search cookie', \OCP\Util::INFO); $this->setPagedResultCookie($base[$key], $filter, $limit, $offset, $cookie); } @@ -1103,8 +1106,9 @@ abstract class Access { if($offset > 0) { \OCP\Util::writeLog('user_ldap', 'Cookie '.$cookie, \OCP\Util::INFO); } - $pagedSearchOK = ldap_control_paged_result($this->connection->getConnectionResource(), - $limit, false, $cookie); + $pagedSearchOK = $this->ldap->controlPagedResult( + $this->connection->getConnectionResource(), $limit, + false, $cookie); if(!$pagedSearchOK) { return false; } diff --git a/apps/user_ldap/lib/backendutility.php b/apps/user_ldap/lib/backendutility.php new file mode 100644 index 00000000000..815757a1a11 --- /dev/null +++ b/apps/user_ldap/lib/backendutility.php @@ -0,0 +1,38 @@ +<?php + +/** + * ownCloud – LDAP BackendUtility + * + * @author Arthur Schiwon + * @copyright 2013 Arthur Schiwon blizzz@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\user_ldap\lib; + +use OCA\user_ldap\lib\Access; + +abstract class BackendUtility { + protected $access; + + /** + * @brief constructor, make sure the subclasses call this one! + * @param $access an instance of Access for LDAP interaction + */ + public function __construct(Access $access) { + $this->access = $access; + } +} diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index e5d9b4d5b40..a53022c27b3 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -23,7 +23,7 @@ namespace OCA\user_ldap\lib; -class Connection { +class Connection extends LDAPUtility { private $ldapConnectionRes = null; private $configPrefix; private $configID; @@ -60,7 +60,7 @@ class Connection { 'ldapQuotaDefault' => null, 'ldapEmailAttribute' => null, 'ldapCacheTTL' => null, - 'ldapUuidAttribute' => null, + 'ldapUuidAttribute' => 'auto', 'ldapOverrideUuidAttribute' => null, 'ldapOverrideMainServer' => false, 'ldapConfigurationActive' => false, @@ -77,7 +77,8 @@ class Connection { * @param $configPrefix a string with the prefix for the configkey column (appconfig table) * @param $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections */ - public function __construct($configPrefix = '', $configID = 'user_ldap') { + public function __construct(ILDAPWrapper $ldap, $configPrefix = '', $configID = 'user_ldap') { + parent::__construct($ldap); $this->configPrefix = $configPrefix; $this->configID = $configID; $memcache = new \OC\Memcache\Factory(); @@ -86,13 +87,14 @@ class Connection { } else { $this->cache = \OC_Cache::getGlobalCache(); } - $this->config['hasPagedResultSupport'] = (function_exists('ldap_control_paged_result') - && function_exists('ldap_control_paged_result_response')); + $this->config['hasPagedResultSupport'] = + $this->ldap->hasPagedResultSupport(); } public function __destruct() { - if(!$this->dontDestruct && is_resource($this->ldapConnectionRes)) { - @ldap_unbind($this->ldapConnectionRes); + if(!$this->dontDestruct && + $this->ldap->isResource($this->ldapConnectionRes)) { + @$this->ldap->unbind($this->ldapConnectionRes); }; } @@ -148,7 +150,7 @@ class Connection { public function getConnectionResource() { if(!$this->ldapConnectionRes) { $this->init(); - } else if(!is_resource($this->ldapConnectionRes)) { + } else if(!$this->ldap->isResource($this->ldapConnectionRes)) { $this->ldapConnectionRes = null; $this->establishConnection(); } @@ -361,6 +363,14 @@ class Connection { && $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; @@ -386,7 +396,8 @@ class Connection { public function saveConfiguration() { $trans = array_flip($this->getConfigTranslationArray()); foreach($this->config as $key => $value) { - \OCP\Util::writeLog('user_ldap', 'LDAP: storing key '.$key.' value '.$value, \OCP\Util::DEBUG); + \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); @@ -431,8 +442,9 @@ class Connection { $config[$dbKey] = ''; } continue; - } else if((strpos($classKey, 'ldapBase') !== false) - || (strpos($classKey, 'ldapAttributes') !== false)) { + } else if((strpos($classKey, 'ldapBase') !== false + || strpos($classKey, 'ldapAttributes') !== false) + && is_array($this->config[$classKey])) { $config[$dbKey] = implode("\n", $this->config[$classKey]); continue; } @@ -551,7 +563,7 @@ class Connection { * @returns an associative array with the default values. Keys are correspond * to config-value entries in the database table */ - public function getDefaults() { + static public function getDefaults() { return array( 'ldap_host' => '', 'ldap_port' => '389', @@ -603,7 +615,7 @@ class Connection { return false; } if(!$this->ldapConnectionRes) { - if(!function_exists('ldap_connect')) { + 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.', @@ -623,7 +635,8 @@ class Connection { if(!$this->config['ldapOverrideMainServer'] && !$this->getFromCache('overrideMainServer')) { $this->doConnect($this->config['ldapHost'], $this->config['ldapPort']); $bindStatus = $this->bind(); - $error = is_resource($this->ldapConnectionRes) ? ldap_errno($this->ldapConnectionRes) : -1; + $error = $this->ldap->isResource($this->ldapConnectionRes) ? + $this->ldap->errno($this->ldapConnectionRes) : -1; } else { $bindStatus = false; $error = null; @@ -653,11 +666,11 @@ class Connection { //ldap_connect ignores port paramater when URLs are passed $host .= ':' . $port; } - $this->ldapConnectionRes = ldap_connect($host, $port); - if(ldap_set_option($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) { - if(ldap_set_option($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) { + $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']) { - ldap_start_tls($this->ldapConnectionRes); + $this->ldap->startTls($this->ldapConnectionRes); } } } @@ -678,13 +691,15 @@ class Connection { $getConnectionResourceAttempt = true; $cr = $this->getConnectionResource(); $getConnectionResourceAttempt = false; - if(!is_resource($cr)) { + if(!$this->ldap->isResource($cr)) { return false; } - $ldapLogin = @ldap_bind($cr, $this->config['ldapAgentName'], $this->config['ldapAgentPassword']); + $ldapLogin = @$this->ldap->bind($cr, + $this->config['ldapAgentName'], + $this->config['ldapAgentPassword']); if(!$ldapLogin) { \OCP\Util::writeLog('user_ldap', - 'Bind failed: ' . ldap_errno($cr) . ': ' . ldap_error($cr), + 'Bind failed: ' . $this->ldap->errno($cr) . ': ' . $this->ldap->error($cr), \OCP\Util::ERROR); $this->ldapConnectionRes = null; return false; diff --git a/apps/user_ldap/lib/ildapwrapper.php b/apps/user_ldap/lib/ildapwrapper.php new file mode 100644 index 00000000000..9e6bd56ef2a --- /dev/null +++ b/apps/user_ldap/lib/ildapwrapper.php @@ -0,0 +1,180 @@ +<?php + +/** + * ownCloud – LDAP Wrapper Interface + * + * @author Arthur Schiwon + * @copyright 2013 Arthur Schiwon blizzz@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\user_ldap\lib; + +interface ILDAPWrapper { + + //LDAP functions in use + + /** + * @brief Bind to LDAP directory + * @param $link LDAP link resource + * @param $dn an RDN to log in with + * @param $password the password + * @return true on success, false otherwise + * + * with $dn and $password as null a anonymous bind is attempted. + */ + public function bind($link, $dn, $password); + + /** + * @brief connect to an LDAP server + * @param $host The host to connect to + * @param $port The port to connect to + * @return a link resource on success, otherwise false + */ + public function connect($host, $port); + + /** + * @brief Send LDAP pagination control + * @param $link LDAP link resource + * @param $pagesize number of results per page + * @param $isCritical Indicates whether the pagination is critical of not. + * @param $cookie structure sent by LDAP server + * @return true on success, false otherwise + */ + public function controlPagedResult($link, $pagesize, $isCritical, $cookie); + + /** + * @brief Retrieve the LDAP pagination cookie + * @param $link LDAP link resource + * @param $result LDAP result resource + * @param $cookie structure sent by LDAP server + * @return true on success, false otherwise + * + * Corresponds to ldap_control_paged_result_response + */ + public function controlPagedResultResponse($link, $result, &$cookie); + + /** + * @brief Return the LDAP error number of the last LDAP command + * @param $link LDAP link resource + * @return error message as string + */ + public function errno($link); + + /** + * @brief Return the LDAP error message of the last LDAP command + * @param $link LDAP link resource + * @return error code as integer + */ + public function error($link); + + /** + * @brief Return first result id + * @param $link LDAP link resource + * @param $result LDAP result resource + * @return an LDAP search result resource + * */ + public function firstEntry($link, $result); + + /** + * @brief Get attributes from a search result entry + * @param $link LDAP link resource + * @param $result LDAP result resource + * @return array containing the results, false on error + * */ + public function getAttributes($link, $result); + + /** + * @brief Get all result entries + * @param $link LDAP link resource + * @param $result LDAP result resource + * @return array containing the results, false on error + */ + public function getEntries($link, $result); + + /** + * @brief Read an entry + * @param $link LDAP link resource + * @param $baseDN The DN of the entry to read from + * @param $filter An LDAP filter + * @param $attr array of the attributes to read + * @return an LDAP search result resource + */ + public function read($link, $baseDN, $filter, $attr); + + /** + * @brief Search LDAP tree + * @param $link LDAP link resource + * @param $baseDN The DN of the entry to read from + * @param $filter An LDAP filter + * @param $attr array of the attributes to read + * @return an LDAP search result resource, false on error + */ + public function search($link, $baseDN, $filter, $attr); + + /** + * @brief Sets the value of the specified option to be $value + * @param $link LDAP link resource + * @param $option a defined LDAP Server option + * @param $value the new value for the option + * @return true on success, false otherwise + */ + public function setOption($link, $option, $value); + + /** + * @brief establish Start TLS + * @param $link LDAP link resource + * @return true on success, false otherwise + */ + public function startTls($link); + + /** + * @brief Sort the result of a LDAP search + * @param $link LDAP link resource + * @param $result LDAP result resource + * @param $sortfilter attribute to use a key in sort + */ + public function sort($link, $result, $sortfilter); + + /** + * @brief Unbind from LDAP directory + * @param $link LDAP link resource + * @return true on success, false otherwise + */ + public function unbind($link); + + //additional required methods in owncloud + + /** + * @brief Checks whether the server supports LDAP + * @return true if it the case, false otherwise + * */ + public function areLDAPFunctionsAvailable(); + + /** + * @brief Checks whether PHP supports LDAP Paged Results + * @return true if it the case, false otherwise + * */ + public function hasPagedResultSupport(); + + /** + * @brief Checks whether the submitted parameter is a resource + * @param $resource the resource variable to check + * @return true if it is a resource, false otherwise + */ + public function isResource($resource); + +} diff --git a/apps/user_ldap/lib/jobs.php b/apps/user_ldap/lib/jobs.php index 6b7666d4ca1..2f90da3bfb6 100644 --- a/apps/user_ldap/lib/jobs.php +++ b/apps/user_ldap/lib/jobs.php @@ -139,13 +139,14 @@ class Jobs extends \OC\BackgroundJob\TimedJob { return self::$groupBE; } $configPrefixes = Helper::getServerConfigurationPrefixes(true); - if(count($configPrefixes) == 1) { + $ldapWrapper = new OCA\user_ldap\lib\LDAP(); + if(count($configPrefixes) === 1) { //avoid the proxy when there is only one LDAP server configured - $connector = new Connection($configPrefixes[0]); - self::$groupBE = new \OCA\user_ldap\GROUP_LDAP(); - self::$groupBE->setConnector($connector); + $connector = new OCA\user_ldap\lib\Connection($ldapWrapper, $configPrefixes[0]); + $ldapAccess = new OCA\user_ldap\lib\Access($connector, $ldapWrapper); + self::$groupBE = new OCA\user_ldap\GROUP_LDAP($ldapAccess); } else { - self::$groupBE = new \OCA\user_ldap\Group_Proxy($configPrefixes); + self::$groupBE = new \OCA\user_ldap\Group_Proxy($configPrefixes, $ldapWrapper); } return self::$groupBE; diff --git a/apps/user_ldap/lib/ldap.php b/apps/user_ldap/lib/ldap.php new file mode 100644 index 00000000000..b63e969912a --- /dev/null +++ b/apps/user_ldap/lib/ldap.php @@ -0,0 +1,167 @@ +<?php + +/** + * ownCloud – LDAP Wrapper + * + * @author Arthur Schiwon + * @copyright 2013 Arthur Schiwon blizzz@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\user_ldap\lib; + +class LDAP implements ILDAPWrapper { + protected $curFunc = ''; + protected $curArgs = array(); + + public function bind($link, $dn, $password) { + return $this->invokeLDAPMethod('bind', $link, $dn, $password); + } + + public function connect($host, $port) { + return $this->invokeLDAPMethod('connect', $host, $port); + } + + public function controlPagedResultResponse($link, $result, &$cookie) { + $this->preFunctionCall('ldap_control_paged_result_response', + array($link, $result, $cookie)); + $result = ldap_control_paged_result_response($link, $result, $cookie); + $this->postFunctionCall(); + + return $result; + } + + public function controlPagedResult($link, $pagesize, $isCritical, $cookie) { + return $this->invokeLDAPMethod('control_paged_result', $link, $pagesize, + $isCritical, $cookie); + } + + public function errno($link) { + return $this->invokeLDAPMethod('errno', $link); + } + + public function error($link) { + return $this->invokeLDAPMethod('error', $link); + } + + public function firstEntry($link, $result) { + return $this->invokeLDAPMethod('first_entry', $link, $result); + } + + public function getAttributes($link, $result) { + return $this->invokeLDAPMethod('get_attributes', $link, $result); + } + + public function getEntries($link, $result) { + return $this->invokeLDAPMethod('get_entries', $link, $result); + } + + public function read($link, $baseDN, $filter, $attr) { + return $this->invokeLDAPMethod('read', $link, $baseDN, $filter, $attr); + } + + public function search($link, $baseDN, $filter, $attr) { + return $this->invokeLDAPMethod('search', $link, $baseDN, + $filter, $attr); + } + + public function setOption($link, $option, $value) { + $this->invokeLDAPMethod('set_option', $link, $option, $value); + } + + public function sort($link, $result, $sortfilter) { + return $this->invokeLDAPMethod('sort', $link, $result, $sortfilter); + } + + public function startTls($link) { + return $this->invokeLDAPMethod('start_tls', $link); + } + + public function unbind($link) { + return $this->invokeLDAPMethod('unbind', $link); + } + + /** + * @brief Checks whether the server supports LDAP + * @return true if it the case, false otherwise + * */ + public function areLDAPFunctionsAvailable() { + return function_exists('ldap_connect'); + } + + /** + * @brief Checks whether PHP supports LDAP Paged Results + * @return true if it the case, false otherwise + * */ + public function hasPagedResultSupport() { + $hasSupport = function_exists('ldap_control_paged_result') + && function_exists('ldap_control_paged_result_response'); + return $hasSupport; + } + + /** + * @brief Checks whether the submitted parameter is a resource + * @param $resource the resource variable to check + * @return true if it is a resource, false otherwise + */ + public function isResource($resource) { + return is_resource($resource); + } + + private function invokeLDAPMethod() { + $arguments = func_get_args(); + $func = 'ldap_' . array_shift($arguments); + if(function_exists($func)) { + $this->preFunctionCall($func, $arguments); + $result = call_user_func_array($func, $arguments); + $this->postFunctionCall(); + return $result; + } + } + + private function preFunctionCall($functionName, $args) { + $this->curFunc = $functionName; + $this->curArgs = $args; + } + + private function postFunctionCall() { + if($this->isResource($this->curArgs[0])) { + $errorCode = ldap_errno($this->curArgs[0]); + $errorMsg = ldap_error($this->curArgs[0]); + if($errorCode !== 0) { + if($this->curFunc === 'ldap_sort' && $errorCode === -4) { + //You can safely ignore that decoding error. + //… says https://bugs.php.net/bug.php?id=18023 + } else if($this->curFunc === 'ldap_get_entries' + && $errorCode === -4) { + } else if ($errorCode === 32) { + //for now + } else if ($errorCode === 10) { + //referrals, we switch them off, but then there is AD :) + } else { + \OCP\Util::writeLog('user_ldap', + 'LDAP error '.$errorMsg.' (' . + $errorCode.') after calling '. + $this->curFunc, + \OCP\Util::DEBUG); + } + } + } + + $this->curFunc = ''; + $this->curArgs = array(); + } +}
\ No newline at end of file diff --git a/apps/user_ldap/lib/ldaputility.php b/apps/user_ldap/lib/ldaputility.php new file mode 100644 index 00000000000..7fffd9c88d1 --- /dev/null +++ b/apps/user_ldap/lib/ldaputility.php @@ -0,0 +1,36 @@ +<?php + +/** + * ownCloud – LDAP LDAPUtility + * + * @author Arthur Schiwon + * @copyright 2013 Arthur Schiwon blizzz@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\user_ldap\lib; + +abstract class LDAPUtility { + protected $ldap; + + /** + * @brief constructor, make sure the subclasses call this one! + * @param $ldapWrapper an instance of an ILDAPWrapper + */ + public function __construct(ILDAPWrapper $ldapWrapper) { + $this->ldap = $ldapWrapper; + } +} diff --git a/apps/user_ldap/lib/proxy.php b/apps/user_ldap/lib/proxy.php index ae3e3be7361..c74b357bdd2 100644 --- a/apps/user_ldap/lib/proxy.php +++ b/apps/user_ldap/lib/proxy.php @@ -23,26 +23,27 @@ namespace OCA\user_ldap\lib; +use OCA\user_ldap\lib\Access; + abstract class Proxy { - static private $connectors = array(); + static private $accesses = array(); + private $ldap = null; - public function __construct() { + public function __construct(ILDAPWrapper $ldap) { + $this->ldap = $ldap; $this->cache = \OC_Cache::getGlobalCache(); } - private function addConnector($configPrefix) { - self::$connectors[$configPrefix] = new \OCA\user_ldap\lib\Connection($configPrefix); + private function addAccess($configPrefix) { + $connector = new Connection($this->ldap, $configPrefix); + self::$accesses[$configPrefix] = new Access($connector, $this->ldap); } - protected function getConnector($configPrefix) { - if(!isset(self::$connectors[$configPrefix])) { - $this->addConnector($configPrefix); + protected function getAccess($configPrefix) { + if(!isset(self::$accesses[$configPrefix])) { + $this->addAccess($configPrefix); } - return self::$connectors[$configPrefix]; - } - - protected function getConnectors() { - return self::$connectors; + return self::$accesses[$configPrefix]; } protected function getUserCacheKey($uid) { diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php index 7169192a18e..b7070f23183 100644 --- a/apps/user_ldap/settings.php +++ b/apps/user_ldap/settings.php @@ -49,14 +49,9 @@ $tmpl->assign('serverConfigurationPrefixes', $prefixes); $tmpl->assign('serverConfigurationHosts', $hosts); // assign default values -if(!isset($ldap)) { - $ldap = new \OCA\user_ldap\lib\Connection(); -} -$defaults = $ldap->getDefaults(); +$defaults = \OCA\user_ldap\lib\Connection::getDefaults(); foreach($defaults as $key => $default) { $tmpl->assign($key.'_default', $default); } -// $tmpl->assign(); - return $tmpl->fetchPage(); diff --git a/apps/user_ldap/tests/group_ldap.php b/apps/user_ldap/tests/group_ldap.php deleted file mode 100644 index ae635597b71..00000000000 --- a/apps/user_ldap/tests/group_ldap.php +++ /dev/null @@ -1,46 +0,0 @@ -<?php -/** -* ownCloud -* -* @author Arthur Schiwon -* @copyright 2012 Arthur Schiwon blizzz@owncloud.com -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE -* License as published by the Free Software Foundation; either -* version 3 of the License, or any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU AFFERO GENERAL PUBLIC LICENSE for more details. -* -* You should have received a copy of the GNU Affero General Public -* License along with this library. If not, see <http://www.gnu.org/licenses/>. -* -*/ - -class Test_Group_Ldap extends PHPUnit_Framework_TestCase { - function setUp() { - OC_Group::clearBackends(); - } - - function testSingleBackend() { - OC_Group::useBackend(new OCA\user_ldap\GROUP_LDAP()); - $group_ldap = new OCA\user_ldap\GROUP_LDAP(); - - $this->assertIsA(OC_Group::getGroups(), gettype(array())); - $this->assertIsA($group_ldap->getGroups(), gettype(array())); - - $this->assertFalse(OC_Group::inGroup('john', 'dosers'), gettype(false)); - $this->assertFalse($group_ldap->inGroup('john', 'dosers'), gettype(false)); - //TODO: check also for expected true result. This backend won't be able to do any modifications, maybe use a dummy for this. - - $this->assertIsA(OC_Group::getUserGroups('john doe'), gettype(array())); - $this->assertIsA($group_ldap->getUserGroups('john doe'), gettype(array())); - - $this->assertIsA(OC_Group::usersInGroup('campers'), gettype(array())); - $this->assertIsA($group_ldap->usersInGroup('campers'), gettype(array())); - } - -} diff --git a/apps/user_ldap/tests/user_ldap.php b/apps/user_ldap/tests/user_ldap.php new file mode 100644 index 00000000000..6b9b8b3e185 --- /dev/null +++ b/apps/user_ldap/tests/user_ldap.php @@ -0,0 +1,411 @@ +<?php +/** +* ownCloud +* +* @author Arthur Schiwon +* @copyright 2013 Arthur Schiwon blizzz@owncloud.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU AFFERO GENERAL PUBLIC LICENSE for more details. +* +* You should have received a copy of the GNU Affero General Public +* License along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +namespace OCA\user_ldap\tests; + +use \OCA\user_ldap\USER_LDAP as UserLDAP; +use \OCA\user_ldap\lib\Access; +use \OCA\user_ldap\lib\Connection; +use \OCA\user_ldap\lib\ILDAPWrapper; + +class Test_User_Ldap_Direct extends \PHPUnit_Framework_TestCase { + protected $backend; + + public function setUp() { + \OC_User::clearBackends(); + \OC_Group::clearBackends(); + } + + private function getAccessMock() { + static $conMethods; + static $accMethods; + + if(is_null($conMethods) || is_null($accMethods)) { + $conMethods = get_class_methods('\OCA\user_ldap\lib\Connection'); + $accMethods = get_class_methods('\OCA\user_ldap\lib\Access'); + } + $lw = $this->getMock('\OCA\user_ldap\lib\ILDAPWrapper'); + $connector = $this->getMock('\OCA\user_ldap\lib\Connection', + $conMethods, + array($lw, null, null)); + $access = $this->getMock('\OCA\user_ldap\lib\Access', + $accMethods, + array($connector, $lw)); + + return $access; + } + + private function prepareMockForUserExists(&$access) { + $access->expects($this->any()) + ->method('username2dn') + ->will($this->returnCallback(function($uid) { + switch ($uid) { + case 'gunslinger': + return 'dnOfRoland'; + break; + case 'formerUser': + return 'dnOfFormerUser'; + break; + case 'newyorker': + return 'dnOfNewYorker'; + break; + case 'ladyofshadows': + return 'dnOfLadyOfShadows'; + break; + defautl: + return false; + } + })); + } + + /** + * @brief Prepares the Access mock for checkPassword tests + * @param $access mock of \OCA\user_ldap\lib\Access + * @return void + */ + private function prepareAccessForCheckPassword(&$access) { + $access->connection->expects($this->any()) + ->method('__get') + ->will($this->returnCallback(function($name) { + if($name === 'ldapLoginFilter') { + return '%uid'; + } + return null; + })); + + $access->expects($this->any()) + ->method('fetchListOfUsers') + ->will($this->returnCallback(function($filter) { + if($filter === 'roland') { + return array('dnOfRoland'); + } + return array(); + })); + + $access->expects($this->any()) + ->method('dn2username') + ->with($this->equalTo('dnOfRoland')) + ->will($this->returnValue('gunslinger')); + + $access->expects($this->any()) + ->method('areCredentialsValid') + ->will($this->returnCallback(function($dn, $pwd) { + if($pwd === 'dt19') { + return true; + } + return false; + })); + } + + public function testCheckPassword() { + $access = $this->getAccessMock(); + $this->prepareAccessForCheckPassword($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); + + $result = $backend->checkPassword('roland', 'dt19'); + $this->assertEquals('gunslinger', $result); + + $result = $backend->checkPassword('roland', 'wrong'); + $this->assertFalse($result); + + $result = $backend->checkPassword('mallory', 'evil'); + $this->assertFalse($result); + } + + public function testCheckPasswordPublicAPI() { + $access = $this->getAccessMock(); + $this->prepareAccessForCheckPassword($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); + + $result = \OCP\User::checkPassword('roland', 'dt19'); + $this->assertEquals('gunslinger', $result); + + $result = \OCP\User::checkPassword('roland', 'wrong'); + $this->assertFalse($result); + + $result = \OCP\User::checkPassword('mallory', 'evil'); + $this->assertFalse($result); + } + + /** + * @brief Prepares the Access mock for getUsers tests + * @param $access mock of \OCA\user_ldap\lib\Access + * @return void + */ + private function prepareAccessForGetUsers(&$access) { + $access->expects($this->any()) + ->method('getFilterPartForUserSearch') + ->will($this->returnCallback(function($search) { + return $search; + })); + + $access->expects($this->any()) + ->method('combineFilterWithAnd') + ->will($this->returnCallback(function($param) { + return $param[1]; + })); + + $access->expects($this->any()) + ->method('fetchListOfUsers') + ->will($this->returnCallback(function($search, $a, $l, $o) { + $users = array('gunslinger', 'newyorker', 'ladyofshadows'); + if(empty($search)) { + $result = $users; + } else { + $result = array(); + foreach($users as $user) { + if(stripos($user, $search) !== false) { + $result[] = $user; + } + } + } + if(!is_null($l) || !is_null($o)) { + $result = array_slice($result, $o, $l); + } + return $result; + })); + + $access->expects($this->any()) + ->method('ownCloudUserNames') + ->will($this->returnArgument(0)); + } + + public function testGetUsers() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); + + $result = $backend->getUsers(); + $this->assertEquals(3, count($result)); + + $result = $backend->getUsers('', 1, 2); + $this->assertEquals(1, count($result)); + + $result = $backend->getUsers('', 2, 1); + $this->assertEquals(2, count($result)); + + $result = $backend->getUsers('yo'); + $this->assertEquals(2, count($result)); + + $result = $backend->getUsers('nix'); + $this->assertEquals(0, count($result)); + } + + public function testGetUsersViaAPI() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); + + $result = \OCP\User::getUsers(); + $this->assertEquals(3, count($result)); + + $result = \OCP\User::getUsers('', 1, 2); + $this->assertEquals(1, count($result)); + + $result = \OCP\User::getUsers('', 2, 1); + $this->assertEquals(2, count($result)); + + $result = \OCP\User::getUsers('yo'); + $this->assertEquals(2, count($result)); + + $result = \OCP\User::getUsers('nix'); + $this->assertEquals(0, count($result)); + } + + public function testUserExists() { + $access = $this->getAccessMock(); + $backend = new UserLDAP($access); + $this->prepareMockForUserExists($access); + + $access->expects($this->any()) + ->method('readAttribute') + ->will($this->returnCallback(function($dn) { + if($dn === 'dnOfRoland') { + return array(); + } + return false; + })); + + //test for existing user + $result = $backend->userExists('gunslinger'); + $this->assertTrue($result); + + //test for deleted user + $result = $backend->userExists('formerUser'); + $this->assertFalse($result); + + //test for never-existing user + $result = $backend->userExists('mallory'); + $this->assertFalse($result); + } + + public function testUserExistsPublicAPI() { + $access = $this->getAccessMock(); + $backend = new UserLDAP($access); + $this->prepareMockForUserExists($access); + \OC_User::useBackend($backend); + + $access->expects($this->any()) + ->method('readAttribute') + ->will($this->returnCallback(function($dn) { + if($dn === 'dnOfRoland') { + return array(); + } + return false; + })); + + //test for existing user + $result = \OCP\User::userExists('gunslinger'); + $this->assertTrue($result); + + //test for deleted user + $result = \OCP\User::userExists('formerUser'); + $this->assertFalse($result); + + //test for never-existing user + $result = \OCP\User::userExists('mallory'); + $this->assertFalse($result); + } + + public function testDeleteUser() { + $access = $this->getAccessMock(); + $backend = new UserLDAP($access); + + //we do not support deleting users at all + $result = $backend->deleteUser('gunslinger'); + $this->assertFalse($result); + } + + public function testGetHome() { + $access = $this->getAccessMock(); + $backend = new UserLDAP($access); + $this->prepareMockForUserExists($access); + + $access->connection->expects($this->any()) + ->method('__get') + ->will($this->returnCallback(function($name) { + if($name === 'homeFolderNamingRule') { + return 'attr:testAttribute'; + } + return null; + })); + + $access->expects($this->any()) + ->method('readAttribute') + ->will($this->returnCallback(function($dn, $attr) { + switch ($dn) { + case 'dnOfRoland': + if($attr === 'testAttribute') { + return array('/tmp/rolandshome/'); + } + return array(); + break; + case 'dnOfLadyOfShadows': + if($attr === 'testAttribute') { + return array('susannah/'); + } + return array(); + break; + default: + return false; + } + })); + + //absolut path + $result = $backend->getHome('gunslinger'); + $this->assertEquals('/tmp/rolandshome/', $result); + + //datadir-relativ path + $result = $backend->getHome('ladyofshadows'); + $datadir = \OCP\Config::getSystemValue('datadirectory', + \OC::$SERVERROOT.'/data'); + $this->assertEquals($datadir.'/susannah/', $result); + + //no path at all – triggers OC default behaviour + $result = $backend->getHome('newyorker'); + $this->assertFalse($result); + } + + private function prepareAccessForGetDisplayName(&$access) { + $access->connection->expects($this->any()) + ->method('__get') + ->will($this->returnCallback(function($name) { + if($name === 'ldapUserDisplayName') { + return 'displayname'; + } + return null; + })); + + $access->expects($this->any()) + ->method('readAttribute') + ->will($this->returnCallback(function($dn, $attr) { + switch ($dn) { + case 'dnOfRoland': + if($attr === 'displayname') { + return array('Roland Deschain'); + } + return array(); + break; + + default: + return false; + } + })); + } + + public function testGetDisplayName() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetDisplayName($access); + $backend = new UserLDAP($access); + $this->prepareMockForUserExists($access); + + //with displayName + $result = $backend->getDisplayName('gunslinger'); + $this->assertEquals('Roland Deschain', $result); + + //empty displayname retrieved + $result = $backend->getDisplayName('newyorker'); + $this->assertEquals(null, $result); + } + + public function testGetDisplayNamePublicAPI() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetDisplayName($access); + $backend = new UserLDAP($access); + $this->prepareMockForUserExists($access); + \OC_User::useBackend($backend); + + //with displayName + $result = \OCP\User::getDisplayName('gunslinger'); + $this->assertEquals('Roland Deschain', $result); + + //empty displayname retrieved + $result = \OCP\User::getDisplayName('newyorker'); + $this->assertEquals('newyorker', $result); + } + + //no test for getDisplayNames, because it just invokes getUsers and + //getDisplayName +}
\ No newline at end of file diff --git a/apps/user_ldap/user_ldap.php b/apps/user_ldap/user_ldap.php index 850ca0df995..6f52bbdf233 100644 --- a/apps/user_ldap/user_ldap.php +++ b/apps/user_ldap/user_ldap.php @@ -25,37 +25,46 @@ namespace OCA\user_ldap; -class USER_LDAP extends lib\Access implements \OCP\UserInterface { +use OCA\user_ldap\lib\ILDAPWrapper; +use OCA\user_ldap\lib\BackendUtility; + +class USER_LDAP extends BackendUtility implements \OCP\UserInterface { private function updateQuota($dn) { $quota = null; - $quotaDefault = $this->connection->ldapQuotaDefault; - $quotaAttribute = $this->connection->ldapQuotaAttribute; + $quotaDefault = $this->access->connection->ldapQuotaDefault; + $quotaAttribute = $this->access->connection->ldapQuotaAttribute; if(!empty($quotaDefault)) { $quota = $quotaDefault; } if(!empty($quotaAttribute)) { - $aQuota = $this->readAttribute($dn, $quotaAttribute); + $aQuota = $this->access->readAttribute($dn, $quotaAttribute); if($aQuota && (count($aQuota) > 0)) { $quota = $aQuota[0]; } } if(!is_null($quota)) { - \OCP\Config::setUserValue($this->dn2username($dn), 'files', 'quota', \OCP\Util::computerFileSize($quota)); + \OCP\Config::setUserValue( $this->access->dn2username($dn), + 'files', + 'quota', + \OCP\Util::computerFileSize($quota)); } } private function updateEmail($dn) { $email = null; - $emailAttribute = $this->connection->ldapEmailAttribute; + $emailAttribute = $this->access->connection->ldapEmailAttribute; if(!empty($emailAttribute)) { - $aEmail = $this->readAttribute($dn, $emailAttribute); + $aEmail = $this->access->readAttribute($dn, $emailAttribute); if($aEmail && (count($aEmail) > 0)) { $email = $aEmail[0]; } if(!is_null($email)) { - \OCP\Config::setUserValue($this->dn2username($dn), 'settings', 'email', $email); + \OCP\Config::setUserValue( $this->access->dn2username($dn), + 'settings', + 'email', + $email); } } } @@ -70,15 +79,16 @@ class USER_LDAP extends lib\Access implements \OCP\UserInterface { */ public function checkPassword($uid, $password) { //find out dn of the user name - $filter = \OCP\Util::mb_str_replace('%uid', $uid, $this->connection->ldapLoginFilter, 'UTF-8'); - $ldap_users = $this->fetchListOfUsers($filter, 'dn'); + $filter = \OCP\Util::mb_str_replace( + '%uid', $uid, $this->access->connection->ldapLoginFilter, 'UTF-8'); + $ldap_users = $this->access->fetchListOfUsers($filter, 'dn'); if(count($ldap_users) < 1) { return false; } $dn = $ldap_users[0]; //do we have a username for him/her? - $ocname = $this->dn2username($dn); + $ocname = $this->access->dn2username($dn); if($ocname) { //update some settings, if necessary @@ -86,7 +96,7 @@ class USER_LDAP extends lib\Access implements \OCP\UserInterface { $this->updateEmail($dn); //are the credentials OK? - if(!$this->areCredentialsValid($dn, $password)) { + if(!$this->access->areCredentialsValid($dn, $password)) { return false; } @@ -107,7 +117,7 @@ class USER_LDAP extends lib\Access implements \OCP\UserInterface { $cachekey = 'getUsers-'.$search.'-'.$limit.'-'.$offset; //check if users are cached, if so return - $ldap_users = $this->connection->getFromCache($cachekey); + $ldap_users = $this->access->connection->getFromCache($cachekey); if(!is_null($ldap_users)) { return $ldap_users; } @@ -117,21 +127,23 @@ class USER_LDAP extends lib\Access implements \OCP\UserInterface { if($limit <= 0) { $limit = null; } - $filter = $this->combineFilterWithAnd(array( - $this->connection->ldapUserFilter, - $this->getFilterPartForUserSearch($search) + $filter = $this->access->combineFilterWithAnd(array( + $this->access->connection->ldapUserFilter, + $this->access->getFilterPartForUserSearch($search) )); \OCP\Util::writeLog('user_ldap', 'getUsers: Options: search '.$search.' limit '.$limit.' offset '.$offset.' Filter: '.$filter, \OCP\Util::DEBUG); //do the search and translate results to owncloud names - $ldap_users = $this->fetchListOfUsers($filter, array($this->connection->ldapUserDisplayName, 'dn'), + $ldap_users = $this->access->fetchListOfUsers( + $filter, + array($this->access->connection->ldapUserDisplayName, 'dn'), $limit, $offset); - $ldap_users = $this->ownCloudUserNames($ldap_users); + $ldap_users = $this->access->ownCloudUserNames($ldap_users); \OCP\Util::writeLog('user_ldap', 'getUsers: '.count($ldap_users). ' Users found', \OCP\Util::DEBUG); - $this->connection->writeToCache($cachekey, $ldap_users); + $this->access->connection->writeToCache($cachekey, $ldap_users); return $ldap_users; } @@ -141,24 +153,25 @@ class USER_LDAP extends lib\Access implements \OCP\UserInterface { * @return boolean */ public function userExists($uid) { - if($this->connection->isCached('userExists'.$uid)) { - return $this->connection->getFromCache('userExists'.$uid); + if($this->access->connection->isCached('userExists'.$uid)) { + return $this->access->connection->getFromCache('userExists'.$uid); } - //getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking. - $dn = $this->username2dn($uid); + $dn = $this->access->username2dn($uid); if(!$dn) { - $this->connection->writeToCache('userExists'.$uid, false); + \OCP\Util::writeLog('user_ldap', 'No DN found for '.$uid.' on '. + $this->access->connection->ldapHost, \OCP\Util::DEBUG); + $this->access->connection->writeToCache('userExists'.$uid, false); return false; } - //check if user really still exists by reading its entry - if(!is_array($this->readAttribute($dn, ''))) { - $this->connection->writeToCache('userExists'.$uid, false); + if(!is_array($this->access->readAttribute($dn, ''))) { + \OCP\Util::writeLog('user_ldap', 'LDAP says no user '.$dn, \OCP\Util::DEBUG); + $this->access->connection->writeToCache('userExists'.$uid, false); return false; } - $this->connection->writeToCache('userExists'.$uid, true); + $this->access->connection->writeToCache('userExists'.$uid, true); $this->updateQuota($dn); return true; } @@ -186,12 +199,13 @@ class USER_LDAP extends lib\Access implements \OCP\UserInterface { } $cacheKey = 'getHome'.$uid; - if($this->connection->isCached($cacheKey)) { - return $this->connection->getFromCache($cacheKey); + if($this->access->connection->isCached($cacheKey)) { + return $this->access->connection->getFromCache($cacheKey); } - if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) { - $attr = substr($this->connection->homeFolderNamingRule, strlen('attr:')); - $homedir = $this->readAttribute($this->username2dn($uid), $attr); + if(strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0) { + $attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:')); + $homedir = $this->access->readAttribute( + $this->access->username2dn($uid), $attr); if($homedir && isset($homedir[0])) { $path = $homedir[0]; //if attribute's value is an absolute path take this, otherwise append it to data dir @@ -206,13 +220,13 @@ class USER_LDAP extends lib\Access implements \OCP\UserInterface { $homedir = \OCP\Config::getSystemValue('datadirectory', \OC::$SERVERROOT.'/data' ) . '/' . $homedir[0]; } - $this->connection->writeToCache($cacheKey, $homedir); + $this->access->connection->writeToCache($cacheKey, $homedir); return $homedir; } } //false will apply default behaviour as defined and done by OC_User - $this->connection->writeToCache($cacheKey, false); + $this->access->connection->writeToCache($cacheKey, false); return false; } @@ -227,16 +241,16 @@ class USER_LDAP extends lib\Access implements \OCP\UserInterface { } $cacheKey = 'getDisplayName'.$uid; - if(!is_null($displayName = $this->connection->getFromCache($cacheKey))) { + if(!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) { return $displayName; } - $displayName = $this->readAttribute( - $this->username2dn($uid), - $this->connection->ldapUserDisplayName); + $displayName = $this->access->readAttribute( + $this->access->username2dn($uid), + $this->access->connection->ldapUserDisplayName); if($displayName && (count($displayName) > 0)) { - $this->connection->writeToCache($cacheKey, $displayName[0]); + $this->access->connection->writeToCache($cacheKey, $displayName[0]); return $displayName[0]; } @@ -251,7 +265,7 @@ class USER_LDAP extends lib\Access implements \OCP\UserInterface { */ public function getDisplayNames($search = '', $limit = null, $offset = null) { $cacheKey = 'getDisplayNames-'.$search.'-'.$limit.'-'.$offset; - if(!is_null($displayNames = $this->connection->getFromCache($cacheKey))) { + if(!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) { return $displayNames; } @@ -260,7 +274,7 @@ class USER_LDAP extends lib\Access implements \OCP\UserInterface { foreach ($users as $user) { $displayNames[$user] = $this->getDisplayName($user); } - $this->connection->writeToCache($cacheKey, $displayNames); + $this->access->connection->writeToCache($cacheKey, $displayNames); return $displayNames; } diff --git a/apps/user_ldap/user_proxy.php b/apps/user_ldap/user_proxy.php index 0722d8871a4..092fdbf7c78 100644 --- a/apps/user_ldap/user_proxy.php +++ b/apps/user_ldap/user_proxy.php @@ -23,6 +23,8 @@ namespace OCA\user_ldap; +use OCA\user_ldap\lib\ILDAPWrapper; + class User_Proxy extends lib\Proxy implements \OCP\UserInterface { private $backends = array(); private $refBackend = null; @@ -31,12 +33,11 @@ class User_Proxy extends lib\Proxy implements \OCP\UserInterface { * @brief Constructor * @param $serverConfigPrefixes array containing the config Prefixes */ - public function __construct($serverConfigPrefixes) { - parent::__construct(); + public function __construct($serverConfigPrefixes, ILDAPWrapper $ldap) { + parent::__construct($ldap); foreach($serverConfigPrefixes as $configPrefix) { - $this->backends[$configPrefix] = new \OCA\user_ldap\USER_LDAP(); - $connector = $this->getConnector($configPrefix); - $this->backends[$configPrefix]->setConnector($connector); + $this->backends[$configPrefix] = + new \OCA\user_ldap\USER_LDAP($this->getAccess($configPrefix)); if(is_null($this->refBackend)) { $this->refBackend = &$this->backends[$configPrefix]; } |