Passed
Push — master ( f66315...fa7eb5 )
by Roeland
10:18 queued 11s
created
lib/private/Authentication/Token/IProvider.php 1 patch
Indentation   +146 added lines, -146 removed lines patch added patch discarded remove patch
@@ -34,150 +34,150 @@
 block discarded – undo
34 34
 interface IProvider {
35 35
 
36 36
 
37
-	/**
38
-	 * Create and persist a new token
39
-	 *
40
-	 * @param string $token
41
-	 * @param string $uid
42
-	 * @param string $loginName
43
-	 * @param string|null $password
44
-	 * @param string $name
45
-	 * @param int $type token type
46
-	 * @param int $remember whether the session token should be used for remember-me
47
-	 * @return IToken
48
-	 * @throws \RuntimeException when OpenSSL reports a problem
49
-	 */
50
-	public function generateToken(string $token,
51
-								  string $uid,
52
-								  string $loginName,
53
-								  $password,
54
-								  string $name,
55
-								  int $type = IToken::TEMPORARY_TOKEN,
56
-								  int $remember = IToken::DO_NOT_REMEMBER): IToken;
57
-
58
-	/**
59
-	 * Get a token by token id
60
-	 *
61
-	 * @param string $tokenId
62
-	 * @throws InvalidTokenException
63
-	 * @throws ExpiredTokenException
64
-	 * @throws WipeTokenException
65
-	 * @return IToken
66
-	 */
67
-	public function getToken(string $tokenId): IToken;
68
-
69
-	/**
70
-	 * Get a token by token id
71
-	 *
72
-	 * @param int $tokenId
73
-	 * @throws InvalidTokenException
74
-	 * @throws ExpiredTokenException
75
-	 * @throws WipeTokenException
76
-	 * @return IToken
77
-	 */
78
-	public function getTokenById(int $tokenId): IToken;
79
-
80
-	/**
81
-	 * Duplicate an existing session token
82
-	 *
83
-	 * @param string $oldSessionId
84
-	 * @param string $sessionId
85
-	 * @throws InvalidTokenException
86
-	 * @throws \RuntimeException when OpenSSL reports a problem
87
-	 * @return IToken The new token
88
-	 */
89
-	public function renewSessionToken(string $oldSessionId, string $sessionId): IToken;
90
-
91
-	/**
92
-	 * Invalidate (delete) the given session token
93
-	 *
94
-	 * @param string $token
95
-	 */
96
-	public function invalidateToken(string $token);
97
-
98
-	/**
99
-	 * Invalidate (delete) the given token
100
-	 *
101
-	 * @param string $uid
102
-	 * @param int $id
103
-	 */
104
-	public function invalidateTokenById(string $uid, int $id);
105
-
106
-	/**
107
-	 * Invalidate (delete) old session tokens
108
-	 */
109
-	public function invalidateOldTokens();
110
-
111
-	/**
112
-	 * Save the updated token
113
-	 *
114
-	 * @param IToken $token
115
-	 */
116
-	public function updateToken(IToken $token);
117
-
118
-	/**
119
-	 * Update token activity timestamp
120
-	 *
121
-	 * @param IToken $token
122
-	 */
123
-	public function updateTokenActivity(IToken $token);
124
-
125
-	/**
126
-	 * Get all tokens of a user
127
-	 *
128
-	 * The provider may limit the number of result rows in case of an abuse
129
-	 * where a high number of (session) tokens is generated
130
-	 *
131
-	 * @param string $uid
132
-	 * @return IToken[]
133
-	 */
134
-	public function getTokenByUser(string $uid): array;
135
-
136
-	/**
137
-	 * Get the (unencrypted) password of the given token
138
-	 *
139
-	 * @param IToken $token
140
-	 * @param string $tokenId
141
-	 * @throws InvalidTokenException
142
-	 * @throws PasswordlessTokenException
143
-	 * @return string
144
-	 */
145
-	public function getPassword(IToken $token, string $tokenId): string;
146
-
147
-	/**
148
-	 * Encrypt and set the password of the given token
149
-	 *
150
-	 * @param IToken $token
151
-	 * @param string $tokenId
152
-	 * @param string $password
153
-	 * @throws InvalidTokenException
154
-	 */
155
-	public function setPassword(IToken $token, string $tokenId, string $password);
156
-
157
-	/**
158
-	 * Rotate the token. Usefull for for example oauth tokens
159
-	 *
160
-	 * @param IToken $token
161
-	 * @param string $oldTokenId
162
-	 * @param string $newTokenId
163
-	 * @return IToken
164
-	 * @throws \RuntimeException when OpenSSL reports a problem
165
-	 */
166
-	public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken;
167
-
168
-	/**
169
-	 * Marks a token as having an invalid password.
170
-	 *
171
-	 * @param IToken $token
172
-	 * @param string $tokenId
173
-	 */
174
-	public function markPasswordInvalid(IToken $token, string $tokenId);
175
-
176
-	/**
177
-	 * Update all the passwords of $uid if required
178
-	 *
179
-	 * @param string $uid
180
-	 * @param string $password
181
-	 */
182
-	public function updatePasswords(string $uid, string $password);
37
+    /**
38
+     * Create and persist a new token
39
+     *
40
+     * @param string $token
41
+     * @param string $uid
42
+     * @param string $loginName
43
+     * @param string|null $password
44
+     * @param string $name
45
+     * @param int $type token type
46
+     * @param int $remember whether the session token should be used for remember-me
47
+     * @return IToken
48
+     * @throws \RuntimeException when OpenSSL reports a problem
49
+     */
50
+    public function generateToken(string $token,
51
+                                    string $uid,
52
+                                    string $loginName,
53
+                                    $password,
54
+                                    string $name,
55
+                                    int $type = IToken::TEMPORARY_TOKEN,
56
+                                    int $remember = IToken::DO_NOT_REMEMBER): IToken;
57
+
58
+    /**
59
+     * Get a token by token id
60
+     *
61
+     * @param string $tokenId
62
+     * @throws InvalidTokenException
63
+     * @throws ExpiredTokenException
64
+     * @throws WipeTokenException
65
+     * @return IToken
66
+     */
67
+    public function getToken(string $tokenId): IToken;
68
+
69
+    /**
70
+     * Get a token by token id
71
+     *
72
+     * @param int $tokenId
73
+     * @throws InvalidTokenException
74
+     * @throws ExpiredTokenException
75
+     * @throws WipeTokenException
76
+     * @return IToken
77
+     */
78
+    public function getTokenById(int $tokenId): IToken;
79
+
80
+    /**
81
+     * Duplicate an existing session token
82
+     *
83
+     * @param string $oldSessionId
84
+     * @param string $sessionId
85
+     * @throws InvalidTokenException
86
+     * @throws \RuntimeException when OpenSSL reports a problem
87
+     * @return IToken The new token
88
+     */
89
+    public function renewSessionToken(string $oldSessionId, string $sessionId): IToken;
90
+
91
+    /**
92
+     * Invalidate (delete) the given session token
93
+     *
94
+     * @param string $token
95
+     */
96
+    public function invalidateToken(string $token);
97
+
98
+    /**
99
+     * Invalidate (delete) the given token
100
+     *
101
+     * @param string $uid
102
+     * @param int $id
103
+     */
104
+    public function invalidateTokenById(string $uid, int $id);
105
+
106
+    /**
107
+     * Invalidate (delete) old session tokens
108
+     */
109
+    public function invalidateOldTokens();
110
+
111
+    /**
112
+     * Save the updated token
113
+     *
114
+     * @param IToken $token
115
+     */
116
+    public function updateToken(IToken $token);
117
+
118
+    /**
119
+     * Update token activity timestamp
120
+     *
121
+     * @param IToken $token
122
+     */
123
+    public function updateTokenActivity(IToken $token);
124
+
125
+    /**
126
+     * Get all tokens of a user
127
+     *
128
+     * The provider may limit the number of result rows in case of an abuse
129
+     * where a high number of (session) tokens is generated
130
+     *
131
+     * @param string $uid
132
+     * @return IToken[]
133
+     */
134
+    public function getTokenByUser(string $uid): array;
135
+
136
+    /**
137
+     * Get the (unencrypted) password of the given token
138
+     *
139
+     * @param IToken $token
140
+     * @param string $tokenId
141
+     * @throws InvalidTokenException
142
+     * @throws PasswordlessTokenException
143
+     * @return string
144
+     */
145
+    public function getPassword(IToken $token, string $tokenId): string;
146
+
147
+    /**
148
+     * Encrypt and set the password of the given token
149
+     *
150
+     * @param IToken $token
151
+     * @param string $tokenId
152
+     * @param string $password
153
+     * @throws InvalidTokenException
154
+     */
155
+    public function setPassword(IToken $token, string $tokenId, string $password);
156
+
157
+    /**
158
+     * Rotate the token. Usefull for for example oauth tokens
159
+     *
160
+     * @param IToken $token
161
+     * @param string $oldTokenId
162
+     * @param string $newTokenId
163
+     * @return IToken
164
+     * @throws \RuntimeException when OpenSSL reports a problem
165
+     */
166
+    public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken;
167
+
168
+    /**
169
+     * Marks a token as having an invalid password.
170
+     *
171
+     * @param IToken $token
172
+     * @param string $tokenId
173
+     */
174
+    public function markPasswordInvalid(IToken $token, string $tokenId);
175
+
176
+    /**
177
+     * Update all the passwords of $uid if required
178
+     *
179
+     * @param string $uid
180
+     * @param string $password
181
+     */
182
+    public function updatePasswords(string $uid, string $password);
183 183
 }
Please login to merge, or discard this patch.
lib/private/Authentication/Token/PublicKeyTokenProvider.php 1 patch
Indentation   +378 added lines, -378 removed lines patch added patch discarded remove patch
@@ -35,382 +35,382 @@
 block discarded – undo
35 35
 use OCP\Security\ICrypto;
36 36
 
