Passed
Push — master ( 0fa17f...c1215f )
by Joas
15:13 queued 13s
created
core/Controller/AppPasswordController.php 2 patches
Indentation   +126 added lines, -126 removed lines patch added patch discarded remove patch
@@ -43,130 +43,130 @@
 block discarded – undo
43 43
 
44 44
 class AppPasswordController extends \OCP\AppFramework\OCSController {
45 45
 
46
-	/** @var ISession */
47
-	private $session;
48
-
49
-	/** @var ISecureRandom */
50
-	private $random;
51
-
52
-	/** @var IProvider */
53
-	private $tokenProvider;
54
-
55
-	/** @var IStore */
56
-	private $credentialStore;
57
-
58
-	/** @var IEventDispatcher */
59
-	private $eventDispatcher;
60
-
61
-	public function __construct(string $appName,
62
-								IRequest $request,
63
-								ISession $session,
64
-								ISecureRandom $random,
65
-								IProvider $tokenProvider,
66
-								IStore $credentialStore,
67
-								IEventDispatcher $eventDispatcher) {
68
-		parent::__construct($appName, $request);
69
-
70
-		$this->session = $session;
71
-		$this->random = $random;
72
-		$this->tokenProvider = $tokenProvider;
73
-		$this->credentialStore = $credentialStore;
74
-		$this->eventDispatcher = $eventDispatcher;
75
-	}
76
-
77
-	/**
78
-	 * @NoAdminRequired
79
-	 *
80
-	 * @return DataResponse
81
-	 * @throws OCSForbiddenException
82
-	 */
83
-	public function getAppPassword(): DataResponse {
84
-		// We do not allow the creation of new tokens if this is an app password
85
-		if ($this->session->exists('app_password')) {
86
-			throw new OCSForbiddenException('You cannot request an new apppassword with an apppassword');
87
-		}
88
-
89
-		try {
90
-			$credentials = $this->credentialStore->getLoginCredentials();
91
-		} catch (CredentialsUnavailableException $e) {
92
-			throw new OCSForbiddenException();
93
-		}
94
-
95
-		try {
96
-			$password = $credentials->getPassword();
97
-		} catch (PasswordUnavailableException $e) {
98
-			$password = null;
99
-		}
100
-
101
-		$userAgent = $this->request->getHeader('USER_AGENT');
102
-		if (mb_strlen($userAgent) > 128) {
103
-			$userAgent = mb_substr($userAgent, 0, 120) . '…';
104
-		}
105
-
106
-		$token = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
107
-
108
-		$generatedToken = $this->tokenProvider->generateToken(
109
-			$token,
110
-			$credentials->getUID(),
111
-			$credentials->getLoginName(),
112
-			$password,
113
-			$userAgent,
114
-			IToken::PERMANENT_TOKEN,
115
-			IToken::DO_NOT_REMEMBER
116
-		);
117
-
118
-		$this->eventDispatcher->dispatchTyped(
119
-			new AppPasswordCreatedEvent($generatedToken)
120
-		);
121
-
122
-		return new DataResponse([
123
-			'apppassword' => $token
124
-		]);
125
-	}
126
-
127
-	/**
128
-	 * @NoAdminRequired
129
-	 *
130
-	 * @return DataResponse
131
-	 */
132
-	public function deleteAppPassword() {
133
-		if (!$this->session->exists('app_password')) {
134
-			throw new OCSForbiddenException('no app password in use');
135
-		}
136
-
137
-		$appPassword = $this->session->get('app_password');
138
-
139
-		try {
140
-			$token = $this->tokenProvider->getToken($appPassword);
141
-		} catch (InvalidTokenException $e) {
142
-			throw new OCSForbiddenException('could not remove apptoken');
143
-		}
144
-
145
-		$this->tokenProvider->invalidateTokenById($token->getUID(), $token->getId());
146
-		return new DataResponse();
147
-	}
148
-
149
-	/**
150
-	 * @NoAdminRequired
151
-	 */
152
-	public function rotateAppPassword(): DataResponse {
153
-		if (!$this->session->exists('app_password')) {
154
-			throw new OCSForbiddenException('no app password in use');
155
-		}
156
-
157
-		$appPassword = $this->session->get('app_password');
158
-
159
-		try {
160
-			$token = $this->tokenProvider->getToken($appPassword);
161
-		} catch (InvalidTokenException $e) {
162
-			throw new OCSForbiddenException('could not rotate apptoken');
163
-		}
164
-
165
-		$newToken = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
166
-		$this->tokenProvider->rotate($token, $appPassword, $newToken);
167
-
168
-		return new DataResponse([
169
-			'apppassword' => $newToken,
170
-		]);
171
-	}
46
+    /** @var ISession */
47
+    private $session;
48
+
49
+    /** @var ISecureRandom */
50
+    private $random;
51
+
52
+    /** @var IProvider */
53
+    private $tokenProvider;
54
+
55
+    /** @var IStore */
56
+    private $credentialStore;
57
+
58
+    /** @var IEventDispatcher */
59
+    private $eventDispatcher;
60
+
61
+    public function __construct(string $appName,
62
+                                IRequest $request,
63
+                                ISession $session,
64
+                                ISecureRandom $random,
65
+                                IProvider $tokenProvider,
66
+                                IStore $credentialStore,
67
+                                IEventDispatcher $eventDispatcher) {
68
+        parent::__construct($appName, $request);
69
+
70
+        $this->session = $session;
71
+        $this->random = $random;
72
+        $this->tokenProvider = $tokenProvider;
73
+        $this->credentialStore = $credentialStore;
74
+        $this->eventDispatcher = $eventDispatcher;
75
+    }
76
+
77
+    /**
78
+     * @NoAdminRequired
79
+     *
80
+     * @return DataResponse
81
+     * @throws OCSForbiddenException
82
+     */
83
+    public function getAppPassword(): DataResponse {
84
+        // We do not allow the creation of new tokens if this is an app password
85
+        if ($this->session->exists('app_password')) {
86
+            throw new OCSForbiddenException('You cannot request an new apppassword with an apppassword');
87
+        }
88
+
89
+        try {
90
+            $credentials = $this->credentialStore->getLoginCredentials();
91
+        } catch (CredentialsUnavailableException $e) {
92
+            throw new OCSForbiddenException();
93
+        }
94
+
95
+        try {
96
+            $password = $credentials->getPassword();
97
+        } catch (PasswordUnavailableException $e) {
98
+            $password = null;
99
+        }
100
+
101
+        $userAgent = $this->request->getHeader('USER_AGENT');
102
+        if (mb_strlen($userAgent) > 128) {
103
+            $userAgent = mb_substr($userAgent, 0, 120) . '…';
104
+        }
105
+
106
+        $token = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
107
+
108
+        $generatedToken = $this->tokenProvider->generateToken(
109
+            $token,
110
+            $credentials->getUID(),
111
+            $credentials->getLoginName(),
112
+            $password,
113
+            $userAgent,
114
+            IToken::PERMANENT_TOKEN,
115
+            IToken::DO_NOT_REMEMBER
116
+        );
117
+
118
+        $this->eventDispatcher->dispatchTyped(
119
+            new AppPasswordCreatedEvent($generatedToken)
120
+        );
121
+
122
+        return new DataResponse([
123
+            'apppassword' => $token
124
+        ]);
125
+    }
126
+
127
+    /**
128
+     * @NoAdminRequired
129
+     *
130
+     * @return DataResponse
131
+     */
132
+    public function deleteAppPassword() {
133
+        if (!$this->session->exists('app_password')) {
134
+            throw new OCSForbiddenException('no app password in use');
135
+        }
136
+
137
+        $appPassword = $this->session->get('app_password');
138
+
139
+        try {
140
+            $token = $this->tokenProvider->getToken($appPassword);
141
+        } catch (InvalidTokenException $e) {
142
+            throw new OCSForbiddenException('could not remove apptoken');
143
+        }
144
+
145
+        $this->tokenProvider->invalidateTokenById($token->getUID(), $token->getId());
146
+        return new DataResponse();
147
+    }
148
+
149
+    /**
150
+     * @NoAdminRequired
151
+     */
152
+    public function rotateAppPassword(): DataResponse {
153
+        if (!$this->session->exists('app_password')) {
154
+            throw new OCSForbiddenException('no app password in use');
155
+        }
156
+
157
+        $appPassword = $this->session->get('app_password');
158
+
159
+        try {
160
+            $token = $this->tokenProvider->getToken($appPassword);
161
+        } catch (InvalidTokenException $e) {
162
+            throw new OCSForbiddenException('could not rotate apptoken');
163
+        }
164
+
165
+        $newToken = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
166
+        $this->tokenProvider->rotate($token, $appPassword, $newToken);
167
+
168
+        return new DataResponse([
169
+            'apppassword' => $newToken,
170
+        ]);
171
+    }
172 172
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -100,7 +100,7 @@
 block discarded – undo
100 100
 
101 101
 		$userAgent = $this->request->getHeader('USER_AGENT');
102 102
 		if (mb_strlen($userAgent) > 128) {
103
-			$userAgent = mb_substr($userAgent, 0, 120) . '…';
103
+			$userAgent = mb_substr($userAgent, 0, 120).'…';
104 104
 		}
105 105
 
106 106
 		$token = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
Please login to merge, or discard this patch.
core/Controller/ClientFlowLoginController.php 2 patches
Indentation   +371 added lines, -371 removed lines patch added patch discarded remove patch
@@ -55,375 +55,375 @@
 block discarded – undo
55 55
 use OCP\Session\Exceptions\SessionNotAvailableException;
56 56
 
57 57
 class ClientFlowLoginController extends Controller {
58
-	/** @var IUserSession */
59
-	private $userSession;
60
-	/** @var IL10N */
61
-	private $l10n;
62
-	/** @var Defaults */
63
-	private $defaults;
64
-	/** @var ISession */
65
-	private $session;
66
-	/** @var IProvider */
67
-	private $tokenProvider;
68
-	/** @var ISecureRandom */
69
-	private $random;
70
-	/** @var IURLGenerator */
71
-	private $urlGenerator;
72
-	/** @var ClientMapper */
73
-	private $clientMapper;
74
-	/** @var AccessTokenMapper */
75
-	private $accessTokenMapper;
76
-	/** @var ICrypto */
77
-	private $crypto;
78
-	/** @var IEventDispatcher */
79
-	private $eventDispatcher;
80
-
81
-	public const STATE_NAME = 'client.flow.state.token';
82
-
83
-	/**
84
-	 * @param string $appName
85
-	 * @param IRequest $request
86
-	 * @param IUserSession $userSession
87
-	 * @param IL10N $l10n
88
-	 * @param Defaults $defaults
89
-	 * @param ISession $session
90
-	 * @param IProvider $tokenProvider
91
-	 * @param ISecureRandom $random
92
-	 * @param IURLGenerator $urlGenerator
93
-	 * @param ClientMapper $clientMapper
94
-	 * @param AccessTokenMapper $accessTokenMapper
95
-	 * @param ICrypto $crypto
96
-	 * @param IEventDispatcher $eventDispatcher
97
-	 */
98
-	public function __construct($appName,
99
-								IRequest $request,
100
-								IUserSession $userSession,
101
-								IL10N $l10n,
102
-								Defaults $defaults,
103
-								ISession $session,
104
-								IProvider $tokenProvider,
105
-								ISecureRandom $random,
106
-								IURLGenerator $urlGenerator,
107
-								ClientMapper $clientMapper,
108
-								AccessTokenMapper $accessTokenMapper,
109
-								ICrypto $crypto,
110
-								IEventDispatcher $eventDispatcher) {
111
-		parent::__construct($appName, $request);
112
-		$this->userSession = $userSession;
113
-		$this->l10n = $l10n;
114
-		$this->defaults = $defaults;
115
-		$this->session = $session;
116
-		$this->tokenProvider = $tokenProvider;
117
-		$this->random = $random;
118
-		$this->urlGenerator = $urlGenerator;
119
-		$this->clientMapper = $clientMapper;
120
-		$this->accessTokenMapper = $accessTokenMapper;
121
-		$this->crypto = $crypto;
122
-		$this->eventDispatcher = $eventDispatcher;
123
-	}
124
-
125
-	/**
126
-	 * @return string
127
-	 */
128
-	private function getClientName() {
129
-		$userAgent = $this->request->getHeader('USER_AGENT');
130
-		return $userAgent !== '' ? $userAgent : 'unknown';
131
-	}
132
-
133
-	/**
134
-	 * @param string $stateToken
135
-	 * @return bool
136
-	 */
137
-	private function isValidToken($stateToken) {
138
-		$currentToken = $this->session->get(self::STATE_NAME);
139
-		if (!is_string($stateToken) || !is_string($currentToken)) {
140
-			return false;
141
-		}
142
-		return hash_equals($currentToken, $stateToken);
143
-	}
144
-
145
-	/**
146
-	 * @return StandaloneTemplateResponse
147
-	 */
148
-	private function stateTokenForbiddenResponse() {
149
-		$response = new StandaloneTemplateResponse(
150
-			$this->appName,
151
-			'403',
152
-			[
153
-				'message' => $this->l10n->t('State token does not match'),
154
-			],
155
-			'guest'
156
-		);
157
-		$response->setStatus(Http::STATUS_FORBIDDEN);
158
-		return $response;
159
-	}
160
-
161
-	/**
162
-	 * @PublicPage
163
-	 * @NoCSRFRequired
164
-	 * @UseSession
165
-	 *
166
-	 * @param string $clientIdentifier
167
-	 *
168
-	 * @return StandaloneTemplateResponse
169
-	 */
170
-	public function showAuthPickerPage($clientIdentifier = '', $user = '') {
171
-		$clientName = $this->getClientName();
172
-		$client = null;
173
-		if ($clientIdentifier !== '') {
174
-			$client = $this->clientMapper->getByIdentifier($clientIdentifier);
175
-			$clientName = $client->getName();
176
-		}
177
-
178
-		// No valid clientIdentifier given and no valid API Request (APIRequest header not set)
179
-		$clientRequest = $this->request->getHeader('OCS-APIREQUEST');
180
-		if ($clientRequest !== 'true' && $client === null) {
181
-			return new StandaloneTemplateResponse(
182
-				$this->appName,
183
-				'error',
184
-				[
185
-					'errors' =>
186
-					[
187
-						[
188
-							'error' => 'Access Forbidden',
189
-							'hint' => 'Invalid request',
190
-						],
191
-					],
192
-				],
193
-				'guest'
194
-			);
195
-		}
196
-
197
-		$stateToken = $this->random->generate(
198
-			64,
199
-			ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
200
-		);
201
-		$this->session->set(self::STATE_NAME, $stateToken);
202
-
203
-		$csp = new Http\ContentSecurityPolicy();
204
-		if ($client) {
205
-			$csp->addAllowedFormActionDomain($client->getRedirectUri());
206
-		} else {
207
-			$csp->addAllowedFormActionDomain('nc://*');
208
-		}
209
-
210
-		$response = new StandaloneTemplateResponse(
211
-			$this->appName,
212
-			'loginflow/authpicker',
213
-			[
214
-				'client' => $clientName,
215
-				'clientIdentifier' => $clientIdentifier,
216
-				'instanceName' => $this->defaults->getName(),
217
-				'urlGenerator' => $this->urlGenerator,
218
-				'stateToken' => $stateToken,
219
-				'serverHost' => $this->getServerPath(),
220
-				'oauthState' => $this->session->get('oauth.state'),
221
-				'user' => $user,
222
-			],
223
-			'guest'
224
-		);
225
-
226
-		$response->setContentSecurityPolicy($csp);
227
-		return $response;
228
-	}
229
-
230
-	/**
231
-	 * @NoAdminRequired
232
-	 * @NoCSRFRequired
233
-	 * @NoSameSiteCookieRequired
234
-	 * @UseSession
235
-	 *
236
-	 * @param string $stateToken
237
-	 * @param string $clientIdentifier
238
-	 * @return StandaloneTemplateResponse
239
-	 */
240
-	public function grantPage($stateToken = '',
241
-								 $clientIdentifier = '') {
242
-		if (!$this->isValidToken($stateToken)) {
243
-			return $this->stateTokenForbiddenResponse();
244
-		}
245
-
246
-		$clientName = $this->getClientName();
247
-		$client = null;
248
-		if ($clientIdentifier !== '') {
249
-			$client = $this->clientMapper->getByIdentifier($clientIdentifier);
250
-			$clientName = $client->getName();
251
-		}
252
-
253
-		$csp = new Http\ContentSecurityPolicy();
254
-		if ($client) {
255
-			$csp->addAllowedFormActionDomain($client->getRedirectUri());
256
-		} else {
257
-			$csp->addAllowedFormActionDomain('nc://*');
258
-		}
259
-
260
-		$response = new StandaloneTemplateResponse(
261
-			$this->appName,
262
-			'loginflow/grant',
263
-			[
264
-				'client' => $clientName,
265
-				'clientIdentifier' => $clientIdentifier,
266
-				'instanceName' => $this->defaults->getName(),
267
-				'urlGenerator' => $this->urlGenerator,
268
-				'stateToken' => $stateToken,
269
-				'serverHost' => $this->getServerPath(),
270
-				'oauthState' => $this->session->get('oauth.state'),
271
-			],
272
-			'guest'
273
-		);
274
-
275
-		$response->setContentSecurityPolicy($csp);
276
-		return $response;
277
-	}
278
-
279
-	/**
280
-	 * @NoAdminRequired
281
-	 * @UseSession
282
-	 *
283
-	 * @param string $stateToken
284
-	 * @param string $clientIdentifier
285
-	 * @return Http\RedirectResponse|Response
286
-	 */
287
-	public function generateAppPassword($stateToken,
288
-										$clientIdentifier = '') {
289
-		if (!$this->isValidToken($stateToken)) {
290
-			$this->session->remove(self::STATE_NAME);
291
-			return $this->stateTokenForbiddenResponse();
292
-		}
293
-
294
-		$this->session->remove(self::STATE_NAME);
295
-
296
-		try {
297
-			$sessionId = $this->session->getId();
298
-		} catch (SessionNotAvailableException $ex) {
299
-			$response = new Response();
300
-			$response->setStatus(Http::STATUS_FORBIDDEN);
301
-			return $response;
302
-		}
303
-
304
-		try {
305
-			$sessionToken = $this->tokenProvider->getToken($sessionId);
306
-			$loginName = $sessionToken->getLoginName();
307
-			try {
308
-				$password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
309
-			} catch (PasswordlessTokenException $ex) {
310
-				$password = null;
311
-			}
312
-		} catch (InvalidTokenException $ex) {
313
-			$response = new Response();
314
-			$response->setStatus(Http::STATUS_FORBIDDEN);
315
-			return $response;
316
-		}
317
-
318
-		$clientName = $this->getClientName();
319
-		$client = false;
320
-		if ($clientIdentifier !== '') {
321
-			$client = $this->clientMapper->getByIdentifier($clientIdentifier);
322
-			$clientName = $client->getName();
323
-		}
324
-
325
-		if (mb_strlen($clientName) > 128) {
326
-			$clientName = mb_substr($clientName, 0, 120) . '…';
327
-		}
328
-
329
-		$token = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
330
-		$uid = $this->userSession->getUser()->getUID();
331
-		$generatedToken = $this->tokenProvider->generateToken(
332
-			$token,
333
-			$uid,
334
-			$loginName,
335
-			$password,
336
-			$clientName,
337
-			IToken::PERMANENT_TOKEN,
338
-			IToken::DO_NOT_REMEMBER
339
-		);
340
-
341
-		if ($client) {
342
-			$code = $this->random->generate(128, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
343
-			$accessToken = new AccessToken();
344
-			$accessToken->setClientId($client->getId());
345
-			$accessToken->setEncryptedToken($this->crypto->encrypt($token, $code));
346
-			$accessToken->setHashedCode(hash('sha512', $code));
347
-			$accessToken->setTokenId($generatedToken->getId());
348
-			$this->accessTokenMapper->insert($accessToken);
349
-
350
-			$redirectUri = $client->getRedirectUri();
351
-
352
-			if (parse_url($redirectUri, PHP_URL_QUERY)) {
353
-				$redirectUri .= '&';
354
-			} else {
355
-				$redirectUri .= '?';
356
-			}
357
-
358
-			$redirectUri .= sprintf(
359
-				'state=%s&code=%s',
360
-				urlencode($this->session->get('oauth.state')),
361
-				urlencode($code)
362
-			);
363
-			$this->session->remove('oauth.state');
364
-		} else {
365
-			$redirectUri = 'nc://login/server:' . $this->getServerPath() . '&user:' . urlencode($loginName) . '&password:' . urlencode($token);
366
-
367
-			// Clear the token from the login here
368
-			$this->tokenProvider->invalidateToken($sessionId);
369
-		}
370
-
371
-		$this->eventDispatcher->dispatchTyped(
372
-			new AppPasswordCreatedEvent($generatedToken)
373
-		);
374
-
375
-		return new Http\RedirectResponse($redirectUri);
376
-	}
377
-
378
-	/**
379
-	 * @PublicPage
380
-	 */
381
-	public function apptokenRedirect(string $stateToken, string $user, string $password) {
382
-		if (!$this->isValidToken($stateToken)) {
383
-			return $this->stateTokenForbiddenResponse();
384
-		}
385
-
386
-		try {
387
-			$token = $this->tokenProvider->getToken($password);
388
-			if ($token->getLoginName() !== $user) {
389
-				throw new InvalidTokenException('login name does not match');
390
-			}
391
-		} catch (InvalidTokenException $e) {
392
-			$response = new StandaloneTemplateResponse(
393
-				$this->appName,
394
-				'403',
395
-				[
396
-					'message' => $this->l10n->t('Invalid app password'),
397
-				],
398
-				'guest'
399
-			);
400
-			$response->setStatus(Http::STATUS_FORBIDDEN);
401
-			return $response;
402
-		}
403
-
404
-		$redirectUri = 'nc://login/server:' . $this->getServerPath() . '&user:' . urlencode($user) . '&password:' . urlencode($password);
405
-		return new Http\RedirectResponse($redirectUri);
406
-	}
407
-
408
-	private function getServerPath(): string {
409
-		$serverPostfix = '';
410
-
411
-		if (strpos($this->request->getRequestUri(), '/index.php') !== false) {
412
-			$serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/index.php'));
413
-		} elseif (strpos($this->request->getRequestUri(), '/login/flow') !== false) {
414
-			$serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/login/flow'));
415
-		}
416
-
417
-		$protocol = $this->request->getServerProtocol();
418
-
419
-		if ($protocol !== "https") {
420
-			$xForwardedProto = $this->request->getHeader('X-Forwarded-Proto');
421
-			$xForwardedSSL = $this->request->getHeader('X-Forwarded-Ssl');
422
-			if ($xForwardedProto === 'https' || $xForwardedSSL === 'on') {
423
-				$protocol = 'https';
424
-			}
425
-		}
426
-
427
-		return $protocol . "://" . $this->request->getServerHost() . $serverPostfix;
428
-	}
58
+    /** @var IUserSession */
59
+    private $userSession;
60
+    /** @var IL10N */
61
+    private $l10n;
62
+    /** @var Defaults */
63
+    private $defaults;
64
+    /** @var ISession */
65
+    private $session;
66
+    /** @var IProvider */
67
+    private $tokenProvider;
68
+    /** @var ISecureRandom */
69
+    private $random;
70
+    /** @var IURLGenerator */
71
+    private $urlGenerator;
72
+    /** @var ClientMapper */
73
+    private $clientMapper;
74
+    /** @var AccessTokenMapper */
75
+    private $accessTokenMapper;
76
+    /** @var ICrypto */
77
+    private $crypto;
78
+    /** @var IEventDispatcher */
79
+    private $eventDispatcher;
80
+
81
+    public const STATE_NAME = 'client.flow.state.token';
82
+
83
+    /**
84
+     * @param string $appName
85
+     * @param IRequest $request
86
+     * @param IUserSession $userSession
87
+     * @param IL10N $l10n
88
+     * @param Defaults $defaults
89
+     * @param ISession $session
90
+     * @param IProvider $tokenProvider
91
+     * @param ISecureRandom $random
92
+     * @param IURLGenerator $urlGenerator
93
+     * @param ClientMapper $clientMapper
94
+     * @param AccessTokenMapper $accessTokenMapper
95
+     * @param ICrypto $crypto
96
+     * @param IEventDispatcher $eventDispatcher
97
+     */
98
+    public function __construct($appName,
99
+                                IRequest $request,
100
+                                IUserSession $userSession,
101
+                                IL10N $l10n,
102
+                                Defaults $defaults,
103
+                                ISession $session,
104
+                                IProvider $tokenProvider,
105
+                                ISecureRandom $random,
106
+                                IURLGenerator $urlGenerator,
107
+                                ClientMapper $clientMapper,
108
+                                AccessTokenMapper $accessTokenMapper,
109
+                                ICrypto $crypto,
110
+                                IEventDispatcher $eventDispatcher) {
111
+        parent::__construct($appName, $request);
112
+        $this->userSession = $userSession;
113
+        $this->l10n = $l10n;
114
+        $this->defaults = $defaults;
115
+        $this->session = $session;
116
+        $this->tokenProvider = $tokenProvider;
117
+        $this->random = $random;
118
+        $this->urlGenerator = $urlGenerator;
119
+        $this->clientMapper = $clientMapper;
120
+        $this->accessTokenMapper = $accessTokenMapper;
121
+        $this->crypto = $crypto;
122
+        $this->eventDispatcher = $eventDispatcher;
123
+    }
124
+
125
+    /**
126
+     * @return string
127
+     */
128
+    private function getClientName() {
129
+        $userAgent = $this->request->getHeader('USER_AGENT');
130
+        return $userAgent !== '' ? $userAgent : 'unknown';
131
+    }
132
+
133
+    /**
134
+     * @param string $stateToken
135
+     * @return bool
136
+     */
137
+    private function isValidToken($stateToken) {
138
+        $currentToken = $this->session->get(self::STATE_NAME);
139
+        if (!is_string($stateToken) || !is_string($currentToken)) {
140
+            return false;
141
+        }
142
+        return hash_equals($currentToken, $stateToken);
143
+    }
144
+
145
+    /**
146
+     * @return StandaloneTemplateResponse
147
+     */
148
+    private function stateTokenForbiddenResponse() {
149
+        $response = new StandaloneTemplateResponse(
150
+            $this->appName,
151
+            '403',
152
+            [
153
+                'message' => $this->l10n->t('State token does not match'),
154
+            ],
155
+            'guest'
156
+        );
157
+        $response->setStatus(Http::STATUS_FORBIDDEN);
158
+        return $response;
159
+    }
160
+
161
+    /**
162
+     * @PublicPage
163
+     * @NoCSRFRequired
164
+     * @UseSession
165
+     *
166
+     * @param string $clientIdentifier
167
+     *
168
+     * @return StandaloneTemplateResponse
169
+     */
170
+    public function showAuthPickerPage($clientIdentifier = '', $user = '') {
171
+        $clientName = $this->getClientName();
172
+        $client = null;
173
+        if ($clientIdentifier !== '') {
174
+            $client = $this->clientMapper->getByIdentifier($clientIdentifier);
175
+            $clientName = $client->getName();
176
+        }
177
+
178
+        // No valid clientIdentifier given and no valid API Request (APIRequest header not set)
179
+        $clientRequest = $this->request->getHeader('OCS-APIREQUEST');
180
+        if ($clientRequest !== 'true' && $client === null) {
181
+            return new StandaloneTemplateResponse(
182
+                $this->appName,
183
+                'error',
184
+                [
185
+                    'errors' =>
186
+                    [
187
+                        [
188
+                            'error' => 'Access Forbidden',
189
+                            'hint' => 'Invalid request',
190
+                        ],
191
+                    ],
192
+                ],
193
+                'guest'
194
+            );
195
+        }
196
+
197
+        $stateToken = $this->random->generate(
198
+            64,
199
+            ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
200
+        );
201
+        $this->session->set(self::STATE_NAME, $stateToken);
202
+
203
+        $csp = new Http\ContentSecurityPolicy();
204
+        if ($client) {
205
+            $csp->addAllowedFormActionDomain($client->getRedirectUri());
206
+        } else {
207
+            $csp->addAllowedFormActionDomain('nc://*');
208
+        }
209
+
210
+        $response = new StandaloneTemplateResponse(
211
+            $this->appName,
212
+            'loginflow/authpicker',
213
+            [
214
+                'client' => $clientName,
215
+                'clientIdentifier' => $clientIdentifier,
216
+                'instanceName' => $this->defaults->getName(),
217
+                'urlGenerator' => $this->urlGenerator,
218
+                'stateToken' => $stateToken,
219
+                'serverHost' => $this->getServerPath(),
220
+                'oauthState' => $this->session->get('oauth.state'),
221
+                'user' => $user,
222
+            ],
223
+            'guest'
224
+        );
225
+
226
+        $response->setContentSecurityPolicy($csp);
227
+        return $response;
228
+    }
229
+
230
+    /**
231
+     * @NoAdminRequired
232
+     * @NoCSRFRequired
233
+     * @NoSameSiteCookieRequired
234
+     * @UseSession
235
+     *
236
+     * @param string $stateToken
237
+     * @param string $clientIdentifier
238
+     * @return StandaloneTemplateResponse
239
+     */
240
+    public function grantPage($stateToken = '',
241
+                                    $clientIdentifier = '') {
242
+        if (!$this->isValidToken($stateToken)) {
243
+            return $this->stateTokenForbiddenResponse();
244
+        }
245
+
246
+        $clientName = $this->getClientName();
247
+        $client = null;
248
+        if ($clientIdentifier !== '') {
249
+            $client = $this->clientMapper->getByIdentifier($clientIdentifier);
250
+            $clientName = $client->getName();
251
+        }
252
+
253
+        $csp = new Http\ContentSecurityPolicy();
254
+        if ($client) {
255
+            $csp->addAllowedFormActionDomain($client->getRedirectUri());
256
+        } else {
257
+            $csp->addAllowedFormActionDomain('nc://*');
258
+        }
259
+
260
+        $response = new StandaloneTemplateResponse(
261
+            $this->appName,
262
+            'loginflow/grant',
263
+            [
264
+                'client' => $clientName,
265
+                'clientIdentifier' => $clientIdentifier,
266
+                'instanceName' => $this->defaults->getName(),
267
+                'urlGenerator' => $this->urlGenerator,
268
+                'stateToken' => $stateToken,
269
+                'serverHost' => $this->getServerPath(),
270
+                'oauthState' => $this->session->get('oauth.state'),
271
+            ],
272
+            'guest'
273
+        );
274
+
275
+        $response->setContentSecurityPolicy($csp);
276
+        return $response;
277
+    }
278
+
279
+    /**
280
+     * @NoAdminRequired
281
+     * @UseSession
282
+     *
283
+     * @param string $stateToken
284
+     * @param string $clientIdentifier
285
+     * @return Http\RedirectResponse|Response
286
+     */
287
+    public function generateAppPassword($stateToken,
288
+                                        $clientIdentifier = '') {
289
+        if (!$this->isValidToken($stateToken)) {
290
+            $this->session->remove(self::STATE_NAME);
291
+            return $this->stateTokenForbiddenResponse();
292
+        }
293
+
294
+        $this->session->remove(self::STATE_NAME);
295
+
296
+        try {
297
+            $sessionId = $this->session->getId();
298
+        } catch (SessionNotAvailableException $ex) {
299
+            $response = new Response();
300
+            $response->setStatus(Http::STATUS_FORBIDDEN);
301
+            return $response;
302
+        }
303
+
304
+        try {
305
+            $sessionToken = $this->tokenProvider->getToken($sessionId);
306
+            $loginName = $sessionToken->getLoginName();
307
+            try {
308
+                $password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
309
+            } catch (PasswordlessTokenException $ex) {
310
+                $password = null;
311
+            }
312
+        } catch (InvalidTokenException $ex) {
313
+            $response = new Response();
314
+            $response->setStatus(Http::STATUS_FORBIDDEN);
315
+            return $response;
316
+        }
317
+
318
+        $clientName = $this->getClientName();
319
+        $client = false;
320
+        if ($clientIdentifier !== '') {
321
+            $client = $this->clientMapper->getByIdentifier($clientIdentifier);
322
+            $clientName = $client->getName();
323
+        }
324
+
325
+        if (mb_strlen($clientName) > 128) {
326
+            $clientName = mb_substr($clientName, 0, 120) . '…';
327
+        }
328
+
329
+        $token = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
330
+        $uid = $this->userSession->getUser()->getUID();
331
+        $generatedToken = $this->tokenProvider->generateToken(
332
+            $token,
333
+            $uid,
334
+            $loginName,
335
+            $password,
336
+            $clientName,
337
+            IToken::PERMANENT_TOKEN,
338
+            IToken::DO_NOT_REMEMBER
339
+        );
340
+
341
+        if ($client) {
342
+            $code = $this->random->generate(128, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
343
+            $accessToken = new AccessToken();
344
+            $accessToken->setClientId($client->getId());
345
+            $accessToken->setEncryptedToken($this->crypto->encrypt($token, $code));
346
+            $accessToken->setHashedCode(hash('sha512', $code));
347
+            $accessToken->setTokenId($generatedToken->getId());
348
+            $this->accessTokenMapper->insert($accessToken);
349
+
350
+            $redirectUri = $client->getRedirectUri();
351
+
352
+            if (parse_url($redirectUri, PHP_URL_QUERY)) {
353
+                $redirectUri .= '&';
354
+            } else {
355
+                $redirectUri .= '?';
356
+            }
357
+
358
+            $redirectUri .= sprintf(
359
+                'state=%s&code=%s',
360
+                urlencode($this->session->get('oauth.state')),
361
+                urlencode($code)
362
+            );
363
+            $this->session->remove('oauth.state');
364
+        } else {
365
+            $redirectUri = 'nc://login/server:' . $this->getServerPath() . '&user:' . urlencode($loginName) . '&password:' . urlencode($token);
366
+
367
+            // Clear the token from the login here
368
+            $this->tokenProvider->invalidateToken($sessionId);
369
+        }
370
+
371
+        $this->eventDispatcher->dispatchTyped(
372
+            new AppPasswordCreatedEvent($generatedToken)
373
+        );
374
+
375
+        return new Http\RedirectResponse($redirectUri);
376
+    }
377
+
378
+    /**
379
+     * @PublicPage
380
+     */
381
+    public function apptokenRedirect(string $stateToken, string $user, string $password) {
382
+        if (!$this->isValidToken($stateToken)) {
383
+            return $this->stateTokenForbiddenResponse();
384
+        }
385
+
386
+        try {
387
+            $token = $this->tokenProvider->getToken($password);
388
+            if ($token->getLoginName() !== $user) {
389
+                throw new InvalidTokenException('login name does not match');
390
+            }
391
+        } catch (InvalidTokenException $e) {
392
+            $response = new StandaloneTemplateResponse(
393
+                $this->appName,
394
+                '403',
395
+                [
396
+                    'message' => $this->l10n->t('Invalid app password'),
397
+                ],
398
+                'guest'
399
+            );
400
+            $response->setStatus(Http::STATUS_FORBIDDEN);
401
+            return $response;
402
+        }
403
+
404
+        $redirectUri = 'nc://login/server:' . $this->getServerPath() . '&user:' . urlencode($user) . '&password:' . urlencode($password);
405
+        return new Http\RedirectResponse($redirectUri);
406
+    }
407
+
408
+    private function getServerPath(): string {
409
+        $serverPostfix = '';
410
+
411
+        if (strpos($this->request->getRequestUri(), '/index.php') !== false) {
412
+            $serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/index.php'));
413
+        } elseif (strpos($this->request->getRequestUri(), '/login/flow') !== false) {
414
+            $serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/login/flow'));
415
+        }
416
+
417
+        $protocol = $this->request->getServerProtocol();
418
+
419
+        if ($protocol !== "https") {
420
+            $xForwardedProto = $this->request->getHeader('X-Forwarded-Proto');
421
+            $xForwardedSSL = $this->request->getHeader('X-Forwarded-Ssl');
422
+            if ($xForwardedProto === 'https' || $xForwardedSSL === 'on') {
423
+                $protocol = 'https';
424
+            }
425
+        }
426
+
427
+        return $protocol . "://" . $this->request->getServerHost() . $serverPostfix;
428
+    }
429 429
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -323,7 +323,7 @@  discard block
 block discarded – undo
323 323
 		}
324 324
 
325 325
 		if (mb_strlen($clientName) > 128) {
326
-			$clientName = mb_substr($clientName, 0, 120) . '…';
326
+			$clientName = mb_substr($clientName, 0, 120).'…';
327 327
 		}
328 328
 
329 329
 		$token = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
@@ -362,7 +362,7 @@  discard block
 block discarded – undo
362 362
 			);
363 363
 			$this->session->remove('oauth.state');
364 364
 		} else {
365
-			$redirectUri = 'nc://login/server:' . $this->getServerPath() . '&user:' . urlencode($loginName) . '&password:' . urlencode($token);
365
+			$redirectUri = 'nc://login/server:'.$this->getServerPath().'&user:'.urlencode($loginName).'&password:'.urlencode($token);
366 366
 
367 367
 			// Clear the token from the login here
368 368
 			$this->tokenProvider->invalidateToken($sessionId);
@@ -401,7 +401,7 @@  discard block
 block discarded – undo
401 401
 			return $response;
402 402
 		}
403 403
 
404
-		$redirectUri = 'nc://login/server:' . $this->getServerPath() . '&user:' . urlencode($user) . '&password:' . urlencode($password);
404
+		$redirectUri = 'nc://login/server:'.$this->getServerPath().'&user:'.urlencode($user).'&password:'.urlencode($password);
405 405
 		return new Http\RedirectResponse($redirectUri);
406 406
 	}
