Passed
Push — master ( f9d30b...528eb1 )
by Roeland
22:26 queued 10:54
created

TwoFactorChallengeController::setupProviders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 0
dl 0
loc 11
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Christoph Wurst <[email protected]>
6
 * @author Cornelius Kölbel <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Roeland Jago Douma <[email protected]>
10
 *
11
 * @license AGPL-3.0
12
 *
13
 * This code is free software: you can redistribute it and/or modify
14
 * it under the terms of the GNU Affero General Public License, version 3,
15
 * as published by the Free Software Foundation.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License, version 3,
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
24
 *
25
 */
26
27
namespace OC\Core\Controller;
28
29
use OC\Authentication\TwoFactorAuth\Manager;
30
use OC_User;
31
use OC_Util;
32
use OCP\AppFramework\Controller;
33
use OCP\AppFramework\Http\RedirectResponse;
34
use OCP\AppFramework\Http\StandaloneTemplateResponse;
35
use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin;
36
use OCP\Authentication\TwoFactorAuth\IProvider;
37
use OCP\Authentication\TwoFactorAuth\IProvidesCustomCSP;
38
use OCP\Authentication\TwoFactorAuth\TwoFactorException;
39
use OCP\IRequest;
40
use OCP\ISession;
41
use OCP\IURLGenerator;
42
use OCP\IUserSession;
43
44
class TwoFactorChallengeController extends Controller {
45
46
	/** @var Manager */
47
	private $twoFactorManager;
48
49
	/** @var IUserSession */
50
	private $userSession;
51
52
	/** @var ISession */
53
	private $session;
54
55
	/** @var IURLGenerator */
56
	private $urlGenerator;
57
58
	/**
59
	 * @param string $appName
60
	 * @param IRequest $request
61
	 * @param Manager $twoFactorManager
62
	 * @param IUserSession $userSession
63
	 * @param ISession $session
64
	 * @param IURLGenerator $urlGenerator
65
	 */
66
	public function __construct($appName, IRequest $request, Manager $twoFactorManager, IUserSession $userSession,
67
		ISession $session, IURLGenerator $urlGenerator) {
68
		parent::__construct($appName, $request);
69
		$this->twoFactorManager = $twoFactorManager;
70
		$this->userSession = $userSession;
71
		$this->session = $session;
72
		$this->urlGenerator = $urlGenerator;
73
	}
74
75
	/**
76
	 * @return string
77
	 */
78
	protected function getLogoutUrl() {
79
		return OC_User::getLogoutUrl($this->urlGenerator);
80
	}
81
	
82
	/**
83
	 * @param IProvider[] $providers
84
	 */
85
	private function splitProvidersAndBackupCodes(array $providers): array {
86
		$regular = [];
87
		$backup = null;
88
		foreach ($providers as $provider) {
89
			if ($provider->getId() === 'backup_codes') {
90
				$backup = $provider;
91
			} else {
92
				$regular[] = $provider;
93
			}
94
		}
95
96
		return [$regular, $backup];
97
	}
98
99
	/**
100
	 * @NoAdminRequired
101
	 * @NoCSRFRequired
102
	 *
103
	 * @param string $redirect_url
104
	 * @return StandaloneTemplateResponse
105
	 */
106
	public function selectChallenge($redirect_url) {
107
		$user = $this->userSession->getUser();
108
		$providerSet = $this->twoFactorManager->getProviderSet($user);
109
		$allProviders = $providerSet->getProviders();
110
		list($providers, $backupProvider) = $this->splitProvidersAndBackupCodes($allProviders);
111
		$setupProviders = $this->twoFactorManager->getLoginSetupProviders($user);
112
113
		$data = [
114
			'providers' => $providers,
115
			'backupProvider' => $backupProvider,
116
			'providerMissing' => $providerSet->isProviderMissing(),
117
			'redirect_url' => $redirect_url,
118
			'logout_url' => $this->getLogoutUrl(),
119
			'hasSetupProviders' => !empty($setupProviders),
120
		];
121
		return new StandaloneTemplateResponse($this->appName, 'twofactorselectchallenge', $data, 'guest');
122
	}
123
124
	/**
125
	 * @NoAdminRequired
126
	 * @NoCSRFRequired
127
	 * @UseSession
128
	 *
129
	 * @param string $challengeProviderId
130
	 * @param string $redirect_url
131
	 * @return StandaloneTemplateResponse|RedirectResponse
132
	 */
133
	public function showChallenge($challengeProviderId, $redirect_url) {
134
		$user = $this->userSession->getUser();
135
		$providerSet = $this->twoFactorManager->getProviderSet($user);
136
		$provider = $providerSet->getProvider($challengeProviderId);
137
138
		if (is_null($provider)) {
139
			return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
140
		}
141
142
		$backupProvider = $providerSet->getProvider('backup_codes');
143
		if (!is_null($backupProvider) && $backupProvider->getId() === $provider->getId()) {
144
			// Don't show the backup provider link if we're already showing that provider's challenge
145
			$backupProvider = null;
146
		}
147
148
		$errorMessage = '';
149
		$error = false;
150
		if ($this->session->exists('two_factor_auth_error')) {
151
			$this->session->remove('two_factor_auth_error');
152
			$error = true;
153
			$errorMessage = $this->session->get("two_factor_auth_error_message");
154
			$this->session->remove('two_factor_auth_error_message');
155
		}
156
		$tmpl = $provider->getTemplate($user);
157
		$tmpl->assign('redirect_url', $redirect_url);
158
		$data = [
159
			'error' => $error,
160
			'error_message' => $errorMessage,
161
			'provider' => $provider,
162
			'backupProvider' => $backupProvider,
163
			'logout_url' => $this->getLogoutUrl(),
164
			'redirect_url' => $redirect_url,
165
			'template' => $tmpl->fetchPage(),
166
		];
167
		$response = new StandaloneTemplateResponse($this->appName, 'twofactorshowchallenge', $data, 'guest');
168
		if ($provider instanceof IProvidesCustomCSP) {
169
			$response->setContentSecurityPolicy($provider->getCSP());
170
		}
171
		return $response;
172
	}
173
174
	/**
175
	 * @NoAdminRequired
176
	 * @NoCSRFRequired
177
	 * @UseSession
178
	 *
179
	 * @UserRateThrottle(limit=5, period=100)
180
	 *
181
	 * @param string $challengeProviderId
182
	 * @param string $challenge
183
	 * @param string $redirect_url
184
	 * @return RedirectResponse
185
	 */
186
	public function solveChallenge($challengeProviderId, $challenge, $redirect_url = null) {
187
		$user = $this->userSession->getUser();
188
		$provider = $this->twoFactorManager->getProvider($user, $challengeProviderId);
189
		if (is_null($provider)) {
190
			return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
191
		}
192
193
		try {
194
			if ($this->twoFactorManager->verifyChallenge($challengeProviderId, $user, $challenge)) {
195
				if (!is_null($redirect_url)) {
196
					return new RedirectResponse($this->urlGenerator->getAbsoluteURL(urldecode($redirect_url)));
197
				}
198
				return new RedirectResponse(OC_Util::getDefaultPageUrl());
199
			}
200
		} catch (TwoFactorException $e) {
201
			/*
202
			 * The 2FA App threw an TwoFactorException. Now we display more
203
			 * information to the user. The exception text is stored in the
204
			 * session to be used in showChallenge()
205
			 */
206
			$this->session->set('two_factor_auth_error_message', $e->getMessage());
207
		}
208
209
		$this->session->set('two_factor_auth_error', true);
210
		return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.showChallenge', [
211
			'challengeProviderId' => $provider->getId(),
212
			'redirect_url' => $redirect_url,
213
		]));