37 37
 class PublicKeyTokenProvider implements IProvider {
38
-	/** @var PublicKeyTokenMapper */
39
-	private $mapper;
40
-
41
-	/** @var ICrypto */
42
-	private $crypto;
43
-
44
-	/** @var IConfig */
45
-	private $config;
46
-
47
-	/** @var ILogger $logger */
48
-	private $logger;
49
-
50
-	/** @var ITimeFactory $time */
51
-	private $time;
52
-
53
-	/** @var CappedMemoryCache */
54
-	private $cache;
55
-
56
-	public function __construct(PublicKeyTokenMapper $mapper,
57
-								ICrypto $crypto,
58
-								IConfig $config,
59
-								ILogger $logger,
60
-								ITimeFactory $time) {
61
-		$this->mapper = $mapper;
62
-		$this->crypto = $crypto;
63
-		$this->config = $config;
64
-		$this->logger = $logger;
65
-		$this->time = $time;
66
-
67
-		$this->cache = new CappedMemoryCache();
68
-	}
69
-
70
-	/**
71
-	 * {@inheritDoc}
72
-	 */
73
-	public function generateToken(string $token,
74
-								  string $uid,
75
-								  string $loginName,
76
-								  $password,
77
-								  string $name,
78
-								  int $type = IToken::TEMPORARY_TOKEN,
79
-								  int $remember = IToken::DO_NOT_REMEMBER): IToken {
80
-		$dbToken = $this->newToken($token, $uid, $loginName, $password, $name, $type, $remember);
81
-		$this->mapper->insert($dbToken);
82
-
83
-		// Add the token to the cache
84
-		$this->cache[$dbToken->getToken()] = $dbToken;
85
-
86
-		return $dbToken;
87
-	}
88
-
89
-	public function getToken(string $tokenId): IToken {
90
-		$tokenHash = $this->hashToken($tokenId);
91
-
92
-		if (isset($this->cache[$tokenHash])) {
93
-			$token = $this->cache[$tokenHash];
94
-		} else {
95
-			try {
96
-				$token = $this->mapper->getToken($this->hashToken($tokenId));
97
-				$this->cache[$token->getToken()] = $token;
98
-			} catch (DoesNotExistException $ex) {
99
-				throw new InvalidTokenException();
100
-			}
101
-		}
102
-
103
-		if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
104
-			throw new ExpiredTokenException($token);
105
-		}
106
-
107
-		if ($token->getType() === IToken::WIPE_TOKEN) {
108
-			throw new WipeTokenException($token);
109
-		}
110
-
111
-		return $token;
112
-	}
113
-
114
-	public function getTokenById(int $tokenId): IToken {
115
-		try {
116
-			$token = $this->mapper->getTokenById($tokenId);
117
-		} catch (DoesNotExistException $ex) {
118
-			throw new InvalidTokenException();
119
-		}
120
-
121
-		if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
122
-			throw new ExpiredTokenException($token);
123
-		}
124
-
125
-		if ($token->getType() === IToken::WIPE_TOKEN) {
126
-			throw new WipeTokenException($token);
127
-		}
128
-
129
-		return $token;
130
-	}
131
-
132
-	public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
133
-		$this->cache->clear();
134
-
135
-		$token = $this->getToken($oldSessionId);
136
-
137
-		if (!($token instanceof PublicKeyToken)) {
138
-			throw new InvalidTokenException();
139
-		}
140
-
141
-		$password = null;
142
-		if (!is_null($token->getPassword())) {
143
-			$privateKey = $this->decrypt($token->getPrivateKey(), $oldSessionId);
144
-			$password = $this->decryptPassword($token->getPassword(), $privateKey);
145
-		}
146
-
147
-		$newToken = $this->generateToken(
148
-			$sessionId,
149
-			$token->getUID(),
150
-			$token->getLoginName(),
151
-			$password,
152
-			$token->getName(),
153
-			IToken::TEMPORARY_TOKEN,
154
-			$token->getRemember()
155
-		);
156
-
157
-		$this->mapper->delete($token);
158
-
159
-		return $newToken;
160
-	}
161
-
162
-	public function invalidateToken(string $token) {
163
-		$this->cache->clear();
164
-
165
-		$this->mapper->invalidate($this->hashToken($token));
166
-	}
167
-
168
-	public function invalidateTokenById(string $uid, int $id) {
169
-		$this->cache->clear();
170
-
171
-		$this->mapper->deleteById($uid, $id);
172
-	}
173
-
174
-	public function invalidateOldTokens() {
175
-		$this->cache->clear();
176
-
177
-		$olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24);
178
-		$this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
179
-		$this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER);
180
-		$rememberThreshold = $this->time->getTime() - (int) $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
181
-		$this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']);
182
-		$this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER);
183
-	}
184
-
185
-	public function updateToken(IToken $token) {
186
-		$this->cache->clear();
187
-
188
-		if (!($token instanceof PublicKeyToken)) {
189
-			throw new InvalidTokenException();
190
-		}
191
-		$this->mapper->update($token);
192
-	}
193
-
194
-	public function updateTokenActivity(IToken $token) {
195
-		$this->cache->clear();
196
-
197
-		if (!($token instanceof PublicKeyToken)) {
198
-			throw new InvalidTokenException();
199
-		}
200
-		/** @var DefaultToken $token */
201
-		$now = $this->time->getTime();
202
-		if ($token->getLastActivity() < ($now - 60)) {
203
-			// Update token only once per minute
204
-			$token->setLastActivity($now);
205
-			$this->mapper->update($token);
206
-		}
207
-	}
208
-
209
-	public function getTokenByUser(string $uid): array {
210
-		return $this->mapper->getTokenByUser($uid);
211
-	}
212
-
213
-	public function getPassword(IToken $token, string $tokenId): string {
214
-		if (!($token instanceof PublicKeyToken)) {
215
-			throw new InvalidTokenException();
216
-		}
217
-
218
-		if ($token->getPassword() === null) {
219
-			throw new PasswordlessTokenException();
220
-		}
221
-
222
-		// Decrypt private key with tokenId
223
-		$privateKey = $this->decrypt($token->getPrivateKey(), $tokenId);
224
-
225
-		// Decrypt password with private key
226
-		return $this->decryptPassword($token->getPassword(), $privateKey);
227
-	}
228
-
229
-	public function setPassword(IToken $token, string $tokenId, string $password) {
230
-		$this->cache->clear();
231
-
232
-		if (!($token instanceof PublicKeyToken)) {
233
-			throw new InvalidTokenException();
234
-		}
235
-
236
-		// When changing passwords all temp tokens are deleted
237
-		$this->mapper->deleteTempToken($token);
238
-
239
-		// Update the password for all tokens
240
-		$tokens = $this->mapper->getTokenByUser($token->getUID());
241
-		foreach ($tokens as $t) {
242
-			$publicKey = $t->getPublicKey();
243
-			$t->setPassword($this->encryptPassword($password, $publicKey));
244
-			$this->updateToken($t);
245
-		}
246
-	}
247
-
248
-	public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
249
-		$this->cache->clear();
250
-
251
-		if (!($token instanceof PublicKeyToken)) {
252
-			throw new InvalidTokenException();
253
-		}
254
-
255
-		// Decrypt private key with oldTokenId
256
-		$privateKey = $this->decrypt($token->getPrivateKey(), $oldTokenId);
257
-		// Encrypt with the new token
258
-		$token->setPrivateKey($this->encrypt($privateKey, $newTokenId));
259
-
260
-		$token->setToken($this->hashToken($newTokenId));
261
-		$this->updateToken($token);
262
-
263
-		return $token;
264
-	}
265
-
266
-	private function encrypt(string $plaintext, string $token): string {
267
-		$secret = $this->config->getSystemValue('secret');
268
-		return $this->crypto->encrypt($plaintext, $token . $secret);
269
-	}
270
-
271
-	/**
272
-	 * @throws InvalidTokenException
273
-	 */
274
-	private function decrypt(string $cipherText, string $token): string {
275
-		$secret = $this->config->getSystemValue('secret');
276
-		try {
277
-			return $this->crypto->decrypt($cipherText, $token . $secret);
278
-		} catch (\Exception $ex) {
279
-			// Delete the invalid token
280
-			$this->invalidateToken($token);
281
-			throw new InvalidTokenException();
282
-		}
283
-	}
284
-
285
-	private function encryptPassword(string $password, string $publicKey): string {
286
-		openssl_public_encrypt($password, $encryptedPassword, $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
287
-		$encryptedPassword = base64_encode($encryptedPassword);
288
-
289
-		return $encryptedPassword;
290
-	}
291
-
292
-	private function decryptPassword(string $encryptedPassword, string $privateKey): string {
293
-		$encryptedPassword = base64_decode($encryptedPassword);
294
-		openssl_private_decrypt($encryptedPassword, $password, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);
295
-
296
-		return $password;
297
-	}
298
-
299
-	private function hashToken(string $token): string {
300
-		$secret = $this->config->getSystemValue('secret');
301
-		return hash('sha512', $token . $secret);
302
-	}
303
-
304
-	/**
305
-	 * Convert a DefaultToken to a publicKeyToken
306
-	 * This will also be updated directly in the Database
307
-	 * @throws \RuntimeException when OpenSSL reports a problem
308
-	 */
309
-	public function convertToken(DefaultToken $defaultToken, string $token, $password): PublicKeyToken {
310
-		$this->cache->clear();
311
-
312
-		$pkToken = $this->newToken(
313
-			$token,
314
-			$defaultToken->getUID(),
315
-			$defaultToken->getLoginName(),
316
-			$password,
317
-			$defaultToken->getName(),
318
-			$defaultToken->getType(),
319
-			$defaultToken->getRemember()
320
-		);
321
-
322
-		$pkToken->setExpires($defaultToken->getExpires());
323
-		$pkToken->setId($defaultToken->getId());
324
-
325
-		return $this->mapper->update($pkToken);
326
-	}
327
-
328
-	/**
329
-	 * @throws \RuntimeException when OpenSSL reports a problem
330
-	 */
331
-	private function newToken(string $token,
332
-							  string $uid,
333
-							  string $loginName,
334
-							  $password,
335
-							  string $name,
336
-							  int $type,
337
-							  int $remember): PublicKeyToken {
338
-		$dbToken = new PublicKeyToken();
339
-		$dbToken->setUid($uid);
340
-		$dbToken->setLoginName($loginName);
341
-
342
-		$config = array_merge([
343
-			'digest_alg' => 'sha512',
344
-			'private_key_bits' => 2048,
345
-		], $this->config->getSystemValue('openssl', []));
346
-
347
-		// Generate new key
348
-		$res = openssl_pkey_new($config);
349
-		if ($res === false) {
350
-			$this->logOpensslError();
351
-			throw new \RuntimeException('OpenSSL reported a problem');
352
-		}
353
-
354
-		if (openssl_pkey_export($res, $privateKey, null, $config) === false) {
355
-			$this->logOpensslError();
356
-			throw new \RuntimeException('OpenSSL reported a problem');
357
-		}
358
-
359
-		// Extract the public key from $res to $pubKey
360
-		$publicKey = openssl_pkey_get_details($res);
361
-		$publicKey = $publicKey['key'];
362
-
363
-		$dbToken->setPublicKey($publicKey);
364
-		$dbToken->setPrivateKey($this->encrypt($privateKey, $token));
365
-
366
-		if (!is_null($password)) {
367
-			$dbToken->setPassword($this->encryptPassword($password, $publicKey));
368
-		}
369
-
370
-		$dbToken->setName($name);
371
-		$dbToken->setToken($this->hashToken($token));
372
-		$dbToken->setType($type);
373
-		$dbToken->setRemember($remember);
374
-		$dbToken->setLastActivity($this->time->getTime());
375
-		$dbToken->setLastCheck($this->time->getTime());
376
-		$dbToken->setVersion(PublicKeyToken::VERSION);
377
-
378
-		return $dbToken;
379
-	}
380
-
381
-	public function markPasswordInvalid(IToken $token, string $tokenId) {
382
-		$this->cache->clear();
383
-
384
-		if (!($token instanceof PublicKeyToken)) {
385
-			throw new InvalidTokenException();
386
-		}
387
-
388
-		$token->setPasswordInvalid(true);
389
-		$this->mapper->update($token);
390
-	}
391
-
392
-	public function updatePasswords(string $uid, string $password) {
393
-		$this->cache->clear();
394
-
395
-		if (!$this->mapper->hasExpiredTokens($uid)) {
396
-			// Nothing to do here
397
-			return;
398
-		}
399
-
400
-		// Update the password for all tokens
401
-		$tokens = $this->mapper->getTokenByUser($uid);
402
-		foreach ($tokens as $t) {
403
-			$publicKey = $t->getPublicKey();
404
-			$t->setPassword($this->encryptPassword($password, $publicKey));
405
-			$this->updateToken($t);
406
-		}
407
-	}
408
-
409
-	private function logOpensslError() {
410
-		$errors = [];
411
-		while ($error = openssl_error_string()) {
412
-			$errors[] = $error;
413
-		}
414
-		$this->logger->critical('Something is wrong with your openssl setup: ' . implode(', ', $errors));
415
-	}
38
+    /** @var PublicKeyTokenMapper */
39
+    private $mapper;
40
+
41
+    /** @var ICrypto */
42
+    private $crypto;
43
+
44
+    /** @var IConfig */
45
+    private $config;
46
+
47
+    /** @var ILogger $logger */
48
+    private $logger;
49
+
50
+    /** @var ITimeFactory $time */
51
+    private $time;
52
+
53
+    /** @var CappedMemoryCache */
54
+    private $cache;
55
+
56
+    public function __construct(PublicKeyTokenMapper $mapper,
57
+                                ICrypto $crypto,
58
+                                IConfig $config,
59
+                                ILogger $logger,
60
+                                ITimeFactory $time) {
61
+        $this->mapper = $mapper;
62
+        $this->crypto = $crypto;
63
+        $this->config = $config;
64
+        $this->logger = $logger;
65
+        $this->time = $time;
66
+
67
+        $this->cache = new CappedMemoryCache();
68
+    }
69
+
70
+    /**
71
+     * {@inheritDoc}
72
+     */
73
+    public function generateToken(string $token,
74
+                                    string $uid,
75
+                                    string $loginName,
76
+                                    $password,
77
+                                    string $name,
78
+                                    int $type = IToken::TEMPORARY_TOKEN,
79
+                                    int $remember = IToken::DO_NOT_REMEMBER): IToken {
80
+        $dbToken = $this->newToken($token, $uid, $loginName, $password, $name, $type, $remember);
81
+        $this->mapper->insert($dbToken);
82
+
83
+        // Add the token to the cache
84
+        $this->cache[$dbToken->getToken()] = $dbToken;
85
+
86
+        return $dbToken;
87
+    }
88
+
89
+    public function getToken(string $tokenId): IToken {
90
+        $tokenHash = $this->hashToken($tokenId);
91
+
92
+        if (isset($this->cache[$tokenHash])) {
93
+            $token = $this->cache[$tokenHash];
94
+        } else {
95
+            try {
96
+                $token = $this->mapper->getToken($this->hashToken($tokenId));
97
+                $this->cache[$token->getToken()] = $token;
98
+            } catch (DoesNotExistException $ex) {
99
+                throw new InvalidTokenException();
100
+            }
101
+        }
102
+
103
+        if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
104
+            throw new ExpiredTokenException($token);
105
+        }
106
+
107
+        if ($token->getType() === IToken::WIPE_TOKEN) {
108
+            throw new WipeTokenException($token);
109
+        }
110
+
111
+        return $token;
112
+    }
113
+
114
+    public function getTokenById(int $tokenId): IToken {
115
+        try {
116
+            $token = $this->mapper->getTokenById($tokenId);
117
+        } catch (DoesNotExistException $ex) {
118
+            throw new InvalidTokenException();
119
+        }
120
+
121
+        if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
122
+            throw new ExpiredTokenException($token);
123
+        }
124
+
125
+        if ($token->getType() === IToken::WIPE_TOKEN) {
126
+            throw new WipeTokenException($token);
127
+        }
128
+
129
+        return $token;
130
+    }
131
+
132
+    public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
133
+        $this->cache->clear();
134
+
135
+        $token = $this->getToken($oldSessionId);
136
+
137
+        if (!($token instanceof PublicKeyToken)) {
138
+            throw new InvalidTokenException();
139
+        }
140
+
141
+        $password = null;
142
+        if (!is_null($token->getPassword())) {
143
+            $privateKey = $this->decrypt($token->getPrivateKey(), $oldSessionId);
144
+            $password = $this->decryptPassword($token->getPassword(), $privateKey);
145
+        }
146
+
147
+        $newToken = $this->generateToken(
148
+            $sessionId,
149
+            $token->getUID(),
150
+            $token->getLoginName(),
151
+            $password,
152
+            $token->getName(),
153
+            IToken::TEMPORARY_TOKEN,
154
+            $token->getRemember()
155
+        );
156
+
157
+        $this->mapper->delete($token);
158
+
159
+        return $newToken;
160
+    }
161
+
162
+    public function invalidateToken(string $token) {
163
+        $this->cache->clear();
164
+
165
+        $this->mapper->invalidate($this->hashToken($token));
166
+    }
167
+
168
+    public function invalidateTokenById(string $uid, int $id) {
169
+        $this->cache->clear();
170
+
171
+        $this->mapper->deleteById($uid, $id);
172
+    }
173
+
174
+    public function invalidateOldTokens() {
175
+        $this->cache->clear();
176
+
177
+        $olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24);
178
+        $this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
179
+        $this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER);
180
+        $rememberThreshold = $this->time->getTime() - (int) $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
181
+        $this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']);
182
+        $this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER);
183
+    }
184
+
185
+    public function updateToken(IToken $token) {
186
+        $this->cache->clear();
187
+
188
+        if (!($token instanceof PublicKeyToken)) {
189
+            throw new InvalidTokenException();
190
+        }
191
+        $this->mapper->update($token);
192
+    }
193
+
194
+    public function updateTokenActivity(IToken $token) {
195
+        $this->cache->clear();
196
+
197
+        if (!($token instanceof PublicKeyToken)) {
198
+            throw new InvalidTokenException();
199
+        }
200
+        /** @var DefaultToken $token */
201
+        $now = $this->time->getTime();
202
+        if ($token->getLastActivity() < ($now - 60)) {
203
+            // Update token only once per minute
204
+            $token->setLastActivity($now);
205
+            $this->mapper->update($token);
206
+        }
207
+    }
208
+
209
+    public function getTokenByUser(string $uid): array {
210
+        return $this->mapper->getTokenByUser($uid);
211
+    }
212
+
213
+    public function getPassword(IToken $token, string $tokenId): string {
214
+        if (!($token instanceof PublicKeyToken)) {
215
+            throw new InvalidTokenException();
216
+        }
217
+
218
+        if ($token->getPassword() === null) {
219
+            throw new PasswordlessTokenException();
220
+        }
221
+
222
+        // Decrypt private key with tokenId
223
+        $privateKey = $this->decrypt($token->getPrivateKey(), $tokenId);
224
+
225
+        // Decrypt password with private key
226
+        return $this->decryptPassword($token->getPassword(), $privateKey);
227
+    }
228
+
229
+    public function setPassword(IToken $token, string $tokenId, string $password) {
230
+        $this->cache->clear();
231
+
232
+        if (!($token instanceof PublicKeyToken)) {
233
+            throw new InvalidTokenException();
234
+        }
235
+
236
+        // When changing passwords all temp tokens are deleted
237
+        $this->mapper->deleteTempToken($token);
238
+
239
+        // Update the password for all tokens
240
+        $tokens = $this->mapper->getTokenByUser($token->getUID());
241
+        foreach ($tokens as $t) {
242
+            $publicKey = $t->getPublicKey();
243
+            $t->setPassword($this->encryptPassword($password, $publicKey));
244
+            $this->updateToken($t);
245
+        }
246
+    }
247
+
248
+    public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
249
+        $this->cache->clear();
250
+
251
+        if (!($token instanceof PublicKeyToken)) {
252
+            throw new InvalidTokenException();
253
+        }
254
+
255
+        // Decrypt private key with oldTokenId
256
+        $privateKey = $this->decrypt($token->getPrivateKey(), $oldTokenId);
257
+        // Encrypt with the new token
258
+        $token->setPrivateKey($this->encrypt($privateKey, $newTokenId));
259
+
260
+        $token->setToken($this->hashToken($newTokenId));
261
+        $this->updateToken($token);
262
+
263
+        return $token;
264
+    }
265
+
266
+    private function encrypt(string $plaintext, string $token): string {
267
+        $secret = $this->config->getSystemValue('secret');
268
+        return $this->crypto->encrypt($plaintext, $token . $secret);
269
+    }
270
+
271
+    /**
272
+     * @throws InvalidTokenException
273
+     */
274
+    private function decrypt(string $cipherText, string $token): string {
275
+        $secret = $this->config->getSystemValue('secret');
276
+        try {
277
+            return $this->crypto->decrypt($cipherText, $token . $secret);
278
+        } catch (\Exception $ex) {
279
+            // Delete the invalid token
280
+            $this->invalidateToken($token);
281
+            throw new InvalidTokenException();
282
+        }
283
+    }
284
+
285
+    private function encryptPassword(string $password, string $publicKey): string {
286
+        openssl_public_encrypt($password, $encryptedPassword, $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
287
+        $encryptedPassword = base64_encode($encryptedPassword);
288
+
289
+        return $encryptedPassword;
290
+    }
291
+
292
+    private function decryptPassword(string $encryptedPassword, string $privateKey): string {
293
+        $encryptedPassword = base64_decode($encryptedPassword);
294
+        openssl_private_decrypt($encryptedPassword, $password, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);
295
+
296
+        return $password;
297
+    }
298
+
299
+    private function hashToken(string $token): string {
300
+        $secret = $this->config->getSystemValue('secret');
301
+        return hash('sha512', $token . $secret);
302
+    }
303
+
304
+    /**
305
+     * Convert a DefaultToken to a publicKeyToken
306
+     * This will also be updated directly in the Database
307
+     * @throws \RuntimeException when OpenSSL reports a problem
308
+     */
309
+    public function convertToken(DefaultToken $defaultToken, string $token, $password): PublicKeyToken {
310
+        $this->cache->clear();
311
+
312
+        $pkToken = $this->newToken(
313
+            $token,
314
+            $defaultToken->getUID(),
315
+            $defaultToken->getLoginName(),
316
+            $password,
317
+            $defaultToken->getName(),
318
+            $defaultToken->getType(),
319
+            $defaultToken->getRemember()
320
+        );
321
+
322
+        $pkToken->setExpires($defaultToken->getExpires());
323
+        $pkToken->setId($defaultToken->getId());
324
+
325
+        return $this->mapper->update($pkToken);
326
+    }
327
+
328
+    /**
329
+     * @throws \RuntimeException when OpenSSL reports a problem
330
+     */
331
+    private function newToken(string $token,
332
+                                string $uid,
333
+                                string $loginName,
334
+                                $password,
335
+                                string $name,
336
+                                int $type,
337
+                                int $remember): PublicKeyToken {
338
+        $dbToken = new PublicKeyToken();
339
+        $dbToken->setUid($uid);
340
+        $dbToken->setLoginName($loginName);
341
+
342
+        $config = array_merge([
343
+            'digest_alg' => 'sha512',
344
+            'private_key_bits' => 2048,
345
+        ], $this->config->getSystemValue('openssl', []));
346
+
347
+        // Generate new key
348
+        $res = openssl_pkey_new($config);
349
+        if ($res === false) {
350
+            $this->logOpensslError();
351
+            throw new \RuntimeException('OpenSSL reported a problem');
352
+        }
353
+
354
+        if (openssl_pkey_export($res, $privateKey, null, $config) === false) {
355
+            $this->logOpensslError();
356
+            throw new \RuntimeException('OpenSSL reported a problem');
357
+        }
358
+
359
+        // Extract the public key from $res to $pubKey
360
+        $publicKey = openssl_pkey_get_details($res);
361
+        $publicKey = $publicKey['key'];
362
+
363
+        $dbToken->setPublicKey($publicKey);
364
+        $dbToken->setPrivateKey($this->encrypt($privateKey, $token));
365
+
366
+        if (!is_null($password)) {
367
+            $dbToken->setPassword($this->encryptPassword($password, $publicKey));
368
+        }
369
+
370
+        $dbToken->setName($name);
371
+        $dbToken->setToken($this->hashToken($token));
372
+        $dbToken->setType($type);
373
+        $dbToken->setRemember($remember);
374
+        $dbToken->setLastActivity($this->time->getTime());
375
+        $dbToken->setLastCheck($this->time->getTime());
376
+        $dbToken->setVersion(PublicKeyToken::VERSION);
377
+
378
+        return $dbToken;
379
+    }
380
+
381
+    public function markPasswordInvalid(IToken $token, string $tokenId) {
382
+        $this->cache->clear();
383
+
384
+        if (!($token instanceof PublicKeyToken)) {
385
+            throw new InvalidTokenException();
386
+        }
387
+
388
+        $token->setPasswordInvalid(true);
389
+        $this->mapper->update($token);
390
+    }
391
+
392
+    public function updatePasswords(string $uid, string $password) {
393
+        $this->cache->clear();
394
+
395
+        if (!$this->mapper->hasExpiredTokens($uid)) {
396
+            // Nothing to do here
397
+            return;
398
+        }
399
+
400
+        // Update the password for all tokens
401
+        $tokens = $this->mapper->getTokenByUser($uid);
402
+        foreach ($tokens as $t) {
403
+            $publicKey = $t->getPublicKey();
404
+            $t->setPassword($this->encryptPassword($password, $publicKey));
405
+            $this->updateToken($t);
406
+        }
407
+    }
408
+
409
+    private function logOpensslError() {
410
+        $errors = [];
411
+        while ($error = openssl_error_string()) {
412
+            $errors[] = $error;
413
+        }
414
+        $this->logger->critical('Something is wrong with your openssl setup: ' . implode(', ', $errors));
415
+    }
416 416
 }
