OCP\JSON::callCheck();
$subject = $_POST['ldap_clear_mapping'];
-if(\OCA\user_ldap\lib\Helper::clearMapping($subject)) {
+$helper = new \OCA\user_ldap\lib\Helper();
+if($helper->clearMapping($subject)) {
OCP\JSON::success();
} else {
$l=OC_L10N::get('user_ldap');
OCP\JSON::callCheck();
$prefix = $_POST['ldap_serverconfig_chooser'];
-if(\OCA\user_ldap\lib\Helper::deleteServerConfiguration($prefix)) {
+$helper = new \OCA\user_ldap\lib\Helper();
+if($helper->deleteServerConfiguration($prefix)) {
OCP\JSON::success();
} else {
$l=OC_L10N::get('user_ldap');
OCP\JSON::checkAppEnabled('user_ldap');
OCP\JSON::callCheck();
-$serverConnections = \OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes();
+$helper = new \OCA\user_ldap\lib\Helper();
+$serverConnections = $helper->getServerConfigurationPrefixes();
sort($serverConnections);
$lk = array_pop($serverConnections);
$ln = intval(str_replace('s', '', $lk));
*
* @author Dominik Schmidt
* @copyright 2011 Dominik Schmidt dev@dominik-schmidt.de
+* @copyright 2014 Arthur Schiwon <blizzz@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
OCP\App::registerAdmin('user_ldap', 'settings');
-$configPrefixes = OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes(true);
+$helper = new \OCA\user_ldap\lib\Helper();
+$configPrefixes = $helper->getServerConfigurationPrefixes(true);
$ldapWrapper = new OCA\user_ldap\lib\LDAP();
if(count($configPrefixes) === 1) {
$ocConfig = \OC::$server->getConfig();
OC_Group::useBackend($groupBackend);
}
-// add settings page to navigation
-$entry = array(
- 'id' => 'user_ldap_settings',
- 'order'=>1,
- 'href' => OCP\Util::linkTo( 'user_ldap', 'settings.php' ),
- 'name' => 'LDAP'
-);
-
OCP\Backgroundjob::registerJob('OCA\user_ldap\lib\Jobs');
+OCP\Backgroundjob::registerJob('\OCA\User_LDAP\Jobs\CleanUp');
+
if(OCP\App::isEnabled('user_webdavauth')) {
OCP\Util::writeLog('user_ldap',
'user_ldap and user_webdavauth are incompatible. You may experience unexpected behaviour',
$application->add(new OCA\user_ldap\Command\SetConfig());
$application->add(new OCA\user_ldap\Command\TestConfig());
$application->add(new OCA\user_ldap\Command\Search());
+$application->add(new OCA\user_ldap\Command\ShowRemnants());
$installedVersion = OCP\Config::getAppValue('user_ldap', 'installed_version');
$enableRawMode = version_compare($installedVersion, '0.4.1', '<');
-$configPrefixes = OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes(true);
+$helper = new \OCA\user_ldap\lib\Helper();
+$configPrefixes = $helper->getServerConfigurationPrefixes(true);
$ldap = new OCA\user_ldap\lib\LDAP();
foreach($configPrefixes as $config) {
$connection = new OCA\user_ldap\lib\Connection($ldap, $config);
}
protected function execute(InputInterface $input, OutputInterface $output) {
- $configPrefixes = Helper::getServerConfigurationPrefixes(true);
+ $helper = new Helper();
+ $configPrefixes = $helper->getServerConfigurationPrefixes(true);
$ldapWrapper = new LDAP();
$offset = intval($input->getOption('offset'));
}
protected function execute(InputInterface $input, OutputInterface $output) {
- $availableConfigs = Helper::getServerConfigurationPrefixes();
+ $helper = new Helper();
+ $availableConfigs = $helper->getServerConfigurationPrefixes();
$configID = $input->getArgument('configID');
if(!in_array($configID, $availableConfigs)) {
$output->writeln("Invalid configID");
}
protected function execute(InputInterface $input, OutputInterface $output) {
- $availableConfigs = Helper::getServerConfigurationPrefixes();
+ $helper = new Helper();
+ $availableConfigs = $helper->getServerConfigurationPrefixes();
$configID = $input->getArgument('configID');
if(!is_null($configID)) {
$configIDs[] = $configID;
--- /dev/null
+<?php
+/**
+ * Copyright (c) 2014 Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\user_ldap\Command;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+use OCA\user_ldap\lib\user\DeletedUsersIndex;
+use OCA\User_LDAP\lib\Connection;
+use OCA\User_LDAP\lib\Access;
+
+class ShowRemnants extends Command {
+
+ protected function configure() {
+ $this
+ ->setName('ldap:show-remnants')
+ ->setDescription('shows which users are not available on LDAP anymore, but have remnants in ownCloud.')
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $dui = new DeletedUsersIndex(
+ new \OC\Preferences(\OC_DB::getConnection()),
+ \OC::$server->getDatabaseConnection(),
+ $this->getAccess()
+ );
+
+ /** @var \Symfony\Component\Console\Helper\Table $table */
+ $table = $this->getHelperSet()->get('table');
+ $table->setHeaders(array(
+ 'ownCloud name', 'Display Name', 'LDAP UID', 'LDAP DN', 'Last Login',
+ 'Dir', 'Sharer'));
+ $rows = array();
+ $offset = 0;
+ do {
+ $resultSet = $dui->getUsers($offset);
+ $offset += count($resultSet);
+ foreach($resultSet as $user) {
+ $hAS = $user->getHasActiveShares() ? 'Y' : 'N';
+ $lastLogin = ($user->getLastLogin() > 0) ?
+ \OCP\Util::formatDate($user->getLastLogin()) : '-';
+ $rows[] = array(
+ $user->getOCName(),
+ $user->getDisplayName(),
+ $user->getUid(),
+ $user->getDN(),
+ $lastLogin,
+ $user->getHomePath(),
+ $hAS
+ );
+ }
+ } while (count($resultSet) === 10);
+
+ $table->setRows($rows);
+ $table->render($output);
+ }
+
+ protected function getAccess() {
+ $ldap = new \OCA\user_ldap\lib\LDAP();
+ $dummyConnection = new Connection($ldap, '', null);
+ $userManager = new \OCA\user_ldap\lib\user\Manager(
+ \OC::$server->getConfig(),
+ new \OCA\user_ldap\lib\FilesystemHelper(),
+ new \OCA\user_ldap\lib\LogWrapper(),
+ \OC::$server->getAvatarManager(),
+ new \OCP\Image()
+ );
+ $access = new Access($dummyConnection, $ldap, $userManager);
+ return $access;
+ }
+
+}
}
protected function execute(InputInterface $input, OutputInterface $output) {
- $availableConfigs = Helper::getServerConfigurationPrefixes();
+ $helper = new Helper();
+ $availableConfigs = $helper->getServerConfigurationPrefixes();
$configID = $input->getArgument('configID');
if(!in_array($configID, $availableConfigs)) {
$output->writeln("Invalid configID");
* @param boolean $isUser is it a user? otherwise group
* @return string with the LDAP DN on success, otherwise false
*/
- private function ocname2dn($name, $isUser) {
+ public function ocname2dn($name, $isUser) {
$table = $this->getMapTable($isUser);
$query = \OCP\DB::prepare('
}
/**
- * retrieves all known groups from the mappings table
- * @return array with the results
- *
- * retrieves all known groups from the mappings table
- */
- private function mappedGroups() {
- return $this->mappedComponents(false);
- }
-
- /**
- * retrieves all known users from the mappings table
- * @return array with the results
- *
- * retrieves all known users from the mappings table
- */
- private function mappedUsers() {
- return $this->mappedComponents(true);
- }
-
- /**
- * @param boolean $isUsers
- * @return array
+ * removes a user from the mappings table
+ * @param string $ocName
*/
- private function mappedComponents($isUsers) {
- $table = $this->getMapTable($isUsers);
-
- $query = \OCP\DB::prepare('
- SELECT `ldap_dn`, `owncloud_name`
- FROM `'. $table . '`'
- );
-
- return $query->execute()->fetchAll();
+ public function unmapUser($ocName) {
+ $table = $this->getMapTable(true);
+ $delete = \OCP\DB::prepare('
+ DELETE FROM `' . $table . '`
+ WHERE `owncloud_name` = ?
+ ');
+ $delete->execute(array($ocName));
}
/**
if($isUser) {
//make sure that email address is retrieved prior to login, so user
//will be notified when something is shared with him
- $this->userManager->get($ocName)->update();
+ $user = $this->userManager->get($ocName);
+ if($user instanceof user\User) {
+ $user->update();
+ }
}
return true;
}
$this->hasPagedResultSupport =
$this->ldap->hasPagedResultSupport();
+ $helper = new Helper();
$this->doNotValidate = !in_array($this->configPrefix,
- Helper::getServerConfigurationPrefixes());
+ $helper->getServerConfigurationPrefixes());
}
public function __destruct() {
* except the default (first) server shall be connected to.
*
*/
- static public function getServerConfigurationPrefixes($activeConfigurations = false) {
+ public function getServerConfigurationPrefixes($activeConfigurations = false) {
$referenceConfigkey = 'ldap_configuration_active';
$sql = '
* @return array an array with configprefix as keys
*
*/
- static public function getServerConfigurationHosts() {
+ public function getServerConfigurationHosts() {
$referenceConfigkey = 'ldap_host';
$query = '
* @param string $prefix the configuration prefix of the config to delete
* @return bool true on success, false otherwise
*/
- static public function deleteServerConfiguration($prefix) {
+ public function deleteServerConfiguration($prefix) {
//just to be on the safe side
\OCP\User::checkAdminUser();
* @param string $mapping either 'user' or 'group'
* @return bool true on success, false otherwise
*/
- static public function clearMapping($mapping) {
+ public function clearMapping($mapping) {
if($mapping === 'user') {
$table = '`*PREFIX*ldap_user_mapping`';
} else if ($mapping === 'group') {
* @param string $url the URL
* @return string|false domain as string on success, false otherwise
*/
- static public function getDomainFromURL($url) {
+ public function getDomainFromURL($url) {
$uinfo = parse_url($url);
if(!is_array($uinfo)) {
return false;
if(!is_null(self::$groupBE)) {
return self::$groupBE;
}
- $configPrefixes = Helper::getServerConfigurationPrefixes(true);
+ $helper = new Helper();
+ $configPrefixes = $helper->getServerConfigurationPrefixes(true);
$ldapWrapper = new LDAP();
if(count($configPrefixes) === 1) {
//avoid the proxy when there is only one LDAP server configured
--- /dev/null
+<?php
+/**
+ * Copyright (c) 2014 Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\User_LDAP\Jobs;
+
+use \OCA\user_ldap\User_Proxy;
+use \OCA\user_ldap\lib\Helper;
+use \OCA\user_ldap\lib\LDAP;
+
+/**
+ * Class CleanUp
+ *
+ * a Background job to clean up deleted users
+ *
+ * @package OCA\user_ldap\lib;
+ */
+class CleanUp extends \OC\BackgroundJob\TimedJob {
+ /**
+ * @var int $limit amount of users that should be checked per run
+ */
+ protected $limit = 50;
+
+ /**
+ * @var \OCP\UserInterface $userBackend
+ */
+ protected $userBackend;
+
+ /**
+ * @var \OCP\IConfig $ocConfig
+ */
+ protected $ocConfig;
+
+ /**
+ * @var \OCP\IDBConnection $db
+ */
+ protected $db;
+
+ /**
+ * @var Helper $ldapHelper
+ */
+ protected $ldapHelper;
+
+ /**
+ * @var int $defaultIntervalMin default interval in minutes
+ */
+ protected $defaultIntervalMin = 51;
+
+ public function __construct() {
+ $minutes = \OC::$server->getConfig()->getSystemValue(
+ 'ldapUserCleanupInterval', strval($this->defaultIntervalMin));
+ $this->setInterval(intval($minutes) * 60);
+ }
+
+ /**
+ * assigns the instances passed to run() to the class properties
+ * @param array $arguments
+ */
+ public function setArguments($arguments) {
+ //Dependency Injection is not possible, because the constructor will
+ //only get values that are serialized to JSON. I.e. whatever we would
+ //pass in app.php we do add here, except something else is passed e.g.
+ //in tests.
+
+ if(isset($arguments['helper'])) {
+ $this->ldapHelper = $arguments['helper'];
+ } else {
+ $this->ldapHelper = new Helper();
+ }
+
+ if(isset($arguments['userBackend'])) {
+ $this->userBackend = $arguments['userBackend'];
+ } else {
+ $this->userBackend = new User_Proxy(
+ $this->ldapHelper->getServerConfigurationPrefixes(true),
+ new LDAP()
+ );
+ }
+
+ if(isset($arguments['ocConfig'])) {
+ $this->ocConfig = $arguments['ocConfig'];
+ } else {
+ $this->ocConfig = \OC::$server->getConfig();
+ }
+
+ if(isset($arguments['db'])) {
+ $this->db = $arguments['db'];
+ } else {
+ $this->db = \OC::$server->getDatabaseConnection();
+ }
+ }
+
+ /**
+ * makes the background job do its work
+ * @param array $argument
+ */
+ public function run($argument) {
+ $this->setArguments($argument);
+
+ if(!$this->isCleanUpAllowed()) {
+ return;
+ }
+ $users = $this->getMappedUsers($this->limit, $this->getOffset());
+ if(!is_array($users)) {
+ //something wrong? Let's start from the beginning next time and
+ //abort
+ $this->setOffset(true);
+ return;
+ }
+ $resetOffset = $this->isOffsetResetNecessary(count($users));
+ $this->checkUsers($users);
+ $this->setOffset($resetOffset);
+ }
+
+ /**
+ * checks whether next run should start at 0 again
+ * @param int $resultCount
+ * @return bool
+ */
+ public function isOffsetResetNecessary($resultCount) {
+ return ($resultCount < $this->limit) ? true : false;
+ }
+
+ /**
+ * checks whether cleaning up LDAP users is allowed
+ * @return bool
+ */
+ public function isCleanUpAllowed() {
+ try {
+ if($this->haveDisabledConfigurations()) {
+ return false;
+ }
+ } catch (\Exception $e) {
+ return false;
+ }
+
+ $enabled = $this->isCleanUpEnabled();
+
+ return $enabled;
+ }
+
+ /**
+ * checks whether clean up is enabled by configuration
+ * @return bool
+ */
+ private function isCleanUpEnabled() {
+ return (bool)$this->ocConfig->getSystemValue(
+ 'ldapUserCleanupInterval', strval($this->defaultIntervalMin));
+ }
+
+ /**
+ * checks whether there is one or more disabled LDAP configurations
+ * @throws \Exception
+ * @return bool
+ */
+ private function haveDisabledConfigurations() {
+ $all = $this->ldapHelper->getServerConfigurationPrefixes(false);
+ $active = $this->ldapHelper->getServerConfigurationPrefixes(true);
+
+ if(!is_array($all) || !is_array($active)) {
+ throw new \Exception('Unexpected Return Value');
+ }
+
+ return count($all) !== count($active) || count($all) === 0;
+ }
+
+ /**
+ * checks users whether they are still existing
+ * @param array $users result from getMappedUsers()
+ */
+ private function checkUsers($users) {
+ foreach($users as $user) {
+ $this->checkUser($user);
+ }
+ }
+
+ /**
+ * checks whether a user is still existing in LDAP
+ * @param string[] $user
+ */
+ private function checkUser($user) {
+ if($this->userBackend->userExists($user['name'])) {
+ //still available, all good
+ return;
+ }
+
+ $this->ocConfig->setUserValue($user['name'], 'user_ldap', 'isDeleted', '1');
+ }
+
+ /**
+ * returns a batch of users from the mappings table
+ * @param int $limit
+ * @param int $offset
+ * @return array
+ */
+ public function getMappedUsers($limit, $offset) {
+ $query = $this->db->prepare('
+ SELECT
+ `ldap_dn` AS `dn`,
+ `owncloud_name` AS `name`,
+ `directory_uuid` AS `uuid`
+ FROM `*PREFIX*ldap_user_mapping`',
+ $limit,
+ $offset
+ );
+
+ $query->execute();
+ return $query->fetchAll();
+ }
+
+ /**
+ * gets the offset to fetch users from the mappings table
+ * @return int
+ */
+ private function getOffset() {
+ return $this->ocConfig->getAppValue('user_ldap', 'cleanUpJobOffset', 0);
+ }
+
+ /**
+ * sets the new offset for the next run
+ * @param bool $reset whether the offset should be set to 0
+ */
+ public function setOffset($reset = false) {
+ $newOffset = $reset ? 0 :
+ $this->getOffset() + $this->limit;
+ $this->ocConfig->setAppValue('user_ldap', 'cleanUpJobOffset', $newOffset);
+ }
+
+ /**
+ * returns the chunk size (limit in DB speak)
+ * @return int
+ */
+ public function getChunkSize() {
+ return $this->limit;
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * ownCloud – LDAP Helper
+ *
+ * @author Arthur Schiwon
+ * @copyright 2014 Arthur Schiwon <blizzz@owncloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\user_ldap\lib\user;
+
+use OCA\user_ldap\lib\user\OfflineUser;
+use OCA\user_ldap\lib\Access;
+
+/**
+ * Class DeletedUsersIndex
+ * @package OCA\User_LDAP
+ */
+class DeletedUsersIndex {
+ /**
+ * @var \OC\Preferences $preferences
+ */
+ protected $preferences;
+
+ /**
+ * @var \OCP\IDBConnection $db
+ */
+ protected $db;
+
+ /**
+ * @var \OCA\user_ldap\lib\Access $access
+ */
+ protected $access;
+
+ /**
+ * @var int $limit
+ */
+ protected $limit = 10;
+
+ /**
+ * @var array $deletedUsers
+ */
+ protected $deletedUsers = false;
+
+ public function __construct(\OC\Preferences $preferences, \OCP\IDBConnection $db, Access $access) {
+ $this->preferences = $preferences;
+ $this->db = $db;
+ $this->access = $access;
+ }
+
+ /**
+ * returns key to be used against $this->deletedUsers
+ * @param int $limit
+ * @param int $offset
+ * @return string
+ */
+ private function getDeletedUsersCacheKey($limit, $offset) {
+ return strval($limit) . '.' . strval($offset);
+ }
+
+ /**
+ * reads LDAP users marked as deleted from the database
+ * @param int $offset
+ * @return OCA\user_ldap\lib\user\OfflineUser[]
+ */
+ private function fetchDeletedUsers($offset) {
+ $deletedUsers = $this->preferences->getUsersForValue(
+ 'user_ldap', 'isDeleted', '1', $this->limit, $offset);
+ $key = $this->getDeletedUsersCacheKey($this->limit, $offset);
+
+ $userObjects = array();
+ foreach($deletedUsers as $user) {
+ $userObjects[] = new OfflineUser($user, $this->preferences, $this->db, $this->access);
+ }
+
+ $this->deletedUsers[$key] = $userObjects;
+ if(count($userObjects) > 0) {
+ $this->hasUsers();
+ }
+ return $this->deletedUsers[$key];
+ }
+
+ /**
+ * returns all LDAP users that are marked as deleted
+ * @param int|null $offset
+ * @return OCA\user_ldap\lib\user\OfflineUser[]
+ */
+ public function getUsers($offset = null) {
+ $key = $this->getDeletedUsersCacheKey($this->limit, $offset);
+ if(is_array($this->deletedUsers) && isset($this->deletedUsers[$key])) {
+ return $this->deletedUsers[$key];
+ }
+ return $this->fetchDeletedUsers($offset);
+ }
+
+ /**
+ * whether at least one user was detected as deleted
+ * @return bool
+ */
+ public function hasUsers() {
+ if($this->deletedUsers === false) {
+ $this->fetchDeletedUsers(0);
+ }
+ foreach($this->deletedUsers as $batch) {
+ if(count($batch) > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
public function username2dn($name);
+ //temporary hack for LDAP user cleanup, will be removed in OC 8.
+ public function ocname2dn($name, $isUser);
+
}
use OCA\user_ldap\lib\user\User;
use OCA\user_ldap\lib\LogWrapper;
use OCA\user_ldap\lib\FilesystemHelper;
+use OCA\user_ldap\lib\user\OfflineUser;
/**
* Manager
}
}
+ /**
+ * Checks whether the specified user is marked as deleted
+ * @param string $id the ownCloud user name
+ * @return bool
+ */
+ public function isDeletedUser($id) {
+ $isDeleted = $this->ocConfig->getUserValue(
+ $id, 'user_ldap', 'isDeleted', 0);
+ return intval($isDeleted) === 1;
+ }
+
+ /**
+ * creates and returns an instance of OfflineUser for the specified user
+ * @param string $id
+ * @return \OCA\user_ldap\lib\user\OfflineUser
+ */
+ public function getDeletedUser($id) {
+ return new OfflineUser(
+ $id,
+ new \OC\Preferences(\OC_DB::getConnection()),
+ \OC::$server->getDatabaseConnection(),
+ $this->access);
+ }
+
+ protected function createInstancyByUserName($id) {
+ //most likely a uid. Check whether it is a deleted user
+ if($this->isDeletedUser($id)) {
+ return $this->getDeletedUser($id);
+ }
+ $dn = $this->access->username2dn($id);
+ if($dn !== false) {
+ return $this->createAndCache($dn, $id);
+ }
+ throw new \Exception('Could not create User instance');
+ }
+
/**
* @brief returns a User object by it's DN or ownCloud username
* @param string the DN or username of the user
- * @return \OCA\user_ldap\lib\User | null
+ * @return \OCA\user_ldap\lib\user\User | \OCA\user_ldap\lib\user\OfflineUser | null
*/
public function get($id) {
$this->checkAccess();
return $this->users['byUid'][$id];
}
- if(!$this->access->stringResemblesDN($id) ) {
- //most likely a uid
- $dn = $this->access->username2dn($id);
- if($dn !== false) {
- return $this->createAndCache($dn, $id);
- }
- } else {
- //so it's a DN
+ if($this->access->stringResemblesDN($id) ) {
$uid = $this->access->dn2username($id);
if($uid !== false) {
return $this->createAndCache($id, $uid);
}
}
- //either funny uid or invalid. Assume funny to be on the safe side.
- $dn = $this->access->username2dn($id);
- if($dn !== false) {
- return $this->createAndCache($dn, $id);
+
+ try {
+ $user = $this->createInstancyByUserName($id);
+ return $user;
+ } catch (\Exception $e) {
+ return null;
}
- return null;
}
}
--- /dev/null
+<?php
+
+/**
+ * ownCloud – LDAP User
+ *
+ * @author Arthur Schiwon
+ * @copyright 2014 Arthur Schiwon blizzz@owncloud.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\user_ldap\lib\user;
+
+use OCA\user_ldap\lib\Access;
+
+class OfflineUser {
+ /**
+ * @var string $ocName
+ */
+ protected $ocName;
+ /**
+ * @var string $dn
+ */
+ protected $dn;
+ /**
+ * @var string $uid the UID as provided by LDAP
+ */
+ protected $uid;
+ /**
+ * @var string $displayName
+ */
+ protected $displayName;
+ /**
+ * @var string $homePath
+ */
+ protected $homePath;
+ /**
+ * @var string $lastLogin the timestamp of the last login
+ */
+ protected $lastLogin;
+ /**
+ * @var string $email
+ */
+ protected $email;
+ /**
+ * @var bool $hasActiveShares
+ */
+ protected $hasActiveShares;
+ /**
+ * @var \OC\Preferences $preferences
+ */
+ protected $preferences;
+ /**
+ * @var \OCP\IDBConnection $db
+ */
+ protected $db;
+ /**
+ * @var \OCA\user_ldap\lib\Access
+ */
+ protected $access;
+
+ public function __construct($ocName, \OC\Preferences $preferences, \OCP\IDBConnection $db, Access $access) {
+ $this->ocName = $ocName;
+ $this->preferences = $preferences;
+ $this->db = $db;
+ $this->access = $access;
+ $this->fetchDetails();
+ }
+
+ /**
+ * exports the user details in an assoc array
+ * @return array
+ */
+ public function export() {
+ $data = array();
+ $data['ocName'] = $this->getOCName();
+ $data['dn'] = $this->getDN();
+ $data['uid'] = $this->getUID();
+ $data['displayName'] = $this->getDisplayName();
+ $data['homePath'] = $this->getHomePath();
+ $data['lastLogin'] = $this->getLastLogin();
+ $data['email'] = $this->getEmail();
+ $data['hasActiveShares'] = $this->getHasActiveShares();
+
+ return $data;
+ }
+
+ /**
+ * getter for ownCloud internal name
+ * @return string
+ */
+ public function getOCName() {
+ return $this->ocName;
+ }
+
+ /**
+ * getter for LDAP uid
+ * @return string
+ */
+ public function getUID() {
+ return $this->uid;
+ }
+
+ /**
+ * getter for LDAP DN
+ * @return string
+ */
+ public function getDN() {
+ return $this->dn;
+ }
+
+ /**
+ * getter for display name
+ * @return string
+ */
+ public function getDisplayName() {
+ return $this->displayName;
+ }
+
+ /**
+ * getter for email
+ * @return string
+ */
+ public function getEmail() {
+ return $this->email;
+ }
+
+ /**
+ * getter for home directory path
+ * @return string
+ */
+ public function getHomePath() {
+ return $this->homePath;
+ }
+
+ /**
+ * getter for the last login timestamp
+ * @return int
+ */
+ public function getLastLogin() {
+ return intval($this->lastLogin);
+ }
+
+ /**
+ * getter for having active shares
+ * @return bool
+ */
+ public function getHasActiveShares() {
+ return $this->hasActiveShares;
+ }
+
+ /**
+ * reads the user details
+ */
+ protected function fetchDetails() {
+ $properties = array (
+ 'displayName' => 'user_ldap',
+ 'uid' => 'user_ldap',
+ 'homePath' => 'user_ldap',
+ 'email' => 'settings',
+ 'lastLogin' => 'login'
+ );
+ foreach($properties as $property => $app) {
+ $this->$property = $this->preferences->getValue($this->ocName, $app, $property, '');
+ }
+
+ $dn = $this->access->ocname2dn($this->ocName, true);
+ $this->dn = ($dn !== false) ? $dn : '';
+
+ $this->determineShares();
+ }
+
+
+ /**
+ * finds out whether the user has active shares. The result is stored in
+ * $this->hasActiveShares
+ */
+ protected function determineShares() {
+ $query = $this->db->prepare('
+ SELECT COUNT(`uid_owner`)
+ FROM `*PREFIX*share`
+ WHERE `uid_owner` = ?
+ ', 1);
+ $query->execute(array($this->ocName));
+ $sResult = $query->fetchColumn(0);
+ if(intval($sResult) === 1) {
+ $this->hasActiveShares = true;
+ return;
+ }
+
+ $query = $this->db->prepare('
+ SELECT COUNT(`owner`)
+ FROM `*PREFIX*share_external`
+ WHERE `owner` = ?
+ ', 1);
+ $query->execute(array($this->ocName));
+ $sResult = $query->fetchColumn(0);
+ if(intval($sResult) === 1) {
+ $this->hasActiveShares = true;
+ return;
+ }
+
+ $this->hasActiveShares = false;
+ }
+}
return true;
}
+ /**
+ * Stores a key-value pair in relation to this user
+ * @param string $key
+ * @param string $value
+ */
+ private function store($key, $value) {
+ $this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
+ }
+
+ /**
+ * Stores the display name in the databae
+ * @param string $displayName
+ */
+ public function storeDisplayName($displayName) {
+ $this->store('displayName', $displayName);
+ }
+
+ /**
+ * Stores the LDAP Username in the Database
+ * @param string $userName
+ */
+ public function storeLDAPUserName($userName) {
+ $this->store('uid', $userName);
+ }
+
/**
* @brief checks whether an update method specified by feature was run
* already. If not, it will marked like this, because it is expected that
//this did not help :(
//Let's see whether we can parse the Host URL and convert the domain to
//a base DN
- $domain = Helper::getDomainFromURL($this->configuration->ldapHost);
+ $helper = new Helper();
+ $domain = $helper->getDomainFromURL($this->configuration->ldapHost);
if(!$domain) {
return false;
}
// fill template
$tmpl = new OCP\Template('user_ldap', 'settings');
-$prefixes = \OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes();
-$hosts = \OCA\user_ldap\lib\Helper::getServerConfigurationHosts();
+$helper = new \OCA\user_ldap\lib\Helper();
+$prefixes = $helper->getServerConfigurationPrefixes();
+$hosts = $helper->getServerConfigurationHosts();
$wizardHtml = '';
$toc = array();
$result = $statement->execute();
$this->assertEquals(2, $result->fetchOne());
- Helper::clearMapping('user');
+ $helper = new Helper();
+ $helper->clearMapping('user');
$result = $statement->execute();
$this->assertEquals(0, $result->fetchOne());
--- /dev/null
+<?php
+/**
+ * Copyright (c) 2014 Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\user_ldap\tests;
+
+class Test_CleanUp extends \PHPUnit_Framework_TestCase {
+ public function getMocks() {
+ $mocks = array();
+ $mocks['userBackend'] =
+ $this->getMockBuilder('\OCA\user_ldap\User_Proxy')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mocks['ocConfig'] = $this->getMock('\OCP\IConfig');
+ $mocks['db'] = $this->getMock('\OCP\IDBConnection');
+ $mocks['helper'] = $this->getMock('\OCA\user_ldap\lib\Helper');
+
+ return $mocks;
+ }
+
+ /**
+ * clean up job must not run when there are disabled configurations
+ */
+ public function test_runNotAllowedByDisabledConfigurations() {
+ $args = $this->getMocks();
+ $args['helper']->expects($this->exactly(2))
+ ->method('getServerConfigurationPrefixes')
+ ->will($this->onConsecutiveCalls(
+ array_pad(array(), 4, true),
+ array_pad(array(), 3, true))
+ );
+
+ $args['ocConfig']->expects($this->never())
+ ->method('getSystemValue');
+
+ $bgJob = new \OCA\User_LDAP\Jobs\CleanUp();
+ $bgJob->setArguments($args);
+
+ $result = $bgJob->isCleanUpAllowed();
+ $this->assertSame(false, $result);
+ }
+
+ /**
+ * clean up job must not run when LDAP Helper is broken i.e.
+ * returning unexpected results
+ */
+ public function test_runNotAllowedByBrokenHelper() {
+ $args = $this->getMocks();
+ $args['helper']->expects($this->exactly(2))
+ ->method('getServerConfigurationPrefixes')
+ ->will($this->returnValue(null) );
+
+ $args['ocConfig']->expects($this->never())
+ ->method('getSystemValue');
+
+ $bgJob = new \OCA\User_LDAP\Jobs\CleanUp();
+ $bgJob->setArguments($args);
+
+ $result = $bgJob->isCleanUpAllowed();
+ $this->assertSame(false, $result);
+ }
+
+ /**
+ * clean up job must not run when it is not enabled
+ */
+ public function test_runNotAllowedBySysConfig() {
+ $args = $this->getMocks();
+ $args['helper']->expects($this->exactly(2))
+ ->method('getServerConfigurationPrefixes')
+ ->will($this->onConsecutiveCalls(
+ array_pad(array(), 4, true),
+ array_pad(array(), 4, true))
+ );
+
+ $args['ocConfig']->expects($this->once())
+ ->method('getSystemValue')
+ ->will($this->returnValue(false));
+
+ $bgJob = new \OCA\User_LDAP\Jobs\CleanUp();
+ $bgJob->setArguments($args);
+
+ $result = $bgJob->isCleanUpAllowed();
+ $this->assertSame(false, $result);
+ }
+
+ /**
+ * clean up job is allowed to run
+ */
+ public function test_runIsAllowed() {
+ $args = $this->getMocks();
+ $args['helper']->expects($this->exactly(2))
+ ->method('getServerConfigurationPrefixes')
+ ->will($this->onConsecutiveCalls(
+ array_pad(array(), 4, true),
+ array_pad(array(), 4, true))
+ );
+
+ $args['ocConfig']->expects($this->once())
+ ->method('getSystemValue')
+ ->will($this->returnValue(true));
+
+ $bgJob = new \OCA\User_LDAP\Jobs\CleanUp();
+ $bgJob->setArguments($args);
+
+ $result = $bgJob->isCleanUpAllowed();
+ $this->assertSame(true, $result);
+ }
+
+ /**
+ * test whether sql is OK
+ */
+ public function test_getMappedUsers() {
+ $args = $this->getMocks();
+
+ $bgJob = new \OCA\User_LDAP\Jobs\CleanUp();
+ $bgJob->setArguments($args);
+
+ if(version_compare(\PHPUnit_Runner_Version::id(), '3.8', '<')) {
+ //otherwise we run into
+ //https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103
+ $this->markTestIncomplete();
+ }
+
+ $stmt = $this->getMock('\Doctrine\DBAL\Driver\Statement');
+
+ $args['db']->expects($this->once())
+ ->method('prepare')
+ ->will($this->returnValue($stmt));
+
+ $bgJob->getMappedUsers(0, $bgJob->getChunkSize());
+ }
+
+ /**
+ * check whether offset will be reset when it needs to
+ */
+ public function test_OffsetResetIsNecessary() {
+ $args = $this->getMocks();
+
+ $bgJob = new \OCA\User_LDAP\Jobs\CleanUp();
+ $bgJob->setArguments($args);
+
+ $result = $bgJob->isOffsetResetNecessary($bgJob->getChunkSize() - 1);
+ $this->assertSame(true, $result);
+ }
+
+ /**
+ * make sure offset is not reset when it is not due
+ */
+ public function test_OffsetResetIsNotNecessary() {
+ $args = $this->getMocks();
+
+ $bgJob = new \OCA\User_LDAP\Jobs\CleanUp();
+ $bgJob->setArguments($args);
+
+ $result = $bgJob->isOffsetResetNecessary($bgJob->getChunkSize());
+ $this->assertSame(false, $result);
+ }
+
+}
+
$access->expects($this->never())
->method('dn2username');
- $access->expects($this->exactly(2))
+ $access->expects($this->exactly(1))
->method('username2dn')
->with($this->equalTo($uid))
->will($this->returnValue(false));
->method('fetchListOfUsers')
->will($this->returnCallback(function($filter) {
if($filter === 'roland') {
- return array('dnOfRoland,dc=test');
+ return array(array('dn' => 'dnOfRoland,dc=test'));
}
return array();
}));
$this->assertFalse($result);
}
+ public function testDeleteUserCancel() {
+ $access = $this->getAccessMock();
+ $backend = new UserLDAP($access);
+ $result = $backend->deleteUser('notme');
+ $this->assertFalse($result);
+ }
+
+ public function testDeleteUserSuccess() {
+ $access = $this->getAccessMock();
+ $backend = new UserLDAP($access);
+
+ $pref = \OC::$server->getConfig();
+ $pref->setUserValue('jeremy', 'user_ldap', 'isDeleted', 1);
+
+ $result = $backend->deleteUser('jeremy');
+ $this->assertTrue($result);
+ }
+
/**
* Prepares the Access mock for getUsers tests
* @param \OCA\user_ldap\lib\Access $access mock
namespace OCA\user_ldap;
use OCA\user_ldap\lib\BackendUtility;
+use OCA\user_ldap\lib\user\OfflineUser;
+use OCA\User_LDAP\lib\User\User;
class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
+ /**
+ * @var string[] $homesToKill
+ */
+ protected $homesToKill = array();
+
/**
* checks whether the user is allowed to change his avatar in ownCloud
* @param string $uid the ownCloud user name
*/
public function canChangeAvatar($uid) {
$user = $this->access->userManager->get($uid);
- if(is_null($user)) {
+ if(!$user instanceof User) {
return false;
}
if($user->getAvatarImage() === false) {
$uid = $this->access->escapeFilterPart($uid);
//find out dn of the user name
+ $attrs = array($this->access->connection->ldapUserDisplayName, 'dn',
+ 'uid', 'samaccountname');
$filter = \OCP\Util::mb_str_replace(
'%uid', $uid, $this->access->connection->ldapLoginFilter, 'UTF-8');
- $ldap_users = $this->access->fetchListOfUsers($filter, 'dn');
- if(count($ldap_users) < 1) {
+ $users = $this->access->fetchListOfUsers($filter, $attrs);
+ if(count($users) < 1) {
return false;
}
- $dn = $ldap_users[0];
+ $dn = $users[0]['dn'];
$user = $this->access->userManager->get($dn);
- if(is_null($user)) {
+ if(!$user instanceof User) {
\OCP\Util::writeLog('user_ldap',
'LDAP Login: Could not get user object for DN ' . $dn .
'. Maybe the LDAP entry has no set display name attribute?',
}
$user->markLogin();
+ if(isset($users[0][$this->access->connection->ldapUserDisplayName])) {
+ $dpn = $users[0][$this->access->connection->ldapUserDisplayName];
+ $user->storeDisplayName($dpn);
+ }
+ if(isset($users[0]['uid'])) {
+ $user->storeLDAPUserName($users[0]['uid']);
+ } else if(isset($users[0]['samaccountname'])) {
+ $user->storeLDAPUserName($users[0]['samaccountname']);
+ }
return $user->getUsername();
}
$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;
}
$dn = $user->getDN();
//check if user really still exists by reading its entry
}
/**
- * delete a user
+ * returns whether a user was deleted in LDAP
+ *
* @param string $uid The username of the user to delete
* @return bool
- *
- * Deletes a user
*/
public function deleteUser($uid) {
- return false;
+ $pref = \OC::$server->getConfig();
+ $marked = $pref->getUserValue($uid, 'user_ldap', 'isDeleted', 0);
+ if(intval($marked) === 0) {
+ \OC::$server->getLogger()->notice(
+ 'User '.$uid . ' is not marked as deleted, not cleaning up.',
+ array('app' => 'user_ldap'));
+ return false;
+ }
+ \OC::$server->getLogger()->info('Cleaning up after user ' . $uid,
+ array('app' => 'user_ldap'));
+
+ //Get Home Directory out of user preferences so we can return it later,
+ //necessary for removing directories as done by OC_User.
+ $home = $pref->getUserValue($uid, 'user_ldap', 'homePath', '');
+ $this->homesToKill[$uid] = $home;
+ $this->access->unmapUser($uid);
+
+ return true;
}
/**
* get the user's home directory
* @param string $uid the username
- * @return boolean
+ * @return string|bool
*/
public function getHome($uid) {
// user Exists check required as it is not done in user proxy!
return false;
}
+ if(isset($this->homesToKill[$uid]) && !empty($this->homesToKill[$uid])) {
+ //a deleted user who needs some clean up
+ return $this->homesToKill[$uid];
+ }
+
$cacheKey = 'getHome'.$uid;
if($this->access->connection->isCached($cacheKey)) {
return $this->access->connection->getFromCache($cacheKey);
}
+ $pref = \OC::$server->getConfig();
if(strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0) {
$attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
$homedir = $this->access->readAttribute(
\OC::$SERVERROOT.'/data' ) . '/' . $homedir[0];
}
$this->access->connection->writeToCache($cacheKey, $homedir);
+ //we need it to store it in the DB as well in case a user gets
+ //deleted so we can clean up afterwards
+ $pref->setUserValue($uid, 'user_ldap', 'homePath', $homedir);
+ //TODO: if home directory changes, the old one needs to be removed.
return $homedir;
}
}
//false will apply default behaviour as defined and done by OC_User
$this->access->connection->writeToCache($cacheKey, false);
+ $pref->setUserValue($uid, 'user_ldap', 'homePath', '');
return false;
}
* Deletes a user
*/
public function deleteUser($uid) {
- return false;
+ return $this->handleRequest($uid, 'deleteUser', array($uid));
}
/**
/**
* Where user files are stored; this defaults to ``data/`` in the ownCloud
- * directory. The SQLite database is also stored here, when you use SQLite. (SQLite is
+ * directory. The SQLite database is also stored here, when you use SQLite. (SQLite is
* available only in ownCloud Community Edition)
*/
'datadirectory' => '/var/www/owncloud/data',
'OC\Preview\MarkDown'
),
+/**
+ * LDAP
+ *
+ * Global settings used by LDAP User and Group Backend
+ */
+
+/**
+ * defines the interval in minutes for the background job that checks user
+ * existance and marks them as ready to be cleaned up. The number is always
+ * minutes. Setting it to 0 disables the feature.
+ * See command line (occ) methods ldap:show-remnants and user:delete
+ */
+'ldapUserCleanupInterval' => 51,
+
/**
* Maintenance
--- /dev/null
+<?php
+/**
+ * Copyright (c) 2014 Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OC\Core\Command\User;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Input\InputArgument;
+
+class Delete extends Command {
+ protected function configure() {
+ $this
+ ->setName('user:delete')
+ ->setDescription('deletes the specified user')
+ ->addArgument(
+ 'uid',
+ InputArgument::REQUIRED,
+ 'the username'
+ );
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $wasSuccessful = \OC_User::deleteUser($input->getArgument('uid'));
+ if($wasSuccessful === true) {
+ $output->writeln('The specified user was deleted');
+ return;
+ }
+ $output->writeln('<error>The specified could not be deleted. Please check the logs.</error>');
+ }
+}
$application->add(new OC\Core\Command\User\Report());
$application->add(new OC\Core\Command\User\ResetPassword(\OC::$server->getUserManager()));
$application->add(new OC\Core\Command\User\LastSeen());
+$application->add(new OC\Core\Command\User\Delete());
// no changes
return true;
}
-
+
$affectedRows = 0;
if (!$exists && $preCondition === null) {
* @param string $app
* @param string $key
* @param string $value
+ * @param int|null $limit
+ * @param int|null $offset
* @return array
*/
- public function getUsersForValue($app, $key, $value) {
+ public function getUsersForValue($app, $key, $value, $limit = null, $offset = null) {
$users = array();
$query = 'SELECT `userid` '
$query .= ' `configvalue` = ?';
}
- $result = $this->conn->executeQuery($query, array($app, $key, $value));
+ $stmt = $this->conn->prepare($query, $limit, $offset);
+ $stmt->execute(array($app, $key, $value));
- while ($row = $result->fetch()) {
+ while ($row = $stmt->fetch()) {
$users[] = $row['userid'];
}
self::getManager()->delete($uid);
}
- return true;
+ return $result;
} else {
return false;
}