Completed
Push — master ( ede649...d3e7dd )
by Lukas
16:27
created

Sync::setCycle()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2017 Arthur Schiwon <[email protected]>
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 * This program is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License as
11
 * published by the Free Software Foundation, either version 3 of the
12
 * License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
24
namespace OCA\User_LDAP\Jobs;
25
26
use OC\BackgroundJob\TimedJob;
27
use OC\ServerNotAvailableException;
28
use OCA\User_LDAP\Access;
29
use OCA\User_LDAP\Configuration;
30
use OCA\User_LDAP\Connection;
31
use OCA\User_LDAP\FilesystemHelper;
32
use OCA\User_LDAP\Helper;
33
use OCA\User_LDAP\LDAP;
34
use OCA\User_LDAP\LogWrapper;
35
use OCA\User_LDAP\Mapping\UserMapping;
36
use OCA\User_LDAP\User\Manager;
37
use OCP\IAvatarManager;
38
use OCP\IConfig;
39
use OCP\IDBConnection;
40
use OCP\Image;
41
use OCP\IUserManager;
42
use OCP\Notification\IManager;
43
44
class Sync extends TimedJob {
45
	const MAX_INTERVAL = 12 * 60 * 60; // 12h
46
	const MIN_INTERVAL = 30 * 60; // 30min
47
	/** @var  Helper */
48
	protected $ldapHelper;
49
	/** @var  LDAP */
50
	protected $ldap;
51
	/** @var  Manager */
52
	protected $userManager;
53
	/** @var UserMapping */
54
	protected $mapper;
55
	/** @var  IConfig */
56
	protected $config;
57
	/** @var  IAvatarManager */
58
	protected $avatarManager;
59
	/** @var  IDBConnection */
60
	protected $dbc;
61
	/** @var  IUserManager */
62
	protected $ncUserManager;
63
	/** @var  IManager */
64
	protected $notificationManager;
65
66
	public function __construct() {
67
		$this->setInterval(
68
			\OC::$server->getConfig()->getAppValue(
69
				'user_ldap',
70
				'background_sync_interval',
71
				self::MIN_INTERVAL
72
			)
73
		);
74
	}
75
76
	/**
77
	 * updates the interval
78
	 *
79
	 * the idea is to adjust the interval depending on the amount of known users
80
	 * and the attempt to update each user one day. At most it would run every
81
	 * 30 minutes, and at least every 12 hours.
82
	 */
83
	public function updateInterval() {
84
		$minPagingSize = $this->getMinPagingSize();
85
		$mappedUsers = $this->mapper->count();
86
87
		$runsPerDay = ($minPagingSize === 0 || $mappedUsers === 0) ? self::MAX_INTERVAL
88
			: $mappedUsers / $minPagingSize;
89
		$interval = floor(24 * 60 * 60 / $runsPerDay);
90
		$interval = min(max($interval, self::MIN_INTERVAL), self::MAX_INTERVAL);
91
92
		$this->config->setAppValue('user_ldap', 'background_sync_interval', $interval);
93
	}
94
95
	/**
96
	 * returns the smallest configured paging size
97
	 * @return int
98
	 */
99
	protected function getMinPagingSize() {
100
		$configKeys = $this->config->getAppKeys('user_ldap');
101
		$configKeys = array_filter($configKeys, function($key) {
102
			return strpos($key, 'ldap_paging_size') !== false;
103
		});
104
		$minPagingSize = null;
105
		foreach ($configKeys as $configKey) {
106
			$pagingSize = $this->config->getAppValue('user_ldap', $configKey, $minPagingSize);
107
			$minPagingSize = $minPagingSize === null ? $pagingSize : min($minPagingSize, $pagingSize);
108
		}
109
		return (int)$minPagingSize;
110
	}
111
112
	/**
113
	 * @param array $argument
114
	 */
115
	protected function run($argument) {
116
		$this->setArgument($argument);
117
118
		$isBackgroundJobModeAjax = $this->config
119
				->getAppValue('core', 'backgroundjobs_mode', 'ajax') === 'ajax';
120
		if($isBackgroundJobModeAjax) {
121
			return;
122
		}
123
124
		$cycleData = $this->getCycle();
125
		if($cycleData === null) {
126
			$cycleData = $this->determineNextCycle();
127
			if($cycleData === null) {
128
				$this->updateInterval();
129
				return;
130
			}
131
		}
132
133
		if(!$this->qualifiesToRun($cycleData)) {
134
			$this->updateInterval();
135
			return;
136
		}
137
138
		try {
139
			$expectMoreResults = $this->runCycle($cycleData);
140
			if ($expectMoreResults) {
141
				$this->increaseOffset($cycleData);
142
			} else {
143
				$this->determineNextCycle();
144
			}
145
			$this->updateInterval();
146
		} catch (ServerNotAvailableException $e) {
147
			$this->determineNextCycle();
148
		}
149
	}
150
151
	/**
152
	 * @param array $cycleData
153
	 * @return bool whether more results are expected from the same configuration
154
	 */
155
	public function runCycle($cycleData) {
156
		$connection = new Connection($this->ldap, $cycleData['prefix']);
157
		$access = new Access($connection, $this->ldap, $this->userManager, $this->ldapHelper, $this->config);
158
		$access->setUserMapper($this->mapper);
159
160
		$filter = $access->combineFilterWithAnd(array(
161
			$access->connection->ldapUserFilter,
162
			$access->connection->ldapUserDisplayName . '=*',
163
			$access->getFilterPartForUserSearch('')
164
		));
165
		$results = $access->fetchListOfUsers(
166
			$filter,
167
			$access->userManager->getAttributes(),
168
			$connection->ldapPagingSize,
169
			$cycleData['offset'],
170
			true
171
		);
172
173
		if($connection->ldapPagingSize === 0) {
174
			return true;
175
		}
176
		return count($results) !== $connection->ldapPagingSize;
177
	}
178
179
	/**
180
	 * returns the info about the current cycle that should be run, if any,
181
	 * otherwise null
182
	 *
183
	 * @return array|null
184
	 */
185
	public function getCycle() {
186
		$prefixes = $this->ldapHelper->getServerConfigurationPrefixes(true);
187
		if(count($prefixes) === 0) {
188
			return null;
189
		}
190
191
		$cycleData = [
192
			'prefix' => $this->config->getAppValue('user_ldap', 'background_sync_prefix', null),
193
			'offset' => (int)$this->config->getAppValue('user_ldap', 'background_sync_offset', 0),
194
		];
195
196
		if(
197
			$cycleData['prefix'] !== null
198
			&& in_array($cycleData['prefix'], $prefixes)
199
		) {
200
			return $cycleData;
201
		}
202
203
		return null;
204
	}
205
206
	/**
207
	 * Save the provided cycle information in the DB
208
	 *
209
	 * @param array $cycleData
210
	 */
211
	public function setCycle(array $cycleData) {
212
		$this->config->setAppValue('user_ldap', 'background_sync_prefix', $cycleData['prefix']);
213
		$this->config->setAppValue('user_ldap', 'background_sync_offset', $cycleData['offset']);
214
	}
215
216
	/**
217
	 * returns data about the next cycle that should run, if any, otherwise
218
	 * null. It also always goes for the next LDAP configuration!
219
	 *
220
	 * @param array|null $cycleData the old cycle
221
	 * @return array|null
222
	 */
223
	public function determineNextCycle(array $cycleData = null) {
224
		$prefixes = $this->ldapHelper->getServerConfigurationPrefixes(true);
225
		if(count($prefixes) === 0) {
226
			return null;
227
		}
228
229
		// get the next prefix in line and remember it
230
		$oldPrefix = $cycleData === null ? null : $cycleData['prefix'];
231
		$prefix = $this->getNextPrefix($oldPrefix);
232
		if($prefix === null) {
233
			return null;
234
		}
235
		$cycleData['prefix'] = $prefix;
236
		$cycleData['offset'] = 0;
237
		$this->setCycle(['prefix' => $prefix, 'offset' => 0]);
238
239
		return $cycleData;
240
	}
241
242
	/**
243
	 * Checks whether the provided cycle should be run. Currently only the
244
	 * last configuration change goes into account (at least one hour).
245
	 *
246
	 * @param $cycleData
247
	 * @return bool
248
	 */
249
	protected function qualifiesToRun($cycleData) {
250
		$lastChange = $this->config->getAppValue('user_ldap', $cycleData['prefix'] . '_lastChange', 0);
251
		if((time() - $lastChange) > 60 * 30) {
252
			return true;
253
		}
254
		return false;
255
	}
256
257
	/**
258
	 * increases the offset of the current cycle for the next run
259
	 *
260
	 * @param $cycleData
261
	 */
262
	protected function increaseOffset($cycleData) {
263
		$ldapConfig = new Configuration($cycleData['prefix']);
264
		$cycleData['offset'] += (int)$ldapConfig->ldapPagingSize;
265
		$this->setCycle($cycleData);
266
	}
267
268
	/**
269
	 * determines the next configuration prefix based on the last one (if any)
270
	 *
271
	 * @param string|null $lastPrefix
272
	 * @return string|null
273
	 */
274
	protected function getNextPrefix($lastPrefix) {
275
		$prefixes = $this->ldapHelper->getServerConfigurationPrefixes(true);
276
		$noOfPrefixes = count($prefixes);
277
		if($noOfPrefixes === 0) {
278
			return null;
279
		}
280
		$i = $lastPrefix === null ? false : array_search($lastPrefix, $prefixes, true);
281
		if($i === false) {
282
			$i = -1;
283
		} else {
284
			$i++;
285
		}
286
287
		if(!isset($prefixes[$i])) {
288
			$i = 0;
289
		}
290
		return $prefixes[$i];
291
	}
292
293
	/**
294
	 * "fixes" DI
295
	 *
296
	 * @param array $argument
297
	 */
298
	public function setArgument($argument) {
299 View Code Duplication
		if(isset($argument['config'])) {
300
			$this->config = $argument['config'];
301
		} else {
302
			$this->config = \OC::$server->getConfig();
303
		}
304
305
		if(isset($argument['helper'])) {
306
			$this->ldapHelper = $argument['helper'];
307
		} else {
308
			$this->ldapHelper = new Helper($this->config);
309
		}
310
311
		if(isset($argument['ldapWrapper'])) {
312
			$this->ldap = $argument['ldapWrapper'];
313
		} else {
314
			$this->ldap = new LDAP();
315
		}
316
317
		if(isset($argument['avatarManager'])) {
318
			$this->avatarManager = $argument['avatarManager'];
319
		} else {
320
			$this->avatarManager = \OC::$server->getAvatarManager();
321
		}
322
323 View Code Duplication
		if(isset($argument['dbc'])) {
324
			$this->dbc = $argument['dbc'];
325
		} else {
326
			$this->dbc = \OC::$server->getDatabaseConnection();
327
		}
328
329
		if(isset($argument['ncUserManager'])) {
330
			$this->ncUserManager = $argument['ncUserManager'];
331
		} else {
332
			$this->ncUserManager = \OC::$server->getUserManager();
333
		}
334
335
		if(isset($argument['notificationManager'])) {
336
			$this->notificationManager = $argument['notificationManager'];
337
		} else {
338
			$this->notificationManager = \OC::$server->getNotificationManager();
339
		}
340
341
		if(isset($argument['userManager'])) {
342
			$this->userManager = $argument['userManager'];
343
		} else {
344
			$this->userManager = new Manager(
345
				$this->config,
346
				new FilesystemHelper(),
347
				new LogWrapper(),
348
				$this->avatarManager,
349
				new Image(),
350
				$this->dbc,
351
				$this->ncUserManager,
352
				$this->notificationManager
353
			);
354
		}
355
356
		if(isset($argument['mapper'])) {
357
			$this->mapper = $argument['mapper'];
358
		} else {
359
			$this->mapper = new UserMapping($this->dbc);
360
		}
361
	}
362
}
363