Completed
Push — stable8.2 ( 09e830...ae9bfd )
by Olivier
12:09
created

EnvCheckMiddleware::checkSession()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 2
nop 1
crap 3
1
<?php
2
/**
3
 * ownCloud - galleryplus
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Olivier Paroz <[email protected]>
9
 * @author Bernhard Posselt <[email protected]>
10
 * @author Authors of \OCA\Files_Sharing\Helper
11
 *
12
 * @copyright Olivier Paroz 2014-2015
13
 * @copyright Bernhard Posselt 2012-2015
14
 * @copyright Authors of \OCA\Files_Sharing\Helper 2014-2015
15
 */
16
17
namespace OCA\GalleryPlus\Middleware;
18
19
use OCP\IRequest;
20
use OCP\IURLGenerator;
21
use OCP\ISession;
22
use OCP\ILogger;
23
use OCP\Share;
24
use OCP\Security\IHasher;
25
26
use OCP\AppFramework\Http;
27
use OCP\AppFramework\Utility\IControllerMethodReflector;
28
29
use OCA\GalleryPlus\Environment\Environment;
30
31
/**
32
 * Checks that we have a valid token linked to a valid resource and that the
33
 * user is authorised to access it
34
 *
35
 * Once all checks have been passed, the environment is ready to use
36
 *
37
 * @package OCA\GalleryPlus\Middleware
38
 */