214
	}
215
216
	/**
217
	 * @NoAdminRequired
218
	 * @NoCSRFRequired
219
	 */
220
	public function setupProviders() {
221
		$user = $this->userSession->getUser();
222
		$setupProviders = $this->twoFactorManager->getLoginSetupProviders($user);
223
224
		$data = [
225
			'providers' => $setupProviders,
226
			'logout_url' => $this->getLogoutUrl(),
227
		];
228
229
		$response = new StandaloneTemplateResponse($this->appName, 'twofactorsetupselection', $data, 'guest');
230
		return $response;
231
	}
232
233
	/**
234
	 * @NoAdminRequired
235
	 * @NoCSRFRequired
236
	 */
237
	public function setupProvider(string $providerId) {
238
		$user = $this->userSession->getUser();
239
		$providers = $this->twoFactorManager->getLoginSetupProviders($user);
240
241
		$provider = null;
242
		foreach ($providers as $p) {
243
			if ($p->getId() === $providerId) {
244
				$provider = $p;
245
				break;
246
			}
247
		}
248
249
		if ($provider === null) {
250
			return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
251
		}
252
253
		/** @var IActivatableAtLogin $provider */
254
		$tmpl = $provider->getLoginSetup($user)->getBody();
255
		$data = [
256
			'provider' => $provider,
257
			'logout_url' => $this->getLogoutUrl(),
258
			'template' => $tmpl->fetchPage(),
259
		];
260
		$response = new StandaloneTemplateResponse($this->appName, 'twofactorsetupchallenge', $data, 'guest');
261
		return $response;
262
	}
263
264
	/**
265
	 * @NoAdminRequired
266
	 * @NoCSRFRequired
267
	 *
268
	 * @todo handle the extreme edge case of an invalid provider ID and redirect to the provider selection page
269
	 */
270
	public function confirmProviderSetup(string $providerId) {
271
		return new RedirectResponse($this->urlGenerator->linkToRoute(
272
			'core.TwoFactorChallenge.showChallenge',
273
			[
274
				'challengeProviderId' => $providerId,
275
			]
276
		));
277
	}
278
279
}
280