Completed
Push — master ( 19d552...6b730b )
by Morris
21:07 queued 10:33
created

DefaultTokenProvider::updatePasswords()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
/**
4
 * @copyright Copyright (c) 2016, ownCloud, Inc.
5
 * @copyright Copyright (c) 2016, Christoph Wurst <[email protected]>
6
 *
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Marcel Waldvogel <[email protected]>
10
 * @author Martin <[email protected]>
11
 * @author Robin Appelman <[email protected]>
12
 *
13
 * @license AGPL-3.0
14
 *
15
 * This code is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License, version 3,
17
 * as published by the Free Software Foundation.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License, version 3,
25
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
26
 *
27
 */
28
29
namespace OC\Authentication\Token;
30
31
use Exception;
32
use OC\Authentication\Exceptions\InvalidTokenException;
33
use OC\Authentication\Exceptions\PasswordlessTokenException;
34
use OCP\AppFramework\Db\DoesNotExistException;
35
use OCP\AppFramework\Utility\ITimeFactory;
36
use OCP\IConfig;
37
use OCP\ILogger;
38
use OCP\Security\ICrypto;
39
40
class DefaultTokenProvider implements IProvider {
41
42
	/** @var DefaultTokenMapper */
43
	private $mapper;
44
45
	/** @var ICrypto */
46
	private $crypto;
47
48
	/** @var IConfig */
49
	private $config;
50
51
	/** @var ILogger $logger */
52
	private $logger;
53
54
	/** @var ITimeFactory $time */
55
	private $time;
56
57
	/**
58
	 * @param DefaultTokenMapper $mapper
59
	 * @param ICrypto $crypto
60
	 * @param IConfig $config
61
	 * @param ILogger $logger
62
	 * @param ITimeFactory $time
63
	 */
64 View Code Duplication
	public function __construct(DefaultTokenMapper $mapper,
65
								ICrypto $crypto,
66
								IConfig $config,
67
								ILogger $logger,
68
								ITimeFactory $time) {
69
		$this->mapper = $mapper;
70
		$this->crypto = $crypto;
71
		$this->config = $config;
72
		$this->logger = $logger;
73
		$this->time = $time;
74
	}
75
76
	/**
77
	 * Create and persist a new token
78
	 *
79
	 * @param string $token
80
	 * @param string $uid
81
	 * @param string $loginName
82
	 * @param string|null $password
83
	 * @param string $name
84
	 * @param int $type token type
85
	 * @param int $remember whether the session token should be used for remember-me
86
	 * @return IToken
87
	 */
88
	public function generateToken(string $token,
89
								  string $uid,
90
								  string $loginName,
91
								  $password,
92
								  string $name,
93
								  int $type = IToken::TEMPORARY_TOKEN,
94
								  int $remember = IToken::DO_NOT_REMEMBER): IToken {
95
		$dbToken = new DefaultToken();
96
		$dbToken->setUid($uid);
97
		$dbToken->setLoginName($loginName);
98
		if (!is_null($password)) {
99
			$dbToken->setPassword($this->encryptPassword($password, $token));
100
		}
101
		$dbToken->setName($name);
102
		$dbToken->setToken($this->hashToken($token));
103
		$dbToken->setType($type);
104
		$dbToken->setRemember($remember);
105
		$dbToken->setLastActivity($this->time->getTime());
106
		$dbToken->setLastCheck($this->time->getTime());
107
		$dbToken->setVersion(DefaultToken::VERSION);
108
109
		$this->mapper->insert($dbToken);
110
111
		return $dbToken;
112
	}
113
114
	/**
115
	 * Save the updated token
116
	 *
117
	 * @param IToken $token
118
	 * @throws InvalidTokenException
119
	 */
120
	public function updateToken(IToken $token) {
121
		if (!($token instanceof DefaultToken)) {
122
			throw new InvalidTokenException();
123
		}
124
		$this->mapper->update($token);
125
	}
126
127
	/**
128
	 * Update token activity timestamp
129
	 *
130
	 * @throws InvalidTokenException
131
	 * @param IToken $token
132
	 */
133 View Code Duplication
	public function updateTokenActivity(IToken $token) {
134
		if (!($token instanceof DefaultToken)) {
135
			throw new InvalidTokenException();
136
		}
137
		/** @var DefaultToken $token */
138
		$now = $this->time->getTime();
139
		if ($token->getLastActivity() < ($now - 60)) {
140
			// Update token only once per minute
141
			$token->setLastActivity($now);
142
			$this->mapper->update($token);
143
		}
144
	}
145
146
	public function getTokenByUser(string $uid): array {
147
		return $this->mapper->getTokenByUser($uid);
148
	}
149
150
	/**
151
	 * Get a token by token
152
	 *
153
	 * @param string $tokenId
154
	 * @throws InvalidTokenException
155
	 * @throws ExpiredTokenException
156
	 * @return IToken
157
	 */
158 View Code Duplication
	public function getToken(string $tokenId): IToken {
159
		try {
160
			$token = $this->mapper->getToken($this->hashToken($tokenId));
161
		} catch (DoesNotExistException $ex) {
162
			throw new InvalidTokenException();
163
		}
164
165
		if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
166
			throw new ExpiredTokenException($token);
167
		}
168
169
		return $token;
170
	}
171
172
	/**
173
	 * Get a token by token id
174
	 *
175
	 * @param int $tokenId
176
	 * @throws InvalidTokenException
177
	 * @throws ExpiredTokenException
178
	 * @return IToken
179
	 */
