Completed
Push — master ( 83508d...7052aa )
by Lukas
50s
created
lib/private/Authentication/TwoFactorAuth/Manager.php 1 patch
Indentation   +288 added lines, -288 removed lines patch added patch discarded remove patch
@@ -41,296 +41,296 @@
 block discarded – undo
41 41
 
42 42
 class Manager {
43 43
 
44
-	const SESSION_UID_KEY = 'two_factor_auth_uid';
45
-	const SESSION_UID_DONE = 'two_factor_auth_passed';
46
-	const BACKUP_CODES_APP_ID = 'twofactor_backupcodes';
47
-	const BACKUP_CODES_PROVIDER_ID = 'backup_codes';
48
-	const REMEMBER_LOGIN = 'two_factor_remember_login';
49
-
50
-	/** @var AppManager */
51
-	private $appManager;
52
-
53
-	/** @var ISession */
54
-	private $session;
55
-
56
-	/** @var IConfig */
57
-	private $config;
58
-
59
-	/** @var IManager */
60
-	private $activityManager;
61
-
62
-	/** @var ILogger */
63
-	private $logger;
64
-
65
-	/** @var TokenProvider */
66
-	private $tokenProvider;
67
-
68
-	/** @var ITimeFactory */
69
-	private $timeFactory;
70
-
71
-	/**
72
-	 * @param AppManager $appManager
73
-	 * @param ISession $session
74
-	 * @param IConfig $config
75
-	 * @param IManager $activityManager
76
-	 * @param ILogger $logger
77
-	 * @param TokenProvider $tokenProvider
78
-	 * @param ITimeFactory $timeFactory
79
-	 */
80
-	public function __construct(AppManager $appManager,
81
-								ISession $session,
82
-								IConfig $config,
83
-								IManager $activityManager,
84
-								ILogger $logger,
85
-								TokenProvider $tokenProvider,
86
-								ITimeFactory $timeFactory) {
87
-		$this->appManager = $appManager;
88
-		$this->session = $session;
89
-		$this->config = $config;
90
-		$this->activityManager = $activityManager;
91
-		$this->logger = $logger;
92
-		$this->tokenProvider = $tokenProvider;
93
-		$this->timeFactory = $timeFactory;
94
-	}
95
-
96
-	/**
97
-	 * Determine whether the user must provide a second factor challenge
98
-	 *
99
-	 * @param IUser $user
100
-	 * @return boolean
101
-	 */
102
-	public function isTwoFactorAuthenticated(IUser $user) {
103
-		$twoFactorEnabled = ((int) $this->config->getUserValue($user->getUID(), 'core', 'two_factor_auth_disabled', 0)) === 0;
104
-		return $twoFactorEnabled && count($this->getProviders($user)) > 0;
105
-	}
106
-
107
-	/**
108
-	 * Disable 2FA checks for the given user
109
-	 *
110
-	 * @param IUser $user
111
-	 */
112
-	public function disableTwoFactorAuthentication(IUser $user) {
113
-		$this->config->setUserValue($user->getUID(), 'core', 'two_factor_auth_disabled', 1);
114
-	}
115
-
116
-	/**
117
-	 * Enable all 2FA checks for the given user
118
-	 *
119
-	 * @param IUser $user
120
-	 */
121
-	public function enableTwoFactorAuthentication(IUser $user) {
122
-		$this->config->deleteUserValue($user->getUID(), 'core', 'two_factor_auth_disabled');
123
-	}
124
-
125
-	/**
126
-	 * Get a 2FA provider by its ID
127
-	 *
128
-	 * @param IUser $user
129
-	 * @param string $challengeProviderId
130
-	 * @return IProvider|null
131
-	 */
132
-	public function getProvider(IUser $user, $challengeProviderId) {
133
-		$providers = $this->getProviders($user, true);
134
-		return isset($providers[$challengeProviderId]) ? $providers[$challengeProviderId] : null;
135
-	}
136
-
137
-	/**
138
-	 * @param IUser $user
139
-	 * @return IProvider|null the backup provider, if enabled for the given user
140
-	 */
141
-	public function getBackupProvider(IUser $user) {
142
-		$providers = $this->getProviders($user, true);
143
-		if (!isset($providers[self::BACKUP_CODES_PROVIDER_ID])) {
144
-			return null;
145
-		}
146
-		return $providers[self::BACKUP_CODES_PROVIDER_ID];
147
-	}
148
-
149
-	/**
150
-	 * Get the list of 2FA providers for the given user
151
-	 *
152
-	 * @param IUser $user
153
-	 * @param bool $includeBackupApp
154
-	 * @return IProvider[]
155
-	 * @throws Exception
156
-	 */
157
-	public function getProviders(IUser $user, $includeBackupApp = false) {
158
-		$allApps = $this->appManager->getEnabledAppsForUser($user);
159
-		$providers = [];
160
-
161
-		foreach ($allApps as $appId) {
162
-			if (!$includeBackupApp && $appId === self::BACKUP_CODES_APP_ID) {
163
-				continue;
164
-			}
165
-
166
-			$info = $this->appManager->getAppInfo($appId);
167
-			if (isset($info['two-factor-providers'])) {
168
-				$providerClasses = $info['two-factor-providers'];
169
-				foreach ($providerClasses as $class) {
170
-					try {
171
-						$this->loadTwoFactorApp($appId);
172
-						$provider = OC::$server->query($class);
173
-						$providers[$provider->getId()] = $provider;
174
-					} catch (QueryException $exc) {
175
-						// Provider class can not be resolved
176
-						throw new Exception("Could not load two-factor auth provider $class");
177
-					}
178
-				}
179
-			}
180
-		}
181
-
182
-		return array_filter($providers, function ($provider) use ($user) {
183
-			/* @var $provider IProvider */
184
-			return $provider->isTwoFactorAuthEnabledForUser($user);
185
-		});
186
-	}
187
-
188
-	/**
189
-	 * Load an app by ID if it has not been loaded yet
190
-	 *
191
-	 * @param string $appId
192
-	 */
193
-	protected function loadTwoFactorApp($appId) {
194
-		if (!OC_App::isAppLoaded($appId)) {
195
-			OC_App::loadApp($appId);
196
-		}
197
-	}
198
-
199
-	/**
200
-	 * Verify the given challenge
201
-	 *
202
-	 * @param string $providerId
203
-	 * @param IUser $user
204
-	 * @param string $challenge
205
-	 * @return boolean
206
-	 */
207
-	public function verifyChallenge($providerId, IUser $user, $challenge) {
208
-		$provider = $this->getProvider($user, $providerId);
209
-		if (is_null($provider)) {
210
-			return false;
211
-		}
212
-
213
-		$passed = $provider->verifyChallenge($user, $challenge);
214
-		if ($passed) {
215
-			if ($this->session->get(self::REMEMBER_LOGIN) === true) {
216
-				// TODO: resolve cyclic dependency and use DI
217
-				\OC::$server->getUserSession()->createRememberMeToken($user);
218
-			}
219
-			$this->session->remove(self::SESSION_UID_KEY);
220
-			$this->session->remove(self::REMEMBER_LOGIN);
221
-			$this->session->set(self::SESSION_UID_DONE, $user->getUID());
222
-
223
-			// Clear token from db
224
-			$sessionId = $this->session->getId();
225
-			$token = $this->tokenProvider->getToken($sessionId);
226
-			$tokenId = $token->getId();
227
-			$this->config->deleteUserValue($user->getUID(), 'login_token_2fa', $tokenId);
228
-
229
-			$this->publishEvent($user, 'twofactor_success', [
230
-				'provider' => $provider->getDisplayName(),
231
-			]);
232
-		} else {
233
-			$this->publishEvent($user, 'twofactor_failed', [
234
-				'provider' => $provider->getDisplayName(),
235
-			]);
236
-		}
237
-		return $passed;
238
-	}
239
-
240
-	/**
241
-	 * Push a 2fa event the user's activity stream
242
-	 *
243
-	 * @param IUser $user
244
-	 * @param string $event
245
-	 */
246
-	private function publishEvent(IUser $user, $event, array $params) {
247
-		$activity = $this->activityManager->generateEvent();
248
-		$activity->setApp('core')
249
-			->setType('security')
250
-			->setAuthor($user->getUID())
251
-			->setAffectedUser($user->getUID())
252
-			->setSubject($event, $params);
253
-		try {
254
-			$this->activityManager->publish($activity);
255
-		} catch (BadMethodCallException $e) {
256
-			$this->logger->warning('could not publish backup code creation activity', ['app' => 'core']);
257
-			$this->logger->logException($e, ['app' => 'core']);
258
-		}
259
-	}
260
-
261
-	/**
262
-	 * Check if the currently logged in user needs to pass 2FA
263
-	 *
264
-	 * @param IUser $user the currently logged in user
265
-	 * @return boolean
266
-	 */
267
-	public function needsSecondFactor(IUser $user = null) {
268
-		if ($user === null) {
269
-			return false;
270
-		}
271
-
272
-		// If we are authenticated using an app password skip all this
273
-		if ($this->session->exists('app_password')) {
274
-			return false;
275
-		}
276
-
277
-		// First check if the session tells us we should do 2FA (99% case)
278
-		if (!$this->session->exists(self::SESSION_UID_KEY)) {
279
-
280
-			// Check if the session tells us it is 2FA authenticated already
281
-			if ($this->session->exists(self::SESSION_UID_DONE) &&
282
-				$this->session->get(self::SESSION_UID_DONE) === $user->getUID()) {
283
-				return false;
284
-			}
285
-
286
-			/*
44
+    const SESSION_UID_KEY = 'two_factor_auth_uid';
45
+    const SESSION_UID_DONE = 'two_factor_auth_passed';
46
+    const BACKUP_CODES_APP_ID = 'twofactor_backupcodes';
47
+    const BACKUP_CODES_PROVIDER_ID = 'backup_codes';
48
+    const REMEMBER_LOGIN = 'two_factor_remember_login';
49
+
50
+    /** @var AppManager */
51
+    private $appManager;
52
+
53
+    /** @var ISession */
54
+    private $session;
55
+
56
+    /** @var IConfig */
57
+    private $config;
58
+
59
+    /** @var IManager */
60
+    private $activityManager;
61
+
62
+    /** @var ILogger */
63
+    private $logger;
64
+
65
+    /** @var TokenProvider */
66
+    private $tokenProvider;
67
+
68
+    /** @var ITimeFactory */
69
+    private $timeFactory;
70
+
71
+    /**
72
+     * @param AppManager $appManager
73
+     * @param ISession $session
74
+     * @param IConfig $config
75
+     * @param IManager $activityManager
76
+     * @param ILogger $logger
77
+     * @param TokenProvider $tokenProvider
78
+     * @param ITimeFactory $timeFactory
79
+     */
80
+    public function __construct(AppManager $appManager,
81
+                                ISession $session,
82
+                                IConfig $config,
83
+                                IManager $activityManager,
84
+                                ILogger $logger,
85
+                                TokenProvider $tokenProvider,
86
+                                ITimeFactory $timeFactory) {
87
+        $this->appManager = $appManager;
88
+        $this->session = $session;
89
+        $this->config = $config;
90
+        $this->activityManager = $activityManager;
91
+        $this->logger = $logger;
92
+        $this->tokenProvider = $tokenProvider;
93
+        $this->timeFactory = $timeFactory;
94
+    }
95
+
96
+    /**
97
+     * Determine whether the user must provide a second factor challenge
98
+     *
99
+     * @param IUser $user
100
+     * @return boolean
101
+     */
102
+    public function isTwoFactorAuthenticated(IUser $user) {
103
+        $twoFactorEnabled = ((int) $this->config->getUserValue($user->getUID(), 'core', 'two_factor_auth_disabled', 0)) === 0;
104
+        return $twoFactorEnabled && count($this->getProviders($user)) > 0;
105
+    }
106
+
107
+    /**
108
+     * Disable 2FA checks for the given user
109
+     *
110
+     * @param IUser $user
111
+     */
112
+    public function disableTwoFactorAuthentication(IUser $user) {
113
+        $this->config->setUserValue($user->getUID(), 'core', 'two_factor_auth_disabled', 1);
114
+    }
115
+
116
+    /**
117
+     * Enable all 2FA checks for the given user
118
+     *
119
+     * @param IUser $user
120
+     */
121
+    public function enableTwoFactorAuthentication(IUser $user) {
122
+        $this->config->deleteUserValue($user->getUID(), 'core', 'two_factor_auth_disabled');
123
+    }
124
+
125
+    /**
126
+     * Get a 2FA provider by its ID
127
+     *
128
+     * @param IUser $user
129
+     * @param string $challengeProviderId
130
+     * @return IProvider|null
131
+     */
132
+    public function getProvider(IUser $user, $challengeProviderId) {
133
+        $providers = $this->getProviders($user, true);
134
+        return isset($providers[$challengeProviderId]) ? $providers[$challengeProviderId] : null;
135
+    }
136
+
137
+    /**
138
+     * @param IUser $user
139
+     * @return IProvider|null the backup provider, if enabled for the given user
140
+     */
141
+    public function getBackupProvider(IUser $user) {
142
+        $providers = $this->getProviders($user, true);
143
+        if (!isset($providers[self::BACKUP_CODES_PROVIDER_ID])) {
144
+            return null;
145
+        }
146
+        return $providers[self::BACKUP_CODES_PROVIDER_ID];
147
+    }
148
+
149
+    /**
150
+     * Get the list of 2FA providers for the given user
151
+     *
152
+     * @param IUser $user
153
+     * @param bool $includeBackupApp
154
+     * @return IProvider[]
155
+     * @throws Exception
156
+     */
157
+    public function getProviders(IUser $user, $includeBackupApp = false) {
158
+        $allApps = $this->appManager->getEnabledAppsForUser($user);
159
+        $providers = [];
160
+
161
+        foreach ($allApps as $appId) {
162
+            if (!$includeBackupApp && $appId === self::BACKUP_CODES_APP_ID) {
163
+                continue;
164
+            }
165
+
166
+            $info = $this->appManager->getAppInfo($appId);
167
+            if (isset($info['two-factor-providers'])) {
168
+                $providerClasses = $info['two-factor-providers'];
169
+                foreach ($providerClasses as $class) {
170
+                    try {
171
+                        $this->loadTwoFactorApp($appId);
172
+                        $provider = OC::$server->query($class);
173
+                        $providers[$provider->getId()] = $provider;
174
+                    } catch (QueryException $exc) {
175
+                        // Provider class can not be resolved
176
+                        throw new Exception("Could not load two-factor auth provider $class");
177
+                    }
178
+                }
179
+            }
180
+        }
181
+
182
+        return array_filter($providers, function ($provider) use ($user) {
183
+            /* @var $provider IProvider */
184
+            return $provider->isTwoFactorAuthEnabledForUser($user);
185
+        });
186
+    }
187
+
188
+    /**
189
+     * Load an app by ID if it has not been loaded yet
190
+     *
191
+     * @param string $appId
192
+     */
193
+    protected function loadTwoFactorApp($appId) {
194
+        if (!OC_App::isAppLoaded($appId)) {
195
+            OC_App::loadApp($appId);
196
+        }
197
+    }
198
+
199
+    /**
200
+     * Verify the given challenge
201
+     *
202
+     * @param string $providerId
203
+     * @param IUser $user
204
+     * @param string $challenge
205
+     * @return boolean
206
+     */
207
+    public function verifyChallenge($providerId, IUser $user, $challenge) {
208
+        $provider = $this->getProvider($user, $providerId);
209
+        if (is_null($provider)) {
210
+            return false;
211
+        }
212
+
213
+        $passed = $provider->verifyChallenge($user, $challenge);
214
+        if ($passed) {
215
+            if ($this->session->get(self::REMEMBER_LOGIN) === true) {
216
+                // TODO: resolve cyclic dependency and use DI
217
+                \OC::$server->getUserSession()->createRememberMeToken($user);
218
+            }
219
+            $this->session->remove(self::SESSION_UID_KEY);
220
+            $this->session->remove(self::REMEMBER_LOGIN);
221
+            $this->session->set(self::SESSION_UID_DONE, $user->getUID());
222
+
223
+            // Clear token from db
224
+            $sessionId = $this->session->getId();
225
+            $token = $this->tokenProvider->getToken($sessionId);
226
+            $tokenId = $token->getId();
227
+            $this->config->deleteUserValue($user->getUID(), 'login_token_2fa', $tokenId);
228
+
229
+            $this->publishEvent($user, 'twofactor_success', [
230
+                'provider' => $provider->getDisplayName(),
231
+            ]);
232
+        } else {
233
+            $this->publishEvent($user, 'twofactor_failed', [
234
+                'provider' => $provider->getDisplayName(),
235
+            ]);
236
+        }
237
+        return $passed;
238
+    }
239
+
240
+    /**
241
+     * Push a 2fa event the user's activity stream
242
+     *
243
+     * @param IUser $user
244
+     * @param string $event
245
+     */
246
+    private function publishEvent(IUser $user, $event, array $params) {
247
+        $activity = $this->activityManager->generateEvent();
248
+        $activity->setApp('core')
249
+            ->setType('security')
250
+            ->setAuthor($user->getUID())
251
+            ->setAffectedUser($user->getUID())
252
+            ->setSubject($event, $params);
253
+        try {
254
+            $this->activityManager->publish($activity);
255
+        } catch (BadMethodCallException $e) {
256
+            $this->logger->warning('could not publish backup code creation activity', ['app' => 'core']);
257
+            $this->logger->logException($e, ['app' => 'core']);
258
+        }
259
+    }
260
+
261
+    /**
262
+     * Check if the currently logged in user needs to pass 2FA
263
+     *
264
+     * @param IUser $user the currently logged in user
265
+     * @return boolean
266
+     */
267
+    public function needsSecondFactor(IUser $user = null) {
268
+        if ($user === null) {
269
+            return false;
270
+        }
271
+
272
+        // If we are authenticated using an app password skip all this
273
+        if ($this->session->exists('app_password')) {
274
+            return false;
275
+        }
276
+
277
+        // First check if the session tells us we should do 2FA (99% case)
278
+        if (!$this->session->exists(self::SESSION_UID_KEY)) {
279
+
280
+            // Check if the session tells us it is 2FA authenticated already
281
+            if ($this->session->exists(self::SESSION_UID_DONE) &&
282
+                $this->session->get(self::SESSION_UID_DONE) === $user->getUID()) {
283
+                return false;
284
+            }
285
+
286
+            /*
287 287
 			 * If the session is expired check if we are not logged in by a token
288 288
 			 * that still needs 2FA auth
289 289
 			 */
290
-			try {
291
-				$sessionId = $this->session->getId();
292
-				$token = $this->tokenProvider->getToken($sessionId);
293
-				$tokenId = $token->getId();
294
-				$tokensNeeding2FA = $this->config->getUserKeys($user->getUID(), 'login_token_2fa');
295
-
296
-				if (!in_array($tokenId, $tokensNeeding2FA, true)) {
297
-					$this->session->set(self::SESSION_UID_DONE, $user->getUID());
298
-					return false;
299
-				}
300
-			} catch (InvalidTokenException $e) {
301
-			}
302
-		}
303
-
304
-		if (!$this->isTwoFactorAuthenticated($user)) {
305
-			// There is no second factor any more -> let the user pass
306
-			//   This prevents infinite redirect loops when a user is about
307
-			//   to solve the 2FA challenge, and the provider app is
308
-			//   disabled the same time
309
-			$this->session->remove(self::SESSION_UID_KEY);
310
-
311
-			$keys = $this->config->getUserKeys($user->getUID(), 'login_token_2fa');
312
-			foreach ($keys as $key) {
313
-				$this->config->deleteUserValue($user->getUID(), 'login_token_2fa', $key);
314
-			}
315
-			return false;
316
-		}
317
-
318
-		return true;
319
-	}
320
-
321
-	/**
322
-	 * Prepare the 2FA login
323
-	 *
324
-	 * @param IUser $user
325
-	 * @param boolean $rememberMe
326
-	 */
327
-	public function prepareTwoFactorLogin(IUser $user, $rememberMe) {
328
-		$this->session->set(self::SESSION_UID_KEY, $user->getUID());
329
-		$this->session->set(self::REMEMBER_LOGIN, $rememberMe);
330
-
331
-		$id = $this->session->getId();
332
-		$token = $this->tokenProvider->getToken($id);
333
-		$this->config->setUserValue($user->getUID(), 'login_token_2fa', $token->getId(), $this->timeFactory->getTime());
334
-	}
290
+            try {
291
+                $sessionId = $this->session->getId();
292
+                $token = $this->tokenProvider->getToken($sessionId);
293
+                $tokenId = $token->getId();
294
+                $tokensNeeding2FA = $this->config->getUserKeys($user->getUID(), 'login_token_2fa');
295
+
296
+                if (!in_array($tokenId, $tokensNeeding2FA, true)) {
297
+                    $this->session->set(self::SESSION_UID_DONE, $user->getUID());
298
+                    return false;
299
+                }
300
+            } catch (InvalidTokenException $e) {
301
+            }
302
+        }
303
+
304
+        if (!$this->isTwoFactorAuthenticated($user)) {
305
+            // There is no second factor any more -> let the user pass
306
+            //   This prevents infinite redirect loops when a user is about
307
+            //   to solve the 2FA challenge, and the provider app is
308
+            //   disabled the same time
309
+            $this->session->remove(self::SESSION_UID_KEY);
310
+
311
+            $keys = $this->config->getUserKeys($user->getUID(), 'login_token_2fa');
312
+            foreach ($keys as $key) {
313
+                $this->config->deleteUserValue($user->getUID(), 'login_token_2fa', $key);
314
+            }
315
+            return false;
316
+        }
317
+
318
+        return true;
319
+    }
320
+
321
+    /**
322
+     * Prepare the 2FA login
323
+     *
324
+     * @param IUser $user
325
+     * @param boolean $rememberMe
326
+     */
327
+    public function prepareTwoFactorLogin(IUser $user, $rememberMe) {
328
+        $this->session->set(self::SESSION_UID_KEY, $user->getUID());
329
+        $this->session->set(self::REMEMBER_LOGIN, $rememberMe);
330
+
331
+        $id = $this->session->getId();
332
+        $token = $this->tokenProvider->getToken($id);
333
+        $this->config->setUserValue($user->getUID(), 'login_token_2fa', $token->getId(), $this->timeFactory->getTime());
334
+    }
335 335
 
336 336
 }
Please login to merge, or discard this patch.