[Pkg-owncloud-commits] [owncloud] 24/90: LDAP User Cleanup

David Prévot taffit at moszumanska.debian.org
Fri Feb 6 21:10:43 UTC 2015


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository owncloud.

commit dd8ba68e072543e4814674aae8b5943e823697a6
Author: Arthur Schiwon <blizzz at owncloud.com>
Date:   Thu Aug 21 17:59:13 2014 +0200

    LDAP User Cleanup
    
    background job for user clean up
    
    adjust user backend for clean up
    
    register background job
    
    remove dead code
    
    dependency injection
    
    make Helper non-static for proper testing
    
    check whether it is OK to run clean up job. Do not forget to pass arguments.
    
    use correct method to get the config from server
    
    methods can be private, proper indirect testing is given
    
    no automatic user deletion
    
    make limit readable for test purposes
    
    make method less complex
    
    add first tests
    
    let preferences accept limit and offset for getUsersForValue
    
    DI via constructor does not work for background jobs
    
    after detecting, now we have retrieving deleted users and their details
    
    we need this method to be public for now
    
    finalize export method, add missing getter
    
    clean up namespaces and get rid of unnecessary files
    
    helper is not static anymore
    
    cleanup according to scrutinizer
    
    add cli tool to show deleted users
    
    uses are necessary after recent namespace change
    
    also remove user from mappings table on deletion
    
    add occ command to delete users
    
    fix use statement
    
    improve output
    
    big fixes / improvements
    
    PHP doc
    
    return true in userExists early for cleaning up deleted users
    
    bump version
    
    control state and interval with one config.php setting, now ldapUserCleanupInterval. 0 will disable it. enabled by default.
    
    improve doc
    
    rename cli method to be consistent with  others
    
    introduce ldapUserCleanupInterval in sample config
    
    don't show last login as unix epoche start when no  login happend
    
    less log output
    
    consistent namespace for OfflineUser
    
    rename GarbageCollector to DeletedUsersIndex and move it to user subdir
    
    fix unit tests
    
    add tests for deleteUser
    
    more test adjustements
---
 apps/user_ldap/ajax/clearMappings.php            |   3 +-
 apps/user_ldap/ajax/deleteConfiguration.php      |   3 +-
 apps/user_ldap/ajax/getNewServerConfigPrefix.php |   3 +-
 apps/user_ldap/appinfo/app.php                   |  14 +-
 apps/user_ldap/appinfo/register_command.php      |   1 +
 apps/user_ldap/appinfo/update.php                |   3 +-
 apps/user_ldap/appinfo/version                   |   2 +-
 apps/user_ldap/command/search.php                |   3 +-
 apps/user_ldap/command/setconfig.php             |   3 +-
 apps/user_ldap/command/showconfig.php            |   3 +-
 apps/user_ldap/command/showremnants.php          |  81 ++++++++
 apps/user_ldap/command/testconfig.php            |   3 +-
 apps/user_ldap/lib/access.php                    |  47 ++---
 apps/user_ldap/lib/connection.php                |   3 +-
 apps/user_ldap/lib/helper.php                    |  10 +-
 apps/user_ldap/lib/jobs.php                      |   3 +-
 apps/user_ldap/lib/jobs/cleanup.php              | 241 +++++++++++++++++++++++
 apps/user_ldap/lib/user/deletedusersindex.php    | 125 ++++++++++++
 apps/user_ldap/lib/user/iusertools.php           |   3 +
 apps/user_ldap/lib/user/manager.php              |  59 ++++--
 apps/user_ldap/lib/user/offlineuser.php          | 217 ++++++++++++++++++++
 apps/user_ldap/lib/user/user.php                 |  25 +++
 apps/user_ldap/lib/wizard.php                    |   3 +-
 apps/user_ldap/settings.php                      |   5 +-
 apps/user_ldap/tests/helper.php                  |   3 +-
 apps/user_ldap/tests/jobs/cleanup.php            | 164 +++++++++++++++
 apps/user_ldap/tests/user/manager.php            |   2 +-
 apps/user_ldap/tests/user_ldap.php               |  20 +-
 apps/user_ldap/user_ldap.php                     |  69 ++++++-
 apps/user_ldap/user_proxy.php                    |   2 +-
 config/config.sample.php                         |  16 +-
 core/command/user/delete.php                     |  36 ++++
 core/register_command.php                        |   1 +
 lib/private/preferences.php                      |  11 +-
 lib/private/user.php                             |   2 +-
 35 files changed, 1094 insertions(+), 95 deletions(-)