180 View Code Duplication
	public function getTokenById(int $tokenId): IToken {
181
		try {
182
			$token = $this->mapper->getTokenById($tokenId);
183
		} catch (DoesNotExistException $ex) {
184
			throw new InvalidTokenException();
185
		}
186
187
		if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
188
			throw new ExpiredTokenException($token);
189
		}
190
191
		return $token;
192
	}
193
194
	/**
195
	 * @param string $oldSessionId
196
	 * @param string $sessionId
197
	 * @throws InvalidTokenException
198
	 */
199
	public function renewSessionToken(string $oldSessionId, string $sessionId) {
200
		$token = $this->getToken($oldSessionId);
201
202
		$newToken = new DefaultToken();
203
		$newToken->setUid($token->getUID());
204
		$newToken->setLoginName($token->getLoginName());
205
		if (!is_null($token->getPassword())) {
206
			$password = $this->decryptPassword($token->getPassword(), $oldSessionId);
207
			$newToken->setPassword($this->encryptPassword($password, $sessionId));
208
		}
209
		$newToken->setName($token->getName());
210
		$newToken->setToken($this->hashToken($sessionId));
211
		$newToken->setType(IToken::TEMPORARY_TOKEN);
212
		$newToken->setRemember($token->getRemember());
213
		$newToken->setLastActivity($this->time->getTime());
214
		$this->mapper->insert($newToken);
215
		$this->mapper->delete($token);
0 ignored issues
show
Documentation introduced by
$token is of type object<OC\Authentication\Token\IToken>, but the function expects a object<OCP\AppFramework\Db\Entity>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
216
	}
217
218
	/**
219
	 * @param IToken $savedToken
220
	 * @param string $tokenId session token
221
	 * @throws InvalidTokenException
222
	 * @throws PasswordlessTokenException
223
	 * @return string
224
	 */
225
	public function getPassword(IToken $savedToken, string $tokenId): string {
226
		$password = $savedToken->getPassword();
227
		if (is_null($password)) {
228
			throw new PasswordlessTokenException();
229
		}
230
		return $this->decryptPassword($password, $tokenId);
231
	}
232
233
	/**
234
	 * Encrypt and set the password of the given token
235
	 *
236
	 * @param IToken $token
237
	 * @param string $tokenId
238
	 * @param string $password
239
	 * @throws InvalidTokenException
240
	 */
241
	public function setPassword(IToken $token, string $tokenId, string $password) {
242
		if (!($token instanceof DefaultToken)) {
243
			throw new InvalidTokenException();
244
		}
245
		/** @var DefaultToken $token */
246
		$token->setPassword($this->encryptPassword($password, $tokenId));
247
		$this->mapper->update($token);
248
	}
249
250
	/**
251
	 * Invalidate (delete) the given session token
252
	 *
253
	 * @param string $token
254
	 */
255
	public function invalidateToken(string $token) {
256
		$this->mapper->invalidate($this->hashToken($token));
257
	}
258
259
	public function invalidateTokenById(string $uid, int $id) {
260
		$this->mapper->deleteById($uid, $id);
261
	}
262
263
	/**
264
	 * Invalidate (delete) old session tokens
265
	 */
266 View Code Duplication
	public function invalidateOldTokens() {
267
		$olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24);
268
		$this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
269
		$this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER);
270
		$rememberThreshold = $this->time->getTime() - (int) $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
271
		$this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']);
272
		$this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER);
273
	}
274
275
	/**
276
	 * Rotate the token. Usefull for for example oauth tokens
277
	 *
278
	 * @param IToken $token
279
	 * @param string $oldTokenId
280
	 * @param string $newTokenId
281
	 * @return IToken
282
	 */
283
	public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
284
		try {
285
			$password = $this->getPassword($token, $oldTokenId);
286
			$token->setPassword($this->encryptPassword($password, $newTokenId));
287
		} catch (PasswordlessTokenException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
288
289
		}
290
291
		$token->setToken($this->hashToken($newTokenId));
292
		$this->updateToken($token);
293
294
		return $token;
295
	}
296
297
	/**
298
	 * @param string $token
299
	 * @return string
300
	 */
301
	private function hashToken(string $token): string {
302
		$secret = $this->config->getSystemValue('secret');
303
		return hash('sha512', $token . $secret);
304
	}
305
306
	/**
307
	 * Encrypt the given password
308
	 *
309
	 * The token is used as key
310
	 *
311
	 * @param string $password
312
	 * @param string $token
313
	 * @return string encrypted password
314
	 */
315
	private function encryptPassword(string $password, string $token): string {
316
		$secret = $this->config->getSystemValue('secret');
317
		return $this->crypto->encrypt($password, $token . $secret);
318
	}
319
320
	/**
321
	 * Decrypt the given password
322
	 *
323
	 * The token is used as key
324
	 *
325
	 * @param string $password
326
	 * @param string $token
327
	 * @throws InvalidTokenException
328
	 * @return string the decrypted key
329
	 */
330 View Code Duplication
	private function decryptPassword(string $password, string $token): string {
331
		$secret = $this->config->getSystemValue('secret');
332
		try {
333
			return $this->crypto->decrypt($password, $token . $secret);
334
		} catch (Exception $ex) {
335
			// Delete the invalid token
336
			$this->invalidateToken($token);
337
			throw new InvalidTokenException();
338
		}
339
	}
340
341
	public function markPasswordInvalid(IToken $token, string $tokenId) {
342
		if (!($token instanceof DefaultToken)) {
343
			throw new InvalidTokenException();
344
		}
345
346
		//No need to mark as invalid. We just invalide default tokens
347
		$this->invalidateToken($tokenId);
348
	}
349
350
	public function updatePasswords(string $uid, string $password) {
351
		// Nothing to do here
352
	}
353
}
354