407 407
 
@@ -424,6 +424,6 @@  discard block
 block discarded – undo
424 424
 			}
425 425
 		}
426 426
 
427
-		return $protocol . "://" . $this->request->getServerHost() . $serverPostfix;
427
+		return $protocol."://".$this->request->getServerHost().$serverPostfix;
428 428
 	}
429 429
 }
Please login to merge, or discard this patch.
lib/private/Authentication/Token/PublicKeyTokenProvider.php 1 patch
Indentation   +377 added lines, -377 removed lines patch added patch discarded remove patch
@@ -42,381 +42,381 @@
 block discarded – undo
42 42
 use Psr\Log\LoggerInterface;
43 43
 
44 44
 class PublicKeyTokenProvider implements IProvider {
45
-	/** @var PublicKeyTokenMapper */
46
-	private $mapper;
47
-
48
-	/** @var ICrypto */
49
-	private $crypto;
50
-
51
-	/** @var IConfig */
52
-	private $config;
53
-
54
-	/** @var LoggerInterface */
55
-	private $logger;
56
-
57
-	/** @var ITimeFactory */
58
-	private $time;
59
-
60
-	/** @var CappedMemoryCache */
61
-	private $cache;
62
-
63
-	public function __construct(PublicKeyTokenMapper $mapper,
64
-								ICrypto $crypto,
65
-								IConfig $config,
66
-								LoggerInterface $logger,
67
-								ITimeFactory $time) {
68
-		$this->mapper = $mapper;
69
-		$this->crypto = $crypto;
70
-		$this->config = $config;
71
-		$this->logger = $logger;
72
-		$this->time = $time;
73
-
74
-		$this->cache = new CappedMemoryCache();
75
-	}
76
-
77
-	/**
78
-	 * {@inheritDoc}
79
-	 */
80
-	public function generateToken(string $token,
81
-								  string $uid,
82
-								  string $loginName,
83
-								  ?string $password,
84
-								  string $name,
85
-								  int $type = IToken::TEMPORARY_TOKEN,
86
-								  int $remember = IToken::DO_NOT_REMEMBER): IToken {
87
-		if (mb_strlen($name) > 128) {
88
-			throw new InvalidTokenException('The given name is too long');
89
-		}
90
-
91
-		$dbToken = $this->newToken($token, $uid, $loginName, $password, $name, $type, $remember);
92
-		$this->mapper->insert($dbToken);
93
-
94
-		// Add the token to the cache
95
-		$this->cache[$dbToken->getToken()] = $dbToken;
96
-
97
-		return $dbToken;
98
-	}
99
-
100
-	public function getToken(string $tokenId): IToken {
101
-		$tokenHash = $this->hashToken($tokenId);
102
-
103
-		if (isset($this->cache[$tokenHash])) {
104
-			if ($this->cache[$tokenHash] instanceof DoesNotExistException) {
105
-				$ex = $this->cache[$tokenHash];
106
-				throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
107
-			}
108
-			$token = $this->cache[$tokenHash];
109
-		} else {
110
-			try {
111
-				$token = $this->mapper->getToken($this->hashToken($tokenId));
112
-				$this->cache[$token->getToken()] = $token;
113
-			} catch (DoesNotExistException $ex) {
114
-				$this->cache[$tokenHash] = $ex;
115
-				throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
116
-			}
117
-		}
118
-
119
-		if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
120
-			throw new ExpiredTokenException($token);
121
-		}
122
-
123
-		if ($token->getType() === IToken::WIPE_TOKEN) {
124
-			throw new WipeTokenException($token);
125
-		}
126
-
127
-		if ($token->getPasswordInvalid() === true) {
128
-			//The password is invalid we should throw an TokenPasswordExpiredException
129
-			throw new TokenPasswordExpiredException($token);
130
-		}
131
-
132
-		return $token;
133
-	}
134
-
135
-	public function getTokenById(int $tokenId): IToken {
136
-		try {
137
-			$token = $this->mapper->getTokenById($tokenId);
138
-		} catch (DoesNotExistException $ex) {
139
-			throw new InvalidTokenException("Token with ID $tokenId does not exist: " . $ex->getMessage(), 0, $ex);
140
-		}
141
-
142
-		if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
143
-			throw new ExpiredTokenException($token);
144
-		}
145
-
146
-		if ($token->getType() === IToken::WIPE_TOKEN) {
147
-			throw new WipeTokenException($token);
148
-		}
149
-
150
-		if ($token->getPasswordInvalid() === true) {
151
-			//The password is invalid we should throw an TokenPasswordExpiredException
152
-			throw new TokenPasswordExpiredException($token);
153
-		}
154
-
155
-		return $token;
156
-	}
157
-
158
-	public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
159
-		$this->cache->clear();
160
-
161
-		$token = $this->getToken($oldSessionId);
162
-
163
-		if (!($token instanceof PublicKeyToken)) {
164
-			throw new InvalidTokenException("Invalid token type");
165
-		}
166
-
167
-		$password = null;
168
-		if (!is_null($token->getPassword())) {
169
-			$privateKey = $this->decrypt($token->getPrivateKey(), $oldSessionId);
170
-			$password = $this->decryptPassword($token->getPassword(), $privateKey);
171
-		}
172
-
173
-		$newToken = $this->generateToken(
174
-			$sessionId,
175
-			$token->getUID(),
176
-			$token->getLoginName(),
177
-			$password,
178
-			$token->getName(),
179
-			IToken::TEMPORARY_TOKEN,
180
-			$token->getRemember()
181
-		);
182
-
183
-		$this->mapper->delete($token);
184
-
185
-		return $newToken;
186
-	}
187
-
188
-	public function invalidateToken(string $token) {
189
-		$this->cache->clear();
190
-
191
-		$this->mapper->invalidate($this->hashToken($token));
192
-	}
193
-
194
-	public function invalidateTokenById(string $uid, int $id) {
195
-		$this->cache->clear();
196
-
197
-		$this->mapper->deleteById($uid, $id);
198
-	}
199
-
200
-	public function invalidateOldTokens() {
201
-		$this->cache->clear();
202
-
203
-		$olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24);
204
-		$this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
205
-		$this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER);
206
-		$rememberThreshold = $this->time->getTime() - (int) $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
207
-		$this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']);
208
-		$this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER);
209
-	}
210
-
211
-	public function updateToken(IToken $token) {
212
-		$this->cache->clear();
213
-
214
-		if (!($token instanceof PublicKeyToken)) {
215
-			throw new InvalidTokenException("Invalid token type");
216
-		}
217
-		$this->mapper->update($token);
218
-	}
219
-
220
-	public function updateTokenActivity(IToken $token) {
221
-		$this->cache->clear();
222
-
223
-		if (!($token instanceof PublicKeyToken)) {
224
-			throw new InvalidTokenException("Invalid token type");
225
-		}
226
-
227
-		$activityInterval = $this->config->getSystemValueInt('token_auth_activity_update', 60);
228
-		$activityInterval = min(max($activityInterval, 0), 300);
229
-
230
-		/** @var PublicKeyToken $token */
231
-		$now = $this->time->getTime();
232
-		if ($token->getLastActivity() < ($now - $activityInterval)) {
233
-			$token->setLastActivity($now);
234
-			$this->mapper->updateActivity($token, $now);
235
-		}
236
-	}
237
-
238
-	public function getTokenByUser(string $uid): array {
239
-		return $this->mapper->getTokenByUser($uid);
240
-	}
241
-
242
-	public function getPassword(IToken $savedToken, string $tokenId): string {
243
-		if (!($savedToken instanceof PublicKeyToken)) {
244
-			throw new InvalidTokenException("Invalid token type");
245
-		}
246
-
247
-		if ($savedToken->getPassword() === null) {
248
-			throw new PasswordlessTokenException();
249
-		}
250
-
251
-		// Decrypt private key with tokenId
252
-		$privateKey = $this->decrypt($savedToken->getPrivateKey(), $tokenId);
253
-
254
-		// Decrypt password with private key
255
-		return $this->decryptPassword($savedToken->getPassword(), $privateKey);
256
-	}
257
-
258
-	public function setPassword(IToken $token, string $tokenId, string $password) {
259
-		$this->cache->clear();
260
-
261
-		if (!($token instanceof PublicKeyToken)) {
262
-			throw new InvalidTokenException("Invalid token type");
263
-		}
264
-
265
-		// When changing passwords all temp tokens are deleted
266
-		$this->mapper->deleteTempToken($token);
267
-
268
-		// Update the password for all tokens
269
-		$tokens = $this->mapper->getTokenByUser($token->getUID());
270
-		foreach ($tokens as $t) {
271
-			$publicKey = $t->getPublicKey();
272
-			$t->setPassword($this->encryptPassword($password, $publicKey));
273
-			$this->updateToken($t);
274
-		}
275
-	}
276
-
277
-	public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
278
-		$this->cache->clear();
279
-
280
-		if (!($token instanceof PublicKeyToken)) {
281
-			throw new InvalidTokenException("Invalid token type");
282
-		}
283
-
284
-		// Decrypt private key with oldTokenId
285
-		$privateKey = $this->decrypt($token->getPrivateKey(), $oldTokenId);
286
-		// Encrypt with the new token
287
-		$token->setPrivateKey($this->encrypt($privateKey, $newTokenId));
288
-
289
-		$token->setToken($this->hashToken($newTokenId));
290
-		$this->updateToken($token);
291
-
292
-		return $token;
293
-	}
294
-
295
-	private function encrypt(string $plaintext, string $token): string {
296
-		$secret = $this->config->getSystemValue('secret');
297
-		return $this->crypto->encrypt($plaintext, $token . $secret);
298
-	}
299
-
300
-	/**
301
-	 * @throws InvalidTokenException
302
-	 */
303
-	private function decrypt(string $cipherText, string $token): string {
304
-		$secret = $this->config->getSystemValue('secret');
305
-		try {
306
-			return $this->crypto->decrypt($cipherText, $token . $secret);
307
-		} catch (\Exception $ex) {
308
-			// Delete the invalid token
309
-			$this->invalidateToken($token);
310
-			throw new InvalidTokenException("Could not decrypt token password: " . $ex->getMessage(), 0, $ex);
311
-		}
312
-	}
313
-
314
-	private function encryptPassword(string $password, string $publicKey): string {
315
-		openssl_public_encrypt($password, $encryptedPassword, $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
316
-		$encryptedPassword = base64_encode($encryptedPassword);
317
-
318
-		return $encryptedPassword;
319
-	}
320
-
321
-	private function decryptPassword(string $encryptedPassword, string $privateKey): string {
322
-		$encryptedPassword = base64_decode($encryptedPassword);
323
-		openssl_private_decrypt($encryptedPassword, $password, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);
324
-
325
-		return $password;
326
-	}
327
-
328
-	private function hashToken(string $token): string {
329
-		$secret = $this->config->getSystemValue('secret');
330
-		return hash('sha512', $token . $secret);
331
-	}
332
-
333
-	/**
334
-	 * @throws \RuntimeException when OpenSSL reports a problem
335
-	 */
336
-	private function newToken(string $token,
337
-							  string $uid,
338
-							  string $loginName,
339
-							  $password,
340
-							  string $name,
341
-							  int $type,
342
-							  int $remember): PublicKeyToken {
343
-		$dbToken = new PublicKeyToken();
344
-		$dbToken->setUid($uid);
345
-		$dbToken->setLoginName($loginName);
346
-
347
-		$config = array_merge([
348
-			'digest_alg' => 'sha512',
349
-			'private_key_bits' => 2048,
350
-		], $this->config->getSystemValue('openssl', []));
351
-
352
-		// Generate new key
353
-		$res = openssl_pkey_new($config);
354
-		if ($res === false) {
355
-			$this->logOpensslError();
356
-			throw new \RuntimeException('OpenSSL reported a problem');
357
-		}
358
-
359
-		if (openssl_pkey_export($res, $privateKey, null, $config) === false) {
360
-			$this->logOpensslError();
361
-			throw new \RuntimeException('OpenSSL reported a problem');
362
-		}
363
-
364
-		// Extract the public key from $res to $pubKey
365
-		$publicKey = openssl_pkey_get_details($res);
366
-		$publicKey = $publicKey['key'];
367
-
368
-		$dbToken->setPublicKey($publicKey);
369
-		$dbToken->setPrivateKey($this->encrypt($privateKey, $token));
370
-
371
-		if (!is_null($password)) {
372
-			$dbToken->setPassword($this->encryptPassword($password, $publicKey));
373
-		}
374
-
375
-		$dbToken->setName($name);
376
-		$dbToken->setToken($this->hashToken($token));
377
-		$dbToken->setType($type);
378
-		$dbToken->setRemember($remember);
379
-		$dbToken->setLastActivity($this->time->getTime());
380
-		$dbToken->setLastCheck($this->time->getTime());
381
-		$dbToken->setVersion(PublicKeyToken::VERSION);
382
-
383
-		return $dbToken;
384
-	}
385
-
386
-	public function markPasswordInvalid(IToken $token, string $tokenId) {
387
-		$this->cache->clear();
388
-
389
-		if (!($token instanceof PublicKeyToken)) {
390
-			throw new InvalidTokenException("Invalid token type");
391
-		}
392
-
393
-		$token->setPasswordInvalid(true);
394
-		$this->mapper->update($token);
395
-	}
396
-
397
-	public function updatePasswords(string $uid, string $password) {
398
-		$this->cache->clear();
399
-
400
-		// prevent setting an empty pw as result of pw-less-login
401
-		if ($password === '') {
402
-			return;
403
-		}
404
-
405
-		// Update the password for all tokens
406
-		$tokens = $this->mapper->getTokenByUser($uid);
407
-		foreach ($tokens as $t) {
408
-			$publicKey = $t->getPublicKey();
409
-			$t->setPassword($this->encryptPassword($password, $publicKey));
410
-			$t->setPasswordInvalid(false);
411
-			$this->updateToken($t);
412
-		}
413
-	}
414
-
415
-	private function logOpensslError() {
416
-		$errors = [];
417
-		while ($error = openssl_error_string()) {
418
-			$errors[] = $error;
419
-		}
420
-		$this->logger->critical('Something is wrong with your openssl setup: ' . implode(', ', $errors));
421
-	}
45
+    /** @var PublicKeyTokenMapper */
46
+    private $mapper;
47
+
48
+    /** @var ICrypto */
49
+    private $crypto;
50
+
51
+    /** @var IConfig */
52
+    private $config;
53
+
54
+    /** @var LoggerInterface */
55
+    private $logger;
56
+
57
+    /** @var ITimeFactory */
58
+    private $time;
59
+
60
+    /** @var CappedMemoryCache */
61
+    private $cache;
62
+
63
+    public function __construct(PublicKeyTokenMapper $mapper,
64
+                                ICrypto $crypto,
65
+                                IConfig $config,
66
+                                LoggerInterface $logger,
67
+                                ITimeFactory $time) {
68
+        $this->mapper = $mapper;
69
+        $this->crypto = $crypto;
70
+        $this->config = $config;
71
+        $this->logger = $logger;
72
+        $this->time = $time;
73
+
74
+        $this->cache = new CappedMemoryCache();
75
+    }
76
+
77
+    /**
78
+     * {@inheritDoc}
79
+     */
80
+    public function generateToken(string $token,
81
+                                    string $uid,
82
+                                    string $loginName,
83
+                                  ?string $password,
84
+                                    string $name,
85
+                                    int $type = IToken::TEMPORARY_TOKEN,
86
+                                    int $remember = IToken::DO_NOT_REMEMBER): IToken {
87
+        if (mb_strlen($name) > 128) {
88
+            throw new InvalidTokenException('The given name is too long');
89
+        }
90
+
91
+        $dbToken = $this->newToken($token, $uid, $loginName, $password, $name, $type, $remember);
92
+        $this->mapper->insert($dbToken);
93
+
94
+        // Add the token to the cache
95
+        $this->cache[$dbToken->getToken()] = $dbToken;
96
+
97
+        return $dbToken;
98
+    }
99
+
100
+    public function getToken(string $tokenId): IToken {
101
+        $tokenHash = $this->hashToken($tokenId);
102
+
103
+        if (isset($this->cache[$tokenHash])) {
104
+            if ($this->cache[$tokenHash] instanceof DoesNotExistException) {
105
+                $ex = $this->cache[$tokenHash];
106
+                throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
107
+            }
108
+            $token = $this->cache[$tokenHash];
109
+        } else {
110
+            try {
111
+                $token = $this->mapper->getToken($this->hashToken($tokenId));
112
+                $this->cache[$token->getToken()] = $token;
113
+            } catch (DoesNotExistException $ex) {
114
+                $this->cache[$tokenHash] = $ex;
115
+                throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
116
+            }
117
+        }
118
+
119
+        if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
120
+            throw new ExpiredTokenException($token);
121
+        }
122
+
123
+        if ($token->getType() === IToken::WIPE_TOKEN) {
124
+            throw new WipeTokenException($token);
125
+        }
126
+
127
+        if ($token->getPasswordInvalid() === true) {
128
+            //The password is invalid we should throw an TokenPasswordExpiredException
129
+            throw new TokenPasswordExpiredException($token);
130
+        }
131
+
132
+        return $token;
133
+    }
134
+
135
+    public function getTokenById(int $tokenId): IToken {
136
+        try {
137
+            $token = $this->mapper->getTokenById($tokenId);
138
+        } catch (DoesNotExistException $ex) {
139
+            throw new InvalidTokenException("Token with ID $tokenId does not exist: " . $ex->getMessage(), 0, $ex);
140
+        }
141
+
142
+        if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
143
+            throw new ExpiredTokenException($token);
144
+        }
145
+
146
+        if ($token->getType() === IToken::WIPE_TOKEN) {
147
+            throw new WipeTokenException($token);
148
+        }
149
+
150
+        if ($token->getPasswordInvalid() === true) {
151
+            //The password is invalid we should throw an TokenPasswordExpiredException
152
+            throw new TokenPasswordExpiredException($token);
153
+        }
154
+
155
+        return $token;
156
+    }
157
+
158
+    public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
159
+        $this->cache->clear();
160
+
161
+        $token = $this->getToken($oldSessionId);
162
+
163
+        if (!($token instanceof PublicKeyToken)) {
164
+            throw new InvalidTokenException("Invalid token type");
165
+        }
166
+
167
+        $password = null;
168
+        if (!is_null($token->getPassword())) {
169
+            $privateKey = $this->decrypt($token->getPrivateKey(), $oldSessionId);
170
+            $password = $this->decryptPassword($token->getPassword(), $privateKey);
171
+        }
172
+
173
+        $newToken = $this->generateToken(
174
+            $sessionId,
175
+            $token->getUID(),
176
+            $token->getLoginName(),
177
+            $password,
178
+            $token->getName(),
179
+            IToken::TEMPORARY_TOKEN,
180
+            $token->getRemember()
181
+        );
182
+
183
+        $this->mapper->delete($token);
184
+
185
+        return $newToken;
186
+    }
187
+
188
+    public function invalidateToken(string $token) {
189
+        $this->cache->clear();
190
+
191
+        $this->mapper->invalidate($this->hashToken($token));
192
+    }
193
+
194
+    public function invalidateTokenById(string $uid, int $id) {
195
+        $this->cache->clear();
196
+
197
+        $this->mapper->deleteById($uid, $id);
198
+    }
199
+
200
+    public function invalidateOldTokens() {
201
+        $this->cache->clear();
202
+
203
+        $olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24);
204
+        $this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
205
+        $this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER);
206
+        $rememberThreshold = $this->time->getTime() - (int) $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
207
+        $this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']);
208
+        $this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER);
209
+    }
210
+
211
+    public function updateToken(IToken $token) {
212
+        $this->cache->clear();
213
+
214
+        if (!($token instanceof PublicKeyToken)) {
215
+            throw new InvalidTokenException("Invalid token type");
216
+        }
217
+        $this->mapper->update($token);
218
+    }
219
+
220
+    public function updateTokenActivity(IToken $token) {
221
+        $this->cache->clear();
222
+
223
+        if (!($token instanceof PublicKeyToken)) {
224
+            throw new InvalidTokenException("Invalid token type");
225
+        }
226
+
227
+        $activityInterval = $this->config->getSystemValueInt('token_auth_activity_update', 60);
228
+        $activityInterval = min(max($activityInterval, 0), 300);
229
+
230
+        /** @var PublicKeyToken $token */
231
+        $now = $this->time->getTime();
232
+        if ($token->getLastActivity() < ($now - $activityInterval)) {
233
+            $token->setLastActivity($now);
234
+            $this->mapper->updateActivity($token, $now);
235
+        }
236
+    }
237
+
238
+    public function getTokenByUser(string $uid): array {
239
+        return $this->mapper->getTokenByUser($uid);
240
+    }
241
+
242
+    public function getPassword(IToken $savedToken, string $tokenId): string {
243
+        if (!($savedToken instanceof PublicKeyToken)) {
244
+            throw new InvalidTokenException("Invalid token type");
245
+        }
246
+
247
+        if ($savedToken->getPassword() === null) {
248
+            throw new PasswordlessTokenException();
249
+        }
250
+
251
+        // Decrypt private key with tokenId
252
+        $privateKey = $this->decrypt($savedToken->getPrivateKey(), $tokenId);
253
+
254
+        // Decrypt password with private key
255
+        return $this->decryptPassword($savedToken->getPassword(), $privateKey);
256
+    }
257
+
258
+    public function setPassword(IToken $token, string $tokenId, string $password) {
259
+        $this->cache->clear();
260
+
261
+        if (!($token instanceof PublicKeyToken)) {
262
+            throw new InvalidTokenException("Invalid token type");
263
+        }
264
+
265
+        // When changing passwords all temp tokens are deleted
266
+        $this->mapper->deleteTempToken($token);
267
+
268
+        // Update the password for all tokens
269
+        $tokens = $this->mapper->getTokenByUser($token->getUID());
270
+        foreach ($tokens as $t) {
271
+            $publicKey = $t->getPublicKey();
272
+            $t->setPassword($this->encryptPassword($password, $publicKey));
273
+            $this->updateToken($t);
274
+        }
275
+    }
276
+
277
+    public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
278
+        $this->cache->clear();
279
+
280
+        if (!($token instanceof PublicKeyToken)) {
281
+            throw new InvalidTokenException("Invalid token type");
282
+        }
283
+
284
+        // Decrypt private key with oldTokenId
285
+        $privateKey = $this->decrypt($token->getPrivateKey(), $oldTokenId);
286
+        // Encrypt with the new token
287
+        $token->setPrivateKey($this->encrypt($privateKey, $newTokenId));
288
+
289
+        $token->setToken($this->hashToken($newTokenId));
290
+        $this->updateToken($token);
291
+
292
+        return $token;
293
+    }
294
+
295
+    private function encrypt(string $plaintext, string $token): string {
296
+        $secret = $this->config->getSystemValue('secret');
297
+        return $this->crypto->encrypt($plaintext, $token . $secret);
298
+    }
299
+
300
+    /**
301
+     * @throws InvalidTokenException
302
+     */
303
+    private function decrypt(string $cipherText, string $token): string {
304
+        $secret = $this->config->getSystemValue('secret');
305
+        try {
306
+            return $this->crypto->decrypt($cipherText, $token . $secret);
307
+        } catch (\Exception $ex) {
308
+            // Delete the invalid token
309
+            $this->invalidateToken($token);
310
+            throw new InvalidTokenException("Could not decrypt token password: " . $ex->getMessage(), 0, $ex);
311
+        }
312
+    }
313
+
314
+    private function encryptPassword(string $password, string $publicKey): string {
315
+        openssl_public_encrypt($password, $encryptedPassword, $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
316
+        $encryptedPassword = base64_encode($encryptedPassword);
317
+
318
+        return $encryptedPassword;
319
+    }
320
+
321
+    private function decryptPassword(string $encryptedPassword, string $privateKey): string {
322
+        $encryptedPassword = base64_decode($encryptedPassword);
323
+        openssl_private_decrypt($encryptedPassword, $password, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);
324
+
325
+        return $password;
326
+    }
327
+
328
+    private function hashToken(string $token): string {
329
+        $secret = $this->config->getSystemValue('secret');
330
+        return hash('sha512', $token . $secret);
331
+    }
332
+
333
+    /**
334
+     * @throws \RuntimeException when OpenSSL reports a problem
335
+     */
336
+    private function newToken(string $token,
337
+                                string $uid,
338
+                                string $loginName,
339
+                                $password,
340
+                                string $name,
341
+                                int $type,
342
+                                int $remember): PublicKeyToken {
343
+        $dbToken = new PublicKeyToken();
344
+        $dbToken->setUid($uid);
345
+        $dbToken->setLoginName($loginName);
346
+
347
+        $config = array_merge([
348
+            'digest_alg' => 'sha512',
349
+            'private_key_bits' => 2048,
350
+        ], $this->config->getSystemValue('openssl', []));
351
+
352
+        // Generate new key
353
+        $res = openssl_pkey_new($config);
354
+        if ($res === false) {
355
+            $this->logOpensslError();
356
+            throw new \RuntimeException('OpenSSL reported a problem');
357
+        }
358
+
359
+        if (openssl_pkey_export($res, $privateKey, null, $config) === false) {
360
+            $this->logOpensslError();
361
+            throw new \RuntimeException('OpenSSL reported a problem');
362
+        }
363
+
364
+        // Extract the public key from $res to $pubKey
365
+        $publicKey = openssl_pkey_get_details($res);
366
+        $publicKey = $publicKey['key'];
367
+
368
+        $dbToken->setPublicKey($publicKey);
369
+        $dbToken->setPrivateKey($this->encrypt($privateKey, $token));
370
+
371
+        if (!is_null($password)) {
372
+            $dbToken->setPassword($this->encryptPassword($password, $publicKey));
373
+        }
374
+
375
+        $dbToken->setName($name);
376
+        $dbToken->setToken($this->hashToken($token));
377
+        $dbToken->setType($type);
378
+        $dbToken->setRemember($remember);
379
+        $dbToken->setLastActivity($this->time->getTime());
380
+        $dbToken->setLastCheck($this->time->getTime());
381
+        $dbToken->setVersion(PublicKeyToken::VERSION);
382
+
383
+        return $dbToken;
384
+    }
385
+
386
+    public function markPasswordInvalid(IToken $token, string $tokenId) {
387
+        $this->cache->clear();
388
+
389
+        if (!($token instanceof PublicKeyToken)) {
390
+            throw new InvalidTokenException("Invalid token type");
391
+        }
392
+
393
+        $token->setPasswordInvalid(true);
394
+        $this->mapper->update($token);
395
+    }
396
+
397
+    public function updatePasswords(string $uid, string $password) {
398
+        $this->cache->clear();
399
+
400
+        // prevent setting an empty pw as result of pw-less-login
401
+        if ($password === '') {
402
+            return;
403
+        }
404
+
405
+        // Update the password for all tokens
406
+        $tokens = $this->mapper->getTokenByUser($uid);
407
+        foreach ($tokens as $t) {
408
+            $publicKey = $t->getPublicKey();
409
+            $t->setPassword($this->encryptPassword($password, $publicKey));
410
+            $t->setPasswordInvalid(false);
411
+            $this->updateToken($t);
412
+        }
413
+    }
414
+
415
+    private function logOpensslError() {
416
+        $errors = [];
417
+        while ($error = openssl_error_string()) {
418
+            $errors[] = $error;
419
+        }
420
+        $this->logger->critical('Something is wrong with your openssl setup: ' . implode(', ', $errors));
421
+    }
422 422
 }
