Completed
Pull Request — master (#7611)
by Blizzz
27:16
created
lib/private/User/Manager.php 1 patch
Indentation   +482 added lines, -482 removed lines patch added patch discarded remove patch
@@ -54,486 +54,486 @@
 block discarded – undo
54 54
  * @package OC\User
55 55
  */
56 56
 class Manager extends PublicEmitter implements IUserManager {
57
-	/**
58
-	 * @var \OCP\UserInterface[] $backends
59
-	 */
60
-	private $backends = array();
61
-
62
-	/**
63
-	 * @var \OC\User\User[] $cachedUsers
64
-	 */
65
-	private $cachedUsers = array();
66
-
67
-	/**
68
-	 * @var \OCP\IConfig $config
69
-	 */
70
-	private $config;
71
-
72
-	/**
73
-	 * @param \OCP\IConfig $config
74
-	 */
75
-	public function __construct(IConfig $config) {
76
-		$this->config = $config;
77
-		$cachedUsers = &$this->cachedUsers;
78
-		$this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
79
-			/** @var \OC\User\User $user */
80
-			unset($cachedUsers[$user->getUID()]);
81
-		});
82
-	}
83
-
84
-	/**
85
-	 * Get the active backends
86
-	 * @return \OCP\UserInterface[]
87
-	 */
88
-	public function getBackends() {
89
-		return $this->backends;
90
-	}
91
-
92
-	/**
93
-	 * register a user backend
94
-	 *
95
-	 * @param \OCP\UserInterface $backend
96
-	 */
97
-	public function registerBackend($backend) {
98
-		$this->backends[] = $backend;
99
-	}
100
-
101
-	/**
102
-	 * remove a user backend
103
-	 *
104
-	 * @param \OCP\UserInterface $backend
105
-	 */
106
-	public function removeBackend($backend) {
107
-		$this->cachedUsers = array();
108
-		if (($i = array_search($backend, $this->backends)) !== false) {
109
-			unset($this->backends[$i]);
110
-		}
111
-	}
112
-
113
-	/**
114
-	 * remove all user backends
115
-	 */
116
-	public function clearBackends() {
117
-		$this->cachedUsers = array();
118
-		$this->backends = array();
119
-	}
120
-
121
-	/**
122
-	 * get a user by user id
123
-	 *
124
-	 * @param string $uid
125
-	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
126
-	 */
127
-	public function get($uid) {
128
-		if (is_null($uid) || $uid === '' || $uid === false) {
129
-			return null;
130
-		}
131
-		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
132
-			return $this->cachedUsers[$uid];
133
-		}
134
-		foreach ($this->backends as $backend) {
135
-			if ($backend->userExists($uid)) {
136
-				return $this->getUserObject($uid, $backend);
137
-			}
138
-		}
139
-		return null;
140
-	}
141
-
142
-	/**
143
-	 * get or construct the user object
144
-	 *
145
-	 * @param string $uid
146
-	 * @param \OCP\UserInterface $backend
147
-	 * @param bool $cacheUser If false the newly created user object will not be cached
148
-	 * @return \OC\User\User
149
-	 */
150
-	protected function getUserObject($uid, $backend, $cacheUser = true) {
151
-		if (isset($this->cachedUsers[$uid])) {
152
-			return $this->cachedUsers[$uid];
153
-		}
154
-
155
-		$user = new User($uid, $backend, $this, $this->config);
156
-		if ($cacheUser) {
157
-			$this->cachedUsers[$uid] = $user;
158
-		}
159
-		return $user;
160
-	}
161
-
162
-	/**
163
-	 * check if a user exists
164
-	 *
165
-	 * @param string $uid
166
-	 * @return bool
167
-	 */
168
-	public function userExists($uid) {
169
-		$user = $this->get($uid);
170
-		return ($user !== null);
171
-	}
172
-
173
-	/**
174
-	 * Check if the password is valid for the user
175
-	 *
176
-	 * @param string $loginName
177
-	 * @param string $password
178
-	 * @return mixed the User object on success, false otherwise
179
-	 */
180
-	public function checkPassword($loginName, $password) {
181
-		$result = $this->checkPasswordNoLogging($loginName, $password);
182
-
183
-		if ($result === false) {
184
-			\OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
185
-		}
186
-
187
-		return $result;
188
-	}
189
-
190
-	/**
191
-	 * Check if the password is valid for the user
192
-	 *
193
-	 * @internal
194
-	 * @param string $loginName
195
-	 * @param string $password
196
-	 * @return mixed the User object on success, false otherwise
197
-	 */
198
-	public function checkPasswordNoLogging($loginName, $password) {
199
-		$loginName = str_replace("\0", '', $loginName);
200
-		$password = str_replace("\0", '', $password);
201
-
202
-		foreach ($this->backends as $backend) {
203
-			if ($backend->implementsActions(Backend::CHECK_PASSWORD)) {
204
-				$uid = $backend->checkPassword($loginName, $password);
205
-				if ($uid !== false) {
206
-					return $this->getUserObject($uid, $backend);
207
-				}
208
-			}
209
-		}
210
-
211
-		return false;
212
-	}
213
-
214
-	/**
215
-	 * search by user id
216
-	 *
217
-	 * @param string $pattern
218
-	 * @param int $limit
219
-	 * @param int $offset
220
-	 * @return \OC\User\User[]
221
-	 */
222
-	public function search($pattern, $limit = null, $offset = null) {
223
-		$users = array();
224
-		foreach ($this->backends as $backend) {
225
-			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
226
-			if (is_array($backendUsers)) {
227
-				foreach ($backendUsers as $uid) {
228
-					$users[$uid] = $this->getUserObject($uid, $backend);
229
-				}
230
-			}
231
-		}
232
-
233
-		uasort($users, function ($a, $b) {
234
-			/**
235
-			 * @var \OC\User\User $a
236
-			 * @var \OC\User\User $b
237
-			 */
238
-			return strcmp($a->getUID(), $b->getUID());
239
-		});
240
-		return $users;
241
-	}
242
-
243
-	/**
244
-	 * search by displayName
245
-	 *
246
-	 * @param string $pattern
247
-	 * @param int $limit
248
-	 * @param int $offset
249
-	 * @return \OC\User\User[]
250
-	 */
251
-	public function searchDisplayName($pattern, $limit = null, $offset = null) {
252
-		$users = array();
253
-		foreach ($this->backends as $backend) {
254
-			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
255
-			if (is_array($backendUsers)) {
256
-				foreach ($backendUsers as $uid => $displayName) {
257
-					$users[] = $this->getUserObject($uid, $backend);
258
-				}
259
-			}
260
-		}
261
-
262
-		usort($users, function ($a, $b) {
263
-			/**
264
-			 * @var \OC\User\User $a
265
-			 * @var \OC\User\User $b
266
-			 */
267
-			return strcmp(strtolower($a->getDisplayName()), strtolower($b->getDisplayName()));
268
-		});
269
-		return $users;
270
-	}
271
-
272
-	/**
273
-	 * @param string $uid
274
-	 * @param string $password
275
-	 * @throws \InvalidArgumentException
276
-	 * @return bool|IUser the created user or false
277
-	 */
278
-	public function createUser($uid, $password) {
279
-		$localBackends = [];
280
-		foreach ($this->backends as $backend) {
281
-			if ($backend instanceof Database) {
282
-				// First check if there is another user backend
283
-				$localBackends[] = $backend;
284
-				continue;
285
-			}
286
-
287
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
288
-				return $this->createUserFromBackend($uid, $password, $backend);
289
-			}
290
-		}
291
-
292
-		foreach ($localBackends as $backend) {
293
-			if ($backend->implementsActions(Backend::CREATE_USER)) {
294
-				return $this->createUserFromBackend($uid, $password, $backend);
295
-			}
296
-		}
297
-
298
-		return false;
299
-	}
300
-
301
-	/**
302
-	 * @param string $uid
303
-	 * @param string $password
304
-	 * @param UserInterface $backend
305
-	 * @return IUser|null
306
-	 * @throws \InvalidArgumentException
307
-	 */
308
-	public function createUserFromBackend($uid, $password, UserInterface $backend) {
309
-		$l = \OC::$server->getL10N('lib');
310
-
311
-		// Check the name for bad characters
312
-		// Allowed are: "a-z", "A-Z", "0-9" and "_.@-'"
313
-		if (preg_match('/[^a-zA-Z0-9 _\.@\-\']/', $uid)) {
314
-			throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:'
315
-				. ' "a-z", "A-Z", "0-9", and "_.@-\'"'));
316
-		}
317
-		// No empty username
318
-		if (trim($uid) === '') {
319
-			throw new \InvalidArgumentException($l->t('A valid username must be provided'));
320
-		}
321
-		// No whitespace at the beginning or at the end
322
-		if (trim($uid) !== $uid) {
323
-			throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end'));
324
-		}
325
-		// Username only consists of 1 or 2 dots (directory traversal)
326
-		if ($uid === '.' || $uid === '..') {
327
-			throw new \InvalidArgumentException($l->t('Username must not consist of dots only'));
328
-		}
329
-		// No empty password
330
-		if (trim($password) === '') {
331
-			throw new \InvalidArgumentException($l->t('A valid password must be provided'));
332
-		}
333
-
334
-		// Check if user already exists
335
-		if ($this->userExists($uid)) {
336
-			throw new \InvalidArgumentException($l->t('The username is already being used'));
337
-		}
338
-
339
-		$this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
340
-		$state = $backend->createUser($uid, $password);
341
-		if($state === false) {
342
-			throw new \InvalidArgumentException($l->t('Could not create user'));
343
-		}
344
-		$user = $this->getUserObject($uid, $backend);
345
-		if ($user instanceof IUser) {
346
-			$this->emit('\OC\User', 'postCreateUser', [$user, $password]);
347
-		}
348
-		return $user;
349
-	}
350
-
351
-	/**
352
-	 * returns how many users per backend exist (if supported by backend)
353
-	 *
354
-	 * @param boolean $hasLoggedIn when true only users that have a lastLogin
355
-	 *                entry in the preferences table will be affected
356
-	 * @return array|int an array of backend class as key and count number as value
357
-	 *                if $hasLoggedIn is true only an int is returned
358
-	 */
359
-	public function countUsers($hasLoggedIn = false) {
360
-		if ($hasLoggedIn) {
361
-			return $this->countSeenUsers();
362
-		}
363
-		$userCountStatistics = [];
364
-		foreach ($this->backends as $backend) {
365
-			if ($backend->implementsActions(Backend::COUNT_USERS)) {
366
-				$backendUsers = $backend->countUsers();
367
-				if($backendUsers !== false) {
368
-					if($backend instanceof IUserBackend) {
369
-						$name = $backend->getBackendName();
370
-					} else {
371
-						$name = get_class($backend);
372
-					}
373
-					if(isset($userCountStatistics[$name])) {
374
-						$userCountStatistics[$name] += $backendUsers;
375
-					} else {
376
-						$userCountStatistics[$name] = $backendUsers;
377
-					}
378
-				}
379
-			}
380
-		}
381
-		return $userCountStatistics;
382
-	}
383
-
384
-	/**
385
-	 * The callback is executed for each user on each backend.
386
-	 * If the callback returns false no further users will be retrieved.
387
-	 *
388
-	 * @param \Closure $callback
389
-	 * @param string $search
390
-	 * @param boolean $onlySeen when true only users that have a lastLogin entry
391
-	 *                in the preferences table will be affected
392
-	 * @since 9.0.0
393
-	 */
394
-	public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
395
-		if ($onlySeen) {
396
-			$this->callForSeenUsers($callback);
397
-		} else {
398
-			foreach ($this->getBackends() as $backend) {
399
-				$limit = 500;
400
-				$offset = 0;
401
-				do {
402
-					$users = $backend->getUsers($search, $limit, $offset);
403
-					foreach ($users as $uid) {
404
-						if (!$backend->userExists($uid)) {
405
-							continue;
406
-						}
407
-						$user = $this->getUserObject($uid, $backend, false);
408
-						$return = $callback($user);
409
-						if ($return === false) {
410
-							break;
411
-						}
412
-					}
413
-					$offset += $limit;
414
-				} while (count($users) >= $limit);
415
-			}
416
-		}
417
-	}
418
-
419
-	/**
420
-	 * returns how many users have logged in once
421
-	 *
422
-	 * @return int
423
-	 * @since 12.0.0
424
-	 */
425
-	public function countDisabledUsers() {
426
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
427
-		$queryBuilder->select($queryBuilder->createFunction('COUNT(*)'))
428
-			->from('preferences')
429
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
430
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
431
-			->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
432
-
433
-		$query = $queryBuilder->execute();
434
-
435
-		$result = (int)$query->fetchColumn();
436
-		$query->closeCursor();
437
-
438
-		return $result;
439
-	}
440
-
441
-	/**
442
-	 * returns how many users have logged in once
443
-	 *
444
-	 * @return int
445
-	 * @since 11.0.0
446
-	 */
447
-	public function countSeenUsers() {
448
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
449
-		$queryBuilder->select($queryBuilder->createFunction('COUNT(*)'))
450
-			->from('preferences')
451
-			->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
452
-			->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')))
453
-			->andWhere($queryBuilder->expr()->isNotNull('configvalue'));
454
-
455
-		$query = $queryBuilder->execute();
456
-
457
-		$result = (int)$query->fetchColumn();
458
-		$query->closeCursor();
459
-
460
-		return $result;
461
-	}
462
-
463
-	/**
464
-	 * @param \Closure $callback
465
-	 * @since 11.0.0
466
-	 */
467
-	public function callForSeenUsers(\Closure $callback) {
468
-		$limit = 1000;
469
-		$offset = 0;
470
-		do {
471
-			$userIds = $this->getSeenUserIds($limit, $offset);
472
-			$offset += $limit;
473
-			foreach ($userIds as $userId) {
474
-				foreach ($this->backends as $backend) {
475
-					if ($backend->userExists($userId)) {
476
-						$user = $this->getUserObject($userId, $backend, false);
477
-						$return = $callback($user);
478
-						if ($return === false) {
479
-							return;
480
-						}
481
-					}
482
-				}
483
-			}
484
-		} while (count($userIds) >= $limit);
485
-	}
486
-
487
-	/**
488
-	 * Getting all userIds that have a listLogin value requires checking the
489
-	 * value in php because on oracle you cannot use a clob in a where clause,
490
-	 * preventing us from doing a not null or length(value) > 0 check.
491
-	 * 
492
-	 * @param int $limit
493
-	 * @param int $offset
494
-	 * @return string[] with user ids
495
-	 */
496
-	private function getSeenUserIds($limit = null, $offset = null) {
497
-		$queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
498
-		$queryBuilder->select(['userid'])
499
-			->from('preferences')
500
-			->where($queryBuilder->expr()->eq(
501
-				'appid', $queryBuilder->createNamedParameter('login'))
502
-			)
503
-			->andWhere($queryBuilder->expr()->eq(
504
-				'configkey', $queryBuilder->createNamedParameter('lastLogin'))
505
-			)
506
-			->andWhere($queryBuilder->expr()->isNotNull('configvalue')
507
-			);
508
-
509
-		if ($limit !== null) {
510
-			$queryBuilder->setMaxResults($limit);
511
-		}
512
-		if ($offset !== null) {
513
-			$queryBuilder->setFirstResult($offset);
514
-		}
515
-		$query = $queryBuilder->execute();
516
-		$result = [];
517
-
518
-		while ($row = $query->fetch()) {
519
-			$result[] = $row['userid'];
520
-		}
521
-
522
-		$query->closeCursor();
523
-
524
-		return $result;
525
-	}
526
-
527
-	/**
528
-	 * @param string $email
529
-	 * @return IUser[]
530
-	 * @since 9.1.0
531
-	 */
532
-	public function getByEmail($email) {
533
-		$userIds = $this->config->getUsersForUserValue('settings', 'email', $email);
534
-
535
-		return array_map(function($uid) {
536
-			return $this->get($uid);
537
-		}, $userIds);
538
-	}
57
+    /**
58
+     * @var \OCP\UserInterface[] $backends
59
+     */
60
+    private $backends = array();
61
+
62
+    /**
63
+     * @var \OC\User\User[] $cachedUsers
64
+     */
65
+    private $cachedUsers = array();
66
+
67
+    /**
68
+     * @var \OCP\IConfig $config
69
+     */
70
+    private $config;
71
+
72
+    /**
73
+     * @param \OCP\IConfig $config
74
+     */
75
+    public function __construct(IConfig $config) {
76
+        $this->config = $config;
77
+        $cachedUsers = &$this->cachedUsers;
78
+        $this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
79
+            /** @var \OC\User\User $user */
80
+            unset($cachedUsers[$user->getUID()]);
81
+        });
82
+    }
83
+
84
+    /**
85
+     * Get the active backends
86
+     * @return \OCP\UserInterface[]
87
+     */
88
+    public function getBackends() {
89
+        return $this->backends;
90
+    }
91
+
92
+    /**
93
+     * register a user backend
94
+     *
95
+     * @param \OCP\UserInterface $backend
96
+     */
97
+    public function registerBackend($backend) {
98
+        $this->backends[] = $backend;
99
+    }
100
+
101
+    /**
102
+     * remove a user backend
103
+     *
104
+     * @param \OCP\UserInterface $backend
105
+     */
106
+    public function removeBackend($backend) {
107
+        $this->cachedUsers = array();
108
+        if (($i = array_search($backend, $this->backends)) !== false) {
109
+            unset($this->backends[$i]);
110
+        }
111
+    }
112
+
113
+    /**
114
+     * remove all user backends
115
+     */
116
+    public function clearBackends() {
117
+        $this->cachedUsers = array();
118
+        $this->backends = array();
119
+    }
120
+
121
+    /**
122
+     * get a user by user id
123
+     *
124
+     * @param string $uid
125
+     * @return \OC\User\User|null Either the user or null if the specified user does not exist
126
+     */
127
+    public function get($uid) {
128
+        if (is_null($uid) || $uid === '' || $uid === false) {
129
+            return null;
130
+        }
131
+        if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
132
+            return $this->cachedUsers[$uid];
133
+        }
134
+        foreach ($this->backends as $backend) {
135
+            if ($backend->userExists($uid)) {
136
+                return $this->getUserObject($uid, $backend);
137
+            }
138
+        }
139
+        return null;
140
+    }
141
+
142
+    /**
143
+     * get or construct the user object
144
+     *
145
+     * @param string $uid
146
+     * @param \OCP\UserInterface $backend
147
+     * @param bool $cacheUser If false the newly created user object will not be cached
148
+     * @return \OC\User\User
149
+     */
150
+    protected function getUserObject($uid, $backend, $cacheUser = true) {
151
+        if (isset($this->cachedUsers[$uid])) {
152
+            return $this->cachedUsers[$uid];
153
+        }
154
+
155
+        $user = new User($uid, $backend, $this, $this->config);
156
+        if ($cacheUser) {
157
+            $this->cachedUsers[$uid] = $user;
158
+        }
159
+        return $user;
160
+    }
161
+
162
+    /**
163
+     * check if a user exists
164
+     *
165
+     * @param string $uid
166
+     * @return bool
167
+     */
168
+    public function userExists($uid) {
169
+        $user = $this->get($uid);
170
+        return ($user !== null);
171
+    }
172
+
173
+    /**
174
+     * Check if the password is valid for the user
175
+     *
176
+     * @param string $loginName
177
+     * @param string $password
178
+     * @return mixed the User object on success, false otherwise
179
+     */
180
+    public function checkPassword($loginName, $password) {
181
+        $result = $this->checkPasswordNoLogging($loginName, $password);
182
+
183
+        if ($result === false) {
184
+            \OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
185
+        }
186
+
187
+        return $result;
188
+    }
189
+
190
+    /**
191
+     * Check if the password is valid for the user
192
+     *
193
+     * @internal
194
+     * @param string $loginName
195
+     * @param string $password
196
+     * @return mixed the User object on success, false otherwise
197
+     */
198
+    public function checkPasswordNoLogging($loginName, $password) {
199
+        $loginName = str_replace("\0", '', $loginName);
200
+        $password = str_replace("\0", '', $password);
201
+
202
+        foreach ($this->backends as $backend) {
203
+            if ($backend->implementsActions(Backend::CHECK_PASSWORD)) {
204
+                $uid = $backend->checkPassword($loginName, $password);
205
+                if ($uid !== false) {
206
+                    return $this->getUserObject($uid, $backend);
207
+                }
208
+            }
209
+        }
210
+
211
+        return false;
212
+    }
213
+
214
+    /**
215
+     * search by user id
216
+     *
217
+     * @param string $pattern
218
+     * @param int $limit
219
+     * @param int $offset
220
+     * @return \OC\User\User[]
221
+     */
222
+    public function search($pattern, $limit = null, $offset = null) {
223
+        $users = array();
224
+        foreach ($this->backends as $backend) {
225
+            $backendUsers = $backend->getUsers($pattern, $limit, $offset);
226
+            if (is_array($backendUsers)) {
227
+                foreach ($backendUsers as $uid) {
228
+                    $users[$uid] = $this->getUserObject($uid, $backend);
229
+                }
230
+            }
231
+        }
232
+
233
+        uasort($users, function ($a, $b) {
234
+            /**
235
+             * @var \OC\User\User $a
236
+             * @var \OC\User\User $b
237
+             */
238
+            return strcmp($a->getUID(), $b->getUID());
239
+        });
240
+        return $users;
241
+    }
242
+
243
+    /**
244
+     * search by displayName
245
+     *
246
+     * @param string $pattern
247
+     * @param int $limit
248
+     * @param int $offset
249
+     * @return \OC\User\User[]
250
+     */
251
+    public function searchDisplayName($pattern, $limit = null, $offset = null) {
252
+        $users = array();
253
+        foreach ($this->backends as $backend) {
254
+            $backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
255
+            if (is_array($backendUsers)) {
256
+                foreach ($backendUsers as $uid => $displayName) {
257
+                    $users[] = $this->getUserObject($uid, $backend);
258
+                }
259
+            }
260
+        }
261
+
262
+        usort($users, function ($a, $b) {
263
+            /**
264
+             * @var \OC\User\User $a
265
+             * @var \OC\User\User $b
266
+             */
267
+            return strcmp(strtolower($a->getDisplayName()), strtolower($b->getDisplayName()));
268
+        });
269
+        return $users;
270
+    }
271
+
272
+    /**
273
+     * @param string $uid
274
+     * @param string $password
275
+     * @throws \InvalidArgumentException
276
+     * @return bool|IUser the created user or false
277
+     */
278
+    public function createUser($uid, $password) {
279
+        $localBackends = [];
280
+        foreach ($this->backends as $backend) {
281
+            if ($backend instanceof Database) {
282
+                // First check if there is another user backend
283
+                $localBackends[] = $backend;
284
+                continue;
285
+            }
286
+
287
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
288
+                return $this->createUserFromBackend($uid, $password, $backend);
289
+            }
290
+        }
291
+
292
+        foreach ($localBackends as $backend) {
293
+            if ($backend->implementsActions(Backend::CREATE_USER)) {
294
+                return $this->createUserFromBackend($uid, $password, $backend);
295
+            }
296
+        }
297
+
298
+        return false;
299
+    }
300
+
301
+    /**
302
+     * @param string $uid
303
+     * @param string $password
304
+     * @param UserInterface $backend
305
+     * @return IUser|null
306
+     * @throws \InvalidArgumentException
307
+     */
308
+    public function createUserFromBackend($uid, $password, UserInterface $backend) {
309
+        $l = \OC::$server->getL10N('lib');
310
+
311
+        // Check the name for bad characters
312
+        // Allowed are: "a-z", "A-Z", "0-9" and "_.@-'"
313
+        if (preg_match('/[^a-zA-Z0-9 _\.@\-\']/', $uid)) {
314
+            throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:'
315
+                . ' "a-z", "A-Z", "0-9", and "_.@-\'"'));
316
+        }
317
+        // No empty username
318
+        if (trim($uid) === '') {
319
+            throw new \InvalidArgumentException($l->t('A valid username must be provided'));
320
+        }
321
+        // No whitespace at the beginning or at the end
322
+        if (trim($uid) !== $uid) {
323
+            throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end'));
324
+        }
325
+        // Username only consists of 1 or 2 dots (directory traversal)
326
+        if ($uid === '.' || $uid === '..') {
327
+            throw new \InvalidArgumentException($l->t('Username must not consist of dots only'));
328
+        }
329
+        // No empty password
330
+        if (trim($password) === '') {
331
+            throw new \InvalidArgumentException($l->t('A valid password must be provided'));
332
+        }
333
+
334
+        // Check if user already exists
335
+        if ($this->userExists($uid)) {
336
+            throw new \InvalidArgumentException($l->t('The username is already being used'));
337
+        }
338
+
339
+        $this->emit('\OC\User', 'preCreateUser', [$uid, $password]);
340
+        $state = $backend->createUser($uid, $password);
341
+        if($state === false) {
342
+            throw new \InvalidArgumentException($l->t('Could not create user'));
343
+        }
344
+        $user = $this->getUserObject($uid, $backend);
345
+        if ($user instanceof IUser) {
346
+            $this->emit('\OC\User', 'postCreateUser', [$user, $password]);
347
+        }
348
+        return $user;
349
+    }
350
+
351
+    /**
352
+     * returns how many users per backend exist (if supported by backend)
353
+     *
354
+     * @param boolean $hasLoggedIn when true only users that have a lastLogin
355
+     *                entry in the preferences table will be affected
356
+     * @return array|int an array of backend class as key and count number as value
357
+     *                if $hasLoggedIn is true only an int is returned
358
+     */
359
+    public function countUsers($hasLoggedIn = false) {
360
+        if ($hasLoggedIn) {
361
+            return $this->countSeenUsers();
362
+        }
363
+        $userCountStatistics = [];
364
+        foreach ($this->backends as $backend) {
365
+            if ($backend->implementsActions(Backend::COUNT_USERS)) {
366
+                $backendUsers = $backend->countUsers();
367
+                if($backendUsers !== false) {
368
+                    if($backend instanceof IUserBackend) {
369
+                        $name = $backend->getBackendName();
370
+                    } else {
371
+                        $name = get_class($backend);
372
+                    }
373
+                    if(isset($userCountStatistics[$name])) {
374
+                        $userCountStatistics[$name] += $backendUsers;
375
+                    } else {
376
+                        $userCountStatistics[$name] = $backendUsers;
377
+                    }
378
+                }
379
+            }
380
+        }
381
+        return $userCountStatistics;
382
+    }
383
+
384
+    /**
385
+     * The callback is executed for each user on each backend.
386
+     * If the callback returns false no further users will be retrieved.
387
+     *
388
+     * @param \Closure $callback
389
+     * @param string $search
390
+     * @param boolean $onlySeen when true only users that have a lastLogin entry
391
+     *                in the preferences table will be affected
392
+     * @since 9.0.0
393
+     */
394
+    public function callForAllUsers(\Closure $callback, $search = '', $onlySeen = false) {
395
+        if ($onlySeen) {
396
+            $this->callForSeenUsers($callback);
397
+        } else {
398
+            foreach ($this->getBackends() as $backend) {
399
+                $limit = 500;
400
+                $offset = 0;
401
+                do {
402
+                    $users = $backend->getUsers($search, $limit, $offset);
403
+                    foreach ($users as $uid) {
404
+                        if (!$backend->userExists($uid)) {
405
+                            continue;
406
+                        }
407
+                        $user = $this->getUserObject($uid, $backend, false);
408
+                        $return = $callback($user);
409
+                        if ($return === false) {
410
+                            break;
411
+                        }
412
+                    }
413
+                    $offset += $limit;
414
+                } while (count($users) >= $limit);
415
+            }
416
+        }
417
+    }
418
+
419
+    /**
420
+     * returns how many users have logged in once
421
+     *
422
+     * @return int
423
+     * @since 12.0.0
424
+     */
425
+    public function countDisabledUsers() {
426
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
427
+        $queryBuilder->select($queryBuilder->createFunction('COUNT(*)'))
428
+            ->from('preferences')
429
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('core')))
430
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('enabled')))
431
+            ->andWhere($queryBuilder->expr()->eq('configvalue', $queryBuilder->createNamedParameter('false'), IQueryBuilder::PARAM_STR));
432
+
433
+        $query = $queryBuilder->execute();
434
+
435
+        $result = (int)$query->fetchColumn();
436
+        $query->closeCursor();
437
+
438
+        return $result;
439
+    }
440
+
441
+    /**
442
+     * returns how many users have logged in once
443
+     *
444
+     * @return int
445
+     * @since 11.0.0
446
+     */
447
+    public function countSeenUsers() {
448
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
449
+        $queryBuilder->select($queryBuilder->createFunction('COUNT(*)'))
450
+            ->from('preferences')
451
+            ->where($queryBuilder->expr()->eq('appid', $queryBuilder->createNamedParameter('login')))
452
+            ->andWhere($queryBuilder->expr()->eq('configkey', $queryBuilder->createNamedParameter('lastLogin')))
453
+            ->andWhere($queryBuilder->expr()->isNotNull('configvalue'));
454
+
455
+        $query = $queryBuilder->execute();
456
+
457
+        $result = (int)$query->fetchColumn();
458
+        $query->closeCursor();
459
+
460
+        return $result;
461
+    }
462
+
463
+    /**
464
+     * @param \Closure $callback
465
+     * @since 11.0.0
466
+     */
467
+    public function callForSeenUsers(\Closure $callback) {
468
+        $limit = 1000;
469
+        $offset = 0;
470
+        do {
471
+            $userIds = $this->getSeenUserIds($limit, $offset);
472
+            $offset += $limit;
473
+            foreach ($userIds as $userId) {
474
+                foreach ($this->backends as $backend) {
475
+                    if ($backend->userExists($userId)) {
476
+                        $user = $this->getUserObject($userId, $backend, false);
477
+                        $return = $callback($user);
478
+                        if ($return === false) {
479
+                            return;
480
+                        }
481
+                    }
482
+                }
483
+            }
484
+        } while (count($userIds) >= $limit);
485
+    }
486
+
487
+    /**
488
+     * Getting all userIds that have a listLogin value requires checking the
489
+     * value in php because on oracle you cannot use a clob in a where clause,
490
+     * preventing us from doing a not null or length(value) > 0 check.
491
+     * 
492
+     * @param int $limit
493
+     * @param int $offset
494
+     * @return string[] with user ids
495
+     */
496
+    private function getSeenUserIds($limit = null, $offset = null) {
497
+        $queryBuilder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
498
+        $queryBuilder->select(['userid'])
499
+            ->from('preferences')
500
+            ->where($queryBuilder->expr()->eq(
501
+                'appid', $queryBuilder->createNamedParameter('login'))
502
+            )
503
+            ->andWhere($queryBuilder->expr()->eq(
504
+                'configkey', $queryBuilder->createNamedParameter('lastLogin'))
505
+            )
506
+            ->andWhere($queryBuilder->expr()->isNotNull('configvalue')
507
+            );
508
+
509
+        if ($limit !== null) {
510
+            $queryBuilder->setMaxResults($limit);
511
+        }
512
+        if ($offset !== null) {
513
+            $queryBuilder->setFirstResult($offset);
514
+        }
515
+        $query = $queryBuilder->execute();
516
+        $result = [];
517
+
518
+        while ($row = $query->fetch()) {
519
+            $result[] = $row['userid'];
520
+        }
521
+
522
+        $query->closeCursor();
523
+
524
+        return $result;
525
+    }
526
+
527
+    /**
528
+     * @param string $email
529
+     * @return IUser[]
530
+     * @since 9.1.0
531
+     */
532
+    public function getByEmail($email) {
533
+        $userIds = $this->config->getUsersForUserValue('settings', 'email', $email);
534
+
535
+        return array_map(function($uid) {
536
+            return $this->get($uid);
537
+        }, $userIds);
538
+    }
539 539
 }
