Completed
Push — master ( 5e8f2e...32d663 )
by Joas
02:58
created

Operation::checkFileAccess()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 5
Bugs 0 Features 0
Metric Value
c 5
b 0
f 0
dl 0
loc 21
ccs 0
cts 11
cp 0
rs 8.7624
cc 5
eloc 10
nc 3
nop 2
crap 30
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\IL10N;
27
use OCP\WorkflowEngine\IManager;
28
use OCP\WorkflowEngine\IOperation;
29
30
class Operation implements IOperation{
31
	/** @var IManager */
32
	protected $manager;
33
34
	/** @var IL10N */
35
	protected $l;
36
37
	/** @var int */
38
	protected $nestingLevel = 0;
39
40
	/**
41
	 * @param IManager $manager
42
	 * @param IL10N $l
43
	 */
44
	public function __construct(IManager $manager, IL10N $l) {
45
		$this->manager = $manager;
46
		$this->l = $l;
47
	}
48
49
	/**
50
	 * @param StorageWrapper $storage
51
	 * @param string $path
52
	 * @throws ForbiddenException
53
	 */
54
	public function checkFileAccess(StorageWrapper $storage, $path) {
55
		if (!$this->isBlockablePath($storage, $path) || $this->isCreatingSkeletonFiles() || $this->nestingLevel !== 0) {
56
			// Allow creating skeletons and theming
57
			// https://github.com/nextcloud/files_accesscontrol/issues/5
58
			// https://github.com/nextcloud/files_accesscontrol/issues/12
59
			return;
60
		}
61
62
		$this->nestingLevel++;
63
64
		$filePath = $this->translatePath($storage, $path);
65
		$this->manager->setFileInfo($storage, $filePath);
66
		$match = $this->manager->getMatchingOperations('OCA\FilesAccessControl\Operation');
67
68
		$this->nestingLevel--;
69
70
		if (!empty($match)) {
71
			// All Checks of one operation matched: prevent access
72
			throw new ForbiddenException('Access denied', true);
73
		}
74
	}
75
76
	/**
77
	 * @param StorageWrapper $storage
78
	 * @param string $path
79
	 * @return bool
80
	 */
81
	protected function isBlockablePath(StorageWrapper $storage, $path) {
82
		$fullPath = $storage->mountPoint . $path;
83
84
		if (substr_count($fullPath, '/') < 3) {
85
			return false;
86
		}
87
88
		// '', admin, 'files', 'path/to/file.txt'
89
		$segment = explode('/', $fullPath, 4);
90
91
		return isset($segment[2]) && in_array($segment[2], [
92
			'files',
93
			'thumbnails',
94
			'files_versions',
95
		]);
96
	}
97
98
	/**
99
	 * For thumbnails and versions we want to check the tags of the original file
100
	 *
101
	 * @param StorageWrapper $storage
102
	 * @param string $path
103
	 * @return bool
104
	 */
105
	protected function translatePath(StorageWrapper $storage, $path) {
106
		if (substr_count($path, '/') < 1) {
107
			return $path;
108
		}
109
110
		// 'files', 'path/to/file.txt'
111
		list($folder, $innerPath) = explode('/', $path, 2);
112
113
		if ($folder === 'files_versions') {
114
			$innerPath = substr($innerPath, 0, strrpos($innerPath, '.v'));
115
			return 'files/' . $innerPath;
116
		} else if ($folder === 'thumbnails') {
117
			list($fileId,) = explode('/', $innerPath, 2);
118
			$innerPath = $storage->getCache()->getPathById($fileId);
119
120
			if ($innerPath !== null) {
121
				return 'files/' . $innerPath;
122
			}
123
		}
124
125
		return $path;
126
	}
127
128
	/**
129
	 * Check if we are in the LoginController and if so, ignore the firewall
130
	 * @return bool
131
	 */
132
	protected function isCreatingSkeletonFiles() {
133
		$exception = new \Exception();
134
		$trace = $exception->getTrace();
135
136
		foreach ($trace as $step) {
137
			if (isset($step['class']) && $step['class'] === 'OC\Core\Controller\LoginController' &&
138
				isset($step['function']) && $step['function'] === 'tryLogin') {
139
				return true;
140
			}
141
		}
142
143
		return false;
144
	}
145
146
	/**
147
	 * @param string $name
148
	 * @param array[] $checks
149
	 * @param string $operation
150
	 * @throws \UnexpectedValueException
151
	 */
152
	public function validateOperation($name, array $checks, $operation) {
153
		if (empty($checks)) {
154
			throw new \UnexpectedValueException($this->l->t('No rule given'));
155
		}
156
	}
157
}
158