123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- <?php
- /**
- * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- namespace OCA\User_LDAP\Jobs;
- use OC\ServerNotAvailableException;
- use OCA\User_LDAP\AccessFactory;
- use OCA\User_LDAP\Configuration;
- use OCA\User_LDAP\ConnectionFactory;
- use OCA\User_LDAP\Helper;
- use OCA\User_LDAP\LDAP;
- use OCA\User_LDAP\Mapping\UserMapping;
- use OCA\User_LDAP\User\Manager;
- use OCP\AppFramework\Utility\ITimeFactory;
- use OCP\BackgroundJob\TimedJob;
- use OCP\IAvatarManager;
- use OCP\IConfig;
- use OCP\IDBConnection;
- use OCP\IUserManager;
- use OCP\Notification\IManager;
- use Psr\Log\LoggerInterface;
- class Sync extends TimedJob {
- public const MAX_INTERVAL = 12 * 60 * 60; // 12h
- public const MIN_INTERVAL = 30 * 60; // 30min
- /** @var Helper */
- protected $ldapHelper;
- /** @var LDAP */
- protected $ldap;
- /** @var Manager */
- protected $userManager;
- /** @var UserMapping */
- protected $mapper;
- /** @var IConfig */
- protected $config;
- /** @var IAvatarManager */
- protected $avatarManager;
- /** @var IDBConnection */
- protected $dbc;
- /** @var IUserManager */
- protected $ncUserManager;
- /** @var LoggerInterface */
- protected $logger;
- /** @var IManager */
- protected $notificationManager;
- /** @var ConnectionFactory */
- protected $connectionFactory;
- /** @var AccessFactory */
- protected $accessFactory;
- public function __construct(Manager $userManager, ITimeFactory $time) {
- parent::__construct($time);
- $this->userManager = $userManager;
- $this->setInterval(
- \OC::$server->getConfig()->getAppValue(
- 'user_ldap',
- 'background_sync_interval',
- self::MIN_INTERVAL
- )
- );
- }
- /**
- * updates the interval
- *
- * the idea is to adjust the interval depending on the amount of known users
- * and the attempt to update each user one day. At most it would run every
- * 30 minutes, and at least every 12 hours.
- */
- public function updateInterval() {
- $minPagingSize = $this->getMinPagingSize();
- $mappedUsers = $this->mapper->count();
- $runsPerDay = ($minPagingSize === 0 || $mappedUsers === 0) ? self::MAX_INTERVAL
- : $mappedUsers / $minPagingSize;
- $interval = floor(24 * 60 * 60 / $runsPerDay);
- $interval = min(max($interval, self::MIN_INTERVAL), self::MAX_INTERVAL);
- $this->config->setAppValue('user_ldap', 'background_sync_interval', $interval);
- }
- /**
- * returns the smallest configured paging size
- * @return int
- */
- protected function getMinPagingSize() {
- $configKeys = $this->config->getAppKeys('user_ldap');
- $configKeys = array_filter($configKeys, function ($key) {
- return strpos($key, 'ldap_paging_size') !== false;
- });
- $minPagingSize = null;
- foreach ($configKeys as $configKey) {
- $pagingSize = $this->config->getAppValue('user_ldap', $configKey, $minPagingSize);
- $minPagingSize = $minPagingSize === null ? $pagingSize : min($minPagingSize, $pagingSize);
- }
- return (int)$minPagingSize;
- }
- /**
- * @param array $argument
- */
- public function run($argument) {
- $this->setArgument($argument);
- $isBackgroundJobModeAjax = $this->config
- ->getAppValue('core', 'backgroundjobs_mode', 'ajax') === 'ajax';
- if ($isBackgroundJobModeAjax) {
- return;
- }
- $cycleData = $this->getCycle();
- if ($cycleData === null) {
- $cycleData = $this->determineNextCycle();
- if ($cycleData === null) {
- $this->updateInterval();
- return;
- }
- }
- if (!$this->qualifiesToRun($cycleData)) {
- $this->updateInterval();
- return;
- }
- try {
- $expectMoreResults = $this->runCycle($cycleData);
- if ($expectMoreResults) {
- $this->increaseOffset($cycleData);
- } else {
- $this->determineNextCycle($cycleData);
- }
- $this->updateInterval();
- } catch (ServerNotAvailableException $e) {
- $this->determineNextCycle($cycleData);
- }
- }
- /**
- * @param array $cycleData
- * @return bool whether more results are expected from the same configuration
- */
- public function runCycle($cycleData) {
- $connection = $this->connectionFactory->get($cycleData['prefix']);
- $access = $this->accessFactory->get($connection);
- $access->setUserMapper($this->mapper);
- $filter = $access->combineFilterWithAnd([
- $access->connection->ldapUserFilter,
- $access->connection->ldapUserDisplayName . '=*',
- $access->getFilterPartForUserSearch('')
- ]);
- $results = $access->fetchListOfUsers(
- $filter,
- $access->userManager->getAttributes(),
- $connection->ldapPagingSize,
- $cycleData['offset'],
- true
- );
- if ((int)$connection->ldapPagingSize === 0) {
- return false;
- }
- return count($results) >= (int)$connection->ldapPagingSize;
- }
- /**
- * returns the info about the current cycle that should be run, if any,
- * otherwise null
- *
- * @return array|null
- */
- public function getCycle() {
- $prefixes = $this->ldapHelper->getServerConfigurationPrefixes(true);
- if (count($prefixes) === 0) {
- return null;
- }
- $cycleData = [
- 'prefix' => $this->config->getAppValue('user_ldap', 'background_sync_prefix', null),
- 'offset' => (int)$this->config->getAppValue('user_ldap', 'background_sync_offset', 0),
- ];
- if (
- $cycleData['prefix'] !== null
- && in_array($cycleData['prefix'], $prefixes)
- ) {
- return $cycleData;
- }
- return null;
- }
- /**
- * Save the provided cycle information in the DB
- *
- * @param array $cycleData
- */
- public function setCycle(array $cycleData) {
- $this->config->setAppValue('user_ldap', 'background_sync_prefix', $cycleData['prefix']);
- $this->config->setAppValue('user_ldap', 'background_sync_offset', $cycleData['offset']);
- }
- /**
- * returns data about the next cycle that should run, if any, otherwise
- * null. It also always goes for the next LDAP configuration!
- *
- * @param array|null $cycleData the old cycle
- * @return array|null
- */
- public function determineNextCycle(array $cycleData = null) {
- $prefixes = $this->ldapHelper->getServerConfigurationPrefixes(true);
- if (count($prefixes) === 0) {
- return null;
- }
- // get the next prefix in line and remember it
- $oldPrefix = $cycleData === null ? null : $cycleData['prefix'];
- $prefix = $this->getNextPrefix($oldPrefix);
- if ($prefix === null) {
- return null;
- }
- $cycleData['prefix'] = $prefix;
- $cycleData['offset'] = 0;
- $this->setCycle(['prefix' => $prefix, 'offset' => 0]);
- return $cycleData;
- }
- /**
- * Checks whether the provided cycle should be run. Currently only the
- * last configuration change goes into account (at least one hour).
- *
- * @param $cycleData
- * @return bool
- */
- public function qualifiesToRun($cycleData) {
- $lastChange = $this->config->getAppValue('user_ldap', $cycleData['prefix'] . '_lastChange', 0);
- if ((time() - $lastChange) > 60 * 30) {
- return true;
- }
- return false;
- }
- /**
- * increases the offset of the current cycle for the next run
- *
- * @param $cycleData
- */
- protected function increaseOffset($cycleData) {
- $ldapConfig = new Configuration($cycleData['prefix']);
- $cycleData['offset'] += (int)$ldapConfig->ldapPagingSize;
- $this->setCycle($cycleData);
- }
- /**
- * determines the next configuration prefix based on the last one (if any)
- *
- * @param string|null $lastPrefix
- * @return string|null
- */
- protected function getNextPrefix($lastPrefix) {
- $prefixes = $this->ldapHelper->getServerConfigurationPrefixes(true);
- $noOfPrefixes = count($prefixes);
- if ($noOfPrefixes === 0) {
- return null;
- }
- $i = $lastPrefix === null ? false : array_search($lastPrefix, $prefixes, true);
- if ($i === false) {
- $i = -1;
- } else {
- $i++;
- }
- if (!isset($prefixes[$i])) {
- $i = 0;
- }
- return $prefixes[$i];
- }
- /**
- * "fixes" DI
- */
- public function setArgument($argument) {
- if (isset($argument['config'])) {
- $this->config = $argument['config'];
- } else {
- $this->config = \OC::$server->getConfig();
- }
- if (isset($argument['helper'])) {
- $this->ldapHelper = $argument['helper'];
- } else {
- $this->ldapHelper = new Helper($this->config, \OC::$server->getDatabaseConnection());
- }
- if (isset($argument['ldapWrapper'])) {
- $this->ldap = $argument['ldapWrapper'];
- } else {
- $this->ldap = new LDAP($this->config->getSystemValueString('ldap_log_file'));
- }
- if (isset($argument['avatarManager'])) {
- $this->avatarManager = $argument['avatarManager'];
- } else {
- $this->avatarManager = \OC::$server->getAvatarManager();
- }
- if (isset($argument['dbc'])) {
- $this->dbc = $argument['dbc'];
- } else {
- $this->dbc = \OC::$server->getDatabaseConnection();
- }
- if (isset($argument['ncUserManager'])) {
- $this->ncUserManager = $argument['ncUserManager'];
- } else {
- $this->ncUserManager = \OC::$server->getUserManager();
- }
- if (isset($argument['logger'])) {
- $this->logger = $argument['logger'];
- } else {
- $this->logger = \OC::$server->get(LoggerInterface::class);
- }
- if (isset($argument['notificationManager'])) {
- $this->notificationManager = $argument['notificationManager'];
- } else {
- $this->notificationManager = \OC::$server->getNotificationManager();
- }
- if (isset($argument['userManager'])) {
- $this->userManager = $argument['userManager'];
- }
- if (isset($argument['mapper'])) {
- $this->mapper = $argument['mapper'];
- } else {
- $this->mapper = \OCP\Server::get(UserMapping::class);
- }
- if (isset($argument['connectionFactory'])) {
- $this->connectionFactory = $argument['connectionFactory'];
- } else {
- $this->connectionFactory = new ConnectionFactory($this->ldap);
- }
- if (isset($argument['accessFactory'])) {
- $this->accessFactory = $argument['accessFactory'];
- } else {
- $this->accessFactory = new AccessFactory(
- $this->ldap,
- $this->userManager,
- $this->ldapHelper,
- $this->config,
- $this->ncUserManager,
- $this->logger
- );
- }
- }
- }
|