Passed
Push — master ( 79c677...5cd404 )
by Joas
11:32 queued 11s
created
core/Controller/ClientFlowLoginV2Controller.php 1 patch
Indentation   +254 added lines, -254 removed lines patch added patch discarded remove patch
@@ -44,258 +44,258 @@
 block discarded – undo
44 44
 use OCP\Security\ISecureRandom;
45 45
 
46 46
 class ClientFlowLoginV2Controller extends Controller {
47
-	public const TOKEN_NAME = 'client.flow.v2.login.token';
48
-	public const STATE_NAME = 'client.flow.v2.state.token';
49
-
50
-	/** @var LoginFlowV2Service */
51
-	private $loginFlowV2Service;
52
-	/** @var IURLGenerator */
53
-	private $urlGenerator;
54
-	/** @var ISession */
55
-	private $session;
56
-	/** @var ISecureRandom */
57
-	private $random;
58
-	/** @var Defaults */
59
-	private $defaults;
60
-	/** @var string */
61
-	private $userId;
62
-	/** @var IL10N */
63
-	private $l10n;
64
-
65
-	public function __construct(string $appName,
66
-								IRequest $request,
67
-								LoginFlowV2Service $loginFlowV2Service,
68
-								IURLGenerator $urlGenerator,
69
-								ISession $session,
70
-								ISecureRandom $random,
71
-								Defaults $defaults,
72
-								?string $userId,
73
-								IL10N $l10n) {
74
-		parent::__construct($appName, $request);
75
-		$this->loginFlowV2Service = $loginFlowV2Service;
76
-		$this->urlGenerator = $urlGenerator;
77
-		$this->session = $session;
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
-	 * @UseSession
102
-	 */
103
-	public function landing(string $token): 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')
112
-		);
113
-	}
114
-
115
-	/**
116
-	 * @NoCSRFRequired
117
-	 * @PublicPage
118
-	 * @UseSession
119
-	 */
120
-	public function showAuthPickerPage(): 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
-			],
142
-			'guest'
143
-		);
144
-	}
145
-
146
-	/**
147
-	 * @NoAdminRequired
148
-	 * @UseSession
149
-	 * @NoCSRFRequired
150
-	 * @NoSameSiteCookieRequired
151
-	 */
152
-	public function grantPage(string $stateToken): StandaloneTemplateResponse {
153
-		if (!$this->isValidStateToken($stateToken)) {
154
-			return $this->stateTokenForbiddenResponse();
155
-		}
156
-
157
-		try {
158
-			$flow = $this->getFlowByLoginToken();
159
-		} catch (LoginFlowV2NotFoundException $e) {
160
-			return $this->loginTokenForbiddenResponse();
161
-		}
162
-
163
-		return new StandaloneTemplateResponse(
164
-			$this->appName,
165
-			'loginflowv2/grant',
166
-			[
167
-				'client' => $flow->getClientName(),
168
-				'instanceName' => $this->defaults->getName(),
169
-				'urlGenerator' => $this->urlGenerator,
170
-				'stateToken' => $stateToken,
171
-			],
172
-			'guest'
173
-		);
174
-	}
175
-
176
-	/**
177
-	 * @NoAdminRequired
178
-	 * @UseSession
179
-	 */
180
-	public function generateAppPassword(string $stateToken): Response {
181
-		if (!$this->isValidStateToken($stateToken)) {
182
-			return $this->stateTokenForbiddenResponse();
183
-		}
184
-
185
-		try {
186
-			$this->getFlowByLoginToken();
187
-		} catch (LoginFlowV2NotFoundException $e) {
188
-			return $this->loginTokenForbiddenResponse();
189
-		}
190
-
191
-		$loginToken = $this->session->get(self::TOKEN_NAME);
192
-
193
-		// Clear session variables
194
-		$this->session->remove(self::TOKEN_NAME);
195
-		$this->session->remove(self::STATE_NAME);
196
-		$sessionId = $this->session->getId();
197
-
198
-		$result = $this->loginFlowV2Service->flowDone($loginToken, $sessionId, $this->getServerPath(), $this->userId);
199
-
200
-		if ($result) {
201
-			return new StandaloneTemplateResponse(
202
-				$this->appName,
203
-				'loginflowv2/done',
204
-				[],
205
-				'guest'
206
-			);
207
-		}
208
-
209
-		$response = new StandaloneTemplateResponse(
210
-			$this->appName,
211
-			'403',
212
-			[
213
-				'message' => $this->l10n->t('Could not complete login'),
214
-			],
215
-			'guest'
216
-		);
217
-		$response->setStatus(Http::STATUS_FORBIDDEN);
218
-		return $response;
219
-	}
220
-
221
-	/**
222
-	 * @NoCSRFRequired
223
-	 * @PublicPage
224
-	 */
225
-	public function init(): JSONResponse {
226
-		// Get client user agent
227
-		$userAgent = $this->request->getHeader('USER_AGENT');
228
-
229
-		$tokens = $this->loginFlowV2Service->createTokens($userAgent);
230
-
231
-		$data = [
232
-			'poll' => [
233
-				'token' => $tokens->getPollToken(),
234
-				'endpoint' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.poll')
235
-			],
236
-			'login' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.landing', ['token' => $tokens->getLoginToken()]),
237
-		];
238
-
239
-		return new JSONResponse($data);
240
-	}
241
-
242
-	private function isValidStateToken(string $stateToken): bool {
243
-		$currentToken = $this->session->get(self::STATE_NAME);
244
-		if (!is_string($stateToken) || !is_string($currentToken)) {
245
-			return false;
246
-		}
247
-		return hash_equals($currentToken, $stateToken);
248
-	}
249
-
250
-	private function stateTokenForbiddenResponse(): StandaloneTemplateResponse {
251
-		$response = new StandaloneTemplateResponse(
252
-			$this->appName,
253
-			'403',
254
-			[
255
-				'message' => $this->l10n->t('State token does not match'),
256
-			],
257
-			'guest'
258
-		);
259
-		$response->setStatus(Http::STATUS_FORBIDDEN);
260
-		return $response;
261
-	}
262
-
263
-	/**
264
-	 * @return LoginFlowV2
265
-	 * @throws LoginFlowV2NotFoundException
266
-	 */
267
-	private function getFlowByLoginToken(): LoginFlowV2 {
268
-		$currentToken = $this->session->get(self::TOKEN_NAME);
269
-		if (!is_string($currentToken)) {
270
-			throw new LoginFlowV2NotFoundException('Login token not set in session');
271
-		}
272
-
273
-		return $this->loginFlowV2Service->getByLoginToken($currentToken);
274
-	}
275
-
276
-	private function loginTokenForbiddenResponse(): StandaloneTemplateResponse {
277
-		$response = new StandaloneTemplateResponse(
278
-			$this->appName,
279
-			'403',
280
-			[
281
-				'message' => $this->l10n->t('Your login token is invalid or has expired'),
282
-			],
283
-			'guest'
284
-		);
285
-		$response->setStatus(Http::STATUS_FORBIDDEN);
286
-		return $response;
287
-	}
288
-
289
-	private function getServerPath(): string {
290
-		$serverPostfix = '';
291
-
292
-		if (strpos($this->request->getRequestUri(), '/index.php') !== false) {
293
-			$serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/index.php'));
294
-		} elseif (strpos($this->request->getRequestUri(), '/login/v2') !== false) {
295
-			$serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/login/v2'));
296
-		}
297
-
298
-		$protocol = $this->request->getServerProtocol();
299
-		return $protocol . '://' . $this->request->getServerHost() . $serverPostfix;
300
-	}
47
+    public const TOKEN_NAME = 'client.flow.v2.login.token';
48
+    public const STATE_NAME = 'client.flow.v2.state.token';
49
+
50
+    /** @var LoginFlowV2Service */
51
+    private $loginFlowV2Service;
52
+    /** @var IURLGenerator */
53
+    private $urlGenerator;
54
+    /** @var ISession */
55
+    private $session;
56
+    /** @var ISecureRandom */
57
+    private $random;
58
+    /** @var Defaults */
59
+    private $defaults;
60
+    /** @var string */
61
+    private $userId;
62
+    /** @var IL10N */
63
+    private $l10n;
64
+
65
+    public function __construct(string $appName,
66
+                                IRequest $request,
67
+                                LoginFlowV2Service $loginFlowV2Service,
68
+                                IURLGenerator $urlGenerator,
69
+                                ISession $session,
70
+                                ISecureRandom $random,
71
+                                Defaults $defaults,
72
+                                ?string $userId,
73
+                                IL10N $l10n) {
74
+        parent::__construct($appName, $request);
75
+        $this->loginFlowV2Service = $loginFlowV2Service;
76
+        $this->urlGenerator = $urlGenerator;
77
+        $this->session = $session;
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
+     * @UseSession
102
+     */
103
+    public function landing(string $token): 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')
112
+        );
113
+    }
114
+
115
+    /**
116
+     * @NoCSRFRequired
117
+     * @PublicPage
118
+     * @UseSession
119
+     */
120
+    public function showAuthPickerPage(): 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
+            ],
142
+            'guest'
143
+        );
144
+    }
145
+
146
+    /**
147
+     * @NoAdminRequired
148
+     * @UseSession
149
+     * @NoCSRFRequired
150
+     * @NoSameSiteCookieRequired
151
+     */
152
+    public function grantPage(string $stateToken): StandaloneTemplateResponse {
153
+        if (!$this->isValidStateToken($stateToken)) {
154
+            return $this->stateTokenForbiddenResponse();
155
+        }
156
+
157
+        try {
158
+            $flow = $this->getFlowByLoginToken();
159
+        } catch (LoginFlowV2NotFoundException $e) {
160
+            return $this->loginTokenForbiddenResponse();
161
+        }
162
+
163
+        return new StandaloneTemplateResponse(
164
+            $this->appName,
165
+            'loginflowv2/grant',
166
+            [
167
+                'client' => $flow->getClientName(),
168
+                'instanceName' => $this->defaults->getName(),
169
+                'urlGenerator' => $this->urlGenerator,
170
+                'stateToken' => $stateToken,
171
+            ],
172
+            'guest'
173
+        );
174
+    }
175
+
176
+    /**
177
+     * @NoAdminRequired
178
+     * @UseSession
179
+     */
180
+    public function generateAppPassword(string $stateToken): Response {
181
+        if (!$this->isValidStateToken($stateToken)) {
182
+            return $this->stateTokenForbiddenResponse();
183
+        }
184
+
185
+        try {
186
+            $this->getFlowByLoginToken();
187
+        } catch (LoginFlowV2NotFoundException $e) {
188
+            return $this->loginTokenForbiddenResponse();
189
+        }
190
+
191
+        $loginToken = $this->session->get(self::TOKEN_NAME);
192
+
193
+        // Clear session variables
194
+        $this->session->remove(self::TOKEN_NAME);
195
+        $this->session->remove(self::STATE_NAME);
196
+        $sessionId = $this->session->getId();
197
+
198
+        $result = $this->loginFlowV2Service->flowDone($loginToken, $sessionId, $this->getServerPath(), $this->userId);
199
+
200
+        if ($result) {
201
+            return new StandaloneTemplateResponse(
202
+                $this->appName,
203
+                'loginflowv2/done',
204
+                [],
205
+                'guest'
206
+            );
207
+        }
208
+
209
+        $response = new StandaloneTemplateResponse(
210
+            $this->appName,
211
+            '403',
212
+            [
213
+                'message' => $this->l10n->t('Could not complete login'),
214
+            ],
215
+            'guest'
216
+        );
217
+        $response->setStatus(Http::STATUS_FORBIDDEN);
218
+        return $response;
219
+    }
220
+
221
+    /**
222
+     * @NoCSRFRequired
223
+     * @PublicPage
224
+     */
225
+    public function init(): JSONResponse {
226
+        // Get client user agent
227
+        $userAgent = $this->request->getHeader('USER_AGENT');
228
+
229
+        $tokens = $this->loginFlowV2Service->createTokens($userAgent);
230
+
231
+        $data = [
232
+            'poll' => [
233
+                'token' => $tokens->getPollToken(),
234
+                'endpoint' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.poll')
235
+            ],
236
+            'login' => $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.landing', ['token' => $tokens->getLoginToken()]),
237
+        ];
238
+
239
+        return new JSONResponse($data);
240
+    }
241
+
242
+    private function isValidStateToken(string $stateToken): bool {
243
+        $currentToken = $this->session->get(self::STATE_NAME);
244
+        if (!is_string($stateToken) || !is_string($currentToken)) {
245
+            return false;
246
+        }
247
+        return hash_equals($currentToken, $stateToken);
248
+    }
249
+
250
+    private function stateTokenForbiddenResponse(): StandaloneTemplateResponse {
251
+        $response = new StandaloneTemplateResponse(
252
+            $this->appName,
253
+            '403',
254
+            [
255
+                'message' => $this->l10n->t('State token does not match'),
256
+            ],
257
+            'guest'
258
+        );
259
+        $response->setStatus(Http::STATUS_FORBIDDEN);
260
+        return $response;
261
+    }
262
+
263
+    /**
264
+     * @return LoginFlowV2
265
+     * @throws LoginFlowV2NotFoundException
266
+     */
267
+    private function getFlowByLoginToken(): LoginFlowV2 {
268
+        $currentToken = $this->session->get(self::TOKEN_NAME);
269
+        if (!is_string($currentToken)) {
270
+            throw new LoginFlowV2NotFoundException('Login token not set in session');
271
+        }
272
+
273
+        return $this->loginFlowV2Service->getByLoginToken($currentToken);
274
+    }
275
+
276
+    private function loginTokenForbiddenResponse(): StandaloneTemplateResponse {
277
+        $response = new StandaloneTemplateResponse(
278
+            $this->appName,
279
+            '403',
280
+            [
281
+                'message' => $this->l10n->t('Your login token is invalid or has expired'),
282
+            ],
283
+            'guest'
284
+        );
285
+        $response->setStatus(Http::STATUS_FORBIDDEN);
286
+        return $response;
287
+    }
288
+
289
+    private function getServerPath(): string {
290
+        $serverPostfix = '';
291
+
292
+        if (strpos($this->request->getRequestUri(), '/index.php') !== false) {
293
+            $serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/index.php'));
294
+        } elseif (strpos($this->request->getRequestUri(), '/login/v2') !== false) {
295
+            $serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/login/v2'));
296
+        }
297
+
298
+        $protocol = $this->request->getServerProtocol();
299
+        return $protocol . '://' . $this->request->getServerHost() . $serverPostfix;
300
+    }
301 301
 }