39
class EnvCheckMiddleware extends CheckMiddleware {
40
41
	/** @var IHasher */
42
	private $hasher;
43
	/** @var ISession */
44
	private $session;
45
	/** @var Environment */
46
	private $environment;
47
	/** @var IControllerMethodReflector */
48
	protected $reflector;
49
50
	/***
51
	 * Constructor
52
	 *
53
	 * @param string $appName
54
	 * @param IRequest $request
55
	 * @param IHasher $hasher
56
	 * @param ISession $session
57
	 * @param Environment $environment
58
	 * @param IControllerMethodReflector $reflector
59
	 * @param IURLGenerator $urlGenerator
60
	 * @param ILogger $logger
61
	 */
62 56
	public function __construct(
63
		$appName,
64
		IRequest $request,
65
		IHasher $hasher,
66
		ISession $session,
67
		Environment $environment,
68
		IControllerMethodReflector $reflector,
69
		IURLGenerator $urlGenerator,
70
		ILogger $logger
71
	) {
72 56
		parent::__construct(
73
			$appName,
74
			$request,
75
			$urlGenerator,
76
			$logger
77
		);
78
79 56
		$this->hasher = $hasher;
80 56
		$this->session = $session;
81 56
		$this->environment = $environment;
82 56
		$this->reflector = $reflector;
83 56
	}
84
85
	/**
86
	 * Checks that we have a valid token linked to a valid resource and that the
87
	 * user is authorised to access it
88
	 *
89
	 * Inspects the controller method annotations and if PublicPage is found
90
	 * it checks that we have a token and an optional password giving access to a valid resource.
91
	 * Once that's done, the environment is setup so that our services can find the resources they
92
	 * need.
93
	 *
94
	 * The checks are not performed on "guest" pages and the environment is not setup. Typical
95
	 * guest pages are anonymous error ages
96
	 *
97
	 * @inheritDoc
98
	 */
99 31
	public function beforeController($controller, $methodName) {
100 31
		if ($this->reflector->hasAnnotation('Guest')) {
101 3
			return;
102
		}
103 30
		$isPublicPage = $this->reflector->hasAnnotation('PublicPage');
104 30
		if ($isPublicPage) {
105 11
			$this->validateAndSetTokenBasedEnv();
106
		} else {
107 21
			$this->environment->setStandardEnv();
108
		}
109 27
	}
110
111
	/**
112
	 * Checks that we have a token and an optional password giving access to a
113
	 * valid resource. Sets the token based environment after that
114
	 *
115
	 * @throws CheckException
116
	 */
117 11
	private function validateAndSetTokenBasedEnv() {
118 11
		$token = $this->request->getParam('token');
119 11
		if (!$token) {
120 2
			throw new CheckException(
121 2
				"Can't access a public resource without a token", Http::STATUS_NOT_FOUND
122
			);
123
		} else {
124 9
			$linkItem = $this->getLinkItem($token);
125 7
			$password = $this->request->getParam('password');
126
			// Let's see if the user needs to provide a password
127 7
			$this->checkAuthorisation($linkItem, $password);
128
129 7
			$this->environment->setTokenBasedEnv($linkItem);
130
		}
131 7
	}
132
133
	/**
134
	 * Validates a token to make sure its linked to a valid resource
135
	 *
136
	 * Logic mostly duplicated from @see \OCA\Files_Sharing\Helper
137
	 *
138
	 * @fixme setIncognitoMode in 8.1 https://github.com/owncloud/core/pull/12912
139
	 *
140
	 * @param string $token
141
	 *
142
	 * @return array
143
	 *
144
	 * @throws CheckException
145
	 */
146 9
	private function getLinkItem($token) {
147
		// Allows a logged in user to access public links
148 9
		\OC_User::setIncognitoMode(true);
149
150 9
		$linkItem = Share::getShareByToken($token, false);
151
152 9
		$this->checkLinkItemExists($linkItem);
153 7
		$this->checkLinkItemIsValid($linkItem, $token);
154 7
		$this->checkItemType($linkItem);
155
156
		// Checks passed, let's store the linkItem
157 7
		return $linkItem;
158
	}
159
160
	/**
161
	 * Makes sure that the token exists
162
	 *
163
	 * @param array|bool $linkItem
164
	 *
165
	 * @throws CheckException
166
	 */
167 13
	private function checkLinkItemExists($linkItem) {
168 13
		if ($linkItem === false
169 10
			|| ($linkItem['item_type'] !== 'file'
170 13
				&& $linkItem['item_type'] !== 'folder')
171
		) {
172 4
			$message = 'Passed token parameter is not valid';
173 4
			throw new CheckException($message, Http::STATUS_BAD_REQUEST);
174
		}
175 9
	}
176
177
	/**
178
	 * Makes sure that the token contains all the information that we need
179
	 *
180
	 * @param array|bool $linkItem
181
	 * @param string $token
182
	 *
183
	 * @throws CheckException
184
	 */
185 10
	private function checkLinkItemIsValid($linkItem, $token) {
186 10
		if (!isset($linkItem['uid_owner'])
187 10
			|| !isset($linkItem['file_source'])
188
		) {
189
			$message =
190
				'Passed token seems to be valid, but it does not contain all necessary information . ("'
191 2
				. $token . '")';
192 2
			throw new CheckException($message, Http::STATUS_NOT_FOUND);
193
		}
194 8
	}
195
196
	/**
197
	 * Makes sure an item type was set for that token
198
	 *
199
	 * @param array|bool $linkItem
200
	 *
201
	 * @throws CheckException
202
	 */
203 9
	private function checkItemType($linkItem) {
204 9
		if (!isset($linkItem['item_type'])) {
205 1
			$message = 'No item type set for share id: ' . $linkItem['id'];
206 1
			throw new CheckException($message, Http::STATUS_NOT_FOUND);
207
		}
208 8
	}
209
210
	/**
211
	 * Checks if a password is required or if the one supplied is working
212
	 *
213
	 * @param array|bool $linkItem
214
	 * @param string|null $password optional password
215
	 *
216
	 * @throws CheckException
217
	 */
218 10
	private function checkAuthorisation($linkItem, $password) {
219 10
		$passwordRequired = isset($linkItem['share_with']);
220
221 10
		if ($passwordRequired) {
222 8
			if ($password !== null) {
223 7
				$this->authenticate($linkItem, $password);
224
			} else {
225 1
				$this->checkSession($linkItem);
226
			}
227
		}
228 9
	}
229
230
	/**
231
	 * Authenticate link item with the given password
232
	 * or with the session if no password was given.
233
	 *
234
	 * @fixme @LukasReschke says: Migrate old hashes to new hash format
235
	 * Due to the fact that there is no reasonable functionality to update the password
236
	 * of an existing share no migration is yet performed there.
237
	 * The only possibility is to update the existing share which will result in a new
238
	 * share ID and is a major hack.
239
	 *
240
	 * In the future the migration should be performed once there is a proper method
241
	 * to update the share's password. (for example `$share->updatePassword($password)`
242
	 *
243
	 * @link https://github.com/owncloud/core/issues/10671
244
	 *
245
	 * @param array|bool $linkItem
246
	 * @param string $password
247
	 *
248
	 * @return bool true if authorized, an exception is raised otherwise
249
	 *
250
	 * @throws CheckException
251
	 */
252 10
	private function authenticate($linkItem, $password) {
253 10
		if ((int)$linkItem['share_type'] === Share::SHARE_TYPE_LINK) {
254 9
			$this->checkPassword($linkItem, $password);
255
		} else {
256 1
			throw new CheckException(
257 1
				'Unknown share type ' . $linkItem['share_type'] . ' for share id '
258 1
				. $linkItem['id'], Http::STATUS_NOT_FOUND
259
			);
260
		}
261
262 7
		return true;
263
	}
264
265
	/**
266
	 * Validates the given password
267
	 *
268
	 * @param array|bool $linkItem
269
	 * @param string $password
270
	 *
271
	 * @throws CheckException
272
	 */
273 11
	private function checkPassword($linkItem, $password) {
274 11
		$newHash = '';
275 11
		if ($this->hasher->verify($password, $linkItem['share_with'], $newHash)) {
276
			// Save item id in session for future requests
277 8
			$this->session->set('public_link_authenticated', $linkItem['id']);
278
			// @codeCoverageIgnoreStart
279
			if (!empty($newHash)) {
280
				// For future use
281
			}
282
			// @codeCoverageIgnoreEnd
283
		} else {
284 3
			throw new CheckException("Wrong password", Http::STATUS_UNAUTHORIZED);
285
		}
286 8
	}
287
288
	/**
289
	 * Makes sure the user is already properly authenticated when a password is required and none
290
	 * was provided
291
	 *
292
	 * @param array|bool $linkItem
293
	 *
294
	 * @throws CheckException
295
	 */
296 4
	private function checkSession($linkItem) {
297
		// Not authenticated ?
298 4
		if (!$this->session->exists('public_link_authenticated')
299 4
			|| $this->session->get('public_link_authenticated') !== $linkItem['id']
300
		) {
301 2
			throw new CheckException("Missing password", Http::STATUS_UNAUTHORIZED);
302
		}
303 2
	}
304
305
}
306