LDAP User Cleanup: Port from stable7 without further adjustements

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

Conflicts:
	apps/user_ldap/ajax/clearMappings.php
	apps/user_ldap/appinfo/app.php
	apps/user_ldap/lib/access.php
	apps/user_ldap/lib/helper.php
	apps/user_ldap/tests/helper.php
	core/register_command.php
	lib/private/preferences.php
	lib/private/user.php

add ldap:check-user to check user existance on the fly

Conflicts:
	apps/user_ldap/lib/helper.php

forgotten file

PHPdoc fixes, no code change

and don't forget to adjust tests
remotes/origin/fix-10825
Arthur Schiwon 10 years ago
parent 8164415b45
commit 4fa39250e7

@ -27,7 +27,8 @@ OCP\JSON::checkAppEnabled('user_ldap');
OCP\JSON::callCheck();
$prefix = $_POST['ldap_serverconfig_chooser'];
if(\OCA\user_ldap\lib\Helper::deleteServerConfiguration($prefix)) {
$helper = new \OCA\user_ldap\lib\Helper();
if($helper->deleteServerConfiguration($prefix)) {
OCP\JSON::success();
} else {
$l = \OC::$server->getL10N('user_ldap');

@ -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));

@ -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();
@ -50,16 +52,10 @@ if(count($configPrefixes) > 0) {
OC_Group::useBackend($groupBackend);
}
// add settings page to navigation
$entry = array(
'id' => 'user_ldap_settings',
'order'=>1,
'href' => OCP\Util::linkTo( 'user_ldap', 'settings.php' ),
'name' => 'LDAP'
);
OCP\Util::addTranslations('user_ldap');
OCP\Backgroundjob::registerJob('OCA\user_ldap\lib\Jobs');
OCP\Backgroundjob::registerJob('\OCA\User_LDAP\Jobs\CleanUp');
if(OCP\App::isEnabled('user_webdavauth')) {
OCP\Util::writeLog('user_ldap',
'user_ldap and user_webdavauth are incompatible. You may experience unexpected behaviour',

@ -6,9 +6,22 @@
* See the COPYING-README file.
*/
use OCA\user_ldap\lib\Helper;
use OCA\user_ldap\lib\LDAP;
use OCA\user_ldap\User_Proxy;
$application->add(new OCA\user_ldap\Command\ShowConfig());
$application->add(new OCA\user_ldap\Command\SetConfig());
$application->add(new OCA\user_ldap\Command\TestConfig());
$application->add(new OCA\user_ldap\Command\CreateEmptyConfig());
$application->add(new OCA\user_ldap\Command\DeleteConfig());
$application->add(new OCA\user_ldap\Command\Search());
$application->add(new OCA\user_ldap\Command\ShowRemnants());
$helper = new OCA\user_ldap\lib\Helper();
$uBackend = new OCA\user_ldap\User_Proxy(
$helper->getServerConfigurationPrefixes(true),
new OCA\user_ldap\lib\LDAP()
);
$application->add(new OCA\user_ldap\Command\CheckUser(
$uBackend, $helper, \OC::$server->getConfig()
));

@ -12,7 +12,8 @@ if($state === 'unset') {
$installedVersion = $configInstance->getAppValue('user_ldap', 'installed_version');
$enableRawMode = version_compare($installedVersion, '0.4.1', '<');
$configPrefixes = OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes(true);
$helper = new \OCA\user_ldap\lib\Helper();
$configPrefixes = $helper->getServerConfigurationPrefixes(true);
$ldap = new OCA\user_ldap\lib\LDAP();
foreach($configPrefixes as $config) {
$connection = new OCA\user_ldap\lib\Connection($ldap, $config);

@ -0,0 +1,129 @@
<?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\User;
use OCA\User_LDAP\lib\user\Manager;
use OCA\user_ldap\lib\Helper;
use OCA\user_ldap\User_Proxy;
class CheckUser extends Command {
/** @var \OCA\user_ldap\User_Proxy */
protected $backend;
/** @var \OCA\User_LDAP\lib\Helper */
protected $helper;
/** @var \OCP\IConfig */
protected $config;
/**
* @param OCA\user_ldap\User_Proxy $uBackend
* @param OCA\User_LDAP\lib\Helper $helper
* @param OCP\IConfig $config
*/
public function __construct(User_Proxy $uBackend, Helper $helper, \OCP\IConfig $config) {
$this->backend = $uBackend;
$this->helper = $helper;
$this->config = $config;
parent::__construct();
}
protected function configure() {
$this
->setName('ldap:check-user')
->setDescription('checks whether a user exists on LDAP.')
->addArgument(
'ocName',
InputArgument::REQUIRED,
'the user name as used in ownCloud'
)
->addOption(
'force',
null,
InputOption::VALUE_NONE,
'ignores disabled LDAP configuration'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output) {
try {
$uid = $input->getArgument('ocName');
$this->isAllowed($input->getOption('force'));
$this->confirmUserIsMapped($uid);
$exists = $this->backend->userExistsOnLDAP($uid);
if($exists === true) {
$output->writeln('The user is still available on LDAP.');
return;
}
// TODO FIXME consolidate next line in DeletedUsersIndex
// (impractical now, because of class dependencies)
$this->config->setUserValue($uid, 'user_ldap', 'isDeleted', '1');
$output->writeln('The user does not exists on LDAP anymore.');
$output->writeln('Clean up the user\'s remnants by: ./occ user:delete "'
. $uid . '"');
} catch (\Exception $e) {
$output->writeln('<error>' . $e->getMessage(). '</error>');
}
}
/**
* checks whether a user is actually mapped
* @param string $ocName the username as used in ownCloud
* @throws \Exception
* @return bool
*/
protected function confirmUserIsMapped($ocName) {
//TODO FIXME this should go to Mappings in OC 8
$db = \OC::$server->getDatabaseConnection();
$query = $db->prepare('
SELECT
`ldap_dn` AS `dn`
FROM `*PREFIX*ldap_user_mapping`
WHERE `owncloud_name` = ?'
);
$query->execute(array($ocName));
$result = $query->fetchColumn();
if($result === false) {
throw new \Exception('The given user is not a recognized LDAP user.');
}
return true;
}
/**
* checks whether the setup allows reliable checking of LDAP user existance
* @throws \Exception
* @return bool
*/
protected function isAllowed($force) {
if($this->helper->haveDisabledConfigurations() && !$force) {
throw new \Exception('Cannot check user existance, because '
. 'disabled LDAP configurations are present.');
}
// we don't check ldapUserCleanupInterval from config.php because this
// action is triggered manually, while the setting only controls the
// background job.
return true;
}
}

@ -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'));

@ -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");

@ -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;

@ -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;
}
}

@ -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");

@ -290,6 +290,7 @@ class Access extends LDAPUtility implements user\IUserTools {
}
/**
public function ocname2dn($name, $isUser) {
* returns the internal ownCloud name for the given LDAP DN of the group, false on DN outside of search DN or failure
* @param string $fdn the dn of the group object
* @param string $ldapName optional, the display name of the object

@ -71,8 +71,9 @@ class Connection extends LDAPUtility {
}
$this->hasPagedResultSupport =
$this->ldap->hasPagedResultSupport();
$helper = new Helper();
$this->doNotValidate = !in_array($this->configPrefix,
Helper::getServerConfigurationPrefixes());
$helper->getServerConfigurationPrefixes());
}
public function __destruct() {

@ -45,7 +45,7 @@ class Helper {
* except the default (first) server shall be connected to.
*
*/
static public function getServerConfigurationPrefixes($activeConfigurations = false) {
public function getServerConfigurationPrefixes($activeConfigurations = false) {
$referenceConfigkey = 'ldap_configuration_active';
$sql = '
@ -83,7 +83,7 @@ class Helper {
* @return array an array with configprefix as keys
*
*/
static public function getServerConfigurationHosts() {
public function getServerConfigurationHosts() {
$referenceConfigkey = 'ldap_host';
$query = '
@ -110,7 +110,7 @@ class Helper {
* @param string $prefix the configuration prefix of the config to delete
* @return bool true on success, false otherwise
*/
static public function deleteServerConfiguration($prefix) {
public function deleteServerConfiguration($prefix) {
if(!in_array($prefix, self::getServerConfigurationPrefixes())) {
return false;
}
@ -141,12 +141,28 @@ class Helper {
return true;
}
/**
* checks whether there is one or more disabled LDAP configurations
* @throws \Exception
* @return bool
*/
public function haveDisabledConfigurations() {
$all = $this->getServerConfigurationPrefixes(false);
$active = $this->getServerConfigurationPrefixes(true);
if(!is_array($all) || !is_array($active)) {
throw new \Exception('Unexpected Return Value');
}
return count($all) !== count($active) || count($all) === 0;
}
/**
* extracts the domain from a given URL
* @param string $url the URL
* @return string|false domain as string on success, false otherwise
*/
static public function getDomainFromURL($url) {
public function getDomainFromURL($url) {
$uinfo = parse_url($url);
if(!is_array($uinfo)) {
return false;

@ -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

@ -0,0 +1,227 @@
<?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->ldapHelper->haveDisabledConfigurations()) {
return false;
}
} catch (\Exception $e) {
return false;
}
$enabled = $this->isCleanUpEnabled();
return $enabled;
}
/**
* checks whether clean up is enabled by configuration
* @return bool
*/
private function isCleanUpEnabled() {
return (bool)$this->ocConfig->getSystemValue(
'ldapUserCleanupInterval', strval($this->defaultIntervalMin));
}
/**
* checks users whether they are still existing
* @param array $users result from getMappedUsers()
*/
private function checkUsers($users) {
foreach($users as $user) {
$this->checkUser($user);
}
}
/**
* checks whether a user is still existing in LDAP
* @param string[] $user
*/
private function checkUser($user) {
if($this->userBackend->userExistsOnLDAP($user['name'])) {
//still available, all good
return;
}
// TODO FIXME consolidate next line in DeletedUsersIndex
// (impractical now, because of class dependencies)
$this->ocConfig->setUserValue($user['name'], 'user_ldap', 'isDeleted', '1');
}
/**
* returns a batch of users from the mappings table
* @param int $limit
* @param int $offset
* @return array
*/
public function getMappedUsers($limit, $offset) {
$query = $this->db->prepare('
SELECT
`ldap_dn` AS `dn`,
`owncloud_name` AS `name`,
`directory_uuid` AS `uuid`
FROM `*PREFIX*ldap_user_mapping`',
$limit,
$offset
);
$query->execute();
return $query->fetchAll();
}
/**
* gets the offset to fetch users from the mappings table
* @return int
*/
private function getOffset() {
return $this->ocConfig->getAppValue('user_ldap', 'cleanUpJobOffset', 0);
}
/**
* sets the new offset for the next run
* @param bool $reset whether the offset should be set to 0
*/
public function setOffset($reset = false) {
$newOffset = $reset ? 0 :
$this->getOffset() + $this->limit;
$this->ocConfig->setAppValue('user_ldap', 'cleanUpJobOffset', $newOffset);
}
/**
* returns the chunk size (limit in DB speak)
* @return int
*/
public function getChunkSize() {
return $this->limit;
}
}

@ -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;
}
}

@ -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);
}

@ -27,6 +27,7 @@ use OCA\user_ldap\lib\user\IUserTools;
use OCA\user_ldap\lib\user\User;
use OCA\user_ldap\lib\LogWrapper;
use OCA\user_ldap\lib\FilesystemHelper;
use OCA\user_ldap\lib\user\OfflineUser;
/**
* Manager
@ -60,7 +61,9 @@ class Manager {
*/
protected $avatarManager;
/**
* @var string[][]
* array['byDN'] \OCA\user_ldap\lib\User[]
* ['byUid'] \OCA\user_ldap\lib\User[]
* @var array $users
*/
protected $users = array(
'byDN' => array(),
@ -130,10 +133,46 @@ class Manager {
}
}
/**
* Checks whether the specified user is marked as deleted
* @param string $id the ownCloud user name
* @return bool
*/
public function isDeletedUser($id) {
$isDeleted = $this->ocConfig->getUserValue(
$id, 'user_ldap', 'isDeleted', 0);
return intval($isDeleted) === 1;
}
/**
* creates and returns an instance of OfflineUser for the specified user
* @param string $id
* @return \OCA\user_ldap\lib\user\OfflineUser
*/
public function getDeletedUser($id) {
return new OfflineUser(
$id,
new \OC\Preferences(\OC_DB::getConnection()),
\OC::$server->getDatabaseConnection(),
$this->access);
}
protected function createInstancyByUserName($id) {
//most likely a uid. Check whether it is a deleted user
if($this->isDeletedUser($id)) {
return $this->getDeletedUser($id);
}
$dn = $this->access->username2dn($id);
if($dn !== false) {
return $this->createAndCache($dn, $id);
}
throw new \Exception('Could not create User instance');
}
/**
* @brief returns a User object by it's DN or ownCloud username
* @param string the DN or username of the user
* @return \OCA\user_ldap\lib\User | null
* @return \OCA\user_ldap\lib\user\User|\OCA\user_ldap\lib\user\OfflineUser|null
*/
public function get($id) {
$this->checkAccess();
@ -143,25 +182,19 @@ class Manager {
return $this->users['byUid'][$id];
}
if(!$this->access->stringResemblesDN($id) ) {
//most likely a uid
$dn = $this->access->username2dn($id);
if($dn !== false) {
return $this->createAndCache($dn, $id);
}
} else {
//so it's a DN
if($this->access->stringResemblesDN($id) ) {
$uid = $this->access->dn2username($id);
if($uid !== false) {
return $this->createAndCache($id, $uid);
}
}
//either funny uid or invalid. Assume funny to be on the safe side.
$dn = $this->access->username2dn($id);
if($dn !== false) {
return $this->createAndCache($dn, $id);
try {
$user = $this->createInstancyByUserName($id);
return $user;
} catch (\Exception $e) {
return null;
}
return null;
}
}

@ -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;
}
}

@ -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

@ -659,7 +659,8 @@ class Wizard extends LDAPUtility {
//this did not help :(
//Let's see whether we can parse the Host URL and convert the domain to
//a base DN
$domain = Helper::getDomainFromURL($this->configuration->ldapHost);
$helper = new Helper();
$domain = $helper->getDomainFromURL($this->configuration->ldapHost);
if(!$domain) {
return false;
}

@ -35,8 +35,9 @@ OCP\Util::addStyle('user_ldap', 'settings');
// fill template
$tmpl = new OCP\Template('user_ldap', 'settings');
$prefixes = \OCA\user_ldap\lib\Helper::getServerConfigurationPrefixes();
$hosts = \OCA\user_ldap\lib\Helper::getServerConfigurationHosts();
$helper = new \OCA\user_ldap\lib\Helper();
$prefixes = $helper->getServerConfigurationPrefixes();
$hosts = $helper->getServerConfigurationHosts();
$wizardHtml = '';
$toc = array();

@ -0,0 +1,155 @@
<?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->once())
->method('haveDisabledConfigurations')
->will($this->returnValue(true) );
$args['ocConfig']->expects($this->never())
->method('getSystemValue');
$bgJob = new \OCA\User_LDAP\Jobs\CleanUp();
$bgJob->setArguments($args);
$result = $bgJob->isCleanUpAllowed();
$this->assertSame(false, $result);
}
/**
* clean up job must not run when LDAP Helper is broken i.e.
* returning unexpected results
*/
public function test_runNotAllowedByBrokenHelper() {
$args = $this->getMocks();
$args['helper']->expects($this->once())
->method('haveDisabledConfigurations')
->will($this->throwException(new \Exception()));
$args['ocConfig']->expects($this->never())
->method('getSystemValue');
$bgJob = new \OCA\User_LDAP\Jobs\CleanUp();
$bgJob->setArguments($args);
$result = $bgJob->isCleanUpAllowed();
$this->assertSame(false, $result);
}
/**
* clean up job must not run when it is not enabled
*/
public function test_runNotAllowedBySysConfig() {
$args = $this->getMocks();
$args['helper']->expects($this->once())
->method('haveDisabledConfigurations')
->will($this->returnValue(false));
$args['ocConfig']->expects($this->once())
->method('getSystemValue')
->will($this->returnValue(false));
$bgJob = new \OCA\User_LDAP\Jobs\CleanUp();
$bgJob->setArguments($args);
$result = $bgJob->isCleanUpAllowed();
$this->assertSame(false, $result);
}
/**
* clean up job is allowed to run
*/
public function test_runIsAllowed() {
$args = $this->getMocks();
$args['helper']->expects($this->once())
->method('haveDisabledConfigurations')
->will($this->returnValue(false));
$args['ocConfig']->expects($this->once())
->method('getSystemValue')
->will($this->returnValue(true));
$bgJob = new \OCA\User_LDAP\Jobs\CleanUp();
$bgJob->setArguments($args);
$result = $bgJob->isCleanUpAllowed();
$this->assertSame(true, $result);
}
/**
* test whether sql is OK
*/
public function test_getMappedUsers() {
$args = $this->getMocks();
$bgJob = new \OCA\User_LDAP\Jobs\CleanUp();
$bgJob->setArguments($args);
if(version_compare(\PHPUnit_Runner_Version::id(), '3.8', '<')) {
//otherwise we run into
//https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103
$this->markTestIncomplete();
}
$stmt = $this->getMock('\Doctrine\DBAL\Driver\Statement');
$args['db']->expects($this->once())
->method('prepare')
->will($this->returnValue($stmt));
$bgJob->getMappedUsers(0, $bgJob->getChunkSize());
}
/**
* check whether offset will be reset when it needs to
*/
public function test_OffsetResetIsNecessary() {
$args = $this->getMocks();
$bgJob = new \OCA\User_LDAP\Jobs\CleanUp();
$bgJob->setArguments($args);
$result = $bgJob->isOffsetResetNecessary($bgJob->getChunkSize() - 1);
$this->assertSame(true, $result);
}
/**
* make sure offset is not reset when it is not due
*/
public function test_OffsetResetIsNotNecessary() {
$args = $this->getMocks();
$bgJob = new \OCA\User_LDAP\Jobs\CleanUp();
$bgJob->setArguments($args);
$result = $bgJob->isOffsetResetNecessary($bgJob->getChunkSize());
$this->assertSame(false, $result);
}
}

@ -183,7 +183,7 @@ class Test_User_Manager extends \Test\TestCase {
$access->expects($this->never())
->method('dn2username');
$access->expects($this->exactly(2))
$access->expects($this->exactly(1))
->method('username2dn')
->with($this->equalTo($uid))
->will($this->returnValue(false));

@ -123,7 +123,7 @@ class Test_User_Ldap_Direct extends \Test\TestCase {
->method('fetchListOfUsers')
->will($this->returnCallback(function($filter) {
if($filter === 'roland') {
return array('dnOfRoland,dc=test');
return array(array('dn' => 'dnOfRoland,dc=test'));
}
return array();
}));
@ -230,6 +230,24 @@ class Test_User_Ldap_Direct extends \Test\TestCase {
$this->assertFalse($result);
}
public function testDeleteUserCancel() {
$access = $this->getAccessMock();
$backend = new UserLDAP($access);
$result = $backend->deleteUser('notme');
$this->assertFalse($result);
}
public function testDeleteUserSuccess() {
$access = $this->getAccessMock();
$backend = new UserLDAP($access);
$pref = \OC::$server->getConfig();
$pref->setUserValue('jeremy', 'user_ldap', 'isDeleted', 1);
$result = $backend->deleteUser('jeremy');
$this->assertTrue($result);
}
/**
* Prepares the Access mock for getUsers tests
* @param \OCA\user_ldap\lib\Access $access mock

@ -26,8 +26,15 @@
namespace OCA\user_ldap;
use OCA\user_ldap\lib\BackendUtility;
use OCA\user_ldap\lib\user\OfflineUser;
use OCA\User_LDAP\lib\User\User;
class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserInterface {
/**
* @var string[] $homesToKill
*/
protected $homesToKill = array();
/**
* checks whether the user is allowed to change his avatar in ownCloud
* @param string $uid the ownCloud user name
@ -35,7 +42,7 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
*/
public function canChangeAvatar($uid) {
$user = $this->access->userManager->get($uid);
if(is_null($user)) {
if(!$user instanceof User) {
return false;
}
if($user->getAvatarImage() === false) {
@ -57,15 +64,17 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
$uid = $this->access->escapeFilterPart($uid);
//find out dn of the user name
$attrs = array($this->access->connection->ldapUserDisplayName, 'dn',
'uid', 'samaccountname');
$filter = \OCP\Util::mb_str_replace(
'%uid', $uid, $this->access->connection->ldapLoginFilter, 'UTF-8');
$ldap_users = $this->access->fetchListOfUsers($filter, 'dn');
if(count($ldap_users) < 1) {
$users = $this->access->fetchListOfUsers($filter, $attrs);
if(count($users) < 1) {
return false;
}
$dn = $ldap_users[0];
$dn = $users[0]['dn'];
$user = $this->access->userManager->get($dn);
if(is_null($user)) {
if(!$user instanceof User) {
\OCP\Util::writeLog('user_ldap',
'LDAP Login: Could not get user object for DN ' . $dn .
'. Maybe the LDAP entry has no set display name attribute?',
@ -79,6 +88,15 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
}
$user->markLogin();
if(isset($users[0][$this->access->connection->ldapUserDisplayName])) {
$dpn = $users[0][$this->access->connection->ldapUserDisplayName];
$user->storeDisplayName($dpn);
}
if(isset($users[0]['uid'])) {
$user->storeLDAPUserName($users[0]['uid']);
} else if(isset($users[0]['samaccountname'])) {
$user->storeLDAPUserName($users[0]['samaccountname']);
}
return $user->getUsername();
}
@ -127,6 +145,33 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
return $ldap_users;
}
/**
* checks whether a user is still available on LDAP
* @param string|\OCA\User_LDAP\lib\user\User $user either the ownCloud user
* name or an instance of that user
* @return bool
*/
public function userExistsOnLDAP($user) {
if(is_string($user)) {
$user = $this->access->userManager->get($user);
}
if(!$user instanceof User) {
return false;
}
$dn = $user->getDN();
//check if user really still exists by reading its entry
if(!is_array($this->access->readAttribute($dn, ''))) {
$lcr = $this->access->connection->getConnectionResource();
if(is_null($lcr)) {
throw new \Exception('No LDAP Connection to server ' . $this->access->connection->ldapHost);
}
return false;
}
return true;
}
/**
* check if a user exists
* @param string $uid the username
@ -143,36 +188,56 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
$this->access->connection->ldapHost, \OCP\Util::DEBUG);
$this->access->connection->writeToCache('userExists'.$uid, false);
return false;
} 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
if(!is_array($this->access->readAttribute($dn, ''))) {
\OCP\Util::writeLog('user_ldap', 'LDAP says no user '.$dn.' on '.
$this->access->connection->ldapHost, \OCP\Util::DEBUG);
$this->access->connection->writeToCache('userExists'.$uid, false);
try {
$result = $this->userExistsOnLDAP($user);
$this->access->connection->writeToCache('userExists'.$uid, $result);
if($result === true) {
$user->update();
}
return $result;
} catch (\Exception $e) {
\OCP\Util::writeLog('user_ldap', $e->getMessage(), \OCP\Util::WARN);
return false;
}
$this->access->connection->writeToCache('userExists'.$uid, true);
$user->update();
return true;
}
/**
* delete a user
* returns whether a user was deleted in LDAP
*
* @param string $uid The username of the user to delete
* @return bool
*
* Deletes a user
*/
public function deleteUser($uid) {
return false;
$pref = \OC::$server->getConfig();
$marked = $pref->getUserValue($uid, 'user_ldap', 'isDeleted', 0);
if(intval($marked) === 0) {
\OC::$server->getLogger()->notice(
'User '.$uid . ' is not marked as deleted, not cleaning up.',
array('app' => 'user_ldap'));
return false;
}
\OC::$server->getLogger()->info('Cleaning up after user ' . $uid,
array('app' => 'user_ldap'));
//Get Home Directory out of user preferences so we can return it later,
//necessary for removing directories as done by OC_User.
$home = $pref->getUserValue($uid, 'user_ldap', 'homePath', '');
$this->homesToKill[$uid] = $home;
$this->access->unmapUser($uid);
return true;
}
/**
* get the user's home directory
* @param string $uid the username
* @return boolean
* @return string|bool
*/
public function getHome($uid) {
// user Exists check required as it is not done in user proxy!
@ -180,10 +245,16 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
return false;
}
if(isset($this->homesToKill[$uid]) && !empty($this->homesToKill[$uid])) {
//a deleted user who needs some clean up
return $this->homesToKill[$uid];
}
$cacheKey = 'getHome'.$uid;
if($this->access->connection->isCached($cacheKey)) {
return $this->access->connection->getFromCache($cacheKey);
}
$pref = \OC::$server->getConfig();
if(strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0) {
$attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
$homedir = $this->access->readAttribute(
@ -203,12 +274,17 @@ class USER_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserIn
\OC::$SERVERROOT.'/data' ) . '/' . $homedir[0];
}
$this->access->connection->writeToCache($cacheKey, $homedir);
//we need it to store it in the DB as well in case a user gets
//deleted so we can clean up afterwards
$pref->setUserValue($uid, 'user_ldap', 'homePath', $homedir);
//TODO: if home directory changes, the old one needs to be removed.
return $homedir;
}
}
//false will apply default behaviour as defined and done by OC_User
$this->access->connection->writeToCache($cacheKey, false);
$pref->setUserValue($uid, 'user_ldap', 'homePath', '');
return false;
}

@ -24,6 +24,7 @@
namespace OCA\user_ldap;
use OCA\user_ldap\lib\ILDAPWrapper;
use OCA\User_LDAP\lib\User\User;
class User_Proxy extends lib\Proxy implements \OCP\IUserBackend, \OCP\UserInterface {
private $backends = array();
@ -152,6 +153,17 @@ class User_Proxy extends lib\Proxy implements \OCP\IUserBackend, \OCP\UserInterf
return $this->handleRequest($uid, 'userExists', array($uid));
}
/**
* check if a user exists on LDAP
* @param string|OCA\User_LDAP\lib\User\User $user either the ownCloud user
* name or an instance of that user
* @return boolean
*/
public function userExistsOnLDAP($user) {
$id = ($user instanceof User) ? $user->getUsername() : $user;
return $this->handleRequest($id, 'userExistsOnLDAP', array($user));
}
/**
* Check if the password is correct
* @param string $uid The username
@ -217,7 +229,7 @@ class User_Proxy extends lib\Proxy implements \OCP\IUserBackend, \OCP\UserInterf
* Deletes a user
*/
public function deleteUser($uid) {
return false;
return $this->handleRequest($uid, 'deleteUser', array($uid));
}
/**

@ -80,7 +80,7 @@ $CONFIG = array(
/**
* Where user files are stored; this defaults to ``data/`` in the ownCloud
* directory. The SQLite database is also stored here, when you use SQLite. (SQLite is
* directory. The SQLite database is also stored here, when you use SQLite. (SQLite is
* available only in ownCloud Community Edition)
*/
'datadirectory' => '/var/www/owncloud/data',
@ -665,6 +665,20 @@ $CONFIG = array(
'OC\Preview\MarkDown'
),
/**
* LDAP
*
* Global settings used by LDAP User and Group Backend
*/
/**
* defines the interval in minutes for the background job that checks user
* existance and marks them as ready to be cleaned up. The number is always
* minutes. Setting it to 0 disables the feature.
* See command line (occ) methods ldap:show-remnants and user:delete
*/
'ldapUserCleanupInterval' => 51,
/**
* Maintenance

@ -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>');
}
}

@ -22,5 +22,6 @@ $application->add(new OC\Core\Command\Maintenance\Repair($repair, \OC::$server->
$application->add(new OC\Core\Command\User\Report());
$application->add(new OC\Core\Command\User\ResetPassword(\OC::$server->getUserManager()));
$application->add(new OC\Core\Command\User\LastSeen());
$application->add(new OC\Core\Command\User\Delete());
$application->add(new OC\Core\Command\L10n\CreateJs());

@ -137,10 +137,12 @@ class Preferences {
* @param string $app
* @param string $key
* @param string $value
* @param int|null $limit
* @param int|null $offset
* @return array
* @deprecated use getUsersForUserValue of \OCP\IConfig instead
*/
public function getUsersForValue($app, $key, $value) {
public function getUsersForValue($app, $key, $value, $limit = null, $offset = null) {
return $this->config->getUsersForUserValue($app, $key, $value);
}

Loading…
Cancel
Save