diff --git a/apps/user_ldap/ajax/clearMappings.php b/apps/user_ldap/ajax/clearMappings.php
index 9118d58..5b4fd4b 100644
--- a/apps/user_ldap/ajax/clearMappings.php
+++ b/apps/user_ldap/ajax/clearMappings.php
@@ -27,7 +27,8 @@ OCP\JSON::checkAppEnabled('user_ldap');
 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');
diff --git a/apps/user_ldap/ajax/deleteConfiguration.php b/apps/user_ldap/ajax/deleteConfiguration.php
index ade5711..268468e 100644
--- a/apps/user_ldap/ajax/deleteConfiguration.php
+++ b/apps/user_ldap/ajax/deleteConfiguration.php
@@ -27,7 +27,8 @@ OCP\JSON::checkAppEnabled('user_ldap');
 OCP\JSON::callCheck();
 
 $prefix = $_POST['ldap_serverconfig_chooser'];
-if(\OCA\user_ldap\lib\Helper::deleteServerConfiguration($prefix)) {
+$helper = new \OCA\user_ldap\lib\Helper();
+if($helper->deleteServerConfiguration($prefix)) {
 	OCP\JSON::success();
 } else {
 	$l=OC_L10N::get('user_ldap');
diff --git a/apps/user_ldap/ajax/getNewServerConfigPrefix.php b/apps/user_ldap/ajax/getNewServerConfigPrefix.php
index 1c68b2e..ce6c5ae 100644
--- a/apps/user_ldap/ajax/getNewServerConfigPrefix.php
+++ b/apps/user_ldap/ajax/getNewServerConfigPrefix.php
@@ -26,7 +26,8 @@ OCP\JSON::checkAdminUser();
 OCP\JSON::checkAppEnabled('user_ldap');
 OCP\JSON::callCheck();
 
-$serverConnections = \OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes();
+$helper = new \OCA\user_ldap\lib\Helper();
+$serverConnections = $helper->getServerConfigurationPrefixes();
 sort($serverConnections);
 $lk = array_pop($serverConnections);
 $ln = intval(str_replace('s', '', $lk));
diff --git a/apps/user_ldap/appinfo/app.php b/apps/user_ldap/appinfo/app.php
index a26c770..a931d97 100644
--- a/apps/user_ldap/appinfo/app.php
+++ b/apps/user_ldap/appinfo/app.php
@@ -5,6 +5,7 @@
 *
 * @author Dominik Schmidt
 * @copyright 2011 Dominik Schmidt dev at dominik-schmidt.de
+* @copyright 2014 Arthur Schiwon <blizzz at 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
@@ -23,7 +24,8 @@
 
 OCP\App::registerAdmin('user_ldap', 'settings');
 
-$configPrefixes = OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes(true);
+$helper = new \OCA\user_ldap\lib\Helper();
+$configPrefixes = $helper->getServerConfigurationPrefixes(true);
 $ldapWrapper = new OCA\user_ldap\lib\LDAP();
 if(count($configPrefixes) === 1) {
 	$ocConfig = \OC::$server->getConfig();
@@ -47,15 +49,9 @@ if(count($configPrefixes) > 0) {
 	OC_Group::useBackend($groupBackend);
 }
 
-// add settings page to navigation
-$entry = array(
-	'id' => 'user_ldap_settings',
-	'order'=>1,
-	'href' => OCP\Util::linkTo( 'user_ldap', 'settings.php' ),
-	'name' => 'LDAP'
-);
-
 OCP\Backgroundjob::registerJob('OCA\user_ldap\lib\Jobs');
+OCP\Backgroundjob::registerJob('\OCA\User_LDAP\Jobs\CleanUp');
+
 if(OCP\App::isEnabled('user_webdavauth')) {
 	OCP\Util::writeLog('user_ldap',
 		'user_ldap and user_webdavauth are incompatible. You may experience unexpected behaviour',
diff --git a/apps/user_ldap/appinfo/register_command.php b/apps/user_ldap/appinfo/register_command.php
index efce2d0..ff8871e 100644
--- a/apps/user_ldap/appinfo/register_command.php
+++ b/apps/user_ldap/appinfo/register_command.php
@@ -10,3 +10,4 @@ $application->add(new OCA\user_ldap\Command\ShowConfig());
 $application->add(new OCA\user_ldap\Command\SetConfig());
 $application->add(new OCA\user_ldap\Command\TestConfig());
 $application->add(new OCA\user_ldap\Command\Search());
+$application->add(new OCA\user_ldap\Command\ShowRemnants());
diff --git a/apps/user_ldap/appinfo/update.php b/apps/user_ldap/appinfo/update.php
index 5fad23d..0354cf2 100644
--- a/apps/user_ldap/appinfo/update.php
+++ b/apps/user_ldap/appinfo/update.php
@@ -10,7 +10,8 @@ if($state === 'unset') {
 $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);
diff --git a/apps/user_ldap/appinfo/version b/apps/user_ldap/appinfo/version
index 6f2743d..0bfccb0 100644
--- a/apps/user_ldap/appinfo/version
+++ b/apps/user_ldap/appinfo/version
@@ -1 +1 @@
-0.4.4
+0.4.5
diff --git a/apps/user_ldap/command/search.php b/apps/user_ldap/command/search.php
index e202555..d826303 100644
--- a/apps/user_ldap/command/search.php
+++ b/apps/user_ldap/command/search.php
@@ -74,7 +74,8 @@ class Search extends Command {
 	}
 
 	protected function execute(InputInterface $input, OutputInterface $output) {
-		$configPrefixes = Helper::getServerConfigurationPrefixes(true);
+		$helper = new Helper();
+		$configPrefixes = $helper->getServerConfigurationPrefixes(true);
 		$ldapWrapper = new LDAP();
 
 		$offset = intval($input->getOption('offset'));
diff --git a/apps/user_ldap/command/setconfig.php b/apps/user_ldap/command/setconfig.php
index ab1c8d3..9128fcf 100644
--- a/apps/user_ldap/command/setconfig.php
+++ b/apps/user_ldap/command/setconfig.php
@@ -41,7 +41,8 @@ class SetConfig extends Command {
 	}
 
 	protected function execute(InputInterface $input, OutputInterface $output) {
-		$availableConfigs = Helper::getServerConfigurationPrefixes();
+		$helper = new Helper();
+		$availableConfigs = $helper->getServerConfigurationPrefixes();
 		$configID = $input->getArgument('configID');
 		if(!in_array($configID, $availableConfigs)) {
 			$output->writeln("Invalid configID");
diff --git a/apps/user_ldap/command/showconfig.php b/apps/user_ldap/command/showconfig.php
index f51d641..ddbc452 100644
--- a/apps/user_ldap/command/showconfig.php
+++ b/apps/user_ldap/command/showconfig.php
@@ -31,7 +31,8 @@ class ShowConfig extends Command {
 	}
 
 	protected function execute(InputInterface $input, OutputInterface $output) {
-		$availableConfigs = Helper::getServerConfigurationPrefixes();
+		$helper = new Helper();
+		$availableConfigs = $helper->getServerConfigurationPrefixes();
 		$configID = $input->getArgument('configID');
 		if(!is_null($configID)) {
 			$configIDs[] = $configID;
diff --git a/apps/user_ldap/command/showremnants.php b/apps/user_ldap/command/showremnants.php
new file mode 100644
index 0000000..3d39f97
--- /dev/null
+++ b/apps/user_ldap/command/showremnants.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Copyright (c) 2014 Arthur Schiwon <blizzz at 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;
+	}
+
+}
diff --git a/apps/user_ldap/command/testconfig.php b/apps/user_ldap/command/testconfig.php
index 00b4acf..a44e224 100644
--- a/apps/user_ldap/command/testconfig.php
+++ b/apps/user_ldap/command/testconfig.php
@@ -31,7 +31,8 @@ class TestConfig extends Command {
 	}
 
 	protected function execute(InputInterface $input, OutputInterface $output) {
-		$availableConfigs = Helper::getServerConfigurationPrefixes();
+		$helper = new Helper();
+		$availableConfigs = $helper->getServerConfigurationPrefixes();
 		$configID = $input->getArgument('configID');
 		if(!in_array($configID, $availableConfigs)) {
 			$output->writeln("Invalid configID");
diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php
index 3ff1a99..b6e8513 100644
--- a/apps/user_ldap/lib/access.php
+++ b/apps/user_ldap/lib/access.php
@@ -285,7 +285,7 @@ class Access extends LDAPUtility implements user\IUserTools {
 	 * @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('
@@ -626,38 +626,16 @@ class Access extends LDAPUtility implements user\IUserTools {
 	}
 
 	/**
-	 * 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));
 	}
 
 	/**
@@ -705,7 +683,10 @@ class Access extends LDAPUtility implements user\IUserTools {
 		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;
diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php
index 336ea7b..13c6b8b 100644
--- a/apps/user_ldap/lib/connection.php
+++ b/apps/user_ldap/lib/connection.php
@@ -70,8 +70,9 @@ class Connection extends LDAPUtility {
 		}
 		$this->hasPagedResultSupport =
 			$this->ldap->hasPagedResultSupport();
+		$helper = new Helper();
 		$this->doNotValidate = !in_array($this->configPrefix,
-			Helper::getServerConfigurationPrefixes());
+			$helper->getServerConfigurationPrefixes());
 	}
 
 	public function __destruct() {
diff --git a/apps/user_ldap/lib/helper.php b/apps/user_ldap/lib/helper.php
index ecda49f..350942f 100644
--- a/apps/user_ldap/lib/helper.php
+++ b/apps/user_ldap/lib/helper.php
@@ -45,7 +45,7 @@ class Helper {
 	 * except the default (first) server shall be connected to.
 	 *
 	 */
-	static public function getServerConfigurationPrefixes($activeConfigurations = false) {
+	public function getServerConfigurationPrefixes($activeConfigurations = false) {
 		$referenceConfigkey = 'ldap_configuration_active';
 
 		$sql = '
@@ -83,7 +83,7 @@ class Helper {
 	 * @return array an array with configprefix as keys
 	 *
 	 */
-	static public function getServerConfigurationHosts() {
+	public function getServerConfigurationHosts() {
 		$referenceConfigkey = 'ldap_host';
 
 		$query = '
@@ -110,7 +110,7 @@ class Helper {
 	 * @param string $prefix the configuration prefix of the config to delete
 	 * @return bool true on success, false otherwise
 	 */
-	static public function deleteServerConfiguration($prefix) {
+	public function deleteServerConfiguration($prefix) {
 		//just to be on the safe side
 		\OCP\User::checkAdminUser();
 
@@ -150,7 +150,7 @@ class Helper {
 	 * @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') {
@@ -176,7 +176,7 @@ class Helper {
 	 * @param string $url the URL
 	 * @return string|false domain as string on success, false otherwise
 	 */
-	static public function getDomainFromURL($url) {
+	public function getDomainFromURL($url) {
 		$uinfo = parse_url($url);
 		if(!is_array($uinfo)) {
 			return false;
diff --git a/apps/user_ldap/lib/jobs.php b/apps/user_ldap/lib/jobs.php
index 47e536f..30f09cd 100644
--- a/apps/user_ldap/lib/jobs.php
+++ b/apps/user_ldap/lib/jobs.php
@@ -156,7 +156,8 @@ class Jobs extends \OC\BackgroundJob\TimedJob {
 		if(!is_null(self::$groupBE)) {
 			return self::$groupBE;
 		}
-		$configPrefixes = Helper::getServerConfigurationPrefixes(true);
+		$helper = new Helper();
+		$configPrefixes = $helper->getServerConfigurationPrefixes(true);
 		$ldapWrapper = new LDAP();
 		if(count($configPrefixes) === 1) {
 			//avoid the proxy when there is only one LDAP server configured
diff --git a/apps/user_ldap/lib/jobs/cleanup.php b/apps/user_ldap/lib/jobs/cleanup.php
new file mode 100644
index 0000000..c25dfe6
--- /dev/null
+++ b/apps/user_ldap/lib/jobs/cleanup.php
@@ -0,0 +1,241 @@
+<?php
+/**
+ * Copyright (c) 2014 Arthur Schiwon <blizzz at 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;
+	}
+
+}
diff --git a/apps/user_ldap/lib/user/deletedusersindex.php b/apps/user_ldap/lib/user/deletedusersindex.php
new file mode 100644
index 0000000..0d8bacf
--- /dev/null
+++ b/apps/user_ldap/lib/user/deletedusersindex.php
@@ -0,0 +1,125 @@
+<?php
+
+/**
+ * ownCloud – LDAP Helper
+ *
+ * @author Arthur Schiwon
+ * @copyright 2014 Arthur Schiwon <blizzz at 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;
+	}
+}
diff --git a/apps/user_ldap/lib/user/iusertools.php b/apps/user_ldap/lib/user/iusertools.php
index bbc6781..ffdef62 100644
--- a/apps/user_ldap/lib/user/iusertools.php
+++ b/apps/user_ldap/lib/user/iusertools.php
@@ -39,4 +39,7 @@ interface IUserTools {
 
 	public function username2dn($name);
 
+	//temporary hack for LDAP user cleanup, will be removed in OC 8.
+	public function ocname2dn($name, $isUser);
+
 }
diff --git a/apps/user_ldap/lib/user/manager.php b/apps/user_ldap/lib/user/manager.php
index 0ed3d09..3ce2b27 100644
--- a/apps/user_ldap/lib/user/manager.php
+++ b/apps/user_ldap/lib/user/manager.php
@@ -27,6 +27,7 @@ use OCA\user_ldap\lib\user\IUserTools;
 use OCA\user_ldap\lib\user\User;
 use OCA\user_ldap\lib\LogWrapper;
 use OCA\user_ldap\lib\FilesystemHelper;
+use OCA\user_ldap\lib\user\OfflineUser;
 
 /**
  * Manager
@@ -131,9 +132,45 @@ class Manager {
 	}
 
 	/**
+	 * Checks whether the specified user is marked as deleted
+	 * @param string $id the ownCloud user name
+	 * @return bool
+	 */
+	public function isDeletedUser($id) {
+		$isDeleted = $this->ocConfig->getUserValue(
+			$id, 'user_ldap', 'isDeleted', 0);
+		return intval($isDeleted) === 1;
+	}
+
+	/**
+	 * creates and returns an instance of OfflineUser for the specified user
+	 * @param string $id
+	 * @return \OCA\user_ldap\lib\user\OfflineUser
+	 */
+	public function getDeletedUser($id) {
+		return new OfflineUser(
+			$id,
+			new \OC\Preferences(\OC_DB::getConnection()),
+			\OC::$server->getDatabaseConnection(),
+			$this->access);
+	}
+
+	protected function createInstancyByUserName($id) {
+		//most likely a uid. Check whether it is a deleted user
+		if($this->isDeletedUser($id)) {
+			return $this->getDeletedUser($id);
+		}
+		$dn = $this->access->username2dn($id);
+		if($dn !== false) {
+			return $this->createAndCache($dn, $id);
+		}
+		throw new \Exception('Could not create User instance');
+	}
+
+	/**
 	 * @brief returns a User object by it's DN or ownCloud username
 	 * @param string the DN or username of the user
-	 * @return \OCA\user_ldap\lib\User | null
+	 * @return \OCA\user_ldap\lib\user\User | \OCA\user_ldap\lib\user\OfflineUser | null
 	 */
 	public function get($id) {
 		$this->checkAccess();
@@ -143,25 +180,19 @@ class Manager {
 			return $this->users['byUid'][$id];
 		}
 
-		if(!$this->access->stringResemblesDN($id) ) {
-			//most likely a uid
-			$dn = $this->access->username2dn($id);
-			if($dn !== false) {
-				return $this->createAndCache($dn, $id);
-			}
-		} else {
-			//so it's a DN
+		if($this->access->stringResemblesDN($id) ) {
 			$uid = $this->access->dn2username($id);
 			if($uid !== false) {
 				return $this->createAndCache($id, $uid);
 			}
 		}
-		//either funny uid or invalid. Assume funny to be on the safe side.
-		$dn = $this->access->username2dn($id);
-		if($dn !== false) {
-			return $this->createAndCache($dn, $id);
+
+		try {
+			$user = $this->createInstancyByUserName($id);
+			return $user;
+		} catch (\Exception $e) {
+			return null;
 		}
-		return null;
 	}
 
 }
diff --git a/apps/user_ldap/lib/user/offlineuser.php b/apps/user_ldap/lib/user/offlineuser.php
new file mode 100644
index 0000000..7750348
--- /dev/null
+++ b/apps/user_ldap/lib/user/offlineuser.php
@@ -0,0 +1,217 @@
+<?php
+
+/**
+ * ownCloud – LDAP User
+ *
+ * @author Arthur Schiwon
+ * @copyright 2014 Arthur Schiwon blizzz at 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;
+	}
+}
diff --git a/apps/user_ldap/lib/user/user.php b/apps/user_ldap/lib/user/user.php
index d4d2294..c81fb25b 100644
--- a/apps/user_ldap/lib/user/user.php
+++ b/apps/user_ldap/lib/user/user.php
@@ -213,6 +213,31 @@ class User {
 	}
 
 	/**
+	 * 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
 	 * the method will be run, when false is returned.
diff --git a/apps/user_ldap/lib/wizard.php b/apps/user_ldap/lib/wizard.php
index b08516a..0480e5b 100644
--- a/apps/user_ldap/lib/wizard.php
+++ b/apps/user_ldap/lib/wizard.php
@@ -614,7 +614,8 @@ class Wizard extends LDAPUtility {
 		//this did not help :(
 		//Let's see whether we can parse the Host URL and convert the domain to
 		//a base DN
-		$domain = Helper::getDomainFromURL($this->configuration->ldapHost);
+		$helper = new Helper();
+		$domain = $helper->getDomainFromURL($this->configuration->ldapHost);
 		if(!$domain) {
 			return false;
 		}
diff --git a/apps/user_ldap/settings.php b/apps/user_ldap/settings.php
index e24ecf4..12b072d 100644
--- a/apps/user_ldap/settings.php
+++ b/apps/user_ldap/settings.php
@@ -36,8 +36,9 @@ OCP\Util::addStyle('core', 'jquery-ui-1.10.0.custom');
 // fill template
 $tmpl = new OCP\Template('user_ldap', 'settings');
 
-$prefixes = \OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes();
-$hosts = \OCA\user_ldap\lib\Helper::getServerConfigurationHosts();
+$helper = new \OCA\user_ldap\lib\Helper();
+$prefixes = $helper->getServerConfigurationPrefixes();
+$hosts = $helper->getServerConfigurationHosts();
 
 $wizardHtml = '';
 $toc = array();
diff --git a/apps/user_ldap/tests/helper.php b/apps/user_ldap/tests/helper.php
index 07c24d6..cfd4aeb 100644
--- a/apps/user_ldap/tests/helper.php
+++ b/apps/user_ldap/tests/helper.php
@@ -23,7 +23,8 @@ class Test_Helper extends \PHPUnit_Framework_TestCase {
 		$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());
diff --git a/apps/user_ldap/tests/jobs/cleanup.php b/apps/user_ldap/tests/jobs/cleanup.php
new file mode 100644
index 0000000..687f7e1
--- /dev/null
+++ b/apps/user_ldap/tests/jobs/cleanup.php
@@ -0,0 +1,164 @@
+<?php
+/**
+ * Copyright (c) 2014 Arthur Schiwon <blizzz at 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);
+	}
+
+}
+
diff --git a/apps/user_ldap/tests/user/manager.php b/apps/user_ldap/tests/user/manager.php
index 7d68786..5f55448 100644
--- a/apps/user_ldap/tests/user/manager.php
+++ b/apps/user_ldap/tests/user/manager.php
@@ -183,7 +183,7 @@ class Test_User_Manager extends \PHPUnit_Framework_TestCase {
         $access->expects($this->never())
             ->method('dn2username');
 
-        $access->expects($this->exactly(2))
+        $access->expects($this->exactly(1))
             ->method('username2dn')
             ->with($this->equalTo($uid))
             ->will($this->returnValue(false));
diff --git a/apps/user_ldap/tests/user_ldap.php b/apps/user_ldap/tests/user_ldap.php
index c89edc3..6afa9d7 100644
--- a/apps/user_ldap/tests/user_ldap.php
+++ b/apps/user_ldap/tests/user_ldap.php
@@ -121,7 +121,7 @@ class Test_User_Ldap_Direct extends \PHPUnit_Framework_TestCase {
 			   ->method('fetchListOfUsers')
 			   ->will($this->returnCallback(function($filter) {
 					if($filter === 'roland') {
-						return array('dnOfRoland,dc=test');
+						return array(array('dn' => 'dnOfRoland,dc=test'));
 					}
 					return array();
 			   }));
@@ -228,6 +228,24 @@ class Test_User_Ldap_Direct extends \PHPUnit_Framework_TestCase {
 		$this->assertFalse($result);
 	}
 
+	public function testDeleteUserCancel() {
+		$access = $this->getAccessMock();
+		$backend = new UserLDAP($access);
+		$result = $backend->deleteUser('notme');
+		$this->assertFalse($result);
+	}
+
+	public function testDeleteUserSuccess() {
+		$access = $this->getAccessMock();
+		$backend = new UserLDAP($access);
+
+		$pref = \OC::$server->getConfig();
+		$pref->setUserValue('jeremy', 'user_ldap', 'isDeleted', 1);
+
+		$result = $backend->deleteUser('jeremy');
+		$this->assertTrue($result);
+	}
+
 	/**
 	 * Prepares the Access mock for getUsers tests
 	 * @param \OCA\user_ldap\lib\Access $access mock
diff --git a/apps/user_ldap/user_ldap.php b/apps/user_ldap/user_ldap.php
index 8bd9dd9..4fa3bdc 100644
--- a/apps/user_ldap/user_ldap.php
+++ b/apps/user_ldap/user_ldap.php
@@ -26,16 +26,23 @@
 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
 	 * @return boolean either the user can or cannot
 	 */
 	public function canChangeAvatar($uid) {
 		$user = $this->access->userManager->get($uid);
-		if(is_null($user)) {
+		if(!$user instanceof User) {
 			return false;
 		}
 		if($user->getAvatarImage() === false) {
@@ -57,15 +64,17 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
 		$uid = $this->access->escapeFilterPart($uid);
 
 		//find out dn of the user name
+		$attrs = array($this->access->connection->ldapUserDisplayName, 'dn',
+			'uid', 'samaccountname');
 		$filter = \OCP\Util::mb_str_replace(
 			'%uid', $uid, $this->access->connection->ldapLoginFilter, 'UTF-8');
-		$ldap_users = $this->access->fetchListOfUsers($filter, 'dn');
-		if(count($ldap_users) < 1) {
+		$users = $this->access->fetchListOfUsers($filter, $attrs);
+		if(count($users) < 1) {
 			return false;
 		}
-		$dn = $ldap_users[0];
+		$dn = $users[0]['dn'];
 		$user = $this->access->userManager->get($dn);
-		if(is_null($user)) {
+		if(!$user instanceof User) {
 			\OCP\Util::writeLog('user_ldap',
 				'LDAP Login: Could not get user object for DN ' . $dn .
 				'. Maybe the LDAP entry has no set display name attribute?',
@@ -79,6 +88,15 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
 			}
 
 			$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();
 		}
@@ -143,6 +161,10 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
 				$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
@@ -159,20 +181,36 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
 	}
 
 	/**
-	* delete a user
+	* returns whether a user was deleted in LDAP
+	*
 	* @param string $uid The username of the user to delete
 	* @return bool
-	*
-	* Deletes a user
 	*/
 	public function deleteUser($uid) {
-		return false;
+		$pref = \OC::$server->getConfig();
+		$marked = $pref->getUserValue($uid, 'user_ldap', 'isDeleted', 0);
+		if(intval($marked) === 0) {
+			\OC::$server->getLogger()->notice(
+				'User '.$uid . ' is not marked as deleted, not cleaning up.',
+				array('app' => 'user_ldap'));
+			return false;
+		}
+		\OC::$server->getLogger()->info('Cleaning up after user ' . $uid,
+			array('app' => 'user_ldap'));
+
+		//Get Home Directory out of user preferences so we can return it later,
+		//necessary for removing directories as done by OC_User.
+		$home = $pref->getUserValue($uid, 'user_ldap', 'homePath', '');
+		$this->homesToKill[$uid] = $home;
+		$this->access->unmapUser($uid);
+
+		return true;
 	}
 
 	/**
 	* get the user's home directory
 	* @param string $uid the username
-	* @return boolean
+	* @return string|bool
 	*/
 	public function getHome($uid) {
 		// user Exists check required as it is not done in user proxy!
@@ -180,10 +218,16 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
 			return false;
 		}
 
+		if(isset($this->homesToKill[$uid]) && !empty($this->homesToKill[$uid])) {
+			//a deleted user who needs some clean up
+			return $this->homesToKill[$uid];
+		}
+
 		$cacheKey = 'getHome'.$uid;
 		if($this->access->connection->isCached($cacheKey)) {
 			return $this->access->connection->getFromCache($cacheKey);
 		}
+		$pref = \OC::$server->getConfig();
 		if(strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0) {
 			$attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
 			$homedir = $this->access->readAttribute(
@@ -203,12 +247,17 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
 						\OC::$SERVERROOT.'/data' ) . '/' . $homedir[0];
 				}
 				$this->access->connection->writeToCache($cacheKey, $homedir);
+				//we need it to store it in the DB as well in case a user gets
+				//deleted so we can clean up afterwards
+				$pref->setUserValue($uid, 'user_ldap', 'homePath', $homedir);
+				//TODO: if home directory changes, the old one needs to be removed.
 				return $homedir;
 			}
 		}
 
 		//false will apply default behaviour as defined and done by OC_User
 		$this->access->connection->writeToCache($cacheKey, false);
+		$pref->setUserValue($uid, 'user_ldap', 'homePath', '');
 		return false;
 	}
 
diff --git a/apps/user_ldap/user_proxy.php b/apps/user_ldap/user_proxy.php
index fa4d693..ae148ec 100644
--- a/apps/user_ldap/user_proxy.php
+++ b/apps/user_ldap/user_proxy.php
@@ -209,7 +209,7 @@ class User_Proxy extends lib\Proxy implements \OCP\UserInterface {
 	 * Deletes a user
 	 */
 	public function deleteUser($uid) {
-		return false;
+		return $this->handleRequest($uid, 'deleteUser', array($uid));
 	}
 
 	/**
diff --git a/config/config.sample.php b/config/config.sample.php
index 6da3a68..009b06f 100644
--- a/config/config.sample.php
+++ b/config/config.sample.php
@@ -71,7 +71,7 @@ $CONFIG = array(
 
 /**
  * Where user files are stored; this defaults to ``data/`` in the ownCloud
- * directory. The SQLite database is also stored here, when you use SQLite. (SQLite is 
+ * directory. The SQLite database is also stored here, when you use SQLite. (SQLite is
  * available only in ownCloud Community Edition)
  */
 'datadirectory' => '/var/www/owncloud/data',
@@ -644,6 +644,20 @@ $CONFIG = array(
 	'OC\Preview\MarkDown'
 ),
 
+/**
+ * LDAP
+ *
+ * Global settings used by LDAP User and Group Backend
+ */
+
+/**
+ * defines the interval in minutes for the background job that checks user
+ * existance and marks them as ready to be cleaned up. The number is always
+ * minutes. Setting it to 0 disables the feature.
+ * See command line (occ) methods ldap:show-remnants and user:delete
+ */
+'ldapUserCleanupInterval' => 51,
+
 
 /**
  * Maintenance
diff --git a/core/command/user/delete.php b/core/command/user/delete.php
new file mode 100644
index 0000000..f64b40e
--- /dev/null
+++ b/core/command/user/delete.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Copyright (c) 2014 Arthur Schiwon <blizzz at 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>');
+	}
+}
diff --git a/core/register_command.php b/core/register_command.php
index b02988b..6a5aa1d 100644
--- a/core/register_command.php
+++ b/core/register_command.php
@@ -22,4 +22,5 @@ $application->add(new OC\Core\Command\Maintenance\Repair($repair, OC_Config::get
 $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());
 
diff --git a/lib/private/preferences.php b/lib/private/preferences.php
index a849cc2..73edf2f 100644
--- a/lib/private/preferences.php
+++ b/lib/private/preferences.php
@@ -182,7 +182,7 @@ class Preferences {
 			// no changes
 			return true;
 		}
-		
+
 		$affectedRows = 0;
 
 		if (!$exists && $preCondition === null) {
@@ -264,9 +264,11 @@ class Preferences {
 	 * @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` '
@@ -280,9 +282,10 @@ class Preferences {
 			$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'];
 		}
 
diff --git a/lib/private/user.php b/lib/private/user.php
index bd83352..ab56bda 100644
--- a/lib/private/user.php
+++ b/lib/private/user.php
@@ -216,7 +216,7 @@ class OC_User {
 				self::getManager()->delete($uid);
 			}
 
-			return true;
+			return $result;
 		} else {
 			return false;
 		}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-owncloud/owncloud.git



More information about the Pkg-owncloud-commits mailing list