diff options
Diffstat (limited to 'apps/user_ldap/lib')
-rw-r--r-- | apps/user_ldap/lib/Access.php (renamed from apps/user_ldap/lib/access.php) | 20 | ||||
-rw-r--r-- | apps/user_ldap/lib/BackendUtility.php (renamed from apps/user_ldap/lib/backendutility.php) | 2 | ||||
-rw-r--r-- | apps/user_ldap/lib/Command/CheckUser.php | 134 | ||||
-rw-r--r-- | apps/user_ldap/lib/Command/CreateEmptyConfig.php | 73 | ||||
-rw-r--r-- | apps/user_ldap/lib/Command/DeleteConfig.php | 68 | ||||
-rw-r--r-- | apps/user_ldap/lib/Command/Search.php | 127 | ||||
-rw-r--r-- | apps/user_ldap/lib/Command/SetConfig.php | 84 | ||||
-rw-r--r-- | apps/user_ldap/lib/Command/ShowConfig.php | 108 | ||||
-rw-r--r-- | apps/user_ldap/lib/Command/ShowRemnants.php | 92 | ||||
-rw-r--r-- | apps/user_ldap/lib/Command/TestConfig.php | 90 | ||||
-rw-r--r-- | apps/user_ldap/lib/Configuration.php (renamed from apps/user_ldap/lib/configuration.php) | 2 | ||||
-rw-r--r-- | apps/user_ldap/lib/Connection.php (renamed from apps/user_ldap/lib/connection.php) | 2 | ||||
-rw-r--r-- | apps/user_ldap/lib/FilesystemHelper.php (renamed from apps/user_ldap/lib/filesystemhelper.php) | 2 | ||||
-rw-r--r-- | apps/user_ldap/lib/Group_LDAP.php | 899 | ||||
-rw-r--r-- | apps/user_ldap/lib/Group_Proxy.php | 197 | ||||
-rw-r--r-- | apps/user_ldap/lib/Helper.php (renamed from apps/user_ldap/lib/helper.php) | 4 | ||||
-rw-r--r-- | apps/user_ldap/lib/ILDAPWrapper.php (renamed from apps/user_ldap/lib/ildapwrapper.php) | 2 | ||||
-rw-r--r-- | apps/user_ldap/lib/Jobs/CleanUp.php (renamed from apps/user_ldap/lib/jobs/cleanup.php) | 14 | ||||
-rw-r--r-- | apps/user_ldap/lib/Jobs/UpdateGroups.php (renamed from apps/user_ldap/lib/jobs.php) | 23 | ||||
-rw-r--r-- | apps/user_ldap/lib/LDAP.php (renamed from apps/user_ldap/lib/ldap.php) | 4 | ||||
-rw-r--r-- | apps/user_ldap/lib/LDAPUtility.php (renamed from apps/user_ldap/lib/ldaputility.php) | 2 | ||||
-rw-r--r-- | apps/user_ldap/lib/LogWrapper.php (renamed from apps/user_ldap/lib/logwrapper.php) | 2 | ||||
-rw-r--r-- | apps/user_ldap/lib/Mapping/AbstractMapping.php (renamed from apps/user_ldap/lib/mapping/abstractmapping.php) | 0 | ||||
-rw-r--r-- | apps/user_ldap/lib/Mapping/GroupMapping.php (renamed from apps/user_ldap/lib/mapping/groupmapping.php) | 0 | ||||
-rw-r--r-- | apps/user_ldap/lib/Mapping/UserMapping.php (renamed from apps/user_ldap/lib/mapping/usermapping.php) | 0 | ||||
-rw-r--r-- | apps/user_ldap/lib/Proxy.php (renamed from apps/user_ldap/lib/proxy.php) | 3 | ||||
-rw-r--r-- | apps/user_ldap/lib/User/DeletedUsersIndex.php (renamed from apps/user_ldap/lib/user/deletedusersindex.php) | 6 | ||||
-rw-r--r-- | apps/user_ldap/lib/User/IUserTools.php (renamed from apps/user_ldap/lib/user/iusertools.php) | 2 | ||||
-rw-r--r-- | apps/user_ldap/lib/User/Manager.php (renamed from apps/user_ldap/lib/user/manager.php) | 25 | ||||
-rw-r--r-- | apps/user_ldap/lib/User/OfflineUser.php (renamed from apps/user_ldap/lib/user/offlineuser.php) | 2 | ||||
-rw-r--r-- | apps/user_ldap/lib/User/User.php (renamed from apps/user_ldap/lib/user/user.php) | 17 | ||||
-rw-r--r-- | apps/user_ldap/lib/User_LDAP.php | 459 | ||||
-rw-r--r-- | apps/user_ldap/lib/User_Proxy.php | 274 | ||||
-rw-r--r-- | apps/user_ldap/lib/Wizard.php (renamed from apps/user_ldap/lib/wizard.php) | 8 | ||||
-rw-r--r-- | apps/user_ldap/lib/WizardResult.php (renamed from apps/user_ldap/lib/wizardresult.php) | 2 |
35 files changed, 2678 insertions, 71 deletions
diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/Access.php index f92ded64797..6ec90cfff8f 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/Access.php @@ -35,18 +35,20 @@ * */ -namespace OCA\user_ldap\lib; +namespace OCA\User_LDAP; -use OCA\user_ldap\lib\user\OfflineUser; +use OCA\User_LDAP\User\IUserTools; +use OCA\User_LDAP\User\Manager; +use OCA\User_LDAP\User\OfflineUser; use OCA\User_LDAP\Mapping\AbstractMapping; /** * Class Access - * @package OCA\user_ldap\lib + * @package OCA\User_LDAP */ -class Access extends LDAPUtility implements user\IUserTools { +class Access extends LDAPUtility implements IUserTools { /** - * @var \OCA\user_ldap\lib\Connection + * @var \OCA\User_LDAP\Connection */ public $connection; public $userManager; @@ -75,7 +77,7 @@ class Access extends LDAPUtility implements user\IUserTools { protected $groupMapper; public function __construct(Connection $connection, ILDAPWrapper $ldap, - user\Manager $userManager) { + Manager $userManager) { parent::__construct($ldap); $this->connection = $connection; $this->userManager = $userManager; @@ -131,7 +133,7 @@ class Access extends LDAPUtility implements user\IUserTools { /** * returns the Connection instance - * @return \OCA\user_ldap\lib\Connection + * @return \OCA\User_LDAP\Connection */ public function getConnection() { return $this->connection; @@ -314,7 +316,7 @@ class Access extends LDAPUtility implements user\IUserTools { * @return string|false LDAP DN on success, otherwise false */ public function groupname2dn($name) { - return $this->groupMapper->getDNbyName($name); + return $this->groupMapper->getDNByName($name); } /** @@ -323,7 +325,7 @@ class Access extends LDAPUtility implements user\IUserTools { * @return string|false with the LDAP DN on success, otherwise false */ public function username2dn($name) { - $fdn = $this->userMapper->getDNbyName($name); + $fdn = $this->userMapper->getDNByName($name); //Check whether the DN belongs to the Base, to avoid issues on multi- //server setups diff --git a/apps/user_ldap/lib/backendutility.php b/apps/user_ldap/lib/BackendUtility.php index 87c1649cada..fc4efe28924 100644 --- a/apps/user_ldap/lib/backendutility.php +++ b/apps/user_ldap/lib/BackendUtility.php @@ -21,7 +21,7 @@ * */ -namespace OCA\user_ldap\lib; +namespace OCA\User_LDAP; abstract class BackendUtility { diff --git a/apps/user_ldap/lib/Command/CheckUser.php b/apps/user_ldap/lib/Command/CheckUser.php new file mode 100644 index 00000000000..43de421de6a --- /dev/null +++ b/apps/user_ldap/lib/Command/CheckUser.php @@ -0,0 +1,134 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +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\User\DeletedUsersIndex; +use OCA\User_LDAP\Mapping\UserMapping; +use OCA\User_LDAP\Helper as LDAPHelper; +use OCA\User_LDAP\User_Proxy; + +class CheckUser extends Command { + /** @var \OCA\User_LDAP\User_Proxy */ + protected $backend; + + /** @var \OCA\User_LDAP\Helper */ + protected $helper; + + /** @var \OCA\User_LDAP\User\DeletedUsersIndex */ + protected $dui; + + /** @var \OCA\User_LDAP\Mapping\UserMapping */ + protected $mapping; + + /** + * @param User_Proxy $uBackend + * @param LDAPHelper $helper + * @param DeletedUsersIndex $dui + * @param UserMapping $mapping + */ + public function __construct(User_Proxy $uBackend, LDAPHelper $helper, DeletedUsersIndex $dui, UserMapping $mapping) { + $this->backend = $uBackend; + $this->helper = $helper; + $this->dui = $dui; + $this->mapping = $mapping; + 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; + } + + $this->dui->markUser($uid); + $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('<error>' . $e->getMessage(). '</error>'); + } + } + + /** + * checks whether a user is actually mapped + * @param string $ocName the username as used in ownCloud + * @throws \Exception + * @return true + */ + protected function confirmUserIsMapped($ocName) { + $dn = $this->mapping->getDNByName($ocName); + if ($dn === 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 existence + * @throws \Exception + * @return true + */ + protected function isAllowed($force) { + if($this->helper->haveDisabledConfigurations() && !$force) { + throw new \Exception('Cannot check user existence, 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/lib/Command/CreateEmptyConfig.php b/apps/user_ldap/lib/Command/CreateEmptyConfig.php new file mode 100644 index 00000000000..c735e51f491 --- /dev/null +++ b/apps/user_ldap/lib/Command/CreateEmptyConfig.php @@ -0,0 +1,73 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Martin Konrad <konrad@frib.msu.edu> + * @author Morris Jobke <hey@morrisjobke.de> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\User_LDAP\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use \OCA\User_LDAP\Helper; +use \OCA\User_LDAP\Configuration; + +class CreateEmptyConfig extends Command { + /** @var \OCA\User_LDAP\Helper */ + protected $helper; + + /** + * @param Helper $helper + */ + public function __construct(Helper $helper) { + $this->helper = $helper; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('ldap:create-empty-config') + ->setDescription('creates an empty LDAP configuration') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $configPrefix = $this->getNewConfigurationPrefix(); + $output->writeln("Created new configuration with configID '{$configPrefix}'"); + + $configHolder = new Configuration($configPrefix); + $configHolder->saveConfiguration(); + } + + protected function getNewConfigurationPrefix() { + $serverConnections = $this->helper->getServerConfigurationPrefixes(); + + // first connection uses no prefix + if(sizeof($serverConnections) == 0) { + return ''; + } + + sort($serverConnections); + $lastKey = array_pop($serverConnections); + $lastNumber = intval(str_replace('s', '', $lastKey)); + $nextPrefix = 's' . str_pad($lastNumber + 1, 2, '0', STR_PAD_LEFT); + return $nextPrefix; + } +} diff --git a/apps/user_ldap/lib/Command/DeleteConfig.php b/apps/user_ldap/lib/Command/DeleteConfig.php new file mode 100644 index 00000000000..a56753daddc --- /dev/null +++ b/apps/user_ldap/lib/Command/DeleteConfig.php @@ -0,0 +1,68 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Martin Konrad <info@martin-konrad.net> + * @author Morris Jobke <hey@morrisjobke.de> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +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\Output\OutputInterface; +use \OCA\User_LDAP\Helper; + +class DeleteConfig extends Command { + /** @var \OCA\User_LDAP\Helper */ + protected $helper; + + /** + * @param Helper $helper + */ + public function __construct(Helper $helper) { + $this->helper = $helper; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('ldap:delete-config') + ->setDescription('deletes an existing LDAP configuration') + ->addArgument( + 'configID', + InputArgument::REQUIRED, + 'the configuration ID' + ) + ; + } + + + protected function execute(InputInterface $input, OutputInterface $output) { + $configPrefix = $input->getArgument('configID'); + + $success = $this->helper->deleteServerConfiguration($configPrefix); + + if($success) { + $output->writeln("Deleted configuration with configID '{$configPrefix}'"); + } else { + $output->writeln("Cannot delete configuration with configID '{$configPrefix}'"); + } + } +} diff --git a/apps/user_ldap/lib/Command/Search.php b/apps/user_ldap/lib/Command/Search.php new file mode 100644 index 00000000000..795530e2044 --- /dev/null +++ b/apps/user_ldap/lib/Command/Search.php @@ -0,0 +1,127 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +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\User_Proxy; +use OCA\User_LDAP\Group_Proxy; +use OCA\User_LDAP\Helper; +use OCA\User_LDAP\LDAP; +use OCP\IConfig; + +class Search extends Command { + /** @var \OCP\IConfig */ + protected $ocConfig; + + /** + * @param \OCP\IConfig $ocConfig + */ + public function __construct(IConfig $ocConfig) { + $this->ocConfig = $ocConfig; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('ldap:search') + ->setDescription('executes a user or group search') + ->addArgument( + 'search', + InputArgument::REQUIRED, + 'the search string (can be empty)' + ) + ->addOption( + 'group', + null, + InputOption::VALUE_NONE, + 'searches groups instead of users' + ) + ->addOption( + 'offset', + null, + InputOption::VALUE_REQUIRED, + 'The offset of the result set. Needs to be a multiple of limit. defaults to 0.', + 0 + ) + ->addOption( + 'limit', + null, + InputOption::VALUE_REQUIRED, + 'limit the results. 0 means no limit, defaults to 15', + 15 + ) + ; + } + + /** + * Tests whether the offset and limit options are valid + * @param int $offset + * @param int $limit + * @throws \InvalidArgumentException + */ + protected function validateOffsetAndLimit($offset, $limit) { + if($limit < 0) { + throw new \InvalidArgumentException('limit must be 0 or greater'); + } + if($offset < 0) { + throw new \InvalidArgumentException('offset must be 0 or greater'); + } + if($limit === 0 && $offset !== 0) { + throw new \InvalidArgumentException('offset must be 0 if limit is also set to 0'); + } + if($offset > 0 && ($offset % $limit !== 0)) { + throw new \InvalidArgumentException('offset must be a multiple of limit'); + } + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $helper = new Helper(); + $configPrefixes = $helper->getServerConfigurationPrefixes(true); + $ldapWrapper = new LDAP(); + + $offset = intval($input->getOption('offset')); + $limit = intval($input->getOption('limit')); + $this->validateOffsetAndLimit($offset, $limit); + + if($input->getOption('group')) { + $proxy = new Group_Proxy($configPrefixes, $ldapWrapper); + $getMethod = 'getGroups'; + $printID = false; + } else { + $proxy = new User_Proxy($configPrefixes, $ldapWrapper, $this->ocConfig); + $getMethod = 'getDisplayNames'; + $printID = true; + } + + $result = $proxy->$getMethod($input->getArgument('search'), $limit, $offset); + foreach($result as $id => $name) { + $line = $name . ($printID ? ' ('.$id.')' : ''); + $output->writeln($line); + } + } +} diff --git a/apps/user_ldap/lib/Command/SetConfig.php b/apps/user_ldap/lib/Command/SetConfig.php new file mode 100644 index 00000000000..d466e1eb769 --- /dev/null +++ b/apps/user_ldap/lib/Command/SetConfig.php @@ -0,0 +1,84 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +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\Output\OutputInterface; +use OCA\User_LDAP\Helper; +use OCA\User_LDAP\Configuration; + +class SetConfig extends Command { + + protected function configure() { + $this + ->setName('ldap:set-config') + ->setDescription('modifies an LDAP configuration') + ->addArgument( + 'configID', + InputArgument::REQUIRED, + 'the configuration ID' + ) + ->addArgument( + 'configKey', + InputArgument::REQUIRED, + 'the configuration key' + ) + ->addArgument( + 'configValue', + InputArgument::REQUIRED, + 'the new configuration value' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $helper = new Helper(); + $availableConfigs = $helper->getServerConfigurationPrefixes(); + $configID = $input->getArgument('configID'); + if(!in_array($configID, $availableConfigs)) { + $output->writeln("Invalid configID"); + return; + } + + $this->setValue( + $configID, + $input->getArgument('configKey'), + $input->getArgument('configValue') + ); + } + + /** + * save the configuration value as provided + * @param string $configID + * @param string $configKey + * @param string $configValue + */ + protected function setValue($configID, $key, $value) { + $configHolder = new Configuration($configID); + $configHolder->$key = $value; + $configHolder->saveConfiguration(); + } +} diff --git a/apps/user_ldap/lib/Command/ShowConfig.php b/apps/user_ldap/lib/Command/ShowConfig.php new file mode 100644 index 00000000000..dbd18216f81 --- /dev/null +++ b/apps/user_ldap/lib/Command/ShowConfig.php @@ -0,0 +1,108 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Laurens Post <Crote@users.noreply.github.com> + * @author Morris Jobke <hey@morrisjobke.de> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +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\Helper; +use OCA\User_LDAP\Configuration; + +class ShowConfig extends Command { + /** @var \OCA\User_LDAP\Helper */ + protected $helper; + + /** + * @param Helper $helper + */ + public function __construct(Helper $helper) { + $this->helper = $helper; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('ldap:show-config') + ->setDescription('shows the LDAP configuration') + ->addArgument( + 'configID', + InputArgument::OPTIONAL, + 'will show the configuration of the specified id' + ) + ->addOption( + 'show-password', + null, + InputOption::VALUE_NONE, + 'show ldap bind password' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $availableConfigs = $this->helper->getServerConfigurationPrefixes(); + $configID = $input->getArgument('configID'); + if(!is_null($configID)) { + $configIDs[] = $configID; + if(!in_array($configIDs[0], $availableConfigs)) { + $output->writeln("Invalid configID"); + return; + } + } else { + $configIDs = $availableConfigs; + } + + $this->renderConfigs($configIDs, $output, $input->getOption('show-password')); + } + + /** + * prints the LDAP configuration(s) + * @param string[] configID(s) + * @param OutputInterface $output + * @param bool $withPassword Set to TRUE to show plaintext passwords in output + */ + protected function renderConfigs($configIDs, $output, $withPassword) { + foreach($configIDs as $id) { + $configHolder = new Configuration($id); + $configuration = $configHolder->getConfiguration(); + ksort($configuration); + + $table = $this->getHelperSet()->get('table'); + $table->setHeaders(array('Configuration', $id)); + $rows = array(); + foreach($configuration as $key => $value) { + if($key === 'ldapAgentPassword' && !$withPassword) { + $value = '***'; + } + if(is_array($value)) { + $value = implode(';', $value); + } + $rows[] = array($key, $value); + } + $table->setRows($rows); + $table->render($output); + } + } +} diff --git a/apps/user_ldap/lib/Command/ShowRemnants.php b/apps/user_ldap/lib/Command/ShowRemnants.php new file mode 100644 index 00000000000..59da6e5522d --- /dev/null +++ b/apps/user_ldap/lib/Command/ShowRemnants.php @@ -0,0 +1,92 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author scolebrook <scolebrook@mac.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\User_LDAP\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +use OCA\User_LDAP\User\DeletedUsersIndex; +use OCP\IDateTimeFormatter; + +class ShowRemnants extends Command { + /** @var \OCA\User_LDAP\User\DeletedUsersIndex */ + protected $dui; + + /** @var \OCP\IDateTimeFormatter */ + protected $dateFormatter; + + /** + * @param DeletedUsersIndex $dui + * @param IDateTimeFormatter $dateFormatter + */ + public function __construct(DeletedUsersIndex $dui, IDateTimeFormatter $dateFormatter) { + $this->dui = $dui; + $this->dateFormatter = $dateFormatter; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('ldap:show-remnants') + ->setDescription('shows which users are not available on LDAP anymore, but have remnants in ownCloud.') + ->addOption('json', null, InputOption::VALUE_NONE, 'return JSON array instead of pretty table.'); + } + + /** + * executes the command, i.e. creeates and outputs a table of LDAP users marked as deleted + * + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) { + /** @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(); + $resultSet = $this->dui->getUsers(); + foreach($resultSet as $user) { + $hAS = $user->getHasActiveShares() ? 'Y' : 'N'; + $lastLogin = ($user->getLastLogin() > 0) ? + $this->dateFormatter->formatDate($user->getLastLogin()) : '-'; + $rows[] = array('ocName' => $user->getOCName(), + 'displayName' => $user->getDisplayName(), + 'uid' => $user->getUID(), + 'dn' => $user->getDN(), + 'lastLogin' => $lastLogin, + 'homePath' => $user->getHomePath(), + 'sharer' => $hAS + ); + } + + if ($input->getOption('json')) { + $output->writeln(json_encode($rows)); + } else { + $table->setRows($rows); + $table->render($output); + } + } +} diff --git a/apps/user_ldap/lib/Command/TestConfig.php b/apps/user_ldap/lib/Command/TestConfig.php new file mode 100644 index 00000000000..93a9f644714 --- /dev/null +++ b/apps/user_ldap/lib/Command/TestConfig.php @@ -0,0 +1,90 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +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\Output\OutputInterface; +use \OCA\User_LDAP\Helper; +use \OCA\User_LDAP\Connection; + +class TestConfig extends Command { + + protected function configure() { + $this + ->setName('ldap:test-config') + ->setDescription('tests an LDAP configuration') + ->addArgument( + 'configID', + InputArgument::REQUIRED, + 'the configuration ID' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $helper = new Helper(); + $availableConfigs = $helper->getServerConfigurationPrefixes(); + $configID = $input->getArgument('configID'); + if(!in_array($configID, $availableConfigs)) { + $output->writeln("Invalid configID"); + return; + } + + $result = $this->testConfig($configID); + if($result === 0) { + $output->writeln('The configuration is valid and the connection could be established!'); + } else if($result === 1) { + $output->writeln('The configuration is invalid. Please have a look at the logs for further details.'); + } else if($result === 2) { + $output->writeln('The configuration is valid, but the Bind failed. Please check the server settings and credentials.'); + } else { + $output->writeln('Your LDAP server was kidnapped by aliens.'); + } + } + + /** + * tests the specified connection + * @param string $configID + * @return int + */ + protected function testConfig($configID) { + $lw = new \OCA\User_LDAP\LDAP(); + $connection = new Connection($lw, $configID); + + //ensure validation is run before we attempt the bind + $connection->getConfiguration(); + + if(!$connection->setConfiguration(array( + 'ldap_configuration_active' => 1, + ))) { + return 1; + } + if($connection->bind()) { + return 0; + } + return 2; + } +} diff --git a/apps/user_ldap/lib/configuration.php b/apps/user_ldap/lib/Configuration.php index 418a2bfc015..476483ecc2f 100644 --- a/apps/user_ldap/lib/configuration.php +++ b/apps/user_ldap/lib/Configuration.php @@ -26,7 +26,7 @@ * */ -namespace OCA\user_ldap\lib; +namespace OCA\User_LDAP; /** * @property int ldapPagingSize holds an integer diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/Connection.php index 93e7e4bf974..1cf02a0a898 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/Connection.php @@ -26,7 +26,7 @@ * */ -namespace OCA\user_ldap\lib; +namespace OCA\User_LDAP; use OC\ServerNotAvailableException; diff --git a/apps/user_ldap/lib/filesystemhelper.php b/apps/user_ldap/lib/FilesystemHelper.php index 03f4c4274f4..439e7fb1d0b 100644 --- a/apps/user_ldap/lib/filesystemhelper.php +++ b/apps/user_ldap/lib/FilesystemHelper.php @@ -21,7 +21,7 @@ * */ -namespace OCA\user_ldap\lib; +namespace OCA\User_LDAP; /** * @brief wraps around static ownCloud core methods diff --git a/apps/user_ldap/lib/Group_LDAP.php b/apps/user_ldap/lib/Group_LDAP.php new file mode 100644 index 00000000000..39b2e9ff446 --- /dev/null +++ b/apps/user_ldap/lib/Group_LDAP.php @@ -0,0 +1,899 @@ +<?php +/** + * @author Alex Weirig <alex.weirig@technolink.lu> + * @author Alexander Bergolth <leo@strike.wu.ac.at> + * @author alexweirig <alex.weirig@technolink.lu> + * @author Andreas Fischer <bantu@owncloud.com> + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Bart Visscher <bartv@thisnet.nl> + * @author Christopher Schäpers <kondou@ts.unde.re> + * @author Frédéric Fortier <frederic.fortier@oronospolytechnique.com> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Nicolas Grekas <nicolas.grekas@gmail.com> + * @author Robin McCorkell <robin@mccorkell.me.uk> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\User_LDAP; + + +class Group_LDAP extends BackendUtility implements \OCP\GroupInterface { + protected $enabled = false; + + /** + * @var string[] $cachedGroupMembers array of users with gid as key + */ + protected $cachedGroupMembers = array(); + + /** + * @var string[] $cachedGroupsByMember array of groups with uid as key + */ + protected $cachedGroupsByMember = array(); + + public function __construct(Access $access) { + parent::__construct($access); + $filter = $this->access->connection->ldapGroupFilter; + $gassoc = $this->access->connection->ldapGroupMemberAssocAttr; + if(!empty($filter) && !empty($gassoc)) { + $this->enabled = true; + } + } + + /** + * is user in group? + * @param string $uid uid of the user + * @param string $gid gid of the group + * @return bool + * + * Checks whether the user is member of a group or not. + */ + public function inGroup($uid, $gid) { + if(!$this->enabled) { + return false; + } + $cacheKey = 'inGroup'.$uid.':'.$gid; + $inGroup = $this->access->connection->getFromCache($cacheKey); + if(!is_null($inGroup)) { + return (bool)$inGroup; + } + + $userDN = $this->access->username2dn($uid); + + if(isset($this->cachedGroupMembers[$gid])) { + $isInGroup = in_array($userDN, $this->cachedGroupMembers[$gid]); + return $isInGroup; + } + + $cacheKeyMembers = 'inGroup-members:'.$gid; + $members = $this->access->connection->getFromCache($cacheKeyMembers); + if(!is_null($members)) { + $this->cachedGroupMembers[$gid] = $members; + $isInGroup = in_array($userDN, $members); + $this->access->connection->writeToCache($cacheKey, $isInGroup); + return $isInGroup; + } + + $groupDN = $this->access->groupname2dn($gid); + // just in case + if(!$groupDN || !$userDN) { + $this->access->connection->writeToCache($cacheKey, false); + return false; + } + + //check primary group first + if($gid === $this->getUserPrimaryGroup($userDN)) { + $this->access->connection->writeToCache($cacheKey, true); + return true; + } + + //usually, LDAP attributes are said to be case insensitive. But there are exceptions of course. + $members = $this->_groupMembers($groupDN); + $members = array_keys($members); // uids are returned as keys + if(!is_array($members) || count($members) === 0) { + $this->access->connection->writeToCache($cacheKey, false); + return false; + } + + //extra work if we don't get back user DNs + if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') { + $dns = array(); + $filterParts = array(); + $bytes = 0; + foreach($members as $mid) { + $filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter); + $filterParts[] = $filter; + $bytes += strlen($filter); + if($bytes >= 9000000) { + // AD has a default input buffer of 10 MB, we do not want + // to take even the chance to exceed it + $filter = $this->access->combineFilterWithOr($filterParts); + $bytes = 0; + $filterParts = array(); + $users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts)); + $dns = array_merge($dns, $users); + } + } + if(count($filterParts) > 0) { + $filter = $this->access->combineFilterWithOr($filterParts); + $users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts)); + $dns = array_merge($dns, $users); + } + $members = $dns; + } + + $isInGroup = in_array($userDN, $members); + $this->access->connection->writeToCache($cacheKey, $isInGroup); + $this->access->connection->writeToCache($cacheKeyMembers, $members); + $this->cachedGroupMembers[$gid] = $members; + + return $isInGroup; + } + + /** + * @param string $dnGroup + * @return array + * + * For a group that has user membership defined by an LDAP search url attribute returns the users + * that match the search url otherwise returns an empty array. + */ + public function getDynamicGroupMembers($dnGroup) { + $dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL); + + if (empty($dynamicGroupMemberURL)) { + return array(); + } + + $dynamicMembers = array(); + $memberURLs = $this->access->readAttribute( + $dnGroup, + $dynamicGroupMemberURL, + $this->access->connection->ldapGroupFilter + ); + if ($memberURLs !== false) { + // this group has the 'memberURL' attribute so this is a dynamic group + // example 1: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(o=HeadOffice) + // example 2: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(&(o=HeadOffice)(uidNumber>=500)) + $pos = strpos($memberURLs[0], '('); + if ($pos !== false) { + $memberUrlFilter = substr($memberURLs[0], $pos); + $foundMembers = $this->access->searchUsers($memberUrlFilter,'dn'); + $dynamicMembers = array(); + foreach($foundMembers as $value) { + $dynamicMembers[$value['dn'][0]] = 1; + } + } else { + \OCP\Util::writeLog('user_ldap', 'No search filter found on member url '. + 'of group ' . $dnGroup, \OCP\Util::DEBUG); + } + } + return $dynamicMembers; + } + + /** + * @param string $dnGroup + * @param array|null &$seen + * @return array|mixed|null + */ + private function _groupMembers($dnGroup, &$seen = null) { + if ($seen === null) { + $seen = array(); + } + $allMembers = array(); + if (array_key_exists($dnGroup, $seen)) { + // avoid loops + return array(); + } + // used extensively in cron job, caching makes sense for nested groups + $cacheKey = '_groupMembers'.$dnGroup; + $groupMembers = $this->access->connection->getFromCache($cacheKey); + if(!is_null($groupMembers)) { + return $groupMembers; + } + $seen[$dnGroup] = 1; + $members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr, + $this->access->connection->ldapGroupFilter); + if (is_array($members)) { + foreach ($members as $memberDN) { + $allMembers[$memberDN] = 1; + $nestedGroups = $this->access->connection->ldapNestedGroups; + if (!empty($nestedGroups)) { + $subMembers = $this->_groupMembers($memberDN, $seen); + if ($subMembers) { + $allMembers = array_merge($allMembers, $subMembers); + } + } + } + } + + $allMembers = array_merge($allMembers, $this->getDynamicGroupMembers($dnGroup)); + + $this->access->connection->writeToCache($cacheKey, $allMembers); + return $allMembers; + } + + /** + * @param string $DN + * @param array|null &$seen + * @return array + */ + private function _getGroupDNsFromMemberOf($DN, &$seen = null) { + if ($seen === null) { + $seen = array(); + } + if (array_key_exists($DN, $seen)) { + // avoid loops + return array(); + } + $seen[$DN] = 1; + $groups = $this->access->readAttribute($DN, 'memberOf'); + if (!is_array($groups)) { + return array(); + } + $groups = $this->access->groupsMatchFilter($groups); + $allGroups = $groups; + $nestedGroups = $this->access->connection->ldapNestedGroups; + if (intval($nestedGroups) === 1) { + foreach ($groups as $group) { + $subGroups = $this->_getGroupDNsFromMemberOf($group, $seen); + $allGroups = array_merge($allGroups, $subGroups); + } + } + return $allGroups; + } + + /** + * translates a primary group ID into an ownCloud internal name + * @param string $gid as given by primaryGroupID on AD + * @param string $dn a DN that belongs to the same domain as the group + * @return string|bool + */ + public function primaryGroupID2Name($gid, $dn) { + $cacheKey = 'primaryGroupIDtoName'; + $groupNames = $this->access->connection->getFromCache($cacheKey); + if(!is_null($groupNames) && isset($groupNames[$gid])) { + return $groupNames[$gid]; + } + + $domainObjectSid = $this->access->getSID($dn); + if($domainObjectSid === false) { + return false; + } + + //we need to get the DN from LDAP + $filter = $this->access->combineFilterWithAnd(array( + $this->access->connection->ldapGroupFilter, + 'objectsid=' . $domainObjectSid . '-' . $gid + )); + $result = $this->access->searchGroups($filter, array('dn'), 1); + if(empty($result)) { + return false; + } + $dn = $result[0]['dn'][0]; + + //and now the group name + //NOTE once we have separate ownCloud group IDs and group names we can + //directly read the display name attribute instead of the DN + $name = $this->access->dn2groupname($dn); + + $this->access->connection->writeToCache($cacheKey, $name); + + return $name; + } + + /** + * returns the entry's primary group ID + * @param string $dn + * @param string $attribute + * @return string|bool + */ + private function getEntryGroupID($dn, $attribute) { + $value = $this->access->readAttribute($dn, $attribute); + if(is_array($value) && !empty($value)) { + return $value[0]; + } + return false; + } + + /** + * returns the group's primary ID + * @param string $dn + * @return string|bool + */ + public function getGroupPrimaryGroupID($dn) { + return $this->getEntryGroupID($dn, 'primaryGroupToken'); + } + + /** + * returns the user's primary group ID + * @param string $dn + * @return string|bool + */ + public function getUserPrimaryGroupIDs($dn) { + $primaryGroupID = false; + if($this->access->connection->hasPrimaryGroups) { + $primaryGroupID = $this->getEntryGroupID($dn, 'primaryGroupID'); + if($primaryGroupID === false) { + $this->access->connection->hasPrimaryGroups = false; + } + } + return $primaryGroupID; + } + + /** + * returns a filter for a "users in primary group" search or count operation + * + * @param string $groupDN + * @param string $search + * @return string + * @throws \Exception + */ + private function prepareFilterForUsersInPrimaryGroup($groupDN, $search = '') { + $groupID = $this->getGroupPrimaryGroupID($groupDN); + if($groupID === false) { + throw new \Exception('Not a valid group'); + } + + $filterParts = []; + $filterParts[] = $this->access->getFilterForUserCount(); + if(!empty($search)) { + $filterParts[] = $this->access->getFilterPartForUserSearch($search); + } + $filterParts[] = 'primaryGroupID=' . $groupID; + + $filter = $this->access->combineFilterWithAnd($filterParts); + + return $filter; + } + + /** + * returns a list of users that have the given group as primary group + * + * @param string $groupDN + * @param string $search + * @param int $limit + * @param int $offset + * @return string[] + */ + public function getUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) { + try { + $filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search); + $users = $this->access->fetchListOfUsers( + $filter, + array($this->access->connection->ldapUserDisplayName, 'dn'), + $limit, + $offset + ); + return $this->access->ownCloudUserNames($users); + } catch (\Exception $e) { + return array(); + } + } + + /** + * returns the number of users that have the given group as primary group + * + * @param string $groupDN + * @param string $search + * @param int $limit + * @param int $offset + * @return int + */ + public function countUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) { + try { + $filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search); + $users = $this->access->countUsers($filter, array('dn'), $limit, $offset); + return (int)$users; + } catch (\Exception $e) { + return 0; + } + } + + /** + * gets the primary group of a user + * @param string $dn + * @return string + */ + public function getUserPrimaryGroup($dn) { + $groupID = $this->getUserPrimaryGroupIDs($dn); + if($groupID !== false) { + $groupName = $this->primaryGroupID2Name($groupID, $dn); + if($groupName !== false) { + return $groupName; + } + } + + return false; + } + + /** + * Get all groups a user belongs to + * @param string $uid Name of the user + * @return array with group names + * + * This function fetches all groups a user belongs to. It does not check + * if the user exists at all. + * + * This function includes groups based on dynamic group membership. + */ + public function getUserGroups($uid) { + if(!$this->enabled) { + return array(); + } + $cacheKey = 'getUserGroups'.$uid; + $userGroups = $this->access->connection->getFromCache($cacheKey); + if(!is_null($userGroups)) { + return $userGroups; + } + $userDN = $this->access->username2dn($uid); + if(!$userDN) { + $this->access->connection->writeToCache($cacheKey, array()); + return array(); + } + + $groups = []; + $primaryGroup = $this->getUserPrimaryGroup($userDN); + + $dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL); + + if (!empty($dynamicGroupMemberURL)) { + // look through dynamic groups to add them to the result array if needed + $groupsToMatch = $this->access->fetchListOfGroups( + $this->access->connection->ldapGroupFilter,array('dn',$dynamicGroupMemberURL)); + foreach($groupsToMatch as $dynamicGroup) { + if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) { + continue; + } + $pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '('); + if ($pos !== false) { + $memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0],$pos); + // apply filter via ldap search to see if this user is in this + // dynamic group + $userMatch = $this->access->readAttribute( + $userDN, + $this->access->connection->ldapUserDisplayName, + $memberUrlFilter + ); + if ($userMatch !== false) { + // match found so this user is in this group + $groupName = $this->access->dn2groupname($dynamicGroup['dn'][0]); + if(is_string($groupName)) { + // be sure to never return false if the dn could not be + // resolved to a name, for whatever reason. + $groups[] = $groupName; + } + } + } else { + \OCP\Util::writeLog('user_ldap', 'No search filter found on member url '. + 'of group ' . print_r($dynamicGroup, true), \OCP\Util::DEBUG); + } + } + } + + // if possible, read out membership via memberOf. It's far faster than + // performing a search, which still is a fallback later. + if(intval($this->access->connection->hasMemberOfFilterSupport) === 1 + && intval($this->access->connection->useMemberOfToDetectMembership) === 1 + ) { + $groupDNs = $this->_getGroupDNsFromMemberOf($userDN); + if (is_array($groupDNs)) { + foreach ($groupDNs as $dn) { + $groupName = $this->access->dn2groupname($dn); + if(is_string($groupName)) { + // be sure to never return false if the dn could not be + // resolved to a name, for whatever reason. + $groups[] = $groupName; + } + } + } + + if($primaryGroup !== false) { + $groups[] = $primaryGroup; + } + $this->access->connection->writeToCache($cacheKey, $groups); + return $groups; + } + + //uniqueMember takes DN, memberuid the uid, so we need to distinguish + if((strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'uniquemember') + || (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'member') + ) { + $uid = $userDN; + } else if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') { + $result = $this->access->readAttribute($userDN, 'uid'); + if ($result === false) { + \OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN ' . $userDN . ' on '. + $this->access->connection->ldapHost, \OCP\Util::DEBUG); + } + $uid = $result[0]; + } else { + // just in case + $uid = $userDN; + } + + if(isset($this->cachedGroupsByMember[$uid])) { + $groups[] = $this->cachedGroupsByMember[$uid]; + } else { + $groupsByMember = array_values($this->getGroupsByMember($uid)); + $groupsByMember = $this->access->ownCloudGroupNames($groupsByMember); + $this->cachedGroupsByMember[$uid] = $groupsByMember; + $groups = array_merge($groups, $groupsByMember); + } + + if($primaryGroup !== false) { + $groups[] = $primaryGroup; + } + + $groups = array_unique($groups, SORT_LOCALE_STRING); + $this->access->connection->writeToCache($cacheKey, $groups); + + return $groups; + } + + /** + * @param string $dn + * @param array|null &$seen + * @return array + */ + private function getGroupsByMember($dn, &$seen = null) { + if ($seen === null) { + $seen = array(); + } + $allGroups = array(); + if (array_key_exists($dn, $seen)) { + // avoid loops + return array(); + } + $seen[$dn] = true; + $filter = $this->access->combineFilterWithAnd(array( + $this->access->connection->ldapGroupFilter, + $this->access->connection->ldapGroupMemberAssocAttr.'='.$dn + )); + $groups = $this->access->fetchListOfGroups($filter, + array($this->access->connection->ldapGroupDisplayName, 'dn')); + if (is_array($groups)) { + foreach ($groups as $groupobj) { + $groupDN = $groupobj['dn'][0]; + $allGroups[$groupDN] = $groupobj; + $nestedGroups = $this->access->connection->ldapNestedGroups; + if (!empty($nestedGroups)) { + $supergroups = $this->getGroupsByMember($groupDN, $seen); + if (is_array($supergroups) && (count($supergroups)>0)) { + $allGroups = array_merge($allGroups, $supergroups); + } + } + } + } + return $allGroups; + } + + /** + * get a list of all users in a group + * + * @param string $gid + * @param string $search + * @param int $limit + * @param int $offset + * @return array with user ids + */ + public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { + if(!$this->enabled) { + return array(); + } + if(!$this->groupExists($gid)) { + return array(); + } + $search = $this->access->escapeFilterPart($search, true); + $cacheKey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset; + // check for cache of the exact query + $groupUsers = $this->access->connection->getFromCache($cacheKey); + if(!is_null($groupUsers)) { + return $groupUsers; + } + + // check for cache of the query without limit and offset + $groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search); + if(!is_null($groupUsers)) { + $groupUsers = array_slice($groupUsers, $offset, $limit); + $this->access->connection->writeToCache($cacheKey, $groupUsers); + return $groupUsers; + } + + if($limit === -1) { + $limit = null; + } + $groupDN = $this->access->groupname2dn($gid); + if(!$groupDN) { + // group couldn't be found, return empty resultset + $this->access->connection->writeToCache($cacheKey, array()); + return array(); + } + + $primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $search, $limit, $offset); + $members = array_keys($this->_groupMembers($groupDN)); + if(!$members && empty($primaryUsers)) { + //in case users could not be retrieved, return empty result set + $this->access->connection->writeToCache($cacheKey, array()); + return array(); + } + + $groupUsers = array(); + $isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid'); + $attrs = $this->access->userManager->getAttributes(true); + foreach($members as $member) { + if($isMemberUid) { + //we got uids, need to get their DNs to 'translate' them to user names + $filter = $this->access->combineFilterWithAnd(array( + str_replace('%uid', $member, $this->access->connection->ldapLoginFilter), + $this->access->getFilterPartForUserSearch($search) + )); + $ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1); + if(count($ldap_users) < 1) { + continue; + } + $groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]); + } else { + //we got DNs, check if we need to filter by search or we can give back all of them + if(!empty($search)) { + if(!$this->access->readAttribute($member, + $this->access->connection->ldapUserDisplayName, + $this->access->getFilterPartForUserSearch($search))) { + continue; + } + } + // dn2username will also check if the users belong to the allowed base + if($ocname = $this->access->dn2username($member)) { + $groupUsers[] = $ocname; + } + } + } + + $groupUsers = array_unique(array_merge($groupUsers, $primaryUsers)); + natsort($groupUsers); + $this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers); + $groupUsers = array_slice($groupUsers, $offset, $limit); + + + $this->access->connection->writeToCache($cacheKey, $groupUsers); + + return $groupUsers; + } + + /** + * returns the number of users in a group, who match the search term + * @param string $gid the internal group name + * @param string $search optional, a search string + * @return int|bool + */ + public function countUsersInGroup($gid, $search = '') { + $cacheKey = 'countUsersInGroup-'.$gid.'-'.$search; + if(!$this->enabled || !$this->groupExists($gid)) { + return false; + } + $groupUsers = $this->access->connection->getFromCache($cacheKey); + if(!is_null($groupUsers)) { + return $groupUsers; + } + + $groupDN = $this->access->groupname2dn($gid); + if(!$groupDN) { + // group couldn't be found, return empty result set + $this->access->connection->writeToCache($cacheKey, false); + return false; + } + + $members = array_keys($this->_groupMembers($groupDN)); + $primaryUserCount = $this->countUsersInPrimaryGroup($groupDN, ''); + if(!$members && $primaryUserCount === 0) { + //in case users could not be retrieved, return empty result set + $this->access->connection->writeToCache($cacheKey, false); + return false; + } + + if(empty($search)) { + $groupUsers = count($members) + $primaryUserCount; + $this->access->connection->writeToCache($cacheKey, $groupUsers); + return $groupUsers; + } + $search = $this->access->escapeFilterPart($search, true); + $isMemberUid = + (strtolower($this->access->connection->ldapGroupMemberAssocAttr) + === 'memberuid'); + + //we need to apply the search filter + //alternatives that need to be checked: + //a) get all users by search filter and array_intersect them + //b) a, but only when less than 1k 10k ?k users like it is + //c) put all DNs|uids in a LDAP filter, combine with the search string + // and let it count. + //For now this is not important, because the only use of this method + //does not supply a search string + $groupUsers = array(); + foreach($members as $member) { + if($isMemberUid) { + //we got uids, need to get their DNs to 'translate' them to user names + $filter = $this->access->combineFilterWithAnd(array( + str_replace('%uid', $member, $this->access->connection->ldapLoginFilter), + $this->access->getFilterPartForUserSearch($search) + )); + $ldap_users = $this->access->fetchListOfUsers($filter, 'dn', 1); + if(count($ldap_users) < 1) { + continue; + } + $groupUsers[] = $this->access->dn2username($ldap_users[0]); + } else { + //we need to apply the search filter now + if(!$this->access->readAttribute($member, + $this->access->connection->ldapUserDisplayName, + $this->access->getFilterPartForUserSearch($search))) { + continue; + } + // dn2username will also check if the users belong to the allowed base + if($ocname = $this->access->dn2username($member)) { + $groupUsers[] = $ocname; + } + } + } + + //and get users that have the group as primary + $primaryUsers = $this->countUsersInPrimaryGroup($groupDN, $search); + + return count($groupUsers) + $primaryUsers; + } + + /** + * get a list of all groups + * + * @param string $search + * @param $limit + * @param int $offset + * @return array with group names + * + * Returns a list with all groups (used by getGroups) + */ + protected function getGroupsChunk($search = '', $limit = -1, $offset = 0) { + if(!$this->enabled) { + return array(); + } + $cacheKey = 'getGroups-'.$search.'-'.$limit.'-'.$offset; + + //Check cache before driving unnecessary searches + \OCP\Util::writeLog('user_ldap', 'getGroups '.$cacheKey, \OCP\Util::DEBUG); + $ldap_groups = $this->access->connection->getFromCache($cacheKey); + if(!is_null($ldap_groups)) { + return $ldap_groups; + } + + // if we'd pass -1 to LDAP search, we'd end up in a Protocol + // error. With a limit of 0, we get 0 results. So we pass null. + if($limit <= 0) { + $limit = null; + } + $filter = $this->access->combineFilterWithAnd(array( + $this->access->connection->ldapGroupFilter, + $this->access->getFilterPartForGroupSearch($search) + )); + \OCP\Util::writeLog('user_ldap', 'getGroups Filter '.$filter, \OCP\Util::DEBUG); + $ldap_groups = $this->access->fetchListOfGroups($filter, + array($this->access->connection->ldapGroupDisplayName, 'dn'), + $limit, + $offset); + $ldap_groups = $this->access->ownCloudGroupNames($ldap_groups); + + $this->access->connection->writeToCache($cacheKey, $ldap_groups); + return $ldap_groups; + } + + /** + * get a list of all groups using a paged search + * + * @param string $search + * @param int $limit + * @param int $offset + * @return array with group names + * + * Returns a list with all groups + * Uses a paged search if available to override a + * server side search limit. + * (active directory has a limit of 1000 by default) + */ + public function getGroups($search = '', $limit = -1, $offset = 0) { + if(!$this->enabled) { + return array(); + } + $search = $this->access->escapeFilterPart($search, true); + $pagingSize = $this->access->connection->ldapPagingSize; + if ((! $this->access->connection->hasPagedResultSupport) + || empty($pagingSize)) { + return $this->getGroupsChunk($search, $limit, $offset); + } + $maxGroups = 100000; // limit max results (just for safety reasons) + if ($limit > -1) { + $overallLimit = min($limit + $offset, $maxGroups); + } else { + $overallLimit = $maxGroups; + } + $chunkOffset = $offset; + $allGroups = array(); + while ($chunkOffset < $overallLimit) { + $chunkLimit = min($pagingSize, $overallLimit - $chunkOffset); + $ldapGroups = $this->getGroupsChunk($search, $chunkLimit, $chunkOffset); + $nread = count($ldapGroups); + \OCP\Util::writeLog('user_ldap', 'getGroups('.$search.'): read '.$nread.' at offset '.$chunkOffset.' (limit: '.$chunkLimit.')', \OCP\Util::DEBUG); + if ($nread) { + $allGroups = array_merge($allGroups, $ldapGroups); + $chunkOffset += $nread; + } + if ($nread < $chunkLimit) { + break; + } + } + return $allGroups; + } + + /** + * @param string $group + * @return bool + */ + public function groupMatchesFilter($group) { + return (strripos($group, $this->groupSearch) !== false); + } + + /** + * check if a group exists + * @param string $gid + * @return bool + */ + public function groupExists($gid) { + $groupExists = $this->access->connection->getFromCache('groupExists'.$gid); + if(!is_null($groupExists)) { + return (bool)$groupExists; + } + + //getting dn, if false the group does not exist. If dn, it may be mapped + //only, requires more checking. + $dn = $this->access->groupname2dn($gid); + if(!$dn) { + $this->access->connection->writeToCache('groupExists'.$gid, false); + return false; + } + + //if group really still exists, we will be able to read its objectclass + if(!is_array($this->access->readAttribute($dn, ''))) { + $this->access->connection->writeToCache('groupExists'.$gid, false); + return false; + } + + $this->access->connection->writeToCache('groupExists'.$gid, true); + return true; + } + + /** + * Check if backend implements actions + * @param int $actions bitwise-or'ed actions + * @return boolean + * + * Returns the supported actions as int to be + * compared with OC_USER_BACKEND_CREATE_USER etc. + */ + public function implementsActions($actions) { + return (bool)(\OC\Group\Backend::COUNT_USERS & $actions); + } +} diff --git a/apps/user_ldap/lib/Group_Proxy.php b/apps/user_ldap/lib/Group_Proxy.php new file mode 100644 index 00000000000..333a74bab22 --- /dev/null +++ b/apps/user_ldap/lib/Group_Proxy.php @@ -0,0 +1,197 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Christopher Schäpers <kondou@ts.unde.re> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin McCorkell <robin@mccorkell.me.uk> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\User_LDAP; + +class Group_Proxy extends Proxy implements \OCP\GroupInterface { + private $backends = array(); + private $refBackend = null; + + /** + * Constructor + * @param string[] $serverConfigPrefixes array containing the config Prefixes + */ + public function __construct($serverConfigPrefixes, ILDAPWrapper $ldap) { + parent::__construct($ldap); + foreach($serverConfigPrefixes as $configPrefix) { + $this->backends[$configPrefix] = + new \OCA\User_LDAP\Group_LDAP($this->getAccess($configPrefix)); + if(is_null($this->refBackend)) { + $this->refBackend = &$this->backends[$configPrefix]; + } + } + } + + /** + * Tries the backends one after the other until a positive result is returned from the specified method + * @param string $gid the gid connected to the request + * @param string $method the method of the group backend that shall be called + * @param array $parameters an array of parameters to be passed + * @return mixed, the result of the method or false + */ + protected function walkBackends($gid, $method, $parameters) { + $cacheKey = $this->getGroupCacheKey($gid); + foreach($this->backends as $configPrefix => $backend) { + if($result = call_user_func_array(array($backend, $method), $parameters)) { + $this->writeToCache($cacheKey, $configPrefix); + return $result; + } + } + return false; + } + + /** + * Asks the backend connected to the server that supposely takes care of the gid from the request. + * @param string $gid the gid connected to the request + * @param string $method the method of the group backend that shall be called + * @param array $parameters an array of parameters to be passed + * @param mixed $passOnWhen the result matches this variable + * @return mixed, the result of the method or false + */ + protected function callOnLastSeenOn($gid, $method, $parameters, $passOnWhen) { + $cacheKey = $this->getGroupCacheKey($gid);; + $prefix = $this->getFromCache($cacheKey); + //in case the uid has been found in the past, try this stored connection first + if(!is_null($prefix)) { + if(isset($this->backends[$prefix])) { + $result = call_user_func_array(array($this->backends[$prefix], $method), $parameters); + if($result === $passOnWhen) { + //not found here, reset cache to null if group vanished + //because sometimes methods return false with a reason + $groupExists = call_user_func_array( + array($this->backends[$prefix], 'groupExists'), + array($gid) + ); + if(!$groupExists) { + $this->writeToCache($cacheKey, null); + } + } + return $result; + } + } + return false; + } + + /** + * is user in group? + * @param string $uid uid of the user + * @param string $gid gid of the group + * @return bool + * + * Checks whether the user is member of a group or not. + */ + public function inGroup($uid, $gid) { + return $this->handleRequest($gid, 'inGroup', array($uid, $gid)); + } + + /** + * Get all groups a user belongs to + * @param string $uid Name of the user + * @return string[] with group names + * + * This function fetches all groups a user belongs to. It does not check + * if the user exists at all. + */ + public function getUserGroups($uid) { + $groups = array(); + + foreach($this->backends as $backend) { + $backendGroups = $backend->getUserGroups($uid); + if (is_array($backendGroups)) { + $groups = array_merge($groups, $backendGroups); + } + } + + return $groups; + } + + /** + * get a list of all users in a group + * @return string[] with user ids + */ + public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { + $users = array(); + + foreach($this->backends as $backend) { + $backendUsers = $backend->usersInGroup($gid, $search, $limit, $offset); + if (is_array($backendUsers)) { + $users = array_merge($users, $backendUsers); + } + } + + return $users; + } + + /** + * returns the number of users in a group, who match the search term + * @param string $gid the internal group name + * @param string $search optional, a search string + * @return int|bool + */ + public function countUsersInGroup($gid, $search = '') { + return $this->handleRequest( + $gid, 'countUsersInGroup', array($gid, $search)); + } + + /** + * get a list of all groups + * @return string[] with group names + * + * Returns a list with all groups + */ + public function getGroups($search = '', $limit = -1, $offset = 0) { + $groups = array(); + + foreach($this->backends as $backend) { + $backendGroups = $backend->getGroups($search, $limit, $offset); + if (is_array($backendGroups)) { + $groups = array_merge($groups, $backendGroups); + } + } + + return $groups; + } + + /** + * check if a group exists + * @param string $gid + * @return bool + */ + public function groupExists($gid) { + return $this->handleRequest($gid, 'groupExists', array($gid)); + } + + /** + * Check if backend implements actions + * @param int $actions bitwise-or'ed actions + * @return boolean + * + * Returns the supported actions as int to be + * compared with OC_USER_BACKEND_CREATE_USER etc. + */ + public function implementsActions($actions) { + //it's the same across all our user backends obviously + return $this->refBackend->implementsActions($actions); + } +} diff --git a/apps/user_ldap/lib/helper.php b/apps/user_ldap/lib/Helper.php index bfff6baf0d3..aa6d6aa1bce 100644 --- a/apps/user_ldap/lib/helper.php +++ b/apps/user_ldap/lib/Helper.php @@ -25,9 +25,7 @@ * */ -namespace OCA\user_ldap\lib; - -use OCA\user_ldap\User_Proxy; +namespace OCA\User_LDAP; class Helper { diff --git a/apps/user_ldap/lib/ildapwrapper.php b/apps/user_ldap/lib/ILDAPWrapper.php index d607a3fdb66..a95f3a615f2 100644 --- a/apps/user_ldap/lib/ildapwrapper.php +++ b/apps/user_ldap/lib/ILDAPWrapper.php @@ -23,7 +23,7 @@ * */ -namespace OCA\user_ldap\lib; +namespace OCA\User_LDAP; interface ILDAPWrapper { diff --git a/apps/user_ldap/lib/jobs/cleanup.php b/apps/user_ldap/lib/Jobs/CleanUp.php index c9f5f2021eb..48f80ed9fa0 100644 --- a/apps/user_ldap/lib/jobs/cleanup.php +++ b/apps/user_ldap/lib/Jobs/CleanUp.php @@ -23,11 +23,11 @@ namespace OCA\User_LDAP\Jobs; use \OC\BackgroundJob\TimedJob; -use \OCA\user_ldap\User_LDAP; -use \OCA\user_ldap\User_Proxy; -use \OCA\user_ldap\lib\Helper; -use \OCA\user_ldap\lib\LDAP; -use \OCA\user_ldap\lib\user\DeletedUsersIndex; +use \OCA\User_LDAP\User_LDAP; +use \OCA\User_LDAP\User_Proxy; +use \OCA\User_LDAP\Helper; +use \OCA\User_LDAP\LDAP; +use \OCA\User_LDAP\User\DeletedUsersIndex; use \OCA\User_LDAP\Mapping\UserMapping; /** @@ -35,7 +35,7 @@ use \OCA\User_LDAP\Mapping\UserMapping; * * a Background job to clean up deleted users * - * @package OCA\user_ldap\lib; + * @package OCA\User_LDAP\Jobs; */ class CleanUp extends TimedJob { /** @var int $limit amount of users that should be checked per run */ @@ -59,7 +59,7 @@ class CleanUp extends TimedJob { /** @var \OCA\User_LDAP\Mapping\UserMapping */ protected $mapping; - /** @var \OCA\User_LDAP\lib\User\DeletedUsersIndex */ + /** @var \OCA\User_LDAP\User\DeletedUsersIndex */ protected $dui; public function __construct() { diff --git a/apps/user_ldap/lib/jobs.php b/apps/user_ldap/lib/Jobs/UpdateGroups.php index ecf2e6daa0f..3ea74959f5c 100644 --- a/apps/user_ldap/lib/jobs.php +++ b/apps/user_ldap/lib/Jobs/UpdateGroups.php @@ -26,12 +26,19 @@ * */ -namespace OCA\user_ldap\lib; - +namespace OCA\User_LDAP\Jobs; + +use OCA\User_LDAP\Access; +use OCA\User_LDAP\Connection; +use OCA\User_LDAP\FilesystemHelper; +use OCA\User_LDAP\Helper; +use OCA\User_LDAP\LDAP; +use OCA\User_LDAP\LogWrapper; use OCA\User_LDAP\Mapping\GroupMapping; use OCA\User_LDAP\Mapping\UserMapping; +use OCA\User_LDAP\User\Manager; -class Jobs extends \OC\BackgroundJob\TimedJob { +class UpdateGroups extends \OC\BackgroundJob\TimedJob { static private $groupsFromDB; static private $groupBE; @@ -45,7 +52,7 @@ class Jobs extends \OC\BackgroundJob\TimedJob { * @param mixed $argument */ public function run($argument){ - Jobs::updateGroups(); + self::updateGroups(); } static public function updateGroups() { @@ -158,7 +165,7 @@ class Jobs extends \OC\BackgroundJob\TimedJob { } /** - * @return \OCA\user_ldap\GROUP_LDAP|\OCA\user_ldap\Group_Proxy + * @return \OCA\User_LDAP\Group_LDAP|\OCA\User_LDAP\Group_Proxy */ static private function getGroupBE() { if(!is_null(self::$groupBE)) { @@ -170,7 +177,7 @@ class Jobs extends \OC\BackgroundJob\TimedJob { if(count($configPrefixes) === 1) { //avoid the proxy when there is only one LDAP server configured $dbc = \OC::$server->getDatabaseConnection(); - $userManager = new user\Manager( + $userManager = new Manager( \OC::$server->getConfig(), new FilesystemHelper(), new LogWrapper(), @@ -184,9 +191,9 @@ class Jobs extends \OC\BackgroundJob\TimedJob { $userMapper = new UserMapping($dbc); $ldapAccess->setGroupMapper($groupMapper); $ldapAccess->setUserMapper($userMapper); - self::$groupBE = new \OCA\user_ldap\GROUP_LDAP($ldapAccess); + self::$groupBE = new \OCA\User_LDAP\Group_LDAP($ldapAccess); } else { - self::$groupBE = new \OCA\user_ldap\Group_Proxy($configPrefixes, $ldapWrapper); + self::$groupBE = new \OCA\User_LDAP\Group_Proxy($configPrefixes, $ldapWrapper); } return self::$groupBE; diff --git a/apps/user_ldap/lib/ldap.php b/apps/user_ldap/lib/LDAP.php index 383d71ca6eb..a7033d82254 100644 --- a/apps/user_ldap/lib/ldap.php +++ b/apps/user_ldap/lib/LDAP.php @@ -24,7 +24,7 @@ * */ -namespace OCA\user_ldap\lib; +namespace OCA\User_LDAP; use OC\ServerNotAvailableException; @@ -160,7 +160,7 @@ class LDAP implements ILDAPWrapper { /** * @param LDAP $link * @param resource $result - * @return mixed|an + * @return mixed */ public function nextEntry($link, $result) { return $this->invokeLDAPMethod('next_entry', $link, $result); diff --git a/apps/user_ldap/lib/ldaputility.php b/apps/user_ldap/lib/LDAPUtility.php index e80fc12e087..33b2685593f 100644 --- a/apps/user_ldap/lib/ldaputility.php +++ b/apps/user_ldap/lib/LDAPUtility.php @@ -21,7 +21,7 @@ * */ -namespace OCA\user_ldap\lib; +namespace OCA\User_LDAP; abstract class LDAPUtility { protected $ldap; diff --git a/apps/user_ldap/lib/logwrapper.php b/apps/user_ldap/lib/LogWrapper.php index 41ae4fc3426..5368e82417b 100644 --- a/apps/user_ldap/lib/logwrapper.php +++ b/apps/user_ldap/lib/LogWrapper.php @@ -20,7 +20,7 @@ * */ -namespace OCA\user_ldap\lib; +namespace OCA\User_LDAP; /** * @brief wraps around static ownCloud core methods diff --git a/apps/user_ldap/lib/mapping/abstractmapping.php b/apps/user_ldap/lib/Mapping/AbstractMapping.php index 1c896a9bbf4..1c896a9bbf4 100644 --- a/apps/user_ldap/lib/mapping/abstractmapping.php +++ b/apps/user_ldap/lib/Mapping/AbstractMapping.php diff --git a/apps/user_ldap/lib/mapping/groupmapping.php b/apps/user_ldap/lib/Mapping/GroupMapping.php index 49bb41b8c76..49bb41b8c76 100644 --- a/apps/user_ldap/lib/mapping/groupmapping.php +++ b/apps/user_ldap/lib/Mapping/GroupMapping.php diff --git a/apps/user_ldap/lib/mapping/usermapping.php b/apps/user_ldap/lib/Mapping/UserMapping.php index b39f738ea8c..b39f738ea8c 100644 --- a/apps/user_ldap/lib/mapping/usermapping.php +++ b/apps/user_ldap/lib/Mapping/UserMapping.php diff --git a/apps/user_ldap/lib/proxy.php b/apps/user_ldap/lib/Proxy.php index 7002aaadaa5..ce1a67b29de 100644 --- a/apps/user_ldap/lib/proxy.php +++ b/apps/user_ldap/lib/Proxy.php @@ -26,9 +26,8 @@ * */ -namespace OCA\user_ldap\lib; +namespace OCA\User_LDAP; -use OCA\user_ldap\lib\Access; use OCA\User_LDAP\Mapping\UserMapping; use OCA\User_LDAP\Mapping\GroupMapping; diff --git a/apps/user_ldap/lib/user/deletedusersindex.php b/apps/user_ldap/lib/User/DeletedUsersIndex.php index 48daeb9b8bc..7a9823786dd 100644 --- a/apps/user_ldap/lib/user/deletedusersindex.php +++ b/apps/user_ldap/lib/User/DeletedUsersIndex.php @@ -21,7 +21,7 @@ * */ -namespace OCA\user_ldap\lib\user; +namespace OCA\User_LDAP\User; use OCA\User_LDAP\Mapping\UserMapping; @@ -63,7 +63,7 @@ class DeletedUsersIndex { /** * reads LDAP users marked as deleted from the database - * @return \OCA\user_ldap\lib\user\OfflineUser[] + * @return \OCA\User_LDAP\User\OfflineUser[] */ private function fetchDeletedUsers() { $deletedUsers = $this->config->getUsersForUserValue( @@ -80,7 +80,7 @@ class DeletedUsersIndex { /** * returns all LDAP users that are marked as deleted - * @return \OCA\user_ldap\lib\user\OfflineUser[] + * @return \OCA\User_LDAP\User\OfflineUser[] */ public function getUsers() { if(is_array($this->deletedUsers)) { diff --git a/apps/user_ldap/lib/user/iusertools.php b/apps/user_ldap/lib/User/IUserTools.php index b0eb9e1ffb3..747b46ec68a 100644 --- a/apps/user_ldap/lib/user/iusertools.php +++ b/apps/user_ldap/lib/User/IUserTools.php @@ -20,7 +20,7 @@ * */ -namespace OCA\user_ldap\lib\user; +namespace OCA\User_LDAP\User; /** * IUserTools diff --git a/apps/user_ldap/lib/user/manager.php b/apps/user_ldap/lib/User/Manager.php index dc12ebd6e9d..d97112e43c7 100644 --- a/apps/user_ldap/lib/user/manager.php +++ b/apps/user_ldap/lib/User/Manager.php @@ -23,13 +23,10 @@ * */ -namespace OCA\user_ldap\lib\user; +namespace OCA\User_LDAP\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; -use OCA\user_ldap\lib\user\OfflineUser; +use OCA\User_LDAP\LogWrapper; +use OCA\User_LDAP\FilesystemHelper; use OCP\IAvatarManager; use OCP\IConfig; use OCP\IDBConnection; @@ -65,8 +62,8 @@ class Manager { protected $avatarManager; /** - * array['byDN'] \OCA\user_ldap\lib\User[] - * ['byUid'] \OCA\user_ldap\lib\User[] + * array['byDN'] \OCA\User_LDAP\User\User[] + * ['byUid'] \OCA\User_LDAP\User\User[] * @var array $users */ protected $users = array( @@ -76,9 +73,9 @@ class Manager { /** * @param IConfig $ocConfig - * @param \OCA\user_ldap\lib\FilesystemHelper $ocFilesystem object that + * @param \OCA\User_LDAP\FilesystemHelper $ocFilesystem object that * gives access to necessary functions from the OC filesystem - * @param \OCA\user_ldap\lib\LogWrapper $ocLog + * @param \OCA\User_LDAP\LogWrapper $ocLog * @param IAvatarManager $avatarManager * @param Image $image an empty image instance * @param IDBConnection $db @@ -112,7 +109,7 @@ class Manager { * property array * @param string $dn the DN of the user * @param string $uid the internal (owncloud) username - * @return \OCA\user_ldap\lib\User\User + * @return \OCA\User_LDAP\User\User */ private function createAndCache($dn, $uid) { $this->checkAccess(); @@ -187,7 +184,7 @@ class Manager { /** * creates and returns an instance of OfflineUser for the specified user * @param string $id - * @return \OCA\user_ldap\lib\user\OfflineUser + * @return \OCA\User_LDAP\User\OfflineUser */ public function getDeletedUser($id) { return new OfflineUser( @@ -200,7 +197,7 @@ class Manager { /** * @brief returns a User object by it's ownCloud username * @param string $id the DN or username of the user - * @return \OCA\user_ldap\lib\user\User|\OCA\user_ldap\lib\user\OfflineUser|null + * @return \OCA\User_LDAP\User\User|\OCA\User_LDAP\User\OfflineUser|null */ protected function createInstancyByUserName($id) { //most likely a uid. Check whether it is a deleted user @@ -217,7 +214,7 @@ class Manager { /** * @brief returns a User object by it's DN or ownCloud username * @param string $id the DN or username of the user - * @return \OCA\user_ldap\lib\user\User|\OCA\user_ldap\lib\user\OfflineUser|null + * @return \OCA\User_LDAP\User\User|\OCA\User_LDAP\User\OfflineUser|null * @throws \Exception when connection could not be established */ public function get($id) { diff --git a/apps/user_ldap/lib/user/offlineuser.php b/apps/user_ldap/lib/User/OfflineUser.php index aee1a137a96..a3a9995490d 100644 --- a/apps/user_ldap/lib/user/offlineuser.php +++ b/apps/user_ldap/lib/User/OfflineUser.php @@ -21,7 +21,7 @@ * */ -namespace OCA\user_ldap\lib\user; +namespace OCA\User_LDAP\User; use OCA\User_LDAP\Mapping\UserMapping; diff --git a/apps/user_ldap/lib/user/user.php b/apps/user_ldap/lib/User/User.php index 4da8ae5f098..8f7666790c1 100644 --- a/apps/user_ldap/lib/user/user.php +++ b/apps/user_ldap/lib/User/User.php @@ -22,11 +22,11 @@ * */ -namespace OCA\user_ldap\lib\user; +namespace OCA\User_LDAP\User; -use OCA\user_ldap\lib\Connection; -use OCA\user_ldap\lib\FilesystemHelper; -use OCA\user_ldap\lib\LogWrapper; +use OCA\User_LDAP\Connection; +use OCA\User_LDAP\FilesystemHelper; +use OCA\User_LDAP\LogWrapper; use OCP\IAvatarManager; use OCP\IConfig; use OCP\Image; @@ -464,7 +464,7 @@ class User { } } if(!is_null($quota)) { - $user = $this->userManager->get($this->uid)->setQuota($quota); + $this->userManager->get($this->uid)->setQuota($quota); } } @@ -502,16 +502,13 @@ class User { */ private function setOwnCloudAvatar() { if(!$this->image->valid()) { - $this->log->log('user_ldap', 'jpegPhoto data invalid for '.$this->dn, - \OCP\Util::ERROR); + $this->log->log('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); + $this->log->log('croping image for avatar failed for '.$this->dn, \OCP\Util::ERROR); return; } diff --git a/apps/user_ldap/lib/User_LDAP.php b/apps/user_ldap/lib/User_LDAP.php new file mode 100644 index 00000000000..87c603fc753 --- /dev/null +++ b/apps/user_ldap/lib/User_LDAP.php @@ -0,0 +1,459 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Bart Visscher <bartv@thisnet.nl> + * @author Dominik Schmidt <dev@dominik-schmidt.de> + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Renaud Fortier <Renaud.Fortier@fsaa.ulaval.ca> + * @author Robin Appelman <icewind@owncloud.com> + * @author Robin McCorkell <robin@mccorkell.me.uk> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Tom Needham <tom@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\User_LDAP; + +use OC\User\NoUserException; +use OCA\User_LDAP\User\OfflineUser; +use OCA\User_LDAP\User\User; +use OCP\IConfig; + +class User_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserInterface { + /** @var string[] $homesToKill */ + protected $homesToKill = array(); + + /** @var \OCP\IConfig */ + protected $ocConfig; + + /** + * @param Access $access + * @param \OCP\IConfig $ocConfig + */ + public function __construct(Access $access, IConfig $ocConfig) { + parent::__construct($access); + $this->ocConfig = $ocConfig; + } + + /** + * 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) { + $user = $this->access->userManager->get($uid); + if(!$user instanceof User) { + return false; + } + if($user->getAvatarImage() === false) { + return true; + } + + return false; + } + + /** + * returns the username for the given login name, if available + * + * @param string $loginName + * @return string|false + */ + public function loginName2UserName($loginName) { + try { + $ldapRecord = $this->getLDAPUserByLoginName($loginName); + $user = $this->access->userManager->get($ldapRecord['dn'][0]); + if($user instanceof OfflineUser) { + return false; + } + return $user->getUsername(); + } catch (\Exception $e) { + return false; + } + } + + /** + * returns an LDAP record based on a given login name + * + * @param string $loginName + * @return array + * @throws \Exception + */ + public function getLDAPUserByLoginName($loginName) { + //find out dn of the user name + $attrs = $this->access->userManager->getAttributes(); + $users = $this->access->fetchUsersByLoginName($loginName, $attrs); + if(count($users) < 1) { + throw new \Exception('No user available for the given login name on ' . + $this->access->connection->ldapHost . ':' . $this->access->connection->ldapPort); + } + return $users[0]; + } + + /** + * Check if the password is correct + * @param string $uid The username + * @param string $password The password + * @return false|string + * + * Check if the password is correct without logging in the user + */ + public function checkPassword($uid, $password) { + try { + $ldapRecord = $this->getLDAPUserByLoginName($uid); + } catch(\Exception $e) { + \OC::$server->getLogger()->logException($e, ['app' => 'user_ldap']); + return false; + } + $dn = $ldapRecord['dn'][0]; + $user = $this->access->userManager->get($dn); + + 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?', + \OCP\Util::WARN); + return false; + } + if($user->getUsername() !== false) { + //are the credentials OK? + if(!$this->access->areCredentialsValid($dn, $password)) { + return false; + } + + $this->access->cacheUserExists($user->getUsername()); + $user->processAttributes($ldapRecord); + $user->markLogin(); + + return $user->getUsername(); + } + + return false; + } + + /** + * Get a list of all users + * + * @param string $search + * @param integer $limit + * @param integer $offset + * @return string[] an array of all uids + */ + public function getUsers($search = '', $limit = 10, $offset = 0) { + $search = $this->access->escapeFilterPart($search, true); + $cachekey = 'getUsers-'.$search.'-'.$limit.'-'.$offset; + + //check if users are cached, if so return + $ldap_users = $this->access->connection->getFromCache($cachekey); + if(!is_null($ldap_users)) { + return $ldap_users; + } + + // if we'd pass -1 to LDAP search, we'd end up in a Protocol + // error. With a limit of 0, we get 0 results. So we pass null. + if($limit <= 0) { + $limit = null; + } + $filter = $this->access->combineFilterWithAnd(array( + $this->access->connection->ldapUserFilter, + $this->access->connection->ldapUserDisplayName . '=*', + $this->access->getFilterPartForUserSearch($search) + )); + $attrs = array($this->access->connection->ldapUserDisplayName, 'dn'); + $additionalAttribute = $this->access->connection->ldapUserDisplayName2; + if(!empty($additionalAttribute)) { + $attrs[] = $additionalAttribute; + } + + \OCP\Util::writeLog('user_ldap', + 'getUsers: Options: search '.$search.' limit '.$limit.' offset '.$offset.' Filter: '.$filter, + \OCP\Util::DEBUG); + //do the search and translate results to owncloud names + $ldap_users = $this->access->fetchListOfUsers( + $filter, + $this->access->userManager->getAttributes(true), + $limit, $offset); + $ldap_users = $this->access->ownCloudUserNames($ldap_users); + \OCP\Util::writeLog('user_ldap', 'getUsers: '.count($ldap_users). ' Users found', \OCP\Util::DEBUG); + + $this->access->connection->writeToCache($cachekey, $ldap_users); + return $ldap_users; + } + + /** + * checks whether a user is still available on LDAP + * + * @param string|\OCA\User_LDAP\User\User $user either the ownCloud user + * name or an instance of that user + * @return bool + * @throws \Exception + * @throws \OC\ServerNotAvailableException + */ + public function userExistsOnLDAP($user) { + if(is_string($user)) { + $user = $this->access->userManager->get($user); + } + if(is_null($user)) { + return false; + } + + $dn = $user->getDN(); + //check if user really still exists by reading its entry + if(!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapUserFilter))) { + $lcr = $this->access->connection->getConnectionResource(); + if(is_null($lcr)) { + throw new \Exception('No LDAP Connection to server ' . $this->access->connection->ldapHost); + } + + try { + $uuid = $this->access->getUserMapper()->getUUIDByDN($dn); + if(!$uuid) { + return false; + } + $newDn = $this->access->getUserDnByUuid($uuid); + $this->access->getUserMapper()->setDNbyUUID($newDn, $uuid); + return true; + } catch (\Exception $e) { + return false; + } + } + + if($user instanceof OfflineUser) { + $user->unmark(); + } + + return true; + } + + /** + * check if a user exists + * @param string $uid the username + * @return boolean + * @throws \Exception when connection could not be established + */ + public function userExists($uid) { + $userExists = $this->access->connection->getFromCache('userExists'.$uid); + if(!is_null($userExists)) { + return (bool)$userExists; + } + //getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking. + $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; + } else if($user instanceof OfflineUser) { + //express check for users marked as deleted. Returning true is + //necessary for cleanup + return true; + } + + $result = $this->userExistsOnLDAP($user); + $this->access->connection->writeToCache('userExists'.$uid, $result); + if($result === true) { + $user->update(); + } + return $result; + } + + /** + * returns whether a user was deleted in LDAP + * + * @param string $uid The username of the user to delete + * @return bool + */ + public function deleteUser($uid) { + $marked = $this->ocConfig->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 = $this->ocConfig->getUserValue($uid, 'user_ldap', 'homePath', ''); + $this->homesToKill[$uid] = $home; + $this->access->getUserMapper()->unmap($uid); + + return true; + } + + /** + * get the user's home directory + * + * @param string $uid the username + * @return bool|string + * @throws NoUserException + * @throws \Exception + */ + public function getHome($uid) { + if(isset($this->homesToKill[$uid]) && !empty($this->homesToKill[$uid])) { + //a deleted user who needs some clean up + return $this->homesToKill[$uid]; + } + + // user Exists check required as it is not done in user proxy! + if(!$this->userExists($uid)) { + return false; + } + + $cacheKey = 'getHome'.$uid; + $path = $this->access->connection->getFromCache($cacheKey); + if(!is_null($path)) { + return $path; + } + + $user = $this->access->userManager->get($uid); + if(is_null($user) || ($user instanceof OfflineUser && !$this->userExistsOnLDAP($user->getOCName()))) { + throw new NoUserException($uid . ' is not a valid user anymore'); + } + if($user instanceof OfflineUser) { + // apparently this user survived the userExistsOnLDAP check, + // we request the user instance again in order to retrieve a User + // instance instead + $user = $this->access->userManager->get($uid); + } + $path = $user->getHomePath(); + $this->access->cacheUserHome($uid, $path); + + return $path; + } + + /** + * get display name of the user + * @param string $uid user ID of the user + * @return string|false display name + */ + public function getDisplayName($uid) { + if(!$this->userExists($uid)) { + return false; + } + + $cacheKey = 'getDisplayName'.$uid; + if(!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) { + return $displayName; + } + + //Check whether the display name is configured to have a 2nd feature + $additionalAttribute = $this->access->connection->ldapUserDisplayName2; + $displayName2 = ''; + if(!empty($additionalAttribute)) { + $displayName2 = $this->access->readAttribute( + $this->access->username2dn($uid), + $additionalAttribute); + } + + $displayName = $this->access->readAttribute( + $this->access->username2dn($uid), + $this->access->connection->ldapUserDisplayName); + + if($displayName && (count($displayName) > 0)) { + $displayName = $displayName[0]; + + if(is_array($displayName2) && (count($displayName2) > 0)) { + $displayName2 = $displayName2[0]; + } + + $user = $this->access->userManager->get($uid); + $displayName = $user->composeAndStoreDisplayName($displayName, $displayName2); + $this->access->connection->writeToCache($cacheKey, $displayName); + return $displayName; + } + + return null; + } + + /** + * Get a list of all display names + * + * @param string $search + * @param string|null $limit + * @param string|null $offset + * @return array an array of all displayNames (value) and the corresponding uids (key) + */ + public function getDisplayNames($search = '', $limit = null, $offset = null) { + $cacheKey = 'getDisplayNames-'.$search.'-'.$limit.'-'.$offset; + if(!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) { + return $displayNames; + } + + $displayNames = array(); + $users = $this->getUsers($search, $limit, $offset); + foreach ($users as $user) { + $displayNames[$user] = $this->getDisplayName($user); + } + $this->access->connection->writeToCache($cacheKey, $displayNames); + return $displayNames; + } + + /** + * Check if backend implements actions + * @param int $actions bitwise-or'ed actions + * @return boolean + * + * Returns the supported actions as int to be + * compared with OC_USER_BACKEND_CREATE_USER etc. + */ + public function implementsActions($actions) { + return (bool)((\OC\User\Backend::CHECK_PASSWORD + | \OC\User\Backend::GET_HOME + | \OC\User\Backend::GET_DISPLAYNAME + | \OC\User\Backend::PROVIDE_AVATAR + | \OC\User\Backend::COUNT_USERS) + & $actions); + } + + /** + * @return bool + */ + public function hasUserListings() { + return true; + } + + /** + * counts the users in LDAP + * + * @return int|bool + */ + public function countUsers() { + $filter = $this->access->getFilterForUserCount(); + $cacheKey = 'countUsers-'.$filter; + if(!is_null($entries = $this->access->connection->getFromCache($cacheKey))) { + return $entries; + } + $entries = $this->access->countUsers($filter); + $this->access->connection->writeToCache($cacheKey, $entries); + return $entries; + } + + /** + * Backend name to be shown in user management + * @return string the name of the backend to be shown + */ + public function getBackendName(){ + return 'LDAP'; + } + +} diff --git a/apps/user_ldap/lib/User_Proxy.php b/apps/user_ldap/lib/User_Proxy.php new file mode 100644 index 00000000000..800d1d36c16 --- /dev/null +++ b/apps/user_ldap/lib/User_Proxy.php @@ -0,0 +1,274 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * @author Christopher Schäpers <kondou@ts.unde.re> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin McCorkell <robin@mccorkell.me.uk> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\User_LDAP; + +use OCA\User_LDAP\User\User; +use OCP\IConfig; + +class User_Proxy extends Proxy implements \OCP\IUserBackend, \OCP\UserInterface { + private $backends = array(); + private $refBackend = null; + + /** + * Constructor + * @param array $serverConfigPrefixes array containing the config Prefixes + */ + public function __construct(array $serverConfigPrefixes, ILDAPWrapper $ldap, IConfig $ocConfig) { + parent::__construct($ldap); + foreach($serverConfigPrefixes as $configPrefix) { + $this->backends[$configPrefix] = + new User_LDAP($this->getAccess($configPrefix), $ocConfig); + if(is_null($this->refBackend)) { + $this->refBackend = &$this->backends[$configPrefix]; + } + } + } + + /** + * Tries the backends one after the other until a positive result is returned from the specified method + * @param string $uid the uid connected to the request + * @param string $method the method of the user backend that shall be called + * @param array $parameters an array of parameters to be passed + * @return mixed the result of the method or false + */ + protected function walkBackends($uid, $method, $parameters) { + $cacheKey = $this->getUserCacheKey($uid); + foreach($this->backends as $configPrefix => $backend) { + $instance = $backend; + if(!method_exists($instance, $method) + && method_exists($this->getAccess($configPrefix), $method)) { + $instance = $this->getAccess($configPrefix); + } + if($result = call_user_func_array(array($instance, $method), $parameters)) { + $this->writeToCache($cacheKey, $configPrefix); + return $result; + } + } + return false; + } + + /** + * Asks the backend connected to the server that supposely takes care of the uid from the request. + * @param string $uid the uid connected to the request + * @param string $method the method of the user backend that shall be called + * @param array $parameters an array of parameters to be passed + * @param mixed $passOnWhen the result matches this variable + * @return mixed the result of the method or false + */ + protected function callOnLastSeenOn($uid, $method, $parameters, $passOnWhen) { + $cacheKey = $this->getUserCacheKey($uid); + $prefix = $this->getFromCache($cacheKey); + //in case the uid has been found in the past, try this stored connection first + if(!is_null($prefix)) { + if(isset($this->backends[$prefix])) { + $instance = $this->backends[$prefix]; + if(!method_exists($instance, $method) + && method_exists($this->getAccess($prefix), $method)) { + $instance = $this->getAccess($prefix); + } + $result = call_user_func_array(array($instance, $method), $parameters); + if($result === $passOnWhen) { + //not found here, reset cache to null if user vanished + //because sometimes methods return false with a reason + $userExists = call_user_func_array( + array($this->backends[$prefix], 'userExists'), + array($uid) + ); + if(!$userExists) { + $this->writeToCache($cacheKey, null); + } + } + return $result; + } + } + return false; + } + + /** + * Check if backend implements actions + * @param int $actions bitwise-or'ed actions + * @return boolean + * + * Returns the supported actions as int to be + * compared with OC_USER_BACKEND_CREATE_USER etc. + */ + public function implementsActions($actions) { + //it's the same across all our user backends obviously + return $this->refBackend->implementsActions($actions); + } + + /** + * Backend name to be shown in user management + * @return string the name of the backend to be shown + */ + public function getBackendName() { + return $this->refBackend->getBackendName(); + } + + /** + * Get a list of all users + * + * @param string $search + * @param null|int $limit + * @param null|int $offset + * @return string[] an array of all uids + */ + public function getUsers($search = '', $limit = 10, $offset = 0) { + //we do it just as the /OC_User implementation: do not play around with limit and offset but ask all backends + $users = array(); + foreach($this->backends as $backend) { + $backendUsers = $backend->getUsers($search, $limit, $offset); + if (is_array($backendUsers)) { + $users = array_merge($users, $backendUsers); + } + } + return $users; + } + + /** + * check if a user exists + * @param string $uid the username + * @return boolean + */ + public function userExists($uid) { + return $this->handleRequest($uid, 'userExists', array($uid)); + } + + /** + * check if a user exists on LDAP + * @param string|\OCA\User_LDAP\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 + * @param string $password The password + * @return bool + * + * Check if the password is correct without logging in the user + */ + public function checkPassword($uid, $password) { + return $this->handleRequest($uid, 'checkPassword', array($uid, $password)); + } + + /** + * returns the username for the given login name, if available + * + * @param string $loginName + * @return string|false + */ + public function loginName2UserName($loginName) { + $id = 'LOGINNAME,' . $loginName; + return $this->handleRequest($id, 'loginName2UserName', array($loginName)); + } + + /** + * get the user's home directory + * @param string $uid the username + * @return boolean + */ + public function getHome($uid) { + return $this->handleRequest($uid, 'getHome', array($uid)); + } + + /** + * get display name of the user + * @param string $uid user ID of the user + * @return string display name + */ + public function getDisplayName($uid) { + return $this->handleRequest($uid, 'getDisplayName', array($uid)); + } + + /** + * 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) { + return $this->handleRequest($uid, 'canChangeAvatar', array($uid), true); + } + + /** + * Get a list of all display names and user ids. + * @param string $search + * @param string|null $limit + * @param string|null $offset + * @return array an array of all displayNames (value) and the corresponding uids (key) + */ + public function getDisplayNames($search = '', $limit = null, $offset = null) { + //we do it just as the /OC_User implementation: do not play around with limit and offset but ask all backends + $users = array(); + foreach($this->backends as $backend) { + $backendUsers = $backend->getDisplayNames($search, $limit, $offset); + if (is_array($backendUsers)) { + $users = $users + $backendUsers; + } + } + return $users; + } + + /** + * delete a user + * @param string $uid The username of the user to delete + * @return bool + * + * Deletes a user + */ + public function deleteUser($uid) { + return $this->handleRequest($uid, 'deleteUser', array($uid)); + } + + /** + * @return bool + */ + public function hasUserListings() { + return $this->refBackend->hasUserListings(); + } + + /** + * Count the number of users + * @return int|bool + */ + public function countUsers() { + $users = false; + foreach($this->backends as $backend) { + $backendUsers = $backend->countUsers(); + if ($backendUsers !== false) { + $users += $backendUsers; + } + } + return $users; + } + +} diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/Wizard.php index 0b475ee7143..f71d5af2c49 100644 --- a/apps/user_ldap/lib/wizard.php +++ b/apps/user_ldap/lib/Wizard.php @@ -29,11 +29,12 @@ * */ -namespace OCA\user_ldap\lib; +namespace OCA\User_LDAP; use OC\ServerNotAvailableException; class Wizard extends LDAPUtility { + /** @var \OCP\IL10N */ static protected $l; protected $access; protected $cr; @@ -58,6 +59,7 @@ class Wizard extends LDAPUtility { * Constructor * @param Configuration $configuration an instance of Configuration * @param ILDAPWrapper $ldap an instance of ILDAPWrapper + * @param Access $access */ public function __construct(Configuration $configuration, ILDAPWrapper $ldap, Access $access) { parent::__construct($ldap); @@ -1032,12 +1034,12 @@ class Wizard extends LDAPUtility { $host = $this->configuration->ldapHost; $hostInfo = parse_url($host); if(!$hostInfo) { - throw new \Exception($this->l->t('Invalid Host')); + throw new \Exception(self::$l->t('Invalid Host')); } \OCP\Util::writeLog('user_ldap', 'Wiz: Attempting to connect ', \OCP\Util::DEBUG); $cr = $this->ldap->connect($host, $port); if(!is_resource($cr)) { - throw new \Exception($this->l->t('Invalid Host')); + throw new \Exception(self::$l->t('Invalid Host')); } \OCP\Util::writeLog('user_ldap', 'Wiz: Setting LDAP Options ', \OCP\Util::DEBUG); diff --git a/apps/user_ldap/lib/wizardresult.php b/apps/user_ldap/lib/WizardResult.php index 54f01cf59b8..1adce9a1389 100644 --- a/apps/user_ldap/lib/wizardresult.php +++ b/apps/user_ldap/lib/WizardResult.php @@ -23,7 +23,7 @@ * */ -namespace OCA\user_ldap\lib; +namespace OCA\User_LDAP; class WizardResult { protected $changes = array(); |