Please login to merge, or discard this patch.
lib/private/Authentication/Token/Manager.php 1 patch
Indentation   +222 added lines, -222 removed lines patch added patch discarded remove patch
@@ -30,228 +30,228 @@
 block discarded – undo
30 30
 
31 31
 class Manager implements IProvider {
32 32
 
33
-	/** @var DefaultTokenProvider */
34
-	private $defaultTokenProvider;
35
-
36
-	/** @var PublicKeyTokenProvider */
37
-	private $publicKeyTokenProvider;
38
-
39
-	public function __construct(DefaultTokenProvider $defaultTokenProvider, PublicKeyTokenProvider $publicKeyTokenProvider) {
40
-		$this->defaultTokenProvider = $defaultTokenProvider;
41
-		$this->publicKeyTokenProvider = $publicKeyTokenProvider;
42
-	}
43
-
44
-	/**
45
-	 * Create and persist a new token
46
-	 *
47
-	 * @param string $token
48
-	 * @param string $uid
49
-	 * @param string $loginName
50
-	 * @param string|null $password
51
-	 * @param string $name
52
-	 * @param int $type token type
53
-	 * @param int $remember whether the session token should be used for remember-me
54
-	 * @return IToken
55
-	 */
56
-	public function generateToken(string $token,
57
-								  string $uid,
58
-								  string $loginName,
59
-								  $password,
60
-								  string $name,
61
-								  int $type = IToken::TEMPORARY_TOKEN,
62
-								  int $remember = IToken::DO_NOT_REMEMBER): IToken {
63
-		return $this->publicKeyTokenProvider->generateToken(
64
-			$token,
65
-			$uid,
66
-			$loginName,
67
-			$password,
68
-			$name,
69
-			$type,
70
-			$remember
71
-		);
72
-	}
73
-
74
-	/**
75
-	 * Save the updated token
76
-	 *
77
-	 * @param IToken $token
78
-	 * @throws InvalidTokenException
79
-	 */
80
-	public function updateToken(IToken $token) {
81
-		$provider = $this->getProvider($token);
82
-		$provider->updateToken($token);
83
-	}
84
-
85
-	/**
86
-	 * Update token activity timestamp
87
-	 *
88
-	 * @throws InvalidTokenException
89
-	 * @param IToken $token
90
-	 */
91
-	public function updateTokenActivity(IToken $token) {
92
-		$provider = $this->getProvider($token);
93
-		$provider->updateTokenActivity($token);
94
-	}
95
-
96
-	/**
97
-	 * @param string $uid
98
-	 * @return IToken[]
99
-	 */
100
-	public function getTokenByUser(string $uid): array {
101
-		$old = $this->defaultTokenProvider->getTokenByUser($uid);
102
-		$new = $this->publicKeyTokenProvider->getTokenByUser($uid);
103
-
104
-		return array_merge($old, $new);
105
-	}
106
-
107
-	/**
108
-	 * Get a token by token
109
-	 *
110
-	 * @param string $tokenId
111
-	 * @throws InvalidTokenException
112
-	 * @throws \RuntimeException when OpenSSL reports a problem
113
-	 * @return IToken
114
-	 */
115
-	public function getToken(string $tokenId): IToken {
116
-		try {
117
-			return $this->publicKeyTokenProvider->getToken($tokenId);
118
-		} catch (WipeTokenException $e) {
119
-			throw $e;
120
-		} catch (ExpiredTokenException $e) {
121
-			throw $e;
122
-		} catch(InvalidTokenException $e) {
123
-			// No worries we try to convert it to a PublicKey Token
124
-		}
125
-
126
-		//Convert!
127
-		$token = $this->defaultTokenProvider->getToken($tokenId);
128
-
129
-		try {
130
-			$password = $this->defaultTokenProvider->getPassword($token, $tokenId);
131
-		} catch (PasswordlessTokenException $e) {
132
-			$password = null;
133
-		}
134
-
135
-		return $this->publicKeyTokenProvider->convertToken($token, $tokenId, $password);
136
-	}
137
-
138
-	/**
139
-	 * Get a token by token id
140
-	 *
141
-	 * @param int $tokenId
142
-	 * @throws InvalidTokenException
143
-	 * @return IToken
144
-	 */
145
-	public function getTokenById(int $tokenId): IToken {
146
-		try {
147
-			return $this->publicKeyTokenProvider->getTokenById($tokenId);
148
-		} catch (ExpiredTokenException $e) {
149
-			throw $e;
150
-		} catch (WipeTokenException $e) {
151
-			throw $e;
152
-		} catch (InvalidTokenException $e) {
153
-			return $this->defaultTokenProvider->getTokenById($tokenId);
154
-		}
155
-	}
156
-
157
-	/**
158
-	 * @param string $oldSessionId
159
-	 * @param string $sessionId
160
-	 * @throws InvalidTokenException
161
-	 * @return IToken
162
-	 */
163
-	public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
164
-		try {
165
-			return $this->publicKeyTokenProvider->renewSessionToken($oldSessionId, $sessionId);
166
-		} catch (ExpiredTokenException $e) {
167
-			throw $e;
168
-		} catch (InvalidTokenException $e) {
169
-			return $this->defaultTokenProvider->renewSessionToken($oldSessionId, $sessionId);
170
-		}
171
-	}
172
-
173
-	/**
174
-	 * @param IToken $savedToken
175
-	 * @param string $tokenId session token
176
-	 * @throws InvalidTokenException
177
-	 * @throws PasswordlessTokenException
178
-	 * @return string
179
-	 */
180
-	public function getPassword(IToken $savedToken, string $tokenId): string {
181
-		$provider = $this->getProvider($savedToken);
182
-		return $provider->getPassword($savedToken, $tokenId);
183
-	}
184
-
185
-	public function setPassword(IToken $token, string $tokenId, string $password) {
186
-		$provider = $this->getProvider($token);
187
-		$provider->setPassword($token, $tokenId, $password);
188
-	}
189
-
190
-	public function invalidateToken(string $token) {
191
-		$this->defaultTokenProvider->invalidateToken($token);
192
-		$this->publicKeyTokenProvider->invalidateToken($token);
193
-	}
194
-
195
-	public function invalidateTokenById(string $uid, int $id) {
196
-		$this->defaultTokenProvider->invalidateTokenById($uid, $id);
197
-		$this->publicKeyTokenProvider->invalidateTokenById($uid, $id);
198
-	}
199
-
200
-	public function invalidateOldTokens() {
201
-		$this->defaultTokenProvider->invalidateOldTokens();
202
-		$this->publicKeyTokenProvider->invalidateOldTokens();
203
-	}
204
-
205
-	/**
206
-	 * @param IToken $token
207
-	 * @param string $oldTokenId
208
-	 * @param string $newTokenId
209
-	 * @return IToken
210
-	 * @throws InvalidTokenException
211
-	 * @throws \RuntimeException when OpenSSL reports a problem
212
-	 */
213
-	public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
214
-		if ($token instanceof DefaultToken) {
215
-			try {
216
-				$password = $this->defaultTokenProvider->getPassword($token, $oldTokenId);
217
-			} catch (PasswordlessTokenException $e) {
218
-				$password = null;
219
-			}
220
-
221
-			return $this->publicKeyTokenProvider->convertToken($token, $newTokenId, $password);
222
-		}
223
-
224
-		if ($token instanceof PublicKeyToken) {
225
-			return $this->publicKeyTokenProvider->rotate($token, $oldTokenId, $newTokenId);
226
-		}
227
-
228
-		throw new InvalidTokenException();
229
-	}
230
-
231
-	/**
232
-	 * @param IToken $token
233
-	 * @return IProvider
234
-	 * @throws InvalidTokenException
235
-	 */
236
-	private function getProvider(IToken $token): IProvider {
237
-		if ($token instanceof DefaultToken) {
238
-			return $this->defaultTokenProvider;
239
-		}
240
-		if ($token instanceof PublicKeyToken) {
241
-			return $this->publicKeyTokenProvider;
242
-		}
243
-		throw new InvalidTokenException();
244
-	}
245
-
246
-
247
-	public function markPasswordInvalid(IToken $token, string $tokenId) {
248
-		$this->getProvider($token)->markPasswordInvalid($token, $tokenId);
249
-	}
250
-
251
-	public function updatePasswords(string $uid, string $password) {
252
-		$this->defaultTokenProvider->updatePasswords($uid, $password);
253
-		$this->publicKeyTokenProvider->updatePasswords($uid, $password);
254
-	}
33
+    /** @var DefaultTokenProvider */
34
+    private $defaultTokenProvider;
35
+
36
+    /** @var PublicKeyTokenProvider */
37
+    private $publicKeyTokenProvider;
38
+
39
+    public function __construct(DefaultTokenProvider $defaultTokenProvider, PublicKeyTokenProvider $publicKeyTokenProvider) {
40
+        $this->defaultTokenProvider = $defaultTokenProvider;
41
+        $this->publicKeyTokenProvider = $publicKeyTokenProvider;
42
+    }
43
+
44
+    /**
45
+     * Create and persist a new token
46
+     *
47
+     * @param string $token
48
+     * @param string $uid
49
+     * @param string $loginName
50
+     * @param string|null $password
51
+     * @param string $name
52
+     * @param int $type token type
53
+     * @param int $remember whether the session token should be used for remember-me
54
+     * @return IToken
55
+     */
56
+    public function generateToken(string $token,
57
+                                    string $uid,
58
+                                    string $loginName,
59
+                                    $password,
60
+                                    string $name,
61
+                                    int $type = IToken::TEMPORARY_TOKEN,
62
+                                    int $remember = IToken::DO_NOT_REMEMBER): IToken {
63
+        return $this->publicKeyTokenProvider->generateToken(
64
+            $token,
65
+            $uid,
66
+            $loginName,
67
+            $password,
68
+            $name,
69
+            $type,
70
+            $remember
71
+        );
72
+    }
73
+
74
+    /**
75
+     * Save the updated token
76
+     *
77
+     * @param IToken $token
78
+     * @throws InvalidTokenException
79
+     */
80
+    public function updateToken(IToken $token) {
81
+        $provider = $this->getProvider($token);
82
+        $provider->updateToken($token);
83
+    }
84
+
85
+    /**
86
+     * Update token activity timestamp
87
+     *
88
+     * @throws InvalidTokenException
89
+     * @param IToken $token
90
+     */
91
+    public function updateTokenActivity(IToken $token) {
92
+        $provider = $this->getProvider($token);
93
+        $provider->updateTokenActivity($token);
94
+    }
95
+
96
+    /**
97
+     * @param string $uid
98
+     * @return IToken[]
99
+     */
100
+    public function getTokenByUser(string $uid): array {
101
+        $old = $this->defaultTokenProvider->getTokenByUser($uid);
102
+        $new = $this->publicKeyTokenProvider->getTokenByUser($uid);
103
+
104
+        return array_merge($old, $new);
105
+    }
106
+
107
+    /**
108
+     * Get a token by token
109
+     *
110
+     * @param string $tokenId
111
+     * @throws InvalidTokenException
112
+     * @throws \RuntimeException when OpenSSL reports a problem
113
+     * @return IToken
114
+     */
115
+    public function getToken(string $tokenId): IToken {
116
+        try {
117
+            return $this->publicKeyTokenProvider->getToken($tokenId);
118
+        } catch (WipeTokenException $e) {
119
+            throw $e;
120
+        } catch (ExpiredTokenException $e) {
121
+            throw $e;
122
+        } catch(InvalidTokenException $e) {
123
+            // No worries we try to convert it to a PublicKey Token
124
+        }
125
+
126
+        //Convert!
127
+        $token = $this->defaultTokenProvider->getToken($tokenId);
128
+
129
+        try {
130
+            $password = $this->defaultTokenProvider->getPassword($token, $tokenId);
131
+        } catch (PasswordlessTokenException $e) {
132
+            $password = null;
133
+        }
134
+
135
+        return $this->publicKeyTokenProvider->convertToken($token, $tokenId, $password);
136
+    }
137
+
138
+    /**
139
+     * Get a token by token id
140
+     *
141
+     * @param int $tokenId
142
+     * @throws InvalidTokenException
143
+     * @return IToken
144
+     */
145
+    public function getTokenById(int $tokenId): IToken {
146
+        try {
147
+            return $this->publicKeyTokenProvider->getTokenById($tokenId);
148
+        } catch (ExpiredTokenException $e) {
149
+            throw $e;
150
+        } catch (WipeTokenException $e) {
151
+            throw $e;
152
+        } catch (InvalidTokenException $e) {
153
+            return $this->defaultTokenProvider->getTokenById($tokenId);
154
+        }
155
+    }
156
+
157
+    /**
158
+     * @param string $oldSessionId
159
+     * @param string $sessionId
160
+     * @throws InvalidTokenException
161
+     * @return IToken
162
+     */
163
+    public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
164
+        try {
165
+            return $this->publicKeyTokenProvider->renewSessionToken($oldSessionId, $sessionId);
166
+        } catch (ExpiredTokenException $e) {
167
+            throw $e;
168
+        } catch (InvalidTokenException $e) {
169
+            return $this->defaultTokenProvider->renewSessionToken($oldSessionId, $sessionId);
170
+        }
171
+    }
172
+
173
+    /**
174
+     * @param IToken $savedToken
175
+     * @param string $tokenId session token
176
+     * @throws InvalidTokenException
177
+     * @throws PasswordlessTokenException
178
+     * @return string
179
+     */
180
+    public function getPassword(IToken $savedToken, string $tokenId): string {
181
+        $provider = $this->getProvider($savedToken);
182
+        return $provider->getPassword($savedToken, $tokenId);
183
+    }
184
+
185
+    public function setPassword(IToken $token, string $tokenId, string $password) {
186
+        $provider = $this->getProvider($token);
187
+        $provider->setPassword($token, $tokenId, $password);
188
+    }
189
+
190
+    public function invalidateToken(string $token) {
191
+        $this->defaultTokenProvider->invalidateToken($token);
192
+        $this->publicKeyTokenProvider->invalidateToken($token);
193
+    }
194
+
195
+    public function invalidateTokenById(string $uid, int $id) {
196
+        $this->defaultTokenProvider->invalidateTokenById($uid, $id);
197
+        $this->publicKeyTokenProvider->invalidateTokenById($uid, $id);
198
+    }
199
+
200
+    public function invalidateOldTokens() {
201
+        $this->defaultTokenProvider->invalidateOldTokens();
202
+        $this->publicKeyTokenProvider->invalidateOldTokens();
203
+    }
204
+
205
+    /**
206
+     * @param IToken $token
207
+     * @param string $oldTokenId
208
+     * @param string $newTokenId
209
+     * @return IToken
210
+     * @throws InvalidTokenException
211
+     * @throws \RuntimeException when OpenSSL reports a problem
212
+     */
213
+    public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
214
+        if ($token instanceof DefaultToken) {
215
+            try {
216
+                $password = $this->defaultTokenProvider->getPassword($token, $oldTokenId);
217
+            } catch (PasswordlessTokenException $e) {
218
+                $password = null;
219
+            }
220
+
221
+            return $this->publicKeyTokenProvider->convertToken($token, $newTokenId, $password);
222
+        }
223
+
224
+        if ($token instanceof PublicKeyToken) {
225
+            return $this->publicKeyTokenProvider->rotate($token, $oldTokenId, $newTokenId);
226
+        }
227
+
228
+        throw new InvalidTokenException();
229
+    }
230
+
231
+    /**
232
+     * @param IToken $token
233
+     * @return IProvider
234
+     * @throws InvalidTokenException
235
+     */
236
+    private function getProvider(IToken $token): IProvider {
237
+        if ($token instanceof DefaultToken) {
238
+            return $this->defaultTokenProvider;
239
+        }
240
+        if ($token instanceof PublicKeyToken) {
241
+            return $this->publicKeyTokenProvider;
242
+        }
243
+        throw new InvalidTokenException();
244
+    }
245
+
246
+
247
+    public function markPasswordInvalid(IToken $token, string $tokenId) {
248
+        $this->getProvider($token)->markPasswordInvalid($token, $tokenId);
249
+    }
250
+
251
+    public function updatePasswords(string $uid, string $password) {
252
+        $this->defaultTokenProvider->updatePasswords($uid, $password);
253
+        $this->publicKeyTokenProvider->updatePasswords($uid, $password);
254
+    }
255 255
 