Please login to merge, or discard this patch.
lib/private/Authentication/Token/Manager.php 1 patch
Indentation   +205 added lines, -205 removed lines patch added patch discarded remove patch
@@ -35,209 +35,209 @@
 block discarded – undo
35 35
 
36 36
 class Manager implements IProvider {
37 37
 
38
-	/** @var PublicKeyTokenProvider */
39
-	private $publicKeyTokenProvider;
40
-
41
-	public function __construct(PublicKeyTokenProvider $publicKeyTokenProvider) {
42
-		$this->publicKeyTokenProvider = $publicKeyTokenProvider;
43
-	}
44
-
45
-	/**
46
-	 * Create and persist a new token
47
-	 *
48
-	 * @param string $token
49
-	 * @param string $uid
50
-	 * @param string $loginName
51
-	 * @param string|null $password
52
-	 * @param string $name
53
-	 * @param int $type token type
54
-	 * @param int $remember whether the session token should be used for remember-me
55
-	 * @return IToken
56
-	 */
57
-	public function generateToken(string $token,
58
-								  string $uid,
59
-								  string $loginName,
60
-								  $password,
61
-								  string $name,
62
-								  int $type = IToken::TEMPORARY_TOKEN,
63
-								  int $remember = IToken::DO_NOT_REMEMBER): IToken {
64
-		if (mb_strlen($name) > 128) {
65
-			throw new InvalidTokenException('The given name is too long');
66
-		}
67
-
68
-		try {
69
-			return $this->publicKeyTokenProvider->generateToken(
70
-				$token,
71
-				$uid,
72
-				$loginName,
73
-				$password,
74
-				$name,
75
-				$type,
76
-				$remember
77
-			);
78
-		} catch (UniqueConstraintViolationException $e) {
79
-			// It's rare, but if two requests of the same session (e.g. env-based SAML)
80
-			// try to create the session token they might end up here at the same time
81
-			// because we use the session ID as token and the db token is created anew
82
-			// with every request.
83
-			//
84
-			// If the UIDs match, then this should be fine.
85
-			$existing = $this->getToken($token);
86
-			if ($existing->getUID() !== $uid) {
87
-				throw new \Exception('Token conflict handled, but UIDs do not match. This should not happen', 0, $e);
88
-			}
89
-			return $existing;
90
-		}
91
-	}
92
-
93
-	/**
94
-	 * Save the updated token
95
-	 *
96
-	 * @param IToken $token
97
-	 * @throws InvalidTokenException
98
-	 */
99
-	public function updateToken(IToken $token) {
100
-		$provider = $this->getProvider($token);
101
-		$provider->updateToken($token);
102
-	}
103
-
104
-	/**
105
-	 * Update token activity timestamp
106
-	 *
107
-	 * @throws InvalidTokenException
108
-	 * @param IToken $token
109
-	 */
110
-	public function updateTokenActivity(IToken $token) {
111
-		$provider = $this->getProvider($token);
112
-		$provider->updateTokenActivity($token);
113
-	}
114
-
115
-	/**
116
-	 * @param string $uid
117
-	 * @return IToken[]
118
-	 */
119
-	public function getTokenByUser(string $uid): array {
120
-		return $this->publicKeyTokenProvider->getTokenByUser($uid);
121
-	}
122
-
123
-	/**
124
-	 * Get a token by token
125
-	 *
126
-	 * @param string $tokenId
127
-	 * @throws InvalidTokenException
128
-	 * @throws \RuntimeException when OpenSSL reports a problem
129
-	 * @return IToken
130
-	 */
131
-	public function getToken(string $tokenId): IToken {
132
-		try {
133
-			return $this->publicKeyTokenProvider->getToken($tokenId);
134
-		} catch (WipeTokenException $e) {
135
-			throw $e;
136
-		} catch (ExpiredTokenException $e) {
137
-			throw $e;
138
-		} catch (InvalidTokenException $e) {
139
-			throw $e;
140
-		}
141
-	}
142
-
143
-	/**
144
-	 * Get a token by token id
145
-	 *
146
-	 * @param int $tokenId
147
-	 * @throws InvalidTokenException
148
-	 * @return IToken
149
-	 */
150
-	public function getTokenById(int $tokenId): IToken {
151
-		try {
152
-			return $this->publicKeyTokenProvider->getTokenById($tokenId);
153
-		} catch (ExpiredTokenException $e) {
154
-			throw $e;
155
-		} catch (WipeTokenException $e) {
156
-			throw $e;
157
-		} catch (InvalidTokenException $e) {
158
-			throw $e;
159
-		}
160
-	}
161
-
162
-	/**
163
-	 * @param string $oldSessionId
164
-	 * @param string $sessionId
165
-	 * @throws InvalidTokenException
166
-	 * @return IToken
167
-	 */
168
-	public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
169
-		try {
170
-			return $this->publicKeyTokenProvider->renewSessionToken($oldSessionId, $sessionId);
171
-		} catch (ExpiredTokenException $e) {
172
-			throw $e;
173
-		} catch (InvalidTokenException $e) {
174
-			throw $e;
175
-		}
176
-	}
177
-
178
-	/**
179
-	 * @param IToken $savedToken
180
-	 * @param string $tokenId session token
181
-	 * @throws InvalidTokenException
182
-	 * @throws PasswordlessTokenException
183
-	 * @return string
184
-	 */
185
-	public function getPassword(IToken $savedToken, string $tokenId): string {
186
-		$provider = $this->getProvider($savedToken);
187
-		return $provider->getPassword($savedToken, $tokenId);
188
-	}
189
-
190
-	public function setPassword(IToken $token, string $tokenId, string $password) {
191
-		$provider = $this->getProvider($token);
192
-		$provider->setPassword($token, $tokenId, $password);
193
-	}
194
-
195
-	public function invalidateToken(string $token) {
196
-		$this->publicKeyTokenProvider->invalidateToken($token);
197
-	}
198
-
199
-	public function invalidateTokenById(string $uid, int $id) {
200
-		$this->publicKeyTokenProvider->invalidateTokenById($uid, $id);
201
-	}
202
-
203
-	public function invalidateOldTokens() {
204
-		$this->publicKeyTokenProvider->invalidateOldTokens();
205
-	}
206
-
207
-	/**
208
-	 * @param IToken $token
209
-	 * @param string $oldTokenId
210
-	 * @param string $newTokenId
211
-	 * @return IToken
212
-	 * @throws InvalidTokenException
213
-	 * @throws \RuntimeException when OpenSSL reports a problem
214
-	 */
215
-	public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
216
-		if ($token instanceof PublicKeyToken) {
217
-			return $this->publicKeyTokenProvider->rotate($token, $oldTokenId, $newTokenId);
218
-		}
219
-
220
-		throw new InvalidTokenException();
221
-	}
222
-
223
-	/**
224
-	 * @param IToken $token
225
-	 * @return IProvider
226
-	 * @throws InvalidTokenException
227
-	 */
228
-	private function getProvider(IToken $token): IProvider {
229
-		if ($token instanceof PublicKeyToken) {
230
-			return $this->publicKeyTokenProvider;
231
-		}
232
-		throw new InvalidTokenException();
233
-	}
234
-
235
-
236
-	public function markPasswordInvalid(IToken $token, string $tokenId) {
237
-		$this->getProvider($token)->markPasswordInvalid($token, $tokenId);
238
-	}
239
-
240
-	public function updatePasswords(string $uid, string $password) {
241
-		$this->publicKeyTokenProvider->updatePasswords($uid, $password);
242
-	}
38
+    /** @var PublicKeyTokenProvider */
39
+    private $publicKeyTokenProvider;
40
+
41
+    public function __construct(PublicKeyTokenProvider $publicKeyTokenProvider) {
42
+        $this->publicKeyTokenProvider = $publicKeyTokenProvider;
43
+    }
44
+
45
+    /**
46
+     * Create and persist a new token
47
+     *
48
+     * @param string $token
49
+     * @param string $uid
50
+     * @param string $loginName
51
+     * @param string|null $password
52
+     * @param string $name
53
+     * @param int $type token type
54
+     * @param int $remember whether the session token should be used for remember-me
55
+     * @return IToken
56
+     */
57
+    public function generateToken(string $token,
58
+                                    string $uid,
59
+                                    string $loginName,
60
+                                    $password,
61
+                                    string $name,
62
+                                    int $type = IToken::TEMPORARY_TOKEN,
63
+                                    int $remember = IToken::DO_NOT_REMEMBER): IToken {
64
+        if (mb_strlen($name) > 128) {
65
+            throw new InvalidTokenException('The given name is too long');
66
+        }
67
+
68
+        try {
69
+            return $this->publicKeyTokenProvider->generateToken(
70
+                $token,
71
+                $uid,
72
+                $loginName,
73
+                $password,
74
+                $name,
75
+                $type,
76
+                $remember
77
+            );
78
+        } catch (UniqueConstraintViolationException $e) {
79
+            // It's rare, but if two requests of the same session (e.g. env-based SAML)
80
+            // try to create the session token they might end up here at the same time
81
+            // because we use the session ID as token and the db token is created anew
82
+            // with every request.
83
+            //
84
+            // If the UIDs match, then this should be fine.
85
+            $existing = $this->getToken($token);
86
+            if ($existing->getUID() !== $uid) {
87
+                throw new \Exception('Token conflict handled, but UIDs do not match. This should not happen', 0, $e);
88
+            }
89
+            return $existing;
90
+        }
91
+    }
92
+
93
+    /**
94
+     * Save the updated token
95
+     *
96
+     * @param IToken $token
97
+     * @throws InvalidTokenException
98
+     */
99
+    public function updateToken(IToken $token) {
100
+        $provider = $this->getProvider($token);
101
+        $provider->updateToken($token);
102
+    }
103
+
104
+    /**
105
+     * Update token activity timestamp
106
+     *
107
+     * @throws InvalidTokenException
108
+     * @param IToken $token
109
+     */
110
+    public function updateTokenActivity(IToken $token) {
111
+        $provider = $this->getProvider($token);
112
+        $provider->updateTokenActivity($token);
113
+    }
114
+
115
+    /**
116
+     * @param string $uid
117
+     * @return IToken[]
118
+     */
119
+    public function getTokenByUser(string $uid): array {
120
+        return $this->publicKeyTokenProvider->getTokenByUser($uid);
121
+    }
122
+
123
+    /**
124
+     * Get a token by token
125
+     *
126
+     * @param string $tokenId
127
+     * @throws InvalidTokenException
128
+     * @throws \RuntimeException when OpenSSL reports a problem
129
+     * @return IToken
130
+     */
131
+    public function getToken(string $tokenId): IToken {
132
+        try {
133
+            return $this->publicKeyTokenProvider->getToken($tokenId);
134
+        } catch (WipeTokenException $e) {
135
+            throw $e;
136
+        } catch (ExpiredTokenException $e) {
137
+            throw $e;
138
+        } catch (InvalidTokenException $e) {
139
+            throw $e;
140
+        }
141
+    }
142
+
143
+    /**
144
+     * Get a token by token id
145
+     *
146
+     * @param int $tokenId
147
+     * @throws InvalidTokenException
148
+     * @return IToken
149
+     */
150
+    public function getTokenById(int $tokenId): IToken {
151
+        try {
152
+            return $this->publicKeyTokenProvider->getTokenById($tokenId);
153
+        } catch (ExpiredTokenException $e) {
154
+            throw $e;
155
+        } catch (WipeTokenException $e) {
156
+            throw $e;
157
+        } catch (InvalidTokenException $e) {
158
+            throw $e;
159
+        }
160
+    }
161
+
162
+    /**
163
+     * @param string $oldSessionId
164
+     * @param string $sessionId
165
+     * @throws InvalidTokenException
166
+     * @return IToken
167
+     */
168
+    public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
169
+        try {
170
+            return $this->publicKeyTokenProvider->renewSessionToken($oldSessionId, $sessionId);
171
+        } catch (ExpiredTokenException $e) {
172
+            throw $e;
173
+        } catch (InvalidTokenException $e) {
174
+            throw $e;
175
+        }
176
+    }
177
+
178
+    /**
179
+     * @param IToken $savedToken
180
+     * @param string $tokenId session token
181
+     * @throws InvalidTokenException
182
+     * @throws PasswordlessTokenException
183
+     * @return string
184
+     */
185
+    public function getPassword(IToken $savedToken, string $tokenId): string {
186
+        $provider = $this->getProvider($savedToken);
187
+        return $provider->getPassword($savedToken, $tokenId);
188
+    }
189
+
190
+    public function setPassword(IToken $token, string $tokenId, string $password) {
191
+        $provider = $this->getProvider($token);
192
+        $provider->setPassword($token, $tokenId, $password);
193
+    }
194
+
195
+    public function invalidateToken(string $token) {
196
+        $this->publicKeyTokenProvider->invalidateToken($token);
197
+    }
198
+
199
+    public function invalidateTokenById(string $uid, int $id) {
200
+        $this->publicKeyTokenProvider->invalidateTokenById($uid, $id);
201
+    }
202
+
203
+    public function invalidateOldTokens() {
204
+        $this->publicKeyTokenProvider->invalidateOldTokens();
205
+    }
206
+
207
+    /**
208
+     * @param IToken $token
209
+     * @param string $oldTokenId
210
+     * @param string $newTokenId
211
+     * @return IToken
212
+     * @throws InvalidTokenException
213
+     * @throws \RuntimeException when OpenSSL reports a problem
214
+     */
215
+    public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
216
+        if ($token instanceof PublicKeyToken) {
217
+            return $this->publicKeyTokenProvider->rotate($token, $oldTokenId, $newTokenId);
218
+        }
219
+
220
+        throw new InvalidTokenException();
221
+    }
222
+
223
+    /**
224
+     * @param IToken $token
225
+     * @return IProvider
226
+     * @throws InvalidTokenException
227
+     */
228
+    private function getProvider(IToken $token): IProvider {
229
+        if ($token instanceof PublicKeyToken) {
230
+            return $this->publicKeyTokenProvider;
231
+        }
232
+        throw new InvalidTokenException();
233
+    }
234
+
235
+
236
+    public function markPasswordInvalid(IToken $token, string $tokenId) {
237
+        $this->getProvider($token)->markPasswordInvalid($token, $tokenId);
238
+    }
239
+
240
+    public function updatePasswords(string $uid, string $password) {
241
+        $this->publicKeyTokenProvider->updatePasswords($uid, $password);
242
+    }
243 243
 }
