Completed
Push — master ( 301509...5ed01a )
by Joas
14s
created

Operation::isBlockablePath()   B

Complexity

Conditions 6
Paths 15

Size

Total Lines 32
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 32
ccs 0
cts 22
cp 0
rs 8.439
cc 6
eloc 20
nc 15
nop 2
crap 42
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016 Morris Jobke <[email protected]>
4
 *
5
 * @license GNU AGPL version 3 or any later version
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Affero General Public License as
9
 * published by the Free Software Foundation, either version 3 of the
10
 * License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 *
20
 */
21
22
namespace OCA\FilesAccessControl;
23
24
25
use OCP\Files\ForbiddenException;
26
use OCP\Files\Storage\IStorage;
27
use OCP\IL10N;
28
use OCP\WorkflowEngine\IManager;
29
use OCP\WorkflowEngine\IOperation;
30
31
class Operation implements IOperation{
32
	/** @var IManager */
33
	protected $manager;
34
35
	/** @var IL10N */
36
	protected $l;
37
38
	/** @var int */
39
	protected $nestingLevel = 0;
40
41
	/**
42
	 * @param IManager $manager
43
	 * @param IL10N $l
44
	 */
45
	public function __construct(IManager $manager, IL10N $l) {
46
		$this->manager = $manager;
47
		$this->l = $l;
48
	}
49
50
	/**
51
	 * @param IStorage $storage
52
	 * @param string $path
53
	 * @throws ForbiddenException
54
	 */
55
	public function checkFileAccess(IStorage $storage, $path) {
56
		if (!$this->isBlockablePath($storage, $path) || $this->isCreatingSkeletonFiles() || $this->nestingLevel !== 0) {
57
			// Allow creating skeletons and theming
58
			// https://github.com/nextcloud/files_accesscontrol/issues/5
59
			// https://github.com/nextcloud/files_accesscontrol/issues/12
60
			return;
61
		}
62
63
		$this->nestingLevel++;
64
65
		$filePath = $this->translatePath($storage, $path);
66
		$this->manager->setFileInfo($storage, $filePath);
67
		$match = $this->manager->getMatchingOperations('OCA\FilesAccessControl\Operation');
68
69
		$this->nestingLevel--;
70
71
		if (!empty($match)) {
72
			// All Checks of one operation matched: prevent access
73
			throw new ForbiddenException('Access denied', true);
74
		}
75
	}
76
77
	/**
78
	 * @param IStorage $storage
79
	 * @param string $path
80
	 * @return bool
81
	 */
82
	protected function isBlockablePath(IStorage $storage, $path) {
83
		if (property_exists($storage, 'mountPoint')) {
84
			$hasMountPoint = $storage instanceof StorageWrapper;
85
			if (!$hasMountPoint) {
86
				$ref = new \ReflectionClass($storage);
87
				$prop = $ref->getProperty('mountPoint');
88
				$hasMountPoint = $prop->isPublic();
89
			}
90
91
			if ($hasMountPoint) {
92
				/** @var StorageWrapper $storage */
93
				$fullPath = $storage->mountPoint . $path;
94
			} else {
95
				$fullPath = $path;
96
			}
97
		} else {
98
			$fullPath = $path;
99
		}
100
101
		if (substr_count($fullPath, '/') < 3) {
102
			return false;
103
		}
104
105
		// '', admin, 'files', 'path/to/file.txt'
106
		$segment = explode('/', $fullPath, 4);
107
108
		return isset($segment[2]) && in_array($segment[2], [
109
			'files',
110
			'thumbnails',
111
			'files_versions',
112
		]);
113
	}
114
115
	/**
116
	 * For thumbnails and versions we want to check the tags of the original file
117
	 *
118
	 * @param IStorage $storage
119
	 * @param string $path
120
	 * @return bool
121
	 */
122
	protected function translatePath(IStorage $storage, $path) {
123
		if (substr_count($path, '/') < 1) {
124
			return $path;
125
		}
126
127
		// 'files', 'path/to/file.txt'
128
		list($folder, $innerPath) = explode('/', $path, 2);
129
130
		if ($folder === 'files_versions') {
131
			$innerPath = substr($innerPath, 0, strrpos($innerPath, '.v'));
132
			return 'files/' . $innerPath;
133
		} else if ($folder === 'thumbnails') {
134
			list($fileId,) = explode('/', $innerPath, 2);
135
			$innerPath = $storage->getCache()->getPathById($fileId);
136
137
			if ($innerPath !== null) {
138
				return 'files/' . $innerPath;
139
			}
140
		}
141
142
		return $path;
143
	}
144
145
	/**
146
	 * Check if we are in the LoginController and if so, ignore the firewall
147
	 * @return bool
148
	 */
149
	protected function isCreatingSkeletonFiles() {
150
		$exception = new \Exception();
151
		$trace = $exception->getTrace();
152
153
		foreach ($trace as $step) {
154
			if (isset($step['class']) && $step['class'] === 'OC\Core\Controller\LoginController' &&
155
				isset($step['function']) && $step['function'] === 'tryLogin') {
156
				return true;
157
			}
158
		}
159
160
		return false;
161
	}
162
163
	/**
164
	 * @param string $name
165
	 * @param array[] $checks
166
	 * @param string $operation
167
	 * @throws \UnexpectedValueException
168
	 */
169
	public function validateOperation($name, array $checks, $operation) {
170
		if (empty($checks)) {
171
			throw new \UnexpectedValueException($this->l->t('No rule given'));
172
		}
173
	}
174
}
175