]> source.dussan.org Git - nextcloud-server.git/commitdiff
LDAP User Cleanup
authorArthur Schiwon <blizzz@owncloud.com>
Thu, 21 Aug 2014 15:59:13 +0000 (17:59 +0200)
committerArthur Schiwon <blizzz@owncloud.com>
Mon, 15 Dec 2014 11:56:16 +0000 (12:56 +0100)
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

35 files changed:
apps/user_ldap/ajax/clearMappings.php
apps/user_ldap/ajax/deleteConfiguration.php
apps/user_ldap/ajax/getNewServerConfigPrefix.php
apps/user_ldap/appinfo/app.php
apps/user_ldap/appinfo/register_command.php
apps/user_ldap/appinfo/update.php
apps/user_ldap/appinfo/version
apps/user_ldap/command/search.php
apps/user_ldap/command/setconfig.php
apps/user_ldap/command/showconfig.php
apps/user_ldap/command/showremnants.php [new file with mode: 0644]
apps/user_ldap/command/testconfig.php
apps/user_ldap/lib/access.php
apps/user_ldap/lib/connection.php
apps/user_ldap/lib/helper.php
apps/user_ldap/lib/jobs.php
apps/user_ldap/lib/jobs/cleanup.php [new file with mode: 0644]
apps/user_ldap/lib/user/deletedusersindex.php [new file with mode: 0644]
apps/user_ldap/lib/user/iusertools.php
apps/user_ldap/lib/user/manager.php
apps/user_ldap/lib/user/offlineuser.php [new file with mode: 0644]
apps/user_ldap/lib/user/user.php
apps/user_ldap/lib/wizard.php
apps/user_ldap/settings.php
apps/user_ldap/tests/helper.php
apps/user_ldap/tests/jobs/cleanup.php [new file with mode: 0644]
apps/user_ldap/tests/user/manager.php
apps/user_ldap/tests/user_ldap.php
apps/user_ldap/user_ldap.php
apps/user_ldap/user_proxy.php
config/config.sample.php
core/command/user/delete.php [new file with mode: 0644]
core/register_command.php
lib/private/preferences.php
lib/private/user.php

index 9118d58c5cf0f73d92f962fdac3a68919f125f36..5b4fd4bebaab348193fdbe07506735ca3b4e5b47 100644 (file)
@@ -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');
index ade57110d346b8986e724e5100d1f7e2b0064f1a..268468eff1b1c37ea29f36b1c103842e0c3a8c17 100644 (file)
@@ -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');
index 1c68b2e9a760717e94b761f63343063edab009f2..ce6c5ae76e853948d8dc40f123cb989a4fe81969 100644 (file)
@@ -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));
index a26c7709d41096b34cc16c8ae26dd5e018ee9a7c..a931d970e8d843fbd20b272302d67e6abcaa68c4 100644 (file)
@@ -5,6 +5,7 @@
 *
 * @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
@@ -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',
index efce2d0b49e08b47d7b86ef5859de4fbbaabad8e..ff8871ee4e1b900d6f120fcf959878f6476be968 100644 (file)
@@ -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());
index 5fad23de4f6ce26b7164edd6c7babfeabfe841a6..0354cf27379b28fd5d03285ca93a32498fdd81d9 100644 (file)
@@ -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);
index 6f2743d65dc06508954334e88edb660bf8efea20..0bfccb08040473231c42ec1ff3b9e773528a43f5 100644 (file)
@@ -1 +1 @@
-0.4.4
+0.4.5
index e20255510d8171dad4f95b4e9a691ad6472dc2a7..d826303c55de315077c1e122a58af2e3c1971f57 100644 (file)
@@ -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'));
index ab1c8d39eada8670b3731c6db8c076f2d2ac3eaf..9128fcf04fcf75a37a8bea3ce1f8426d60b7f93a 100644 (file)
@@ -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");
index f51d641beecee6979be08ed44bff910b757dc45a..ddbc45243ffac7b9f53c70974a96a618312166b1 100644 (file)
@@ -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 (file)
index 0000000..3d39f97
--- /dev/null
@@ -0,0 +1,81 @@
+<?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;
+       }
+
+}
index 00b4acf2f6642a429b61f5d448deeb759a52ec7b..a44e22415e9209606628b8dc00b4f2e71b721c2d 100644 (file)
@@ -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");
index 3ff1a9985c212b21de754d44e2f751614456814c..b6e851379d91d8f3075b0de5986fb39228adca6a 100644 (file)
@@ -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;
index 336ea7b3bbc424f7b1a1e6cd9e557fa689e2ee85..13c6b8b37facfede082cf87f841ecab8b77ef3f1 100644 (file)
@@ -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() {
index ecda49f73fcd0cd216ddbe6e9b60ca8f143ea4a8..350942fb28c59735bacffc921c4b66c979ed6cb5 100644 (file)
@@ -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;
index 47e536f8f646db35a2f1b4257634587831db44cb..30f09cdc8f85808284acdafe794eb9f3b4698ed8 100644 (file)
@@ -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 (file)
index 0000000..c25dfe6
--- /dev/null
@@ -0,0 +1,241 @@
+<?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;
+       }
+
+}
diff --git a/apps/user_ldap/lib/user/deletedusersindex.php b/apps/user_ldap/lib/user/deletedusersindex.php
new file mode 100644 (file)
index 0000000..0d8bacf
--- /dev/null
@@ -0,0 +1,125 @@
+<?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;
+       }
+}
index bbc678153de3d538a4e838a739d554a41bcc17de..ffdef62410dcd1bf6c625a7fd9565fb75b6e4644 100644 (file)
@@ -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);
+
 }
