Completed
Pull Request — master (#186)
by Lukas
10:10
created

Auth::isDavAuthenticated()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
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) 2016, ownCloud, Inc.
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\PasswordLoginForbiddenException;
35
use OC\Authentication\TwoFactorAuth\Manager;
36
use OC\User\Session;
37
use OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden;
38
use OCP\IRequest;
39
use OCP\ISession;
40
use Sabre\DAV\Auth\Backend\AbstractBasic;
41
use Sabre\DAV\Exception\NotAuthenticated;
42
use Sabre\DAV\Exception\ServiceUnavailable;
43
use Sabre\HTTP\RequestInterface;
44
use Sabre\HTTP\ResponseInterface;
45
46
class Auth extends AbstractBasic {
47
48
49
	const DAV_AUTHENTICATED = 'AUTHENTICATED_TO_DAV_BACKEND';
50
51
	/** @var ISession */
52
	private $session;
53
	/** @var Session */
54
	private $userSession;
55
	/** @var IRequest */
56
	private $request;
57
	/** @var string */
58
	private $currentUser;
59
	/** @var Manager */
60
	private $twoFactorManager;
61
62
	/**
63
	 * @param ISession $session
64
	 * @param Session $userSession
65
	 * @param IRequest $request
66
	 * @param Manager $twoFactorManager
67
	 * @param string $principalPrefix
68
	 */
69 View Code Duplication
	public function __construct(ISession $session,
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
70
								Session $userSession,
71
								IRequest $request,
72
								Manager $twoFactorManager,
73
								$principalPrefix = 'principals/users/') {
74
		$this->session = $session;
75
		$this->userSession = $userSession;
76
		$this->twoFactorManager = $twoFactorManager;
77
		$this->request = $request;
78
		$this->principalPrefix = $principalPrefix;
79
80
		// setup realm
81
		$defaults = new \OC_Defaults();
82
		$this->realm = $defaults->getName();
83
	}
84
85
	/**
86
	 * Whether the user has initially authenticated via DAV
87
	 *
88
	 * This is required for WebDAV clients that resent the cookies even when the
89
	 * account was changed.
90
	 *
91
	 * @see https://github.com/owncloud/core/issues/13245
92
	 *
93
	 * @param string $username
94
	 * @return bool
95
	 */
96
	public function isDavAuthenticated($username) {
97
		return !is_null($this->session->get(self::DAV_AUTHENTICATED)) &&
98
		$this->session->get(self::DAV_AUTHENTICATED) === $username;
99
	}
100
101
	/**
102
	 * Validates a username and password
103
	 *
104
	 * This method should return true or false depending on if login
105
	 * succeeded.
106
	 *
107
	 * @param string $username
108
	 * @param string $password
109
	 * @return bool
110
	 */
111
	protected function validateUserPass($username, $password) {
112
		if ($this->userSession->isLoggedIn() &&
113
			$this->isDavAuthenticated($this->userSession->getUser()->getUID())
114
		) {
115
			\OC_Util::setupFS($this->userSession->getUser()->getUID());
116
			$this->session->close();
117
			return true;
118
		} else {
119
			\OC_Util::setupFS(); //login hooks may need early access to the filesystem
120
			try {
121
				if ($this->userSession->logClientIn($username, $password, $this->request)) {
122
					\OC_Util::setupFS($this->userSession->getUser()->getUID());
123
					$this->session->set(self::DAV_AUTHENTICATED, $this->userSession->getUser()->getUID());
124
					$this->session->close();
125
					return true;
126
				} else {
127
					$this->session->close();
128
					return false;
129
				}
130
			} catch (PasswordLoginForbiddenException $ex) {
131
				$this->session->close();
132
				throw new PasswordLoginForbidden();
133
			}
134
		}
135
	}
136
137
	/**
138
	 * @param RequestInterface $request
139
	 * @param ResponseInterface $response
140
	 * @return array
141
	 * @throws NotAuthenticated
142
	 * @throws ServiceUnavailable
143
	 */
144
	function check(RequestInterface $request, ResponseInterface $response) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
145
		try {
146
			$result = $this->auth($request, $response);
147
			return $result;
148
		} catch (NotAuthenticated $e) {
0 ignored issues
show
Bug introduced by
The class Sabre\DAV\Exception\NotAuthenticated does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
149
			throw $e;
150
		} catch (Exception $e) {
151
			$class = get_class($e);
152
			$msg = $e->getMessage();
153
			throw new ServiceUnavailable("$class: $msg");
154
		}
155
	}
