Passed
Push — master ( 3473b6...eca7ab )
by Christoph
16:51 queued 13s
created
core/Controller/ClientFlowLoginV2Controller.php 1 patch
Indentation   +324 added lines, -324 removed lines patch added patch discarded remove patch
@@ -48,328 +48,328 @@
 block discarded – undo
48 48
 use OCP\Security\ISecureRandom;
49 49
 
50 50
 class ClientFlowLoginV2Controller extends Controller {
51
-	public const TOKEN_NAME = 'client.flow.v2.login.token';
52
-	public const STATE_NAME = 'client.flow.v2.state.token';
53
-
54
-	private LoginFlowV2Service $loginFlowV2Service;
55
-	private IURLGenerator $urlGenerator;
56
-	private IUserSession $userSession;
57
-	private ISession $session;
58
-	private ISecureRandom $random;
59
-	private Defaults $defaults;
60
-	private ?string $userId;
61
-	private IL10N $l10n;
62
-
63
-	public function __construct(string $appName,
64
-								IRequest $request,
65
-								LoginFlowV2Service $loginFlowV2Service,
66
-								IURLGenerator $urlGenerator,
67
-								ISession $session,
68
-								IUserSession $userSession,
69
-								ISecureRandom $random,
70
-								Defaults $defaults,
71
-								?string $userId,
72
-								IL10N $l10n) {
73
-		parent::__construct($appName, $request);
74
-		$this->loginFlowV2Service = $loginFlowV2Service;
75
-		$this->urlGenerator = $urlGenerator;
76
-		$this->session = $session;
77
-		$this->userSession = $userSession;
78
-		$this->random = $random;
79
-		$this->defaults = $defaults;
80
-		$this->userId = $userId;
81
-		$this->l10n = $l10n;
82
-	}
83
-
84
-	/**
85
-	 * @NoCSRFRequired
86
-	 * @PublicPage
87
-	 */
88
-	public function poll(string $token): JSONResponse {
89
-		try {
90
-			$creds = $this->loginFlowV2Service->poll($token);
91
-		} catch (LoginFlowV2NotFoundException $e) {
92
-			return new JSONResponse([], Http::STATUS_NOT_FOUND);
93
-		}
94
-
95
-		return new JSONResponse($creds);
96
-	}
97
-
98
-	/**
99
-	 * @NoCSRFRequired
100
-	 * @PublicPage
101
-	 */
102
-	#[UseSession]
103
-	public function landing(string $token, $user = ''): Response {
104
-		if (!$this->loginFlowV2Service->startLoginFlow($token)) {
105
-			return $this->loginTokenForbiddenResponse();
106
-		}
107
-
108
-		$this->session->set(self::TOKEN_NAME, $token);
109
-
110
-		return new RedirectResponse(
111
-			$this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.showAuthPickerPage', ['user' => $user])
112
-		);
113
-	}
114
-
115
-	/**
116
-	 * @NoCSRFRequired
117
-	 * @PublicPage
118
-	 */
119
-	#[UseSession]
120
-	public function showAuthPickerPage($user = ''): StandaloneTemplateResponse {
121
-		try {
122
-			$flow = $this->getFlowByLoginToken();
123
-		} catch (LoginFlowV2NotFoundException $e) {
124
-			return $this->loginTokenForbiddenResponse();
125
-		}
126
-
127
-		$stateToken = $this->random->generate(
128
-			64,
129
-			ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
130
-		);
131
-		$this->session->set(self::STATE_NAME, $stateToken);
132
-
133
-		return new StandaloneTemplateResponse(
134
-			$this->appName,
135
-			'loginflowv2/authpicker',
136
-			[
137
-				'client' => $flow->getClientName(),
138
-				'instanceName' => $this->defaults->getName(),
139
-				'urlGenerator' => $this->urlGenerator,
140
-				'stateToken' => $stateToken,
141
-				'user' => $user,
142
-			],
143
-			'guest'
144
-		);
145
-	}
146
-
147
-	/**
148
-	 * @NoAdminRequired
149
-	 * @NoCSRFRequired
150
-	 * @NoSameSiteCookieRequired
151
-	 */
152
-	#[UseSession]
153
-	public function grantPage(?string $stateToken): StandaloneTemplateResponse {
154
-		if ($stateToken === null) {
155
-			return $this->stateTokenMissingResponse();
156
-		}
157
-		if (!$this->isValidStateToken($stateToken)) {
158
-			return $this->stateTokenForbiddenResponse();
159
-		}
160
-
161
-		try {
162
-			$flow = $this->getFlowByLoginToken();
163
-		} catch (LoginFlowV2NotFoundException $e) {
164
-			return $this->loginTokenForbiddenResponse();
165
-		}
166
-
167
-		/** @var IUser $user */
168
-		$user = $this->userSession->getUser();
169
-
170
-		return new StandaloneTemplateResponse(
171
-			$this->appName,
172
-			'loginflowv2/grant',
173
-			[
174
-				'userId' => $user->getUID(),
175
-				'userDisplayName' => $user->getDisplayName(),
176
-				'client' => $flow->getClientName(),
177
-				'instanceName' => $this->defaults->getName(),
178
-				'urlGenerator' => $this->urlGenerator,
179
-				'stateToken' => $stateToken,
180
-			],
181
-			'guest'
182
-		);
183
-	}
184
-
185
-	/**
186
-	 * @PublicPage
187
-	 */
188
-	public function apptokenRedirect(?string $stateToken, string $user, string $password) {
189
-		if ($stateToken === null) {
190
-			return $this->loginTokenForbiddenResponse();
191
-		}
192
-
193
-		if (!$this->isValidStateToken($stateToken)) {
194
-			return $this->stateTokenForbiddenResponse();
195
-		}
196
-
197
-		try {
198
-			$this->getFlowByLoginToken();
199
-		} catch (LoginFlowV2NotFoundException $e) {
200
-			return $this->loginTokenForbiddenResponse();
201
-		}
202
-
203
-		$loginToken = $this->session->get(self::TOKEN_NAME);
204
-
205
-		// Clear session variables
206
-		$this->session->remove(self::TOKEN_NAME);
207
-		$this->session->remove(self::STATE_NAME);
208
-
209
-		try {
210
-			$token = \OC::$server->get(\OC\Authentication\Token\IProvider::class)->getToken($password);
211
-			if ($token->getLoginName() !== $user) {
212
-				throw new InvalidTokenException('login name does not match');
213
-			}
214
-		} catch (InvalidTokenException $e) {
215
-			$response = new StandaloneTemplateResponse(
216
-				$this->appName,
217
-				'403',
218
-				[
219
-					'message' => $this->l10n->t('Invalid app password'),
220
-				],
221
-				'guest'
222
-			);
223
-			$response->setStatus(Http::STATUS_FORBIDDEN);
224
-			return $response;
225
-		}
226
-
227
-		$result = $this->loginFlowV2Service->flowDoneWithAppPassword($loginToken, $this->getServerPath(), $token->getLoginName(), $password);
228
-		return $this->handleFlowDone($result);
229
-	}
230
-
231
-	/**
232
-	 * @NoAdminRequired
233
-	 */
234
-	#[UseSession]
235
-	public function generateAppPassword(?string $stateToken): Response {
236
-		if ($stateToken === null) {
237
-			return $this->stateTokenMissingResponse();
238
-		}
239
-		if (!$this->isValidStateToken($stateToken)) {
240
-			return $this->stateTokenForbiddenResponse();
241
-		}
242
-
243
-		try {
244
-			$this->getFlowByLoginToken();
245
-		} catch (LoginFlowV2NotFoundException $e) {
246
-			return $this->loginTokenForbiddenResponse();
247
-		}
248
-
249
-		$loginToken = $this->session->get(self::TOKEN_NAME);
250
-
251
-		// Clear session variables
252
-		$this->session->remove(self::TOKEN_NAME);
253
-		$this->session->remove(self::STATE_NAME);
254
-		$sessionId = $this->session->getId();
255
-
256
-		$result = $this->loginFlowV2Service->flowDone($loginToken, $sessionId, $this->getServerPath(), $this->userId);
257
-		return $this->handleFlowDone($result);
258
-	}
259
-
260
-	private function handleFlowDone(bool $result): StandaloneTemplateResponse {
261
-		if ($result) {
262
-			return new StandaloneTemplateResponse(
263
-				$this->appName,
264
-				'loginflowv2/done',
265
-				[],
266
-				'guest'
267
-			);
268
-		}
269
-
270
-		$response = new StandaloneTemplateResponse(
271
-			$this->appName,
272
-			'403',
273
-			[
274
-				'message' => $this->l10n->t('Could not complete login'),
275
-			],
276
-			'guest'
277
-		);
278
-		$response->setStatus(Http::STATUS_FORBIDDEN);
279
-		return $response;
280
-	}
281
-
282
-	/**
283
-	 * @NoCSRFRequired
284
-	 * @PublicPage
285
-	 */
286
-	public function init(): JSONResponse {
287
-		// Get client user agent
288
-		$userAgent = $this->request->getHeader('USER_AGENT');
289
-
290
-		$tokens = $this->loginFlowV2Service->createTokens($userAgent);
291
-
292
-		$data = [
293
-			'poll' => [
294
-				'token' => $tokens->getPollToken(),
295
-				'endpoint' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.poll')
296
-			],
297
-			'login' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.landing', ['token' => $tokens->getLoginToken()]),
298
-		];
299
-
300
-		return new JSONResponse($data);
301
-	}
302
-
303
-	private function isValidStateToken(string $stateToken): bool {
304
-		$currentToken = $this->session->get(self::STATE_NAME);
305
-		if (!is_string($stateToken) || !is_string($currentToken)) {
306
-			return false;
307
-		}
308
-		return hash_equals($currentToken, $stateToken);
309
-	}
310
-
311
-	private function stateTokenMissingResponse(): StandaloneTemplateResponse {
312
-		$response = new StandaloneTemplateResponse(
313
-			$this->appName,
314
-			'403',
315
-			[
316
-				'message' => $this->l10n->t('State token missing'),
317
-			],
318
-			'guest'
319
-		);
320
-		$response->setStatus(Http::STATUS_FORBIDDEN);
321
-		return $response;
322
-	}
323
-
324
-	private function stateTokenForbiddenResponse(): StandaloneTemplateResponse {
325
-		$response = new StandaloneTemplateResponse(
326
-			$this->appName,
327
-			'403',
328
-			[
329
-				'message' => $this->l10n->t('State token does not match'),
330
-			],
331
-			'guest'
332
-		);
333
-		$response->setStatus(Http::STATUS_FORBIDDEN);
334
-		return $response;
335
-	}
336
-
337
-	/**
338
-	 * @return LoginFlowV2
339
-	 * @throws LoginFlowV2NotFoundException
340
-	 */
341
-	private function getFlowByLoginToken(): LoginFlowV2 {
342
-		$currentToken = $this->session->get(self::TOKEN_NAME);
343
-		if (!is_string($currentToken)) {
344
-			throw new LoginFlowV2NotFoundException('Login token not set in session');
345
-		}
346
-
347
-		return $this->loginFlowV2Service->getByLoginToken($currentToken);
348
-	}
349
-
350
-	private function loginTokenForbiddenResponse(): StandaloneTemplateResponse {
351
-		$response = new StandaloneTemplateResponse(
352
-			$this->appName,
353
-			'403',
354
-			[
355
-				'message' => $this->l10n->t('Your login token is invalid or has expired'),
356
-			],
357
-			'guest'
358
-		);
359
-		$response->setStatus(Http::STATUS_FORBIDDEN);
360
-		return $response;
361
-	}
362
-
363
-	private function getServerPath(): string {
364
-		$serverPostfix = '';
365
-
366
-		if (strpos($this->request->getRequestUri(), '/index.php') !== false) {
367
-			$serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/index.php'));
368
-		} elseif (strpos($this->request->getRequestUri(), '/login/v2') !== false) {
369
-			$serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/login/v2'));
370
-		}
371
-
372
-		$protocol = $this->request->getServerProtocol();
373
-		return $protocol . '://' . $this->request->getServerHost() . $serverPostfix;
374
-	}
51
+    public const TOKEN_NAME = 'client.flow.v2.login.token';
52
+    public const STATE_NAME = 'client.flow.v2.state.token';
53
+
54
+    private LoginFlowV2Service $loginFlowV2Service;
55
+    private IURLGenerator $urlGenerator;
56
+    private IUserSession $userSession;
57
+    private ISession $session;
58
+    private ISecureRandom $random;
59
+    private Defaults $defaults;
60
+    private ?string $userId;
61
+    private IL10N $l10n;
62
+
63
+    public function __construct(string $appName,
64
+                                IRequest $request,
65
+                                LoginFlowV2Service $loginFlowV2Service,
66
+                                IURLGenerator $urlGenerator,
67
+                                ISession $session,
68
+                                IUserSession $userSession,
69
+                                ISecureRandom $random,
70
+                                Defaults $defaults,
71
+                                ?string $userId,
72
+                                IL10N $l10n) {
73
+        parent::__construct($appName, $request);
74
+        $this->loginFlowV2Service = $loginFlowV2Service;
75
+        $this->urlGenerator = $urlGenerator;
76
+        $this->session = $session;
77
+        $this->userSession = $userSession;
78
+        $this->random = $random;
79
+        $this->defaults = $defaults;
80
+        $this->userId = $userId;
81
+        $this->l10n = $l10n;
82
+    }
83
+
84
+    /**
85
+     * @NoCSRFRequired
86
+     * @PublicPage
87
+     */
88
+    public function poll(string $token): JSONResponse {
89
+        try {
90
+            $creds = $this->loginFlowV2Service->poll($token);
91
+        } catch (LoginFlowV2NotFoundException $e) {
92
+            return new JSONResponse([], Http::STATUS_NOT_FOUND);
93
+        }
94
+
95
+        return new JSONResponse($creds);
96
+    }
97
+
98
+    /**
99
+     * @NoCSRFRequired
100
+     * @PublicPage
101
+     */
102
+    #[UseSession]
103
+    public function landing(string $token, $user = ''): Response {
104
+        if (!$this->loginFlowV2Service->startLoginFlow($token)) {
105
+            return $this->loginTokenForbiddenResponse();
106
+        }
107
+
108
+        $this->session->set(self::TOKEN_NAME, $token);
109
+
110
+        return new RedirectResponse(
111
+            $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.showAuthPickerPage', ['user' => $user])
112
+        );
113
+    }
114
+
115
+    /**
116
+     * @NoCSRFRequired
117
+     * @PublicPage
118
+     */
119
+    #[UseSession]
120
+    public function showAuthPickerPage($user = ''): StandaloneTemplateResponse {
121
+        try {
122
+            $flow = $this->getFlowByLoginToken();
123
+        } catch (LoginFlowV2NotFoundException $e) {
124
+            return $this->loginTokenForbiddenResponse();
125
+        }
126
+
127
+        $stateToken = $this->random->generate(
128
+            64,
129
+            ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
130
+        );
131
+        $this->session->set(self::STATE_NAME, $stateToken);
132
+
133
+        return new StandaloneTemplateResponse(
134
+            $this->appName,
135
+            'loginflowv2/authpicker',
136
+            [
137
+                'client' => $flow->getClientName(),
138
+                'instanceName' => $this->defaults->getName(),
139
+                'urlGenerator' => $this->urlGenerator,
140
+                'stateToken' => $stateToken,
141
+                'user' => $user,
142
+            ],
143
+            'guest'
144
+        );
145
+    }
146
+
147
+    /**
148
+     * @NoAdminRequired
149
+     * @NoCSRFRequired
150
+     * @NoSameSiteCookieRequired
151
+     */
152
+    #[UseSession]
153
+    public function grantPage(?string $stateToken): StandaloneTemplateResponse {
154
+        if ($stateToken === null) {
155
+            return $this->stateTokenMissingResponse();
156
+        }
157
+        if (!$this->isValidStateToken($stateToken)) {
158
+            return $this->stateTokenForbiddenResponse();
159
+        }
160
+
161
+        try {
162
+            $flow = $this->getFlowByLoginToken();
163
+        } catch (LoginFlowV2NotFoundException $e) {
164
+            return $this->loginTokenForbiddenResponse();
165
+        }
166
+
167
+        /** @var IUser $user */
168
+        $user = $this->userSession->getUser();
169
+
170
+        return new StandaloneTemplateResponse(
171
+            $this->appName,
172
+            'loginflowv2/grant',
173
+            [
174
+                'userId' => $user->getUID(),
175
+                'userDisplayName' => $user->getDisplayName(),
176
+                'client' => $flow->getClientName(),
177
+                'instanceName' => $this->defaults->getName(),
178
+                'urlGenerator' => $this->urlGenerator,
179
+                'stateToken' => $stateToken,
180
+            ],
181
+            'guest'
182
+        );
183
+    }
184
+
185
+    /**
186
+     * @PublicPage
187
+     */
188
+    public function apptokenRedirect(?string $stateToken, string $user, string $password) {
189
+        if ($stateToken === null) {
190
+            return $this->loginTokenForbiddenResponse();
191
+        }
192
+
193
+        if (!$this->isValidStateToken($stateToken)) {
194
+            return $this->stateTokenForbiddenResponse();
195
+        }
196
+
197
+        try {
198
+            $this->getFlowByLoginToken();
199
+        } catch (LoginFlowV2NotFoundException $e) {
200
+            return $this->loginTokenForbiddenResponse();
201
+        }
202
+
203
+        $loginToken = $this->session->get(self::TOKEN_NAME);
204
+
205
+        // Clear session variables
206
+        $this->session->remove(self::TOKEN_NAME);
207
+        $this->session->remove(self::STATE_NAME);
208
+
209
+        try {
210
+            $token = \OC::$server->get(\OC\Authentication\Token\IProvider::class)->getToken($password);
211
+            if ($token->getLoginName() !== $user) {
212
+                throw new InvalidTokenException('login name does not match');
213
+            }
214
+        } catch (InvalidTokenException $e) {
215
+            $response = new StandaloneTemplateResponse(
216
+                $this->appName,
217
+                '403',
218
+                [
219
+                    'message' => $this->l10n->t('Invalid app password'),
220
+                ],
221
+                'guest'
222
+            );
223
+            $response->setStatus(Http::STATUS_FORBIDDEN);
224
+            return $response;
225
+        }
226
+
227
+        $result = $this->loginFlowV2Service->flowDoneWithAppPassword($loginToken, $this->getServerPath(), $token->getLoginName(), $password);
228
+        return $this->handleFlowDone($result);
229
+    }
230
+
231
+    /**
232
+     * @NoAdminRequired
233
+     */
234
+    #[UseSession]
235
+    public function generateAppPassword(?string $stateToken): Response {
236
+        if ($stateToken === null) {
237
+            return $this->stateTokenMissingResponse();
238
+        }
239
+        if (!$this->isValidStateToken($stateToken)) {
240
+            return $this->stateTokenForbiddenResponse();
241
+        }
242
+
243
+        try {
244
+            $this->getFlowByLoginToken();
245
+        } catch (LoginFlowV2NotFoundException $e) {
246
+            return $this->loginTokenForbiddenResponse();
247
+        }
248
+
249
+        $loginToken = $this->session->get(self::TOKEN_NAME);
250
+
251
+        // Clear session variables
252
+        $this->session->remove(self::TOKEN_NAME);
253
+        $this->session->remove(self::STATE_NAME);
254
+        $sessionId = $this->session->getId();
255
+
256
+        $result = $this->loginFlowV2Service->flowDone($loginToken, $sessionId, $this->getServerPath(), $this->userId);
257
+        return $this->handleFlowDone($result);
258
+    }
259
+
260
+    private function handleFlowDone(bool $result): StandaloneTemplateResponse {
261
+        if ($result) {
262
+            return new StandaloneTemplateResponse(
263
+                $this->appName,
264
+                'loginflowv2/done',
265
+                [],
266
+                'guest'
267
+            );
268
+        }
269
+
270
+        $response = new StandaloneTemplateResponse(
271
+            $this->appName,
272
+            '403',
273
+            [
274
+                'message' => $this->l10n->t('Could not complete login'),
275
+            ],
276
+            'guest'
277
+        );
278
+        $response->setStatus(Http::STATUS_FORBIDDEN);
279
+        return $response;
280
+    }
281
+
282
+    /**
283
+     * @NoCSRFRequired
284
+     * @PublicPage
285
+     */
286
+    public function init(): JSONResponse {
287
+        // Get client user agent
288
+        $userAgent = $this->request->getHeader('USER_AGENT');
289
+
290
+        $tokens = $this->loginFlowV2Service->createTokens($userAgent);
291
+
292
+        $data = [
293
+            'poll' => [
294
+                'token' => $tokens->getPollToken(),
295
+                'endpoint' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.poll')
296
+            ],
297
+            'login' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.landing', ['token' => $tokens->getLoginToken()]),
298
+        ];
299
+
300
+        return new JSONResponse($data);
301
+    }
302
+
303
+    private function isValidStateToken(string $stateToken): bool {
304
+        $currentToken = $this->session->get(self::STATE_NAME);
305
+        if (!is_string($stateToken) || !is_string($currentToken)) {
306
+            return false;
307
+        }
308
+        return hash_equals($currentToken, $stateToken);
309
+    }
310
+
311
+    private function stateTokenMissingResponse(): StandaloneTemplateResponse {
312
+        $response = new StandaloneTemplateResponse(
313
+            $this->appName,
314
+            '403',
315
+            [
316
+                'message' => $this->l10n->t('State token missing'),
317
+            ],
318
+            'guest'
319
+        );
320
+        $response->setStatus(Http::STATUS_FORBIDDEN);
321
+        return $response;
322
+    }
323
+
324
+    private function stateTokenForbiddenResponse(): StandaloneTemplateResponse {
325
+        $response = new StandaloneTemplateResponse(
326
+            $this->appName,
327
+            '403',
328
+            [
329
+                'message' => $this->l10n->t('State token does not match'),
330
+            ],
331
+            'guest'
332
+        );
333
+        $response->setStatus(Http::STATUS_FORBIDDEN);
334
+        return $response;
335
+    }
336
+
337
+    /**
338
+     * @return LoginFlowV2
339
+     * @throws LoginFlowV2NotFoundException
340
+     */
341
+    private function getFlowByLoginToken(): LoginFlowV2 {
342
+        $currentToken = $this->session->get(self::TOKEN_NAME);
343
+        if (!is_string($currentToken)) {
344
+            throw new LoginFlowV2NotFoundException('Login token not set in session');
345
+        }
346
+
347
+        return $this->loginFlowV2Service->getByLoginToken($currentToken);
348
+    }
349
+
350
+    private function loginTokenForbiddenResponse(): StandaloneTemplateResponse {
351
+        $response = new StandaloneTemplateResponse(
352
+            $this->appName,
353
+            '403',
354
+            [
355
+                'message' => $this->l10n->t('Your login token is invalid or has expired'),
356
+            ],
357
+            'guest'
358
+        );
359
+        $response->setStatus(Http::STATUS_FORBIDDEN);
360
+        return $response;
361
+    }
362
+
363
+    private function getServerPath(): string {
364
+        $serverPostfix = '';
365
+
366
+        if (strpos($this->request->getRequestUri(), '/index.php') !== false) {
367
+            $serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/index.php'));
368
+        } elseif (strpos($this->request->getRequestUri(), '/login/v2') !== false) {
369
+            $serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/login/v2'));
370
+        }
371
+
372
+        $protocol = $this->request->getServerProtocol();
373
+        return $protocol . '://' . $this->request->getServerHost() . $serverPostfix;
374
+    }
375 375
 }
Please login to merge, or discard this patch.