index 0ed3d09c48f9ee6dee4cbb9ec462810e72e8fc11..3ce2b27f2188c1b3bf43175ead8e8d654e11baab 100644 (file)
@@ -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
@@ -130,10 +131,46 @@ class Manager {
                }
        }
 
+       /**
+        * Checks whether the specified user is marked as deleted
+        * @param string $id the ownCloud user name
+        * @return bool
+        */
+       public function isDeletedUser($id) {
+               $isDeleted = $this->ocConfig->getUserValue(
+                       $id, 'user_ldap', 'isDeleted', 0);
+               return intval($isDeleted) === 1;
+       }
+
+       /**
+        * creates and returns an instance of OfflineUser for the specified user
+        * @param string $id
+        * @return \OCA\user_ldap\lib\user\OfflineUser
+        */
+       public function getDeletedUser($id) {
+               return new OfflineUser(
+                       $id,
+                       new \OC\Preferences(\OC_DB::getConnection()),
+                       \OC::$server->getDatabaseConnection(),
+                       $this->access);
+       }
+
+       protected function createInstancyByUserName($id) {
+               //most likely a uid. Check whether it is a deleted user
+               if($this->isDeletedUser($id)) {
+                       return $this->getDeletedUser($id);
+               }
+               $dn = $this->access->username2dn($id);
+               if($dn !== false) {
+                       return $this->createAndCache($dn, $id);
+               }
+               throw new \Exception('Could not create User instance');
+       }
+
        /**
         * @brief returns a User object by it's DN or ownCloud username
         * @param string the DN or username of the user
-        * @return \OCA\user_ldap\lib\User | null
+        * @return \OCA\user_ldap\lib\user\User | \OCA\user_ldap\lib\user\OfflineUser | null
         */
        public function get($id) {
                $this->checkAccess();
@@ -143,25 +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 (file)
index 0000000..7750348
--- /dev/null
@@ -0,0 +1,217 @@
+<?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;
+       }
+}
index d4d2294307dbcc1ac15cedd567935b8ea2aad636..c81fb25b541604c7b504a2a36c76e0429068b742 100644 (file)
@@ -212,6 +212,31 @@ class User {
                return  true;
        }
 
+       /**
+        * Stores a key-value pair in relation to this user
+        * @param string $key
+        * @param string $value
+        */
+       private function store($key, $value) {
+               $this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
+       }
+
+       /**
+        * Stores the display name in the databae
+        * @param string $displayName
+        */
+       public function storeDisplayName($displayName) {
+               $this->store('displayName', $displayName);
+       }
+
+       /**
+        * Stores the LDAP Username in the Database
+        * @param string $userName
+        */
+       public function storeLDAPUserName($userName) {
+               $this->store('uid', $userName);
+       }
+
        /**
         * @brief checks whether an update method specified by feature was run
         * already. If not, it will marked like this, because it is expected that
index b08516a4d77a305d769b36ba791eb08498f7ba40..0480e5b6b646b9d52d10637455dfd017866d1b89 100644 (file)
@@ -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;
                }
index e24ecf4a64e9a36cf6064f61f9f3f82a5f54568e..12b072d81546bfaebdaa9feafa76d5762159e615 100644 (file)
@@ -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();
index 07c24d644992acb91d30c76273c8fbcf54a6a838..cfd4aebf71375b03921b1397af9648a2a2ee25fe 100644 (file)
@@ -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 (file)
index 0000000..687f7e1
--- /dev/null
@@ -0,0 +1,164 @@
+<?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);
+       }
+
+}
+
index 7d687867213629eefaf480a43c9d805b2ef481fc..5f55448fc09960dd75a483aada40ca3b6b457ae0 100644 (file)
@@ -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));
index c89edc33fa94c04e4eaef51d396796139a1cefa3..6afa9d79e3e2a308d9d98ff3bbd933354c888363 100644 (file)
@@ -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
index 8bd9dd9e8c51927d361af2842c3a3be64b25126d..4fa3bdc67a1e0deacb14335e69f63b18d90515f5 100644 (file)
 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
@@ -35,7 +42,7 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
         */
        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;
        }
 
index fa4d69393038da73be5eb7ffb08b5059f5191947..ae148ec661a38f5fa91a1ba513424eb20ca624f2 100644 (file)
@@ -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));
        }
 
        /**
index 6da3a682f19e01014d9e7946156ca69c23af2584..009b06fdbbbb9fdafcd476988280a4835f8b7de5 100644 (file)
@@ -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 (file)
index 0000000..f64b40e
--- /dev/null
@@ -0,0 +1,36 @@
+<?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>');
+       }
+}
index b02988bbdd8108c9269547261372403f8924c5a3..6a5aa1d5650ddf4af663b0cfb24de51c1af38986 100644 (file)
@@ -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());
 
index a849cc23e1a695c3af76f6b3bacb4c2f3a0408d9..73edf2fbeff2fdcbf90db8f9d78928c85b2b6224 100644 (file)
@@ -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'];
                }
 
index bd833526718162dff937b03124effedb8f4a939b..ab56bda206cb49f6a8cf1857d2443c6afd45f8b1 100644 (file)
@@ -216,7 +216,7 @@ class OC_User {
                                self::getManager()->delete($uid);
                        }
 
-                       return true;
+                       return $result;
                } else {
                        return false;
                }