256 256
 
257 257
 }
Please login to merge, or discard this patch.
lib/private/Authentication/Token/DefaultTokenProvider.php 1 patch
Indentation   +314 added lines, -314 removed lines patch added patch discarded remove patch
@@ -40,318 +40,318 @@
 block discarded – undo
40 40
 
41 41
 class DefaultTokenProvider implements IProvider {
42 42
 
43
-	/** @var DefaultTokenMapper */
44
-	private $mapper;
45
-
46
-	/** @var ICrypto */
47
-	private $crypto;
48
-
49
-	/** @var IConfig */
50
-	private $config;
51
-
52
-	/** @var ILogger $logger */
53
-	private $logger;
54
-
55
-	/** @var ITimeFactory $time */
56
-	private $time;
57
-
58
-	/**
59
-	 * @param DefaultTokenMapper $mapper
60
-	 * @param ICrypto $crypto
61
-	 * @param IConfig $config
62
-	 * @param ILogger $logger
63
-	 * @param ITimeFactory $time
64
-	 */
65
-	public function __construct(DefaultTokenMapper $mapper,
66
-								ICrypto $crypto,
67
-								IConfig $config,
68
-								ILogger $logger,
69
-								ITimeFactory $time) {
70
-		$this->mapper = $mapper;
71
-		$this->crypto = $crypto;
72
-		$this->config = $config;
73
-		$this->logger = $logger;
74
-		$this->time = $time;
75
-	}
76
-
77
-	/**
78
-	 * Create and persist a new token
79
-	 *
80
-	 * @param string $token
81
-	 * @param string $uid
82
-	 * @param string $loginName
83
-	 * @param string|null $password
84
-	 * @param string $name
85
-	 * @param int $type token type
86
-	 * @param int $remember whether the session token should be used for remember-me
87
-	 * @return IToken
88
-	 */
89
-	public function generateToken(string $token,
90
-								  string $uid,
91
-								  string $loginName,
92
-								  $password,
93
-								  string $name,
94
-								  int $type = IToken::TEMPORARY_TOKEN,
95
-								  int $remember = IToken::DO_NOT_REMEMBER): IToken {
96
-		$dbToken = new DefaultToken();
97
-		$dbToken->setUid($uid);
98
-		$dbToken->setLoginName($loginName);
99
-		if (!is_null($password)) {
100
-			$dbToken->setPassword($this->encryptPassword($password, $token));
101
-		}
102
-		$dbToken->setName($name);
103
-		$dbToken->setToken($this->hashToken($token));
104
-		$dbToken->setType($type);
105
-		$dbToken->setRemember($remember);
106
-		$dbToken->setLastActivity($this->time->getTime());
107
-		$dbToken->setLastCheck($this->time->getTime());
108
-		$dbToken->setVersion(DefaultToken::VERSION);
109
-
110
-		$this->mapper->insert($dbToken);
111
-
112
-		return $dbToken;
113
-	}
114
-
115
-	/**
116
-	 * Save the updated token
117
-	 *
118
-	 * @param IToken $token
119
-	 * @throws InvalidTokenException
120
-	 */
121
-	public function updateToken(IToken $token) {
122
-		if (!($token instanceof DefaultToken)) {
123
-			throw new InvalidTokenException();
124
-		}
125
-		$this->mapper->update($token);
126
-	}
127
-
128
-	/**
129
-	 * Update token activity timestamp
130
-	 *
131
-	 * @throws InvalidTokenException
132
-	 * @param IToken $token
133
-	 */
134
-	public function updateTokenActivity(IToken $token) {
135
-		if (!($token instanceof DefaultToken)) {
136
-			throw new InvalidTokenException();
137
-		}
138
-		/** @var DefaultToken $token */
139
-		$now = $this->time->getTime();
140
-		if ($token->getLastActivity() < ($now - 60)) {
141
-			// Update token only once per minute
142
-			$token->setLastActivity($now);
143
-			$this->mapper->update($token);
144
-		}
145
-	}
146
-
147
-	public function getTokenByUser(string $uid): array {
148
-		return $this->mapper->getTokenByUser($uid);
149
-	}
150
-
151
-	/**
152
-	 * Get a token by token
153
-	 *
154
-	 * @param string $tokenId
155
-	 * @throws InvalidTokenException
156
-	 * @throws ExpiredTokenException
157
-	 * @return IToken
158
-	 */
159
-	public function getToken(string $tokenId): IToken {
160
-		try {
161
-			$token = $this->mapper->getToken($this->hashToken($tokenId));
162
-		} catch (DoesNotExistException $ex) {
163
-			throw new InvalidTokenException();
164
-		}
165
-
166
-		if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
167
-			throw new ExpiredTokenException($token);
168
-		}
169
-
170
-		return $token;
171
-	}
172
-
173
-	/**
174
-	 * Get a token by token id
175
-	 *
176
-	 * @param int $tokenId
177
-	 * @throws InvalidTokenException
178
-	 * @throws ExpiredTokenException
179
-	 * @return IToken
180
-	 */
181
-	public function getTokenById(int $tokenId): IToken {
182
-		try {
183
-			$token = $this->mapper->getTokenById($tokenId);
184
-		} catch (DoesNotExistException $ex) {
185
-			throw new InvalidTokenException();
186
-		}
187
-
188
-		if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
189
-			throw new ExpiredTokenException($token);
190
-		}
191
-
192
-		return $token;
193
-	}
194
-
195
-	/**
196
-	 * @param string $oldSessionId
197
-	 * @param string $sessionId
198
-	 * @throws InvalidTokenException
199
-	 * @return IToken
200
-	 */
201
-	public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
202
-		$token = $this->getToken($oldSessionId);
203
-
204
-		$newToken = new DefaultToken();
205
-		$newToken->setUid($token->getUID());
206
-		$newToken->setLoginName($token->getLoginName());
207
-		if (!is_null($token->getPassword())) {
208
-			$password = $this->decryptPassword($token->getPassword(), $oldSessionId);
209
-			$newToken->setPassword($this->encryptPassword($password, $sessionId));
210
-		}
211
-		$newToken->setName($token->getName());
212
-		$newToken->setToken($this->hashToken($sessionId));
213
-		$newToken->setType(IToken::TEMPORARY_TOKEN);
214
-		$newToken->setRemember($token->getRemember());
215
-		$newToken->setLastActivity($this->time->getTime());
216
-		$this->mapper->insert($newToken);
217
-		$this->mapper->delete($token);
218
-
219
-		return $newToken;
220
-	}
221
-
222
-	/**
223
-	 * @param IToken $savedToken
224
-	 * @param string $tokenId session token
225
-	 * @throws InvalidTokenException
226
-	 * @throws PasswordlessTokenException
227
-	 * @return string
228
-	 */
229
-	public function getPassword(IToken $savedToken, string $tokenId): string {
230
-		$password = $savedToken->getPassword();
231
-		if (is_null($password)) {
232
-			throw new PasswordlessTokenException();
233
-		}
234
-		return $this->decryptPassword($password, $tokenId);
235
-	}
236
-
237
-	/**
238
-	 * Encrypt and set the password of the given token
239
-	 *
240
-	 * @param IToken $token
241
-	 * @param string $tokenId
242
-	 * @param string $password
243
-	 * @throws InvalidTokenException
244
-	 */
245
-	public function setPassword(IToken $token, string $tokenId, string $password) {
246
-		if (!($token instanceof DefaultToken)) {
247
-			throw new InvalidTokenException();
248
-		}
249
-		/** @var DefaultToken $token */
250
-		$token->setPassword($this->encryptPassword($password, $tokenId));
251
-		$this->mapper->update($token);
252
-	}
253
-
254
-	/**
255
-	 * Invalidate (delete) the given session token
256
-	 *
257
-	 * @param string $token
258
-	 */
259
-	public function invalidateToken(string $token) {
260
-		$this->mapper->invalidate($this->hashToken($token));
261
-	}
262
-
263
-	public function invalidateTokenById(string $uid, int $id) {
264
-		$this->mapper->deleteById($uid, $id);
265
-	}
266
-
267
-	/**
268
-	 * Invalidate (delete) old session tokens
269
-	 */
270
-	public function invalidateOldTokens() {
271
-		$olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24);
272
-		$this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
273
-		$this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER);
274
-		$rememberThreshold = $this->time->getTime() - (int) $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
275
-		$this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']);
276
-		$this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER);
277
-	}
278
-
279
-	/**
280
-	 * Rotate the token. Usefull for for example oauth tokens
281
-	 *
282
-	 * @param IToken $token
283
-	 * @param string $oldTokenId
284
-	 * @param string $newTokenId
285
-	 * @return IToken
286
-	 */
287
-	public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
288
-		try {
289
-			$password = $this->getPassword($token, $oldTokenId);
290
-			$token->setPassword($this->encryptPassword($password, $newTokenId));
291
-		} catch (PasswordlessTokenException $e) {
292
-
293
-		}
294
-
295
-		$token->setToken($this->hashToken($newTokenId));
296
-		$this->updateToken($token);
297
-
298
-		return $token;
299
-	}
300
-
301
-	/**
302
-	 * @param string $token
303
-	 * @return string
304
-	 */
305
-	private function hashToken(string $token): string {
306
-		$secret = $this->config->getSystemValue('secret');
307
-		return hash('sha512', $token . $secret);
308
-	}
309
-
310
-	/**
311
-	 * Encrypt the given password
312
-	 *
313
-	 * The token is used as key
314
-	 *
315
-	 * @param string $password
316
-	 * @param string $token
317
-	 * @return string encrypted password
318
-	 */
319
-	private function encryptPassword(string $password, string $token): string {
320
-		$secret = $this->config->getSystemValue('secret');
321
-		return $this->crypto->encrypt($password, $token . $secret);
322
-	}
323
-
324
-	/**
325
-	 * Decrypt the given password
326
-	 *
327
-	 * The token is used as key
328
-	 *
329
-	 * @param string $password
330
-	 * @param string $token
331
-	 * @throws InvalidTokenException
332
-	 * @return string the decrypted key
333
-	 */
334
-	private function decryptPassword(string $password, string $token): string {
335
-		$secret = $this->config->getSystemValue('secret');
336
-		try {
337
-			return $this->crypto->decrypt($password, $token . $secret);
338
-		} catch (Exception $ex) {
339
-			// Delete the invalid token
340
-			$this->invalidateToken($token);
341
-			throw new InvalidTokenException();
342
-		}
343
-	}
344
-
345
-	public function markPasswordInvalid(IToken $token, string $tokenId) {
346
-		if (!($token instanceof DefaultToken)) {
347
-			throw new InvalidTokenException();
348
-		}
349
-
350
-		//No need to mark as invalid. We just invalide default tokens
351
-		$this->invalidateToken($tokenId);
352
-	}
353
-
354
-	public function updatePasswords(string $uid, string $password) {
355
-		// Nothing to do here
356
-	}
43
+    /** @var DefaultTokenMapper */
44
+    private $mapper;
45
+
46
+    /** @var ICrypto */
47
+    private $crypto;
48
+
49
+    /** @var IConfig */
50
+    private $config;
51
+
52
+    /** @var ILogger $logger */
53
+    private $logger;
54
+
55
+    /** @var ITimeFactory $time */
56
+    private $time;
57
+
58
+    /**
59
+     * @param DefaultTokenMapper $mapper
60
+     * @param ICrypto $crypto
61
+     * @param IConfig $config
62
+     * @param ILogger $logger
63
+     * @param ITimeFactory $time
64
+     */
65
+    public function __construct(DefaultTokenMapper $mapper,
66
+                                ICrypto $crypto,
67
+                                IConfig $config,
68
+                                ILogger $logger,
69
+                                ITimeFactory $time) {
70
+        $this->mapper = $mapper;
71
+        $this->crypto = $crypto;
72
+        $this->config = $config;
73
+        $this->logger = $logger;
74
+        $this->time = $time;
75
+    }
76
+
77
+    /**
78
+     * Create and persist a new token
79
+     *
80
+     * @param string $token
81
+     * @param string $uid
82
+     * @param string $loginName
83
+     * @param string|null $password
84
+     * @param string $name
85
+     * @param int $type token type
86
+     * @param int $remember whether the session token should be used for remember-me
87
+     * @return IToken
88
+     */
89
+    public function generateToken(string $token,
90
+                                    string $uid,
91
+                                    string $loginName,
92
+                                    $password,
93
+                                    string $name,
94
+                                    int $type = IToken::TEMPORARY_TOKEN,
95
+                                    int $remember = IToken::DO_NOT_REMEMBER): IToken {
96
+        $dbToken = new DefaultToken();
97
+        $dbToken->setUid($uid);
98
+        $dbToken->setLoginName($loginName);
99
+        if (!is_null($password)) {
100
+            $dbToken->setPassword($this->encryptPassword($password, $token));
101
+        }
102
+        $dbToken->setName($name);
103
+        $dbToken->setToken($this->hashToken($token));
104
+        $dbToken->setType($type);
105
+        $dbToken->setRemember($remember);
106
+        $dbToken->setLastActivity($this->time->getTime());
107
+        $dbToken->setLastCheck($this->time->getTime());
108
+        $dbToken->setVersion(DefaultToken::VERSION);
109
+
110
+        $this->mapper->insert($dbToken);
111
+
112
+        return $dbToken;
113
+    }
114
+
115
+    /**
116
+     * Save the updated token
117
+     *
118
+     * @param IToken $token
119
+     * @throws InvalidTokenException
120
+     */
121
+    public function updateToken(IToken $token) {
122
+        if (!($token instanceof DefaultToken)) {
123
+            throw new InvalidTokenException();
124
+        }
125
+        $this->mapper->update($token);
126
+    }
127
+
128
+    /**
129
+     * Update token activity timestamp
130
+     *
131
+     * @throws InvalidTokenException
132
+     * @param IToken $token
133
+     */
134
+    public function updateTokenActivity(IToken $token) {
135
+        if (!($token instanceof DefaultToken)) {
136
+            throw new InvalidTokenException();
137
+        }
138
+        /** @var DefaultToken $token */
139
+        $now = $this->time->getTime();
140
+        if ($token->getLastActivity() < ($now - 60)) {
141
+            // Update token only once per minute
142
+            $token->setLastActivity($now);
143
+            $this->mapper->update($token);
144
+        }
145
+    }
146
+
147
+    public function getTokenByUser(string $uid): array {
148
+        return $this->mapper->getTokenByUser($uid);
149
+    }
150
+
151
+    /**
152
+     * Get a token by token
153
+     *
154
+     * @param string $tokenId
155
+     * @throws InvalidTokenException
156
+     * @throws ExpiredTokenException
157
+     * @return IToken
158
+     */
159
+    public function getToken(string $tokenId): IToken {
160
+        try {
161
+            $token = $this->mapper->getToken($this->hashToken($tokenId));
162
+        } catch (DoesNotExistException $ex) {
163
+            throw new InvalidTokenException();
164
+        }
165
+
166
+        if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
167
+            throw new ExpiredTokenException($token);
168
+        }
169
+
170
+        return $token;
171
+    }
172
+
173
+    /**
174
+     * Get a token by token id
175
+     *
176
+     * @param int $tokenId
177
+     * @throws InvalidTokenException
178
+     * @throws ExpiredTokenException
179
+     * @return IToken
180
+     */
181
+    public function getTokenById(int $tokenId): IToken {
182
+        try {
183
+            $token = $this->mapper->getTokenById($tokenId);
184
+        } catch (DoesNotExistException $ex) {
185
+            throw new InvalidTokenException();
186
+        }
187
+
188
+        if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
189
+            throw new ExpiredTokenException($token);
190
+        }
191
+
192
+        return $token;
193
+    }
194
+
195
+    /**
196
+     * @param string $oldSessionId
197
+     * @param string $sessionId
198
+     * @throws InvalidTokenException
199
+     * @return IToken
200
+     */
201
+    public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
202
+        $token = $this->getToken($oldSessionId);
203
+
204
+        $newToken = new DefaultToken();
205
+        $newToken->setUid($token->getUID());
206
+        $newToken->setLoginName($token->getLoginName());
207
+        if (!is_null($token->getPassword())) {
208
+            $password = $this->decryptPassword($token->getPassword(), $oldSessionId);
209
+            $newToken->setPassword($this->encryptPassword($password, $sessionId));
210
+        }
211
+        $newToken->setName($token->getName());
212
+        $newToken->setToken($this->hashToken($sessionId));
213
+        $newToken->setType(IToken::TEMPORARY_TOKEN);
214
+        $newToken->setRemember($token->getRemember());
215
+        $newToken->setLastActivity($this->time->getTime());
216
+        $this->mapper->insert($newToken);
217
+        $this->mapper->delete($token);
218
+
219
+        return $newToken;
220
+    }
221
+
222
+    /**
223
+     * @param IToken $savedToken
224
+     * @param string $tokenId session token
225
+     * @throws InvalidTokenException
226
+     * @throws PasswordlessTokenException
227
+     * @return string
228
+     */
229
+    public function getPassword(IToken $savedToken, string $tokenId): string {
230
+        $password = $savedToken->getPassword();
231
+        if (is_null($password)) {
232
+            throw new PasswordlessTokenException();
233
+        }
234
+        return $this->decryptPassword($password, $tokenId);
235
+    }
236
+
237
+    /**
238
+     * Encrypt and set the password of the given token
239
+     *
240
+     * @param IToken $token
241
+     * @param string $tokenId
242
+     * @param string $password
243
+     * @throws InvalidTokenException
244
+     */
245
+    public function setPassword(IToken $token, string $tokenId, string $password) {
246
+        if (!($token instanceof DefaultToken)) {
247
+            throw new InvalidTokenException();
248
+        }
249
+        /** @var DefaultToken $token */
250
+        $token->setPassword($this->encryptPassword($password, $tokenId));
251
+        $this->mapper->update($token);
252
+    }
253
+
254
+    /**
255
+     * Invalidate (delete) the given session token
256
+     *
257
+     * @param string $token
258
+     */
259
+    public function invalidateToken(string $token) {
260
+        $this->mapper->invalidate($this->hashToken($token));
261
+    }
262
+
263
+    public function invalidateTokenById(string $uid, int $id) {
264
+        $this->mapper->deleteById($uid, $id);
265
+    }
266
+
267
+    /**
268
+     * Invalidate (delete) old session tokens
269
+     */
270
+    public function invalidateOldTokens() {
271
+        $olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24);
272
+        $this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
273
+        $this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER);
274
+        $rememberThreshold = $this->time->getTime() - (int) $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
275
+        $this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']);
276
+        $this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER);
277
+    }
278
+
279
+    /**
280
+     * Rotate the token. Usefull for for example oauth tokens
281
+     *
282
+     * @param IToken $token
283
+     * @param string $oldTokenId
284
+     * @param string $newTokenId
285
+     * @return IToken
286
+     */
287
+    public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
288
+        try {
289
+            $password = $this->getPassword($token, $oldTokenId);
290
+            $token->setPassword($this->encryptPassword($password, $newTokenId));
291
+        } catch (PasswordlessTokenException $e) {
292
+
293
+        }
294
+
295
+        $token->setToken($this->hashToken($newTokenId));
296
+        $this->updateToken($token);
297
+
298
+        return $token;
299
+    }
300
+
301
+    /**
302
+     * @param string $token
303
+     * @return string
304
+     */
305
+    private function hashToken(string $token): string {
306
+        $secret = $this->config->getSystemValue('secret');
307
+        return hash('sha512', $token . $secret);
308
+    }
309
+
310
+    /**
311
+     * Encrypt the given password
312
+     *
313
+     * The token is used as key
314
+     *
315
+     * @param string $password
316
+     * @param string $token
317
+     * @return string encrypted password
318
+     */
319
+    private function encryptPassword(string $password, string $token): string {
320
+        $secret = $this->config->getSystemValue('secret');
321
+        return $this->crypto->encrypt($password, $token . $secret);
322
+    }
323
+
324
+    /**
325
+     * Decrypt the given password
326
+     *
327
+     * The token is used as key
328
+     *
329
+     * @param string $password
330
+     * @param string $token
331
+     * @throws InvalidTokenException
332
+     * @return string the decrypted key
333
+     */
334
+    private function decryptPassword(string $password, string $token): string {
335
+        $secret = $this->config->getSystemValue('secret');
336
+        try {
337
+            return $this->crypto->decrypt($password, $token . $secret);
338
+        } catch (Exception $ex) {
339
+            // Delete the invalid token
340
+            $this->invalidateToken($token);
341
+            throw new InvalidTokenException();
342
+        }
343
+    }
344
+
345
+    public function markPasswordInvalid(IToken $token, string $tokenId) {
346
+        if (!($token instanceof DefaultToken)) {
347
+            throw new InvalidTokenException();
348
+        }
349
+
350
+        //No need to mark as invalid. We just invalide default tokens
351
+        $this->invalidateToken($tokenId);
352
+    }
353
+
354
+    public function updatePasswords(string $uid, string $password) {
355
+        // Nothing to do here
356
+    }
357 357
 }
