diff options
33 files changed, 720 insertions, 156 deletions
diff --git a/apps/files/index.php b/apps/files/index.php index 9ae378d7a1d..8f6838aa0d9 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -108,7 +108,6 @@ if ($needUpgrade) { // 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(); } diff --git a/apps/files_encryption/lib/helper.php b/apps/files_encryption/lib/helper.php index 0ac6fcf403a..17bcac5c585 100755 --- a/apps/files_encryption/lib/helper.php +++ b/apps/files_encryption/lib/helper.php @@ -225,10 +225,7 @@ class Helper { * @return bool */ public static function isPublicAccess() { - if (\OCP\USER::getUser() === false - || (isset($_GET['service']) && $_GET['service'] == 'files' - && isset($_GET['t'])) - ) { + if (\OCP\User::getUser() === false) { return true; } else { return false; @@ -256,10 +253,45 @@ class Helper { } /** - * @brief get path to the correspondig file in data/user/files if path points + * @brief try to get the user from the path if no user is logged in + * @param string $path + * @return mixed user or false if we couldn't determine a user + */ + public static function getUser($path) { + + $user = \OCP\User::getUser(); + + + // if we are logged in, then we return the userid + if ($user) { + return $user; + } + + // if no user is logged in we try to access a publicly shared files. + // In this case we need to try to get the user from the path + + $trimmed = ltrim($path, '/'); + $split = explode('/', $trimmed); + + // it is not a file relative to data/user/files + if (count($split) < 2 || $split[1] !== 'files') { + return false; + } + + $user = $split[0]; + + if (\OCP\User::userExists($user)) { + return $user; + } + + return false; + } + + /** + * @brief get path to the corresponding file in data/user/files if path points * to a version or to a file in cache * @param string $path path to a version or a file in the trash - * @return string path to correspondig file relative to data/user/files + * @return string path to corresponding file relative to data/user/files */ public static function getPathToRealFile($path) { $trimmed = ltrim($path, '/'); diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 3427e8a963a..b2c756894b4 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -112,21 +112,18 @@ class Keymanager { * @brief store file encryption key * * @param \OC_FilesystemView $view + * @param \OCA\Encryption\Util $util * @param string $path relative path of the file, including filename - * @param $userId - * @param $catfile - * @internal param string $key + * @param string $catfile keyfile content * @return bool true/false * @note The keyfile is not encrypted here. Client code must * asymmetrically encrypt the keyfile before passing it to this method */ - public static function setFileKey(\OC_FilesystemView $view, $path, $userId, $catfile) { + public static function setFileKey(\OC_FilesystemView $view, $util, $path, $catfile) { $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; - //here we need the currently logged in user, while userId can be a different user - $util = new Util($view, \OCP\User::getUser()); list($owner, $filename) = $util->getUidAndFilename($path); // in case of system wide mount points the keys are stored directly in the data directory @@ -172,15 +169,15 @@ class Keymanager { /** * @brief retrieve keyfile for an encrypted file * @param \OC_FilesystemView $view + * @param \OCA\Encryption\Util $util * @param $filePath * @internal param \OCA\Encryption\file $string name * @return string file key or false * @note The keyfile returned is asymmetrically encrypted. Decryption * of the keyfile must be performed by client code */ - public static function getFileKey(\OC_FilesystemView $view, $filePath) { + public static function getFileKey($view, $util, $filePath) { - $util = new Util($view, \OCP\User::getUser()); list($owner, $filename) = $util->getUidAndFilename($filePath); $filename = Helper::stripPartialFileExtension($filename); @@ -216,17 +213,17 @@ class Keymanager { * @brief Delete a keyfile * * @param \OC_FilesystemView $view - * @param string $userId username * @param string $path path of the file the key belongs to * @return bool Outcome of unlink operation * @note $path must be relative to data/user/files. e.g. mydoc.txt NOT * /data/admin/files/mydoc.txt */ - public static function deleteFileKey(\OC_FilesystemView $view, $userId, $path) { + public static function deleteFileKey(\OC_FilesystemView $view, $path) { $trimmed = ltrim($path, '/'); - $util = new Util($view, \OCP\User::getUser()); + $userId = Helper::getUser($path); + $util = new Util($view, $userId); if($util->isSystemWideMountPoint($path)) { $keyPath = '/files_encryption/keyfiles/' . $trimmed; @@ -315,16 +312,15 @@ class Keymanager { /** * @brief store multiple share keys for a single file * @param \OC_FilesystemView $view - * @param $path + * @param \OCA\Encryption\Util $util + * @param string $path * @param array $shareKeys * @return bool */ - public static function setShareKeys(\OC_FilesystemView $view, $path, array $shareKeys) { + public static function setShareKeys(\OC_FilesystemView $view, $util, $path, array $shareKeys) { // $shareKeys must be an array with the following format: // [userId] => [encrypted key] - // Here we need the currently logged in user, while userId can be a different user - $util = new Util($view, \OCP\User::getUser()); list($owner, $filename) = $util->getUidAndFilename($path); @@ -363,21 +359,18 @@ class Keymanager { * @brief retrieve shareKey for an encrypted file * @param \OC_FilesystemView $view * @param string $userId + * @param \OCA\Encryption\Util $util * @param string $filePath - * @internal param \OCA\Encryption\file $string name * @return string file key or false * @note The sharekey returned is encrypted. Decryption * of the keyfile must be performed by client code */ - public static function getShareKey(\OC_FilesystemView $view, $userId, $filePath) { + public static function getShareKey(\OC_FilesystemView $view, $userId, $util, $filePath) { // try reusing key file if part file $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; - //here we need the currently logged in user, while userId can be a different user - $util = new Util($view, \OCP\User::getUser()); - list($owner, $filename) = $util->getUidAndFilename($filePath); $filename = Helper::stripPartialFileExtension($filename); // in case of system wide mount points the keys are stored directly in the data directory @@ -444,8 +437,9 @@ class Keymanager { $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; - //here we need the currently logged in user, while userId can be a different user - $util = new Util($view, \OCP\User::getUser()); + $userId = Helper::getUser($filePath); + + $util = new Util($view, $userId); list($owner, $filename) = $util->getUidAndFilename($filePath); diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index a8c74bd9dd4..5ba3bfa784f 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -47,8 +47,10 @@ class Proxy extends \OC_FileProxy { */ private static function shouldEncrypt($path) { + $userId = Helper::getUser($path); + if (\OCP\App::isEnabled('files_encryption') === false || Crypt::mode() !== 'server' || - strpos($path, '/' . \OCP\User::getUser() . '/files') !== 0) { + strpos($path, '/' . $userId . '/files') !== 0) { return false; } @@ -201,7 +203,7 @@ class Proxy extends \OC_FileProxy { list($owner, $ownerPath) = $util->getUidAndFilename($relativePath); // Delete keyfile & shareKey so it isn't orphaned - if (!Keymanager::deleteFileKey($view, $owner, $ownerPath)) { + if (!Keymanager::deleteFileKey($view, $ownerPath)) { \OCP\Util::writeLog('Encryption library', 'Keyfile or shareKey could not be deleted for file "' . $ownerPath . '"', \OCP\Util::ERROR); } @@ -244,9 +246,6 @@ class Proxy extends \OC_FileProxy { // split the path parts $pathParts = explode('/', $path); - // get relative path - $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); - // FIXME: handling for /userId/cache used by webdav for chunking. The cache chunks are NOT encrypted if (isset($pathParts[2]) && $pathParts[2] === 'cache') { return $result; @@ -260,7 +259,8 @@ class Proxy extends \OC_FileProxy { $view = new \OC_FilesystemView(''); - $util = new Util($view, \OCP\USER::getUser()); + $userId = Helper::getUser($path); + $util = new Util($view, $userId); // If file is already encrypted, decrypt using crypto protocol if ( @@ -323,7 +323,7 @@ class Proxy extends \OC_FileProxy { $view = new \OC_FilesystemView('/'); - $userId = \OCP\User::getUser(); + $userId = Helper::getUser($path); $util = new Util($view, $userId); // if encryption is no longer enabled or if the files aren't migrated yet @@ -401,7 +401,7 @@ class Proxy extends \OC_FileProxy { $view = new \OC_FilesystemView('/'); $session = new \OCA\Encryption\Session($view); - $userId = \OCP\User::getUser(); + $userId = Helper::getUser($path); $util = new Util($view, $userId); // split the path parts diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 1186a5f1d8d..7a37d2200a4 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -55,6 +55,7 @@ class Stream { private $rawPath; // The raw path relative to the data dir private $relPath; // rel path to users file dir private $userId; + private $keyId; private $handle; // Resource returned by fopen private $meta = array(); // Header / meta for source stream private $writeCache; @@ -90,17 +91,22 @@ class Stream { $this->rootView = new \OC_FilesystemView('/'); } - $this->session = new \OCA\Encryption\Session($this->rootView); - - $this->privateKey = $this->session->getPrivateKey($this->userId); - $util = new Util($this->rootView, \OCP\USER::getUser()); + $this->session = new \OCA\Encryption\Session($this->rootView); - $this->userId = $util->getUserId(); + $this->privateKey = $this->session->getPrivateKey(); // rawPath is relative to the data directory $this->rawPath = \OC\Files\Filesystem::normalizePath(str_replace('crypt://', '', $path)); + $this->userId = Helper::getUser($this->rawPath); + + $util = new Util($this->rootView, $this->userId); + + // get the key ID which we want to use, can be the users key or the + // public share key + $this->keyId = $util->getKeyId(); + // Strip identifier text from path, this gives us the path relative to data/<user>/files $this->relPath = Helper::stripUserFilesPath($this->rawPath); // if raw path doesn't point to a real file, check if it is a version or a file in the trash bin @@ -250,12 +256,13 @@ class Stream { // Fetch and decrypt keyfile // Fetch existing keyfile - $this->encKeyfile = Keymanager::getFileKey($this->rootView, $this->relPath); + $util = new \OCA\Encryption\Util($this->rootView, $this->userId); + $this->encKeyfile = Keymanager::getFileKey($this->rootView, $util, $this->relPath); // If a keyfile already exists if ($this->encKeyfile) { - $shareKey = Keymanager::getShareKey($this->rootView, $this->userId, $this->relPath); + $shareKey = Keymanager::getShareKey($this->rootView, $this->keyId, $util, $this->relPath); // if there is no valid private key return false if ($this->privateKey === false) { @@ -503,7 +510,7 @@ class Stream { \OC_FileProxy::$enabled = false; // Fetch user's public key - $this->publicKey = Keymanager::getPublicKey($this->rootView, $this->userId); + $this->publicKey = Keymanager::getPublicKey($this->rootView, $this->keyId); // Check if OC sharing api is enabled $sharingEnabled = \OCP\Share::isEnabled(); @@ -521,10 +528,10 @@ class Stream { $this->encKeyfiles = Crypt::multiKeyEncrypt($this->plainKey, $publicKeys); // Save the new encrypted file key - Keymanager::setFileKey($this->rootView, $this->relPath, $this->userId, $this->encKeyfiles['data']); + Keymanager::setFileKey($this->rootView, $util, $this->relPath, $this->encKeyfiles['data']); // Save the sharekeys - Keymanager::setShareKeys($this->rootView, $this->relPath, $this->encKeyfiles['keys']); + Keymanager::setShareKeys($this->rootView, $util, $this->relPath, $this->encKeyfiles['keys']); // Re-enable proxy - our work is done \OC_FileProxy::$enabled = $proxyStatus; diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 7e46a5016a3..434ed225644 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -38,7 +38,8 @@ class Util { 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 $userId; // ID of the user we use to encrypt/decrypt files + private $keyId; // ID of the key we want to manipulate private $client; // Client side encryption mode flag private $publicKeyDir; // Dir containing all public user keys private $encryptionDir; // Dir containing user's files_encryption @@ -58,51 +59,33 @@ class Util { public function __construct(\OC_FilesystemView $view, $userId, $client = false) { $this->view = $view; - $this->userId = $userId; $this->client = $client; - $this->isPublic = false; + $this->userId = $userId; $this->publicShareKeyId = \OC_Appconfig::getValue('files_encryption', 'publicShareKeyId'); $this->recoveryKeyId = \OC_Appconfig::getValue('files_encryption', 'recoveryKeyId'); - // if we are anonymous/public - if (\OCA\Encryption\Helper::isPublicAccess()) { - $this->userId = $this->publicShareKeyId; - - // only handle for files_sharing app - if (isset($GLOBALS['app']) && $GLOBALS['app'] === 'files_sharing') { - $this->userDir = '/' . $GLOBALS['fileOwner']; - $this->fileFolderName = 'files'; - $this->userFilesDir = '/' . $GLOBALS['fileOwner'] . '/' - . $this->fileFolderName; // TODO: Does this need to be user configurable? - $this->publicKeyDir = '/' . 'public-keys'; - $this->encryptionDir = '/' . $GLOBALS['fileOwner'] . '/' . 'files_encryption'; - $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; - $this->shareKeysPath = $this->encryptionDir . '/' . 'share-keys'; - $this->publicKeyPath = - $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key - $this->privateKeyPath = - '/owncloud_private_key/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key - $this->isPublic = true; - // make sure that the owners home is mounted - \OC\Files\Filesystem::initMountPoints($GLOBALS['fileOwner']); - } - - } else { - $this->userDir = '/' . $this->userId; - $this->fileFolderName = 'files'; - $this->userFilesDir = - '/' . $this->userId . '/' . $this->fileFolderName; // TODO: Does this need to be user configurable? - $this->publicKeyDir = '/' . 'public-keys'; - $this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption'; - $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; - $this->shareKeysPath = $this->encryptionDir . '/' . 'share-keys'; - $this->publicKeyPath = + $this->userDir = '/' . $this->userId; + $this->fileFolderName = 'files'; + $this->userFilesDir = + '/' . $userId . '/' . $this->fileFolderName; // TODO: Does this need to be user configurable? + $this->publicKeyDir = '/' . 'public-keys'; + $this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption'; + $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; + $this->shareKeysPath = $this->encryptionDir . '/' . 'share-keys'; + $this->publicKeyPath = $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key - $this->privateKeyPath = + $this->privateKeyPath = $this->encryptionDir . '/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key - // make sure that the owners home is mounted - \OC\Files\Filesystem::initMountPoints($this->userId); + // make sure that the owners home is mounted + \OC\Files\Filesystem::initMountPoints($userId); + + if (\OCA\Encryption\Helper::isPublicAccess()) { + $this->keyId = $this->publicShareKeyId; + $this->isPublic = true; + } else { + $this->keyId = $this->userId; + $this->isPublic = false; } } @@ -188,13 +171,13 @@ class Util { // check if public-key exists but private-key is missing if ($this->view->file_exists($this->publicKeyPath) && !$this->view->file_exists($this->privateKeyPath)) { \OCP\Util::writeLog('Encryption library', - 'public key exists but private key is missing for "' . $this->userId . '"', \OCP\Util::FATAL); + 'public key exists but private key is missing for "' . $this->keyId . '"', \OCP\Util::FATAL); return false; } else { if (!$this->view->file_exists($this->publicKeyPath) && $this->view->file_exists($this->privateKeyPath) ) { \OCP\Util::writeLog('Encryption library', - 'private key exists but public key is missing for "' . $this->userId . '"', \OCP\Util::FATAL); + 'private key exists but public key is missing for "' . $this->keyId . '"', \OCP\Util::FATAL); return false; } } @@ -367,7 +350,7 @@ class Util { // scanning every file like this // will eat server resources :( if ( - Keymanager::getFileKey($this->view, $relPath) + Keymanager::getFileKey($this->view, $this, $relPath) && $isEncryptedPath ) { @@ -478,7 +461,7 @@ class Util { $relPath = Helper::stripUserFilesPath($path); } - $fileKey = Keymanager::getFileKey($this->view, $relPath); + $fileKey = Keymanager::getFileKey($this->view, $this, $relPath); if ($fileKey === false) { return false; @@ -1056,10 +1039,10 @@ class Util { private function decryptKeyfile($filePath, $privateKey) { // Get the encrypted keyfile - $encKeyfile = Keymanager::getFileKey($this->view, $filePath); + $encKeyfile = Keymanager::getFileKey($this->view, $this, $filePath); // The file has a shareKey and must use it for decryption - $shareKey = Keymanager::getShareKey($this->view, $this->userId, $filePath); + $shareKey = Keymanager::getShareKey($this->view, $this->keyId, $this, $filePath); $plainKeyfile = Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey); @@ -1110,8 +1093,8 @@ class Util { // Save the recrypted key to it's owner's keyfiles directory // Save new sharekeys to all necessary user directory if ( - !Keymanager::setFileKey($this->view, $filePath, $fileOwner, $multiEncKey['data']) - || !Keymanager::setShareKeys($this->view, $filePath, $multiEncKey['keys']) + !Keymanager::setFileKey($this->view, $this, $filePath, $multiEncKey['data']) + || !Keymanager::setShareKeys($this->view, $this, $filePath, $multiEncKey['keys']) ) { \OCP\Util::writeLog('Encryption library', @@ -1337,7 +1320,7 @@ class Util { // handle public access if ($this->isPublic) { $filename = $path; - $fileOwnerUid = $GLOBALS['fileOwner']; + $fileOwnerUid = $this->userId; return array( $fileOwnerUid, @@ -1565,6 +1548,13 @@ class Util { /** * @return string */ + public function getKeyId() { + return $this->keyId; + } + + /** + * @return string + */ public function getUserFilesDir() { return $this->userFilesDir; } diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 9c32ee06453..ca14e3e2ccb 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -157,6 +157,8 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase { $filename = 'tmp-' . time() . '.test'; + $util = new Encryption\Util(new \OC_FilesystemView(), $this->userId); + $cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/'. $filename, $this->dataShort); // Test that data was successfully written @@ -176,10 +178,10 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase { $this->assertNotEquals($this->dataShort, $retreivedCryptedFile); // Get the encrypted keyfile - $encKeyfile = Encryption\Keymanager::getFileKey($this->view, $filename); + $encKeyfile = Encryption\Keymanager::getFileKey($this->view, $util, $filename); // Attempt to fetch the user's shareKey - $shareKey = Encryption\Keymanager::getShareKey($this->view, $this->userId, $filename); + $shareKey = Encryption\Keymanager::getShareKey($this->view, $this->userId, $util, $filename); // get session $session = new \OCA\Encryption\Session($this->view); @@ -199,7 +201,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase { // Teardown $this->view->unlink($this->userId . '/files/' . $filename); - Encryption\Keymanager::deleteFileKey($this->view, $this->userId, $filename); + Encryption\Keymanager::deleteFileKey($this->view, $filename); } /** @@ -214,6 +216,8 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase { // Generate a a random filename $filename = 'tmp-' . time() . '.test'; + $util = new Encryption\Util(new \OC_FilesystemView(), $this->userId); + // Save long data as encrypted file using stream wrapper $cryptedFile = file_put_contents('crypt:///' . $this->userId . '/files/' . $filename, $this->dataLong . $this->dataLong); @@ -250,10 +254,10 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase { //print_r($e); // Get the encrypted keyfile - $encKeyfile = Encryption\Keymanager::getFileKey($this->view, $filename); + $encKeyfile = Encryption\Keymanager::getFileKey($this->view, $util, $filename); // Attempt to fetch the user's shareKey - $shareKey = Encryption\Keymanager::getShareKey($this->view, $this->userId, $filename); + $shareKey = Encryption\Keymanager::getShareKey($this->view, $this->userId, $util, $filename); // get session $session = new \OCA\Encryption\Session($this->view); @@ -283,7 +287,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase { $this->view->unlink($this->userId . '/files/' . $filename); - Encryption\Keymanager::deleteFileKey($this->view, $this->userId, $filename); + Encryption\Keymanager::deleteFileKey($this->view, $filename); } diff --git a/apps/files_encryption/tests/keymanager.php b/apps/files_encryption/tests/keymanager.php index ad6bbd3a7e9..1467979f005 100644 --- a/apps/files_encryption/tests/keymanager.php +++ b/apps/files_encryption/tests/keymanager.php @@ -145,13 +145,15 @@ class Test_Encryption_Keymanager extends \PHPUnit_Framework_TestCase { $file = 'unittest-' . time() . '.txt'; + $util = new Encryption\Util($this->view, $this->userId); + // Disable encryption proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; $this->view->file_put_contents($this->userId . '/files/' . $file, $this->dataShort); - Encryption\Keymanager::setFileKey($this->view, $file, $this->userId, $key); + Encryption\Keymanager::setFileKey($this->view, $util, $file, $key); $this->assertTrue($this->view->file_exists('/' . $this->userId . '/files_encryption/keyfiles/' . $file . '.key')); diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php index 3c902ea9a3a..f4042f65248 100644 --- a/apps/files_sharing/public.php +++ b/apps/files_sharing/public.php @@ -152,9 +152,6 @@ if (isset($path)) { $tmpl->assign('sharingToken', $token); $tmpl->assign('disableSharing', true); $allowPublicUploadEnabled = (bool) ($linkItem['permissions'] & OCP\PERMISSION_CREATE); - if (\OCP\App::isEnabled('files_encryption')) { - $allowPublicUploadEnabled = false; - } if (OC_Appconfig::getValue('core', 'shareapi_allow_public_upload', 'yes') === 'no') { $allowPublicUploadEnabled = false; } diff --git a/apps/user_ldap/ajax/wizard.php b/apps/user_ldap/ajax/wizard.php index e580c097867..ad75a384369 100644 --- a/apps/user_ldap/ajax/wizard.php +++ b/apps/user_ldap/ajax/wizard.php @@ -53,8 +53,11 @@ switch($action) { case 'determineGroupsForGroups': case 'determineAttributes': case 'getUserListFilter': + case 'getLoginFilterMode': case 'getUserLoginFilter': + case 'getUserFilterMode': case 'getGroupFilter': + case 'getGroupFilterMode': case 'countUsers': case 'countGroups': try { @@ -87,6 +90,9 @@ switch($action) { exit; } $configuration->saveConfiguration(); + //clear the cache on save + $connection = new \OCA\user_ldap\lib\Connection($ldapWrapper, $prefix); + $connection->clearCache(); OCP\JSON::success(); break; default: diff --git a/apps/user_ldap/group_proxy.php b/apps/user_ldap/group_proxy.php index acc563c9532..4404bd7fe3a 100644 --- a/apps/user_ldap/group_proxy.php +++ b/apps/user_ldap/group_proxy.php @@ -67,16 +67,17 @@ class Group_Proxy extends lib\Proxy implements \OCP\GroupInterface { * @param $gid string, the gid connected to the request * @param $method string, the method of the group backend that shall be called * @param $parameters an array of parameters to be passed + * @param $passOnWhen the result matches this variable * @return mixed, the result of the method or false */ - protected function callOnLastSeenOn($gid, $method, $parameters) { + protected function callOnLastSeenOn($gid, $method, $parameters, $passOnWhen) { $cacheKey = $this->getGroupCacheKey($gid);; $prefix = $this->getFromCache($cacheKey); //in case the uid has been found in the past, try this stored connection first if(!is_null($prefix)) { if(isset($this->backends[$prefix])) { $result = call_user_func_array(array($this->backends[$prefix], $method), $parameters); - if(!$result) { + if($result === $passOnWhen) { //not found here, reset cache to null if group vanished //because sometimes methods return false with a reason $groupExists = call_user_func_array( diff --git a/apps/user_ldap/js/settings.js b/apps/user_ldap/js/settings.js index 2fa0ed066c4..5b5f2030635 100644 --- a/apps/user_ldap/js/settings.js +++ b/apps/user_ldap/js/settings.js @@ -120,7 +120,7 @@ var LdapConfiguration = { clearMappings: function(mappingSubject) { $.post( OC.filePath('user_ldap','ajax','clearMappings.php'), - 'ldap_clear_mapping='+mappingSubject, + 'ldap_clear_mapping='+encodeURIComponent(mappingSubject), function(result) { if(result.status == 'success') { OC.dialogs.info( @@ -143,6 +143,8 @@ var LdapWizard = { saveBlacklist: {}, userFilterGroupSelectState: 'enable', spinner: '<img class="wizSpinner" src="'+ OC.imagePath('core', 'loading.gif') +'">', + filterModeAssisted: 0, + filterModeRaw: 1, ajax: function(param, fnOnSuccess, fnOnError) { $.post( @@ -160,10 +162,7 @@ var LdapWizard = { applyChanges: function (result) { for (id in result.changes) { - if(!$.isArray(result.changes[id])) { - //no need to blacklist multiselect - LdapWizard.saveBlacklist[id] = true; - } + LdapWizard.blacklistAdd(id); if(id.indexOf('count') > 0) { $('#'+id).text(result.changes[id]); } else { @@ -195,6 +194,25 @@ var LdapWizard = { } }, + + blacklistAdd: function(id) { + obj = $('#'+id); + if(!(obj[0].hasOwnProperty('multiple') && obj[0]['multiple'] == true)) { + //no need to blacklist multiselect + LdapWizard.saveBlacklist[id] = true; + return true; + } + return false; + }, + + blacklistRemove: function(id) { + if(LdapWizard.saveBlacklist.hasOwnProperty(id)) { + delete LdapWizard.saveBlacklist[id]; + return true; + } + return false; + }, + checkBaseDN: function() { host = $('#ldap_host').val(); port = $('#ldap_port').val(); @@ -204,7 +222,8 @@ var LdapWizard = { //FIXME: determine base dn with anonymous access if(host && port && user && pass) { param = 'action=guessBaseDN'+ - '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + '&ldap_serverconfig_chooser='+ + encodeURIComponent($('#ldap_serverconfig_chooser').val()); LdapWizard.showSpinner('#ldap_base'); $('#ldap_base').prop('disabled', 'disabled'); @@ -232,7 +251,8 @@ var LdapWizard = { if(host && !port) { param = 'action=guessPortAndTLS'+ - '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + '&ldap_serverconfig_chooser='+ + encodeURIComponent($('#ldap_serverconfig_chooser').val()); LdapWizard.showSpinner('#ldap_port'); $('#ldap_port').prop('disabled', 'disabled'); @@ -256,6 +276,12 @@ var LdapWizard = { }, composeFilter: function(type) { + subject = type.charAt(0).toUpperCase() + type.substr(1); + if(!$('#raw'+subject+'FilterContainer').hasClass('invisible')) { + //Raw filter editing, i.e. user defined filter, don't compose + return; + } + if(type == 'user') { action = 'getUserListFilter'; } else if(type == 'login') { @@ -265,7 +291,8 @@ var LdapWizard = { } param = 'action='+action+ - '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + '&ldap_serverconfig_chooser='+ + encodeURIComponent($('#ldap_serverconfig_chooser').val()); LdapWizard.ajax(param, function(result) { @@ -323,7 +350,8 @@ var LdapWizard = { _countThings: function(method) { param = 'action='+method+ - '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + '&ldap_serverconfig_chooser='+ + encodeURIComponent($('#ldap_serverconfig_chooser').val()); LdapWizard.ajax(param, function(result) { @@ -345,7 +373,8 @@ var LdapWizard = { detectGroupMemberAssoc: function() { param = 'action=determineGroupMemberAssoc'+ - '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + '&ldap_serverconfig_chooser='+ + encodeURIComponent($('#ldap_serverconfig_chooser').val()); LdapWizard.ajax(param, function(result) { @@ -359,7 +388,8 @@ var LdapWizard = { findAttributes: function() { param = 'action=determineAttributes'+ - '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + '&ldap_serverconfig_chooser='+ + encodeURIComponent($('#ldap_serverconfig_chooser').val()); LdapWizard.showSpinner('#ldap_loginfilter_attributes'); LdapWizard.ajax(param, @@ -374,7 +404,9 @@ var LdapWizard = { LdapWizard.hideSpinner('#ldap_loginfilter_attributes'); LdapWizard.applyChanges(result); $('#ldap_loginfilter_attributes').multiselect('refresh'); - $('#ldap_loginfilter_attributes').multiselect('enable'); + if($('#rawLoginFilterContainer').hasClass('invisible')) { + $('#ldap_loginfilter_attributes').multiselect('enable'); + } }, function (result) { //deactivate if no attributes found @@ -390,8 +422,9 @@ var LdapWizard = { if(type != 'Users' && type != 'Groups') { return false; } - param = 'action=determineGroupsFor'+type+ - '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + param = 'action=determineGroupsFor'+encodeURIComponent(type)+ + '&ldap_serverconfig_chooser='+ + encodeURIComponent($('#ldap_serverconfig_chooser').val()); LdapWizard.showSpinner('#'+multisel); LdapWizard.ajax(param, @@ -405,7 +438,11 @@ var LdapWizard = { LdapWizard.hideSpinner('#'+multisel); LdapWizard.applyChanges(result); $('#'+multisel).multiselect('refresh'); - $('#'+multisel).multiselect('enable'); + part = type.slice(0, -1); + if($('#raw' + part + 'FilterContainer').hasClass('invisible')) { + //enable only when raw filter editing is not turned on + $('#'+multisel).multiselect('enable'); + } }, function (result) { LdapWizard.hideSpinner('#'+multisel); @@ -418,8 +455,9 @@ var LdapWizard = { if(type != 'User' && type != 'Group') { return false; } - param = 'action=determine'+type+'ObjectClasses'+ - '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); + param = 'action=determine'+encodeURIComponent(type)+'ObjectClasses'+ + '&ldap_serverconfig_chooser='+ + encodeURIComponent($('#ldap_serverconfig_chooser').val()); LdapWizard.showSpinner('#'+multisel); LdapWizard.ajax(param, @@ -485,15 +523,15 @@ var LdapWizard = { }, initGroupFilter: function() { + LdapWizard.regardFilterMode('Group'); LdapWizard.findObjectClasses('ldap_groupfilter_objectclass', 'Group'); LdapWizard.findAvailableGroups('ldap_groupfilter_groups', 'Groups'); - LdapWizard.composeFilter('group'); LdapWizard.countGroups(); }, initLoginFilter: function() { + LdapWizard.regardFilterMode('Login'); LdapWizard.findAttributes(); - LdapWizard.composeFilter('login'); }, initMultiSelect: function(object, id, caption) { @@ -509,9 +547,9 @@ var LdapWizard = { }, initUserFilter: function() { + LdapWizard.regardFilterMode('User'); LdapWizard.findObjectClasses('ldap_userfilter_objectclass', 'User'); LdapWizard.findAvailableGroups('ldap_userfilter_groups', 'Users'); - LdapWizard.composeFilter('user'); LdapWizard.countUsers(); }, @@ -566,9 +604,36 @@ var LdapWizard = { } }, + regardFilterMode: function(subject) { + param = 'action=get'+encodeURIComponent(subject)+'FilterMode'+ + '&ldap_serverconfig_chooser='+ + encodeURIComponent($('#ldap_serverconfig_chooser').val()); + + LdapWizard.ajax(param, + function(result) { + property = 'ldap' + subject + 'FilterMode'; + mode = result.changes[property]; + if(mode == LdapWizard.filterModeRaw + && $('#raw'+subject+'FilterContainer').hasClass('invisible')) { + LdapWizard['toggleRaw'+subject+'Filter'](); + } else if(mode == LdapWizard.filterModeAssisted + && !$('#raw'+subject+'FilterContainer').hasClass('invisible')) { + LdapWizard['toggleRaw'+subject+'Filter'](); + } else { + c = $('#raw'+subject+'FilterContainer').hasClass('invisible'); + } + }, + function (result) { + //on error case get back to default i.e. Assisted + if(!$('#raw'+subject+'FilterContainer').hasClass('invisible')) { + LdapWizard['toggleRaw'+subject+'Filter'](); + } + } + ); + }, + save: function(inputObj) { - if(LdapWizard.saveBlacklist.hasOwnProperty(inputObj.id)) { - delete LdapWizard.saveBlacklist[inputObj.id]; + if(LdapWizard.blacklistRemove(inputObj.id)) { return; } if($(inputObj).is('input[type=checkbox]') @@ -601,8 +666,8 @@ var LdapWizard = { }, _save: function(object, value) { - param = 'cfgkey='+object.id+ - '&cfgval='+value+ + param = 'cfgkey='+encodeURIComponent(object.id)+ + '&cfgval='+encodeURIComponent(value)+ '&action=save'+ '&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val(); @@ -632,7 +697,9 @@ var LdapWizard = { } }, - toggleRawFilter: function(container, moc, mg, stateVar) { + toggleRawFilter: function(container, moc, mg, stateVar, modeKey) { + //moc = multiselect objectclass + //mg = mutliselect groups if($(container).hasClass('invisible')) { $(container).removeClass('invisible'); $(moc).multiselect('disable'); @@ -642,26 +709,62 @@ var LdapWizard = { LdapWizard[stateVar] = 'enable'; } $(mg).multiselect('disable'); + LdapWizard._save({ id: modeKey }, LdapWizard.filterModeRaw); } else { $(container).addClass('invisible'); $(mg).multiselect(LdapWizard[stateVar]); $(moc).multiselect('enable'); + LdapWizard._save({ id: modeKey }, LdapWizard.filterModeAssisted); + if(moc.indexOf('user') >= 0) { + LdapWizard.blacklistRemove('ldap_userlist_filter'); + LdapWizard.composeFilter('user'); + } else { + LdapWizard.blacklistRemove('ldap_group_filter'); + LdapWizard.composeFilter('group'); + } } }, toggleRawGroupFilter: function() { + LdapWizard.blacklistRemove('ldap_group_filter'); LdapWizard.toggleRawFilter('#rawGroupFilterContainer', '#ldap_groupfilter_objectclass', '#ldap_groupfilter_groups', - 'groupFilterGroupSelectState' + 'groupFilterGroupSelectState', + 'ldapGroupFilterMode' ); }, + toggleRawLoginFilter: function() { + LdapWizard.blacklistRemove('ldap_login_filter'); + container = '#rawLoginFilterContainer'; + if($(container).hasClass('invisible')) { + $(container).removeClass('invisible'); + action = 'disable'; + property = 'disabled'; + mode = LdapWizard.filterModeRaw; + } else { + $(container).addClass('invisible'); + action = 'enable'; + property = false; + mode = LdapWizard.filterModeAssisted; + } + $('#ldap_loginfilter_attributes').multiselect(action); + $('#ldap_loginfilter_email').prop('disabled', property); + $('#ldap_loginfilter_username').prop('disabled', property); + LdapWizard._save({ id: 'ldapLoginFilterMode' }, mode); + if(action == 'enable') { + LdapWizard.composeFilter('login'); + } + }, + toggleRawUserFilter: function() { + LdapWizard.blacklistRemove('ldap_userlist_filter'); LdapWizard.toggleRawFilter('#rawUserFilterContainer', '#ldap_userfilter_objectclass', '#ldap_userfilter_groups', - 'userFilterGroupSelectState' + 'userFilterGroupSelectState', + 'ldapUserFilterMode' ); }, @@ -722,6 +825,7 @@ $(document).ready(function() { $('.lwautosave').change(function() { LdapWizard.save(this); }); $('#toggleRawUserFilter').click(LdapWizard.toggleRawUserFilter); $('#toggleRawGroupFilter').click(LdapWizard.toggleRawGroupFilter); + $('#toggleRawLoginFilter').click(LdapWizard.toggleRawLoginFilter); LdapConfiguration.refreshConfig(); $('.ldap_action_continue').click(function(event) { event.preventDefault(); diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index a07bd3fa11f..ecc74b6cf54 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -199,7 +199,9 @@ class Access extends LDAPUtility { */ public function username2dn($name) { $dn = $this->ocname2dn($name, true); - if($dn) { + //Check whether the DN belongs to the Base, to avoid issues on multi- + //server setups + if($dn && $this->isDNPartOfBase($dn, $this->connection->ldapBaseUsers)) { return $dn; } diff --git a/apps/user_ldap/lib/configuration.php b/apps/user_ldap/lib/configuration.php index e14ed824a74..c396d5b4bf8 100644 --- a/apps/user_ldap/lib/configuration.php +++ b/apps/user_ldap/lib/configuration.php @@ -47,12 +47,15 @@ class Configuration { 'ldapUserFilterObjectclass' => null, 'ldapUserFilterGroups' => null, 'ldapUserFilter' => null, + 'ldapUserFilterMode' => null, 'ldapGroupFilter' => null, + 'ldapGroupFilterMode' => null, 'ldapGroupFilterObjectclass' => null, 'ldapGroupFilterGroups' => null, 'ldapGroupDisplayName' => null, 'ldapGroupMemberAssocAttr' => null, 'ldapLoginFilter' => null, + 'ldapLoginFilterMode' => null, 'ldapLoginFilterEmail' => null, 'ldapLoginFilterUsername' => null, 'ldapLoginFilterAttributes' => null, @@ -72,6 +75,7 @@ class Configuration { 'ldapExpertUsernameAttr' => null, 'ldapExpertUUIDUserAttr' => null, 'ldapExpertUUIDGroupAttr' => null, + 'lastJpegPhotoLookup' => null, ); public function __construct($configPrefix, $autoread = true) { @@ -301,13 +305,16 @@ class Configuration { 'ldap_base_users' => '', 'ldap_base_groups' => '', 'ldap_userlist_filter' => '', + 'ldap_user_filter_mode' => 0, 'ldap_userfilter_objectclass' => '', 'ldap_userfilter_groups' => '', 'ldap_login_filter' => 'uid=%uid', + 'ldap_login_filter_mode' => 0, 'ldap_loginfilter_email' => 0, 'ldap_loginfilter_username' => 1, 'ldap_loginfilter_attributes' => '', 'ldap_group_filter' => '', + 'ldap_group_filter_mode' => 0, 'ldap_groupfilter_objectclass' => '', 'ldap_groupfilter_groups' => '', 'ldap_display_name' => 'displayName', @@ -330,6 +337,7 @@ class Configuration { 'ldap_expert_uuid_user_attr' => '', 'ldap_expert_uuid_group_attr' => '', 'has_memberof_filter_support' => 0, + 'last_jpegPhoto_lookup' => 0, ); } @@ -352,11 +360,14 @@ class Configuration { 'ldap_userfilter_objectclass' => 'ldapUserFilterObjectclass', 'ldap_userfilter_groups' => 'ldapUserFilterGroups', 'ldap_userlist_filter' => 'ldapUserFilter', + 'ldap_user_filter_mode' => 'ldapUserFilterMode', 'ldap_login_filter' => 'ldapLoginFilter', + 'ldap_login_filter_mode' => 'ldapLoginFilterMode', 'ldap_loginfilter_email' => 'ldapLoginFilterEmail', 'ldap_loginfilter_username' => 'ldapLoginFilterUsername', 'ldap_loginfilter_attributes' => 'ldapLoginFilterAttributes', 'ldap_group_filter' => 'ldapGroupFilter', + 'ldap_group_filter_mode' => 'ldapGroupFilterMode', 'ldap_groupfilter_objectclass' => 'ldapGroupFilterObjectclass', 'ldap_groupfilter_groups' => 'ldapGroupFilterGroups', 'ldap_display_name' => 'ldapUserDisplayName', @@ -377,6 +388,7 @@ class Configuration { 'ldap_expert_uuid_user_attr' => 'ldapExpertUUIDUserAttr', 'ldap_expert_uuid_group_attr' => 'ldapExpertUUIDGroupAttr', 'has_memberof_filter_support' => 'hasMemberOfFilterSupport', + 'last_jpegPhoto_lookup' => 'lastJpegPhotoLookup', ); return $array; } diff --git a/apps/user_ldap/lib/proxy.php b/apps/user_ldap/lib/proxy.php index c74b357bdd2..30e1875901c 100644 --- a/apps/user_ldap/lib/proxy.php +++ b/apps/user_ldap/lib/proxy.php @@ -54,7 +54,7 @@ abstract class Proxy { return 'group-'.$gid.'-lastSeenOn'; } - abstract protected function callOnLastSeenOn($id, $method, $parameters); + abstract protected function callOnLastSeenOn($id, $method, $parameters, $passOnWhen); abstract protected function walkBackends($id, $method, $parameters); /** @@ -64,8 +64,9 @@ abstract class Proxy { * @param $parameters an array of parameters to be passed * @return mixed, the result of the specified method */ - protected function handleRequest($id, $method, $parameters) { - if(!$result = $this->callOnLastSeenOn($id, $method, $parameters)) { + protected function handleRequest($id, $method, $parameters, $passOnWhen = false) { + $result = $this->callOnLastSeenOn($id, $method, $parameters, $passOnWhen); + if($result === $passOnWhen) { $result = $this->walkBackends($id, $method, $parameters); } return $result; diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php index 9286af46bea..84b397cf5e8 100644 --- a/apps/user_ldap/lib/wizard.php +++ b/apps/user_ldap/lib/wizard.php @@ -38,6 +38,9 @@ class Wizard extends LDAPUtility { const LFILTER_USER_LIST = 3; const LFILTER_GROUP_LIST = 4; + const LFILTER_MODE_ASSISTED = 2; + const LFILTER_MODE_RAW = 1; + const LDAP_NW_TIMEOUT = 4; /** @@ -148,6 +151,42 @@ class Wizard extends LDAPUtility { } /** + * @brief return the state of the Group Filter Mode + */ + public function getGroupFilterMode() { + $this->getFilterMode('ldapGroupFilterMode'); + return $this->result; + } + + /** + * @brief return the state of the Login Filter Mode + */ + public function getLoginFilterMode() { + $this->getFilterMode('ldapLoginFilterMode'); + return $this->result; + } + + /** + * @brief return the state of the User Filter Mode + */ + public function getUserFilterMode() { + $this->getFilterMode('ldapUserFilterMode'); + return $this->result; + } + + /** + * @brief return the state of the mode of the specified filter + * @param $confkey string, contains the access key of the Configuration + */ + private function getFilterMode($confkey) { + $mode = $this->configuration->$confkey; + if(is_null($mode)) { + $mode = $this->LFILTER_MODE_ASSISTED; + } + $this->result->addChange($confkey, $mode); + } + + /** * @brief detects the available LDAP attributes * @returns the instance's WizardResult instance */ diff --git a/apps/user_ldap/templates/part.wizard-loginfilter.php b/apps/user_ldap/templates/part.wizard-loginfilter.php index d4a36eb0cb7..dc5d61e9f77 100644 --- a/apps/user_ldap/templates/part.wizard-loginfilter.php +++ b/apps/user_ldap/templates/part.wizard-loginfilter.php @@ -29,6 +29,16 @@ </select> </p> <p> + <label><a id='toggleRawLoginFilter'>↓ <?php p($l->t('Edit raw filter instead'));?></a></label> + </p> + <p id="rawLoginFilterContainer" class="invisible"> + <input type="text" id="ldap_login_filter" name="ldap_login_filter" + class="lwautosave" + placeholder="<?php p($l->t('Raw LDAP filter'));?>" + title="<?php p($l->t('Defines the filter to apply, when login is attempted. %%uid replaces the username in the login action. Example: "uid=%%uid"'));?>" + /> + </p> + <p> <div class="ldapWizardInfo invisible"> </div> </p> diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php index feb5ac6385d..3ccc7a860f5 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -20,9 +20,6 @@ <h3><?php p($l->t('Connection Settings'));?></h3> <div> <p><label for="ldap_configuration_active"><?php p($l->t('Configuration Active'));?></label><input type="checkbox" id="ldap_configuration_active" name="ldap_configuration_active" value="1" data-default="<?php p($_['ldap_configuration_active_default']); ?>" title="<?php p($l->t('When unchecked, this configuration will be skipped.'));?>" /></p> - <p><label for="ldap_login_filter"><?php p($l->t('User Login Filter'));?></label><input type="text" id="ldap_login_filter" name="ldap_login_filter" - data-default="<?php p($_['ldap_login_filter_default']); ?>" - title="<?php p($l->t('Defines the filter to apply, when login is attempted. %%uid replaces the username in the login action. Example: "uid=%%uid"'));?>" /></p> <p><label for="ldap_backup_host"><?php p($l->t('Backup (Replica) Host'));?></label><input type="text" id="ldap_backup_host" name="ldap_backup_host" data-default="<?php p($_['ldap_backup_host_default']); ?>" title="<?php p($l->t('Give an optional backup host. It must be a replica of the main LDAP/AD server.'));?>"></p> <p><label for="ldap_backup_port"><?php p($l->t('Backup (Replica) Port'));?></label><input type="number" id="ldap_backup_port" name="ldap_backup_port" data-default="<?php p($_['ldap_backup_port_default']); ?>" /></p> <p><label for="ldap_override_main_server"><?php p($l->t('Disable Main Server'));?></label><input type="checkbox" id="ldap_override_main_server" name="ldap_override_main_server" value="1" data-default="<?php p($_['ldap_override_main_server_default']); ?>" title="<?php p($l->t('Only connect to the replica server.'));?>" /></p> diff --git a/apps/user_ldap/user_ldap.php b/apps/user_ldap/user_ldap.php index 6f52bbdf233..527a5c10b85 100644 --- a/apps/user_ldap/user_ldap.php +++ b/apps/user_ldap/user_ldap.php @@ -70,6 +70,74 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface { } /** + * @brief reads jpegPhoto and set is as avatar if available + * @param $uid string ownCloud user name + * @param $dn string the user's LDAP DN + * @return void + */ + private function updateAvatar($uid, $dn) { + $hasLoggedIn = \OCP\Config::getUserValue($uid, 'user_ldap', + 'firstLoginAccomplished', 0); + $lastChecked = \OCP\Config::getUserValue($uid, 'user_ldap', + 'lastJpegPhotoLookup', 0); + if(($hasLoggedIn !== '1') || (time() - intval($lastChecked)) < 86400 ) { + //update only once a day + return; + } + + $jpegPhoto = $this->access->readAttribute($dn, 'jpegPhoto'); + \OCP\Config::setUserValue($uid, 'user_ldap', 'lastJpegPhotoLookup', time()); + if(!$jpegPhoto || !is_array($jpegPhoto) || !isset($jpegPhoto[0])) { + //not set, nothing left to do; + return; + } + + $image = new \OCP\Image(); + $image->loadFromBase64(base64_encode($jpegPhoto[0])); + + if(!$image->valid()) { + \OCP\Util::writeLog('user_ldap', 'jpegPhoto data invalid for '.$dn, + \OCP\Util::ERROR); + return; + } + //make sure it is a square and not bigger than 128x128 + $size = min(array($image->width(), $image->height(), 128)); + if(!$image->centerCrop($size)) { + \OCP\Util::writeLog('user_ldap', + 'croping image for avatar failed for '.$dn, + \OCP\Util::ERROR); + return; + } + + if(!\OC\Files\Filesystem::$loaded) { + \OC_Util::setupFS($uid); + } + + $avatarManager = \OC::$server->getAvatarManager(); + $avatar = $avatarManager->getAvatar($uid); + $avatar->set($image); + } + + /** + * @brief checks whether the user is allowed to change his avatar in ownCloud + * @param $uid string the ownCloud user name + * @return boolean either the user can or cannot + */ + public function canChangeAvatar($uid) { + $dn = $this->access->username2dn($uid); + if(!$dn) { + return false; + } + $jpegPhoto = $this->access->readAttribute($dn, 'jpegPhoto'); + if(!$jpegPhoto || !is_array($jpegPhoto) || !isset($jpegPhoto[0])) { + //The user is allowed to change his avatar in ownCloud only if no + //avatar is provided by LDAP + return true; + } + return false; + } + + /** * @brief Check if the password is correct * @param $uid The username * @param $password The password @@ -100,6 +168,10 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface { return false; } + \OCP\Config::setUserValue($ocname, 'user_ldap', + 'firstLoginAccomplished', 1); + + $this->updateAvatar($ocname, $dn); //give back the display name return $ocname; } @@ -173,6 +245,7 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface { $this->access->connection->writeToCache('userExists'.$uid, true); $this->updateQuota($dn); + $this->updateAvatar($uid, $dn); return true; } @@ -289,7 +362,8 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface { public function implementsActions($actions) { return (bool)((OC_USER_BACKEND_CHECK_PASSWORD | OC_USER_BACKEND_GET_HOME - | OC_USER_BACKEND_GET_DISPLAYNAME) + | OC_USER_BACKEND_GET_DISPLAYNAME + | OC_USER_BACKEND_PROVIDE_AVATAR) & $actions); } diff --git a/apps/user_ldap/user_proxy.php b/apps/user_ldap/user_proxy.php index 092fdbf7c78..b073b143e74 100644 --- a/apps/user_ldap/user_proxy.php +++ b/apps/user_ldap/user_proxy.php @@ -54,6 +54,7 @@ class User_Proxy extends lib\Proxy implements \OCP\UserInterface { protected function walkBackends($uid, $method, $parameters) { $cacheKey = $this->getUserCacheKey($uid); foreach($this->backends as $configPrefix => $backend) { +// print("walkBackend '$configPrefix'<br/>"); if($result = call_user_func_array(array($backend, $method), $parameters)) { $this->writeToCache($cacheKey, $configPrefix); return $result; @@ -67,16 +68,17 @@ class User_Proxy extends lib\Proxy implements \OCP\UserInterface { * @param $uid string, the uid connected to the request * @param $method string, the method of the user backend that shall be called * @param $parameters an array of parameters to be passed + * @param $passOnWhen the result matches this variable * @return mixed, the result of the method or false */ - protected function callOnLastSeenOn($uid, $method, $parameters) { + protected function callOnLastSeenOn($uid, $method, $parameters, $passOnWhen) { $cacheKey = $this->getUserCacheKey($uid); $prefix = $this->getFromCache($cacheKey); //in case the uid has been found in the past, try this stored connection first if(!is_null($prefix)) { if(isset($this->backends[$prefix])) { $result = call_user_func_array(array($this->backends[$prefix], $method), $parameters); - if(!$result) { + if($result === $passOnWhen) { //not found here, reset cache to null if user vanished //because sometimes methods return false with a reason $userExists = call_user_func_array( @@ -164,6 +166,15 @@ class User_Proxy extends lib\Proxy implements \OCP\UserInterface { } /** + * @brief checks whether the user is allowed to change his avatar in ownCloud + * @param $uid string the ownCloud user name + * @return boolean either the user can or cannot + */ + public function canChangeAvatar($uid) { + return $this->handleRequest($uid, 'canChangeAvatar', array($uid), true); + } + + /** * @brief Get a list of all display names * @returns array with all displayNames (value) and the corresponding uids (key) * diff --git a/db_structure.xml b/db_structure.xml index 377363b7eea..db817faecee 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -9,6 +9,11 @@ <table> + <!-- + Namespaced Key-Value Store for Application Configuration. + - Keys are namespaced per appid. + - E.g. (core, global_cache_gc_lastrun) -> 1385463286 + --> <name>*dbprefix*appconfig</name> <declaration> @@ -62,6 +67,13 @@ <table> + <!-- + Bidirectional Map for Storage Names and Storage Ids. + - Assigns each storage name a unique storage id integer. + - Long storage names are hashed. + - E.g. local::/tmp/ <-> 2 + - E.g. b5db994aa8c6625100e418406c798269 <-> 27 + --> <name>*dbprefix*storages</name> <declaration> @@ -159,6 +171,12 @@ <table> + <!-- + Bidirectional Map for Mimetypes and Mimetype Id + - Assigns each mimetype (and supertype) a unique mimetype id integer. + - E.g. application <-> 5 + - E.g. application/pdf <-> 6 + --> <name>*dbprefix*mimetypes</name> <declaration> @@ -195,6 +213,16 @@ <table> + <!-- + Main file table containing one row for each directory and file. + - Assigns a unique integer fileid to each file (and directory) + - Assigns an etag to each file (and directory) + - Caches various file/dir properties such as: + - path (filename, e.g. files/combinatoricslib-2.0_doc.zip) + - path_hash = md5(path) + - name (basename, e.g. combinatoricslib-2.0_doc.zip) + - size (for directories this is the sum of all contained file sizes) + --> <name>*dbprefix*filecache</name> <declaration> @@ -208,6 +236,7 @@ <length>4</length> </field> + <!-- Foreign Key storages::numeric_id --> <field> <name>storage</name> <type>integer</type> @@ -232,6 +261,7 @@ <length>32</length> </field> + <!-- Foreign Key filecache::fileid --> <field> <name>parent</name> <type>integer</type> @@ -248,6 +278,7 @@ <length>250</length> </field> + <!-- Foreign Key mimetypes::id --> <field> <name>mimetype</name> <type>integer</type> @@ -256,6 +287,7 @@ <length>4</length> </field> + <!-- Foreign Key mimetypes::id --> <field> <name>mimepart</name> <type>integer</type> @@ -367,10 +399,15 @@ <table> + <!-- + Maps (fileid, user) to an integer which is a permission bitfield. + - E.g. (4, admin) -> 27 + --> <name>*dbprefix*permissions</name> <declaration> + <!-- Foreign Key filecache::fileid --> <field> <name>fileid</name> <type>integer</type> @@ -379,6 +416,7 @@ <length>4</length> </field> + <!-- Foreign Key users::uid --> <field> <name>user</name> <type>text</type> @@ -413,10 +451,16 @@ <table> + <!-- + Stores which groups have which users as members in an n:m relationship. + - Maps group id (gid) to a set of users (uid) + - Maps user id (uid) to a set of groups (gid) (but without index) + --> <name>*dbprefix*group_user</name> <declaration> + <!-- Foreign Key groups::gid --> <field> <name>gid</name> <type>text</type> @@ -425,6 +469,7 @@ <length>64</length> </field> + <!-- Foreign Key users::uid --> <field> <name>uid</name> <type>text</type> @@ -453,10 +498,19 @@ <table> + <!-- + Stores which groups have which users as admins in an n:m relationship. + - Maps group id (gid) to a set of users (uid) + - Maps user id (uid) to a set of groups (gid) + + NOTE: This could (very likely) be reduced to a single bit in group_user + instead of repeating varchars gid and uid here + --> <name>*dbprefix*group_admin</name> <declaration> + <!-- Foreign Key groups::gid --> <field> <name>gid</name> <type>text</type> @@ -465,6 +519,7 @@ <length>64</length> </field> + <!-- Foreign Key users::uid --> <field> <name>uid</name> <type>text</type> @@ -500,6 +555,9 @@ <table> + <!-- + A simple list of groups. + --> <name>*dbprefix*groups</name> <declaration> @@ -527,6 +585,9 @@ <table> + <!-- + Locks held by WebDAV clients via OC_Connector_Sabre_Locks. + --> <name>*dbprefix*locks</name> <declaration> @@ -541,6 +602,7 @@ <length>4</length> </field> + <!-- Foreign Key users::uid --> <field> <name>userid</name> <type>text</type> @@ -606,10 +668,16 @@ <table> + <!-- + Namespaced Key-Value Store for User Preferences + - Keys are namespaced per userid and appid. + - E.g. (admin, files, cache_version) -> 5 + --> <name>*dbprefix*preferences</name> <declaration> + <!-- Foreign Key users::uid --> <field> <name>userid</name> <type>text</type> @@ -664,6 +732,9 @@ <table> + <!-- + WebDAV properties. + --> <name>*dbprefix*properties</name> <declaration> @@ -677,6 +748,7 @@ <length>4</length> </field> + <!-- Foreign Key users::uid --> <field> <name>userid</name> <type>text</type> @@ -722,6 +794,9 @@ <table> + <!-- + Shares of all types (user-to-user, external-via-link, etc.) + --> <name>*dbprefix*share</name> <declaration> @@ -735,6 +810,7 @@ <length>4</length> </field> + <!-- Constant OCP\Share::SHARE_TYPE_* --> <field> <name>share_type</name> <type>integer</type> @@ -743,6 +819,7 @@ <length>1</length> </field> + <!-- Foreign Key users::uid or NULL --> <field> <name>share_with</name> <type>text</type> @@ -751,6 +828,7 @@ <length>255</length> </field> + <!-- Foreign Key users::uid --> <field> <name>uid_owner</name> <type>text</type> @@ -759,6 +837,7 @@ <length>255</length> </field> + <!-- Foreign Key share::id or NULL --> <field> <name>parent</name> <type>integer</type> @@ -766,6 +845,7 @@ <length>4</length> </field> + <!-- E.g. file or folder --> <field> <name>item_type</name> <type>text</type> @@ -774,6 +854,7 @@ <length>64</length> </field> + <!-- Foreign Key filecache::fileid --> <field> <name>item_source</name> <type>text</type> @@ -790,6 +871,7 @@ <length>255</length> </field> + <!-- Foreign Key filecache::fileid --> <field> <name>file_source</name> <type>integer</type> @@ -805,6 +887,7 @@ <length>512</length> </field> + <!-- Permission bitfield --> <field> <name>permissions</name> <type>integer</type> @@ -813,6 +896,7 @@ <length>1</length> </field> + <!-- Time of share creation --> <field> <name>stime</name> <type>integer</type> @@ -821,6 +905,7 @@ <length>8</length> </field> + <!-- Whether the receiver accepted the share, if share_with is set. --> <field> <name>accepted</name> <type>integer</type> @@ -829,6 +914,7 @@ <length>1</length> </field> + <!-- Time of share expiration --> <field> <name>expiration</name> <type>timestamp</type> @@ -883,6 +969,10 @@ <table> + <!-- + Scheduled background jobs. + See OC\BackgroundJob\JobList. + --> <name>*dbprefix*jobs</name> <declaration> @@ -934,6 +1024,9 @@ <table> + <!-- + List of usernames, their display name and login password. + --> <name>*dbprefix*users</name> <declaration> @@ -976,6 +1069,9 @@ <table> + <!-- + List of tags (category) + a unique tag id (id) per user (uid) and type. + --> <name>*dbprefix*vcategory</name> <declaration> @@ -990,6 +1086,7 @@ <length>4</length> </field> + <!-- Foreign Key users::uid --> <field> <name>uid</name> <type>text</type> @@ -1043,6 +1140,9 @@ <table> + <!-- + Object-Tag associations per tag type. + --> <name>*dbprefix*vcategory_to_object</name> <declaration> @@ -1056,6 +1156,7 @@ <length>4</length> </field> + <!-- Foreign Key vcategory::id --> <field> <name>categoryid</name> <type>integer</type> @@ -1109,6 +1210,11 @@ <table> + <!-- + Namespaced Key-Value Store for arbitrary data. + - Keys are namespaced per userid and appid. + - E.g. (admin, files, foo) -> bar + --> <name>*dbprefix*privatedata</name> <declaration> @@ -1123,6 +1229,7 @@ <autoincrement>1</autoincrement> </field> + <!-- Foreign Key users::uid --> <field> <name>user</name> <type>text</type> diff --git a/lib/private/appframework/app.php b/lib/private/appframework/app.php index 6d3effbf1fa..b835188661a 100644 --- a/lib/private/appframework/app.php +++ b/lib/private/appframework/app.php @@ -43,8 +43,12 @@ class App { * stored in the DI container * @param string $methodName the method that you want to call * @param DIContainer $container an instance of a pimple container. + * @param array $urlParams list of URL parameters (optional) */ - public static function main($controllerName, $methodName, IAppContainer $container) { + public static function main($controllerName, $methodName, DIContainer $container, array $urlParams = null) { + if (!is_null($urlParams)) { + $container['urlParams'] = $urlParams; + } $controller = $container[$controllerName]; // initialize the dispatcher and run all the middleware before the controller diff --git a/lib/private/appframework/routing/routeactionhandler.php b/lib/private/appframework/routing/routeactionhandler.php index 7fb56f14eab..2b9dc38dc43 100644 --- a/lib/private/appframework/routing/routeactionhandler.php +++ b/lib/private/appframework/routing/routeactionhandler.php @@ -37,6 +37,6 @@ class RouteActionHandler { } public function __invoke($params) { - App::main($this->controllerName, $this->actionName, $params, $this->container); + App::main($this->controllerName, $this->actionName, $this->container, $params); } } diff --git a/lib/private/avatar.php b/lib/private/avatar.php index 814a9b22bed..e97f55eecaf 100644 --- a/lib/private/avatar.php +++ b/lib/private/avatar.php @@ -44,15 +44,19 @@ class OC_Avatar implements \OCP\IAvatar { /** * @brief sets the users avatar - * @param $data mixed imagedata or path to set a new avatar + * @param $data mixed OC_Image, imagedata or path to set a new avatar * @throws Exception if the provided file is not a jpg or png image * @throws Exception if the provided image is not valid * @throws \OC\NotSquareException if the image is not square * @return void */ public function set ($data) { - - $img = new OC_Image($data); + if($data instanceOf OC_Image) { + $img = $data; + $data = $img->data(); + } else { + $img = new OC_Image($data); + } $type = substr($img->mimeType(), -3); if ($type === 'peg') { $type = 'jpg'; diff --git a/lib/private/user.php b/lib/private/user.php index 5bd36006750..210e5ed3f02 100644 --- a/lib/private/user.php +++ b/lib/private/user.php @@ -425,6 +425,22 @@ class OC_User { } /** + * @brief Check whether user can change his avatar + * @param string $uid The username + * @return bool + * + * Check whether a specified user can change his avatar + */ + public static function canUserChangeAvatar($uid) { + $user = self::getManager()->get($uid); + if ($user) { + return $user->canChangeAvatar(); + } else { + return false; + } + } + + /** * @brief Check whether user can change his password * @param string $uid The username * @return bool diff --git a/lib/private/user/backend.php b/lib/private/user/backend.php index e9be08e429c..02c93d13bdf 100644 --- a/lib/private/user/backend.php +++ b/lib/private/user/backend.php @@ -31,13 +31,13 @@ define('OC_USER_BACKEND_NOT_IMPLEMENTED', -501); /** * actions that user backends can define */ -define('OC_USER_BACKEND_CREATE_USER', 0x000001); -define('OC_USER_BACKEND_SET_PASSWORD', 0x000010); -define('OC_USER_BACKEND_CHECK_PASSWORD', 0x000100); -define('OC_USER_BACKEND_GET_HOME', 0x001000); -define('OC_USER_BACKEND_GET_DISPLAYNAME', 0x010000); -define('OC_USER_BACKEND_SET_DISPLAYNAME', 0x100000); - +define('OC_USER_BACKEND_CREATE_USER', 0x0000001); +define('OC_USER_BACKEND_SET_PASSWORD', 0x0000010); +define('OC_USER_BACKEND_CHECK_PASSWORD', 0x0000100); +define('OC_USER_BACKEND_GET_HOME', 0x0001000); +define('OC_USER_BACKEND_GET_DISPLAYNAME', 0x0010000); +define('OC_USER_BACKEND_SET_DISPLAYNAME', 0x0100000); +define('OC_USER_BACKEND_PROVIDE_AVATAR', 0x1000000); /** * Abstract base class for user management. Provides methods for querying backend @@ -54,6 +54,7 @@ abstract class OC_User_Backend implements OC_User_Interface { OC_USER_BACKEND_GET_HOME => 'getHome', OC_USER_BACKEND_GET_DISPLAYNAME => 'getDisplayName', OC_USER_BACKEND_SET_DISPLAYNAME => 'setDisplayName', + OC_USER_BACKEND_PROVIDE_AVATAR => 'canChangeAvatar', ); /** diff --git a/lib/private/user/user.php b/lib/private/user/user.php index e5f842944f1..e773473ec41 100644 --- a/lib/private/user/user.php +++ b/lib/private/user/user.php @@ -140,6 +140,18 @@ class User { } /** + * check if the backend allows the user to change his avatar on Personal page + * + * @return bool + */ + public function canChangeAvatar() { + if($this->backend->implementsActions(\OC_USER_BACKEND_PROVIDE_AVATAR)) { + return $this->backend->canChangeAvatar($this->uid); + } + return true; + } + + /** * check if the backend supports changing passwords * * @return bool diff --git a/lib/public/appframework/app.php b/lib/public/appframework/app.php index 0ff6648c5d4..90150245c41 100644 --- a/lib/public/appframework/app.php +++ b/lib/public/appframework/app.php @@ -26,6 +26,7 @@ */ namespace OCP\AppFramework; +use OC\AppFramework\routing\RouteConfig; /** @@ -53,6 +54,28 @@ class App { } /** + * This function is to be called to create single routes and restful routes based on the given $routes array. + * + * Example code in routes.php of tasks app (it will register two restful resources): + * $routes = array( + * 'resources' => array( + * 'lists' => array('url' => '/tasklists'), + * 'tasks' => array('url' => '/tasklists/{listId}/tasks') + * ) + * ); + * + * $a = new TasksApp(); + * $a->registerRoutes($this, $routes); + * + * @param \OC_Router $router + * @param array $routes + */ + public function registerRoutes($router, $routes) { + $routeConfig = new RouteConfig($this->container, $router, $routes); + $routeConfig->register(); + } + + /** * This function is called by the routing component to fire up the frameworks dispatch mechanism. * * Example code in routes.php of the task app: diff --git a/settings/personal.php b/settings/personal.php index 670e18e20ef..44e1048941b 100644 --- a/settings/personal.php +++ b/settings/personal.php @@ -90,6 +90,7 @@ $tmpl->assign('displayNameChangeSupported', OC_User::canUserChangeDisplayName(OC $tmpl->assign('displayName', OC_User::getDisplayName()); $tmpl->assign('enableDecryptAll' , $enableDecryptAll); $tmpl->assign('enableAvatars', \OC_Config::getValue('enable_avatars', true)); +$tmpl->assign('avatarChangeSupported', OC_User::canUserChangeAvatar(OC_User::getUser())); $forms=OC_App::getForms('personal'); $tmpl->assign('forms', array()); diff --git a/settings/templates/admin.php b/settings/templates/admin.php index 253a7ff5f50..523bbd66248 100644 --- a/settings/templates/admin.php +++ b/settings/templates/admin.php @@ -15,6 +15,20 @@ $levelLabels = array( <?php +// is ssl working ? +if (!$_['isConnectedViaHTTPS']) { + ?> +<fieldset class="personalblock"> + <h2><?php p($l->t('Security Warning'));?></h2> + + <span class="securitywarning"> + <?php p($l->t('You are accessing %s via HTTP. We strongly suggest you configure your server to require using HTTPS instead.', $theme->getTitle())); ?> + </span> + +</fieldset> +<?php +} + // is htaccess working ? if (!$_['htaccessworking']) { ?> diff --git a/settings/templates/personal.php b/settings/templates/personal.php index a79eeefa796..3eb864655bb 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -87,11 +87,15 @@ if($_['passwordChangeSupported']) { <div id="displayavatar"> <div class="avatardiv"></div><br> <div class="warning hidden"></div> + <?php if ($_['avatarChangeSupported']): ?> <div class="inlineblock button" id="uploadavatarbutton"><?php p($l->t('Upload new')); ?></div> <input type="file" class="hidden" name="files[]" id="uploadavatar"> <div class="inlineblock button" id="selectavatar"><?php p($l->t('Select new from Files')); ?></div> <div class="inlineblock button" id="removeavatar"><?php p($l->t('Remove image')); ?></div><br> <?php p($l->t('Either png or jpg. Ideally square but you will be able to crop it.')); ?> + <?php else: ?> + <?php p($l->t('Your avatar is provided by your original account.')); ?> + <?php endif; ?> </div> <div id="cropper" class="hidden"> <div class="inlineblock button" id="abortcropperbutton"><?php p($l->t('Abort')); ?></div> diff --git a/tests/lib/user/avataruserdummy.php b/tests/lib/user/avataruserdummy.php new file mode 100644 index 00000000000..738b10492ea --- /dev/null +++ b/tests/lib/user/avataruserdummy.php @@ -0,0 +1,27 @@ +<?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/>. +* +*/ + +class Avatar_User_Dummy extends \OC_User_Dummy { + public function canChangeAvatar($uid) { + return true; + } +}
\ No newline at end of file diff --git a/tests/lib/user/user.php b/tests/lib/user/user.php index de5ccbf38c1..0bbcda013ce 100644 --- a/tests/lib/user/user.php +++ b/tests/lib/user/user.php @@ -87,6 +87,75 @@ class User extends \PHPUnit_Framework_TestCase { $this->assertFalse($user->setPassword('bar','')); } + public function testChangeAvatarSupportedYes() { + /** + * @var \OC_User_Backend | \PHPUnit_Framework_MockObject_MockObject $backend + */ + require_once 'avataruserdummy.php'; + $backend = $this->getMock('Avatar_User_Dummy'); + $backend->expects($this->once()) + ->method('canChangeAvatar') + ->with($this->equalTo('foo')) + ->will($this->returnValue(true)); + + $backend->expects($this->any()) + ->method('implementsActions') + ->will($this->returnCallback(function ($actions) { + if ($actions === \OC_USER_BACKEND_PROVIDE_AVATAR) { + return true; + } else { + return false; + } + })); + + $user = new \OC\User\User('foo', $backend); + $this->assertTrue($user->canChangeAvatar()); + } + + public function testChangeAvatarSupportedNo() { + /** + * @var \OC_User_Backend | \PHPUnit_Framework_MockObject_MockObject $backend + */ + require_once 'avataruserdummy.php'; + $backend = $this->getMock('Avatar_User_Dummy'); + $backend->expects($this->once()) + ->method('canChangeAvatar') + ->with($this->equalTo('foo')) + ->will($this->returnValue(false)); + + $backend->expects($this->any()) + ->method('implementsActions') + ->will($this->returnCallback(function ($actions) { + if ($actions === \OC_USER_BACKEND_PROVIDE_AVATAR) { + return true; + } else { + return false; + } + })); + + $user = new \OC\User\User('foo', $backend); + $this->assertFalse($user->canChangeAvatar()); + } + + public function testChangeAvatarNotSupported() { + /** + * @var \OC_User_Backend | \PHPUnit_Framework_MockObject_MockObject $backend + */ + require_once 'avataruserdummy.php'; + $backend = $this->getMock('Avatar_User_Dummy'); + $backend->expects($this->never()) + ->method('canChangeAvatar'); + + $backend->expects($this->any()) + ->method('implementsActions') + ->will($this->returnCallback(function ($actions) { + return false; + })); + + $user = new \OC\User\User('foo', $backend); + $this->assertTrue($user->canChangeAvatar()); + } + public function testDelete() { /** * @var \OC_User_Backend | \PHPUnit_Framework_MockObject_MockObject $backend |