Passed
Push — master ( 257e7c...47b10c )
by Morris
31:48 queued 15:52
created
lib/private/Authentication/TwoFactorAuth/Manager.php 2 patches
Indentation   +336 added lines, -336 removed lines patch added patch discarded remove patch
@@ -47,343 +47,343 @@
 block discarded – undo
47 47
 use function array_filter;
48 48
 
49 49
 class Manager {
50
-	public const SESSION_UID_KEY = 'two_factor_auth_uid';
51
-	public const SESSION_UID_DONE = 'two_factor_auth_passed';
52
-	public const REMEMBER_LOGIN = 'two_factor_remember_login';
53
-	public const BACKUP_CODES_PROVIDER_ID = 'backup_codes';
54
-
55
-	/** @var ProviderLoader */
56
-	private $providerLoader;
57
-
58
-	/** @var IRegistry */
59
-	private $providerRegistry;
60
-
61
-	/** @var MandatoryTwoFactor */
62
-	private $mandatoryTwoFactor;
63
-
64
-	/** @var ISession */
65
-	private $session;
66
-
67
-	/** @var IConfig */
68
-	private $config;
69
-
70
-	/** @var IManager */
71
-	private $activityManager;
72
-
73
-	/** @var LoggerInterface */
74
-	private $logger;
75
-
76
-	/** @var TokenProvider */
77
-	private $tokenProvider;
78
-
79
-	/** @var ITimeFactory */
80
-	private $timeFactory;
81
-
82
-	/** @var EventDispatcherInterface */
83
-	private $dispatcher;
84
-
85
-	public function __construct(ProviderLoader $providerLoader,
86
-								IRegistry $providerRegistry,
87
-								MandatoryTwoFactor $mandatoryTwoFactor,
88
-								ISession $session,
89
-								IConfig $config,
90
-								IManager $activityManager,
91
-								LoggerInterface $logger,
92
-								TokenProvider $tokenProvider,
93
-								ITimeFactory $timeFactory,
94
-								EventDispatcherInterface $eventDispatcher) {
95
-		$this->providerLoader = $providerLoader;
96
-		$this->providerRegistry = $providerRegistry;
97
-		$this->mandatoryTwoFactor = $mandatoryTwoFactor;
98
-		$this->session = $session;
99
-		$this->config = $config;
100
-		$this->activityManager = $activityManager;
101
-		$this->logger = $logger;
102
-		$this->tokenProvider = $tokenProvider;
103
-		$this->timeFactory = $timeFactory;
104
-		$this->dispatcher = $eventDispatcher;
105
-	}
106
-
107
-	/**
108
-	 * Determine whether the user must provide a second factor challenge
109
-	 *
110
-	 * @param IUser $user
111
-	 * @return boolean
112
-	 */
113
-	public function isTwoFactorAuthenticated(IUser $user): bool {
114
-		if ($this->mandatoryTwoFactor->isEnforcedFor($user)) {
115
-			return true;
116
-		}
117
-
118
-		$providerStates = $this->providerRegistry->getProviderStates($user);
119
-		$providers = $this->providerLoader->getProviders($user);
120
-		$fixedStates = $this->fixMissingProviderStates($providerStates, $providers, $user);
121
-		$enabled = array_filter($fixedStates);
122
-		$providerIds = array_keys($enabled);
123
-		$providerIdsWithoutBackupCodes = array_diff($providerIds, [self::BACKUP_CODES_PROVIDER_ID]);
124
-
125
-		return !empty($providerIdsWithoutBackupCodes);
126
-	}
127
-
128
-	/**
129
-	 * Get a 2FA provider by its ID
130
-	 *
131
-	 * @param IUser $user
132
-	 * @param string $challengeProviderId
133
-	 * @return IProvider|null
134
-	 */
135
-	public function getProvider(IUser $user, string $challengeProviderId) {
136
-		$providers = $this->getProviderSet($user)->getProviders();
137
-		return $providers[$challengeProviderId] ?? null;
138
-	}
139
-
140
-	/**
141
-	 * @param IUser $user
142
-	 * @return IActivatableAtLogin[]
143
-	 * @throws Exception
144
-	 */
145
-	public function getLoginSetupProviders(IUser $user): array {
146
-		$providers = $this->providerLoader->getProviders($user);
147
-		return array_filter($providers, function (IProvider $provider) {
148
-			return ($provider instanceof IActivatableAtLogin);
149
-		});
150
-	}
151
-
152
-	/**
153
-	 * Check if the persistant mapping of enabled/disabled state of each available
154
-	 * provider is missing an entry and add it to the registry in that case.
155
-	 *
156
-	 * @todo remove in Nextcloud 17 as by then all providers should have been updated
157
-	 *
158
-	 * @param string[] $providerStates
159
-	 * @param IProvider[] $providers
160
-	 * @param IUser $user
161
-	 * @return string[] the updated $providerStates variable
162
-	 */
163
-	private function fixMissingProviderStates(array $providerStates,
164
-		array $providers, IUser $user): array {
165
-		foreach ($providers as $provider) {
166
-			if (isset($providerStates[$provider->getId()])) {
167
-				// All good
168
-				continue;
169
-			}
170
-
171
-			$enabled = $provider->isTwoFactorAuthEnabledForUser($user);
172
-			if ($enabled) {
173
-				$this->providerRegistry->enableProviderFor($provider, $user);
174
-			} else {
175
-				$this->providerRegistry->disableProviderFor($provider, $user);
176
-			}
177
-			$providerStates[$provider->getId()] = $enabled;
178
-		}
179
-
180
-		return $providerStates;
181
-	}
182
-
183
-	/**
184
-	 * @param array $states
185
-	 * @param IProvider[] $providers
186
-	 */
187
-	private function isProviderMissing(array $states, array $providers): bool {
188
-		$indexed = [];
189
-		foreach ($providers as $provider) {
190
-			$indexed[$provider->getId()] = $provider;
191
-		}
192
-
193
-		$missing = [];
194
-		foreach ($states as $providerId => $enabled) {
195
-			if (!$enabled) {
196
-				// Don't care
197
-				continue;
198
-			}
199
-
200
-			if (!isset($indexed[$providerId])) {
201
-				$missing[] = $providerId;
202
-				$this->logger->alert("two-factor auth provider '$providerId' failed to load",
203
-					[
204
-						'app' => 'core',
205
-					]);
206
-			}
207
-		}
208
-
209
-		if (!empty($missing)) {
210
-			// There was at least one provider missing
211
-			$this->logger->alert(count($missing) . " two-factor auth providers failed to load", ['app' => 'core']);
212
-
213
-			return true;
214
-		}
215
-
216
-		// If we reach this, there was not a single provider missing
217
-		return false;
218
-	}
219
-
220
-	/**
221
-	 * Get the list of 2FA providers for the given user
222
-	 *
223
-	 * @param IUser $user
224
-	 * @throws Exception
225
-	 */
226
-	public function getProviderSet(IUser $user): ProviderSet {
227
-		$providerStates = $this->providerRegistry->getProviderStates($user);
228
-		$providers = $this->providerLoader->getProviders($user);
229
-
230
-		$fixedStates = $this->fixMissingProviderStates($providerStates, $providers, $user);
231
-		$isProviderMissing = $this->isProviderMissing($fixedStates, $providers);
232
-
233
-		$enabled = array_filter($providers, function (IProvider $provider) use ($fixedStates) {
234
-			return $fixedStates[$provider->getId()];
235
-		});
236
-		return new ProviderSet($enabled, $isProviderMissing);
237
-	}
238
-
239
-	/**
240
-	 * Verify the given challenge
241
-	 *
242
-	 * @param string $providerId
243
-	 * @param IUser $user
244
-	 * @param string $challenge
245
-	 * @return boolean
246
-	 */
247
-	public function verifyChallenge(string $providerId, IUser $user, string $challenge): bool {
248
-		$provider = $this->getProvider($user, $providerId);
249
-		if ($provider === null) {
250
-			return false;
251
-		}
252
-
253
-		$passed = $provider->verifyChallenge($user, $challenge);
254
-		if ($passed) {
255
-			if ($this->session->get(self::REMEMBER_LOGIN) === true) {
256
-				// TODO: resolve cyclic dependency and use DI
257
-				\OC::$server->getUserSession()->createRememberMeToken($user);
258
-			}
259
-			$this->session->remove(self::SESSION_UID_KEY);
260
-			$this->session->remove(self::REMEMBER_LOGIN);
261
-			$this->session->set(self::SESSION_UID_DONE, $user->getUID());
262
-
263
-			// Clear token from db
264
-			$sessionId = $this->session->getId();
265
-			$token = $this->tokenProvider->getToken($sessionId);
266
-			$tokenId = $token->getId();
267
-			$this->config->deleteUserValue($user->getUID(), 'login_token_2fa', $tokenId);
268
-
269
-			$dispatchEvent = new GenericEvent($user, ['provider' => $provider->getDisplayName()]);
270
-			$this->dispatcher->dispatch(IProvider::EVENT_SUCCESS, $dispatchEvent);
271
-
272
-			$this->publishEvent($user, 'twofactor_success', [
273
-				'provider' => $provider->getDisplayName(),
274
-			]);
275
-		} else {
276
-			$dispatchEvent = new GenericEvent($user, ['provider' => $provider->getDisplayName()]);
277
-			$this->dispatcher->dispatch(IProvider::EVENT_FAILED, $dispatchEvent);
278
-
279
-			$this->publishEvent($user, 'twofactor_failed', [
280
-				'provider' => $provider->getDisplayName(),
281
-			]);
282
-		}
283
-		return $passed;
284
-	}
285
-
286
-	/**
287
-	 * Push a 2fa event the user's activity stream
288
-	 *
289
-	 * @param IUser $user
290
-	 * @param string $event
291
-	 * @param array $params
292
-	 */
293
-	private function publishEvent(IUser $user, string $event, array $params) {
294
-		$activity = $this->activityManager->generateEvent();
295
-		$activity->setApp('core')
296
-			->setType('security')
297
-			->setAuthor($user->getUID())
298
-			->setAffectedUser($user->getUID())
299
-			->setSubject($event, $params);
300
-		try {
301
-			$this->activityManager->publish($activity);
302
-		} catch (BadMethodCallException $e) {
303
-			$this->logger->warning('could not publish activity', ['app' => 'core', 'exception' => $e]);
304
-		}
305
-	}
306
-
307
-	/**
308
-	 * Check if the currently logged in user needs to pass 2FA
309
-	 *
310
-	 * @param IUser $user the currently logged in user
311
-	 * @return boolean
312
-	 */
313
-	public function needsSecondFactor(IUser $user = null): bool {
314
-		if ($user === null) {
315
-			return false;
316
-		}
317
-
318
-		// If we are authenticated using an app password skip all this
319
-		if ($this->session->exists('app_password')) {
320
-			return false;
321
-		}
322
-
323
-		// First check if the session tells us we should do 2FA (99% case)
324
-		if (!$this->session->exists(self::SESSION_UID_KEY)) {
325
-
326
-			// Check if the session tells us it is 2FA authenticated already
327
-			if ($this->session->exists(self::SESSION_UID_DONE) &&
328
-				$this->session->get(self::SESSION_UID_DONE) === $user->getUID()) {
329
-				return false;
330
-			}
331
-
332
-			/*
50
+    public const SESSION_UID_KEY = 'two_factor_auth_uid';
51
+    public const SESSION_UID_DONE = 'two_factor_auth_passed';
52
+    public const REMEMBER_LOGIN = 'two_factor_remember_login';
53
+    public const BACKUP_CODES_PROVIDER_ID = 'backup_codes';
54
+
55
+    /** @var ProviderLoader */
56
+    private $providerLoader;
57
+
58
+    /** @var IRegistry */
59
+    private $providerRegistry;
60
+
61
+    /** @var MandatoryTwoFactor */
62
+    private $mandatoryTwoFactor;
63
+
64
+    /** @var ISession */
65
+    private $session;
66
+
67
+    /** @var IConfig */
68
+    private $config;
69
+
70
+    /** @var IManager */
71
+    private $activityManager;
72
+
73
+    /** @var LoggerInterface */
74
+    private $logger;
75
+
76
+    /** @var TokenProvider */
77
+    private $tokenProvider;
78
+
79
+    /** @var ITimeFactory */
80
+    private $timeFactory;
81
+
82
+    /** @var EventDispatcherInterface */
83
+    private $dispatcher;
84
+
85
+    public function __construct(ProviderLoader $providerLoader,
86
+                                IRegistry $providerRegistry,
87
+                                MandatoryTwoFactor $mandatoryTwoFactor,
88
+                                ISession $session,
89
+                                IConfig $config,
90
+                                IManager $activityManager,
91
+                                LoggerInterface $logger,
92
+                                TokenProvider $tokenProvider,
93
+                                ITimeFactory $timeFactory,
94
+                                EventDispatcherInterface $eventDispatcher) {
95
+        $this->providerLoader = $providerLoader;
96
+        $this->providerRegistry = $providerRegistry;
97
+        $this->mandatoryTwoFactor = $mandatoryTwoFactor;
98
+        $this->session = $session;
99
+        $this->config = $config;
100
+        $this->activityManager = $activityManager;
101
+        $this->logger = $logger;
102
+        $this->tokenProvider = $tokenProvider;
103
+        $this->timeFactory = $timeFactory;
104
+        $this->dispatcher = $eventDispatcher;
105
+    }
106
+
107
+    /**
108
+     * Determine whether the user must provide a second factor challenge
109
+     *
110
+     * @param IUser $user
111
+     * @return boolean
112
+     */
113
+    public function isTwoFactorAuthenticated(IUser $user): bool {
114
+        if ($this->mandatoryTwoFactor->isEnforcedFor($user)) {
115
+            return true;
116
+        }
117
+
118
+        $providerStates = $this->providerRegistry->getProviderStates($user);
119
+        $providers = $this->providerLoader->getProviders($user);
120
+        $fixedStates = $this->fixMissingProviderStates($providerStates, $providers, $user);
121
+        $enabled = array_filter($fixedStates);
122
+        $providerIds = array_keys($enabled);
123
+        $providerIdsWithoutBackupCodes = array_diff($providerIds, [self::BACKUP_CODES_PROVIDER_ID]);
124
+
125
+        return !empty($providerIdsWithoutBackupCodes);
126
+    }
127
+
128
+    /**
129
+     * Get a 2FA provider by its ID
130
+     *
131
+     * @param IUser $user
132
+     * @param string $challengeProviderId
133
+     * @return IProvider|null
134
+     */
135
+    public function getProvider(IUser $user, string $challengeProviderId) {
136
+        $providers = $this->getProviderSet($user)->getProviders();
137
+        return $providers[$challengeProviderId] ?? null;
138
+    }
139
+
140
+    /**
141
+     * @param IUser $user
142
+     * @return IActivatableAtLogin[]
143
+     * @throws Exception
144
+     */
145
+    public function getLoginSetupProviders(IUser $user): array {
146
+        $providers = $this->providerLoader->getProviders($user);
147
+        return array_filter($providers, function (IProvider $provider) {
148
+            return ($provider instanceof IActivatableAtLogin);
149
+        });
150
+    }
151
+
152
+    /**
153
+     * Check if the persistant mapping of enabled/disabled state of each available
154
+     * provider is missing an entry and add it to the registry in that case.
155
+     *
156
+     * @todo remove in Nextcloud 17 as by then all providers should have been updated
157
+     *
158
+     * @param string[] $providerStates
159
+     * @param IProvider[] $providers
160
+     * @param IUser $user
161
+     * @return string[] the updated $providerStates variable
162
+     */
163
+    private function fixMissingProviderStates(array $providerStates,
164
+        array $providers, IUser $user): array {
165
+        foreach ($providers as $provider) {
166
+            if (isset($providerStates[$provider->getId()])) {
167
+                // All good
168
+                continue;
169
+            }
170
+
171
+            $enabled = $provider->isTwoFactorAuthEnabledForUser($user);
172
+            if ($enabled) {
173
+                $this->providerRegistry->enableProviderFor($provider, $user);
174
+            } else {
175
+                $this->providerRegistry->disableProviderFor($provider, $user);
176
+            }
177
+            $providerStates[$provider->getId()] = $enabled;
178
+        }
179
+
180
+        return $providerStates;
181
+    }
182
+
183
+    /**
184
+     * @param array $states
185
+     * @param IProvider[] $providers
186
+     */
187
+    private function isProviderMissing(array $states, array $providers): bool {
188
+        $indexed = [];
189
+        foreach ($providers as $provider) {
190
+            $indexed[$provider->getId()] = $provider;
191
+        }
192
+
193
+        $missing = [];
194
+        foreach ($states as $providerId => $enabled) {
195
+            if (!$enabled) {
196
+                // Don't care
197
+                continue;
198
+            }
199
+
200
+            if (!isset($indexed[$providerId])) {
201
+                $missing[] = $providerId;
202
+                $this->logger->alert("two-factor auth provider '$providerId' failed to load",
203
+                    [
204
+                        'app' => 'core',
205
+                    ]);
206
+            }
207
+        }
208
+
209
+        if (!empty($missing)) {
210
+            // There was at least one provider missing
211
+            $this->logger->alert(count($missing) . " two-factor auth providers failed to load", ['app' => 'core']);
212
+
213
+            return true;
214
+        }
215
+
216
+        // If we reach this, there was not a single provider missing
217
+        return false;
218
+    }
219
+
220
+    /**
221
+     * Get the list of 2FA providers for the given user
222
+     *
223
+     * @param IUser $user
224
+     * @throws Exception
225
+     */
226
+    public function getProviderSet(IUser $user): ProviderSet {
227
+        $providerStates = $this->providerRegistry->getProviderStates($user);
228
+        $providers = $this->providerLoader->getProviders($user);
229
+
230
+        $fixedStates = $this->fixMissingProviderStates($providerStates, $providers, $user);
231
+        $isProviderMissing = $this->isProviderMissing($fixedStates, $providers);
232
+
233
+        $enabled = array_filter($providers, function (IProvider $provider) use ($fixedStates) {
234
+            return $fixedStates[$provider->getId()];
235
+        });
236
+        return new ProviderSet($enabled, $isProviderMissing);
237
+    }
238
+
239
+    /**
240
+     * Verify the given challenge
241
+     *
242
+     * @param string $providerId
243
+     * @param IUser $user
244
+     * @param string $challenge
245
+     * @return boolean
246
+     */
247
+    public function verifyChallenge(string $providerId, IUser $user, string $challenge): bool {
248
+        $provider = $this->getProvider($user, $providerId);
249
+        if ($provider === null) {
250
+            return false;
251
+        }
252
+
253
+        $passed = $provider->verifyChallenge($user, $challenge);
254
+        if ($passed) {
255
+            if ($this->session->get(self::REMEMBER_LOGIN) === true) {
256
+                // TODO: resolve cyclic dependency and use DI
257
+                \OC::$server->getUserSession()->createRememberMeToken($user);
258
+            }
259
+            $this->session->remove(self::SESSION_UID_KEY);
260
+            $this->session->remove(self::REMEMBER_LOGIN);
261
+            $this->session->set(self::SESSION_UID_DONE, $user->getUID());
262
+
263
+            // Clear token from db
264
+            $sessionId = $this->session->getId();
265
+            $token = $this->tokenProvider->getToken($sessionId);
266
+            $tokenId = $token->getId();
267
+            $this->config->deleteUserValue($user->getUID(), 'login_token_2fa', $tokenId);
268
+
269
+            $dispatchEvent = new GenericEvent($user, ['provider' => $provider->getDisplayName()]);
270
+            $this->dispatcher->dispatch(IProvider::EVENT_SUCCESS, $dispatchEvent);
271
+
272
+            $this->publishEvent($user, 'twofactor_success', [
273
+                'provider' => $provider->getDisplayName(),
274
+            ]);
275
+        } else {
276
+            $dispatchEvent = new GenericEvent($user, ['provider' => $provider->getDisplayName()]);
277
+            $this->dispatcher->dispatch(IProvider::EVENT_FAILED, $dispatchEvent);
278
+
279
+            $this->publishEvent($user, 'twofactor_failed', [
280
+                'provider' => $provider->getDisplayName(),
281
+            ]);
282
+        }
283
+        return $passed;
284
+    }
285
+
286
+    /**
287
+     * Push a 2fa event the user's activity stream
288
+     *
289
+     * @param IUser $user
290
+     * @param string $event
291
+     * @param array $params
292
+     */
293
+    private function publishEvent(IUser $user, string $event, array $params) {
294
+        $activity = $this->activityManager->generateEvent();
295
+        $activity->setApp('core')
296
+            ->setType('security')
297
+            ->setAuthor($user->getUID())
298
+            ->setAffectedUser($user->getUID())
299
+            ->setSubject($event, $params);
300
+        try {
301
+            $this->activityManager->publish($activity);
302
+        } catch (BadMethodCallException $e) {
303
+            $this->logger->warning('could not publish activity', ['app' => 'core', 'exception' => $e]);
304
+        }
305
+    }
306
+
307
+    /**
308
+     * Check if the currently logged in user needs to pass 2FA
309
+     *
310
+     * @param IUser $user the currently logged in user
311
+     * @return boolean
312
+     */
313
+    public function needsSecondFactor(IUser $user = null): bool {
314
+        if ($user === null) {
315
+            return false;
316
+        }
317
+
318
+        // If we are authenticated using an app password skip all this
319
+        if ($this->session->exists('app_password')) {
320
+            return false;
321
+        }
322
+
323
+        // First check if the session tells us we should do 2FA (99% case)
324
+        if (!$this->session->exists(self::SESSION_UID_KEY)) {
325
+
326
+            // Check if the session tells us it is 2FA authenticated already
327
+            if ($this->session->exists(self::SESSION_UID_DONE) &&
328
+                $this->session->get(self::SESSION_UID_DONE) === $user->getUID()) {
329
+                return false;
330
+            }
331
+
332
+            /*
333 333
 			 * If the session is expired check if we are not logged in by a token
334 334
 			 * that still needs 2FA auth
335 335
 			 */
336
-			try {
337
-				$sessionId = $this->session->getId();
338
-				$token = $this->tokenProvider->getToken($sessionId);
339
-				$tokenId = $token->getId();
340
-				$tokensNeeding2FA = $this->config->getUserKeys($user->getUID(), 'login_token_2fa');
341
-
342
-				if (!\in_array((string) $tokenId, $tokensNeeding2FA, true)) {
343
-					$this->session->set(self::SESSION_UID_DONE, $user->getUID());
344
-					return false;
345
-				}
346
-			} catch (InvalidTokenException $e) {
347
-			}
348
-		}
349
-
350
-		if (!$this->isTwoFactorAuthenticated($user)) {
351
-			// There is no second factor any more -> let the user pass
352
-			//   This prevents infinite redirect loops when a user is about
353
-			//   to solve the 2FA challenge, and the provider app is
354
-			//   disabled the same time
355
-			$this->session->remove(self::SESSION_UID_KEY);
356
-
357
-			$keys = $this->config->getUserKeys($user->getUID(), 'login_token_2fa');
358
-			foreach ($keys as $key) {
359
-				$this->config->deleteUserValue($user->getUID(), 'login_token_2fa', $key);
360
-			}
361
-			return false;
362
-		}
363
-
364
-		return true;
365
-	}
366
-
367
-	/**
368
-	 * Prepare the 2FA login
369
-	 *
370
-	 * @param IUser $user
371
-	 * @param boolean $rememberMe
372
-	 */
373
-	public function prepareTwoFactorLogin(IUser $user, bool $rememberMe) {
374
-		$this->session->set(self::SESSION_UID_KEY, $user->getUID());
375
-		$this->session->set(self::REMEMBER_LOGIN, $rememberMe);
376
-
377
-		$id = $this->session->getId();
378
-		$token = $this->tokenProvider->getToken($id);
379
-		$this->config->setUserValue($user->getUID(), 'login_token_2fa', (string) $token->getId(), $this->timeFactory->getTime());
380
-	}
381
-
382
-	public function clearTwoFactorPending(string $userId) {
383
-		$tokensNeeding2FA = $this->config->getUserKeys($userId, 'login_token_2fa');
384
-
385
-		foreach ($tokensNeeding2FA as $tokenId) {
386
-			$this->tokenProvider->invalidateTokenById($userId, (int)$tokenId);
387
-		}
388
-	}
336
+            try {
337
+                $sessionId = $this->session->getId();
338
+                $token = $this->tokenProvider->getToken($sessionId);
339
+                $tokenId = $token->getId();
340
+                $tokensNeeding2FA = $this->config->getUserKeys($user->getUID(), 'login_token_2fa');
341
+
342
+                if (!\in_array((string) $tokenId, $tokensNeeding2FA, true)) {
343
+                    $this->session->set(self::SESSION_UID_DONE, $user->getUID());
344
+                    return false;
345
+                }
346
+            } catch (InvalidTokenException $e) {
347
+            }
348
+        }
349
+
350
+        if (!$this->isTwoFactorAuthenticated($user)) {
351
+            // There is no second factor any more -> let the user pass
352
+            //   This prevents infinite redirect loops when a user is about
353
+            //   to solve the 2FA challenge, and the provider app is
354
+            //   disabled the same time
355
+            $this->session->remove(self::SESSION_UID_KEY);
356
+
357
+            $keys = $this->config->getUserKeys($user->getUID(), 'login_token_2fa');
358
+            foreach ($keys as $key) {
359
+                $this->config->deleteUserValue($user->getUID(), 'login_token_2fa', $key);
360
+            }
361
+            return false;
362
+        }
363
+
364
+        return true;
365
+    }
366
+
367
+    /**
368
+     * Prepare the 2FA login
369
+     *
370
+     * @param IUser $user
371
+     * @param boolean $rememberMe
372
+     */
373
+    public function prepareTwoFactorLogin(IUser $user, bool $rememberMe) {
374
+        $this->session->set(self::SESSION_UID_KEY, $user->getUID());
375
+        $this->session->set(self::REMEMBER_LOGIN, $rememberMe);
376
+
377
+        $id = $this->session->getId();
378
+        $token = $this->tokenProvider->getToken($id);
379
+        $this->config->setUserValue($user->getUID(), 'login_token_2fa', (string) $token->getId(), $this->timeFactory->getTime());
380
+    }
381
+
382
+    public function clearTwoFactorPending(string $userId) {
383
+        $tokensNeeding2FA = $this->config->getUserKeys($userId, 'login_token_2fa');
384
+
385
+        foreach ($tokensNeeding2FA as $tokenId) {
386
+            $this->tokenProvider->invalidateTokenById($userId, (int)$tokenId);
387
+        }
388
+    }
389 389
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -144,7 +144,7 @@  discard block
 block discarded – undo
144 144
 	 */
145 145
 	public function getLoginSetupProviders(IUser $user): array {
146 146
 		$providers = $this->providerLoader->getProviders($user);
147
-		return array_filter($providers, function (IProvider $provider) {
147
+		return array_filter($providers, function(IProvider $provider) {
148 148
 			return ($provider instanceof IActivatableAtLogin);
149 149
 		});
150 150
 	}
@@ -208,7 +208,7 @@  discard block
 block discarded – undo
208 208
 
209 209
 		if (!empty($missing)) {
210 210
 			// There was at least one provider missing
211
-			$this->logger->alert(count($missing) . " two-factor auth providers failed to load", ['app' => 'core']);
211
+			$this->logger->alert(count($missing)." two-factor auth providers failed to load", ['app' => 'core']);
212 212
 
213 213
 			return true;
214 214
 		}
@@ -230,7 +230,7 @@  discard block
 block discarded – undo
230 230
 		$fixedStates = $this->fixMissingProviderStates($providerStates, $providers, $user);
231 231
 		$isProviderMissing = $this->isProviderMissing($fixedStates, $providers);
232 232
 
233
-		$enabled = array_filter($providers, function (IProvider $provider) use ($fixedStates) {
233
+		$enabled = array_filter($providers, function(IProvider $provider) use ($fixedStates) {
234 234
 			return $fixedStates[$provider->getId()];
235 235
 		});
236 236
 		return new ProviderSet($enabled, $isProviderMissing);
@@ -383,7 +383,7 @@  discard block
 block discarded – undo
383 383
 		$tokensNeeding2FA = $this->config->getUserKeys($userId, 'login_token_2fa');
384 384
 
385 385
 		foreach ($tokensNeeding2FA as $tokenId) {
386
-			$this->tokenProvider->invalidateTokenById($userId, (int)$tokenId);
386
+			$this->tokenProvider->invalidateTokenById($userId, (int) $tokenId);
387 387
 		}
388 388
 	}
389 389
 }
Please login to merge, or discard this patch.