Please login to merge, or discard this patch.
lib/private/User/Session.php 1 patch
Indentation   +919 added lines, -919 removed lines patch added patch discarded remove patch
@@ -88,925 +88,925 @@
 block discarded – undo
88 88
  */
89 89
 class Session implements IUserSession, Emitter {
90 90
 
91
-	/** @var Manager|PublicEmitter $manager */
92
-	private $manager;
93
-
94
-	/** @var ISession $session */
95
-	private $session;
96
-
97
-	/** @var ITimeFactory */
98
-	private $timeFactory;
99
-
100
-	/** @var IProvider */
101
-	private $tokenProvider;
102
-
103
-	/** @var IConfig */
104
-	private $config;
105
-
106
-	/** @var User $activeUser */
107
-	protected $activeUser;
108
-
109
-	/** @var ISecureRandom */
110
-	private $random;
111
-
112
-	/** @var ILockdownManager  */
113
-	private $lockdownManager;
114
-
115
-	/** @var ILogger */
116
-	private $logger;
117
-	/** @var IEventDispatcher */
118
-	private $dispatcher;
119
-
120
-	/**
121
-	 * @param Manager $manager
122
-	 * @param ISession $session
123
-	 * @param ITimeFactory $timeFactory
124
-	 * @param IProvider $tokenProvider
125
-	 * @param IConfig $config
126
-	 * @param ISecureRandom $random
127
-	 * @param ILockdownManager $lockdownManager
128
-	 * @param ILogger $logger
129
-	 */
130
-	public function __construct(Manager $manager,
131
-								ISession $session,
132
-								ITimeFactory $timeFactory,
133
-								$tokenProvider,
134
-								IConfig $config,
135
-								ISecureRandom $random,
136
-								ILockdownManager $lockdownManager,
137
-								ILogger $logger,
138
-								IEventDispatcher $dispatcher) {
139
-		$this->manager = $manager;
140
-		$this->session = $session;
141
-		$this->timeFactory = $timeFactory;
142
-		$this->tokenProvider = $tokenProvider;
143
-		$this->config = $config;
144
-		$this->random = $random;
145
-		$this->lockdownManager = $lockdownManager;
146
-		$this->logger = $logger;
147
-		$this->dispatcher = $dispatcher;
148
-	}
149
-
150
-	/**
151
-	 * @param IProvider $provider
152
-	 */
153
-	public function setTokenProvider(IProvider $provider) {
154
-		$this->tokenProvider = $provider;
155
-	}
156
-
157
-	/**
158
-	 * @param string $scope
159
-	 * @param string $method
160
-	 * @param callable $callback
161
-	 */
162
-	public function listen($scope, $method, callable $callback) {
163
-		$this->manager->listen($scope, $method, $callback);
164
-	}
165
-
166
-	/**
167
-	 * @param string $scope optional
168
-	 * @param string $method optional
169
-	 * @param callable $callback optional
170
-	 */
171
-	public function removeListener($scope = null, $method = null, callable $callback = null) {
172
-		$this->manager->removeListener($scope, $method, $callback);
173
-	}
174
-
175
-	/**
176
-	 * get the manager object
177
-	 *
178
-	 * @return Manager|PublicEmitter
179
-	 */
180
-	public function getManager() {
181
-		return $this->manager;
182
-	}
183
-
184
-	/**
185
-	 * get the session object
186
-	 *
187
-	 * @return ISession
188
-	 */
189
-	public function getSession() {
190
-		return $this->session;
191
-	}
192
-
193
-	/**
194
-	 * set the session object
195
-	 *
196
-	 * @param ISession $session
197
-	 */
198
-	public function setSession(ISession $session) {
199
-		if ($this->session instanceof ISession) {
200
-			$this->session->close();
201
-		}
202
-		$this->session = $session;
203
-		$this->activeUser = null;
204
-	}
205
-
206
-	/**
207
-	 * set the currently active user
208
-	 *
209
-	 * @param IUser|null $user
210
-	 */
211
-	public function setUser($user) {
212
-		if (is_null($user)) {
213
-			$this->session->remove('user_id');
214
-		} else {
215
-			$this->session->set('user_id', $user->getUID());
216
-		}
217
-		$this->activeUser = $user;
218
-	}
219
-
220
-	/**
221
-	 * get the current active user
222
-	 *
223
-	 * @return IUser|null Current user, otherwise null
224
-	 */
225
-	public function getUser() {
226
-		// FIXME: This is a quick'n dirty work-around for the incognito mode as
227
-		// described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155
228
-		if (OC_User::isIncognitoMode()) {
229
-			return null;
230
-		}
231
-		if (is_null($this->activeUser)) {
232
-			$uid = $this->session->get('user_id');
233
-			if (is_null($uid)) {
234
-				return null;
235
-			}
236
-			$this->activeUser = $this->manager->get($uid);
237
-			if (is_null($this->activeUser)) {
238
-				return null;
239
-			}
240
-			$this->validateSession();
241
-		}
242
-		return $this->activeUser;
243
-	}
244
-
245
-	/**
246
-	 * Validate whether the current session is valid
247
-	 *
248
-	 * - For token-authenticated clients, the token validity is checked
249
-	 * - For browsers, the session token validity is checked
250
-	 */
251
-	protected function validateSession() {
252
-		$token = null;
253
-		$appPassword = $this->session->get('app_password');
254
-
255
-		if (is_null($appPassword)) {
256
-			try {
257
-				$token = $this->session->getId();
258
-			} catch (SessionNotAvailableException $ex) {
259
-				return;
260
-			}
261
-		} else {
262
-			$token = $appPassword;
263
-		}
264
-
265
-		if (!$this->validateToken($token)) {
266
-			// Session was invalidated
267
-			$this->logout();
268
-		}
269
-	}
270
-
271
-	/**
272
-	 * Checks whether the user is logged in
273
-	 *
274
-	 * @return bool if logged in
275
-	 */
276
-	public function isLoggedIn() {
277
-		$user = $this->getUser();
278
-		if (is_null($user)) {
279
-			return false;
280
-		}
281
-
282
-		return $user->isEnabled();
283
-	}
284
-
285
-	/**
286
-	 * set the login name
287
-	 *
288
-	 * @param string|null $loginName for the logged in user
289
-	 */
290
-	public function setLoginName($loginName) {
291
-		if (is_null($loginName)) {
292
-			$this->session->remove('loginname');
293
-		} else {
294
-			$this->session->set('loginname', $loginName);
295
-		}
296
-	}
297
-
298
-	/**
299
-	 * get the login name of the current user
300
-	 *
301
-	 * @return string
302
-	 */
303
-	public function getLoginName() {
304
-		if ($this->activeUser) {
305
-			return $this->session->get('loginname');
306
-		}
307
-
308
-		$uid = $this->session->get('user_id');
309
-		if ($uid) {
310
-			$this->activeUser = $this->manager->get($uid);
311
-			return $this->session->get('loginname');
312
-		}
313
-
314
-		return null;
315
-	}
316
-
317
-	/**
318
-	 * @return mixed
319
-	 */
320
-	public function getImpersonatingUserID(): ?string {
321
-
322
-		return $this->session->get('oldUserId');
323
-
324
-	}
325
-
326
-	public function setImpersonatingUserID(bool $useCurrentUser = true): void {
327
-		if ($useCurrentUser === false) {
328
-			$this->session->remove('oldUserId');
329
-			return;
330
-		}
331
-
332
-		$currentUser = $this->getUser();
333
-
334
-		if ($currentUser === null) {
335
-			throw new \OC\User\NoUserException();
336
-		}
337
-		$this->session->set('oldUserId', $currentUser->getUID());
338
-
339
-	}
340
-	/**
341
-	 * set the token id
342
-	 *
343
-	 * @param int|null $token that was used to log in
344
-	 */
345
-	protected function setToken($token) {
346
-		if ($token === null) {
347
-			$this->session->remove('token-id');
348
-		} else {
349
-			$this->session->set('token-id', $token);
350
-		}
351
-	}
352
-
353
-	/**
354
-	 * try to log in with the provided credentials
355
-	 *
356
-	 * @param string $uid
357
-	 * @param string $password
358
-	 * @return boolean|null
359
-	 * @throws LoginException
360
-	 */
361
-	public function login($uid, $password) {
362
-		$this->session->regenerateId();
363
-		if ($this->validateToken($password, $uid)) {
364
-			return $this->loginWithToken($password);
365
-		}
366
-		return $this->loginWithPassword($uid, $password);
367
-	}
368
-
369
-	/**
370
-	 * @param IUser $user
371
-	 * @param array $loginDetails
372
-	 * @param bool $regenerateSessionId
373
-	 * @return true returns true if login successful or an exception otherwise
374
-	 * @throws LoginException
375
-	 */
376
-	public function completeLogin(IUser $user, array $loginDetails, $regenerateSessionId = true) {
377
-		if (!$user->isEnabled()) {
378
-			// disabled users can not log in
379
-			// injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
380
-			$message = \OC::$server->getL10N('lib')->t('User disabled');
381
-			throw new LoginException($message);
382
-		}
383
-
384
-		if($regenerateSessionId) {
385
-			$this->session->regenerateId();
386
-		}
387
-
388
-		$this->setUser($user);
389
-		$this->setLoginName($loginDetails['loginName']);
390
-
391
-		$isToken = isset($loginDetails['token']) && $loginDetails['token'] instanceof IToken;
392
-		if ($isToken) {
393
-			$this->setToken($loginDetails['token']->getId());
394
-			$this->lockdownManager->setToken($loginDetails['token']);
395
-			$firstTimeLogin = false;
396
-		} else {
397
-			$this->setToken(null);
398
-			$firstTimeLogin = $user->updateLastLoginTimestamp();
399
-		}
400
-
401
-		$postLoginEvent = new OC\User\Events\PostLoginEvent(
402
-			$user,
403
-			$loginDetails['password'],
404
-			$isToken
405
-		);
406
-		$this->dispatcher->dispatch(OC\User\Events\PostLoginEvent::class, $postLoginEvent);
407
-
408
-		$this->manager->emit('\OC\User', 'postLogin', [
409
-			$user,
410
-			$loginDetails['password'],
411
-			$isToken,
412
-		]);
413
-		if($this->isLoggedIn()) {
414
-			$this->prepareUserLogin($firstTimeLogin, $regenerateSessionId);
415
-			return true;
416
-		}
417
-
418
-		$message = \OC::$server->getL10N('lib')->t('Login canceled by app');
419
-		throw new LoginException($message);
420
-	}
421
-
422
-	/**
423
-	 * Tries to log in a client
424
-	 *
425
-	 * Checks token auth enforced
426
-	 * Checks 2FA enabled
427
-	 *
428
-	 * @param string $user
429
-	 * @param string $password
430
-	 * @param IRequest $request
431
-	 * @param OC\Security\Bruteforce\Throttler $throttler
432
-	 * @throws LoginException
433
-	 * @throws PasswordLoginForbiddenException
434
-	 * @return boolean
435
-	 */
436
-	public function logClientIn($user,
437
-								$password,
438
-								IRequest $request,
439
-								OC\Security\Bruteforce\Throttler $throttler) {
440
-		$currentDelay = $throttler->sleepDelay($request->getRemoteAddress(), 'login');
441
-
442
-		if ($this->manager instanceof PublicEmitter) {
443
-			$this->manager->emit('\OC\User', 'preLogin', array($user, $password));
444
-		}
445
-
446
-		try {
447
-			$isTokenPassword = $this->isTokenPassword($password);
448
-		} catch (ExpiredTokenException $e) {
449
-			// Just return on an expired token no need to check further or record a failed login
450
-			return false;
451
-		}
452
-
453
-		if (!$isTokenPassword && $this->isTokenAuthEnforced()) {
454
-			throw new PasswordLoginForbiddenException();
455
-		}
456
-		if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) {
457
-			throw new PasswordLoginForbiddenException();
458
-		}
459
-
460
-		// Try to login with this username and password
461
-		if (!$this->login($user, $password) ) {
462
-
463
-			// Failed, maybe the user used their email address
464
-			$users = $this->manager->getByEmail($user);
465
-			if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) {
466
-
467
-				$this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
468
-
469
-				$throttler->registerAttempt('login', $request->getRemoteAddress(), ['user' => $user]);
470
-				if ($currentDelay === 0) {
471
-					$throttler->sleepDelay($request->getRemoteAddress(), 'login');
472
-				}
473
-				return false;
474
-			}
475
-		}
476
-
477
-		if ($isTokenPassword) {
478
-			$this->session->set('app_password', $password);
479
-		} else if($this->supportsCookies($request)) {
480
-			// Password login, but cookies supported -> create (browser) session token
481
-			$this->createSessionToken($request, $this->getUser()->getUID(), $user, $password);
482
-		}
483
-
484
-		return true;
485
-	}
486
-
487
-	protected function supportsCookies(IRequest $request) {
488
-		if (!is_null($request->getCookie('cookie_test'))) {
489
-			return true;
490
-		}
491
-		setcookie('cookie_test', 'test', $this->timeFactory->getTime() + 3600);
492
-		return false;
493
-	}
494
-
495
-	private function isTokenAuthEnforced() {
496
-		return $this->config->getSystemValue('token_auth_enforced', false);
497
-	}
498
-
499
-	protected function isTwoFactorEnforced($username) {
500
-		Util::emitHook(
501
-			'\OCA\Files_Sharing\API\Server2Server',
502
-			'preLoginNameUsedAsUserName',
503
-			array('uid' => &$username)
504
-		);
505
-		$user = $this->manager->get($username);
506
-		if (is_null($user)) {
507
-			$users = $this->manager->getByEmail($username);
508
-			if (empty($users)) {
509
-				return false;
510
-			}
511
-			if (count($users) !== 1) {
512
-				return true;
513
-			}
514
-			$user = $users[0];
515
-		}
516
-		// DI not possible due to cyclic dependencies :'-/
517
-		return OC::$server->getTwoFactorAuthManager()->isTwoFactorAuthenticated($user);
518
-	}
519
-
520
-	/**
521
-	 * Check if the given 'password' is actually a device token
522
-	 *
523
-	 * @param string $password
524
-	 * @return boolean
525
-	 * @throws ExpiredTokenException
526
-	 */
527
-	public function isTokenPassword($password) {
528
-		try {
529
-			$this->tokenProvider->getToken($password);
530
-			return true;
531
-		} catch (ExpiredTokenException $e) {
532
-			throw $e;
533
-		} catch (InvalidTokenException $ex) {
534
-			return false;
535
-		}
536
-	}
537
-
538
-	protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
539
-		if ($refreshCsrfToken) {
540
-			// TODO: mock/inject/use non-static
541
-			// Refresh the token
542
-			\OC::$server->getCsrfTokenManager()->refreshToken();
543
-		}
544
-
545
-		//we need to pass the user name, which may differ from login name
546
-		$user = $this->getUser()->getUID();
547
-		OC_Util::setupFS($user);
548
-
549
-		if ($firstTimeLogin) {
550
-			// TODO: lock necessary?
551
-			//trigger creation of user home and /files folder
552
-			$userFolder = \OC::$server->getUserFolder($user);
553
-
554
-			try {
555
-				// copy skeleton
556
-				\OC_Util::copySkeleton($user, $userFolder);
557
-			} catch (NotPermittedException $ex) {
558
-				// read only uses
559
-			}
560
-
561
-			// trigger any other initialization
562
-			\OC::$server->getEventDispatcher()->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser()));
563
-		}
564
-	}
565
-
566
-	/**
567
-	 * Tries to login the user with HTTP Basic Authentication
568
-	 *
569
-	 * @todo do not allow basic auth if the user is 2FA enforced
570
-	 * @param IRequest $request
571
-	 * @param OC\Security\Bruteforce\Throttler $throttler
572
-	 * @return boolean if the login was successful
573
-	 */
574
-	public function tryBasicAuthLogin(IRequest $request,
575
-									  OC\Security\Bruteforce\Throttler $throttler) {
576
-		if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) {
577
-			try {
578
-				if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) {
579
-					/**
580
-					 * Add DAV authenticated. This should in an ideal world not be
581
-					 * necessary but the iOS App reads cookies from anywhere instead
582
-					 * only the DAV endpoint.
583
-					 * This makes sure that the cookies will be valid for the whole scope
584
-					 * @see https://github.com/owncloud/core/issues/22893
585
-					 */
586
-					$this->session->set(
587
-						Auth::DAV_AUTHENTICATED, $this->getUser()->getUID()
588
-					);
589
-
590
-					// Set the last-password-confirm session to make the sudo mode work
591
-					 $this->session->set('last-password-confirm', $this->timeFactory->getTime());
592
-
593
-					return true;
594
-				}
595
-			} catch (PasswordLoginForbiddenException $ex) {
596
-				// Nothing to do
597
-			}
598
-		}
599
-		return false;
600
-	}
601
-
602
-	/**
603
-	 * Log an user in via login name and password
604
-	 *
605
-	 * @param string $uid
606
-	 * @param string $password
607
-	 * @return boolean
608
-	 * @throws LoginException if an app canceld the login process or the user is not enabled
609
-	 */
610
-	private function loginWithPassword($uid, $password) {
611
-		$user = $this->manager->checkPasswordNoLogging($uid, $password);
612
-		if ($user === false) {
613
-			// Password check failed
614
-			return false;
615
-		}
616
-
617
-		return $this->completeLogin($user, ['loginName' => $uid, 'password' => $password], false);
618
-	}
619
-
620
-	/**
621
-	 * Log an user in with a given token (id)
622
-	 *
623
-	 * @param string $token
624
-	 * @return boolean
625
-	 * @throws LoginException if an app canceled the login process or the user is not enabled
626
-	 */
627
-	private function loginWithToken($token) {
628
-		try {
629
-			$dbToken = $this->tokenProvider->getToken($token);
630
-		} catch (InvalidTokenException $ex) {
631
-			return false;
632
-		}
633
-		$uid = $dbToken->getUID();
634
-
635
-		// When logging in with token, the password must be decrypted first before passing to login hook
636
-		$password = '';
637
-		try {
638
-			$password = $this->tokenProvider->getPassword($dbToken, $token);
639
-		} catch (PasswordlessTokenException $ex) {
640
-			// Ignore and use empty string instead
641
-		}
642
-
643
-		$this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
644
-
645
-		$user = $this->manager->get($uid);
646
-		if (is_null($user)) {
647
-			// user does not exist
648
-			return false;
649
-		}
650
-
651
-		return $this->completeLogin(
652
-			$user,
653
-			[
654
-				'loginName' => $dbToken->getLoginName(),
655
-				'password' => $password,
656
-				'token' => $dbToken
657
-			],
658
-			false);
659
-	}
660
-
661
-	/**
662
-	 * Create a new session token for the given user credentials
663
-	 *
664
-	 * @param IRequest $request
665
-	 * @param string $uid user UID
666
-	 * @param string $loginName login name
667
-	 * @param string $password
668
-	 * @param int $remember
669
-	 * @return boolean
670
-	 */
671
-	public function createSessionToken(IRequest $request, $uid, $loginName, $password = null, $remember = IToken::DO_NOT_REMEMBER) {
672
-		if (is_null($this->manager->get($uid))) {
673
-			// User does not exist
674
-			return false;
675
-		}
676
-		$name = isset($request->server['HTTP_USER_AGENT']) ? $request->server['HTTP_USER_AGENT'] : 'unknown browser';
677
-		try {
678
-			$sessionId = $this->session->getId();
679
-			$pwd = $this->getPassword($password);
680
-			// Make sure the current sessionId has no leftover tokens
681
-			$this->tokenProvider->invalidateToken($sessionId);
682
-			$this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name, IToken::TEMPORARY_TOKEN, $remember);
683
-			return true;
684
-		} catch (SessionNotAvailableException $ex) {
685
-			// This can happen with OCC, where a memory session is used
686
-			// if a memory session is used, we shouldn't create a session token anyway
687
-			return false;
688
-		}
689
-	}
690
-
691
-	/**
692
-	 * Checks if the given password is a token.
693
-	 * If yes, the password is extracted from the token.
694
-	 * If no, the same password is returned.
695
-	 *
696
-	 * @param string $password either the login password or a device token
697
-	 * @return string|null the password or null if none was set in the token
698
-	 */
699
-	private function getPassword($password) {
700
-		if (is_null($password)) {
701
-			// This is surely no token ;-)
702
-			return null;
703
-		}
704
-		try {
705
-			$token = $this->tokenProvider->getToken($password);
706
-			try {
707
-				return $this->tokenProvider->getPassword($token, $password);
708
-			} catch (PasswordlessTokenException $ex) {
709
-				return null;
710
-			}
711
-		} catch (InvalidTokenException $ex) {
712
-			return $password;
713
-		}
714
-	}
715
-
716
-	/**
717
-	 * @param IToken $dbToken
718
-	 * @param string $token
719
-	 * @return boolean
720
-	 */
721
-	private function checkTokenCredentials(IToken $dbToken, $token) {
722
-		// Check whether login credentials are still valid and the user was not disabled
723
-		// This check is performed each 5 minutes
724
-		$lastCheck = $dbToken->getLastCheck() ? : 0;
725
-		$now = $this->timeFactory->getTime();
726
-		if ($lastCheck > ($now - 60 * 5)) {
727
-			// Checked performed recently, nothing to do now
728
-			return true;
729
-		}
730
-
731
-		try {
732
-			$pwd = $this->tokenProvider->getPassword($dbToken, $token);
733
-		} catch (InvalidTokenException $ex) {
734
-			// An invalid token password was used -> log user out
735
-			return false;
736
-		} catch (PasswordlessTokenException $ex) {
737
-			// Token has no password
738
-
739
-			if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
740
-				$this->tokenProvider->invalidateToken($token);
741
-				return false;
742
-			}
743
-
744
-			$dbToken->setLastCheck($now);
745
-			return true;
746
-		}
747
-
748
-		// Invalidate token if the user is no longer active
749
-		if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
750
-			$this->tokenProvider->invalidateToken($token);
751
-			return false;
752
-		}
753
-
754
-		// If the token password is no longer valid mark it as such
755
-		if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false) {
756
-			$this->tokenProvider->markPasswordInvalid($dbToken, $token);
757
-			// User is logged out
758
-			return false;
759
-		}
760
-
761
-		$dbToken->setLastCheck($now);
762
-		return true;
763
-	}
764
-
765
-	/**
766
-	 * Check if the given token exists and performs password/user-enabled checks
767
-	 *
768
-	 * Invalidates the token if checks fail
769
-	 *
770
-	 * @param string $token
771
-	 * @param string $user login name
772
-	 * @return boolean
773
-	 */
774
-	private function validateToken($token, $user = null) {
775
-		try {
776
-			$dbToken = $this->tokenProvider->getToken($token);
777
-		} catch (InvalidTokenException $ex) {
778
-			return false;
779
-		}
780
-
781
-		// Check if login names match
782
-		if (!is_null($user) && $dbToken->getLoginName() !== $user) {
783
-			// TODO: this makes it imposssible to use different login names on browser and client
784
-			// e.g. login by e-mail '[email protected]' on browser for generating the token will not
785
-			//      allow to use the client token with the login name 'user'.
786
-			return false;
787
-		}
788
-
789
-		if (!$this->checkTokenCredentials($dbToken, $token)) {
790
-			return false;
791
-		}
792
-
793
-		// Update token scope
794
-		$this->lockdownManager->setToken($dbToken);
795
-
796
-		$this->tokenProvider->updateTokenActivity($dbToken);
797
-
798
-		return true;
799
-	}
800
-
801
-	/**
802
-	 * Tries to login the user with auth token header
803
-	 *
804
-	 * @param IRequest $request
805
-	 * @todo check remember me cookie
806
-	 * @return boolean
807
-	 */
808
-	public function tryTokenLogin(IRequest $request) {
809
-		$authHeader = $request->getHeader('Authorization');
810
-		if (strpos($authHeader, 'Bearer ') === false) {
811
-			// No auth header, let's try session id
812
-			try {
813
-				$token = $this->session->getId();
814
-			} catch (SessionNotAvailableException $ex) {
815
-				return false;
816
-			}
817
-		} else {
818
-			$token = substr($authHeader, 7);
819
-		}
820
-
821
-		if (!$this->loginWithToken($token)) {
822
-			return false;
823
-		}
824
-		if(!$this->validateToken($token)) {
825
-			return false;
826
-		}
827
-
828
-		// Set the session variable so we know this is an app password
829
-		$this->session->set('app_password', $token);
830
-
831
-		return true;
832
-	}
833
-
834
-	/**
835
-	 * perform login using the magic cookie (remember login)
836
-	 *
837
-	 * @param string $uid the username
838
-	 * @param string $currentToken
839
-	 * @param string $oldSessionId
840
-	 * @return bool
841
-	 */
842
-	public function loginWithCookie($uid, $currentToken, $oldSessionId) {
843
-		$this->session->regenerateId();
844
-		$this->manager->emit('\OC\User', 'preRememberedLogin', array($uid));
845
-		$user = $this->manager->get($uid);
846
-		if (is_null($user)) {
847
-			// user does not exist
848
-			return false;
849
-		}
850
-
851
-		// get stored tokens
852
-		$tokens = $this->config->getUserKeys($uid, 'login_token');
853
-		// test cookies token against stored tokens
854
-		if (!in_array($currentToken, $tokens, true)) {
855
-			return false;
856
-		}
857
-		// replace successfully used token with a new one
858
-		$this->config->deleteUserValue($uid, 'login_token', $currentToken);
859
-		$newToken = $this->random->generate(32);
860
-		$this->config->setUserValue($uid, 'login_token', $newToken, $this->timeFactory->getTime());
861
-
862
-		try {
863
-			$sessionId = $this->session->getId();
864
-			$token = $this->tokenProvider->renewSessionToken($oldSessionId, $sessionId);
865
-		} catch (SessionNotAvailableException $ex) {
866
-			return false;
867
-		} catch (InvalidTokenException $ex) {
868
-			\OC::$server->getLogger()->warning('Renewing session token failed', ['app' => 'core']);
869
-			return false;
870
-		}
871
-
872
-		$this->setMagicInCookie($user->getUID(), $newToken);
873
-
874
-		//login
875
-		$this->setUser($user);
876
-		$this->setLoginName($token->getLoginName());
877
-		$this->setToken($token->getId());
878
-		$this->lockdownManager->setToken($token);
879
-		$user->updateLastLoginTimestamp();
880
-		$password = null;
881
-		try {
882
-			$password = $this->tokenProvider->getPassword($token, $sessionId);
883
-		} catch (PasswordlessTokenException $ex) {
884
-			// Ignore
885
-		}
886
-		$this->manager->emit('\OC\User', 'postRememberedLogin', [$user, $password]);
887
-		return true;
888
-	}
889
-
890
-	/**
891
-	 * @param IUser $user
892
-	 */
893
-	public function createRememberMeToken(IUser $user) {
894
-		$token = $this->random->generate(32);
895
-		$this->config->setUserValue($user->getUID(), 'login_token', $token, $this->timeFactory->getTime());
896
-		$this->setMagicInCookie($user->getUID(), $token);
897
-	}
898
-
899
-	/**
900
-	 * logout the user from the session
901
-	 */
902
-	public function logout() {
903
-		$this->manager->emit('\OC\User', 'logout');
904
-		$user = $this->getUser();
905
-		if (!is_null($user)) {
906
-			try {
907
-				$this->tokenProvider->invalidateToken($this->session->getId());
908
-			} catch (SessionNotAvailableException $ex) {
909
-
910
-			}
911
-		}
912
-		$this->setUser(null);
913
-		$this->setLoginName(null);
914
-		$this->setToken(null);
915
-		$this->unsetMagicInCookie();
916
-		$this->session->clear();
917
-		$this->manager->emit('\OC\User', 'postLogout');
918
-	}
919
-
920
-	/**
921
-	 * Set cookie value to use in next page load
922
-	 *
923
-	 * @param string $username username to be set
924
-	 * @param string $token
925
-	 */
926
-	public function setMagicInCookie($username, $token) {
927
-		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
928
-		$webRoot = \OC::$WEBROOT;
929
-		if ($webRoot === '') {
930
-			$webRoot = '/';
931
-		}
932
-
933
-		$maxAge = $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
934
-		\OC\Http\CookieHelper::setCookie(
935
-			'nc_username',
936
-			$username,
937
-			$maxAge,
938
-			$webRoot,
939
-			'',
940
-			$secureCookie,
941
-			true,
942
-			\OC\Http\CookieHelper::SAMESITE_LAX
943
-		);
944
-		\OC\Http\CookieHelper::setCookie(
945
-			'nc_token',
946
-			$token,
947
-			$maxAge,
948
-			$webRoot,
949
-			'',
950
-			$secureCookie,
951
-			true,
952
-			\OC\Http\CookieHelper::SAMESITE_LAX
953
-		);
954
-		try {
955
-			\OC\Http\CookieHelper::setCookie(
956
-				'nc_session_id',
957
-				$this->session->getId(),
958
-				$maxAge,
959
-				$webRoot,
960
-				'',
961
-				$secureCookie,
962
-				true,
963
-				\OC\Http\CookieHelper::SAMESITE_LAX
964
-			);
965
-		} catch (SessionNotAvailableException $ex) {
966
-			// ignore
967
-		}
968
-	}
969
-
970
-	/**
971
-	 * Remove cookie for "remember username"
972
-	 */
973
-	public function unsetMagicInCookie() {
974
-		//TODO: DI for cookies and IRequest
975
-		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
976
-
977
-		unset($_COOKIE['nc_username']); //TODO: DI
978
-		unset($_COOKIE['nc_token']);
979
-		unset($_COOKIE['nc_session_id']);
980
-		setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
981
-		setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
982
-		setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
983
-		// old cookies might be stored under /webroot/ instead of /webroot
984
-		// and Firefox doesn't like it!
985
-		setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
986
-		setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
987
-		setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
988
-	}
989
-
990
-	/**
991
-	 * Update password of the browser session token if there is one
992
-	 *
993
-	 * @param string $password
994
-	 */
995
-	public function updateSessionTokenPassword($password) {
996
-		try {
997
-			$sessionId = $this->session->getId();
998
-			$token = $this->tokenProvider->getToken($sessionId);
999
-			$this->tokenProvider->setPassword($token, $sessionId, $password);
1000
-		} catch (SessionNotAvailableException $ex) {
1001
-			// Nothing to do
1002
-		} catch (InvalidTokenException $ex) {
1003
-			// Nothing to do
1004
-		}
1005
-	}
1006
-
1007
-	public function updateTokens(string $uid, string $password) {
1008
-		$this->tokenProvider->updatePasswords($uid, $password);
1009
-	}
91
+    /** @var Manager|PublicEmitter $manager */
92
+    private $manager;
93
+
94
+    /** @var ISession $session */
95
+    private $session;
96
+
97
+    /** @var ITimeFactory */
98
+    private $timeFactory;
99
+
100
+    /** @var IProvider */
101
+    private $tokenProvider;
102
+
103
+    /** @var IConfig */
104
+    private $config;
105
+
106
+    /** @var User $activeUser */
107
+    protected $activeUser;
108
+
109
+    /** @var ISecureRandom */
110
+    private $random;
111
+
112
+    /** @var ILockdownManager  */
113
+    private $lockdownManager;
114
+
115
+    /** @var ILogger */
116
+    private $logger;
117
+    /** @var IEventDispatcher */
118
+    private $dispatcher;
119
+
120
+    /**
121
+     * @param Manager $manager
122
+     * @param ISession $session
123
+     * @param ITimeFactory $timeFactory
124
+     * @param IProvider $tokenProvider
125
+     * @param IConfig $config
126
+     * @param ISecureRandom $random
127
+     * @param ILockdownManager $lockdownManager
128
+     * @param ILogger $logger
129
+     */
130
+    public function __construct(Manager $manager,
131
+                                ISession $session,
132
+                                ITimeFactory $timeFactory,
133
+                                $tokenProvider,
134
+                                IConfig $config,
135
+                                ISecureRandom $random,
136
+                                ILockdownManager $lockdownManager,
137
+                                ILogger $logger,
138
+                                IEventDispatcher $dispatcher) {
139
+        $this->manager = $manager;
140
+        $this->session = $session;
141
+        $this->timeFactory = $timeFactory;
142
+        $this->tokenProvider = $tokenProvider;
143
+        $this->config = $config;
144
+        $this->random = $random;
145
+        $this->lockdownManager = $lockdownManager;
146
+        $this->logger = $logger;
147
+        $this->dispatcher = $dispatcher;
148
+    }
149
+
150
+    /**
151
+     * @param IProvider $provider
152
+     */
153
+    public function setTokenProvider(IProvider $provider) {
154
+        $this->tokenProvider = $provider;
155
+    }
156
+
157
+    /**
158
+     * @param string $scope
159
+     * @param string $method
160
+     * @param callable $callback
161
+     */
162
+    public function listen($scope, $method, callable $callback) {
163
+        $this->manager->listen($scope, $method, $callback);
164
+    }
165
+
166
+    /**
167
+     * @param string $scope optional
168
+     * @param string $method optional
169
+     * @param callable $callback optional
170
+     */
171
+    public function removeListener($scope = null, $method = null, callable $callback = null) {
172
+        $this->manager->removeListener($scope, $method, $callback);
173
+    }
174
+
175
+    /**
176
+     * get the manager object
177
+     *
178
+     * @return Manager|PublicEmitter
179
+     */
180
+    public function getManager() {
181
+        return $this->manager;
182
+    }
183
+
184
+    /**
185
+     * get the session object
186
+     *
187
+     * @return ISession
188
+     */
189
+    public function getSession() {
190
+        return $this->session;
191
+    }
192
+
193
+    /**
194
+     * set the session object
195
+     *
196
+     * @param ISession $session
197
+     */
198
+    public function setSession(ISession $session) {
199
+        if ($this->session instanceof ISession) {
200
+            $this->session->close();
201
+        }
202
+        $this->session = $session;
203
+        $this->activeUser = null;
204
+    }
205
+
206
+    /**
207
+     * set the currently active user
208
+     *
209
+     * @param IUser|null $user
210
+     */
211
+    public function setUser($user) {
212
+        if (is_null($user)) {
213
+            $this->session->remove('user_id');
214
+        } else {
215
+            $this->session->set('user_id', $user->getUID());
216
+        }
217
+        $this->activeUser = $user;
218
+    }
219
+
220
+    /**
221
+     * get the current active user
222
+     *
223
+     * @return IUser|null Current user, otherwise null
224
+     */
225
+    public function getUser() {
226
+        // FIXME: This is a quick'n dirty work-around for the incognito mode as
227
+        // described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155
228
+        if (OC_User::isIncognitoMode()) {
229
+            return null;
230
+        }
231
+        if (is_null($this->activeUser)) {
232
+            $uid = $this->session->get('user_id');
233
+            if (is_null($uid)) {
234
+                return null;
235
+            }
236
+            $this->activeUser = $this->manager->get($uid);
237
+            if (is_null($this->activeUser)) {
238
+                return null;
239
+            }
240
+            $this->validateSession();
241
+        }
242
+        return $this->activeUser;
243
+    }
244
+
245
+    /**
246
+     * Validate whether the current session is valid
247
+     *
248
+     * - For token-authenticated clients, the token validity is checked
249
+     * - For browsers, the session token validity is checked
250
+     */
251
+    protected function validateSession() {
252
+        $token = null;
253
+        $appPassword = $this->session->get('app_password');
254
+
255
+        if (is_null($appPassword)) {
256
+            try {
257
+                $token = $this->session->getId();
258
+            } catch (SessionNotAvailableException $ex) {
259
+                return;
260
+            }
261
+        } else {
262
+            $token = $appPassword;
263
+        }
264
+
265
+        if (!$this->validateToken($token)) {
266
+            // Session was invalidated
267
+            $this->logout();
268
+        }
269
+    }
270
+
271
+    /**
272
+     * Checks whether the user is logged in
273
+     *
274
+     * @return bool if logged in
275
+     */
276
+    public function isLoggedIn() {
277
+        $user = $this->getUser();
278
+        if (is_null($user)) {
279
+            return false;
280
+        }
281
+
282
+        return $user->isEnabled();
283
+    }
284
+
285
+    /**
286
+     * set the login name
287
+     *
288
+     * @param string|null $loginName for the logged in user
289
+     */
290
+    public function setLoginName($loginName) {
291
+        if (is_null($loginName)) {
292
+            $this->session->remove('loginname');
293
+        } else {
294
+            $this->session->set('loginname', $loginName);
295
+        }
296
+    }
297
+
298
+    /**
299
+     * get the login name of the current user
300
+     *
301
+     * @return string
302
+     */
303
+    public function getLoginName() {
304
+        if ($this->activeUser) {
305
+            return $this->session->get('loginname');
306
+        }
307
+
308
+        $uid = $this->session->get('user_id');
309
+        if ($uid) {
310
+            $this->activeUser = $this->manager->get($uid);
311
+            return $this->session->get('loginname');
312
+        }
313
+
314
+        return null;
315
+    }
316
+
317
+    /**
318
+     * @return mixed
319
+     */
320
+    public function getImpersonatingUserID(): ?string {
321
+
322
+        return $this->session->get('oldUserId');
323
+
324
+    }
325
+
326
+    public function setImpersonatingUserID(bool $useCurrentUser = true): void {
327
+        if ($useCurrentUser === false) {
328
+            $this->session->remove('oldUserId');
329
+            return;
330
+        }
331
+
332
+        $currentUser = $this->getUser();
333
+
334
+        if ($currentUser === null) {
335
+            throw new \OC\User\NoUserException();
336
+        }
337
+        $this->session->set('oldUserId', $currentUser->getUID());
338
+
339
+    }
340
+    /**
341
+     * set the token id
342
+     *
343
+     * @param int|null $token that was used to log in
344
+     */
345
+    protected function setToken($token) {
346
+        if ($token === null) {
347
+            $this->session->remove('token-id');
348
+        } else {
349
+            $this->session->set('token-id', $token);
350
+        }
351
+    }
352
+
353
+    /**
354
+     * try to log in with the provided credentials
355
+     *
356
+     * @param string $uid
357
+     * @param string $password
358
+     * @return boolean|null
359
+     * @throws LoginException
360
+     */
361
+    public function login($uid, $password) {
362
+        $this->session->regenerateId();
363
+        if ($this->validateToken($password, $uid)) {
364
+            return $this->loginWithToken($password);
365
+        }
366
+        return $this->loginWithPassword($uid, $password);
367
+    }
368
+
369
+    /**
370
+     * @param IUser $user
371
+     * @param array $loginDetails
372
+     * @param bool $regenerateSessionId
373
+     * @return true returns true if login successful or an exception otherwise
374
+     * @throws LoginException
375
+     */
376
+    public function completeLogin(IUser $user, array $loginDetails, $regenerateSessionId = true) {
377
+        if (!$user->isEnabled()) {
378
+            // disabled users can not log in
379
+            // injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
380
+            $message = \OC::$server->getL10N('lib')->t('User disabled');
381
+            throw new LoginException($message);
382
+        }
383
+
384
+        if($regenerateSessionId) {
385
+            $this->session->regenerateId();
386
+        }
387
+
388
+        $this->setUser($user);
389
+        $this->setLoginName($loginDetails['loginName']);
390
+
391
+        $isToken = isset($loginDetails['token']) && $loginDetails['token'] instanceof IToken;
392
+        if ($isToken) {
393
+            $this->setToken($loginDetails['token']->getId());
394
+            $this->lockdownManager->setToken($loginDetails['token']);
395
+            $firstTimeLogin = false;
396
+        } else {
397
+            $this->setToken(null);
398
+            $firstTimeLogin = $user->updateLastLoginTimestamp();
399
+        }
400
+
401
+        $postLoginEvent = new OC\User\Events\PostLoginEvent(
402
+            $user,
403
+            $loginDetails['password'],
404
+            $isToken
405
+        );
406
+        $this->dispatcher->dispatch(OC\User\Events\PostLoginEvent::class, $postLoginEvent);
407
+
408
+        $this->manager->emit('\OC\User', 'postLogin', [
409
+            $user,
410
+            $loginDetails['password'],
411
+            $isToken,
412
+        ]);
413
+        if($this->isLoggedIn()) {
414
+            $this->prepareUserLogin($firstTimeLogin, $regenerateSessionId);
415
+            return true;
416
+        }
417
+
418
+        $message = \OC::$server->getL10N('lib')->t('Login canceled by app');
419
+        throw new LoginException($message);
420
+    }
421
+
422
+    /**
423
+     * Tries to log in a client
424
+     *
425
+     * Checks token auth enforced
426
+     * Checks 2FA enabled
427
+     *
428
+     * @param string $user
429
+     * @param string $password
430
+     * @param IRequest $request
431
+     * @param OC\Security\Bruteforce\Throttler $throttler
432
+     * @throws LoginException
433
+     * @throws PasswordLoginForbiddenException
434
+     * @return boolean
435
+     */
436
+    public function logClientIn($user,
437
+                                $password,
438
+                                IRequest $request,
439
+                                OC\Security\Bruteforce\Throttler $throttler) {
440
+        $currentDelay = $throttler->sleepDelay($request->getRemoteAddress(), 'login');
441
+
442
+        if ($this->manager instanceof PublicEmitter) {
443
+            $this->manager->emit('\OC\User', 'preLogin', array($user, $password));
444
+        }
445
+
446
+        try {
447
+            $isTokenPassword = $this->isTokenPassword($password);
448
+        } catch (ExpiredTokenException $e) {
449
+            // Just return on an expired token no need to check further or record a failed login
450
+            return false;
451
+        }
452
+
453
+        if (!$isTokenPassword && $this->isTokenAuthEnforced()) {
454
+            throw new PasswordLoginForbiddenException();
455
+        }
456
+        if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) {
457
+            throw new PasswordLoginForbiddenException();
458
+        }
459
+
460
+        // Try to login with this username and password
461
+        if (!$this->login($user, $password) ) {
462
+
463
+            // Failed, maybe the user used their email address
464
+            $users = $this->manager->getByEmail($user);
465
+            if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) {
466
+
467
+                $this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
468
+
469
+                $throttler->registerAttempt('login', $request->getRemoteAddress(), ['user' => $user]);
470
+                if ($currentDelay === 0) {
471
+                    $throttler->sleepDelay($request->getRemoteAddress(), 'login');
472
+                }
473
+                return false;
474
+            }
475
+        }
476
+
477
+        if ($isTokenPassword) {
478
+            $this->session->set('app_password', $password);
479
+        } else if($this->supportsCookies($request)) {
480
+            // Password login, but cookies supported -> create (browser) session token
481
+            $this->createSessionToken($request, $this->getUser()->getUID(), $user, $password);
482
+        }
483
+
484
+        return true;
485
+    }
486
+
487
+    protected function supportsCookies(IRequest $request) {
488
+        if (!is_null($request->getCookie('cookie_test'))) {
489
+            return true;
490
+        }
491
+        setcookie('cookie_test', 'test', $this->timeFactory->getTime() + 3600);
492
+        return false;
493
+    }
494
+
495
+    private function isTokenAuthEnforced() {
496
+        return $this->config->getSystemValue('token_auth_enforced', false);
497
+    }
498
+
499
+    protected function isTwoFactorEnforced($username) {
500
+        Util::emitHook(
501
+            '\OCA\Files_Sharing\API\Server2Server',
502
+            'preLoginNameUsedAsUserName',
503
+            array('uid' => &$username)
504
+        );
505
+        $user = $this->manager->get($username);
506
+        if (is_null($user)) {
507
+            $users = $this->manager->getByEmail($username);
508
+            if (empty($users)) {
509
+                return false;
510
+            }
511
+            if (count($users) !== 1) {
512
+                return true;
513
+            }
514
+            $user = $users[0];
515
+        }
516
+        // DI not possible due to cyclic dependencies :'-/
517
+        return OC::$server->getTwoFactorAuthManager()->isTwoFactorAuthenticated($user);
518
+    }
519
+
520
+    /**
521
+     * Check if the given 'password' is actually a device token
522
+     *
523
+     * @param string $password
524
+     * @return boolean
525
+     * @throws ExpiredTokenException
526
+     */
527
+    public function isTokenPassword($password) {
528
+        try {
529
+            $this->tokenProvider->getToken($password);
530
+            return true;
531
+        } catch (ExpiredTokenException $e) {
532
+            throw $e;
533
+        } catch (InvalidTokenException $ex) {
534
+            return false;
535
+        }
536
+    }
537
+
538
+    protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
539
+        if ($refreshCsrfToken) {
540
+            // TODO: mock/inject/use non-static
541
+            // Refresh the token
542
+            \OC::$server->getCsrfTokenManager()->refreshToken();
543
+        }
544
+
545
+        //we need to pass the user name, which may differ from login name
546
+        $user = $this->getUser()->getUID();
547
+        OC_Util::setupFS($user);
548
+
549
+        if ($firstTimeLogin) {
550
+            // TODO: lock necessary?
551
+            //trigger creation of user home and /files folder
552
+            $userFolder = \OC::$server->getUserFolder($user);
553
+
554
+            try {
555
+                // copy skeleton
556
+                \OC_Util::copySkeleton($user, $userFolder);
557
+            } catch (NotPermittedException $ex) {
558
+                // read only uses
559
+            }
560
+
561
+            // trigger any other initialization
562
+            \OC::$server->getEventDispatcher()->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser()));
563
+        }
564
+    }
565
+
566
+    /**
567
+     * Tries to login the user with HTTP Basic Authentication
568
+     *
569
+     * @todo do not allow basic auth if the user is 2FA enforced
570
+     * @param IRequest $request
571
+     * @param OC\Security\Bruteforce\Throttler $throttler
572
+     * @return boolean if the login was successful
573
+     */
574
+    public function tryBasicAuthLogin(IRequest $request,
575
+                                        OC\Security\Bruteforce\Throttler $throttler) {
576
+        if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) {
577
+            try {
578
+                if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) {
579
+                    /**
580
+                     * Add DAV authenticated. This should in an ideal world not be
581
+                     * necessary but the iOS App reads cookies from anywhere instead
582
+                     * only the DAV endpoint.
583
+                     * This makes sure that the cookies will be valid for the whole scope
584
+                     * @see https://github.com/owncloud/core/issues/22893
585
+                     */
586
+                    $this->session->set(
587
+                        Auth::DAV_AUTHENTICATED, $this->getUser()->getUID()
588
+                    );
589
+
590
+                    // Set the last-password-confirm session to make the sudo mode work
591
+                        $this->session->set('last-password-confirm', $this->timeFactory->getTime());
592
+
593
+                    return true;
594
+                }
595
+            } catch (PasswordLoginForbiddenException $ex) {
596
+                // Nothing to do
597
+            }
598
+        }
599
+        return false;
600
+    }
601
+
602
+    /**
603
+     * Log an user in via login name and password
604
+     *
605
+     * @param string $uid
606
+     * @param string $password
607
+     * @return boolean
608
+     * @throws LoginException if an app canceld the login process or the user is not enabled
609
+     */
610
+    private function loginWithPassword($uid, $password) {
611
+        $user = $this->manager->checkPasswordNoLogging($uid, $password);
612
+        if ($user === false) {
613
+            // Password check failed
614
+            return false;
615
+        }
616
+
617
+        return $this->completeLogin($user, ['loginName' => $uid, 'password' => $password], false);
618
+    }
619
+
620
+    /**
621
+     * Log an user in with a given token (id)
622
+     *
623
+     * @param string $token
624
+     * @return boolean
625
+     * @throws LoginException if an app canceled the login process or the user is not enabled
626
+     */
627
+    private function loginWithToken($token) {
628
+        try {
629
+            $dbToken = $this->tokenProvider->getToken($token);
630
+        } catch (InvalidTokenException $ex) {
631
+            return false;
632
+        }
633
+        $uid = $dbToken->getUID();
634
+
635
+        // When logging in with token, the password must be decrypted first before passing to login hook
636
+        $password = '';
637
+        try {
638
+            $password = $this->tokenProvider->getPassword($dbToken, $token);
639
+        } catch (PasswordlessTokenException $ex) {
640
+            // Ignore and use empty string instead
641
+        }
642
+
643
+        $this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
644
+
645
+        $user = $this->manager->get($uid);
646
+        if (is_null($user)) {
647
+            // user does not exist
648
+            return false;
649
+        }
650
+
651
+        return $this->completeLogin(
652
+            $user,
653
+            [
654
+                'loginName' => $dbToken->getLoginName(),
655
+                'password' => $password,
656
+                'token' => $dbToken
657
+            ],
658
+            false);
659
+    }
660
+
661
+    /**
662
+     * Create a new session token for the given user credentials
663
+     *
664
+     * @param IRequest $request
665
+     * @param string $uid user UID
666
+     * @param string $loginName login name
667
+     * @param string $password
668
+     * @param int $remember
669
+     * @return boolean
670
+     */
671
+    public function createSessionToken(IRequest $request, $uid, $loginName, $password = null, $remember = IToken::DO_NOT_REMEMBER) {
672
+        if (is_null($this->manager->get($uid))) {
673
+            // User does not exist
674
+            return false;
675
+        }
676
+        $name = isset($request->server['HTTP_USER_AGENT']) ? $request->server['HTTP_USER_AGENT'] : 'unknown browser';
677
+        try {
678
+            $sessionId = $this->session->getId();
679
+            $pwd = $this->getPassword($password);
680
+            // Make sure the current sessionId has no leftover tokens
681
+            $this->tokenProvider->invalidateToken($sessionId);
682
+            $this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name, IToken::TEMPORARY_TOKEN, $remember);
683
+            return true;
684
+        } catch (SessionNotAvailableException $ex) {
685
+            // This can happen with OCC, where a memory session is used
686
+            // if a memory session is used, we shouldn't create a session token anyway
687
+            return false;
688
+        }
689
+    }
690
+
691
+    /**
692
+     * Checks if the given password is a token.
693
+     * If yes, the password is extracted from the token.
694
+     * If no, the same password is returned.
695
+     *
696
+     * @param string $password either the login password or a device token
697
+     * @return string|null the password or null if none was set in the token
698
+     */
699
+    private function getPassword($password) {
700
+        if (is_null($password)) {
701
+            // This is surely no token ;-)
702
+            return null;
703
+        }
704
+        try {
705
+            $token = $this->tokenProvider->getToken($password);
706
+            try {
707
+                return $this->tokenProvider->getPassword($token, $password);
708
+            } catch (PasswordlessTokenException $ex) {
709
+                return null;
710
+            }
711
+        } catch (InvalidTokenException $ex) {
712
+            return $password;
713
+        }
714
+    }
715
+
716
+    /**
717
+     * @param IToken $dbToken
718
+     * @param string $token
719
+     * @return boolean
720
+     */
721
+    private function checkTokenCredentials(IToken $dbToken, $token) {
722
+        // Check whether login credentials are still valid and the user was not disabled
723
+        // This check is performed each 5 minutes
724
+        $lastCheck = $dbToken->getLastCheck() ? : 0;
725
+        $now = $this->timeFactory->getTime();
726
+        if ($lastCheck > ($now - 60 * 5)) {
727
+            // Checked performed recently, nothing to do now
728
+            return true;
729
+        }
730
+
731
+        try {
732
+            $pwd = $this->tokenProvider->getPassword($dbToken, $token);
733
+        } catch (InvalidTokenException $ex) {
734
+            // An invalid token password was used -> log user out
735
+            return false;
736
+        } catch (PasswordlessTokenException $ex) {
737
+            // Token has no password
738
+
739
+            if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
740
+                $this->tokenProvider->invalidateToken($token);
741
+                return false;
742
+            }
743
+
744
+            $dbToken->setLastCheck($now);
745
+            return true;
746
+        }
747
+
748
+        // Invalidate token if the user is no longer active
749
+        if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
750
+            $this->tokenProvider->invalidateToken($token);
751
+            return false;
752
+        }
753
+
754
+        // If the token password is no longer valid mark it as such
755
+        if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false) {
756
+            $this->tokenProvider->markPasswordInvalid($dbToken, $token);
757
+            // User is logged out
758
+            return false;
759
+        }
760
+
761
+        $dbToken->setLastCheck($now);
762
+        return true;
763
+    }
764
+
765
+    /**
766
+     * Check if the given token exists and performs password/user-enabled checks
767
+     *
768
+     * Invalidates the token if checks fail
769
+     *
770
+     * @param string $token
771
+     * @param string $user login name
772
+     * @return boolean
773
+     */
774
+    private function validateToken($token, $user = null) {
775
+        try {
776
+            $dbToken = $this->tokenProvider->getToken($token);
777
+        } catch (InvalidTokenException $ex) {
778
+            return false;
779
+        }
780
+
781
+        // Check if login names match
782
+        if (!is_null($user) && $dbToken->getLoginName() !== $user) {
783
+            // TODO: this makes it imposssible to use different login names on browser and client
784
+            // e.g. login by e-mail '[email protected]' on browser for generating the token will not
785
+            //      allow to use the client token with the login name 'user'.
786
+            return false;
787
+        }
788
+
789
+        if (!$this->checkTokenCredentials($dbToken, $token)) {
790
+            return false;
791
+        }
792
+
793
+        // Update token scope
794
+        $this->lockdownManager->setToken($dbToken);
795
+
796
+        $this->tokenProvider->updateTokenActivity($dbToken);
797
+
798
+        return true;
799
+    }
800
+
801
+    /**
802
+     * Tries to login the user with auth token header
803
+     *
804
+     * @param IRequest $request
805
+     * @todo check remember me cookie
806
+     * @return boolean
807
+     */
808
+    public function tryTokenLogin(IRequest $request) {
809
+        $authHeader = $request->getHeader('Authorization');
810
+        if (strpos($authHeader, 'Bearer ') === false) {
811
+            // No auth header, let's try session id
812
+            try {
813
+                $token = $this->session->getId();
814
+            } catch (SessionNotAvailableException $ex) {
815
+                return false;
816
+            }
817
+        } else {
818
+            $token = substr($authHeader, 7);
819
+        }
820
+
821
+        if (!$this->loginWithToken($token)) {
822
+            return false;
823
+        }
824
+        if(!$this->validateToken($token)) {
825
+            return false;
826
+        }
827
+
828
+        // Set the session variable so we know this is an app password
829
+        $this->session->set('app_password', $token);
830
+
831
+        return true;
832
+    }
833
+
834
+    /**
835
+     * perform login using the magic cookie (remember login)
836
+     *
837
+     * @param string $uid the username
838
+     * @param string $currentToken
839
+     * @param string $oldSessionId
840
+     * @return bool
841
+     */
842
+    public function loginWithCookie($uid, $currentToken, $oldSessionId) {
843
+        $this->session->regenerateId();
844
+        $this->manager->emit('\OC\User', 'preRememberedLogin', array($uid));
845
+        $user = $this->manager->get($uid);
846
+        if (is_null($user)) {
847
+            // user does not exist
848
+            return false;
849
+        }
850
+
851
+        // get stored tokens
852
+        $tokens = $this->config->getUserKeys($uid, 'login_token');
853
+        // test cookies token against stored tokens
854
+        if (!in_array($currentToken, $tokens, true)) {
855
+            return false;
856
+        }
857
+        // replace successfully used token with a new one
858
+        $this->config->deleteUserValue($uid, 'login_token', $currentToken);
859
+        $newToken = $this->random->generate(32);
860
+        $this->config->setUserValue($uid, 'login_token', $newToken, $this->timeFactory->getTime());
861
+
862
+        try {
863
+            $sessionId = $this->session->getId();
864
+            $token = $this->tokenProvider->renewSessionToken($oldSessionId, $sessionId);
865
+        } catch (SessionNotAvailableException $ex) {
866
+            return false;
867
+        } catch (InvalidTokenException $ex) {
868
+            \OC::$server->getLogger()->warning('Renewing session token failed', ['app' => 'core']);
869
+            return false;
870
+        }
871
+
872
+        $this->setMagicInCookie($user->getUID(), $newToken);
873
+
874
+        //login
875
+        $this->setUser($user);
876
+        $this->setLoginName($token->getLoginName());
877
+        $this->setToken($token->getId());
878
+        $this->lockdownManager->setToken($token);
879
+        $user->updateLastLoginTimestamp();
880
+        $password = null;
881
+        try {
882
+            $password = $this->tokenProvider->getPassword($token, $sessionId);
883
+        } catch (PasswordlessTokenException $ex) {
884
+            // Ignore
885
+        }
886
+        $this->manager->emit('\OC\User', 'postRememberedLogin', [$user, $password]);
887
+        return true;
888
+    }
889
+
890
+    /**
891
+     * @param IUser $user
892
+     */
893
+    public function createRememberMeToken(IUser $user) {
894
+        $token = $this->random->generate(32);
895
+        $this->config->setUserValue($user->getUID(), 'login_token', $token, $this->timeFactory->getTime());
896
+        $this->setMagicInCookie($user->getUID(), $token);
897
+    }
898
+
899
+    /**
900
+     * logout the user from the session
901
+     */
902
+    public function logout() {
903
+        $this->manager->emit('\OC\User', 'logout');
904
+        $user = $this->getUser();
905
+        if (!is_null($user)) {
906
+            try {
907
+                $this->tokenProvider->invalidateToken($this->session->getId());
908
+            } catch (SessionNotAvailableException $ex) {
909
+
910
+            }
911
+        }
912
+        $this->setUser(null);
913
+        $this->setLoginName(null);
914
+        $this->setToken(null);
915
+        $this->unsetMagicInCookie();
916
+        $this->session->clear();
917
+        $this->manager->emit('\OC\User', 'postLogout');
918
+    }
919
+
920
+    /**
921
+     * Set cookie value to use in next page load
922
+     *
923
+     * @param string $username username to be set
924
+     * @param string $token
925
+     */
926
+    public function setMagicInCookie($username, $token) {
927
+        $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
928
+        $webRoot = \OC::$WEBROOT;
929
+        if ($webRoot === '') {
930
+            $webRoot = '/';
931
+        }
932
+
933
+        $maxAge = $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
934
+        \OC\Http\CookieHelper::setCookie(
935
+            'nc_username',
936
+            $username,
937
+            $maxAge,
938
+            $webRoot,
939
+            '',
940
+            $secureCookie,
941
+            true,
942
+            \OC\Http\CookieHelper::SAMESITE_LAX
943
+        );
944
+        \OC\Http\CookieHelper::setCookie(
945
+            'nc_token',
946
+            $token,
947
+            $maxAge,
948
+            $webRoot,
949
+            '',
950
+            $secureCookie,
951
+            true,
952
+            \OC\Http\CookieHelper::SAMESITE_LAX
953
+        );
954
+        try {
955
+            \OC\Http\CookieHelper::setCookie(
956
+                'nc_session_id',
957
+                $this->session->getId(),
958
+                $maxAge,
959
+                $webRoot,
960
+                '',
961
+                $secureCookie,
962
+                true,
963
+                \OC\Http\CookieHelper::SAMESITE_LAX
964
+            );
965
+        } catch (SessionNotAvailableException $ex) {
966
+            // ignore
967
+        }
968
+    }
969
+
970
+    /**
971
+     * Remove cookie for "remember username"
972
+     */
973
+    public function unsetMagicInCookie() {
974
+        //TODO: DI for cookies and IRequest
975
+        $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
976
+
977
+        unset($_COOKIE['nc_username']); //TODO: DI
978
+        unset($_COOKIE['nc_token']);
979
+        unset($_COOKIE['nc_session_id']);
980
+        setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
981
+        setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
982
+        setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
983
+        // old cookies might be stored under /webroot/ instead of /webroot
984
+        // and Firefox doesn't like it!
985
+        setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
986
+        setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
987
+        setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
988
+    }
989
+
990
+    /**
991
+     * Update password of the browser session token if there is one
992
+     *
993
+     * @param string $password
994
+     */
995
+    public function updateSessionTokenPassword($password) {
996
+        try {
997
+            $sessionId = $this->session->getId();
998
+            $token = $this->tokenProvider->getToken($sessionId);
999
+            $this->tokenProvider->setPassword($token, $sessionId, $password);
1000
+        } catch (SessionNotAvailableException $ex) {
1001
+            // Nothing to do
1002
+        } catch (InvalidTokenException $ex) {
1003
+            // Nothing to do
1004
+        }
1005
+    }
1006
+
1007
+    public function updateTokens(string $uid, string $password) {
1008
+        $this->tokenProvider->updatePasswords($uid, $password);
1009
+    }
1010 1010
 
1011 1011
 
1012 1012
 }
Please login to merge, or discard this patch.