From 6d64d7ec3fb64d6b2f196d4008f59b64e5a50446 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Thu, 27 Mar 2014 18:01:14 +0100 Subject: [PATCH] LDAP: put out fetching of user meta data into a fully tested class of its own and update them (mail, quota, etc.) directly after mapping. Fixes #7785 properly on master --- apps/user_ldap/appinfo/app.php | 8 +- apps/user_ldap/lib/access.php | 33 +- apps/user_ldap/lib/filesystemhelper.php | 46 ++ apps/user_ldap/lib/logwrapper.php | 39 ++ apps/user_ldap/lib/proxy.php | 15 +- apps/user_ldap/lib/user/iusertools.php | 35 ++ apps/user_ldap/lib/user/manager.php | 161 ++++++ apps/user_ldap/lib/user/user.php | 320 +++++++++++ apps/user_ldap/tests/user/user.php | 680 ++++++++++++++++++++++++ apps/user_ldap/user_ldap.php | 141 +---- 10 files changed, 1338 insertions(+), 140 deletions(-) create mode 100644 apps/user_ldap/lib/filesystemhelper.php create mode 100644 apps/user_ldap/lib/logwrapper.php create mode 100644 apps/user_ldap/lib/user/iusertools.php create mode 100644 apps/user_ldap/lib/user/manager.php create mode 100644 apps/user_ldap/lib/user/user.php create mode 100644 apps/user_ldap/tests/user/user.php diff --git a/apps/user_ldap/appinfo/app.php b/apps/user_ldap/appinfo/app.php index c2cd295523..b62f205a62 100644 --- a/apps/user_ldap/appinfo/app.php +++ b/apps/user_ldap/appinfo/app.php @@ -26,8 +26,14 @@ 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) { + $ocConfig = \OC::$server->getConfig(); + $userManager = new OCA\user_ldap\lib\user\Manager($ocConfig, + new OCA\user_ldap\lib\FilesystemHelper(), + new OCA\user_ldap\lib\LogWrapper(), + new \OCP\Image(), + \OC::$server->getAvatarManager()); $connector = new OCA\user_ldap\lib\Connection($ldapWrapper, $configPrefixes[0]); - $ldapAccess = new OCA\user_ldap\lib\Access($connector, $ldapWrapper); + $ldapAccess = new OCA\user_ldap\lib\Access($connector, $ldapWrapper, $userManager); $userBackend = new OCA\user_ldap\USER_LDAP($ldapAccess); $groupBackend = new OCA\user_ldap\GROUP_LDAP($ldapAccess); } else if(count($configPrefixes) > 1) { diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index 78de14f4ee..b952910a8c 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -27,20 +27,21 @@ namespace OCA\user_ldap\lib; * Class Access * @package OCA\user_ldap\lib */ -class Access extends LDAPUtility { +class Access extends LDAPUtility implements user\IUserTools { public $connection; + public $userManager; //never ever check this var directly, always use getPagedSearchResultState protected $pagedSearchedSuccessful; protected $cookies = array(); - /** - * @param Connection $connection - * @param ILDAPWrapper $ldap - */ - public function __construct(Connection $connection, ILDAPWrapper $ldap) { + + public function __construct(Connection $connection, ILDAPWrapper $ldap, + user\Manager $userManager) { parent::__construct($ldap); $this->connection = $connection; + $this->userManager = $userManager; + $this->userManager->setLdapAccess($this); } /** @@ -51,9 +52,17 @@ class Access extends LDAPUtility { } /** - * reads a given attribute for an LDAP record identified by a DN - * @param string $dn the record in question - * @param string $attr the attribute that shall be retrieved + * @brief returns the Connection instance + * @return \OCA\user_ldap\lib\Connection + */ + public function getConnection() { + return $this->connection; + } + + /** + * @brief reads a given attribute for an LDAP record identified by a DN + * @param $dn the record in question + * @param $attr the attribute that shall be retrieved * if empty, just check the record's existence * @param string $filter * @return array|false an array of values on success or an empty @@ -626,6 +635,12 @@ class Access extends LDAPUtility { return false; } + if($isUser) { + //make sure that email address is retrieved prior to login, so user + //will be notified when something is shared with him + $this->userManager->get($ocname)->update(); + } + return true; } diff --git a/apps/user_ldap/lib/filesystemhelper.php b/apps/user_ldap/lib/filesystemhelper.php new file mode 100644 index 0000000000..d85173c1f9 --- /dev/null +++ b/apps/user_ldap/lib/filesystemhelper.php @@ -0,0 +1,46 @@ +. + * + */ + +namespace OCA\user_ldap\lib; + +/** + * @brief wraps around static ownCloud core methods + */ +class FilesystemHelper { + + /** + * @brief states whether the filesystem was loaded + * @return bool + */ + public function isLoaded() { + return \OC\Files\Filesystem::$loaded; + } + + /** + * @brief initializes the filesystem for the given user + * @param string the ownCloud username of the user + */ + public function setup($uid) { + \OC_Util::setupFS($uid); + } +} \ No newline at end of file diff --git a/apps/user_ldap/lib/logwrapper.php b/apps/user_ldap/lib/logwrapper.php new file mode 100644 index 0000000000..2d30e0586c --- /dev/null +++ b/apps/user_ldap/lib/logwrapper.php @@ -0,0 +1,39 @@ +. + * + */ + +namespace OCA\user_ldap\lib; + +/** + * @brief wraps around static ownCloud core methods + */ +class LogWrapper { + protected $app = 'user_ldap'; + + /** + * @brief states whether the filesystem was loaded + * @return bool + */ + public function log($msg, $level) { + \OCP\Util::writeLog($this->app, $msg, $level); + } +} \ No newline at end of file diff --git a/apps/user_ldap/lib/proxy.php b/apps/user_ldap/lib/proxy.php index d15d1ae861..73a52a7ddd 100644 --- a/apps/user_ldap/lib/proxy.php +++ b/apps/user_ldap/lib/proxy.php @@ -41,8 +41,21 @@ abstract class Proxy { * @param string $configPrefix */ private function addAccess($configPrefix) { + static $ocConfig; + static $fs; + static $log; + static $avatarM; + if(is_null($fs)) { + $ocConfig = \OC::$server->getConfig(); + $fs = new FilesystemHelper(); + $log = new LogWrapper(); + $avatarM = \OC::$server->getAvatarManager(); + } + $userManager = + new user\Manager($ocConfig, $fs, $log, $avatarM, new \OCP\Image()); $connector = new Connection($this->ldap, $configPrefix); - self::$accesses[$configPrefix] = new Access($connector, $this->ldap); + self::$accesses[$configPrefix] = + new Access($connector, $this->ldap, $userManager); } /** diff --git a/apps/user_ldap/lib/user/iusertools.php b/apps/user_ldap/lib/user/iusertools.php new file mode 100644 index 0000000000..248c975c97 --- /dev/null +++ b/apps/user_ldap/lib/user/iusertools.php @@ -0,0 +1,35 @@ +. + * + */ + +namespace OCA\user_ldap\lib\user; + +interface IUserTools { + public function getConnection(); + + public function readAttribute($dn, $attr, $filter = 'objectClass=*'); + + public function dn2username($dn, $ldapname = null); + + public function username2dn($name); + +} diff --git a/apps/user_ldap/lib/user/manager.php b/apps/user_ldap/lib/user/manager.php new file mode 100644 index 0000000000..1849e86e8c --- /dev/null +++ b/apps/user_ldap/lib/user/manager.php @@ -0,0 +1,161 @@ +. + * + */ + +namespace OCA\user_ldap\lib\user; + +use OCA\user_ldap\lib\user\IUserTools; +use OCA\user_ldap\lib\user\User; +use OCA\user_ldap\lib\LogWrapper; +use OCA\user_ldap\lib\FilesystemHelper; + +class Manager { + /** + * @var IUserTools + */ + protected $access; + /** + * @var \OCP\IConfig + */ + protected $ocConfig; + /** + * @var FilesystemHelper + */ + protected $ocFilesystem; + /** + * @var LogWrapper + */ + protected $ocLog; + /** + * @var \OCP\Image + */ + protected $image; + /** + * @param \OCP\IAvatarManager + */ + protected $avatarManager; + /** + * @var string[][] + */ + protected $users = array( + 'byDN' => array(), + 'byUid' => array(), + ); + + /** + * @brief Constructor + * @param \OCP\IConfig respectively an instance that provides the methods + * setUserValue and getUserValue as implemented in \OCP\Config + * @param \OCA\user_ldap\lib\FilesystemHelper object that gives access to + * necessary functions from the OC filesystem + * @param \OCA\user_ldap\lib\LogWrapper + * @param \OCP\IAvatarManager + * @param \OCP\Image an empty image instance + * @throws Exception when the methods mentioned above do not exist + */ + public function __construct(\OCP\IConfig $ocConfig, + FilesystemHelper $ocFilesystem, LogWrapper $ocLog, + \OCP\IAvatarManager $avatarManager, \OCP\Image $image) { + + if(!method_exists($ocConfig, 'setUserValue') + || !method_exists($ocConfig, 'getUserValue')) { + throw new \Exception('Invalid ownCloud User Config object'); + } + $this->ocConfig = $ocConfig; + $this->ocFilesystem = $ocFilesystem; + $this->ocLog = $ocLog; + $this->avatarManager = $avatarManager; + $this->image = $image; + } + + /** + * @brief binds manager to an instance of IUserTools (implemented by + * Access). It needs to be assigned first before the manager can be used. + * @param IUserTools + */ + public function setLdapAccess(IUserTools $access) { + $this->access = $access; + } + + /** + * @brief creates an instance of User and caches (just runtime) it in the + * property array + * @param string the DN of the user + * @param string the internal (owncloud) username + * @return \OCA\user_ldap\lib\User + */ + private function createAndCache($dn, $uid) { + $this->checkAccess(); + $user = new User($uid, $dn, $this->access, $this->ocConfig, + $this->ocFilesystem, clone $this->image, $this->ocLog, + $this->avatarManager); + $users['byDN'][$dn] = $user; + $users['byUid'][$uid] = $user; + return $user; + } + + /** + * @brief checks whether the Access instance has been set + * @throws Exception if Access has not been set + * @return null + */ + private function checkAccess() { + if(is_null($this->access)) { + throw new \Exception('LDAP Access instance must be set first'); + } + } + + /** + * @brief returns a User object by it's DN or ownCloud username + * @param string the DN or username of the user + * @return \OCA\user_ldap\lib\User | null + */ + public function get($id) { + $this->checkAccess(); + if(isset($this->users['byDN'][$id])) { + return $this->users['byDN'][$id]; + } else if(isset($this->users['byUid'][$id])) { + return $this->users['byUid'][$id]; + } + + if(strpos($id, 'dc=') === false) { + //most likely a uid + $dn = $this->access->username2dn($id); + if($dn !== false) { + return $this->createAndCache($dn, $id); + } + } else { + //so it's a DN + $uid = $this->access->dn2username($id); + if($uid !== false) { + return $this->createAndCache($id, $uid); + } + } + //either funny uid or invalid. Assume funny to be on the safe side. + $dn = $this->access->username2dn($id); + if($dn !== false) { + return $this->createAndCache($dn, $id); + } + return null; + } + +} \ No newline at end of file diff --git a/apps/user_ldap/lib/user/user.php b/apps/user_ldap/lib/user/user.php new file mode 100644 index 0000000000..91417dbae6 --- /dev/null +++ b/apps/user_ldap/lib/user/user.php @@ -0,0 +1,320 @@ +. + * + */ + +namespace OCA\user_ldap\lib\user; + +use OCA\user_ldap\lib\user\IUserTools; +use OCA\user_ldap\lib\Connection; +use OCA\user_ldap\lib\FilesystemHelper; +use OCA\user_ldap\lib\LogWrapper; + +class User { + /** + * @var IUserTools + */ + protected $access; + /** + * @var Connection + */ + protected $connection; + /** + * @var \OCP\IConfig + */ + protected $config; + /** + * @var FilesystemHelper + */ + protected $fs; + /** + * @var \OCP\Image + */ + protected $image; + /** + * @var LogWrapper + */ + protected $log; + /** + * @var \OCP\IAvatarManager + */ + protected $avatarManager; + + /** + * @var string + */ + protected $dn; + /** + * @var string + */ + protected $uid; + /** + * @var string[] + */ + protected $refreshedFeatures = array(); + /** + * @var string + */ + protected $avatarImage; + + /** + * DB config keys for user preferences + */ + const USER_PREFKEY_FIRSTLOGIN = 'firstLoginAccomplished'; + const USER_PREFKEY_LASTREFRESH = 'lastFeatureRefresh'; + + /** + * @brief constructor, make sure the subclasses call this one! + * @param string the internal username + * @param string the LDAP DN + * @param IUserTools $access an instance that implements IUserTools for + * LDAP interaction + * @param \OCP\Config + * @param FilesystemHelper + * @param \OCP\Image any empty instance + * @param LogWrapper + * @param \OCP\IAvatarManager + */ + public function __construct($username, $dn, IUserTools $access, + \OCP\IConfig $config, FilesystemHelper $fs, \OCP\Image $image, + LogWrapper $log, \OCP\IAvatarManager $avatarManager) { + + $this->access = $access; + $this->connection = $access->getConnection(); + $this->config = $config; + $this->fs = $fs; + $this->dn = $dn; + $this->uid = $username; + $this->image = $image; + $this->log = $log; + $this->avatarManager = $avatarManager; + } + + /** + * @brief updates properties like email, quota or avatar provided by LDAP + * @return null + */ + public function update() { + if(is_null($this->dn)) { + return null; + } + + $hasLoggedIn = $this->config->getUserValue($this->uid, 'user_ldap', + self::USER_PREFKEY_FIRSTLOGIN, 0); + + if($this->needsRefresh()) { + $this->updateEmail(); + $this->updateQuota(); + if($hasLoggedIn !== 0) { + //we do not need to try it, when the user has not been logged in + //before, because the file system will not be ready. + $this->updateAvatar(); + //in order to get an avatar as soon as possible, mark the user + //as refreshed only when updating the avatar did happen + $this->markRefreshTime(); + } + } + } + + /** + * @brief returns the LDAP DN of the user + * @return string + */ + public function getDN() { + return $this->dn; + } + + /** + * @brief returns the ownCloud internal username of the user + * @return string + */ + public function getUsername() { + return $this->uid; + } + + /** + * @brief reads the image from LDAP that shall be used as Avatar + * @return string data (provided by LDAP) | false + */ + public function getAvatarImage() { + if(!is_null($this->avatarImage)) { + return $this->avatarImage; + } + + $this->avatarImage = false; + $attributes = array('jpegPhoto', 'thumbnailPhoto'); + foreach($attributes as $attribute) { + $result = $this->access->readAttribute($this->dn, $attribute); + if($result !== false && is_array($result) && isset($result[0])) { + $this->avatarImage = $result[0]; + break; + } + } + + return $this->avatarImage; + } + + /** + * @brief marks the user as having logged in at least once + * @return null + */ + public function markLogin() { + $this->config->setUserValue( + $this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, 1); + } + + /** + * @brief marks the time when user features like email have been updated + * @return null + */ + private function markRefreshTime() { + $this->config->setUserValue( + $this->uid, 'user_ldap', self::USER_PREFKEY_LASTREFRESH, time()); + } + + /** + * @brief checks whether user features needs to be updated again by + * comparing the difference of time of the last refresh to now with the + * desired interval + * @return bool + */ + private function needsRefresh() { + $lastChecked = $this->config->getUserValue($this->uid, 'user_ldap', + self::USER_PREFKEY_LASTREFRESH, 0); + + //TODO make interval configurable + if((time() - intval($lastChecked)) < 86400 ) { + return false; + } + return true; + } + + /** + * @brief checks whether an update method specified by feature was run + * already. If not, it will marked like this, because it is expected that + * the method will be run, when false is returned. + * @param string email | quota | avatar (can be extended) + * @return bool + */ + private function wasRefreshed($feature) { + if(isset($this->refreshedFeatures[$feature])) { + return true; + } + $this->refreshedFeatures[$feature] = 1; + return false; + } + + /** + * @brief fetches the email from LDAP and stores it as ownCloud user value + * @return null + */ + public function updateEmail() { + if($this->wasRefreshed('email')) { + return; + } + + $email = null; + $emailAttribute = $this->connection->ldapEmailAttribute; + if(!empty($emailAttribute)) { + $aEmail = $this->access->readAttribute($this->dn, $emailAttribute); + if($aEmail && (count($aEmail) > 0)) { + $email = $aEmail[0]; + } + if(!is_null($email)) { + $this->config->setUserValue( + $this->uid, 'settings', 'email', $email); + } + } + } + + /** + * @brief fetches the quota from LDAP and stores it as ownCloud user value + * @return null + */ + public function updateQuota() { + if($this->wasRefreshed('quota')) { + return; + } + + $quota = null; + $quotaDefault = $this->connection->ldapQuotaDefault; + $quotaAttribute = $this->connection->ldapQuotaAttribute; + if(!empty($quotaDefault)) { + $quota = $quotaDefault; + } + if(!empty($quotaAttribute)) { + $aQuota = $this->access->readAttribute($this->dn, $quotaAttribute); + + if($aQuota && (count($aQuota) > 0)) { + $quota = $aQuota[0]; + } + } + if(!is_null($quota)) { + $this->config->setUserValue($this->uid, 'files', 'quota', + \OCP\Util::computerFileSize($quota)); + } + } + + /** + * @brief attempts to get an image from LDAP and sets it as ownCloud avatar + * @return null + */ + public function updateAvatar() { + if($this->wasRefreshed('avatar')) { + return; + } + $avatarImage = $this->getAvatarImage(); + if($avatarImage === false) { + //not set, nothing left to do; + return; + } + $this->image->loadFromBase64(base64_encode($avatarImage)); + $this->setOwnCloudAvatar(); + } + + /** + * @brief sets an image as ownCloud avatar + * @return null + */ + private function setOwnCloudAvatar() { + if(!$this->image->valid()) { + $this->log->log('user_ldap', 'jpegPhoto data invalid for '.$this->dn, + \OCP\Util::ERROR); + return; + } + //make sure it is a square and not bigger than 128x128 + $size = min(array($this->image->width(), $this->image->height(), 128)); + if(!$this->image->centerCrop($size)) { + $this->log->log('user_ldap', + 'croping image for avatar failed for '.$this->dn, + \OCP\Util::ERROR); + return; + } + + if(!$this->fs->isLoaded()) { + $this->fs->setup($this->uid); + } + + $avatar = $this->avatarManager->getAvatar($this->uid); + $avatar->set($this->image); + } + +} diff --git a/apps/user_ldap/tests/user/user.php b/apps/user_ldap/tests/user/user.php new file mode 100644 index 0000000000..875e64e4fc --- /dev/null +++ b/apps/user_ldap/tests/user/user.php @@ -0,0 +1,680 @@ +. +* +*/ + +namespace OCA\user_ldap\tests; + +use OCA\user_ldap\lib\user\User; + +class Test_User_User extends \PHPUnit_Framework_TestCase { + + private function getTestInstances() { + $access = $this->getMock('\OCA\user_ldap\lib\user\IUserTools'); + $config = $this->getMock('\OCP\IConfig'); + $filesys = $this->getMock('\OCA\user_ldap\lib\FilesystemHelper'); + $log = $this->getMock('\OCA\user_ldap\lib\LogWrapper'); + $avaMgr = $this->getMock('\OCP\IAvatarManager'); + $image = $this->getMock('\OCP\Image'); + + return array($access, $config, $filesys, $image, $log, $avaMgr); + } + + private function getAdvancedMocks($cfMock, $fsMock, $logMock, $avaMgr) { + static $conMethods; + static $accMethods; + static $umMethods; + + 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'); + //getConnection shall not be replaced + unset($accMethods[array_search('getConnection', $accMethods)]); + $umMethods = get_class_methods('\OCA\user_ldap\lib\user\Manager'); + } + $lw = $this->getMock('\OCA\user_ldap\lib\ILDAPWrapper'); + $im = $this->getMock('\OCP\Image'); + $um = $this->getMock('\OCA\user_ldap\lib\user\Manager', + $umMethods, array($cfMock, $fsMock, $logMock, $avaMgr, $im)); + $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, $um)); + + return array($access, $connector); + } + + public function testGetDNandUsername() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $this->assertSame($dn, $user->getDN()); + $this->assertSame($uid, $user->getUsername()); + } + + public function testUpdateEmailProvided() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + list($access, $connection) = + $this->getAdvancedMocks($config, $filesys, $log, $avaMgr); + + $connection->expects($this->once()) + ->method('__get') + ->with($this->equalTo('ldapEmailAttribute')) + ->will($this->returnValue('email')); + + $access->expects($this->once()) + ->method('readAttribute') + ->with($this->equalTo('uid=alice,dc=foo,dc=bar'), + $this->equalTo('email')) + ->will($this->returnValue(array('alice@foo.bar'))); + + $config->expects($this->once()) + ->method('setUserValue') + ->with($this->equalTo('alice'), $this->equalTo('settings'), + $this->equalTo('email'), + $this->equalTo('alice@foo.bar')) + ->will($this->returnValue(true)); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->updateEmail(); + } + + public function testUpdateEmailNotProvided() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + list($access, $connection) = + $this->getAdvancedMocks($config, $filesys, $log, $avaMgr); + + $connection->expects($this->once()) + ->method('__get') + ->with($this->equalTo('ldapEmailAttribute')) + ->will($this->returnValue('email')); + + $access->expects($this->once()) + ->method('readAttribute') + ->with($this->equalTo('uid=alice,dc=foo,dc=bar'), + $this->equalTo('email')) + ->will($this->returnValue(false)); + + $config->expects($this->never()) + ->method('setUserValue'); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->updateEmail(); + } + + public function testUpdateEmailNotConfigured() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + list($access, $connection) = + $this->getAdvancedMocks($config, $filesys, $log, $avaMgr); + + $connection->expects($this->once()) + ->method('__get') + ->with($this->equalTo('ldapEmailAttribute')) + ->will($this->returnValue('')); + + $access->expects($this->never()) + ->method('readAttribute'); + + $config->expects($this->never()) + ->method('setUserValue'); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->updateEmail(); + } + + public function testUpdateQuotaAllProvided() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + list($access, $connection) = + $this->getAdvancedMocks($config, $filesys, $log, $avaMgr); + + $connection->expects($this->at(0)) + ->method('__get') + ->with($this->equalTo('ldapQuotaDefault')) + ->will($this->returnValue('23 GB')); + + $connection->expects($this->at(1)) + ->method('__get') + ->with($this->equalTo('ldapQuotaAttribute')) + ->will($this->returnValue('myquota')); + + $connection->expects($this->exactly(2)) + ->method('__get'); + + $access->expects($this->once()) + ->method('readAttribute') + ->with($this->equalTo('uid=alice,dc=foo,dc=bar'), + $this->equalTo('myquota')) + ->will($this->returnValue(array('42 GB'))); + + $config->expects($this->once()) + ->method('setUserValue') + ->with($this->equalTo('alice'), + $this->equalTo('files'), + $this->equalTo('quota'), + $this->equalTo(\OCP\Util::computerFileSize('42 GB'))) + ->will($this->returnValue(true)); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->updateQuota(); + } + + public function testUpdateQuotaDefaultProvided() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + list($access, $connection) = + $this->getAdvancedMocks($config, $filesys, $log, $avaMgr); + + $connection->expects($this->at(0)) + ->method('__get') + ->with($this->equalTo('ldapQuotaDefault')) + ->will($this->returnValue('23 GB')); + + $connection->expects($this->at(1)) + ->method('__get') + ->with($this->equalTo('ldapQuotaAttribute')) + ->will($this->returnValue('myquota')); + + $connection->expects($this->exactly(2)) + ->method('__get'); + + $access->expects($this->once()) + ->method('readAttribute') + ->with($this->equalTo('uid=alice,dc=foo,dc=bar'), + $this->equalTo('myquota')) + ->will($this->returnValue(false)); + + $config->expects($this->once()) + ->method('setUserValue') + ->with($this->equalTo('alice'), + $this->equalTo('files'), + $this->equalTo('quota'), + $this->equalTo(\OCP\Util::computerFileSize('23 GB'))) + ->will($this->returnValue(true)); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->updateQuota(); + } + + public function testUpdateQuotaIndividualProvided() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + list($access, $connection) = + $this->getAdvancedMocks($config, $filesys, $log, $avaMgr); + + $connection->expects($this->at(0)) + ->method('__get') + ->with($this->equalTo('ldapQuotaDefault')) + ->will($this->returnValue('')); + + $connection->expects($this->at(1)) + ->method('__get') + ->with($this->equalTo('ldapQuotaAttribute')) + ->will($this->returnValue('myquota')); + + $connection->expects($this->exactly(2)) + ->method('__get'); + + $access->expects($this->once()) + ->method('readAttribute') + ->with($this->equalTo('uid=alice,dc=foo,dc=bar'), + $this->equalTo('myquota')) + ->will($this->returnValue(array('23 GB'))); + + $config->expects($this->once()) + ->method('setUserValue') + ->with($this->equalTo('alice'), + $this->equalTo('files'), + $this->equalTo('quota'), + $this->equalTo(\OCP\Util::computerFileSize('23 GB'))) + ->will($this->returnValue(true)); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->updateQuota(); + } + + public function testUpdateQuotaNoneProvided() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + list($access, $connection) = + $this->getAdvancedMocks($config, $filesys, $log, $avaMgr); + + $connection->expects($this->at(0)) + ->method('__get') + ->with($this->equalTo('ldapQuotaDefault')) + ->will($this->returnValue('')); + + $connection->expects($this->at(1)) + ->method('__get') + ->with($this->equalTo('ldapQuotaAttribute')) + ->will($this->returnValue('myquota')); + + $connection->expects($this->exactly(2)) + ->method('__get'); + + $access->expects($this->once()) + ->method('readAttribute') + ->with($this->equalTo('uid=alice,dc=foo,dc=bar'), + $this->equalTo('myquota')) + ->will($this->returnValue(false)); + + $config->expects($this->never()) + ->method('setUserValue'); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->updateQuota(); + } + + public function testUpdateQuotaNoneConfigured() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + list($access, $connection) = + $this->getAdvancedMocks($config, $filesys, $log, $avaMgr); + + $connection->expects($this->at(0)) + ->method('__get') + ->with($this->equalTo('ldapQuotaDefault')) + ->will($this->returnValue('')); + + $connection->expects($this->at(1)) + ->method('__get') + ->with($this->equalTo('ldapQuotaAttribute')) + ->will($this->returnValue('')); + + $connection->expects($this->exactly(2)) + ->method('__get'); + + $access->expects($this->never()) + ->method('readAttribute'); + + $config->expects($this->never()) + ->method('setUserValue'); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->updateQuota(); + } + + //the testUpdateAvatar series also implicitely tests getAvatarImage + public function testUpdateAvatarJpegPhotoProvided() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + list($access, $connection) = + $this->getAdvancedMocks($config, $filesys, $log, $avaMgr); + + $access->expects($this->once()) + ->method('readAttribute') + ->with($this->equalTo('uid=alice,dc=foo,dc=bar'), + $this->equalTo('jpegPhoto')) + ->will($this->returnValue(array('this is a photo'))); + + $image->expects($this->once()) + ->method('valid') + ->will($this->returnValue(true)); + $image->expects($this->once()) + ->method('width') + ->will($this->returnValue(128)); + $image->expects($this->once()) + ->method('height') + ->will($this->returnValue(128)); + $image->expects($this->once()) + ->method('centerCrop') + ->will($this->returnValue(true)); + + $filesys->expects($this->once()) + ->method('isLoaded') + ->will($this->returnValue(true)); + + $avatar = $this->getMock('\OCP\IAvatar'); + $avatar->expects($this->once()) + ->method('set') + ->with($this->isInstanceOf($image)); + + $avaMgr->expects($this->once()) + ->method('getAvatar') + ->with($this->equalTo('alice')) + ->will($this->returnValue($avatar)); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->updateAvatar(); + } + + public function testUpdateAvatarThumbnailPhotoProvided() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + list($access, $connection) = + $this->getAdvancedMocks($config, $filesys, $log, $avaMgr); + + $access->expects($this->at(0)) + ->method('readAttribute') + ->with($this->equalTo('uid=alice,dc=foo,dc=bar'), + $this->equalTo('jpegPhoto')) + ->will($this->returnValue(false)); + + $access->expects($this->at(1)) + ->method('readAttribute') + ->with($this->equalTo('uid=alice,dc=foo,dc=bar'), + $this->equalTo('thumbnailPhoto')) + ->will($this->returnValue(array('this is a photo'))); + + $access->expects($this->exactly(2)) + ->method('readAttribute'); + + $image->expects($this->once()) + ->method('valid') + ->will($this->returnValue(true)); + $image->expects($this->once()) + ->method('width') + ->will($this->returnValue(128)); + $image->expects($this->once()) + ->method('height') + ->will($this->returnValue(128)); + $image->expects($this->once()) + ->method('centerCrop') + ->will($this->returnValue(true)); + + $filesys->expects($this->once()) + ->method('isLoaded') + ->will($this->returnValue(true)); + + $avatar = $this->getMock('\OCP\IAvatar'); + $avatar->expects($this->once()) + ->method('set') + ->with($this->isInstanceOf($image)); + + $avaMgr->expects($this->once()) + ->method('getAvatar') + ->with($this->equalTo('alice')) + ->will($this->returnValue($avatar)); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->updateAvatar(); + } + + public function testUpdateAvatarNotProvided() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + list($access, $connection) = + $this->getAdvancedMocks($config, $filesys, $log, $avaMgr); + + $access->expects($this->at(0)) + ->method('readAttribute') + ->with($this->equalTo('uid=alice,dc=foo,dc=bar'), + $this->equalTo('jpegPhoto')) + ->will($this->returnValue(false)); + + $access->expects($this->at(1)) + ->method('readAttribute') + ->with($this->equalTo('uid=alice,dc=foo,dc=bar'), + $this->equalTo('thumbnailPhoto')) + ->will($this->returnValue(false)); + + $access->expects($this->exactly(2)) + ->method('readAttribute'); + + $image->expects($this->never()) + ->method('valid'); + $image->expects($this->never()) + ->method('width'); + $image->expects($this->never()) + ->method('height'); + $image->expects($this->never()) + ->method('centerCrop'); + + $filesys->expects($this->never()) + ->method('isLoaded'); + + $avaMgr->expects($this->never()) + ->method('getAvatar'); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->updateAvatar(); + } + + public function testUpdateBeforeFirstLogin() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + list($access, $connection) = + $this->getAdvancedMocks($config, $filesys, $log, $avaMgr); + + $config->expects($this->at(0)) + ->method('getUserValue') + ->with($this->equalTo('alice'), $this->equalTo('user_ldap'), + $this->equalTo(User::USER_PREFKEY_FIRSTLOGIN), + $this->equalTo(0)) + ->will($this->returnValue(0)); + + $config->expects($this->at(1)) + ->method('getUserValue') + ->with($this->equalTo('alice'), $this->equalTo('user_ldap'), + $this->equalTo(User::USER_PREFKEY_LASTREFRESH), + $this->equalTo(0)) + ->will($this->returnValue(0)); + + $config->expects($this->exactly(2)) + ->method('getUserValue'); + + $config->expects($this->never()) + ->method('setUserValue'); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->update(); + } + + public function testUpdateAfterFirstLogin() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + list($access, $connection) = + $this->getAdvancedMocks($config, $filesys, $log, $avaMgr); + + $config->expects($this->at(0)) + ->method('getUserValue') + ->with($this->equalTo('alice'), $this->equalTo('user_ldap'), + $this->equalTo(User::USER_PREFKEY_FIRSTLOGIN), + $this->equalTo(0)) + ->will($this->returnValue(1)); + + $config->expects($this->at(1)) + ->method('getUserValue') + ->with($this->equalTo('alice'), $this->equalTo('user_ldap'), + $this->equalTo(User::USER_PREFKEY_LASTREFRESH), + $this->equalTo(0)) + ->will($this->returnValue(0)); + + $config->expects($this->exactly(2)) + ->method('getUserValue'); + + $config->expects($this->once()) + ->method('setUserValue') + ->with($this->equalTo('alice'), $this->equalTo('user_ldap'), + $this->equalTo(User::USER_PREFKEY_LASTREFRESH), + $this->anything()) + ->will($this->returnValue(true)); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->update(); + } + + public function testUpdateNoRefresh() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + list($access, $connection) = + $this->getAdvancedMocks($config, $filesys, $log, $avaMgr); + + $config->expects($this->at(0)) + ->method('getUserValue') + ->with($this->equalTo('alice'), $this->equalTo('user_ldap'), + $this->equalTo(User::USER_PREFKEY_FIRSTLOGIN), + $this->equalTo(0)) + ->will($this->returnValue(1)); + + $config->expects($this->at(1)) + ->method('getUserValue') + ->with($this->equalTo('alice'), $this->equalTo('user_ldap'), + $this->equalTo(User::USER_PREFKEY_LASTREFRESH), + $this->equalTo(0)) + ->will($this->returnValue(time())); + + $config->expects($this->exactly(2)) + ->method('getUserValue'); + + $config->expects($this->never()) + ->method('setUserValue'); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->update(); + } + + public function testMarkLogin() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + $config->expects($this->once()) + ->method('setUserValue') + ->with($this->equalTo('alice'), + $this->equalTo('user_ldap'), + $this->equalTo(User::USER_PREFKEY_FIRSTLOGIN), + $this->equalTo(1)) + ->will($this->returnValue(true)); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $user->markLogin(); + } + + public function testGetAvatarImageProvided() { + list($access, $config, $filesys, $image, $log, $avaMgr) = + $this->getTestInstances(); + + $access->expects($this->once()) + ->method('readAttribute') + ->with($this->equalTo('uid=alice,dc=foo,dc=bar'), + $this->equalTo('jpegPhoto')) + ->will($this->returnValue(array('this is a photo'))); + + $uid = 'alice'; + $dn = 'uid=alice,dc=foo,dc=bar'; + + $user = new User( + $uid, $dn, $access, $config, $filesys, $image, $log, $avaMgr); + + $photo = $user->getAvatarImage(); + $this->assertSame('this is a photo', $photo); + //make sure readAttribute is not called again but the already fetched + //photo is returned + $photo = $user->getAvatarImage(); + } +} \ No newline at end of file diff --git a/apps/user_ldap/user_ldap.php b/apps/user_ldap/user_ldap.php index aece2ee9aa..9fa2a6b418 100644 --- a/apps/user_ldap/user_ldap.php +++ b/apps/user_ldap/user_ldap.php @@ -28,128 +28,19 @@ namespace OCA\user_ldap; use OCA\user_ldap\lib\BackendUtility; class USER_LDAP extends BackendUtility implements \OCP\UserInterface { - - private function updateQuota($dn) { - $quota = null; - $quotaDefault = $this->access->connection->ldapQuotaDefault; - $quotaAttribute = $this->access->connection->ldapQuotaAttribute; - if(!empty($quotaDefault)) { - $quota = $quotaDefault; - } - if(!empty($quotaAttribute)) { - $aQuota = $this->access->readAttribute($dn, $quotaAttribute); - - if($aQuota && (count($aQuota) > 0)) { - $quota = $aQuota[0]; - } - } - if(!is_null($quota)) { - \OCP\Config::setUserValue( $this->access->dn2username($dn), - 'files', - 'quota', - \OCP\Util::computerFileSize($quota)); - } - } - - private function updateEmail($dn) { - $email = null; - $emailAttribute = $this->access->connection->ldapEmailAttribute; - if(!empty($emailAttribute)) { - $aEmail = $this->access->readAttribute($dn, $emailAttribute); - if($aEmail && (count($aEmail) > 0)) { - $email = $aEmail[0]; - } - if(!is_null($email)) { - \OCP\Config::setUserValue( $this->access->dn2username($dn), - 'settings', - 'email', - $email); - } - } - } - - /** - * reads jpegPhoto and set is as avatar if available - * @param string $uid ownCloud user name - * @param string $dn 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; - } - - $avatarImage = $this->getAvatarImage($uid, $dn); - if($avatarImage === false) { - //not set, nothing left to do; - return; - } - - $image = new \OCP\Image(); - $image->loadFromBase64(base64_encode($avatarImage)); - - 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); - } - /** * checks whether the user is allowed to change his avatar in ownCloud * @param string $uid the ownCloud user name * @return boolean either the user can or cannot */ public function canChangeAvatar($uid) { - $dn = $this->access->username2dn($uid); - if(!$dn) { + $user = $this->access->userManager->get($uid); + if(is_null($user)) { return false; } - if($this->getAvatarImage($uid, $dn) === false) { - //The user is allowed to change his avatar in ownCloud only if no - //avatar is provided by LDAP + if($user->getAvatarImage() === false) { return true; } - return false; - } - - /** - * reads the image from LDAP that shall be used as Avatar - * @param string $uid the ownCloud user name - * @param string $dn the user DN - * @return string data (provided by LDAP) | false - */ - private function getAvatarImage($uid, $dn) { - $attributes = array('jpegPhoto', 'thumbnailPhoto'); - foreach($attributes as $attribute) { - $result = $this->access->readAttribute($dn, $attribute); - \OCP\Config::setUserValue($uid, 'user_ldap', 'lastJpegPhotoLookup', - time()); - if($result !== false && is_array($result) && isset($result[0])) { - return $result[0]; - } - } return false; } @@ -174,25 +65,17 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface { } $dn = $ldap_users[0]; - //do we have a username for him/her? - $ocname = $this->access->dn2username($dn); - - if($ocname) { - //update some settings, if necessary - $this->updateQuota($dn); - $this->updateEmail($dn); - + $user = $this->access->userManager->get($dn); + if($user->getUsername() !== false) { //are the credentials OK? if(!$this->access->areCredentialsValid($dn, $password)) { return false; } - \OCP\Config::setUserValue($ocname, 'user_ldap', - 'firstLoginAccomplished', 1); + $user->markLogin(); + $user->update(); - $this->updateAvatar($ocname, $dn); - //give back the display name - return $ocname; + return $user->getUsername(); } return false; @@ -249,13 +132,14 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface { 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->access->username2dn($uid); - if(!$dn) { + $user = $this->access->userManager->get($uid); + if(is_null($user)) { \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; } + $dn = $user->getDN(); //check if user really still exists by reading its entry if(!is_array($this->access->readAttribute($dn, ''))) { \OCP\Util::writeLog('user_ldap', 'LDAP says no user '.$dn.' on '. @@ -265,8 +149,7 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface { } $this->access->connection->writeToCache('userExists'.$uid, true); - $this->updateQuota($dn); - $this->updateAvatar($uid, $dn); + $user->update(); return true; }