156
157
	/**
158
	 * Checks whether a CSRF check is required on the request
159
	 *
160
	 * @return bool
161
	 */
162
	private function requiresCSRFCheck() {
163
		// GET requires no check at all
164
		if($this->request->getMethod() === 'GET') {
165
			return false;
166
		}
167
168
		// Official ownCloud clients require no checks
169
		if($this->request->isUserAgent([
170
			Request::USER_AGENT_OWNCLOUD_DESKTOP,
171
			Request::USER_AGENT_OWNCLOUD_ANDROID,
172
			Request::USER_AGENT_OWNCLOUD_IOS,
173
		])) {
174
			return false;
175
		}
176
177
		// If not logged-in no check is required
178
		if(!$this->userSession->isLoggedIn()) {
179
			return false;
180
		}
181
182
		// POST always requires a check
183
		if($this->request->getMethod() === 'POST') {
184
			return true;
185
		}
186
187
		// If logged-in AND DAV authenticated no check is required
188
		if($this->userSession->isLoggedIn() &&
189
			$this->isDavAuthenticated($this->userSession->getUser()->getUID())) {
190
			return false;
191
		}
192
193
		return true;
194
	}
195
196
	/**
197
	 * @param RequestInterface $request
198
	 * @param ResponseInterface $response
199
	 * @return array
200
	 * @throws NotAuthenticated
201
	 */
202
	private function auth(RequestInterface $request, ResponseInterface $response) {
203
		$forcedLogout = false;
204
		if(!$this->request->passesCSRFCheck() &&
205
			$this->requiresCSRFCheck()) {
206
			// In case of a fail with POST we need to recheck the credentials
207
			if($this->request->getMethod() === 'POST') {
208
				$forcedLogout = true;
209
			} else {
210
				$response->setStatus(401);
211
				throw new \Sabre\DAV\Exception\NotAuthenticated('CSRF check not passed.');
212
			}
213
		}
214
215
		if($forcedLogout) {
216
			$this->userSession->logout();
217
		} else {
218
			if ($this->twoFactorManager->needsSecondFactor()) {
219
				throw new \Sabre\DAV\Exception\NotAuthenticated('2FA challenge not passed.');
220
			}
221
			if (\OC_User::handleApacheAuth() ||
222
				//Fix for broken webdav clients
223
				($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED))) ||
224
				//Well behaved clients that only send the cookie are allowed
225
				($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && $request->getHeader('Authorization') === null)
226
			) {
227
				$user = $this->userSession->getUser()->getUID();
228
				\OC_Util::setupFS($user);
229
				$this->currentUser = $user;
230
				$this->session->close();
231
				return [true, $this->principalPrefix . $user];
232
			}
233
		}
234
235
		if (!$this->userSession->isLoggedIn() && in_array('XMLHttpRequest', explode(',', $request->getHeader('X-Requested-With')))) {
236
			// do not re-authenticate over ajax, use dummy auth name to prevent browser popup
237
			$response->addHeader('WWW-Authenticate','DummyBasic realm="' . $this->realm . '"');
238
			$response->setStatus(401);
239
			throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls');
240
		}
241
242
		$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...
243
		if($data[0] === true) {
244
			$startPos = strrpos($data[1], '/') + 1;
245
			$user = $this->userSession->getUser()->getUID();
246
			$data[1] = substr_replace($data[1], $user, $startPos);
247
		}
248
		return $data;
249
	}
250
}
251