Please login to merge, or discard this patch.
core/Controller/LostController.php 1 patch
Indentation   +315 added lines, -315 removed lines patch added patch discarded remove patch
@@ -56,319 +56,319 @@
 block discarded – undo
56 56
  */
57 57
 class LostController extends Controller {
58 58
 
59
-	/** @var IURLGenerator */
60
-	protected $urlGenerator;
61
-	/** @var IUserManager */
62
-	protected $userManager;
63
-	/** @var Defaults */
64
-	protected $defaults;
65
-	/** @var IL10N */
66
-	protected $l10n;
67
-	/** @var string */
68
-	protected $from;
69
-	/** @var IManager */
70
-	protected $encryptionManager;
71
-	/** @var IConfig */
72
-	protected $config;
73
-	/** @var ISecureRandom */
74
-	protected $secureRandom;
75
-	/** @var IMailer */
76
-	protected $mailer;
77
-	/** @var ITimeFactory */
78
-	protected $timeFactory;
79
-	/** @var ICrypto */
80
-	protected $crypto;
81
-
82
-	/**
83
-	 * @param string $appName
84
-	 * @param IRequest $request
85
-	 * @param IURLGenerator $urlGenerator
86
-	 * @param IUserManager $userManager
87
-	 * @param Defaults $defaults
88
-	 * @param IL10N $l10n
89
-	 * @param IConfig $config
90
-	 * @param ISecureRandom $secureRandom
91
-	 * @param string $defaultMailAddress
92
-	 * @param IManager $encryptionManager
93
-	 * @param IMailer $mailer
94
-	 * @param ITimeFactory $timeFactory
95
-	 * @param ICrypto $crypto
96
-	 */
97
-	public function __construct($appName,
98
-								IRequest $request,
99
-								IURLGenerator $urlGenerator,
100
-								IUserManager $userManager,
101
-								Defaults $defaults,
102
-								IL10N $l10n,
103
-								IConfig $config,
104
-								ISecureRandom $secureRandom,
105
-								$defaultMailAddress,
106
-								IManager $encryptionManager,
107
-								IMailer $mailer,
108
-								ITimeFactory $timeFactory,
109
-								ICrypto $crypto) {
110
-		parent::__construct($appName, $request);
111
-		$this->urlGenerator = $urlGenerator;
112
-		$this->userManager = $userManager;
113
-		$this->defaults = $defaults;
114
-		$this->l10n = $l10n;
115
-		$this->secureRandom = $secureRandom;
116
-		$this->from = $defaultMailAddress;
117
-		$this->encryptionManager = $encryptionManager;
118
-		$this->config = $config;
119
-		$this->mailer = $mailer;
120
-		$this->timeFactory = $timeFactory;
121
-		$this->crypto = $crypto;
122
-	}
123
-
124
-	/**
125
-	 * Someone wants to reset their password:
126
-	 *
127
-	 * @PublicPage
128
-	 * @NoCSRFRequired
129
-	 *
130
-	 * @param string $token
131
-	 * @param string $userId
132
-	 * @return TemplateResponse
133
-	 */
134
-	public function resetform($token, $userId) {
135
-		if ($this->config->getSystemValue('lost_password_link', '') !== '') {
136
-			return new TemplateResponse('core', 'error', [
137
-					'errors' => [['error' => $this->l10n->t('Password reset is disabled')]]
138
-				],
139
-				'guest'
140
-			);
141
-		}
142
-
143
-		try {
144
-			$this->checkPasswordResetToken($token, $userId);
145
-		} catch (\Exception $e) {
146
-			return new TemplateResponse(
147
-				'core', 'error', [
148
-					"errors" => array(array("error" => $e->getMessage()))
149
-				],
150
-				'guest'
151
-			);
152
-		}
153
-
154
-		return new TemplateResponse(
155
-			'core',
156
-			'lostpassword/resetpassword',
157
-			array(
158
-				'link' => $this->urlGenerator->linkToRouteAbsolute('core.lost.setPassword', array('userId' => $userId, 'token' => $token)),
159
-			),
160
-			'guest'
161
-		);
162
-	}
163
-
164
-	/**
165
-	 * @param string $token
166
-	 * @param string $userId
167
-	 * @throws \Exception
168
-	 */
169
-	protected function checkPasswordResetToken($token, $userId) {
170
-		$user = $this->userManager->get($userId);
171
-		if($user === null || !$user->isEnabled()) {
172
-			throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
173
-		}
174
-
175
-		try {
176
-			$encryptedToken = $this->config->getUserValue($userId, 'core', 'lostpassword', null);
177
-			$mailAddress = !is_null($user->getEMailAddress()) ? $user->getEMailAddress() : '';
178
-			$decryptedToken = $this->crypto->decrypt($encryptedToken, $mailAddress.$this->config->getSystemValue('secret'));
179
-		} catch (\Exception $e) {
180
-			throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
181
-		}
182
-
183
-		$splittedToken = explode(':', $decryptedToken);
184
-		if(count($splittedToken) !== 2) {
185
-			throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
186
-		}
187
-
188
-		if ($splittedToken[0] < ($this->timeFactory->getTime() - 60*60*12) ||
189
-			$user->getLastLogin() > $splittedToken[0]) {
190
-			throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is expired'));
191
-		}
192
-
193
-		if (!hash_equals($splittedToken[1], $token)) {
194
-			throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
195
-		}
196
-	}
197
-
198
-	/**
199
-	 * @param $message
200
-	 * @param array $additional
201
-	 * @return array
202
-	 */
203
-	private function error($message, array $additional=array()) {
204
-		return array_merge(array('status' => 'error', 'msg' => $message), $additional);
205
-	}
206
-
207
-	/**
208
-	 * @return array
209
-	 */
210
-	private function success() {
211
-		return array('status'=>'success');
212
-	}
213
-
214
-	/**
215
-	 * @PublicPage
216
-	 * @BruteForceProtection(action=passwordResetEmail)
217
-	 * @AnonRateThrottle(limit=10, period=300)
218
-	 *
219
-	 * @param string $user
220
-	 * @return JSONResponse
221
-	 */
222
-	public function email($user){
223
-		if ($this->config->getSystemValue('lost_password_link', '') !== '') {
224
-			return new JSONResponse($this->error($this->l10n->t('Password reset is disabled')));
225
-		}
226
-
227
-		\OCP\Util::emitHook(
228
-			'\OCA\Files_Sharing\API\Server2Server',
229
-			'preLoginNameUsedAsUserName',
230
-			['uid' => &$user]
231
-		);
232
-
233
-		// FIXME: use HTTP error codes
234
-		try {
235
-			$this->sendEmail($user);
236
-		} catch (\Exception $e){
237
-			$response = new JSONResponse($this->error($e->getMessage()));
238
-			$response->throttle();
239
-			return $response;
240
-		}
241
-
242
-		$response = new JSONResponse($this->success());
243
-		$response->throttle();
244
-		return $response;
245
-	}
246
-
247
-	/**
248
-	 * @PublicPage
249
-	 * @param string $token
250
-	 * @param string $userId
251
-	 * @param string $password
252
-	 * @param boolean $proceed
253
-	 * @return array
254
-	 */
255
-	public function setPassword($token, $userId, $password, $proceed) {
256
-		if ($this->config->getSystemValue('lost_password_link', '') !== '') {
257
-			return $this->error($this->l10n->t('Password reset is disabled'));
258
-		}
259
-
260
-		if ($this->encryptionManager->isEnabled() && !$proceed) {
261
-			return $this->error('', array('encryption' => true));
262
-		}
263
-
264
-		try {
265
-			$this->checkPasswordResetToken($token, $userId);
266
-			$user = $this->userManager->get($userId);
267
-
268
-			\OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'pre_passwordReset', array('uid' => $userId, 'password' => $password));
269
-
270
-			if (!$user->setPassword($password)) {
271
-				throw new \Exception();
272
-			}
273
-
274
-			\OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'post_passwordReset', array('uid' => $userId, 'password' => $password));
275
-
276
-			$this->config->deleteUserValue($userId, 'core', 'lostpassword');
277
-			@\OC::$server->getUserSession()->unsetMagicInCookie();
278
-		} catch (\Exception $e){
279
-			return $this->error($e->getMessage());
280
-		}
281
-
282
-		return $this->success();
283
-	}
284
-
285
-	/**
286
-	 * @param string $input
287
-	 * @throws \Exception
288
-	 */
289
-	protected function sendEmail($input) {
290
-		$user = $this->findUserByIdOrMail($input);
291
-		$email = $user->getEMailAddress();
292
-
293
-		if (empty($email)) {
294
-			throw new \Exception(
295
-				$this->l10n->t('Could not send reset email because there is no email address for this username. Please contact your administrator.')
296
-			);
297
-		}
298
-
299
-		// Generate the token. It is stored encrypted in the database with the
300
-		// secret being the users' email address appended with the system secret.
301
-		// This makes the token automatically invalidate once the user changes
302
-		// their email address.
303
-		$token = $this->secureRandom->generate(
304
-			21,
305
-			ISecureRandom::CHAR_DIGITS.
306
-			ISecureRandom::CHAR_LOWER.
307
-			ISecureRandom::CHAR_UPPER
308
-		);
309
-		$tokenValue = $this->timeFactory->getTime() .':'. $token;
310
-		$encryptedValue = $this->crypto->encrypt($tokenValue, $email . $this->config->getSystemValue('secret'));
311
-		$this->config->setUserValue($user->getUID(), 'core', 'lostpassword', $encryptedValue);
312
-
313
-		$link = $this->urlGenerator->linkToRouteAbsolute('core.lost.resetform', array('userId' => $user->getUID(), 'token' => $token));
314
-
315
-		$emailTemplate = $this->mailer->createEMailTemplate('core.ResetPassword', [
316
-			'link' => $link,
317
-		]);
318
-
319
-		$emailTemplate->setSubject($this->l10n->t('%s password reset', [$this->defaults->getName()]));
320
-		$emailTemplate->addHeader();
321
-		$emailTemplate->addHeading($this->l10n->t('Password reset'));
322
-
323
-		$emailTemplate->addBodyText(
324
-			$this->l10n->t('Click the following button to reset your password. If you have not requested the password reset, then ignore this email.'),
325
-			$this->l10n->t('Click the following link to reset your password. If you have not requested the password reset, then ignore this email.')
326
-		);
327
-
328
-		$emailTemplate->addBodyButton(
329
-			$this->l10n->t('Reset your password'),
330
-			$link,
331
-			false
332
-		);
333
-		$emailTemplate->addFooter();
334
-
335
-		try {
336
-			$message = $this->mailer->createMessage();
337
-			$message->setTo([$email => $user->getUID()]);
338
-			$message->setFrom([$this->from => $this->defaults->getName()]);
339
-			$message->useTemplate($emailTemplate);
340
-			$this->mailer->send($message);
341
-		} catch (\Exception $e) {
342
-			throw new \Exception($this->l10n->t(
343
-				'Couldn\'t send reset email. Please contact your administrator.'
344
-			));
345
-		}
346
-	}
347
-
348
-	/**
349
-	 * @param string $input
350
-	 * @return IUser
351
-	 * @throws \InvalidArgumentException
352
-	 */
353
-	protected function findUserByIdOrMail($input) {
354
-		$user = $this->userManager->get($input);
355
-		if ($user instanceof IUser) {
356
-			if (!$user->isEnabled()) {
357
-				throw new \InvalidArgumentException($this->l10n->t('Couldn\'t send reset email. Please make sure your username is correct.'));
358
-			}
359
-
360
-			return $user;
361
-		}
362
-		$users = $this->userManager->getByEmail($input);
363
-		if (count($users) === 1) {
364
-			$user = $users[0];
365
-			if (!$user->isEnabled()) {
366
-				throw new \InvalidArgumentException($this->l10n->t('Couldn\'t send reset email. Please make sure your username is correct.'));
367
-			}
368
-
369
-			return $user;
370
-		}
371
-
372
-		throw new \InvalidArgumentException($this->l10n->t('Couldn\'t send reset email. Please make sure your username is correct.'));
373
-	}
59
+    /** @var IURLGenerator */
60
+    protected $urlGenerator;
61
+    /** @var IUserManager */
62
+    protected $userManager;
63
+    /** @var Defaults */
64
+    protected $defaults;
65
+    /** @var IL10N */
66
+    protected $l10n;
67
+    /** @var string */
68
+    protected $from;
69
+    /** @var IManager */
70
+    protected $encryptionManager;
71
+    /** @var IConfig */
72
+    protected $config;
73
+    /** @var ISecureRandom */
74
+    protected $secureRandom;
75
+    /** @var IMailer */
76
+    protected $mailer;
77
+    /** @var ITimeFactory */
78
+    protected $timeFactory;
79
+    /** @var ICrypto */
80
+    protected $crypto;
81
+
82
+    /**
83
+     * @param string $appName
84
+     * @param IRequest $request
85
+     * @param IURLGenerator $urlGenerator
86
+     * @param IUserManager $userManager
87
+     * @param Defaults $defaults
88
+     * @param IL10N $l10n
89
+     * @param IConfig $config
90
+     * @param ISecureRandom $secureRandom
91
+     * @param string $defaultMailAddress
92
+     * @param IManager $encryptionManager
93
+     * @param IMailer $mailer
94
+     * @param ITimeFactory $timeFactory
95
+     * @param ICrypto $crypto
96
+     */
97
+    public function __construct($appName,
98
+                                IRequest $request,
99
+                                IURLGenerator $urlGenerator,
100
+                                IUserManager $userManager,
101
+                                Defaults $defaults,
102
+                                IL10N $l10n,
103
+                                IConfig $config,
104
+                                ISecureRandom $secureRandom,
105
+                                $defaultMailAddress,
106
+                                IManager $encryptionManager,
107
+                                IMailer $mailer,
108
+                                ITimeFactory $timeFactory,
109
+                                ICrypto $crypto) {
110
+        parent::__construct($appName, $request);
111
+        $this->urlGenerator = $urlGenerator;
112
+        $this->userManager = $userManager;
113
+        $this->defaults = $defaults;
114
+        $this->l10n = $l10n;
115
+        $this->secureRandom = $secureRandom;
116
+        $this->from = $defaultMailAddress;
117
+        $this->encryptionManager = $encryptionManager;
118
+        $this->config = $config;
119
+        $this->mailer = $mailer;
120
+        $this->timeFactory = $timeFactory;
121
+        $this->crypto = $crypto;
122
+    }
123
+
124
+    /**
125
+     * Someone wants to reset their password:
126
+     *
127
+     * @PublicPage
128
+     * @NoCSRFRequired
129
+     *
130
+     * @param string $token
131
+     * @param string $userId
132
+     * @return TemplateResponse
133
+     */
134
+    public function resetform($token, $userId) {
135
+        if ($this->config->getSystemValue('lost_password_link', '') !== '') {
136
+            return new TemplateResponse('core', 'error', [
137
+                    'errors' => [['error' => $this->l10n->t('Password reset is disabled')]]
138
+                ],
139
+                'guest'
140
+            );
141
+        }
142
+
143
+        try {
144
+            $this->checkPasswordResetToken($token, $userId);
145
+        } catch (\Exception $e) {
146
+            return new TemplateResponse(
147
+                'core', 'error', [
148
+                    "errors" => array(array("error" => $e->getMessage()))
149
+                ],
150
+                'guest'
151
+            );
152
+        }
153
+
154
+        return new TemplateResponse(
155
+            'core',
156
+            'lostpassword/resetpassword',
157
+            array(
158
+                'link' => $this->urlGenerator->linkToRouteAbsolute('core.lost.setPassword', array('userId' => $userId, 'token' => $token)),
159
+            ),
160
+            'guest'
161
+        );
162
+    }
163
+
164
+    /**
165
+     * @param string $token
166
+     * @param string $userId
167
+     * @throws \Exception
168
+     */
169
+    protected function checkPasswordResetToken($token, $userId) {
170
+        $user = $this->userManager->get($userId);
171
+        if($user === null || !$user->isEnabled()) {
172
+            throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
173
+        }
174
+
175
+        try {
176
+            $encryptedToken = $this->config->getUserValue($userId, 'core', 'lostpassword', null);
177
+            $mailAddress = !is_null($user->getEMailAddress()) ? $user->getEMailAddress() : '';
178
+            $decryptedToken = $this->crypto->decrypt($encryptedToken, $mailAddress.$this->config->getSystemValue('secret'));
179
+        } catch (\Exception $e) {
180
+            throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
181
+        }
182
+
183
+        $splittedToken = explode(':', $decryptedToken);
184
+        if(count($splittedToken) !== 2) {
185
+            throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
186
+        }
187
+
188
+        if ($splittedToken[0] < ($this->timeFactory->getTime() - 60*60*12) ||
189
+            $user->getLastLogin() > $splittedToken[0]) {
190
+            throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is expired'));
191
+        }
192
+
193
+        if (!hash_equals($splittedToken[1], $token)) {
194
+            throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
195
+        }
196
+    }
197
+
198
+    /**
199
+     * @param $message
200
+     * @param array $additional
201
+     * @return array
202
+     */
203
+    private function error($message, array $additional=array()) {
204
+        return array_merge(array('status' => 'error', 'msg' => $message), $additional);
205
+    }
206
+
207
+    /**
208
+     * @return array
209
+     */
210
+    private function success() {
211
+        return array('status'=>'success');
212
+    }
213
+
214
+    /**
215
+     * @PublicPage
216
+     * @BruteForceProtection(action=passwordResetEmail)
217
+     * @AnonRateThrottle(limit=10, period=300)
218
+     *
219
+     * @param string $user
220
+     * @return JSONResponse
221
+     */
222
+    public function email($user){
223
+        if ($this->config->getSystemValue('lost_password_link', '') !== '') {
224
+            return new JSONResponse($this->error($this->l10n->t('Password reset is disabled')));
225
+        }
226
+
227
+        \OCP\Util::emitHook(
228
+            '\OCA\Files_Sharing\API\Server2Server',
229
+            'preLoginNameUsedAsUserName',
230
+            ['uid' => &$user]
231
+        );
232
+
233
+        // FIXME: use HTTP error codes
234
+        try {
235
+            $this->sendEmail($user);
236
+        } catch (\Exception $e){
237
+            $response = new JSONResponse($this->error($e->getMessage()));
238
+            $response->throttle();
239
+            return $response;
240
+        }
241
+
242
+        $response = new JSONResponse($this->success());
243
+        $response->throttle();
244
+        return $response;
245
+    }
246
+
247
+    /**
248
+     * @PublicPage
249
+     * @param string $token
250
+     * @param string $userId
251
+     * @param string $password
252
+     * @param boolean $proceed
253
+     * @return array
254
+     */
255
+    public function setPassword($token, $userId, $password, $proceed) {
256
+        if ($this->config->getSystemValue('lost_password_link', '') !== '') {
257
+            return $this->error($this->l10n->t('Password reset is disabled'));
258
+        }
259
+
260
+        if ($this->encryptionManager->isEnabled() && !$proceed) {
261
+            return $this->error('', array('encryption' => true));
262
+        }
263
+
264
+        try {
265
+            $this->checkPasswordResetToken($token, $userId);
266
+            $user = $this->userManager->get($userId);
267
+
268
+            \OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'pre_passwordReset', array('uid' => $userId, 'password' => $password));
269
+
270
+            if (!$user->setPassword($password)) {
271
+                throw new \Exception();
272
+            }
273
+
274
+            \OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'post_passwordReset', array('uid' => $userId, 'password' => $password));
275
+
276
+            $this->config->deleteUserValue($userId, 'core', 'lostpassword');
277
+            @\OC::$server->getUserSession()->unsetMagicInCookie();
278
+        } catch (\Exception $e){
279
+            return $this->error($e->getMessage());
280
+        }
281
+
282
+        return $this->success();
283
+    }
284
+
285
+    /**
286
+     * @param string $input
287
+     * @throws \Exception
288
+     */
289
+    protected function sendEmail($input) {
290
+        $user = $this->findUserByIdOrMail($input);
291
+        $email = $user->getEMailAddress();
292
+
293
+        if (empty($email)) {
294
+            throw new \Exception(
295
+                $this->l10n->t('Could not send reset email because there is no email address for this username. Please contact your administrator.')
296
+            );
297
+        }
298
+
299
+        // Generate the token. It is stored encrypted in the database with the
300
+        // secret being the users' email address appended with the system secret.
301
+        // This makes the token automatically invalidate once the user changes
302
+        // their email address.
303
+        $token = $this->secureRandom->generate(
304
+            21,
305
+            ISecureRandom::CHAR_DIGITS.
306
+            ISecureRandom::CHAR_LOWER.
307
+            ISecureRandom::CHAR_UPPER
308
+        );
309
+        $tokenValue = $this->timeFactory->getTime() .':'. $token;
310
+        $encryptedValue = $this->crypto->encrypt($tokenValue, $email . $this->config->getSystemValue('secret'));
311
+        $this->config->setUserValue($user->getUID(), 'core', 'lostpassword', $encryptedValue);
312
+
313
+        $link = $this->urlGenerator->linkToRouteAbsolute('core.lost.resetform', array('userId' => $user->getUID(), 'token' => $token));
314
+
315
+        $emailTemplate = $this->mailer->createEMailTemplate('core.ResetPassword', [
316
+            'link' => $link,
317
+        ]);
318
+
319
+        $emailTemplate->setSubject($this->l10n->t('%s password reset', [$this->defaults->getName()]));
320
+        $emailTemplate->addHeader();
321
+        $emailTemplate->addHeading($this->l10n->t('Password reset'));
322
+
323
+        $emailTemplate->addBodyText(
324
+            $this->l10n->t('Click the following button to reset your password. If you have not requested the password reset, then ignore this email.'),
325
+            $this->l10n->t('Click the following link to reset your password. If you have not requested the password reset, then ignore this email.')
326
+        );
327
+
328
+        $emailTemplate->addBodyButton(
329
+            $this->l10n->t('Reset your password'),
330
+            $link,
331
+            false
332
+        );
333
+        $emailTemplate->addFooter();
334
+
335
+        try {
336
+            $message = $this->mailer->createMessage();
337
+            $message->setTo([$email => $user->getUID()]);
338
+            $message->setFrom([$this->from => $this->defaults->getName()]);
339
+            $message->useTemplate($emailTemplate);
340
+            $this->mailer->send($message);
341
+        } catch (\Exception $e) {
342
+            throw new \Exception($this->l10n->t(
343
+                'Couldn\'t send reset email. Please contact your administrator.'
344
+            ));
345
+        }
346
+    }
347
+
348
+    /**
349
+     * @param string $input
350
+     * @return IUser
351
+     * @throws \InvalidArgumentException
352
+     */
353
+    protected function findUserByIdOrMail($input) {
354
+        $user = $this->userManager->get($input);
355
+        if ($user instanceof IUser) {
356
+            if (!$user->isEnabled()) {
357
+                throw new \InvalidArgumentException($this->l10n->t('Couldn\'t send reset email. Please make sure your username is correct.'));
358
+            }
359
+
360
+            return $user;
361
+        }
362
+        $users = $this->userManager->getByEmail($input);
363
+        if (count($users) === 1) {
364
+            $user = $users[0];
365
+            if (!$user->isEnabled()) {
366
+                throw new \InvalidArgumentException($this->l10n->t('Couldn\'t send reset email. Please make sure your username is correct.'));
367
+            }
368
+
369
+            return $user;
370
+        }
371
+
372
+        throw new \InvalidArgumentException($this->l10n->t('Couldn\'t send reset email. Please make sure your username is correct.'));
373
+    }
374 374
 }
Please login to merge, or discard this patch.