Test Failed
Pull Request — master (#99)
by Daniel
24:16
created

Website::assertValidTemplate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
/**
3
 * CMS Pico - Create websites using Pico CMS for Nextcloud.
4
 *
5
 * @copyright Copyright (c) 2017, Maxence Lange (<[email protected]>)
6
 * @copyright Copyright (c) 2019, Daniel Rudolf (<[email protected]>)
7
 *
8
 * @license GNU AGPL version 3 or any later version
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Affero General Public License as
12
 * published by the Free Software Foundation, either version 3 of the
13
 * License, or (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
declare(strict_types=1);
25
26
namespace OCA\CMSPico\Model;
27
28
use OCA\CMSPico\AppInfo\Application;
29
use OCA\CMSPico\Exceptions\TemplateNotCompatibleException;
30
use OCA\CMSPico\Exceptions\TemplateNotFoundException;
31
use OCA\CMSPico\Exceptions\ThemeNotCompatibleException;
32
use OCA\CMSPico\Exceptions\ThemeNotFoundException;
33
use OCA\CMSPico\Exceptions\WebsiteForeignOwnerException;
34
use OCA\CMSPico\Exceptions\WebsiteInvalidDataException;
35
use OCA\CMSPico\Exceptions\WebsiteInvalidFilesystemException;
36
use OCA\CMSPico\Exceptions\WebsiteInvalidOwnerException;
37
use OCA\CMSPico\Exceptions\WebsiteNotPermittedException;
38
use OCA\CMSPico\Files\StorageFile;
39
use OCA\CMSPico\Files\StorageFolder;
40
use OCA\CMSPico\Service\MiscService;
41
use OCA\CMSPico\Service\TemplatesService;
42
use OCA\CMSPico\Service\ThemesService;
43
use OCA\CMSPico\Service\WebsitesService;
44
use OCP\Files\Folder as OCFolder;
45
use OCP\Files\InvalidPathException;
46
use OCP\Files\Node as OCNode;
47
use OCP\Files\NotFoundException;
48
use OCP\Files\NotPermittedException;
49
use OCP\IConfig;
50
use OCP\IGroupManager;
51
use OCP\IL10N;
52
use OCP\IURLGenerator;
53
use OCP\IUserManager;
54
55
class Website extends WebsiteCore
56
{
57
	/** @var int */
58
	public const SITE_LENGTH_MIN = 3;
59
60
	/** @var int */
61
	public const SITE_LENGTH_MAX = 255;
62
63
	/** @var string */
64
	public const SITE_REGEX = '^[a-z0-9][a-z0-9_-]+[a-z0-9]$';
65
66
	/** @var int */
67
	public const NAME_LENGTH_MIN = 3;
68
69
	/** @var int */
70
	public const NAME_LENGTH_MAX = 255;
71
72
	/** @var IConfig */
73
	private $config;
74
75
	/** @var IL10N */
76
	private $l10n;
77
78
	/** @var IUserManager */
79
	private $userManager;
80
81
	/** @var IGroupManager */
82
	private $groupManager;
83
84
	/** @var IURLGenerator */
85
	private $urlGenerator;
86
87
	/** @var WebsitesService */
88
	private $websitesService;
89
90
	/** @var ThemesService */
91
	private $themesService;
92
93
	/** @var TemplatesService */
94
	private $templatesService;
95
96
	/** @var MiscService */
97
	private $miscService;
98
99
	/** @var StorageFolder */
100
	private $folder;
101
102
	/**
103
	 * Website constructor.
104
	 *
105
	 * @param array|string|null $data
106
	 */
107 3
	public function __construct($data = null)
108
	{
109 3
		$this->config = \OC::$server->getConfig();
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getConfig() has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

109
		$this->config = /** @scrutinizer ignore-deprecated */ \OC::$server->getConfig();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
110 3
		$this->l10n = \OC::$server->getL10N(Application::APP_NAME);
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getL10N() has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

110
		$this->l10n = /** @scrutinizer ignore-deprecated */ \OC::$server->getL10N(Application::APP_NAME);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
111 3
		$this->userManager = \OC::$server->getUserManager();
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getUserManager() has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

111
		$this->userManager = /** @scrutinizer ignore-deprecated */ \OC::$server->getUserManager();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
112 3
		$this->groupManager = \OC::$server->getGroupManager();
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getGroupManager() has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

112
		$this->groupManager = /** @scrutinizer ignore-deprecated */ \OC::$server->getGroupManager();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
113 3
		$this->urlGenerator = \OC::$server->getURLGenerator();
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getURLGenerator() has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

113
		$this->urlGenerator = /** @scrutinizer ignore-deprecated */ \OC::$server->getURLGenerator();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
114 3
		$this->websitesService = \OC::$server->query(WebsitesService::class);
1 ignored issue
show
Documentation Bug introduced by
It seems like OC::server->query(OCA\CM...WebsitesService::class) can also be of type stdClass. However, the property $websitesService is declared as type OCA\CMSPico\Service\WebsitesService. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
115 3
		$this->themesService = \OC::$server->query(ThemesService::class);
1 ignored issue
show
Documentation Bug introduced by
It seems like OC::server->query(OCA\CM...e\ThemesService::class) can also be of type stdClass. However, the property $themesService is declared as type OCA\CMSPico\Service\ThemesService. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
116 3
		$this->templatesService = \OC::$server->query(TemplatesService::class);
1 ignored issue
show
Documentation Bug introduced by
It seems like OC::server->query(OCA\CM...emplatesService::class) can also be of type stdClass. However, the property $templatesService is declared as type OCA\CMSPico\Service\TemplatesService. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
117 3
		$this->miscService = \OC::$server->query(MiscService::class);
1 ignored issue
show
Documentation Bug introduced by
It seems like OC::server->query(OCA\CM...ice\MiscService::class) can also be of type stdClass. However, the property $miscService is declared as type OCA\CMSPico\Service\MiscService. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
118
119 3
		parent::__construct($data);
120 3
	}
121
122
	/**
123
	 * @return string
124
	 */
125
	public function getTimeZone(): string
126
	{
127
		$serverTimeZone = date_default_timezone_get() ?: 'UTC';
128
		return $this->config->getUserValue($this->getUserId(), 'core', 'timezone', $serverTimeZone);
129
	}
130
131
	/**
132
	 * @param string $path
133
	 * @param array  $meta
134
	 *
135
	 * @throws InvalidPathException
136
	 * @throws WebsiteInvalidFilesystemException
137
	 * @throws WebsiteNotPermittedException
138
	 * @throws NotPermittedException
139
	 */
140
	public function assertViewerAccess(string $path, array $meta = []): void
141
	{
142
		$exceptionClass = WebsiteNotPermittedException::class;
143
		if ($this->getType() === self::TYPE_PUBLIC) {
144
			if (empty($meta['access'])) {
145
				return;
146
			}
147
148
			$groupPageAccess = $meta['access'];
149
			if (!is_array($groupPageAccess)) {
150
				$groupPageAccess = explode(',', $groupPageAccess);
151
			}
152
153
			foreach ($groupPageAccess as $group) {
154
				$group = trim($group);
155
156
				if ($group === 'public') {
157
					return;
158
				} elseif ($group === 'private') {
159
					continue;
160
				}
161
162
				if ($this->getViewer() && $this->groupManager->groupExists($group)) {
163
					if ($this->groupManager->isInGroup($this->getViewer(), $group)) {
164
						return;
165
					}
166
				}
167
			}
168
169
			$exceptionClass = NotPermittedException::class;
170
		}
171
172
		if ($this->getViewer()) {
173
			if ($this->getViewer() === $this->getUserId()) {
174
				return;
175
			}
176
177
			$groupAccess = $this->getOption('group_access') ?? [];
178
			foreach ($groupAccess as $group) {
179
				if ($this->groupManager->groupExists($group)) {
180
					if ($this->groupManager->isInGroup($this->getViewer(), $group)) {
181
						return;
182
					}
183
				}
184
			}
185
186
			/** @var OCFolder $viewerOCFolder */
187
			$viewerOCFolder = \OC::$server->getUserFolder($this->getViewer());
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getUserFolder() has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

187
			$viewerOCFolder = /** @scrutinizer ignore-deprecated */ \OC::$server->getUserFolder($this->getViewer());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
188
			$viewerAccessClosure = function (OCNode $node) use ($viewerOCFolder) {
189
				$nodeId = $node->getId();
190
191
				$viewerNodes = $viewerOCFolder->getById($nodeId);
192
				foreach ($viewerNodes as $viewerNode) {
193
					if ($viewerNode->isReadable()) {
194
						return true;
195
					}
196
				}
197
198
				return false;
199
			};
200
201
			$websiteFolder = $this->getWebsiteFolder();
202
203
			$path = $this->miscService->normalizePath($path);
204
			while ($path && ($path !== '.')) {
205
				try {
206
					/** @var StorageFile|StorageFolder $file */
207
					$file = $websiteFolder->get($path);
208
				} catch (NotFoundException $e) {
209
					$file = null;
210
				}
211
212
				if ($file) {
213
					if ($viewerAccessClosure($file->getOCNode())) {
214
						return;
215
					}
216
217
					throw new $exceptionClass();
218
				}
219
220
				$path = dirname($path);
221
			}
222
223
			if ($viewerAccessClosure($websiteFolder->getOCNode())) {
224
				return;
225
			}
226
		}
227
228
		throw new $exceptionClass();
229
	}
230
231
	/**
232
	 * @throws WebsiteInvalidOwnerException
233
	 */
234 3
	public function assertValidOwner(): void
235
	{
236 3
		$user = $this->userManager->get($this->getUserId());
237 3
		if ($user === null) {
238
			throw new WebsiteInvalidOwnerException();
239
		}
240 3
		if (!$user->isEnabled()) {
241
			throw new WebsiteInvalidOwnerException();
242
		}
243 3
		if (!$this->websitesService->isUserAllowed($this->getUserId())) {
244
			throw new WebsiteInvalidOwnerException();
245
		}
246 3
	}
247
248
	/**
249
	 * @throws WebsiteInvalidDataException
250
	 */
251 3
	public function assertValidName(): void
252
	{
253 3
		if (strlen($this->getName()) < self::NAME_LENGTH_MIN) {
254
			throw new WebsiteInvalidDataException('name', $this->l10n->t('The name of the website must be longer.'));
255
		}
256 3
		if (strlen($this->getName()) > self::NAME_LENGTH_MAX) {
257
			throw new WebsiteInvalidDataException('name', $this->l10n->t('The name of the website is too long.'));
258
		}
259 3
	}
260
261
	/**
262
	 * @throws WebsiteInvalidDataException
263
	 */
264 3
	public function assertValidSite(): void
265
	{
266 3
		if (strlen($this->getSite()) < self::SITE_LENGTH_MIN) {
267
			$error = $this->l10n->t('The identifier of the website must be longer.');
268
			throw new WebsiteInvalidDataException('site', $error);
269
		}
270 3
		if (strlen($this->getSite()) > self::SITE_LENGTH_MAX) {
271
			$error = $this->l10n->t('The identifier of the website is too long.');
272
			throw new WebsiteInvalidDataException('site', $error);
273
		}
274 3
		if (preg_match('/' . self::SITE_REGEX . '/', $this->getSite()) !== 1) {
275
			$error = $this->l10n->t('The identifier of the website can only contain lowercase alpha numeric chars.');
276
			throw new WebsiteInvalidDataException('site', $error);
277
		}
278 3
	}
279
280
	/**
281
	 * @throws WebsiteInvalidDataException
282
	 */
283 3
	public function assertValidPath(): void
284
	{
285
		try {
286 3
			$path = $this->miscService->normalizePath($this->getPath());
287 3
			if ($path === '') {
288 3
				throw new InvalidPathException();
289
			}
290
		} catch (InvalidPathException $e) {
291
			throw new WebsiteInvalidDataException(
292
				'path',
293
				$this->l10n->t('The path of the website is invalid.')
294
			);
295
		}
296
297 3
		$userFolder = new StorageFolder(\OC::$server->getUserFolder($this->getUserId()));
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getUserFolder() has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

297
		$userFolder = new StorageFolder(/** @scrutinizer ignore-deprecated */ \OC::$server->getUserFolder($this->getUserId()));

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
298
299
		try {
300 3
			$websiteBaseFolder = $userFolder->getFolder(dirname($path));
301
302
			try {
303 3
				$websiteFolder = $websiteBaseFolder->getFolder(basename($path));
304
305
				if (!$websiteFolder->isLocal()) {
306
					throw new WebsiteInvalidDataException(
307
						'path',
308
						$this->l10n->t('The website\'s path is stored on a non-local storage.')
309
					);
310
				}
311 3
			} catch (NotFoundException $e) {
312 3
				if (!$websiteBaseFolder->isLocal()) {
313
					throw new WebsiteInvalidDataException(
314
						'path',
315 3
						$this->l10n->t('The website\'s path is stored on a non-local storage.')
316
					);
317
				}
318
			}
319
		} catch (InvalidPathException | NotFoundException $e) {
320
			throw new WebsiteInvalidDataException(
321
				'path',
322
				$this->l10n->t('Parent folder of the website\'s path not found.')
323
			);
324
		}
325 3
	}
326
327
	/**
328
	 * @throws ThemeNotFoundException
329
	 * @throws ThemeNotCompatibleException
330
	 */
331 3
	public function assertValidTheme(): void
332
	{
333 3
		$this->themesService->assertValidTheme($this->getTheme());
334 3
	}
335
336
	/**
337
	 * @throws TemplateNotFoundException
338
	 * @throws TemplateNotCompatibleException
339
	 */
340 3
	public function assertValidTemplate(): void
341
	{
342 3
		$this->templatesService->assertValidTemplate($this->getTemplateSource());
343 3
	}
344
345
	/**
346
	 * @param string $userId
347
	 *
348
	 * @throws WebsiteForeignOwnerException
349
	 */
350
	public function assertOwnedBy(string $userId): void
351
	{
352
		if ($this->getUserId() !== $userId) {
353
			throw new WebsiteForeignOwnerException();
354
		}
355
	}
356
357
	/**
358
	 * @return StorageFolder
359
	 * @throws WebsiteInvalidFilesystemException
360
	 */
361
	public function getWebsiteFolder(): StorageFolder
362
	{
363
		if ($this->folder !== null) {
364
			try {
365
				// NC doesn't guarantee that mounts are present for the whole request lifetime
366
				// for example, if you call \OC\Files\Utils\Scanner::scan(), all mounts are reset
367
				// this makes OCNode instances, which rely on mounts of different users than the current, unusable
368
				// by calling OCFolder::get('') we can detect this situation and re-init the required mounts
369
				$this->folder->get('');
370
			} catch (\Exception $e) {
371
				$this->folder = null;
372
			}
373
		}
374
375
		if ($this->folder === null) {
376
			try {
377
				$ocUserFolder = \OC::$server->getUserFolder($this->getUserId());
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getUserFolder() has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

377
				$ocUserFolder = /** @scrutinizer ignore-deprecated */ \OC::$server->getUserFolder($this->getUserId());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
378
				$userFolder = new StorageFolder($ocUserFolder);
379
380
				$websiteFolder = $userFolder->getFolder($this->getPath());
381
				$this->folder = $websiteFolder->fakeRoot();
382
			} catch (InvalidPathException | NotFoundException $e) {
383
				throw new WebsiteInvalidFilesystemException($e);
384
			}
385
		}
386
387
		return $this->folder;
388
	}
389
390
	/**
391
	 * @return string
392
	 * @throws WebsiteInvalidFilesystemException
393
	 */
394
	public function getWebsitePath(): string
395
	{
396
		try {
397
			return $this->getWebsiteFolder()->getLocalPath() . '/';
398
		} catch (InvalidPathException | NotFoundException $e) {
399
			throw new WebsiteInvalidFilesystemException($e);
400
		}
401
	}
402
403
	/**
404
	 * @return string
405
	 */
406
	public function getWebsiteUrl(): string
407
	{
408
		if (!$this->getProxyRequest()) {
409
			$route = Application::APP_NAME . '.Pico.getPage';
410
			$parameters = [ 'site' => $this->getSite(), 'page' => '' ];
411
			return $this->urlGenerator->linkToRoute($route, $parameters) . '/';
412
		} else {
413
			return \OC::$WEBROOT . '/sites/' . urlencode($this->getSite()) . '/';
414
		}
415
	}
416
}
417