Please login to merge, or discard this patch.
apps/settings/lib/Controller/AuthSettingsController.php 2 patches
Indentation   +271 added lines, -271 removed lines patch added patch discarded remove patch
@@ -54,275 +54,275 @@
 block discarded – undo
54 54
 
55 55
 class AuthSettingsController extends Controller {
56 56
 
57
-	/** @var IProvider */
58
-	private $tokenProvider;
59
-
60
-	/** @var ISession */
61
-	private $session;
62
-
63
-	/** IUserSession */
64
-	private $userSession;
65
-
66
-	/** @var string */
67
-	private $uid;
68
-
69
-	/** @var ISecureRandom */
70
-	private $random;
71
-
72
-	/** @var IManager */
73
-	private $activityManager;
74
-
75
-	/** @var RemoteWipe */
76
-	private $remoteWipe;
77
-
78
-	/** @var LoggerInterface */
79
-	private $logger;
80
-
81
-	/**
82
-	 * @param string $appName
83
-	 * @param IRequest $request
84
-	 * @param IProvider $tokenProvider
85
-	 * @param ISession $session
86
-	 * @param ISecureRandom $random
87
-	 * @param string|null $userId
88
-	 * @param IUserSession $userSession
89
-	 * @param IManager $activityManager
90
-	 * @param RemoteWipe $remoteWipe
91
-	 * @param LoggerInterface $logger
92
-	 */
93
-	public function __construct(string $appName,
94
-								IRequest $request,
95
-								IProvider $tokenProvider,
96
-								ISession $session,
97
-								ISecureRandom $random,
98
-								?string $userId,
99
-								IUserSession $userSession,
100
-								IManager $activityManager,
101
-								RemoteWipe $remoteWipe,
102
-								LoggerInterface $logger) {
103
-		parent::__construct($appName, $request);
104
-		$this->tokenProvider = $tokenProvider;
105
-		$this->uid = $userId;
106
-		$this->userSession = $userSession;
107
-		$this->session = $session;
108
-		$this->random = $random;
109
-		$this->activityManager = $activityManager;
110
-		$this->remoteWipe = $remoteWipe;
111
-		$this->logger = $logger;
112
-	}
113
-
114
-	/**
115
-	 * @NoAdminRequired
116
-	 * @NoSubAdminRequired
117
-	 * @PasswordConfirmationRequired
118
-	 *
119
-	 * @param string $name
120
-	 * @return JSONResponse
121
-	 */
122
-	public function create($name) {
123
-		if ($this->checkAppToken()) {
124
-			return $this->getServiceNotAvailableResponse();
125
-		}
126
-
127
-		try {
128
-			$sessionId = $this->session->getId();
129
-		} catch (SessionNotAvailableException $ex) {
130
-			return $this->getServiceNotAvailableResponse();
131
-		}
132
-		if ($this->userSession->getImpersonatingUserID() !== null) {
133
-			return $this->getServiceNotAvailableResponse();
134
-		}
135
-
136
-		try {
137
-			$sessionToken = $this->tokenProvider->getToken($sessionId);
138
-			$loginName = $sessionToken->getLoginName();
139
-			try {
140
-				$password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
141
-			} catch (PasswordlessTokenException $ex) {
142
-				$password = null;
143
-			}
144
-		} catch (InvalidTokenException $ex) {
145
-			return $this->getServiceNotAvailableResponse();
146
-		}
147
-
148
-		if (mb_strlen($name) > 128) {
149
-			$name = mb_substr($name, 0, 120) . '…';
150
-		}
151
-
152
-		$token = $this->generateRandomDeviceToken();
153
-		$deviceToken = $this->tokenProvider->generateToken($token, $this->uid, $loginName, $password, $name, IToken::PERMANENT_TOKEN);
154
-		$tokenData = $deviceToken->jsonSerialize();
155
-		$tokenData['canDelete'] = true;
156
-		$tokenData['canRename'] = true;
157
-
158
-		$this->publishActivity(Provider::APP_TOKEN_CREATED, $deviceToken->getId(), ['name' => $deviceToken->getName()]);
159
-
160
-		return new JSONResponse([
161
-			'token' => $token,
162
-			'loginName' => $loginName,
163
-			'deviceToken' => $tokenData,
164
-		]);
165
-	}
166
-
167
-	/**
168
-	 * @return JSONResponse
169
-	 */
170
-	private function getServiceNotAvailableResponse() {
171
-		$resp = new JSONResponse();
172
-		$resp->setStatus(Http::STATUS_SERVICE_UNAVAILABLE);
173
-		return $resp;
174
-	}
175
-
176
-	/**
177
-	 * Return a 25 digit device password
178
-	 *
179
-	 * Example: AbCdE-fGhJk-MnPqR-sTwXy-23456
180
-	 *
181
-	 * @return string
182
-	 */
183
-	private function generateRandomDeviceToken() {
184
-		$groups = [];
185
-		for ($i = 0; $i < 5; $i++) {
186
-			$groups[] = $this->random->generate(5, ISecureRandom::CHAR_HUMAN_READABLE);
187
-		}
188
-		return implode('-', $groups);
189
-	}
190
-
191
-	private function checkAppToken(): bool {
192
-		return $this->session->exists('app_password');
193
-	}
194
-
195
-	/**
196
-	 * @NoAdminRequired
197
-	 * @NoSubAdminRequired
198
-	 *
199
-	 * @param int $id
200
-	 * @return array|JSONResponse
201
-	 */
202
-	public function destroy($id) {
203
-		if ($this->checkAppToken()) {
204
-			return new JSONResponse([], Http::STATUS_BAD_REQUEST);
205
-		}
206
-
207
-		try {
208
-			$token = $this->findTokenByIdAndUser($id);
209
-		} catch (WipeTokenException $e) {
210
-			//continue as we can destroy tokens in wipe
211
-			$token = $e->getToken();
212
-		} catch (InvalidTokenException $e) {
213
-			return new JSONResponse([], Http::STATUS_NOT_FOUND);
214
-		}
215
-
216
-		$this->tokenProvider->invalidateTokenById($this->uid, $token->getId());
217
-		$this->publishActivity(Provider::APP_TOKEN_DELETED, $token->getId(), ['name' => $token->getName()]);
218
-		return [];
219
-	}
220
-
221
-	/**
222
-	 * @NoAdminRequired
223
-	 * @NoSubAdminRequired
224
-	 *
225
-	 * @param int $id
226
-	 * @param array $scope
227
-	 * @param string $name
228
-	 * @return array|JSONResponse
229
-	 */
230
-	public function update($id, array $scope, string $name) {
231
-		if ($this->checkAppToken()) {
232
-			return new JSONResponse([], Http::STATUS_BAD_REQUEST);
233
-		}
234
-
235
-		try {
236
-			$token = $this->findTokenByIdAndUser($id);
237
-		} catch (InvalidTokenException $e) {
238
-			return new JSONResponse([], Http::STATUS_NOT_FOUND);
239
-		}
240
-
241
-		$currentName = $token->getName();
242
-
243
-		if ($scope !== $token->getScopeAsArray()) {
244
-			$token->setScope(['filesystem' => $scope['filesystem']]);
245
-			$this->publishActivity($scope['filesystem'] ? Provider::APP_TOKEN_FILESYSTEM_GRANTED : Provider::APP_TOKEN_FILESYSTEM_REVOKED, $token->getId(), ['name' => $currentName]);
246
-		}
247
-
248
-		if (mb_strlen($name) > 128) {
249
-			$name = mb_substr($name, 0, 120) . '…';
250
-		}
251
-
252
-		if ($token instanceof INamedToken && $name !== $currentName) {
253
-			$token->setName($name);
254
-			$this->publishActivity(Provider::APP_TOKEN_RENAMED, $token->getId(), ['name' => $currentName, 'newName' => $name]);
255
-		}
256
-
257
-		$this->tokenProvider->updateToken($token);
258
-		return [];
259
-	}
260
-
261
-	/**
262
-	 * @param string $subject
263
-	 * @param int $id
264
-	 * @param array $parameters
265
-	 */
266
-	private function publishActivity(string $subject, int $id, array $parameters = []): void {
267
-		$event = $this->activityManager->generateEvent();
268
-		$event->setApp('settings')
269
-			->setType('security')
270
-			->setAffectedUser($this->uid)
271
-			->setAuthor($this->uid)
272
-			->setSubject($subject, $parameters)
273
-			->setObject('app_token', $id, 'App Password');
274
-
275
-		try {
276
-			$this->activityManager->publish($event);
277
-		} catch (BadMethodCallException $e) {
278
-			$this->logger->warning('could not publish activity', ['exception' => $e]);
279
-		}
280
-	}
281
-
282
-	/**
283
-	 * Find a token by given id and check if uid for current session belongs to this token
284
-	 *
285
-	 * @param int $id
286
-	 * @return IToken
287
-	 * @throws InvalidTokenException
288
-	 */
289
-	private function findTokenByIdAndUser(int $id): IToken {
290
-		try {
291
-			$token = $this->tokenProvider->getTokenById($id);
292
-		} catch (ExpiredTokenException $e) {
293
-			$token = $e->getToken();
294
-		}
295
-		if ($token->getUID() !== $this->uid) {
296
-			throw new InvalidTokenException('This token does not belong to you!');
297
-		}
298
-		return $token;
299
-	}
300
-
301
-	/**
302
-	 * @NoAdminRequired
303
-	 * @NoSubAdminRequired
304
-	 * @PasswordConfirmationRequired
305
-	 *
306
-	 * @param int $id
307
-	 * @return JSONResponse
308
-	 * @throws InvalidTokenException
309
-	 * @throws \OC\Authentication\Exceptions\ExpiredTokenException
310
-	 */
311
-	public function wipe(int $id): JSONResponse {
312
-		if ($this->checkAppToken()) {
313
-			return new JSONResponse([], Http::STATUS_BAD_REQUEST);
314
-		}
315
-
316
-		try {
317
-			$token = $this->findTokenByIdAndUser($id);
318
-		} catch (InvalidTokenException $e) {
319
-			return new JSONResponse([], Http::STATUS_NOT_FOUND);
320
-		}
321
-
322
-		if (!$this->remoteWipe->markTokenForWipe($token)) {
323
-			return new JSONResponse([], Http::STATUS_BAD_REQUEST);
324
-		}
325
-
326
-		return new JSONResponse([]);
327
-	}
57
+    /** @var IProvider */
58
+    private $tokenProvider;
59
+
60
+    /** @var ISession */
61
+    private $session;
62
+
63
+    /** IUserSession */
64
+    private $userSession;
65
+
66
+    /** @var string */
67
+    private $uid;
68
+
69
+    /** @var ISecureRandom */
70
+    private $random;
71
+
72
+    /** @var IManager */
73
+    private $activityManager;
74
+
75
+    /** @var RemoteWipe */
76
+    private $remoteWipe;
77
+
78
+    /** @var LoggerInterface */
79
+    private $logger;
80
+
81
+    /**
82
+     * @param string $appName
83
+     * @param IRequest $request
84
+     * @param IProvider $tokenProvider
85
+     * @param ISession $session
86
+     * @param ISecureRandom $random
87
+     * @param string|null $userId
88
+     * @param IUserSession $userSession
89
+     * @param IManager $activityManager
90
+     * @param RemoteWipe $remoteWipe
91
+     * @param LoggerInterface $logger
92
+     */
93
+    public function __construct(string $appName,
94
+                                IRequest $request,
95
+                                IProvider $tokenProvider,
96
+                                ISession $session,
97
+                                ISecureRandom $random,
98
+                                ?string $userId,
99
+                                IUserSession $userSession,
100
+                                IManager $activityManager,
101
+                                RemoteWipe $remoteWipe,
102
+                                LoggerInterface $logger) {
103
+        parent::__construct($appName, $request);
104
+        $this->tokenProvider = $tokenProvider;
105
+        $this->uid = $userId;
106
+        $this->userSession = $userSession;
107
+        $this->session = $session;
108
+        $this->random = $random;
109
+        $this->activityManager = $activityManager;
110
+        $this->remoteWipe = $remoteWipe;
111
+        $this->logger = $logger;
112
+    }
113
+
114
+    /**
115
+     * @NoAdminRequired
116
+     * @NoSubAdminRequired
117
+     * @PasswordConfirmationRequired
118
+     *
119
+     * @param string $name
120
+     * @return JSONResponse
121
+     */
122
+    public function create($name) {
123
+        if ($this->checkAppToken()) {
124
+            return $this->getServiceNotAvailableResponse();
125
+        }
126
+
127
+        try {
128
+            $sessionId = $this->session->getId();
129
+        } catch (SessionNotAvailableException $ex) {
130
+            return $this->getServiceNotAvailableResponse();
131
+        }
132
+        if ($this->userSession->getImpersonatingUserID() !== null) {
133
+            return $this->getServiceNotAvailableResponse();
134
+        }
135
+
136
+        try {
137
+            $sessionToken = $this->tokenProvider->getToken($sessionId);
138
+            $loginName = $sessionToken->getLoginName();
139
+            try {
140
+                $password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
141
+            } catch (PasswordlessTokenException $ex) {
142
+                $password = null;
143
+            }
144
+        } catch (InvalidTokenException $ex) {
145
+            return $this->getServiceNotAvailableResponse();
146
+        }
147
+
148
+        if (mb_strlen($name) > 128) {
149
+            $name = mb_substr($name, 0, 120) . '…';
150
+        }
151
+
152
+        $token = $this->generateRandomDeviceToken();
153
+        $deviceToken = $this->tokenProvider->generateToken($token, $this->uid, $loginName, $password, $name, IToken::PERMANENT_TOKEN);
154
+        $tokenData = $deviceToken->jsonSerialize();
155
+        $tokenData['canDelete'] = true;
156
+        $tokenData['canRename'] = true;
157
+
158
+        $this->publishActivity(Provider::APP_TOKEN_CREATED, $deviceToken->getId(), ['name' => $deviceToken->getName()]);
159
+
160
+        return new JSONResponse([
161
+            'token' => $token,
162
+            'loginName' => $loginName,
163
+            'deviceToken' => $tokenData,
164
+        ]);
165
+    }
166
+
167
+    /**
168
+     * @return JSONResponse
169
+     */
170
+    private function getServiceNotAvailableResponse() {
171
+        $resp = new JSONResponse();
172
+        $resp->setStatus(Http::STATUS_SERVICE_UNAVAILABLE);
173
+        return $resp;
174
+    }
175
+
176
+    /**
177
+     * Return a 25 digit device password
178
+     *
179
+     * Example: AbCdE-fGhJk-MnPqR-sTwXy-23456
180
+     *
181
+     * @return string
182
+     */
183
+    private function generateRandomDeviceToken() {
184
+        $groups = [];
185
+        for ($i = 0; $i < 5; $i++) {
186
+            $groups[] = $this->random->generate(5, ISecureRandom::CHAR_HUMAN_READABLE);
187
+        }
188
+        return implode('-', $groups);
189
+    }
190
+
191
+    private function checkAppToken(): bool {
192
+        return $this->session->exists('app_password');
193
+    }
194
+
195
+    /**
196
+     * @NoAdminRequired
197
+     * @NoSubAdminRequired
198
+     *
199
+     * @param int $id
200
+     * @return array|JSONResponse
201
+     */
202
+    public function destroy($id) {
203
+        if ($this->checkAppToken()) {
204
+            return new JSONResponse([], Http::STATUS_BAD_REQUEST);
205
+        }
206
+
207
+        try {
208
+            $token = $this->findTokenByIdAndUser($id);
209
+        } catch (WipeTokenException $e) {
210
+            //continue as we can destroy tokens in wipe
211
+            $token = $e->getToken();
212
+        } catch (InvalidTokenException $e) {
213
+            return new JSONResponse([], Http::STATUS_NOT_FOUND);
214
+        }
215
+
216
+        $this->tokenProvider->invalidateTokenById($this->uid, $token->getId());
217
+        $this->publishActivity(Provider::APP_TOKEN_DELETED, $token->getId(), ['name' => $token->getName()]);
218
+        return [];
219
+    }
220
+
221
+    /**
222
+     * @NoAdminRequired
223
+     * @NoSubAdminRequired
224
+     *
225
+     * @param int $id
226
+     * @param array $scope
227
+     * @param string $name
228
+     * @return array|JSONResponse
229
+     */
230
+    public function update($id, array $scope, string $name) {
231
+        if ($this->checkAppToken()) {
232
+            return new JSONResponse([], Http::STATUS_BAD_REQUEST);
233
+        }
234
+
235
+        try {
236
+            $token = $this->findTokenByIdAndUser($id);
237
+        } catch (InvalidTokenException $e) {
238
+            return new JSONResponse([], Http::STATUS_NOT_FOUND);
239
+        }
240
+
241
+        $currentName = $token->getName();
242
+
243
+        if ($scope !== $token->getScopeAsArray()) {
244
+            $token->setScope(['filesystem' => $scope['filesystem']]);
245
+            $this->publishActivity($scope['filesystem'] ? Provider::APP_TOKEN_FILESYSTEM_GRANTED : Provider::APP_TOKEN_FILESYSTEM_REVOKED, $token->getId(), ['name' => $currentName]);
246
+        }
247
+
248
+        if (mb_strlen($name) > 128) {
249
+            $name = mb_substr($name, 0, 120) . '…';
250
+        }
251
+
252
+        if ($token instanceof INamedToken && $name !== $currentName) {
253
+            $token->setName($name);
254
+            $this->publishActivity(Provider::APP_TOKEN_RENAMED, $token->getId(), ['name' => $currentName, 'newName' => $name]);
255
+        }
256
+
257
+        $this->tokenProvider->updateToken($token);
258
+        return [];
259
+    }
260
+
261
+    /**
262
+     * @param string $subject
263
+     * @param int $id
264
+     * @param array $parameters
265
+     */
266
+    private function publishActivity(string $subject, int $id, array $parameters = []): void {
267
+        $event = $this->activityManager->generateEvent();
268
+        $event->setApp('settings')
269
+            ->setType('security')
270
+            ->setAffectedUser($this->uid)
271
+            ->setAuthor($this->uid)
272
+            ->setSubject($subject, $parameters)
273
+            ->setObject('app_token', $id, 'App Password');
274
+
275
+        try {
276
+            $this->activityManager->publish($event);
277
+        } catch (BadMethodCallException $e) {
278
+            $this->logger->warning('could not publish activity', ['exception' => $e]);
279
+        }
280
+    }
281
+
282
+    /**
283
+     * Find a token by given id and check if uid for current session belongs to this token
284
+     *
285
+     * @param int $id
286
+     * @return IToken
287
+     * @throws InvalidTokenException
288
+     */
289
+    private function findTokenByIdAndUser(int $id): IToken {
290
+        try {
291
+            $token = $this->tokenProvider->getTokenById($id);
292
+        } catch (ExpiredTokenException $e) {
293
+            $token = $e->getToken();
294
+        }
295
+        if ($token->getUID() !== $this->uid) {
296
+            throw new InvalidTokenException('This token does not belong to you!');
297
+        }
298
+        return $token;
299
+    }
300
+
301
+    /**
302
+     * @NoAdminRequired
303
+     * @NoSubAdminRequired
304
+     * @PasswordConfirmationRequired
305
+     *
306
+     * @param int $id
307
+     * @return JSONResponse
308
+     * @throws InvalidTokenException
309
+     * @throws \OC\Authentication\Exceptions\ExpiredTokenException
310
+     */
311
+    public function wipe(int $id): JSONResponse {
312
+        if ($this->checkAppToken()) {
313
+            return new JSONResponse([], Http::STATUS_BAD_REQUEST);
314
+        }
315
+
316
+        try {
317
+            $token = $this->findTokenByIdAndUser($id);
318
+        } catch (InvalidTokenException $e) {
319
+            return new JSONResponse([], Http::STATUS_NOT_FOUND);
320
+        }
321
+
322
+        if (!$this->remoteWipe->markTokenForWipe($token)) {
323
+            return new JSONResponse([], Http::STATUS_BAD_REQUEST);
324
+        }
325
+
326
+        return new JSONResponse([]);
327
+    }
328 328
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -146,7 +146,7 @@  discard block
 block discarded – undo
146 146
 		}
147 147
 
148 148
 		if (mb_strlen($name) > 128) {
149
-			$name = mb_substr($name, 0, 120) . '…';
149
+			$name = mb_substr($name, 0, 120).'…';
150 150
 		}
151 151
 
152 152
 		$token = $this->generateRandomDeviceToken();
@@ -246,7 +246,7 @@  discard block
 block discarded – undo
246 246
 		}
247 247
 
248 248
 		if (mb_strlen($name) > 128) {
249
-			$name = mb_substr($name, 0, 120) . '…';
249
+			$name = mb_substr($name, 0, 120).'…';
250 250
 		}
251 251
 
252 252
 		if ($token instanceof INamedToken && $name !== $currentName) {
Please login to merge, or discard this patch.