Completed
Pull Request — master (#28003)
by Piotr
11:34
created

Auth   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 223
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 0
Metric Value
dl 0
loc 223
rs 9.44
c 0
b 0
f 0
wmc 37
lcom 1
cbo 14

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 1
A isDavAuthenticated() 0 4 2
B validateUserPass() 0 29 7
A check() 0 14 4
B requiresCSRFCheck() 0 28 6
C auth() 0 46 14
A checkAccountModule() 0 10 3
1
<?php
2
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Bart Visscher <[email protected]>
5
 * @author Christoph Wurst <[email protected]>
6
 * @author Jakob Sack <[email protected]>
7
 * @author Lukas Reschke <[email protected]>
8
 * @author Markus Goetz <[email protected]>
9
 * @author Michael Gapczynski <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Thomas Müller <[email protected]>
12
 * @author Vincent Petry <[email protected]>
13
 *
14
 * @copyright Copyright (c) 2018, ownCloud GmbH
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
namespace OCA\DAV\Connector\Sabre;
31
32
use Exception;
33
use OC\AppFramework\Http\Request;
34
use OC\Authentication\Exceptions\AccountCheckException;
35
use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
36
use OC\Authentication\TwoFactorAuth\Manager;
37
use OC\Authentication\AccountModule\Manager as AccountModuleManager;
38
use OC\User\LoginException;
39
use OC\User\Session;
40
use OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden;
41
use OCP\IRequest;
42
use OCP\ISession;
43
use Sabre\DAV\Auth\Backend\AbstractBasic;
44
use Sabre\DAV\Exception\NotAuthenticated;
45
use Sabre\DAV\Exception\ServiceUnavailable;
46
use Sabre\HTTP\RequestInterface;
47
use Sabre\HTTP\ResponseInterface;
48
49
class Auth extends AbstractBasic {
50
	const DAV_AUTHENTICATED = 'AUTHENTICATED_TO_DAV_BACKEND';
51
52
	/** @var ISession */
53
	private $session;
54
	/** @var Session */
55
	private $userSession;
56
	/** @var IRequest */
57
	private $request;
58
	/** @var string */
59
	private $currentUser;
60
	/** @var Manager */
61
	private $twoFactorManager;
62
	/** @var AccountModuleManager */
63
	private $accountModuleManager;
64
65
	/**
66
	 * @param ISession $session
67
	 * @param Session $userSession
68
	 * @param IRequest $request
69
	 * @param Manager $twoFactorManager
70
	 * @param AccountModuleManager $accountModuleManager
71
	 * @param string $principalPrefix
72
	 */
73
	public function __construct(ISession $session,
74
								Session $userSession,
75
								IRequest $request,
76
								Manager $twoFactorManager,
77
								AccountModuleManager $accountModuleManager,
78
								$principalPrefix = 'principals/users/') {
79
		$this->session = $session;
80
		$this->userSession = $userSession;
81
		$this->twoFactorManager = $twoFactorManager;
82
		$this->accountModuleManager = $accountModuleManager;
83
		$this->request = $request;
84
		$this->principalPrefix = $principalPrefix;
85
86
		// setup realm
87
		$defaults = new \OC_Defaults();
88
		$this->realm = $defaults->getName();
89
	}
90
91
	/**
92
	 * Whether the user has initially authenticated via DAV
93
	 *
94
	 * This is required for WebDAV clients that resent the cookies even when the
95
	 * account was changed.
96
	 *
97
	 * @see https://github.com/owncloud/core/issues/13245
98
	 *
99
	 * @param string $username
100
	 * @return bool
101
	 */
102
	public function isDavAuthenticated($username) {
103
		return $this->session->get(self::DAV_AUTHENTICATED) !== null &&
104
		$this->session->get(self::DAV_AUTHENTICATED) === $username;
105
	}
106
107
	/**
108
	 * Validates a username and password
109
	 *
110
	 * This method should return true or false depending on if login
111
	 * succeeded.
112
	 *
113
	 * @param string $username
114
	 * @param string $password
115
	 * @return bool
116
	 */
117
	protected function validateUserPass($username, $password) {
118
		if (\trim($username) === '') {
119
			return false;
120
		}
121
		if ($this->userSession->isLoggedIn() &&
122
			$this->userSession->verifyAuthHeaders($this->request) &&
123
			$this->isDavAuthenticated($this->userSession->getUser()->getUID())
124
		) {
125
			\OC_Util::setupFS($this->userSession->getUser()->getUID());
126
			$this->session->close();
127
			return true;
128
		} else {
129
			\OC_Util::setupFS(); //login hooks may need early access to the filesystem
130
			try {
131
				if ($this->userSession->logClientIn($username, $password, $this->request)) {
132
					\OC_Util::setupFS($this->userSession->getUser()->getUID());
133
					$this->session->set(self::DAV_AUTHENTICATED, $this->userSession->getUser()->getUID());
134
					$this->session->close();
135
					return true;
136
				} else {
137
					$this->session->close();
138
					return false;
139
				}
140
			} catch (PasswordLoginForbiddenException $ex) {
141
				$this->session->close();
142
				throw new PasswordLoginForbidden();
143
			}
144
		}
145
	}
146
147
	/**
148
	 * @param RequestInterface $request
149
	 * @param ResponseInterface $response
150
	 * @return array
151
	 * @throws NotAuthenticated
152
	 * @throws ServiceUnavailable
153
	 */
154
	public function check(RequestInterface $request, ResponseInterface $response) {
155
		try {
156
			$result = $this->auth($request, $response);
157
			return $result;
158
		} catch (LoginException $e) {
159
			throw new NotAuthenticated($e->getMessage(), $e->getCode(), $e);
160
		} catch (NotAuthenticated $e) {
161
			throw $e;
162
		} catch (Exception $e) {
163
			$class = \get_class($e);
164
			$msg = $e->getMessage();
165
			throw new ServiceUnavailable("$class: $msg");
166
		}
167
	}
168
169
	/**
170
	 * Checks whether a CSRF check is required on the request
171
	 *
172
	 * @return bool
173
	 */
174
	private function requiresCSRFCheck() {
175
		// If not POST no check is required
176
		if ($this->request->getMethod() !== 'POST') {
177
			return false;
178
		}
179
180
		// Official ownCloud clients require no checks
181
		if ($this->request->isUserAgent([
182
			Request::USER_AGENT_OWNCLOUD_DESKTOP,
183
			Request::USER_AGENT_OWNCLOUD_ANDROID,
184
			Request::USER_AGENT_OWNCLOUD_IOS,
185
		])) {
186
			return false;
187
		}
188
189
		// If not logged-in no check is required
190
		if (!$this->userSession->isLoggedIn()) {
191
			return false;
192
		}
193
194
		// If logged-in AND DAV authenticated no check is required
195
		if ($this->userSession->isLoggedIn() &&
196
			$this->isDavAuthenticated($this->userSession->getUser()->getUID())) {
197
			return false;
198
		}
199
200
		return true;
201
	}
202
203
	/**
204
	 * @param RequestInterface $request
205
	 * @param ResponseInterface $response
206
	 * @return array
207
	 * @throws NotAuthenticated
208
	 * @throws ServiceUnavailable
209
	 */
210
	private function auth(RequestInterface $request, ResponseInterface $response) {
211
		$forcedLogout = false;
212
		if (!$this->request->passesCSRFCheck() &&
213
			$this->requiresCSRFCheck()) {
214
			// In case of a fail with POST we need to recheck the credentials
215
			$forcedLogout = true;
216
		}
217
218
		if ($forcedLogout) {
219
			$this->userSession->logout();
220
		} else {
221
			if ($this->twoFactorManager->needsSecondFactor()) {
222
				throw new \Sabre\DAV\Exception\NotAuthenticated('2FA challenge not passed.');
223
			}
224
			if (\OC_User::handleApacheAuth() ||
225
				//Fix for broken webdav clients
226
				($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === null) ||
227
				//Well behaved clients that only send the cookie are allowed
228
				($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && $request->getHeader('Authorization') === null)
229
			) {
230
				$user = $this->userSession->getUser();
231
				$this->checkAccountModule($user);
232
				$uid = $user->getUID();
233
				\OC_Util::setupFS($uid);
234
				$this->currentUser = $uid;
235
				$this->session->close();
236
				return [true, $this->principalPrefix . $uid];
237
			}
238
		}
239
240
		if (!$this->userSession->isLoggedIn() && \in_array('XMLHttpRequest', \explode(',', $request->getHeader('X-Requested-With')))) {
241
			// do not re-authenticate over ajax, use dummy auth name to prevent browser popup
242
			$response->addHeader('WWW-Authenticate', 'DummyBasic realm="' . $this->realm . '"');
243
			$response->setStatus(401);
244
			throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls');
245
		}
246
247
		$data = parent::check($request, $response);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (check() instead of auth()). Are you sure this is correct? If so, you might want to change this to $this->check().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
248
		if ($data[0] === true) {
249
			$user = $this->userSession->getUser();
250
			$this->checkAccountModule($user);
251
			$startPos = \strrpos($data[1], '/') + 1;
252
			$data[1] = \substr_replace($data[1], $user->getUID(), $startPos);
253
		}
254
		return $data;
255
	}
256
257
	/**
258
	 * @param $user
259
	 * @throws ServiceUnavailable
260
	 */
261
	private function checkAccountModule($user) {
262
		if ($user === null) {
263
			throw new \UnexpectedValueException('No user in session');
264
		}
265
		try {
266
			$this->accountModuleManager->check($user);
267
		} catch (AccountCheckException $ex) {
268
			throw new ServiceUnavailable($ex->getMessage(), $ex->getCode(), $ex);
269
		}
270
	}
271
}
272