Please login to merge, or discard this patch.
core/Controller/ClientFlowLoginController.php 1 patch
Indentation   +365 added lines, -365 removed lines patch added patch discarded remove patch
@@ -55,369 +55,369 @@
 block discarded – undo
55 55
 use Symfony\Component\EventDispatcher\GenericEvent;
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 EventDispatcherInterface */
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 EventDispatcherInterface $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
-								EventDispatcherInterface $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 = '') {
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
-			],
222
-			'guest'
223
-		);
224
-
225
-		$response->setContentSecurityPolicy($csp);
226
-		return $response;
227
-	}
228
-
229
-	/**
230
-	 * @NoAdminRequired
231
-	 * @NoCSRFRequired
232
-	 * @NoSameSiteCookieRequired
233
-	 * @UseSession
234
-	 *
235
-	 * @param string $stateToken
236
-	 * @param string $clientIdentifier
237
-	 * @return StandaloneTemplateResponse
238
-	 */
239
-	public function grantPage($stateToken = '',
240
-								 $clientIdentifier = '') {
241
-		if (!$this->isValidToken($stateToken)) {
242
-			return $this->stateTokenForbiddenResponse();
243
-		}
244
-
245
-		$clientName = $this->getClientName();
246
-		$client = null;
247
-		if ($clientIdentifier !== '') {
248
-			$client = $this->clientMapper->getByIdentifier($clientIdentifier);
249
-			$clientName = $client->getName();
250
-		}
251
-
252
-		$csp = new Http\ContentSecurityPolicy();
253
-		if ($client) {
254
-			$csp->addAllowedFormActionDomain($client->getRedirectUri());
255
-		} else {
256
-			$csp->addAllowedFormActionDomain('nc://*');
257
-		}
258
-
259
-		$response = new StandaloneTemplateResponse(
260
-			$this->appName,
261
-			'loginflow/grant',
262
-			[
263
-				'client' => $clientName,
264
-				'clientIdentifier' => $clientIdentifier,
265
-				'instanceName' => $this->defaults->getName(),
266
-				'urlGenerator' => $this->urlGenerator,
267
-				'stateToken' => $stateToken,
268
-				'serverHost' => $this->getServerPath(),
269
-				'oauthState' => $this->session->get('oauth.state'),
270
-			],
271
-			'guest'
272
-		);
273
-
274
-		$response->setContentSecurityPolicy($csp);
275
-		return $response;
276
-	}
277
-
278
-	/**
279
-	 * @NoAdminRequired
280
-	 * @UseSession
281
-	 *
282
-	 * @param string $stateToken
283
-	 * @param string $clientIdentifier
284
-	 * @return Http\RedirectResponse|Response
285
-	 */
286
-	public function generateAppPassword($stateToken,
287
-										$clientIdentifier = '') {
288
-		if (!$this->isValidToken($stateToken)) {
289
-			$this->session->remove(self::STATE_NAME);
290
-			return $this->stateTokenForbiddenResponse();
291
-		}
292
-
293
-		$this->session->remove(self::STATE_NAME);
294
-
295
-		try {
296
-			$sessionId = $this->session->getId();
297
-		} catch (SessionNotAvailableException $ex) {
298
-			$response = new Response();
299
-			$response->setStatus(Http::STATUS_FORBIDDEN);
300
-			return $response;
301
-		}
302
-
303
-		try {
304
-			$sessionToken = $this->tokenProvider->getToken($sessionId);
305
-			$loginName = $sessionToken->getLoginName();
306
-			try {
307
-				$password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
308
-			} catch (PasswordlessTokenException $ex) {
309
-				$password = null;
310
-			}
311
-		} catch (InvalidTokenException $ex) {
312
-			$response = new Response();
313
-			$response->setStatus(Http::STATUS_FORBIDDEN);
314
-			return $response;
315
-		}
316
-
317
-		$clientName = $this->getClientName();
318
-		$client = false;
319
-		if ($clientIdentifier !== '') {
320
-			$client = $this->clientMapper->getByIdentifier($clientIdentifier);
321
-			$clientName = $client->getName();
322
-		}
323
-
324
-		$token = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
325
-		$uid = $this->userSession->getUser()->getUID();
326
-		$generatedToken = $this->tokenProvider->generateToken(
327
-			$token,
328
-			$uid,
329
-			$loginName,
330
-			$password,
331
-			$clientName,
332
-			IToken::PERMANENT_TOKEN,
333
-			IToken::DO_NOT_REMEMBER
334
-		);
335
-
336
-		if ($client) {
337
-			$code = $this->random->generate(128, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
338
-			$accessToken = new AccessToken();
339
-			$accessToken->setClientId($client->getId());
340
-			$accessToken->setEncryptedToken($this->crypto->encrypt($token, $code));
341
-			$accessToken->setHashedCode(hash('sha512', $code));
342
-			$accessToken->setTokenId($generatedToken->getId());
343
-			$this->accessTokenMapper->insert($accessToken);
344
-
345
-			$redirectUri = $client->getRedirectUri();
346
-
347
-			if (parse_url($redirectUri, PHP_URL_QUERY)) {
348
-				$redirectUri .= '&';
349
-			} else {
350
-				$redirectUri .= '?';
351
-			}
352
-
353
-			$redirectUri .= sprintf(
354
-				'state=%s&code=%s',
355
-				urlencode($this->session->get('oauth.state')),
356
-				urlencode($code)
357
-			);
358
-			$this->session->remove('oauth.state');
359
-		} else {
360
-			$redirectUri = 'nc://login/server:' . $this->getServerPath() . '&user:' . urlencode($loginName) . '&password:' . urlencode($token);
361
-
362
-			// Clear the token from the login here
363
-			$this->tokenProvider->invalidateToken($sessionId);
364
-		}
365
-
366
-		$event = new GenericEvent($generatedToken);
367
-		$this->eventDispatcher->dispatch('app_password_created', $event);
368
-
369
-		return new Http\RedirectResponse($redirectUri);
370
-	}
371
-
372
-	/**
373
-	 * @PublicPage
374
-	 */
375
-	public function apptokenRedirect(string $stateToken, string $user, string $password) {
376
-		if (!$this->isValidToken($stateToken)) {
377
-			return $this->stateTokenForbiddenResponse();
378
-		}
379
-
380
-		try {
381
-			$token = $this->tokenProvider->getToken($password);
382
-			if ($token->getLoginName() !== $user) {
383
-				throw new InvalidTokenException('login name does not match');
384
-			}
385
-		} catch (InvalidTokenException $e) {
386
-			$response = new StandaloneTemplateResponse(
387
-				$this->appName,
388
-				'403',
389
-				[
390
-					'message' => $this->l10n->t('Invalid app password'),
391
-				],
392
-				'guest'
393
-			);
394
-			$response->setStatus(Http::STATUS_FORBIDDEN);
395
-			return $response;
396
-		}
397
-
398
-		$redirectUri = 'nc://login/server:' . $this->getServerPath() . '&user:' . urlencode($user) . '&password:' . urlencode($password);
399
-		return new Http\RedirectResponse($redirectUri);
400
-	}
401
-
402
-	private function getServerPath(): string {
403
-		$serverPostfix = '';
404
-
405
-		if (strpos($this->request->getRequestUri(), '/index.php') !== false) {
406
-			$serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/index.php'));
407
-		} elseif (strpos($this->request->getRequestUri(), '/login/flow') !== false) {
408
-			$serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/login/flow'));
409
-		}
410
-
411
-		$protocol = $this->request->getServerProtocol();
412
-
413
-		if ($protocol !== "https") {
414
-			$xForwardedProto = $this->request->getHeader('X-Forwarded-Proto');
415
-			$xForwardedSSL = $this->request->getHeader('X-Forwarded-Ssl');
416
-			if ($xForwardedProto === 'https' || $xForwardedSSL === 'on') {
417
-				$protocol = 'https';
418
-			}
419
-		}
420
-
421
-		return $protocol . "://" . $this->request->getServerHost() . $serverPostfix;
422
-	}
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 EventDispatcherInterface */
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 EventDispatcherInterface $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
+                                EventDispatcherInterface $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 = '') {
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
+            ],
222
+            'guest'
223
+        );
224
+
225
+        $response->setContentSecurityPolicy($csp);
226
+        return $response;
227
+    }
228
+
229
+    /**
230
+     * @NoAdminRequired
231
+     * @NoCSRFRequired
232
+     * @NoSameSiteCookieRequired
233
+     * @UseSession
234
+     *
235
+     * @param string $stateToken
236
+     * @param string $clientIdentifier
237
+     * @return StandaloneTemplateResponse
238
+     */
239
+    public function grantPage($stateToken = '',
240
+                                    $clientIdentifier = '') {
241
+        if (!$this->isValidToken($stateToken)) {
242
+            return $this->stateTokenForbiddenResponse();
243
+        }
244
+
245
+        $clientName = $this->getClientName();
246
+        $client = null;
247
+        if ($clientIdentifier !== '') {
248
+            $client = $this->clientMapper->getByIdentifier($clientIdentifier);
249
+            $clientName = $client->getName();
250
+        }
251
+
252
+        $csp = new Http\ContentSecurityPolicy();
253
+        if ($client) {
254
+            $csp->addAllowedFormActionDomain($client->getRedirectUri());
255
+        } else {
256
+            $csp->addAllowedFormActionDomain('nc://*');
257
+        }
258
+
259
+        $response = new StandaloneTemplateResponse(
260
+            $this->appName,
261
+            'loginflow/grant',
262
+            [
263
+                'client' => $clientName,
264
+                'clientIdentifier' => $clientIdentifier,
265
+                'instanceName' => $this->defaults->getName(),
266
+                'urlGenerator' => $this->urlGenerator,
267
+                'stateToken' => $stateToken,
268
+                'serverHost' => $this->getServerPath(),
269
+                'oauthState' => $this->session->get('oauth.state'),
270
+            ],
271
+            'guest'
272
+        );
273
+
274
+        $response->setContentSecurityPolicy($csp);
275
+        return $response;
276
+    }
277
+
278
+    /**
279
+     * @NoAdminRequired
280
+     * @UseSession
281
+     *
282
+     * @param string $stateToken
283
+     * @param string $clientIdentifier
284
+     * @return Http\RedirectResponse|Response
285
+     */
286
+    public function generateAppPassword($stateToken,
287
+                                        $clientIdentifier = '') {
288
+        if (!$this->isValidToken($stateToken)) {
289
+            $this->session->remove(self::STATE_NAME);
290
+            return $this->stateTokenForbiddenResponse();
291
+        }
292
+
293
+        $this->session->remove(self::STATE_NAME);
294
+
295
+        try {
296
+            $sessionId = $this->session->getId();
297
+        } catch (SessionNotAvailableException $ex) {
298
+            $response = new Response();
299
+            $response->setStatus(Http::STATUS_FORBIDDEN);
300
+            return $response;
301
+        }
302
+
303
+        try {
304
+            $sessionToken = $this->tokenProvider->getToken($sessionId);
305
+            $loginName = $sessionToken->getLoginName();
306
+            try {
307
+                $password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
308
+            } catch (PasswordlessTokenException $ex) {
309
+                $password = null;
310
+            }
311
+        } catch (InvalidTokenException $ex) {
312
+            $response = new Response();
313
+            $response->setStatus(Http::STATUS_FORBIDDEN);
314
+            return $response;
315
+        }
316
+
317
+        $clientName = $this->getClientName();
318
+        $client = false;
319
+        if ($clientIdentifier !== '') {
320
+            $client = $this->clientMapper->getByIdentifier($clientIdentifier);
321
+            $clientName = $client->getName();
322
+        }
323
+
324
+        $token = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
325
+        $uid = $this->userSession->getUser()->getUID();
326
+        $generatedToken = $this->tokenProvider->generateToken(
327
+            $token,
328
+            $uid,
329
+            $loginName,
330
+            $password,
331
+            $clientName,
332
+            IToken::PERMANENT_TOKEN,
333
+            IToken::DO_NOT_REMEMBER
334
+        );
335
+
336
+        if ($client) {
337
+            $code = $this->random->generate(128, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
338
+            $accessToken = new AccessToken();
339
+            $accessToken->setClientId($client->getId());
340
+            $accessToken->setEncryptedToken($this->crypto->encrypt($token, $code));
341
+            $accessToken->setHashedCode(hash('sha512', $code));
342
+            $accessToken->setTokenId($generatedToken->getId());
343
+            $this->accessTokenMapper->insert($accessToken);
344
+
345
+            $redirectUri = $client->getRedirectUri();
346
+
347
+            if (parse_url($redirectUri, PHP_URL_QUERY)) {
348
+                $redirectUri .= '&';
349
+            } else {
350
+                $redirectUri .= '?';
351
+            }
352
+
353
+            $redirectUri .= sprintf(
354
+                'state=%s&code=%s',
355
+                urlencode($this->session->get('oauth.state')),
356
+                urlencode($code)
357
+            );
358
+            $this->session->remove('oauth.state');
359
+        } else {
360
+            $redirectUri = 'nc://login/server:' . $this->getServerPath() . '&user:' . urlencode($loginName) . '&password:' . urlencode($token);
361
+
362
+            // Clear the token from the login here
363
+            $this->tokenProvider->invalidateToken($sessionId);
364
+        }
365
+
366
+        $event = new GenericEvent($generatedToken);
367
+        $this->eventDispatcher->dispatch('app_password_created', $event);
368
+
369
+        return new Http\RedirectResponse($redirectUri);
370
+    }
371
+
372
+    /**
373
+     * @PublicPage
374
+     */
375
+    public function apptokenRedirect(string $stateToken, string $user, string $password) {
376
+        if (!$this->isValidToken($stateToken)) {
377
+            return $this->stateTokenForbiddenResponse();
378
+        }
379
+
380
+        try {
381
+            $token = $this->tokenProvider->getToken($password);
382
+            if ($token->getLoginName() !== $user) {
383
+                throw new InvalidTokenException('login name does not match');
384
+            }
385
+        } catch (InvalidTokenException $e) {
386
+            $response = new StandaloneTemplateResponse(
387
+                $this->appName,
388
+                '403',
389
+                [
390
+                    'message' => $this->l10n->t('Invalid app password'),
391
+                ],
392
+                'guest'
393
+            );
394
+            $response->setStatus(Http::STATUS_FORBIDDEN);
395
+            return $response;
396
+        }
397
+
398
+        $redirectUri = 'nc://login/server:' . $this->getServerPath() . '&user:' . urlencode($user) . '&password:' . urlencode($password);
399
+        return new Http\RedirectResponse($redirectUri);
400
+    }
401
+
402
+    private function getServerPath(): string {
403
+        $serverPostfix = '';
404
+
405
+        if (strpos($this->request->getRequestUri(), '/index.php') !== false) {
406
+            $serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/index.php'));
407
+        } elseif (strpos($this->request->getRequestUri(), '/login/flow') !== false) {
408
+            $serverPostfix = substr($this->request->getRequestUri(), 0, strpos($this->request->getRequestUri(), '/login/flow'));
409
+        }
410
+
411
+        $protocol = $this->request->getServerProtocol();
412
+
413
+        if ($protocol !== "https") {
414
+            $xForwardedProto = $this->request->getHeader('X-Forwarded-Proto');
415
+            $xForwardedSSL = $this->request->getHeader('X-Forwarded-Ssl');
416
+            if ($xForwardedProto === 'https' || $xForwardedSSL === 'on') {
417
+                $protocol = 'https';
418
+            }
419
+        }
420
+
421
+        return $protocol . "://" . $this->request->getServerHost() . $serverPostfix;
422
+    }
423 423
 }
Please login to merge, or discard this patch.