Completed
Push — stable13 ( 3aaeaf...afed9a )
by Morris
84:14 queued 65:55
created
apps/oauth2/lib/Controller/OauthApiController.php 1 patch
Indentation   +144 added lines, -144 removed lines patch added patch discarded remove patch
@@ -38,148 +38,148 @@
 block discarded – undo
38 38
 use OCP\Security\ISecureRandom;
39 39
 
40 40
 class OauthApiController extends Controller {
41
-	/** @var AccessTokenMapper */
42
-	private $accessTokenMapper;
43
-	/** @var ClientMapper */
44
-	private $clientMapper;
45
-	/** @var ICrypto */
46
-	private $crypto;
47
-	/** @var TokenProvider */
48
-	private $tokenProvider;
49
-	/** @var ISecureRandom */
50
-	private $secureRandom;
51
-	/** @var ITimeFactory */
52
-	private $time;
53
-	/** @var Throttler */
54
-	private $throttler;
55
-
56
-	/**
57
-	 * @param string $appName
58
-	 * @param IRequest $request
59
-	 * @param ICrypto $crypto
60
-	 * @param AccessTokenMapper $accessTokenMapper
61
-	 * @param ClientMapper $clientMapper
62
-	 * @param TokenProvider $tokenProvider
63
-	 * @param ISecureRandom $secureRandom
64
-	 * @param ITimeFactory $time
65
-	 * @param Throttler $throttler
66
-	 */
67
-	public function __construct($appName,
68
-								IRequest $request,
69
-								ICrypto $crypto,
70
-								AccessTokenMapper $accessTokenMapper,
71
-								ClientMapper $clientMapper,
72
-								TokenProvider $tokenProvider,
73
-								ISecureRandom $secureRandom,
74
-								ITimeFactory $time,
75
-								Throttler $throttler) {
76
-		parent::__construct($appName, $request);
77
-		$this->crypto = $crypto;
78
-		$this->accessTokenMapper = $accessTokenMapper;
79
-		$this->clientMapper = $clientMapper;
80
-		$this->tokenProvider = $tokenProvider;
81
-		$this->secureRandom = $secureRandom;
82
-		$this->time = $time;
83
-		$this->throttler = $throttler;
84
-	}
85
-
86
-	/**
87
-	 * @PublicPage
88
-	 * @NoCSRFRequired
89
-	 *
90
-	 * @param string $grant_type
91
-	 * @param string $code
92
-	 * @param string $refresh_token
93
-	 * @param string $client_id
94
-	 * @param string $client_secret
95
-	 * @return JSONResponse
96
-	 */
97
-	public function getToken($grant_type, $code, $refresh_token, $client_id, $client_secret) {
98
-
99
-		// We only handle two types
100
-		if ($grant_type !== 'authorization_code' && $grant_type !== 'refresh_token') {
101
-			return new JSONResponse([
102
-				'error' => 'invalid_grant',
103
-			], Http::STATUS_BAD_REQUEST);
104
-		}
105
-
106
-		// We handle the initial and refresh tokens the same way
107
-		if ($grant_type === 'refresh_token' ) {
108
-			$code = $refresh_token;
109
-		}
110
-
111
-		try {
112
-			$accessToken = $this->accessTokenMapper->getByCode($code);
113
-		} catch (AccessTokenNotFoundException $e) {
114
-			return new JSONResponse([
115
-				'error' => 'invalid_request',
116
-			], Http::STATUS_BAD_REQUEST);
117
-		}
118
-
119
-		try {
120
-			$client = $this->clientMapper->getByUid($accessToken->getClientId());
121
-		} catch (ClientNotFoundException $e) {
122
-			return new JSONResponse([
123
-				'error' => 'invalid_request',
124
-			], Http::STATUS_BAD_REQUEST);
125
-		}
126
-
127
-		if (isset($this->request->server['PHP_AUTH_USER'])) {
128
-			$client_id = $this->request->server['PHP_AUTH_USER'];
129
-			$client_secret = $this->request->server['PHP_AUTH_PW'];
130
-		}
131
-
132
-		// The client id and secret must match. Else we don't provide an access token!
133
-		if ($client->getClientIdentifier() !== $client_id || $client->getSecret() !== $client_secret) {
134
-			return new JSONResponse([
135
-				'error' => 'invalid_client',
136
-			], Http::STATUS_BAD_REQUEST);
137
-		}
138
-
139
-		$decryptedToken = $this->crypto->decrypt($accessToken->getEncryptedToken(), $code);
140
-
141
-		// Obtain the appToken assoicated
142
-		try {
143
-			$appToken = $this->tokenProvider->getTokenById($accessToken->getTokenId());
144
-		} catch (ExpiredTokenException $e) {
145
-			$appToken = $e->getToken();
146
-		} catch (InvalidTokenException $e) {
147
-			//We can't do anything...
148
-			$this->accessTokenMapper->delete($accessToken);
149
-			return new JSONResponse([
150
-				'error' => 'invalid_request',
151
-			], Http::STATUS_BAD_REQUEST);
152
-		}
153
-
154
-		// Rotate the apptoken (so the old one becomes invalid basically)
155
-		$newToken = $this->secureRandom->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
156
-
157
-		$appToken = $this->tokenProvider->rotate(
158
-			$appToken,
159
-			$decryptedToken,
160
-			$newToken
161
-		);
162
-
163
-		// Expiration is in 1 hour again
164
-		$appToken->setExpires($this->time->getTime() + 3600);
165
-		$this->tokenProvider->updateToken($appToken);
166
-
167
-		// Generate a new refresh token and encrypt the new apptoken in the DB
168
-		$newCode = $this->secureRandom->generate(128, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
169
-		$accessToken->setHashedCode(hash('sha512', $newCode));
170
-		$accessToken->setEncryptedToken($this->crypto->encrypt($newToken, $newCode));
171
-		$this->accessTokenMapper->update($accessToken);
172
-
173
-		$this->throttler->resetDelay($this->request->getRemoteAddress(), 'login', ['user' => $appToken->getUID()]);
174
-
175
-		return new JSONResponse(
176
-			[
177
-				'access_token' => $newToken,
178
-				'token_type' => 'Bearer',
179
-				'expires_in' => 3600,
180
-				'refresh_token' => $newCode,
181
-				'user_id' => $appToken->getUID(),
182
-			]
183
-		);
184
-	}
41
+    /** @var AccessTokenMapper */
42
+    private $accessTokenMapper;
43
+    /** @var ClientMapper */
44
+    private $clientMapper;
45
+    /** @var ICrypto */
46
+    private $crypto;
47
+    /** @var TokenProvider */
48
+    private $tokenProvider;
49
+    /** @var ISecureRandom */
50
+    private $secureRandom;
51
+    /** @var ITimeFactory */
52
+    private $time;
53
+    /** @var Throttler */
54
+    private $throttler;
55
+
56
+    /**
57
+     * @param string $appName
58
+     * @param IRequest $request
59
+     * @param ICrypto $crypto
60
+     * @param AccessTokenMapper $accessTokenMapper
61
+     * @param ClientMapper $clientMapper
62
+     * @param TokenProvider $tokenProvider
63
+     * @param ISecureRandom $secureRandom
64
+     * @param ITimeFactory $time
65
+     * @param Throttler $throttler
66
+     */
67
+    public function __construct($appName,
68
+                                IRequest $request,
69
+                                ICrypto $crypto,
70
+                                AccessTokenMapper $accessTokenMapper,
71
+                                ClientMapper $clientMapper,
72
+                                TokenProvider $tokenProvider,
73
+                                ISecureRandom $secureRandom,
74
+                                ITimeFactory $time,
75
+                                Throttler $throttler) {
76
+        parent::__construct($appName, $request);
77
+        $this->crypto = $crypto;
78
+        $this->accessTokenMapper = $accessTokenMapper;
79
+        $this->clientMapper = $clientMapper;
80
+        $this->tokenProvider = $tokenProvider;
81
+        $this->secureRandom = $secureRandom;
82
+        $this->time = $time;
83
+        $this->throttler = $throttler;
84
+    }
85
+
86
+    /**
87
+     * @PublicPage
88
+     * @NoCSRFRequired
89
+     *
90
+     * @param string $grant_type
91
+     * @param string $code
92
+     * @param string $refresh_token
93
+     * @param string $client_id
94
+     * @param string $client_secret
95
+     * @return JSONResponse
96
+     */
97
+    public function getToken($grant_type, $code, $refresh_token, $client_id, $client_secret) {
98
+
99
+        // We only handle two types
100
+        if ($grant_type !== 'authorization_code' && $grant_type !== 'refresh_token') {
101
+            return new JSONResponse([
102
+                'error' => 'invalid_grant',
103
+            ], Http::STATUS_BAD_REQUEST);
104
+        }
105
+
106
+        // We handle the initial and refresh tokens the same way
107
+        if ($grant_type === 'refresh_token' ) {
108
+            $code = $refresh_token;
109
+        }
110
+
111
+        try {
112
+            $accessToken = $this->accessTokenMapper->getByCode($code);
113
+        } catch (AccessTokenNotFoundException $e) {
114
+            return new JSONResponse([
115
+                'error' => 'invalid_request',
116
+            ], Http::STATUS_BAD_REQUEST);
117
+        }
118
+
119
+        try {
120
+            $client = $this->clientMapper->getByUid($accessToken->getClientId());
121
+        } catch (ClientNotFoundException $e) {
122
+            return new JSONResponse([
123
+                'error' => 'invalid_request',
124
+            ], Http::STATUS_BAD_REQUEST);
125
+        }
126
+
127
+        if (isset($this->request->server['PHP_AUTH_USER'])) {
128
+            $client_id = $this->request->server['PHP_AUTH_USER'];
129
+            $client_secret = $this->request->server['PHP_AUTH_PW'];
130
+        }
131
+
132
+        // The client id and secret must match. Else we don't provide an access token!
133
+        if ($client->getClientIdentifier() !== $client_id || $client->getSecret() !== $client_secret) {
134
+            return new JSONResponse([
135
+                'error' => 'invalid_client',
136
+            ], Http::STATUS_BAD_REQUEST);
137
+        }
138
+
139
+        $decryptedToken = $this->crypto->decrypt($accessToken->getEncryptedToken(), $code);
140
+
141
+        // Obtain the appToken assoicated
142
+        try {
143
+            $appToken = $this->tokenProvider->getTokenById($accessToken->getTokenId());
144
+        } catch (ExpiredTokenException $e) {
145
+            $appToken = $e->getToken();
146
+        } catch (InvalidTokenException $e) {
147
+            //We can't do anything...
148
+            $this->accessTokenMapper->delete($accessToken);
149
+            return new JSONResponse([
150
+                'error' => 'invalid_request',
151
+            ], Http::STATUS_BAD_REQUEST);
152
+        }
153
+
154
+        // Rotate the apptoken (so the old one becomes invalid basically)
155
+        $newToken = $this->secureRandom->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
156
+
157
+        $appToken = $this->tokenProvider->rotate(
158
+            $appToken,
159
+            $decryptedToken,
160
+            $newToken
161
+        );
162
+
163
+        // Expiration is in 1 hour again
164
+        $appToken->setExpires($this->time->getTime() + 3600);
165
+        $this->tokenProvider->updateToken($appToken);
166
+
167
+        // Generate a new refresh token and encrypt the new apptoken in the DB
168
+        $newCode = $this->secureRandom->generate(128, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
169
+        $accessToken->setHashedCode(hash('sha512', $newCode));
170
+        $accessToken->setEncryptedToken($this->crypto->encrypt($newToken, $newCode));
171
+        $this->accessTokenMapper->update($accessToken);
172
+
173
+        $this->throttler->resetDelay($this->request->getRemoteAddress(), 'login', ['user' => $appToken->getUID()]);
174
+
175
+        return new JSONResponse(
176
+            [
177
+                'access_token' => $newToken,
178
+                'token_type' => 'Bearer',
179
+                'expires_in' => 3600,
180
+                'refresh_token' => $newCode,
181
+                'user_id' => $appToken->getUID(),
182
+            ]
183
+        );
184
+    }
185 185
 }
Please login to merge, or discard this patch.
lib/private/Authentication/Exceptions/ExpiredTokenException.php 1 patch
Indentation   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -25,16 +25,16 @@
 block discarded – undo
25 25
 use OC\Authentication\Token\IToken;
26 26
 
27 27
 class ExpiredTokenException extends InvalidTokenException {
28
-	/** @var IToken */
29
-	private $token;
28
+    /** @var IToken */
29
+    private $token;
30 30
 
31
-	public function __construct(IToken $token) {
32
-		parent::__construct();
31
+    public function __construct(IToken $token) {
32
+        parent::__construct();
33 33
 
34
-		$this->token = $token;
35
-	}
34
+        $this->token = $token;
35
+    }
36 36
 
37
-	public function getToken() {
38
-		return $this->token;
39
-	}
37
+    public function getToken() {
38
+        return $this->token;
39
+    }
40 40
 }
Please login to merge, or discard this patch.
lib/private/User/Session.php 1 patch
Indentation   +838 added lines, -838 removed lines patch added patch discarded remove patch
@@ -85,844 +85,844 @@
 block discarded – undo
85 85
  */
86 86
 class Session implements IUserSession, Emitter {
87 87
 
88
-	/** @var Manager|PublicEmitter $manager */
89
-	private $manager;
90
-
91
-	/** @var ISession $session */
92
-	private $session;
93
-
94
-	/** @var ITimeFactory */
95
-	private $timeFactory;
96
-
97
-	/** @var IProvider */
98
-	private $tokenProvider;
99
-
100
-	/** @var IConfig */
101
-	private $config;
102
-
103
-	/** @var User $activeUser */
104
-	protected $activeUser;
105
-
106
-	/** @var ISecureRandom */
107
-	private $random;
108
-
109
-	/** @var ILockdownManager  */
110
-	private $lockdownManager;
111
-
112
-	/** @var ILogger */
113
-	private $logger;
114
-
115
-	/**
116
-	 * @param Manager $manager
117
-	 * @param ISession $session
118
-	 * @param ITimeFactory $timeFactory
119
-	 * @param IProvider $tokenProvider
120
-	 * @param IConfig $config
121
-	 * @param ISecureRandom $random
122
-	 * @param ILockdownManager $lockdownManager
123
-	 * @param ILogger $logger
124
-	 */
125
-	public function __construct(Manager $manager,
126
-								ISession $session,
127
-								ITimeFactory $timeFactory,
128
-								$tokenProvider,
129
-								IConfig $config,
130
-								ISecureRandom $random,
131
-								ILockdownManager $lockdownManager,
132
-								ILogger $logger) {
133
-		$this->manager = $manager;
134
-		$this->session = $session;
135
-		$this->timeFactory = $timeFactory;
136
-		$this->tokenProvider = $tokenProvider;
137
-		$this->config = $config;
138
-		$this->random = $random;
139
-		$this->lockdownManager = $lockdownManager;
140
-		$this->logger = $logger;
141
-	}
142
-
143
-	/**
144
-	 * @param IProvider $provider
145
-	 */
146
-	public function setTokenProvider(IProvider $provider) {
147
-		$this->tokenProvider = $provider;
148
-	}
149
-
150
-	/**
151
-	 * @param string $scope
152
-	 * @param string $method
153
-	 * @param callable $callback
154
-	 */
155
-	public function listen($scope, $method, callable $callback) {
156
-		$this->manager->listen($scope, $method, $callback);
157
-	}
158
-
159
-	/**
160
-	 * @param string $scope optional
161
-	 * @param string $method optional
162
-	 * @param callable $callback optional
163
-	 */
164
-	public function removeListener($scope = null, $method = null, callable $callback = null) {
165
-		$this->manager->removeListener($scope, $method, $callback);
166
-	}
167
-
168
-	/**
169
-	 * get the manager object
170
-	 *
171
-	 * @return Manager|PublicEmitter
172
-	 */
173
-	public function getManager() {
174
-		return $this->manager;
175
-	}
176
-
177
-	/**
178
-	 * get the session object
179
-	 *
180
-	 * @return ISession
181
-	 */
182
-	public function getSession() {
183
-		return $this->session;
184
-	}
185
-
186
-	/**
187
-	 * set the session object
188
-	 *
189
-	 * @param ISession $session
190
-	 */
191
-	public function setSession(ISession $session) {
192
-		if ($this->session instanceof ISession) {
193
-			$this->session->close();
194
-		}
195
-		$this->session = $session;
196
-		$this->activeUser = null;
197
-	}
198
-
199
-	/**
200
-	 * set the currently active user
201
-	 *
202
-	 * @param IUser|null $user
203
-	 */
204
-	public function setUser($user) {
205
-		if (is_null($user)) {
206
-			$this->session->remove('user_id');
207
-		} else {
208
-			$this->session->set('user_id', $user->getUID());
209
-		}
210
-		$this->activeUser = $user;
211
-	}
212
-
213
-	/**
214
-	 * get the current active user
215
-	 *
216
-	 * @return IUser|null Current user, otherwise null
217
-	 */
218
-	public function getUser() {
219
-		// FIXME: This is a quick'n dirty work-around for the incognito mode as
220
-		// described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155
221
-		if (OC_User::isIncognitoMode()) {
222
-			return null;
223
-		}
224
-		if (is_null($this->activeUser)) {
225
-			$uid = $this->session->get('user_id');
226
-			if (is_null($uid)) {
227
-				return null;
228
-			}
229
-			$this->activeUser = $this->manager->get($uid);
230
-			if (is_null($this->activeUser)) {
231
-				return null;
232
-			}
233
-			$this->validateSession();
234
-		}
235
-		return $this->activeUser;
236
-	}
237
-
238
-	/**
239
-	 * Validate whether the current session is valid
240
-	 *
241
-	 * - For token-authenticated clients, the token validity is checked
242
-	 * - For browsers, the session token validity is checked
243
-	 */
244
-	protected function validateSession() {
245
-		$token = null;
246
-		$appPassword = $this->session->get('app_password');
247
-
248
-		if (is_null($appPassword)) {
249
-			try {
250
-				$token = $this->session->getId();
251
-			} catch (SessionNotAvailableException $ex) {
252
-				return;
253
-			}
254
-		} else {
255
-			$token = $appPassword;
256
-		}
257
-
258
-		if (!$this->validateToken($token)) {
259
-			// Session was invalidated
260
-			$this->logout();
261
-		}
262
-	}
263
-
264
-	/**
265
-	 * Checks whether the user is logged in
266
-	 *
267
-	 * @return bool if logged in
268
-	 */
269
-	public function isLoggedIn() {
270
-		$user = $this->getUser();
271
-		if (is_null($user)) {
272
-			return false;
273
-		}
274
-
275
-		return $user->isEnabled();
276
-	}
277
-
278
-	/**
279
-	 * set the login name
280
-	 *
281
-	 * @param string|null $loginName for the logged in user
282
-	 */
283
-	public function setLoginName($loginName) {
284
-		if (is_null($loginName)) {
285
-			$this->session->remove('loginname');
286
-		} else {
287
-			$this->session->set('loginname', $loginName);
288
-		}
289
-	}
290
-
291
-	/**
292
-	 * get the login name of the current user
293
-	 *
294
-	 * @return string
295
-	 */
296
-	public function getLoginName() {
297
-		if ($this->activeUser) {
298
-			return $this->session->get('loginname');
299
-		} else {
300
-			$uid = $this->session->get('user_id');
301
-			if ($uid) {
302
-				$this->activeUser = $this->manager->get($uid);
303
-				return $this->session->get('loginname');
304
-			} else {
305
-				return null;
306
-			}
307
-		}
308
-	}
309
-
310
-	/**
311
-	 * set the token id
312
-	 *
313
-	 * @param int|null $token that was used to log in
314
-	 */
315
-	protected function setToken($token) {
316
-		if ($token === null) {
317
-			$this->session->remove('token-id');
318
-		} else {
319
-			$this->session->set('token-id', $token);
320
-		}
321
-	}
322
-
323
-	/**
324
-	 * try to log in with the provided credentials
325
-	 *
326
-	 * @param string $uid
327
-	 * @param string $password
328
-	 * @return boolean|null
329
-	 * @throws LoginException
330
-	 */
331
-	public function login($uid, $password) {
332
-		$this->session->regenerateId();
333
-		if ($this->validateToken($password, $uid)) {
334
-			return $this->loginWithToken($password);
335
-		}
336
-		return $this->loginWithPassword($uid, $password);
337
-	}
338
-
339
-	/**
340
-	 * @param IUser $user
341
-	 * @param array $loginDetails
342
-	 * @param bool $regenerateSessionId
343
-	 * @return true returns true if login successful or an exception otherwise
344
-	 * @throws LoginException
345
-	 */
346
-	public function completeLogin(IUser $user, array $loginDetails, $regenerateSessionId = true) {
347
-		if (!$user->isEnabled()) {
348
-			// disabled users can not log in
349
-			// injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
350
-			$message = \OC::$server->getL10N('lib')->t('User disabled');
351
-			throw new LoginException($message);
352
-		}
353
-
354
-		if($regenerateSessionId) {
355
-			$this->session->regenerateId();
356
-		}
357
-
358
-		$this->setUser($user);
359
-		$this->setLoginName($loginDetails['loginName']);
360
-
361
-		if(isset($loginDetails['token']) && $loginDetails['token'] instanceof IToken) {
362
-			$this->setToken($loginDetails['token']->getId());
363
-			$this->lockdownManager->setToken($loginDetails['token']);
364
-			$firstTimeLogin = false;
365
-		} else {
366
-			$this->setToken(null);
367
-			$firstTimeLogin = $user->updateLastLoginTimestamp();
368
-		}
369
-		$this->manager->emit('\OC\User', 'postLogin', [$user, $loginDetails['password']]);
370
-		if($this->isLoggedIn()) {
371
-			$this->prepareUserLogin($firstTimeLogin, $regenerateSessionId);
372
-			return true;
373
-		} else {
374
-			$message = \OC::$server->getL10N('lib')->t('Login canceled by app');
375
-			throw new LoginException($message);
376
-		}
377
-	}
378
-
379
-	/**
380
-	 * Tries to log in a client
381
-	 *
382
-	 * Checks token auth enforced
383
-	 * Checks 2FA enabled
384
-	 *
385
-	 * @param string $user
386
-	 * @param string $password
387
-	 * @param IRequest $request
388
-	 * @param OC\Security\Bruteforce\Throttler $throttler
389
-	 * @throws LoginException
390
-	 * @throws PasswordLoginForbiddenException
391
-	 * @return boolean
392
-	 */
393
-	public function logClientIn($user,
394
-								$password,
395
-								IRequest $request,
396
-								OC\Security\Bruteforce\Throttler $throttler) {
397
-		$currentDelay = $throttler->sleepDelay($request->getRemoteAddress(), 'login');
398
-
399
-		if ($this->manager instanceof PublicEmitter) {
400
-			$this->manager->emit('\OC\User', 'preLogin', array($user, $password));
401
-		}
402
-
403
-		try {
404
-			$isTokenPassword = $this->isTokenPassword($password);
405
-		} catch (ExpiredTokenException $e) {
406
-			// Just return on an expired token no need to check further or record a failed login
407
-			return false;
408
-		}
409
-
410
-		if (!$isTokenPassword && $this->isTokenAuthEnforced()) {
411
-			throw new PasswordLoginForbiddenException();
412
-		}
413
-		if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) {
414
-			throw new PasswordLoginForbiddenException();
415
-		}
416
-
417
-		// Try to login with this username and password
418
-		if (!$this->login($user, $password) ) {
419
-
420
-			// Failed, maybe the user used their email address
421
-			$users = $this->manager->getByEmail($user);
422
-			if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) {
423
-
424
-				$this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
425
-
426
-				$throttler->registerAttempt('login', $request->getRemoteAddress(), ['uid' => $user]);
427
-				if ($currentDelay === 0) {
428
-					$throttler->sleepDelay($request->getRemoteAddress(), 'login');
429
-				}
430
-				return false;
431
-			}
432
-		}
433
-
434
-		if ($isTokenPassword) {
435
-			$this->session->set('app_password', $password);
436
-		} else if($this->supportsCookies($request)) {
437
-			// Password login, but cookies supported -> create (browser) session token
438
-			$this->createSessionToken($request, $this->getUser()->getUID(), $user, $password);
439
-		}
440
-
441
-		return true;
442
-	}
443
-
444
-	protected function supportsCookies(IRequest $request) {
445
-		if (!is_null($request->getCookie('cookie_test'))) {
446
-			return true;
447
-		}
448
-		setcookie('cookie_test', 'test', $this->timeFactory->getTime() + 3600);
449
-		return false;
450
-	}
451
-
452
-	private function isTokenAuthEnforced() {
453
-		return $this->config->getSystemValue('token_auth_enforced', false);
454
-	}
455
-
456
-	protected function isTwoFactorEnforced($username) {
457
-		Util::emitHook(
458
-			'\OCA\Files_Sharing\API\Server2Server',
459
-			'preLoginNameUsedAsUserName',
460
-			array('uid' => &$username)
461
-		);
462
-		$user = $this->manager->get($username);
463
-		if (is_null($user)) {
464
-			$users = $this->manager->getByEmail($username);
465
-			if (empty($users)) {
466
-				return false;
467
-			}
468
-			if (count($users) !== 1) {
469
-				return true;
470
-			}
471
-			$user = $users[0];
472
-		}
473
-		// DI not possible due to cyclic dependencies :'-/
474
-		return OC::$server->getTwoFactorAuthManager()->isTwoFactorAuthenticated($user);
475
-	}
476
-
477
-	/**
478
-	 * Check if the given 'password' is actually a device token
479
-	 *
480
-	 * @param string $password
481
-	 * @return boolean
482
-	 * @throws ExpiredTokenException
483
-	 */
484
-	public function isTokenPassword($password) {
485
-		try {
486
-			$this->tokenProvider->getToken($password);
487
-			return true;
488
-		} catch (ExpiredTokenException $e) {
489
-			throw $e;
490
-		} catch (InvalidTokenException $ex) {
491
-			return false;
492
-		}
493
-	}
494
-
495
-	protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
496
-		if ($refreshCsrfToken) {
497
-			// TODO: mock/inject/use non-static
498
-			// Refresh the token
499
-			\OC::$server->getCsrfTokenManager()->refreshToken();
500
-		}
501
-
502
-		//we need to pass the user name, which may differ from login name
503
-		$user = $this->getUser()->getUID();
504
-		OC_Util::setupFS($user);
505
-
506
-		if ($firstTimeLogin) {
507
-			// TODO: lock necessary?
508
-			//trigger creation of user home and /files folder
509
-			$userFolder = \OC::$server->getUserFolder($user);
510
-
511
-			try {
512
-				// copy skeleton
513
-				\OC_Util::copySkeleton($user, $userFolder);
514
-			} catch (NotPermittedException $ex) {
515
-				// read only uses
516
-			}
517
-
518
-			// trigger any other initialization
519
-			\OC::$server->getEventDispatcher()->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser()));
520
-		}
521
-	}
522
-
523
-	/**
524
-	 * Tries to login the user with HTTP Basic Authentication
525
-	 *
526
-	 * @todo do not allow basic auth if the user is 2FA enforced
527
-	 * @param IRequest $request
528
-	 * @param OC\Security\Bruteforce\Throttler $throttler
529
-	 * @return boolean if the login was successful
530
-	 */
531
-	public function tryBasicAuthLogin(IRequest $request,
532
-									  OC\Security\Bruteforce\Throttler $throttler) {
533
-		if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) {
534
-			try {
535
-				if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) {
536
-					/**
537
-					 * Add DAV authenticated. This should in an ideal world not be
538
-					 * necessary but the iOS App reads cookies from anywhere instead
539
-					 * only the DAV endpoint.
540
-					 * This makes sure that the cookies will be valid for the whole scope
541
-					 * @see https://github.com/owncloud/core/issues/22893
542
-					 */
543
-					$this->session->set(
544
-						Auth::DAV_AUTHENTICATED, $this->getUser()->getUID()
545
-					);
546
-
547
-					// Set the last-password-confirm session to make the sudo mode work
548
-					 $this->session->set('last-password-confirm', $this->timeFactory->getTime());
549
-
550
-					return true;
551
-				}
552
-			} catch (PasswordLoginForbiddenException $ex) {
553
-				// Nothing to do
554
-			}
555
-		}
556
-		return false;
557
-	}
558
-
559
-	/**
560
-	 * Log an user in via login name and password
561
-	 *
562
-	 * @param string $uid
563
-	 * @param string $password
564
-	 * @return boolean
565
-	 * @throws LoginException if an app canceld the login process or the user is not enabled
566
-	 */
567
-	private function loginWithPassword($uid, $password) {
568
-		$user = $this->manager->checkPasswordNoLogging($uid, $password);
569
-		if ($user === false) {
570
-			// Password check failed
571
-			return false;
572
-		}
573
-
574
-		return $this->completeLogin($user, ['loginName' => $uid, 'password' => $password], false);
575
-	}
576
-
577
-	/**
578
-	 * Log an user in with a given token (id)
579
-	 *
580
-	 * @param string $token
581
-	 * @return boolean
582
-	 * @throws LoginException if an app canceled the login process or the user is not enabled
583
-	 */
584
-	private function loginWithToken($token) {
585
-		try {
586
-			$dbToken = $this->tokenProvider->getToken($token);
587
-		} catch (InvalidTokenException $ex) {
588
-			return false;
589
-		}
590
-		$uid = $dbToken->getUID();
591
-
592
-		// When logging in with token, the password must be decrypted first before passing to login hook
593
-		$password = '';
594
-		try {
595
-			$password = $this->tokenProvider->getPassword($dbToken, $token);
596
-		} catch (PasswordlessTokenException $ex) {
597
-			// Ignore and use empty string instead
598
-		}
599
-
600
-		$this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
601
-
602
-		$user = $this->manager->get($uid);
603
-		if (is_null($user)) {
604
-			// user does not exist
605
-			return false;
606
-		}
607
-
608
-		return $this->completeLogin(
609
-			$user,
610
-			[
611
-				'loginName' => $dbToken->getLoginName(),
612
-				'password' => $password,
613
-				'token' => $dbToken
614
-			],
615
-			false);
616
-	}
617
-
618
-	/**
619
-	 * Create a new session token for the given user credentials
620
-	 *
621
-	 * @param IRequest $request
622
-	 * @param string $uid user UID
623
-	 * @param string $loginName login name
624
-	 * @param string $password
625
-	 * @param int $remember
626
-	 * @return boolean
627
-	 */
628
-	public function createSessionToken(IRequest $request, $uid, $loginName, $password = null, $remember = IToken::DO_NOT_REMEMBER) {
629
-		if (is_null($this->manager->get($uid))) {
630
-			// User does not exist
631
-			return false;
632
-		}
633
-		$name = isset($request->server['HTTP_USER_AGENT']) ? $request->server['HTTP_USER_AGENT'] : 'unknown browser';
634
-		try {
635
-			$sessionId = $this->session->getId();
636
-			$pwd = $this->getPassword($password);
637
-			// Make sure the current sessionId has no leftover tokens
638
-			$this->tokenProvider->invalidateToken($sessionId);
639
-			$this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name, IToken::TEMPORARY_TOKEN, $remember);
640
-			return true;
641
-		} catch (SessionNotAvailableException $ex) {
642
-			// This can happen with OCC, where a memory session is used
643
-			// if a memory session is used, we shouldn't create a session token anyway
644
-			return false;
645
-		}
646
-	}
647
-
648
-	/**
649
-	 * Checks if the given password is a token.
650
-	 * If yes, the password is extracted from the token.
651
-	 * If no, the same password is returned.
652
-	 *
653
-	 * @param string $password either the login password or a device token
654
-	 * @return string|null the password or null if none was set in the token
655
-	 */
656
-	private function getPassword($password) {
657
-		if (is_null($password)) {
658
-			// This is surely no token ;-)
659
-			return null;
660
-		}
661
-		try {
662
-			$token = $this->tokenProvider->getToken($password);
663
-			try {
664
-				return $this->tokenProvider->getPassword($token, $password);
665
-			} catch (PasswordlessTokenException $ex) {
666
-				return null;
667
-			}
668
-		} catch (InvalidTokenException $ex) {
669
-			return $password;
670
-		}
671
-	}
672
-
673
-	/**
674
-	 * @param IToken $dbToken
675
-	 * @param string $token
676
-	 * @return boolean
677
-	 */
678
-	private function checkTokenCredentials(IToken $dbToken, $token) {
679
-		// Check whether login credentials are still valid and the user was not disabled
680
-		// This check is performed each 5 minutes
681
-		$lastCheck = $dbToken->getLastCheck() ? : 0;
682
-		$now = $this->timeFactory->getTime();
683
-		if ($lastCheck > ($now - 60 * 5)) {
684
-			// Checked performed recently, nothing to do now
685
-			return true;
686
-		}
687
-
688
-		try {
689
-			$pwd = $this->tokenProvider->getPassword($dbToken, $token);
690
-		} catch (InvalidTokenException $ex) {
691
-			// An invalid token password was used -> log user out
692
-			return false;
693
-		} catch (PasswordlessTokenException $ex) {
694
-			// Token has no password
695
-
696
-			if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
697
-				$this->tokenProvider->invalidateToken($token);
698
-				return false;
699
-			}
700
-
701
-			$dbToken->setLastCheck($now);
702
-			return true;
703
-		}
704
-
705
-		if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false
706
-			|| (!is_null($this->activeUser) && !$this->activeUser->isEnabled())) {
707
-			$this->tokenProvider->invalidateToken($token);
708
-			// Password has changed or user was disabled -> log user out
709
-			return false;
710
-		}
711
-		$dbToken->setLastCheck($now);
712
-		return true;
713
-	}
714
-
715
-	/**
716
-	 * Check if the given token exists and performs password/user-enabled checks
717
-	 *
718
-	 * Invalidates the token if checks fail
719
-	 *
720
-	 * @param string $token
721
-	 * @param string $user login name
722
-	 * @return boolean
723
-	 */
724
-	private function validateToken($token, $user = null) {
725
-		try {
726
-			$dbToken = $this->tokenProvider->getToken($token);
727
-		} catch (InvalidTokenException $ex) {
728
-			return false;
729
-		}
730
-
731
-		// Check if login names match
732
-		if (!is_null($user) && $dbToken->getLoginName() !== $user) {
733
-			// TODO: this makes it imposssible to use different login names on browser and client
734
-			// e.g. login by e-mail '[email protected]' on browser for generating the token will not
735
-			//      allow to use the client token with the login name 'user'.
736
-			return false;
737
-		}
738
-
739
-		if (!$this->checkTokenCredentials($dbToken, $token)) {
740
-			return false;
741
-		}
742
-
743
-		// Update token scope
744
-		$this->lockdownManager->setToken($dbToken);
745
-
746
-		$this->tokenProvider->updateTokenActivity($dbToken);
747
-
748
-		return true;
749
-	}
750
-
751
-	/**
752
-	 * Tries to login the user with auth token header
753
-	 *
754
-	 * @param IRequest $request
755
-	 * @todo check remember me cookie
756
-	 * @return boolean
757
-	 */
758
-	public function tryTokenLogin(IRequest $request) {
759
-		$authHeader = $request->getHeader('Authorization');
760
-		if (strpos($authHeader, 'Bearer ') === false) {
761
-			// No auth header, let's try session id
762
-			try {
763
-				$token = $this->session->getId();
764
-			} catch (SessionNotAvailableException $ex) {
765
-				return false;
766
-			}
767
-		} else {
768
-			$token = substr($authHeader, 7);
769
-		}
770
-
771
-		if (!$this->loginWithToken($token)) {
772
-			return false;
773
-		}
774
-		if(!$this->validateToken($token)) {
775
-			return false;
776
-		}
777
-		return true;
778
-	}
779
-
780
-	/**
781
-	 * perform login using the magic cookie (remember login)
782
-	 *
783
-	 * @param string $uid the username
784
-	 * @param string $currentToken
785
-	 * @param string $oldSessionId
786
-	 * @return bool
787
-	 */
788
-	public function loginWithCookie($uid, $currentToken, $oldSessionId) {
789
-		$this->session->regenerateId();
790
-		$this->manager->emit('\OC\User', 'preRememberedLogin', array($uid));
791
-		$user = $this->manager->get($uid);
792
-		if (is_null($user)) {
793
-			// user does not exist
794
-			return false;
795
-		}
796
-
797
-		// get stored tokens
798
-		$tokens = $this->config->getUserKeys($uid, 'login_token');
799
-		// test cookies token against stored tokens
800
-		if (!in_array($currentToken, $tokens, true)) {
801
-			return false;
802
-		}
803
-		// replace successfully used token with a new one
804
-		$this->config->deleteUserValue($uid, 'login_token', $currentToken);
805
-		$newToken = $this->random->generate(32);
806
-		$this->config->setUserValue($uid, 'login_token', $newToken, $this->timeFactory->getTime());
807
-
808
-		try {
809
-			$sessionId = $this->session->getId();
810
-			$this->tokenProvider->renewSessionToken($oldSessionId, $sessionId);
811
-		} catch (SessionNotAvailableException $ex) {
812
-			return false;
813
-		} catch (InvalidTokenException $ex) {
814
-			\OC::$server->getLogger()->warning('Renewing session token failed', ['app' => 'core']);
815
-			return false;
816
-		}
817
-
818
-		$this->setMagicInCookie($user->getUID(), $newToken);
819
-		$token = $this->tokenProvider->getToken($sessionId);
820
-
821
-		//login
822
-		$this->setUser($user);
823
-		$this->setLoginName($token->getLoginName());
824
-		$this->setToken($token->getId());
825
-		$this->lockdownManager->setToken($token);
826
-		$user->updateLastLoginTimestamp();
827
-		$password = null;
828
-		try {
829
-			$password = $this->tokenProvider->getPassword($token, $sessionId);
830
-		} catch (PasswordlessTokenException $ex) {
831
-			// Ignore
832
-		}
833
-		$this->manager->emit('\OC\User', 'postRememberedLogin', [$user, $password]);
834
-		return true;
835
-	}
836
-
837
-	/**
838
-	 * @param IUser $user
839
-	 */
840
-	public function createRememberMeToken(IUser $user) {
841
-		$token = $this->random->generate(32);
842
-		$this->config->setUserValue($user->getUID(), 'login_token', $token, $this->timeFactory->getTime());
843
-		$this->setMagicInCookie($user->getUID(), $token);
844
-	}
845
-
846
-	/**
847
-	 * logout the user from the session
848
-	 */
849
-	public function logout() {
850
-		$this->manager->emit('\OC\User', 'logout');
851
-		$user = $this->getUser();
852
-		if (!is_null($user)) {
853
-			try {
854
-				$this->tokenProvider->invalidateToken($this->session->getId());
855
-			} catch (SessionNotAvailableException $ex) {
856
-
857
-			}
858
-		}
859
-		$this->setUser(null);
860
-		$this->setLoginName(null);
861
-		$this->setToken(null);
862
-		$this->unsetMagicInCookie();
863
-		$this->session->clear();
864
-		$this->manager->emit('\OC\User', 'postLogout');
865
-	}
866
-
867
-	/**
868
-	 * Set cookie value to use in next page load
869
-	 *
870
-	 * @param string $username username to be set
871
-	 * @param string $token
872
-	 */
873
-	public function setMagicInCookie($username, $token) {
874
-		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
875
-		$webRoot = \OC::$WEBROOT;
876
-		if ($webRoot === '') {
877
-			$webRoot = '/';
878
-		}
879
-
880
-		$expires = $this->timeFactory->getTime() + $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
881
-		setcookie('nc_username', $username, $expires, $webRoot, '', $secureCookie, true);
882
-		setcookie('nc_token', $token, $expires, $webRoot, '', $secureCookie, true);
883
-		try {
884
-			setcookie('nc_session_id', $this->session->getId(), $expires, $webRoot, '', $secureCookie, true);
885
-		} catch (SessionNotAvailableException $ex) {
886
-			// ignore
887
-		}
888
-	}
889
-
890
-	/**
891
-	 * Remove cookie for "remember username"
892
-	 */
893
-	public function unsetMagicInCookie() {
894
-		//TODO: DI for cookies and IRequest
895
-		$secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
896
-
897
-		unset($_COOKIE['nc_username']); //TODO: DI
898
-		unset($_COOKIE['nc_token']);
899
-		unset($_COOKIE['nc_session_id']);
900
-		setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
901
-		setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
902
-		setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
903
-		// old cookies might be stored under /webroot/ instead of /webroot
904
-		// and Firefox doesn't like it!
905
-		setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
906
-		setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
907
-		setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
908
-	}
909
-
910
-	/**
911
-	 * Update password of the browser session token if there is one
912
-	 *
913
-	 * @param string $password
914
-	 */
915
-	public function updateSessionTokenPassword($password) {
916
-		try {
917
-			$sessionId = $this->session->getId();
918
-			$token = $this->tokenProvider->getToken($sessionId);
919
-			$this->tokenProvider->setPassword($token, $sessionId, $password);
920
-		} catch (SessionNotAvailableException $ex) {
921
-			// Nothing to do
922
-		} catch (InvalidTokenException $ex) {
923
-			// Nothing to do
924
-		}
925
-	}
88
+    /** @var Manager|PublicEmitter $manager */
89
+    private $manager;
90
+
91
+    /** @var ISession $session */
92
+    private $session;
93
+
94
+    /** @var ITimeFactory */
95
+    private $timeFactory;
96
+
97
+    /** @var IProvider */
98
+    private $tokenProvider;
99
+
100
+    /** @var IConfig */
101
+    private $config;
102
+
103
+    /** @var User $activeUser */
104
+    protected $activeUser;
105
+
106
+    /** @var ISecureRandom */
107
+    private $random;
108
+
109
+    /** @var ILockdownManager  */
110
+    private $lockdownManager;
111
+
112
+    /** @var ILogger */
113
+    private $logger;
114
+
115
+    /**
116
+     * @param Manager $manager
117
+     * @param ISession $session
118
+     * @param ITimeFactory $timeFactory
119
+     * @param IProvider $tokenProvider
120
+     * @param IConfig $config
121
+     * @param ISecureRandom $random
122
+     * @param ILockdownManager $lockdownManager
123
+     * @param ILogger $logger
124
+     */
125
+    public function __construct(Manager $manager,
126
+                                ISession $session,
127
+                                ITimeFactory $timeFactory,
128
+                                $tokenProvider,
129
+                                IConfig $config,
130
+                                ISecureRandom $random,
131
+                                ILockdownManager $lockdownManager,
132
+                                ILogger $logger) {
133
+        $this->manager = $manager;
134
+        $this->session = $session;
135
+        $this->timeFactory = $timeFactory;
136
+        $this->tokenProvider = $tokenProvider;
137
+        $this->config = $config;
138
+        $this->random = $random;
139
+        $this->lockdownManager = $lockdownManager;
140
+        $this->logger = $logger;
141
+    }
142
+
143
+    /**
144
+     * @param IProvider $provider
145
+     */
146
+    public function setTokenProvider(IProvider $provider) {
147
+        $this->tokenProvider = $provider;
148
+    }
149
+
150
+    /**
151
+     * @param string $scope
152
+     * @param string $method
153
+     * @param callable $callback
154
+     */
155
+    public function listen($scope, $method, callable $callback) {
156
+        $this->manager->listen($scope, $method, $callback);
157
+    }
158
+
159
+    /**
160
+     * @param string $scope optional
161
+     * @param string $method optional
162
+     * @param callable $callback optional
163
+     */
164
+    public function removeListener($scope = null, $method = null, callable $callback = null) {
165
+        $this->manager->removeListener($scope, $method, $callback);
166
+    }
167
+
168
+    /**
169
+     * get the manager object
170
+     *
171
+     * @return Manager|PublicEmitter
172
+     */
173
+    public function getManager() {
174
+        return $this->manager;
175
+    }
176
+
177
+    /**
178
+     * get the session object
179
+     *
180
+     * @return ISession
181
+     */
182
+    public function getSession() {
183
+        return $this->session;
184
+    }
185
+
186
+    /**
187
+     * set the session object
188
+     *
189
+     * @param ISession $session
190
+     */
191
+    public function setSession(ISession $session) {
192
+        if ($this->session instanceof ISession) {
193
+            $this->session->close();
194
+        }
195
+        $this->session = $session;
196
+        $this->activeUser = null;
197
+    }
198
+
199
+    /**
200
+     * set the currently active user
201
+     *
202
+     * @param IUser|null $user
203
+     */
204
+    public function setUser($user) {
205
+        if (is_null($user)) {
206
+            $this->session->remove('user_id');
207
+        } else {
208
+            $this->session->set('user_id', $user->getUID());
209
+        }
210
+        $this->activeUser = $user;
211
+    }
212
+
213
+    /**
214
+     * get the current active user
215
+     *
216
+     * @return IUser|null Current user, otherwise null
217
+     */
218
+    public function getUser() {
219
+        // FIXME: This is a quick'n dirty work-around for the incognito mode as
220
+        // described at https://github.com/owncloud/core/pull/12912#issuecomment-67391155
221
+        if (OC_User::isIncognitoMode()) {
222
+            return null;
223
+        }
224
+        if (is_null($this->activeUser)) {
225
+            $uid = $this->session->get('user_id');
226
+            if (is_null($uid)) {
227
+                return null;
228
+            }
229
+            $this->activeUser = $this->manager->get($uid);
230
+            if (is_null($this->activeUser)) {
231
+                return null;
232
+            }
233
+            $this->validateSession();
234
+        }
235
+        return $this->activeUser;
236
+    }
237
+
238
+    /**
239
+     * Validate whether the current session is valid
240
+     *
241
+     * - For token-authenticated clients, the token validity is checked
242
+     * - For browsers, the session token validity is checked
243
+     */
244
+    protected function validateSession() {
245
+        $token = null;
246
+        $appPassword = $this->session->get('app_password');
247
+
248
+        if (is_null($appPassword)) {
249
+            try {
250
+                $token = $this->session->getId();
251
+            } catch (SessionNotAvailableException $ex) {
252
+                return;
253
+            }
254
+        } else {
255
+            $token = $appPassword;
256
+        }
257
+
258
+        if (!$this->validateToken($token)) {
259
+            // Session was invalidated
260
+            $this->logout();
261
+        }
262
+    }
263
+
264
+    /**
265
+     * Checks whether the user is logged in
266
+     *
267
+     * @return bool if logged in
268
+     */
269
+    public function isLoggedIn() {
270
+        $user = $this->getUser();
271
+        if (is_null($user)) {
272
+            return false;
273
+        }
274
+
275
+        return $user->isEnabled();
276
+    }
277
+
278
+    /**
279
+     * set the login name
280
+     *
281
+     * @param string|null $loginName for the logged in user
282
+     */
283
+    public function setLoginName($loginName) {
284
+        if (is_null($loginName)) {
285
+            $this->session->remove('loginname');
286
+        } else {
287
+            $this->session->set('loginname', $loginName);
288
+        }
289
+    }
290
+
291
+    /**
292
+     * get the login name of the current user
293
+     *
294
+     * @return string
295
+     */
296
+    public function getLoginName() {
297
+        if ($this->activeUser) {
298
+            return $this->session->get('loginname');
299
+        } else {
300
+            $uid = $this->session->get('user_id');
301
+            if ($uid) {
302
+                $this->activeUser = $this->manager->get($uid);
303
+                return $this->session->get('loginname');
304
+            } else {
305
+                return null;
306
+            }
307
+        }
308
+    }
309
+
310
+    /**
311
+     * set the token id
312
+     *
313
+     * @param int|null $token that was used to log in
314
+     */
315
+    protected function setToken($token) {
316
+        if ($token === null) {
317
+            $this->session->remove('token-id');
318
+        } else {
319
+            $this->session->set('token-id', $token);
320
+        }
321
+    }
322
+
323
+    /**
324
+     * try to log in with the provided credentials
325
+     *
326
+     * @param string $uid
327
+     * @param string $password
328
+     * @return boolean|null
329
+     * @throws LoginException
330
+     */
331
+    public function login($uid, $password) {
332
+        $this->session->regenerateId();
333
+        if ($this->validateToken($password, $uid)) {
334
+            return $this->loginWithToken($password);
335
+        }
336
+        return $this->loginWithPassword($uid, $password);
337
+    }
338
+
339
+    /**
340
+     * @param IUser $user
341
+     * @param array $loginDetails
342
+     * @param bool $regenerateSessionId
343
+     * @return true returns true if login successful or an exception otherwise
344
+     * @throws LoginException
345
+     */
346
+    public function completeLogin(IUser $user, array $loginDetails, $regenerateSessionId = true) {
347
+        if (!$user->isEnabled()) {
348
+            // disabled users can not log in
349
+            // injecting l10n does not work - there is a circular dependency between session and \OCP\L10N\IFactory
350
+            $message = \OC::$server->getL10N('lib')->t('User disabled');
351
+            throw new LoginException($message);
352
+        }
353
+
354
+        if($regenerateSessionId) {
355
+            $this->session->regenerateId();
356
+        }
357
+
358
+        $this->setUser($user);
359
+        $this->setLoginName($loginDetails['loginName']);
360
+
361
+        if(isset($loginDetails['token']) && $loginDetails['token'] instanceof IToken) {
362
+            $this->setToken($loginDetails['token']->getId());
363
+            $this->lockdownManager->setToken($loginDetails['token']);
364
+            $firstTimeLogin = false;
365
+        } else {
366
+            $this->setToken(null);
367
+            $firstTimeLogin = $user->updateLastLoginTimestamp();
368
+        }
369
+        $this->manager->emit('\OC\User', 'postLogin', [$user, $loginDetails['password']]);
370
+        if($this->isLoggedIn()) {
371
+            $this->prepareUserLogin($firstTimeLogin, $regenerateSessionId);
372
+            return true;
373
+        } else {
374
+            $message = \OC::$server->getL10N('lib')->t('Login canceled by app');
375
+            throw new LoginException($message);
376
+        }
377
+    }
378
+
379
+    /**
380
+     * Tries to log in a client
381
+     *
382
+     * Checks token auth enforced
383
+     * Checks 2FA enabled
384
+     *
385
+     * @param string $user
386
+     * @param string $password
387
+     * @param IRequest $request
388
+     * @param OC\Security\Bruteforce\Throttler $throttler
389
+     * @throws LoginException
390
+     * @throws PasswordLoginForbiddenException
391
+     * @return boolean
392
+     */
393
+    public function logClientIn($user,
394
+                                $password,
395
+                                IRequest $request,
396
+                                OC\Security\Bruteforce\Throttler $throttler) {
397
+        $currentDelay = $throttler->sleepDelay($request->getRemoteAddress(), 'login');
398
+
399
+        if ($this->manager instanceof PublicEmitter) {
400
+            $this->manager->emit('\OC\User', 'preLogin', array($user, $password));
401
+        }
402
+
403
+        try {
404
+            $isTokenPassword = $this->isTokenPassword($password);
405
+        } catch (ExpiredTokenException $e) {
406
+            // Just return on an expired token no need to check further or record a failed login
407
+            return false;
408
+        }
409
+
410
+        if (!$isTokenPassword && $this->isTokenAuthEnforced()) {
411
+            throw new PasswordLoginForbiddenException();
412
+        }
413
+        if (!$isTokenPassword && $this->isTwoFactorEnforced($user)) {
414
+            throw new PasswordLoginForbiddenException();
415
+        }
416
+
417
+        // Try to login with this username and password
418
+        if (!$this->login($user, $password) ) {
419
+
420
+            // Failed, maybe the user used their email address
421
+            $users = $this->manager->getByEmail($user);
422
+            if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) {
423
+
424
+                $this->logger->warning('Login failed: \'' . $user . '\' (Remote IP: \'' . \OC::$server->getRequest()->getRemoteAddress() . '\')', ['app' => 'core']);
425
+
426
+                $throttler->registerAttempt('login', $request->getRemoteAddress(), ['uid' => $user]);
427
+                if ($currentDelay === 0) {
428
+                    $throttler->sleepDelay($request->getRemoteAddress(), 'login');
429
+                }
430
+                return false;
431
+            }
432
+        }
433
+
434
+        if ($isTokenPassword) {
435
+            $this->session->set('app_password', $password);
436
+        } else if($this->supportsCookies($request)) {
437
+            // Password login, but cookies supported -> create (browser) session token
438
+            $this->createSessionToken($request, $this->getUser()->getUID(), $user, $password);
439
+        }
440
+
441
+        return true;
442
+    }
443
+
444
+    protected function supportsCookies(IRequest $request) {
445
+        if (!is_null($request->getCookie('cookie_test'))) {
446
+            return true;
447
+        }
448
+        setcookie('cookie_test', 'test', $this->timeFactory->getTime() + 3600);
449
+        return false;
450
+    }
451
+
452
+    private function isTokenAuthEnforced() {
453
+        return $this->config->getSystemValue('token_auth_enforced', false);
454
+    }
455
+
456
+    protected function isTwoFactorEnforced($username) {
457
+        Util::emitHook(
458
+            '\OCA\Files_Sharing\API\Server2Server',
459
+            'preLoginNameUsedAsUserName',
460
+            array('uid' => &$username)
461
+        );
462
+        $user = $this->manager->get($username);
463
+        if (is_null($user)) {
464
+            $users = $this->manager->getByEmail($username);
465
+            if (empty($users)) {
466
+                return false;
467
+            }
468
+            if (count($users) !== 1) {
469
+                return true;
470
+            }
471
+            $user = $users[0];
472
+        }
473
+        // DI not possible due to cyclic dependencies :'-/
474
+        return OC::$server->getTwoFactorAuthManager()->isTwoFactorAuthenticated($user);
475
+    }
476
+
477
+    /**
478
+     * Check if the given 'password' is actually a device token
479
+     *
480
+     * @param string $password
481
+     * @return boolean
482
+     * @throws ExpiredTokenException
483
+     */
484
+    public function isTokenPassword($password) {
485
+        try {
486
+            $this->tokenProvider->getToken($password);
487
+            return true;
488
+        } catch (ExpiredTokenException $e) {
489
+            throw $e;
490
+        } catch (InvalidTokenException $ex) {
491
+            return false;
492
+        }
493
+    }
494
+
495
+    protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
496
+        if ($refreshCsrfToken) {
497
+            // TODO: mock/inject/use non-static
498
+            // Refresh the token
499
+            \OC::$server->getCsrfTokenManager()->refreshToken();
500
+        }
501
+
502
+        //we need to pass the user name, which may differ from login name
503
+        $user = $this->getUser()->getUID();
504
+        OC_Util::setupFS($user);
505
+
506
+        if ($firstTimeLogin) {
507
+            // TODO: lock necessary?
508
+            //trigger creation of user home and /files folder
509
+            $userFolder = \OC::$server->getUserFolder($user);
510
+
511
+            try {
512
+                // copy skeleton
513
+                \OC_Util::copySkeleton($user, $userFolder);
514
+            } catch (NotPermittedException $ex) {
515
+                // read only uses
516
+            }
517
+
518
+            // trigger any other initialization
519
+            \OC::$server->getEventDispatcher()->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser()));
520
+        }
521
+    }
522
+
523
+    /**
524
+     * Tries to login the user with HTTP Basic Authentication
525
+     *
526
+     * @todo do not allow basic auth if the user is 2FA enforced
527
+     * @param IRequest $request
528
+     * @param OC\Security\Bruteforce\Throttler $throttler
529
+     * @return boolean if the login was successful
530
+     */
531
+    public function tryBasicAuthLogin(IRequest $request,
532
+                                        OC\Security\Bruteforce\Throttler $throttler) {
533
+        if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) {
534
+            try {
535
+                if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) {
536
+                    /**
537
+                     * Add DAV authenticated. This should in an ideal world not be
538
+                     * necessary but the iOS App reads cookies from anywhere instead
539
+                     * only the DAV endpoint.
540
+                     * This makes sure that the cookies will be valid for the whole scope
541
+                     * @see https://github.com/owncloud/core/issues/22893
542
+                     */
543
+                    $this->session->set(
544
+                        Auth::DAV_AUTHENTICATED, $this->getUser()->getUID()
545
+                    );
546
+
547
+                    // Set the last-password-confirm session to make the sudo mode work
548
+                        $this->session->set('last-password-confirm', $this->timeFactory->getTime());
549
+
550
+                    return true;
551
+                }
552
+            } catch (PasswordLoginForbiddenException $ex) {
553
+                // Nothing to do
554
+            }
555
+        }
556
+        return false;
557
+    }
558
+
559
+    /**
560
+     * Log an user in via login name and password
561
+     *
562
+     * @param string $uid
563
+     * @param string $password
564
+     * @return boolean
565
+     * @throws LoginException if an app canceld the login process or the user is not enabled
566
+     */
567
+    private function loginWithPassword($uid, $password) {
568
+        $user = $this->manager->checkPasswordNoLogging($uid, $password);
569
+        if ($user === false) {
570
+            // Password check failed
571
+            return false;
572
+        }
573
+
574
+        return $this->completeLogin($user, ['loginName' => $uid, 'password' => $password], false);
575
+    }
576
+
577
+    /**
578
+     * Log an user in with a given token (id)
579
+     *
580
+     * @param string $token
581
+     * @return boolean
582
+     * @throws LoginException if an app canceled the login process or the user is not enabled
583
+     */
584
+    private function loginWithToken($token) {
585
+        try {
586
+            $dbToken = $this->tokenProvider->getToken($token);
587
+        } catch (InvalidTokenException $ex) {
588
+            return false;
589
+        }
590
+        $uid = $dbToken->getUID();
591
+
592
+        // When logging in with token, the password must be decrypted first before passing to login hook
593
+        $password = '';
594
+        try {
595
+            $password = $this->tokenProvider->getPassword($dbToken, $token);
596
+        } catch (PasswordlessTokenException $ex) {
597
+            // Ignore and use empty string instead
598
+        }
599
+
600
+        $this->manager->emit('\OC\User', 'preLogin', array($uid, $password));
601
+
602
+        $user = $this->manager->get($uid);
603
+        if (is_null($user)) {
604
+            // user does not exist
605
+            return false;
606
+        }
607
+
608
+        return $this->completeLogin(
609
+            $user,
610
+            [
611
+                'loginName' => $dbToken->getLoginName(),
612
+                'password' => $password,
613
+                'token' => $dbToken
614
+            ],
615
+            false);
616
+    }
617
+
618
+    /**
619
+     * Create a new session token for the given user credentials
620
+     *
621
+     * @param IRequest $request
622
+     * @param string $uid user UID
623
+     * @param string $loginName login name
624
+     * @param string $password
625
+     * @param int $remember
626
+     * @return boolean
627
+     */
628
+    public function createSessionToken(IRequest $request, $uid, $loginName, $password = null, $remember = IToken::DO_NOT_REMEMBER) {
629
+        if (is_null($this->manager->get($uid))) {
630
+            // User does not exist
631
+            return false;
632
+        }
633
+        $name = isset($request->server['HTTP_USER_AGENT']) ? $request->server['HTTP_USER_AGENT'] : 'unknown browser';
634
+        try {
635
+            $sessionId = $this->session->getId();
636
+            $pwd = $this->getPassword($password);
637
+            // Make sure the current sessionId has no leftover tokens
638
+            $this->tokenProvider->invalidateToken($sessionId);
639
+            $this->tokenProvider->generateToken($sessionId, $uid, $loginName, $pwd, $name, IToken::TEMPORARY_TOKEN, $remember);
640
+            return true;
641
+        } catch (SessionNotAvailableException $ex) {
642
+            // This can happen with OCC, where a memory session is used
643
+            // if a memory session is used, we shouldn't create a session token anyway
644
+            return false;
645
+        }
646
+    }
647
+
648
+    /**
649
+     * Checks if the given password is a token.
650
+     * If yes, the password is extracted from the token.
651
+     * If no, the same password is returned.
652
+     *
653
+     * @param string $password either the login password or a device token
654
+     * @return string|null the password or null if none was set in the token
655
+     */
656
+    private function getPassword($password) {
657
+        if (is_null($password)) {
658
+            // This is surely no token ;-)
659
+            return null;
660
+        }
661
+        try {
662
+            $token = $this->tokenProvider->getToken($password);
663
+            try {
664
+                return $this->tokenProvider->getPassword($token, $password);
665
+            } catch (PasswordlessTokenException $ex) {
666
+                return null;
667
+            }
668
+        } catch (InvalidTokenException $ex) {
669
+            return $password;
670
+        }
671
+    }
672
+
673
+    /**
674
+     * @param IToken $dbToken
675
+     * @param string $token
676
+     * @return boolean
677
+     */
678
+    private function checkTokenCredentials(IToken $dbToken, $token) {
679
+        // Check whether login credentials are still valid and the user was not disabled
680
+        // This check is performed each 5 minutes
681
+        $lastCheck = $dbToken->getLastCheck() ? : 0;
682
+        $now = $this->timeFactory->getTime();
683
+        if ($lastCheck > ($now - 60 * 5)) {
684
+            // Checked performed recently, nothing to do now
685
+            return true;
686
+        }
687
+
688
+        try {
689
+            $pwd = $this->tokenProvider->getPassword($dbToken, $token);
690
+        } catch (InvalidTokenException $ex) {
691
+            // An invalid token password was used -> log user out
692
+            return false;
693
+        } catch (PasswordlessTokenException $ex) {
694
+            // Token has no password
695
+
696
+            if (!is_null($this->activeUser) && !$this->activeUser->isEnabled()) {
697
+                $this->tokenProvider->invalidateToken($token);
698
+                return false;
699
+            }
700
+
701
+            $dbToken->setLastCheck($now);
702
+            return true;
703
+        }
704
+
705
+        if ($this->manager->checkPassword($dbToken->getLoginName(), $pwd) === false
706
+            || (!is_null($this->activeUser) && !$this->activeUser->isEnabled())) {
707
+            $this->tokenProvider->invalidateToken($token);
708
+            // Password has changed or user was disabled -> log user out
709
+            return false;
710
+        }
711
+        $dbToken->setLastCheck($now);
712
+        return true;
713
+    }
714
+
715
+    /**
716
+     * Check if the given token exists and performs password/user-enabled checks
717
+     *
718
+     * Invalidates the token if checks fail
719
+     *
720
+     * @param string $token
721
+     * @param string $user login name
722
+     * @return boolean
723
+     */
724
+    private function validateToken($token, $user = null) {
725
+        try {
726
+            $dbToken = $this->tokenProvider->getToken($token);
727
+        } catch (InvalidTokenException $ex) {
728
+            return false;
729
+        }
730
+
731
+        // Check if login names match
732
+        if (!is_null($user) && $dbToken->getLoginName() !== $user) {
733
+            // TODO: this makes it imposssible to use different login names on browser and client
734
+            // e.g. login by e-mail '[email protected]' on browser for generating the token will not
735
+            //      allow to use the client token with the login name 'user'.
736
+            return false;
737
+        }
738
+
739
+        if (!$this->checkTokenCredentials($dbToken, $token)) {
740
+            return false;
741
+        }
742
+
743
+        // Update token scope
744
+        $this->lockdownManager->setToken($dbToken);
745
+
746
+        $this->tokenProvider->updateTokenActivity($dbToken);
747
+
748
+        return true;
749
+    }
750
+
751
+    /**
752
+     * Tries to login the user with auth token header
753
+     *
754
+     * @param IRequest $request
755
+     * @todo check remember me cookie
756
+     * @return boolean
757
+     */
758
+    public function tryTokenLogin(IRequest $request) {
759
+        $authHeader = $request->getHeader('Authorization');
760
+        if (strpos($authHeader, 'Bearer ') === false) {
761
+            // No auth header, let's try session id
762
+            try {
763
+                $token = $this->session->getId();
764
+            } catch (SessionNotAvailableException $ex) {
765
+                return false;
766
+            }
767
+        } else {
768
+            $token = substr($authHeader, 7);
769
+        }
770
+
771
+        if (!$this->loginWithToken($token)) {
772
+            return false;
773
+        }
774
+        if(!$this->validateToken($token)) {
775
+            return false;
776
+        }
777
+        return true;
778
+    }
779
+
780
+    /**
781
+     * perform login using the magic cookie (remember login)
782
+     *
783
+     * @param string $uid the username
784
+     * @param string $currentToken
785
+     * @param string $oldSessionId
786
+     * @return bool
787
+     */
788
+    public function loginWithCookie($uid, $currentToken, $oldSessionId) {
789
+        $this->session->regenerateId();
790
+        $this->manager->emit('\OC\User', 'preRememberedLogin', array($uid));
791
+        $user = $this->manager->get($uid);
792
+        if (is_null($user)) {
793
+            // user does not exist
794
+            return false;
795
+        }
796
+
797
+        // get stored tokens
798
+        $tokens = $this->config->getUserKeys($uid, 'login_token');
799
+        // test cookies token against stored tokens
800
+        if (!in_array($currentToken, $tokens, true)) {
801
+            return false;
802
+        }
803
+        // replace successfully used token with a new one
804
+        $this->config->deleteUserValue($uid, 'login_token', $currentToken);
805
+        $newToken = $this->random->generate(32);
806
+        $this->config->setUserValue($uid, 'login_token', $newToken, $this->timeFactory->getTime());
807
+
808
+        try {
809
+            $sessionId = $this->session->getId();
810
+            $this->tokenProvider->renewSessionToken($oldSessionId, $sessionId);
811
+        } catch (SessionNotAvailableException $ex) {
812
+            return false;
813
+        } catch (InvalidTokenException $ex) {
814
+            \OC::$server->getLogger()->warning('Renewing session token failed', ['app' => 'core']);
815
+            return false;
816
+        }
817
+
818
+        $this->setMagicInCookie($user->getUID(), $newToken);
819
+        $token = $this->tokenProvider->getToken($sessionId);
820
+
821
+        //login
822
+        $this->setUser($user);
823
+        $this->setLoginName($token->getLoginName());
824
+        $this->setToken($token->getId());
825
+        $this->lockdownManager->setToken($token);
826
+        $user->updateLastLoginTimestamp();
827
+        $password = null;
828
+        try {
829
+            $password = $this->tokenProvider->getPassword($token, $sessionId);
830
+        } catch (PasswordlessTokenException $ex) {
831
+            // Ignore
832
+        }
833
+        $this->manager->emit('\OC\User', 'postRememberedLogin', [$user, $password]);
834
+        return true;
835
+    }
836
+
837
+    /**
838
+     * @param IUser $user
839
+     */
840
+    public function createRememberMeToken(IUser $user) {
841
+        $token = $this->random->generate(32);
842
+        $this->config->setUserValue($user->getUID(), 'login_token', $token, $this->timeFactory->getTime());
843
+        $this->setMagicInCookie($user->getUID(), $token);
844
+    }
845
+
846
+    /**
847
+     * logout the user from the session
848
+     */
849
+    public function logout() {
850
+        $this->manager->emit('\OC\User', 'logout');
851
+        $user = $this->getUser();
852
+        if (!is_null($user)) {
853
+            try {
854
+                $this->tokenProvider->invalidateToken($this->session->getId());
855
+            } catch (SessionNotAvailableException $ex) {
856
+
857
+            }
858
+        }
859
+        $this->setUser(null);
860
+        $this->setLoginName(null);
861
+        $this->setToken(null);
862
+        $this->unsetMagicInCookie();
863
+        $this->session->clear();
864
+        $this->manager->emit('\OC\User', 'postLogout');
865
+    }
866
+
867
+    /**
868
+     * Set cookie value to use in next page load
869
+     *
870
+     * @param string $username username to be set
871
+     * @param string $token
872
+     */
873
+    public function setMagicInCookie($username, $token) {
874
+        $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
875
+        $webRoot = \OC::$WEBROOT;
876
+        if ($webRoot === '') {
877
+            $webRoot = '/';
878
+        }
879
+
880
+        $expires = $this->timeFactory->getTime() + $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
881
+        setcookie('nc_username', $username, $expires, $webRoot, '', $secureCookie, true);
882
+        setcookie('nc_token', $token, $expires, $webRoot, '', $secureCookie, true);
883
+        try {
884
+            setcookie('nc_session_id', $this->session->getId(), $expires, $webRoot, '', $secureCookie, true);
885
+        } catch (SessionNotAvailableException $ex) {
886
+            // ignore
887
+        }
888
+    }
889
+
890
+    /**
891
+     * Remove cookie for "remember username"
892
+     */
893
+    public function unsetMagicInCookie() {
894
+        //TODO: DI for cookies and IRequest
895
+        $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https';
896
+
897
+        unset($_COOKIE['nc_username']); //TODO: DI
898
+        unset($_COOKIE['nc_token']);
899
+        unset($_COOKIE['nc_session_id']);
900
+        setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
901
+        setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
902
+        setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true);
903
+        // old cookies might be stored under /webroot/ instead of /webroot
904
+        // and Firefox doesn't like it!
905
+        setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
906
+        setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
907
+        setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true);
908
+    }
909
+
910
+    /**
911
+     * Update password of the browser session token if there is one
912
+     *
913
+     * @param string $password
914
+     */
915
+    public function updateSessionTokenPassword($password) {
916
+        try {
917
+            $sessionId = $this->session->getId();
918
+            $token = $this->tokenProvider->getToken($sessionId);
919
+            $this->tokenProvider->setPassword($token, $sessionId, $password);
920
+        } catch (SessionNotAvailableException $ex) {
921
+            // Nothing to do
922
+        } catch (InvalidTokenException $ex) {
923
+            // Nothing to do
924
+        }
925
+    }
926 926
 
927 927
 
928 928
 }
Please login to merge, or discard this patch.