Completed
Push — master ( a82218...06e071 )
by Thomas
11:03
created

Auth::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 12
nc 1
nop 5
dl 0
loc 15
rs 9.4285
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) 2017, 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\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
	public function __construct(ISession $session,
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 (trim($username) === '') {
113
			return false;
114
		}
115
		if ($this->userSession->isLoggedIn() &&
116
			$this->userSession->verifyAuthHeaders($this->request) &&
117
			$this->isDavAuthenticated($this->userSession->getUser()->getUID())
118
		) {
119
			\OC_Util::setupFS($this->userSession->getUser()->getUID());
120
			$this->session->close();
121
			return true;
122
		} else {
123
			\OC_Util::setupFS(); //login hooks may need early access to the filesystem
124
			try {
125
				if ($this->userSession->logClientIn($username, $password, $this->request)) {
126
					\OC_Util::setupFS($this->userSession->getUser()->getUID());
127
					$this->session->set(self::DAV_AUTHENTICATED, $this->userSession->getUser()->getUID());
128
					$this->session->close();
129
					return true;
130
				} else {
131
					$this->session->close();
132
					return false;
133
				}
134
			} catch (PasswordLoginForbiddenException $ex) {
135
				$this->session->close();
136
				throw new PasswordLoginForbidden();
137
			}
138
		}
139
	}
140
141
	/**
142
	 * @param RequestInterface $request
143
	 * @param ResponseInterface $response
144
	 * @return array
145
	 * @throws NotAuthenticated
146
	 * @throws ServiceUnavailable
147
	 */
148
	function check(RequestInterface $request, ResponseInterface $response) {
149
		try {
150
			$result = $this->auth($request, $response);
151
			return $result;
152
		} catch (NotAuthenticated $e) {
153
			throw $e;
154
		} catch (Exception $e) {
155
			$class = get_class($e);
156
			$msg = $e->getMessage();
157
			throw new ServiceUnavailable("$class: $msg");
158
		}
159
	}
160
161
	/**
162
	 * Checks whether a CSRF check is required on the request
163
	 *
164
	 * @return bool
165
	 */
166
	private function requiresCSRFCheck() {
167
		// GET requires no check at all
168
		if($this->request->getMethod() === 'GET') {
169
			return false;
170
		}
171
172
		// Official ownCloud clients require no checks
173
		if($this->request->isUserAgent([
174
			Request::USER_AGENT_OWNCLOUD_DESKTOP,
175
			Request::USER_AGENT_OWNCLOUD_ANDROID,
176
			Request::USER_AGENT_OWNCLOUD_IOS,
177
		])) {
178
			return false;
179
		}
180
181
		// If not logged-in no check is required
182
		if(!$this->userSession->isLoggedIn()) {
183
			return false;
184
		}
185
186
		// POST always requires a check
187
		if($this->request->getMethod() === 'POST') {
188
			return true;
189
		}
190
191
		// If logged-in AND DAV authenticated no check is required
192
		if($this->userSession->isLoggedIn() &&
193
			$this->isDavAuthenticated($this->userSession->getUser()->getUID())) {
194
			return false;
195
		}
196
197
		return true;
198
	}
199
200
	/**
201
	 * @param RequestInterface $request
202
	 * @param ResponseInterface $response
203
	 * @return array
204
	 * @throws NotAuthenticated
205
	 */
206
	private function auth(RequestInterface $request, ResponseInterface $response) {
207
		$forcedLogout = false;
208
		if(!$this->request->passesCSRFCheck() &&
209
			$this->requiresCSRFCheck()) {
210
			// In case of a fail with POST we need to recheck the credentials
211
			if($this->request->getMethod() === 'POST') {
212
				$forcedLogout = true;
213
			} else {
214
				$response->setStatus(401);
215
				throw new \Sabre\DAV\Exception\NotAuthenticated('CSRF check not passed.');
216
			}
217
		}
218
219
		if($forcedLogout) {
220
			$this->userSession->logout();
221
		} else {
222
			if ($this->twoFactorManager->needsSecondFactor()) {
223
				throw new \Sabre\DAV\Exception\NotAuthenticated('2FA challenge not passed.');
224
			}
225
			if (\OC_User::handleApacheAuth() ||
226
				//Fix for broken webdav clients
227
				($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED))) ||
228
				//Well behaved clients that only send the cookie are allowed
229
				($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && $request->getHeader('Authorization') === null)
230
			) {
231
				$user = $this->userSession->getUser()->getUID();
232
				\OC_Util::setupFS($user);
233
				$this->currentUser = $user;
234
				$this->session->close();
235
				return [true, $this->principalPrefix . $user];
236
			}
237
		}
238
239
		if (!$this->userSession->isLoggedIn() && in_array('XMLHttpRequest', explode(',', $request->getHeader('X-Requested-With')))) {
240
			// do not re-authenticate over ajax, use dummy auth name to prevent browser popup
241
			$response->addHeader('WWW-Authenticate','DummyBasic realm="' . $this->realm . '"');
242
			$response->setStatus(401);
243
			throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls');
244
		}
245
246
		$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...
247
		if($data[0] === true) {
248
			$startPos = strrpos($data[1], '/') + 1;
249
			$user = $this->userSession->getUser()->getUID();
250
			$data[1] = substr_replace($data[1], $user, $startPos);
251
		}
252
		return $data;
253
	}
254
}
255