diff --git a/apps/user_ldap/ajax/deleteConfiguration.php b/apps/user_ldap/ajax/deleteConfiguration.php index bca687c81a..d409d891f6 100644 --- a/apps/user_ldap/ajax/deleteConfiguration.php +++ b/apps/user_ldap/ajax/deleteConfiguration.php @@ -27,7 +27,8 @@ OCP\JSON::checkAppEnabled('user_ldap'); OCP\JSON::callCheck(); $prefix = $_POST['ldap_serverconfig_chooser']; -if(\OCA\user_ldap\lib\Helper::deleteServerConfiguration($prefix)) { +$helper = new \OCA\user_ldap\lib\Helper(); +if($helper->deleteServerConfiguration($prefix)) { OCP\JSON::success(); } else { $l = \OC::$server->getL10N('user_ldap'); diff --git a/apps/user_ldap/ajax/getNewServerConfigPrefix.php b/apps/user_ldap/ajax/getNewServerConfigPrefix.php index 1c68b2e9a7..ce6c5ae76e 100644 --- a/apps/user_ldap/ajax/getNewServerConfigPrefix.php +++ b/apps/user_ldap/ajax/getNewServerConfigPrefix.php @@ -26,7 +26,8 @@ OCP\JSON::checkAdminUser(); OCP\JSON::checkAppEnabled('user_ldap'); OCP\JSON::callCheck(); -$serverConnections = \OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes(); +$helper = new \OCA\user_ldap\lib\Helper(); +$serverConnections = $helper->getServerConfigurationPrefixes(); sort($serverConnections); $lk = array_pop($serverConnections); $ln = intval(str_replace('s', '', $lk)); diff --git a/apps/user_ldap/appinfo/app.php b/apps/user_ldap/appinfo/app.php index 98d5fb6018..302575c368 100644 --- a/apps/user_ldap/appinfo/app.php +++ b/apps/user_ldap/appinfo/app.php @@ -5,6 +5,7 @@ * * @author Dominik Schmidt * @copyright 2011 Dominik Schmidt dev@dominik-schmidt.de +* @copyright 2014 Arthur Schiwon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -23,7 +24,8 @@ OCP\App::registerAdmin('user_ldap', 'settings'); -$configPrefixes = OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes(true); +$helper = new \OCA\user_ldap\lib\Helper(); +$configPrefixes = $helper->getServerConfigurationPrefixes(true); $ldapWrapper = new OCA\user_ldap\lib\LDAP(); if(count($configPrefixes) === 1) { $ocConfig = \OC::$server->getConfig(); @@ -50,16 +52,10 @@ if(count($configPrefixes) > 0) { OC_Group::useBackend($groupBackend); } -// add settings page to navigation -$entry = array( - 'id' => 'user_ldap_settings', - 'order'=>1, - 'href' => OCP\Util::linkTo( 'user_ldap', 'settings.php' ), - 'name' => 'LDAP' -); OCP\Util::addTranslations('user_ldap'); - OCP\Backgroundjob::registerJob('OCA\user_ldap\lib\Jobs'); +OCP\Backgroundjob::registerJob('\OCA\User_LDAP\Jobs\CleanUp'); + if(OCP\App::isEnabled('user_webdavauth')) { OCP\Util::writeLog('user_ldap', 'user_ldap and user_webdavauth are incompatible. You may experience unexpected behaviour', diff --git a/apps/user_ldap/appinfo/register_command.php b/apps/user_ldap/appinfo/register_command.php index 1a0227db95..55a187d965 100644 --- a/apps/user_ldap/appinfo/register_command.php +++ b/apps/user_ldap/appinfo/register_command.php @@ -6,9 +6,22 @@ * See the COPYING-README file. */ +use OCA\user_ldap\lib\Helper; +use OCA\user_ldap\lib\LDAP; +use OCA\user_ldap\User_Proxy; + $application->add(new OCA\user_ldap\Command\ShowConfig()); $application->add(new OCA\user_ldap\Command\SetConfig()); $application->add(new OCA\user_ldap\Command\TestConfig()); $application->add(new OCA\user_ldap\Command\CreateEmptyConfig()); $application->add(new OCA\user_ldap\Command\DeleteConfig()); $application->add(new OCA\user_ldap\Command\Search()); +$application->add(new OCA\user_ldap\Command\ShowRemnants()); +$helper = new OCA\user_ldap\lib\Helper(); +$uBackend = new OCA\user_ldap\User_Proxy( + $helper->getServerConfigurationPrefixes(true), + new OCA\user_ldap\lib\LDAP() +); +$application->add(new OCA\user_ldap\Command\CheckUser( + $uBackend, $helper, \OC::$server->getConfig() +)); diff --git a/apps/user_ldap/appinfo/update.php b/apps/user_ldap/appinfo/update.php index 9bf0ca4ab5..b4121b1985 100644 --- a/apps/user_ldap/appinfo/update.php +++ b/apps/user_ldap/appinfo/update.php @@ -12,7 +12,8 @@ if($state === 'unset') { $installedVersion = $configInstance->getAppValue('user_ldap', 'installed_version'); $enableRawMode = version_compare($installedVersion, '0.4.1', '<'); -$configPrefixes = OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes(true); +$helper = new \OCA\user_ldap\lib\Helper(); +$configPrefixes = $helper->getServerConfigurationPrefixes(true); $ldap = new OCA\user_ldap\lib\LDAP(); foreach($configPrefixes as $config) { $connection = new OCA\user_ldap\lib\Connection($ldap, $config); diff --git a/apps/user_ldap/appinfo/version b/apps/user_ldap/appinfo/version index 6f2743d65d..0bfccb0804 100644 --- a/apps/user_ldap/appinfo/version +++ b/apps/user_ldap/appinfo/version @@ -1 +1 @@ -0.4.4 +0.4.5 diff --git a/apps/user_ldap/command/checkuser.php b/apps/user_ldap/command/checkuser.php new file mode 100644 index 0000000000..96c6c83235 --- /dev/null +++ b/apps/user_ldap/command/checkuser.php @@ -0,0 +1,129 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCA\user_ldap\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +use OCA\user_ldap\lib\user\User; +use OCA\User_LDAP\lib\user\Manager; +use OCA\user_ldap\lib\Helper; +use OCA\user_ldap\User_Proxy; + +class CheckUser extends Command { + /** @var \OCA\user_ldap\User_Proxy */ + protected $backend; + + /** @var \OCA\User_LDAP\lib\Helper */ + protected $helper; + + /** @var \OCP\IConfig */ + protected $config; + + /** + * @param OCA\user_ldap\User_Proxy $uBackend + * @param OCA\User_LDAP\lib\Helper $helper + * @param OCP\IConfig $config + */ + public function __construct(User_Proxy $uBackend, Helper $helper, \OCP\IConfig $config) { + $this->backend = $uBackend; + $this->helper = $helper; + $this->config = $config; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('ldap:check-user') + ->setDescription('checks whether a user exists on LDAP.') + ->addArgument( + 'ocName', + InputArgument::REQUIRED, + 'the user name as used in ownCloud' + ) + ->addOption( + 'force', + null, + InputOption::VALUE_NONE, + 'ignores disabled LDAP configuration' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + try { + $uid = $input->getArgument('ocName'); + $this->isAllowed($input->getOption('force')); + $this->confirmUserIsMapped($uid); + $exists = $this->backend->userExistsOnLDAP($uid); + if($exists === true) { + $output->writeln('The user is still available on LDAP.'); + return; + } + + // TODO FIXME consolidate next line in DeletedUsersIndex + // (impractical now, because of class dependencies) + $this->config->setUserValue($uid, 'user_ldap', 'isDeleted', '1'); + + $output->writeln('The user does not exists on LDAP anymore.'); + $output->writeln('Clean up the user\'s remnants by: ./occ user:delete "' + . $uid . '"'); + } catch (\Exception $e) { + $output->writeln('' . $e->getMessage(). ''); + } + } + + /** + * checks whether a user is actually mapped + * @param string $ocName the username as used in ownCloud + * @throws \Exception + * @return bool + */ + protected function confirmUserIsMapped($ocName) { + //TODO FIXME this should go to Mappings in OC 8 + $db = \OC::$server->getDatabaseConnection(); + $query = $db->prepare(' + SELECT + `ldap_dn` AS `dn` + FROM `*PREFIX*ldap_user_mapping` + WHERE `owncloud_name` = ?' + ); + + $query->execute(array($ocName)); + $result = $query->fetchColumn(); + + if($result === false) { + throw new \Exception('The given user is not a recognized LDAP user.'); + } + + return true; + } + + /** + * checks whether the setup allows reliable checking of LDAP user existance + * @throws \Exception + * @return bool + */ + protected function isAllowed($force) { + if($this->helper->haveDisabledConfigurations() && !$force) { + throw new \Exception('Cannot check user existance, because ' + . 'disabled LDAP configurations are present.'); + } + + // we don't check ldapUserCleanupInterval from config.php because this + // action is triggered manually, while the setting only controls the + // background job. + + return true; + } + +} diff --git a/apps/user_ldap/command/search.php b/apps/user_ldap/command/search.php index e20255510d..d826303c55 100644 --- a/apps/user_ldap/command/search.php +++ b/apps/user_ldap/command/search.php @@ -74,7 +74,8 @@ class Search extends Command { } protected function execute(InputInterface $input, OutputInterface $output) { - $configPrefixes = Helper::getServerConfigurationPrefixes(true); + $helper = new Helper(); + $configPrefixes = $helper->getServerConfigurationPrefixes(true); $ldapWrapper = new LDAP(); $offset = intval($input->getOption('offset')); diff --git a/apps/user_ldap/command/setconfig.php b/apps/user_ldap/command/setconfig.php index ab1c8d39ea..9128fcf04f 100644 --- a/apps/user_ldap/command/setconfig.php +++ b/apps/user_ldap/command/setconfig.php @@ -41,7 +41,8 @@ class SetConfig extends Command { } protected function execute(InputInterface $input, OutputInterface $output) { - $availableConfigs = Helper::getServerConfigurationPrefixes(); + $helper = new Helper(); + $availableConfigs = $helper->getServerConfigurationPrefixes(); $configID = $input->getArgument('configID'); if(!in_array($configID, $availableConfigs)) { $output->writeln("Invalid configID"); diff --git a/apps/user_ldap/command/showconfig.php b/apps/user_ldap/command/showconfig.php index f51d641bee..ddbc45243f 100644 --- a/apps/user_ldap/command/showconfig.php +++ b/apps/user_ldap/command/showconfig.php @@ -31,7 +31,8 @@ class ShowConfig extends Command { } protected function execute(InputInterface $input, OutputInterface $output) { - $availableConfigs = Helper::getServerConfigurationPrefixes(); + $helper = new Helper(); + $availableConfigs = $helper->getServerConfigurationPrefixes(); $configID = $input->getArgument('configID'); if(!is_null($configID)) { $configIDs[] = $configID; diff --git a/apps/user_ldap/command/showremnants.php b/apps/user_ldap/command/showremnants.php new file mode 100644 index 0000000000..3d39f97742 --- /dev/null +++ b/apps/user_ldap/command/showremnants.php @@ -0,0 +1,81 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCA\user_ldap\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +use OCA\user_ldap\lib\user\DeletedUsersIndex; +use OCA\User_LDAP\lib\Connection; +use OCA\User_LDAP\lib\Access; + +class ShowRemnants extends Command { + + protected function configure() { + $this + ->setName('ldap:show-remnants') + ->setDescription('shows which users are not available on LDAP anymore, but have remnants in ownCloud.') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $dui = new DeletedUsersIndex( + new \OC\Preferences(\OC_DB::getConnection()), + \OC::$server->getDatabaseConnection(), + $this->getAccess() + ); + + /** @var \Symfony\Component\Console\Helper\Table $table */ + $table = $this->getHelperSet()->get('table'); + $table->setHeaders(array( + 'ownCloud name', 'Display Name', 'LDAP UID', 'LDAP DN', 'Last Login', + 'Dir', 'Sharer')); + $rows = array(); + $offset = 0; + do { + $resultSet = $dui->getUsers($offset); + $offset += count($resultSet); + foreach($resultSet as $user) { + $hAS = $user->getHasActiveShares() ? 'Y' : 'N'; + $lastLogin = ($user->getLastLogin() > 0) ? + \OCP\Util::formatDate($user->getLastLogin()) : '-'; + $rows[] = array( + $user->getOCName(), + $user->getDisplayName(), + $user->getUid(), + $user->getDN(), + $lastLogin, + $user->getHomePath(), + $hAS + ); + } + } while (count($resultSet) === 10); + + $table->setRows($rows); + $table->render($output); + } + + protected function getAccess() { + $ldap = new \OCA\user_ldap\lib\LDAP(); + $dummyConnection = new Connection($ldap, '', null); + $userManager = new \OCA\user_ldap\lib\user\Manager( + \OC::$server->getConfig(), + new \OCA\user_ldap\lib\FilesystemHelper(), + new \OCA\user_ldap\lib\LogWrapper(), + \OC::$server->getAvatarManager(), + new \OCP\Image() + ); + $access = new Access($dummyConnection, $ldap, $userManager); + return $access; + } + +} diff --git a/apps/user_ldap/command/testconfig.php b/apps/user_ldap/command/testconfig.php index 00b4acf2f6..a44e22415e 100644 --- a/apps/user_ldap/command/testconfig.php +++ b/apps/user_ldap/command/testconfig.php @@ -31,7 +31,8 @@ class TestConfig extends Command { } protected function execute(InputInterface $input, OutputInterface $output) { - $availableConfigs = Helper::getServerConfigurationPrefixes(); + $helper = new Helper(); + $availableConfigs = $helper->getServerConfigurationPrefixes(); $configID = $input->getArgument('configID'); if(!in_array($configID, $availableConfigs)) { $output->writeln("Invalid configID"); diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index 5d0910320b..692afb98f9 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -290,6 +290,7 @@ class Access extends LDAPUtility implements user\IUserTools { } /** + public function ocname2dn($name, $isUser) { * returns the internal ownCloud name for the given LDAP DN of the group, false on DN outside of search DN or failure * @param string $fdn the dn of the group object * @param string $ldapName optional, the display name of the object diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index 54aafb9341..5df5031d00 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -71,8 +71,9 @@ class Connection extends LDAPUtility { } $this->hasPagedResultSupport = $this->ldap->hasPagedResultSupport(); + $helper = new Helper(); $this->doNotValidate = !in_array($this->configPrefix, - Helper::getServerConfigurationPrefixes()); + $helper->getServerConfigurationPrefixes()); } public function __destruct() { diff --git a/apps/user_ldap/lib/helper.php b/apps/user_ldap/lib/helper.php index fa36e30417..7a96cfa36c 100644 --- a/apps/user_ldap/lib/helper.php +++ b/apps/user_ldap/lib/helper.php @@ -45,7 +45,7 @@ class Helper { * except the default (first) server shall be connected to. * */ - static public function getServerConfigurationPrefixes($activeConfigurations = false) { + public function getServerConfigurationPrefixes($activeConfigurations = false) { $referenceConfigkey = 'ldap_configuration_active'; $sql = ' @@ -83,7 +83,7 @@ class Helper { * @return array an array with configprefix as keys * */ - static public function getServerConfigurationHosts() { + public function getServerConfigurationHosts() { $referenceConfigkey = 'ldap_host'; $query = ' @@ -110,7 +110,7 @@ class Helper { * @param string $prefix the configuration prefix of the config to delete * @return bool true on success, false otherwise */ - static public function deleteServerConfiguration($prefix) { + public function deleteServerConfiguration($prefix) { if(!in_array($prefix, self::getServerConfigurationPrefixes())) { return false; } @@ -141,12 +141,28 @@ class Helper { return true; } + /** + * checks whether there is one or more disabled LDAP configurations + * @throws \Exception + * @return bool + */ + public function haveDisabledConfigurations() { + $all = $this->getServerConfigurationPrefixes(false); + $active = $this->getServerConfigurationPrefixes(true); + + if(!is_array($all) || !is_array($active)) { + throw new \Exception('Unexpected Return Value'); + } + + return count($all) !== count($active) || count($all) === 0; + } + /** * extracts the domain from a given URL * @param string $url the URL * @return string|false domain as string on success, false otherwise */ - static public function getDomainFromURL($url) { + public function getDomainFromURL($url) { $uinfo = parse_url($url); if(!is_array($uinfo)) { return false; diff --git a/apps/user_ldap/lib/jobs.php b/apps/user_ldap/lib/jobs.php index 47e536f8f6..30f09cdc8f 100644 --- a/apps/user_ldap/lib/jobs.php +++ b/apps/user_ldap/lib/jobs.php @@ -156,7 +156,8 @@ class Jobs extends \OC\BackgroundJob\TimedJob { if(!is_null(self::$groupBE)) { return self::$groupBE; } - $configPrefixes = Helper::getServerConfigurationPrefixes(true); + $helper = new Helper(); + $configPrefixes = $helper->getServerConfigurationPrefixes(true); $ldapWrapper = new LDAP(); if(count($configPrefixes) === 1) { //avoid the proxy when there is only one LDAP server configured diff --git a/apps/user_ldap/lib/jobs/cleanup.php b/apps/user_ldap/lib/jobs/cleanup.php new file mode 100644 index 0000000000..56fb296609 --- /dev/null +++ b/apps/user_ldap/lib/jobs/cleanup.php @@ -0,0 +1,227 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCA\User_LDAP\Jobs; + +use \OCA\user_ldap\User_Proxy; +use \OCA\user_ldap\lib\Helper; +use \OCA\user_ldap\lib\LDAP; + +/** + * Class CleanUp + * + * a Background job to clean up deleted users + * + * @package OCA\user_ldap\lib; + */ +class CleanUp extends \OC\BackgroundJob\TimedJob { + /** + * @var int $limit amount of users that should be checked per run + */ + protected $limit = 50; + + /** + * @var \OCP\UserInterface $userBackend + */ + protected $userBackend; + + /** + * @var \OCP\IConfig $ocConfig + */ + protected $ocConfig; + + /** + * @var \OCP\IDBConnection $db + */ + protected $db; + + /** + * @var Helper $ldapHelper + */ + protected $ldapHelper; + + /** + * @var int $defaultIntervalMin default interval in minutes + */ + protected $defaultIntervalMin = 51; + + public function __construct() { + $minutes = \OC::$server->getConfig()->getSystemValue( + 'ldapUserCleanupInterval', strval($this->defaultIntervalMin)); + $this->setInterval(intval($minutes) * 60); + } + + /** + * assigns the instances passed to run() to the class properties + * @param array $arguments + */ + public function setArguments($arguments) { + //Dependency Injection is not possible, because the constructor will + //only get values that are serialized to JSON. I.e. whatever we would + //pass in app.php we do add here, except something else is passed e.g. + //in tests. + + if(isset($arguments['helper'])) { + $this->ldapHelper = $arguments['helper']; + } else { + $this->ldapHelper = new Helper(); + } + + if(isset($arguments['userBackend'])) { + $this->userBackend = $arguments['userBackend']; + } else { + $this->userBackend = new User_Proxy( + $this->ldapHelper->getServerConfigurationPrefixes(true), + new LDAP() + ); + } + + if(isset($arguments['ocConfig'])) { + $this->ocConfig = $arguments['ocConfig']; + } else { + $this->ocConfig = \OC::$server->getConfig(); + } + + if(isset($arguments['db'])) { + $this->db = $arguments['db']; + } else { + $this->db = \OC::$server->getDatabaseConnection(); + } + } + + /** + * makes the background job do its work + * @param array $argument + */ + public function run($argument) { + $this->setArguments($argument); + + if(!$this->isCleanUpAllowed()) { + return; + } + $users = $this->getMappedUsers($this->limit, $this->getOffset()); + if(!is_array($users)) { + //something wrong? Let's start from the beginning next time and + //abort + $this->setOffset(true); + return; + } + $resetOffset = $this->isOffsetResetNecessary(count($users)); + $this->checkUsers($users); + $this->setOffset($resetOffset); + } + + /** + * checks whether next run should start at 0 again + * @param int $resultCount + * @return bool + */ + public function isOffsetResetNecessary($resultCount) { + return ($resultCount < $this->limit) ? true : false; + } + + /** + * checks whether cleaning up LDAP users is allowed + * @return bool + */ + public function isCleanUpAllowed() { + try { + if($this->ldapHelper->haveDisabledConfigurations()) { + return false; + } + } catch (\Exception $e) { + return false; + } + + $enabled = $this->isCleanUpEnabled(); + + return $enabled; + } + + /** + * checks whether clean up is enabled by configuration + * @return bool + */ + private function isCleanUpEnabled() { + return (bool)$this->ocConfig->getSystemValue( + 'ldapUserCleanupInterval', strval($this->defaultIntervalMin)); + } + + /** + * checks users whether they are still existing + * @param array $users result from getMappedUsers() + */ + private function checkUsers($users) { + foreach($users as $user) { + $this->checkUser($user); + } + } + + /** + * checks whether a user is still existing in LDAP + * @param string[] $user + */ + private function checkUser($user) { + if($this->userBackend->userExistsOnLDAP($user['name'])) { + //still available, all good + return; + } + + // TODO FIXME consolidate next line in DeletedUsersIndex + // (impractical now, because of class dependencies) + $this->ocConfig->setUserValue($user['name'], 'user_ldap', 'isDeleted', '1'); + } + + /** + * returns a batch of users from the mappings table + * @param int $limit + * @param int $offset + * @return array + */ + public function getMappedUsers($limit, $offset) { + $query = $this->db->prepare(' + SELECT + `ldap_dn` AS `dn`, + `owncloud_name` AS `name`, + `directory_uuid` AS `uuid` + FROM `*PREFIX*ldap_user_mapping`', + $limit, + $offset + ); + + $query->execute(); + return $query->fetchAll(); + } + + /** + * gets the offset to fetch users from the mappings table + * @return int + */ + private function getOffset() { + return $this->ocConfig->getAppValue('user_ldap', 'cleanUpJobOffset', 0); + } + + /** + * sets the new offset for the next run + * @param bool $reset whether the offset should be set to 0 + */ + public function setOffset($reset = false) { + $newOffset = $reset ? 0 : + $this->getOffset() + $this->limit; + $this->ocConfig->setAppValue('user_ldap', 'cleanUpJobOffset', $newOffset); + } + + /** + * returns the chunk size (limit in DB speak) + * @return int + */ + public function getChunkSize() { + return $this->limit; + } + +} diff --git a/apps/user_ldap/lib/user/deletedusersindex.php b/apps/user_ldap/lib/user/deletedusersindex.php new file mode 100644 index 0000000000..0d8bacffe9 --- /dev/null +++ b/apps/user_ldap/lib/user/deletedusersindex.php @@ -0,0 +1,125 @@ + + * + * 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 . + * + */ + +namespace OCA\user_ldap\lib\user; + +use OCA\user_ldap\lib\user\OfflineUser; +use OCA\user_ldap\lib\Access; + +/** + * Class DeletedUsersIndex + * @package OCA\User_LDAP + */ +class DeletedUsersIndex { + /** + * @var \OC\Preferences $preferences + */ + protected $preferences; + + /** + * @var \OCP\IDBConnection $db + */ + protected $db; + + /** + * @var \OCA\user_ldap\lib\Access $access + */ + protected $access; + + /** + * @var int $limit + */ + protected $limit = 10; + + /** + * @var array $deletedUsers + */ + protected $deletedUsers = false; + + public function __construct(\OC\Preferences $preferences, \OCP\IDBConnection $db, Access $access) { + $this->preferences = $preferences; + $this->db = $db; + $this->access = $access; + } + + /** + * returns key to be used against $this->deletedUsers + * @param int $limit + * @param int $offset + * @return string + */ + private function getDeletedUsersCacheKey($limit, $offset) { + return strval($limit) . '.' . strval($offset); + } + + /** + * reads LDAP users marked as deleted from the database + * @param int $offset + * @return OCA\user_ldap\lib\user\OfflineUser[] + */ + private function fetchDeletedUsers($offset) { + $deletedUsers = $this->preferences->getUsersForValue( + 'user_ldap', 'isDeleted', '1', $this->limit, $offset); + $key = $this->getDeletedUsersCacheKey($this->limit, $offset); + + $userObjects = array(); + foreach($deletedUsers as $user) { + $userObjects[] = new OfflineUser($user, $this->preferences, $this->db, $this->access); + } + + $this->deletedUsers[$key] = $userObjects; + if(count($userObjects) > 0) { + $this->hasUsers(); + } + return $this->deletedUsers[$key]; + } + + /** + * returns all LDAP users that are marked as deleted + * @param int|null $offset + * @return OCA\user_ldap\lib\user\OfflineUser[] + */ + public function getUsers($offset = null) { + $key = $this->getDeletedUsersCacheKey($this->limit, $offset); + if(is_array($this->deletedUsers) && isset($this->deletedUsers[$key])) { + return $this->deletedUsers[$key]; + } + return $this->fetchDeletedUsers($offset); + } + + /** + * whether at least one user was detected as deleted + * @return bool + */ + public function hasUsers() { + if($this->deletedUsers === false) { + $this->fetchDeletedUsers(0); + } + foreach($this->deletedUsers as $batch) { + if(count($batch) > 0) { + return true; + } + } + return false; + } +} diff --git a/apps/user_ldap/lib/user/iusertools.php b/apps/user_ldap/lib/user/iusertools.php index bbc678153d..ffdef62410 100644 --- a/apps/user_ldap/lib/user/iusertools.php +++ b/apps/user_ldap/lib/user/iusertools.php @@ -39,4 +39,7 @@ interface IUserTools { public function username2dn($name); + //temporary hack for LDAP user cleanup, will be removed in OC 8. + public function ocname2dn($name, $isUser); + } diff --git a/apps/user_ldap/lib/user/manager.php b/apps/user_ldap/lib/user/manager.php index 0ed3d09c48..1bcc9b96d8 100644 --- a/apps/user_ldap/lib/user/manager.php +++ b/apps/user_ldap/lib/user/manager.php @@ -27,6 +27,7 @@ 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; +use OCA\user_ldap\lib\user\OfflineUser; /** * Manager @@ -60,7 +61,9 @@ class Manager { */ protected $avatarManager; /** - * @var string[][] + * array['byDN'] \OCA\user_ldap\lib\User[] + * ['byUid'] \OCA\user_ldap\lib\User[] + * @var array $users */ protected $users = array( 'byDN' => array(), @@ -130,10 +133,46 @@ class Manager { } } + /** + * Checks whether the specified user is marked as deleted + * @param string $id the ownCloud user name + * @return bool + */ + public function isDeletedUser($id) { + $isDeleted = $this->ocConfig->getUserValue( + $id, 'user_ldap', 'isDeleted', 0); + return intval($isDeleted) === 1; + } + + /** + * creates and returns an instance of OfflineUser for the specified user + * @param string $id + * @return \OCA\user_ldap\lib\user\OfflineUser + */ + public function getDeletedUser($id) { + return new OfflineUser( + $id, + new \OC\Preferences(\OC_DB::getConnection()), + \OC::$server->getDatabaseConnection(), + $this->access); + } + + protected function createInstancyByUserName($id) { + //most likely a uid. Check whether it is a deleted user + if($this->isDeletedUser($id)) { + return $this->getDeletedUser($id); + } + $dn = $this->access->username2dn($id); + if($dn !== false) { + return $this->createAndCache($dn, $id); + } + throw new \Exception('Could not create User instance'); + } + /** * @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 + * @return \OCA\user_ldap\lib\user\User|\OCA\user_ldap\lib\user\OfflineUser|null */ public function get($id) { $this->checkAccess(); @@ -143,25 +182,19 @@ class Manager { return $this->users['byUid'][$id]; } - if(!$this->access->stringResemblesDN($id) ) { - //most likely a uid - $dn = $this->access->username2dn($id); - if($dn !== false) { - return $this->createAndCache($dn, $id); - } - } else { - //so it's a DN + if($this->access->stringResemblesDN($id) ) { $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); + + try { + $user = $this->createInstancyByUserName($id); + return $user; + } catch (\Exception $e) { + return null; } - return null; } } diff --git a/apps/user_ldap/lib/user/offlineuser.php b/apps/user_ldap/lib/user/offlineuser.php new file mode 100644 index 0000000000..7750348a28 --- /dev/null +++ b/apps/user_ldap/lib/user/offlineuser.php @@ -0,0 +1,217 @@ +. + * + */ + +namespace OCA\user_ldap\lib\user; + +use OCA\user_ldap\lib\Access; + +class OfflineUser { + /** + * @var string $ocName + */ + protected $ocName; + /** + * @var string $dn + */ + protected $dn; + /** + * @var string $uid the UID as provided by LDAP + */ + protected $uid; + /** + * @var string $displayName + */ + protected $displayName; + /** + * @var string $homePath + */ + protected $homePath; + /** + * @var string $lastLogin the timestamp of the last login + */ + protected $lastLogin; + /** + * @var string $email + */ + protected $email; + /** + * @var bool $hasActiveShares + */ + protected $hasActiveShares; + /** + * @var \OC\Preferences $preferences + */ + protected $preferences; + /** + * @var \OCP\IDBConnection $db + */ + protected $db; + /** + * @var \OCA\user_ldap\lib\Access + */ + protected $access; + + public function __construct($ocName, \OC\Preferences $preferences, \OCP\IDBConnection $db, Access $access) { + $this->ocName = $ocName; + $this->preferences = $preferences; + $this->db = $db; + $this->access = $access; + $this->fetchDetails(); + } + + /** + * exports the user details in an assoc array + * @return array + */ + public function export() { + $data = array(); + $data['ocName'] = $this->getOCName(); + $data['dn'] = $this->getDN(); + $data['uid'] = $this->getUID(); + $data['displayName'] = $this->getDisplayName(); + $data['homePath'] = $this->getHomePath(); + $data['lastLogin'] = $this->getLastLogin(); + $data['email'] = $this->getEmail(); + $data['hasActiveShares'] = $this->getHasActiveShares(); + + return $data; + } + + /** + * getter for ownCloud internal name + * @return string + */ + public function getOCName() { + return $this->ocName; + } + + /** + * getter for LDAP uid + * @return string + */ + public function getUID() { + return $this->uid; + } + + /** + * getter for LDAP DN + * @return string + */ + public function getDN() { + return $this->dn; + } + + /** + * getter for display name + * @return string + */ + public function getDisplayName() { + return $this->displayName; + } + + /** + * getter for email + * @return string + */ + public function getEmail() { + return $this->email; + } + + /** + * getter for home directory path + * @return string + */ + public function getHomePath() { + return $this->homePath; + } + + /** + * getter for the last login timestamp + * @return int + */ + public function getLastLogin() { + return intval($this->lastLogin); + } + + /** + * getter for having active shares + * @return bool + */ + public function getHasActiveShares() { + return $this->hasActiveShares; + } + + /** + * reads the user details + */ + protected function fetchDetails() { + $properties = array ( + 'displayName' => 'user_ldap', + 'uid' => 'user_ldap', + 'homePath' => 'user_ldap', + 'email' => 'settings', + 'lastLogin' => 'login' + ); + foreach($properties as $property => $app) { + $this->$property = $this->preferences->getValue($this->ocName, $app, $property, ''); + } + + $dn = $this->access->ocname2dn($this->ocName, true); + $this->dn = ($dn !== false) ? $dn : ''; + + $this->determineShares(); + } + + + /** + * finds out whether the user has active shares. The result is stored in + * $this->hasActiveShares + */ + protected function determineShares() { + $query = $this->db->prepare(' + SELECT COUNT(`uid_owner`) + FROM `*PREFIX*share` + WHERE `uid_owner` = ? + ', 1); + $query->execute(array($this->ocName)); + $sResult = $query->fetchColumn(0); + if(intval($sResult) === 1) { + $this->hasActiveShares = true; + return; + } + + $query = $this->db->prepare(' + SELECT COUNT(`owner`) + FROM `*PREFIX*share_external` + WHERE `owner` = ? + ', 1); + $query->execute(array($this->ocName)); + $sResult = $query->fetchColumn(0); + if(intval($sResult) === 1) { + $this->hasActiveShares = true; + return; + } + + $this->hasActiveShares = false; + } +} diff --git a/apps/user_ldap/lib/user/user.php b/apps/user_ldap/lib/user/user.php index d4d2294307..c81fb25b54 100644 --- a/apps/user_ldap/lib/user/user.php +++ b/apps/user_ldap/lib/user/user.php @@ -212,6 +212,31 @@ class User { return true; } + /** + * Stores a key-value pair in relation to this user + * @param string $key + * @param string $value + */ + private function store($key, $value) { + $this->config->setUserValue($this->uid, 'user_ldap', $key, $value); + } + + /** + * Stores the display name in the databae + * @param string $displayName + */ + public function storeDisplayName($displayName) { + $this->store('displayName', $displayName); + } + + /** + * Stores the LDAP Username in the Database + * @param string $userName + */ + public function storeLDAPUserName($userName) { + $this->store('uid', $userName); + } + /** * @brief checks whether an update method specified by feature was run * already. If not, it will marked like this, because it is expected that diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php index 578a920f00..2e4507a258 100644 --- a/apps/user_ldap/lib/wizard.php +++ b/apps/user_ldap/lib/wizard.php @@ -659,7 +659,8 @@ class Wizard extends LDAPUtility { //this did not help :( //Let's see whether we can parse the Host URL and convert the domain to //a base DN - $domain = Helper::getDomainFromURL($this->configuration->ldapHost); + $helper = new Helper(); + $domain = $helper->getDomainFromURL($this->configuration->ldapHost); if(!$domain) { return false; } diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php index 5527cf2c6d..a19ec0bda6 100644 --- a/apps/user_ldap/settings.php +++ b/apps/user_ldap/settings.php @@ -35,8 +35,9 @@ OCP\Util::addStyle('user_ldap', 'settings'); // fill template $tmpl = new OCP\Template('user_ldap', 'settings'); -$prefixes = \OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes(); -$hosts = \OCA\user_ldap\lib\Helper::getServerConfigurationHosts(); +$helper = new \OCA\user_ldap\lib\Helper(); +$prefixes = $helper->getServerConfigurationPrefixes(); +$hosts = $helper->getServerConfigurationHosts(); $wizardHtml = ''; $toc = array(); diff --git a/apps/user_ldap/tests/jobs/cleanup.php b/apps/user_ldap/tests/jobs/cleanup.php new file mode 100644 index 0000000000..3aa9a4a43c --- /dev/null +++ b/apps/user_ldap/tests/jobs/cleanup.php @@ -0,0 +1,155 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OCA\user_ldap\tests; + +class Test_CleanUp extends \PHPUnit_Framework_TestCase { + public function getMocks() { + $mocks = array(); + $mocks['userBackend'] = + $this->getMockBuilder('\OCA\user_ldap\User_Proxy') + ->disableOriginalConstructor() + ->getMock(); + $mocks['ocConfig'] = $this->getMock('\OCP\IConfig'); + $mocks['db'] = $this->getMock('\OCP\IDBConnection'); + $mocks['helper'] = $this->getMock('\OCA\user_ldap\lib\Helper'); + + return $mocks; + } + + /** + * clean up job must not run when there are disabled configurations + */ + public function test_runNotAllowedByDisabledConfigurations() { + $args = $this->getMocks(); + $args['helper']->expects($this->once()) + ->method('haveDisabledConfigurations') + ->will($this->returnValue(true) ); + + $args['ocConfig']->expects($this->never()) + ->method('getSystemValue'); + + $bgJob = new \OCA\User_LDAP\Jobs\CleanUp(); + $bgJob->setArguments($args); + + $result = $bgJob->isCleanUpAllowed(); + $this->assertSame(false, $result); + } + + /** + * clean up job must not run when LDAP Helper is broken i.e. + * returning unexpected results + */ + public function test_runNotAllowedByBrokenHelper() { + $args = $this->getMocks(); + $args['helper']->expects($this->once()) + ->method('haveDisabledConfigurations') + ->will($this->throwException(new \Exception())); + + $args['ocConfig']->expects($this->never()) + ->method('getSystemValue'); + + $bgJob = new \OCA\User_LDAP\Jobs\CleanUp(); + $bgJob->setArguments($args); + + $result = $bgJob->isCleanUpAllowed(); + $this->assertSame(false, $result); + } + + /** + * clean up job must not run when it is not enabled + */ + public function test_runNotAllowedBySysConfig() { + $args = $this->getMocks(); + $args['helper']->expects($this->once()) + ->method('haveDisabledConfigurations') + ->will($this->returnValue(false)); + + $args['ocConfig']->expects($this->once()) + ->method('getSystemValue') + ->will($this->returnValue(false)); + + $bgJob = new \OCA\User_LDAP\Jobs\CleanUp(); + $bgJob->setArguments($args); + + $result = $bgJob->isCleanUpAllowed(); + $this->assertSame(false, $result); + } + + /** + * clean up job is allowed to run + */ + public function test_runIsAllowed() { + $args = $this->getMocks(); + $args['helper']->expects($this->once()) + ->method('haveDisabledConfigurations') + ->will($this->returnValue(false)); + + $args['ocConfig']->expects($this->once()) + ->method('getSystemValue') + ->will($this->returnValue(true)); + + $bgJob = new \OCA\User_LDAP\Jobs\CleanUp(); + $bgJob->setArguments($args); + + $result = $bgJob->isCleanUpAllowed(); + $this->assertSame(true, $result); + } + + /** + * test whether sql is OK + */ + public function test_getMappedUsers() { + $args = $this->getMocks(); + + $bgJob = new \OCA\User_LDAP\Jobs\CleanUp(); + $bgJob->setArguments($args); + + if(version_compare(\PHPUnit_Runner_Version::id(), '3.8', '<')) { + //otherwise we run into + //https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 + $this->markTestIncomplete(); + } + + $stmt = $this->getMock('\Doctrine\DBAL\Driver\Statement'); + + $args['db']->expects($this->once()) + ->method('prepare') + ->will($this->returnValue($stmt)); + + $bgJob->getMappedUsers(0, $bgJob->getChunkSize()); + } + + /** + * check whether offset will be reset when it needs to + */ + public function test_OffsetResetIsNecessary() { + $args = $this->getMocks(); + + $bgJob = new \OCA\User_LDAP\Jobs\CleanUp(); + $bgJob->setArguments($args); + + $result = $bgJob->isOffsetResetNecessary($bgJob->getChunkSize() - 1); + $this->assertSame(true, $result); + } + + /** + * make sure offset is not reset when it is not due + */ + public function test_OffsetResetIsNotNecessary() { + $args = $this->getMocks(); + + $bgJob = new \OCA\User_LDAP\Jobs\CleanUp(); + $bgJob->setArguments($args); + + $result = $bgJob->isOffsetResetNecessary($bgJob->getChunkSize()); + $this->assertSame(false, $result); + } + +} + diff --git a/apps/user_ldap/tests/user/manager.php b/apps/user_ldap/tests/user/manager.php index b3e52084db..fb47f60539 100644 --- a/apps/user_ldap/tests/user/manager.php +++ b/apps/user_ldap/tests/user/manager.php @@ -183,7 +183,7 @@ class Test_User_Manager extends \Test\TestCase { $access->expects($this->never()) ->method('dn2username'); - $access->expects($this->exactly(2)) + $access->expects($this->exactly(1)) ->method('username2dn') ->with($this->equalTo($uid)) ->will($this->returnValue(false)); diff --git a/apps/user_ldap/tests/user_ldap.php b/apps/user_ldap/tests/user_ldap.php index 33cec0247b..876b3d0903 100644 --- a/apps/user_ldap/tests/user_ldap.php +++ b/apps/user_ldap/tests/user_ldap.php @@ -123,7 +123,7 @@ class Test_User_Ldap_Direct extends \Test\TestCase { ->method('fetchListOfUsers') ->will($this->returnCallback(function($filter) { if($filter === 'roland') { - return array('dnOfRoland,dc=test'); + return array(array('dn' => 'dnOfRoland,dc=test')); } return array(); })); @@ -230,6 +230,24 @@ class Test_User_Ldap_Direct extends \Test\TestCase { $this->assertFalse($result); } + public function testDeleteUserCancel() { + $access = $this->getAccessMock(); + $backend = new UserLDAP($access); + $result = $backend->deleteUser('notme'); + $this->assertFalse($result); + } + + public function testDeleteUserSuccess() { + $access = $this->getAccessMock(); + $backend = new UserLDAP($access); + + $pref = \OC::$server->getConfig(); + $pref->setUserValue('jeremy', 'user_ldap', 'isDeleted', 1); + + $result = $backend->deleteUser('jeremy'); + $this->assertTrue($result); + } + /** * Prepares the Access mock for getUsers tests * @param \OCA\user_ldap\lib\Access $access mock diff --git a/apps/user_ldap/user_ldap.php b/apps/user_ldap/user_ldap.php index 482715b368..2274e4156c 100644 --- a/apps/user_ldap/user_ldap.php +++ b/apps/user_ldap/user_ldap.php @@ -26,8 +26,15 @@ namespace OCA\user_ldap; use OCA\user_ldap\lib\BackendUtility; +use OCA\user_ldap\lib\user\OfflineUser; +use OCA\User_LDAP\lib\User\User; class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserInterface { + /** + * @var string[] $homesToKill + */ + protected $homesToKill = array(); + /** * checks whether the user is allowed to change his avatar in ownCloud * @param string $uid the ownCloud user name @@ -35,7 +42,7 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn */ public function canChangeAvatar($uid) { $user = $this->access->userManager->get($uid); - if(is_null($user)) { + if(!$user instanceof User) { return false; } if($user->getAvatarImage() === false) { @@ -57,15 +64,17 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn $uid = $this->access->escapeFilterPart($uid); //find out dn of the user name + $attrs = array($this->access->connection->ldapUserDisplayName, 'dn', + 'uid', 'samaccountname'); $filter = \OCP\Util::mb_str_replace( '%uid', $uid, $this->access->connection->ldapLoginFilter, 'UTF-8'); - $ldap_users = $this->access->fetchListOfUsers($filter, 'dn'); - if(count($ldap_users) < 1) { + $users = $this->access->fetchListOfUsers($filter, $attrs); + if(count($users) < 1) { return false; } - $dn = $ldap_users[0]; + $dn = $users[0]['dn']; $user = $this->access->userManager->get($dn); - if(is_null($user)) { + if(!$user instanceof User) { \OCP\Util::writeLog('user_ldap', 'LDAP Login: Could not get user object for DN ' . $dn . '. Maybe the LDAP entry has no set display name attribute?', @@ -79,6 +88,15 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn } $user->markLogin(); + if(isset($users[0][$this->access->connection->ldapUserDisplayName])) { + $dpn = $users[0][$this->access->connection->ldapUserDisplayName]; + $user->storeDisplayName($dpn); + } + if(isset($users[0]['uid'])) { + $user->storeLDAPUserName($users[0]['uid']); + } else if(isset($users[0]['samaccountname'])) { + $user->storeLDAPUserName($users[0]['samaccountname']); + } return $user->getUsername(); } @@ -127,6 +145,33 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn return $ldap_users; } + /** + * checks whether a user is still available on LDAP + * @param string|\OCA\User_LDAP\lib\user\User $user either the ownCloud user + * name or an instance of that user + * @return bool + */ + public function userExistsOnLDAP($user) { + if(is_string($user)) { + $user = $this->access->userManager->get($user); + } + if(!$user instanceof User) { + return false; + } + + $dn = $user->getDN(); + //check if user really still exists by reading its entry + if(!is_array($this->access->readAttribute($dn, ''))) { + $lcr = $this->access->connection->getConnectionResource(); + if(is_null($lcr)) { + throw new \Exception('No LDAP Connection to server ' . $this->access->connection->ldapHost); + } + return false; + } + + return true; + } + /** * check if a user exists * @param string $uid the username @@ -143,36 +188,56 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn $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 '. - $this->access->connection->ldapHost, \OCP\Util::DEBUG); - $this->access->connection->writeToCache('userExists'.$uid, false); - return false; + } else if($user instanceof OfflineUser) { + //express check for users marked as deleted. Returning true is + //necessary for cleanup + return true; } - $this->access->connection->writeToCache('userExists'.$uid, true); - $user->update(); - return true; + try { + $result = $this->userExistsOnLDAP($user); + $this->access->connection->writeToCache('userExists'.$uid, $result); + if($result === true) { + $user->update(); + } + return $result; + } catch (\Exception $e) { + \OCP\Util::writeLog('user_ldap', $e->getMessage(), \OCP\Util::WARN); + return false; + } } /** - * delete a user + * returns whether a user was deleted in LDAP + * * @param string $uid The username of the user to delete * @return bool - * - * Deletes a user */ public function deleteUser($uid) { - return false; + $pref = \OC::$server->getConfig(); + $marked = $pref->getUserValue($uid, 'user_ldap', 'isDeleted', 0); + if(intval($marked) === 0) { + \OC::$server->getLogger()->notice( + 'User '.$uid . ' is not marked as deleted, not cleaning up.', + array('app' => 'user_ldap')); + return false; + } + \OC::$server->getLogger()->info('Cleaning up after user ' . $uid, + array('app' => 'user_ldap')); + + //Get Home Directory out of user preferences so we can return it later, + //necessary for removing directories as done by OC_User. + $home = $pref->getUserValue($uid, 'user_ldap', 'homePath', ''); + $this->homesToKill[$uid] = $home; + $this->access->unmapUser($uid); + + return true; } /** * get the user's home directory * @param string $uid the username - * @return boolean + * @return string|bool */ public function getHome($uid) { // user Exists check required as it is not done in user proxy! @@ -180,10 +245,16 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn return false; } + if(isset($this->homesToKill[$uid]) && !empty($this->homesToKill[$uid])) { + //a deleted user who needs some clean up + return $this->homesToKill[$uid]; + } + $cacheKey = 'getHome'.$uid; if($this->access->connection->isCached($cacheKey)) { return $this->access->connection->getFromCache($cacheKey); } + $pref = \OC::$server->getConfig(); if(strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0) { $attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:')); $homedir = $this->access->readAttribute( @@ -203,12 +274,17 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn \OC::$SERVERROOT.'/data' ) . '/' . $homedir[0]; } $this->access->connection->writeToCache($cacheKey, $homedir); + //we need it to store it in the DB as well in case a user gets + //deleted so we can clean up afterwards + $pref->setUserValue($uid, 'user_ldap', 'homePath', $homedir); + //TODO: if home directory changes, the old one needs to be removed. return $homedir; } } //false will apply default behaviour as defined and done by OC_User $this->access->connection->writeToCache($cacheKey, false); + $pref->setUserValue($uid, 'user_ldap', 'homePath', ''); return false; } diff --git a/apps/user_ldap/user_proxy.php b/apps/user_ldap/user_proxy.php index 6414a04807..77caa84ecd 100644 --- a/apps/user_ldap/user_proxy.php +++ b/apps/user_ldap/user_proxy.php @@ -24,6 +24,7 @@ namespace OCA\user_ldap; use OCA\user_ldap\lib\ILDAPWrapper; +use OCA\User_LDAP\lib\User\User; class User_Proxy extends lib\Proxy implements \OCP\IUserBackend, \OCP\UserInterface { private $backends = array(); @@ -152,6 +153,17 @@ class User_Proxy extends lib\Proxy implements \OCP\IUserBackend, \OCP\UserInterf return $this->handleRequest($uid, 'userExists', array($uid)); } + /** + * check if a user exists on LDAP + * @param string|OCA\User_LDAP\lib\User\User $user either the ownCloud user + * name or an instance of that user + * @return boolean + */ + public function userExistsOnLDAP($user) { + $id = ($user instanceof User) ? $user->getUsername() : $user; + return $this->handleRequest($id, 'userExistsOnLDAP', array($user)); + } + /** * Check if the password is correct * @param string $uid The username @@ -217,7 +229,7 @@ class User_Proxy extends lib\Proxy implements \OCP\IUserBackend, \OCP\UserInterf * Deletes a user */ public function deleteUser($uid) { - return false; + return $this->handleRequest($uid, 'deleteUser', array($uid)); } /** diff --git a/config/config.sample.php b/config/config.sample.php index 35e3f6ce5f..e5b8344ad3 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -80,7 +80,7 @@ $CONFIG = array( /** * Where user files are stored; this defaults to ``data/`` in the ownCloud - * directory. The SQLite database is also stored here, when you use SQLite. (SQLite is + * directory. The SQLite database is also stored here, when you use SQLite. (SQLite is * available only in ownCloud Community Edition) */ 'datadirectory' => '/var/www/owncloud/data', @@ -665,6 +665,20 @@ $CONFIG = array( 'OC\Preview\MarkDown' ), +/** + * LDAP + * + * Global settings used by LDAP User and Group Backend + */ + +/** + * defines the interval in minutes for the background job that checks user + * existance and marks them as ready to be cleaned up. The number is always + * minutes. Setting it to 0 disables the feature. + * See command line (occ) methods ldap:show-remnants and user:delete + */ +'ldapUserCleanupInterval' => 51, + /** * Maintenance diff --git a/core/command/user/delete.php b/core/command/user/delete.php new file mode 100644 index 0000000000..f64b40e492 --- /dev/null +++ b/core/command/user/delete.php @@ -0,0 +1,36 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Core\Command\User; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputArgument; + +class Delete extends Command { + protected function configure() { + $this + ->setName('user:delete') + ->setDescription('deletes the specified user') + ->addArgument( + 'uid', + InputArgument::REQUIRED, + 'the username' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $wasSuccessful = \OC_User::deleteUser($input->getArgument('uid')); + if($wasSuccessful === true) { + $output->writeln('The specified user was deleted'); + return; + } + $output->writeln('The specified could not be deleted. Please check the logs.'); + } +} diff --git a/core/register_command.php b/core/register_command.php index 8f79473ced..690e9879c4 100644 --- a/core/register_command.php +++ b/core/register_command.php @@ -22,5 +22,6 @@ $application->add(new OC\Core\Command\Maintenance\Repair($repair, \OC::$server-> $application->add(new OC\Core\Command\User\Report()); $application->add(new OC\Core\Command\User\ResetPassword(\OC::$server->getUserManager())); $application->add(new OC\Core\Command\User\LastSeen()); +$application->add(new OC\Core\Command\User\Delete()); $application->add(new OC\Core\Command\L10n\CreateJs()); diff --git a/lib/private/preferences.php b/lib/private/preferences.php index cd4a9fd1c1..1784d37226 100644 --- a/lib/private/preferences.php +++ b/lib/private/preferences.php @@ -137,10 +137,12 @@ class Preferences { * @param string $app * @param string $key * @param string $value + * @param int|null $limit + * @param int|null $offset * @return array * @deprecated use getUsersForUserValue of \OCP\IConfig instead */ - public function getUsersForValue($app, $key, $value) { + public function getUsersForValue($app, $key, $value, $limit = null, $offset = null) { return $this->config->getUsersForUserValue($app, $key, $value); }