Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/current/2898-local-download-warning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Fixed

- Add a warning when accessing local servers
5 changes: 3 additions & 2 deletions cookbook.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@
"**/vendor/**/{Tests,tests}/**",
"**/.history/**",
"**/vendor/**/vendor/**",
// "3rdparty/**"
".github/actions/**",
".hook-checkout/**"
".hook-checkout/**",
"${workspaceFolder:cookbook}/.github/actions/**",
"**/.github/actions/**"
],
"cSpell.words": [
"Nextcloud"
Expand Down
49 changes: 49 additions & 0 deletions lib/Controller/Implementation/RecipeImplementation.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace OCA\Cookbook\Controller\Implementation;

use Exception;
use OCA\Cookbook\Exception\InvalidDownloadURLException;
use OCA\Cookbook\Exception\InvalidJSONFileException;
use OCA\Cookbook\Exception\NoRecipeNameGivenException;
use OCA\Cookbook\Exception\RecipeExistsException;
Expand Down Expand Up @@ -56,6 +57,7 @@
AcceptHeaderParsingHelper $acceptHeaderParsingHelper,
IL10N $iL10N,
LoggerInterface $logger,
private string $userId,
) {
$this->request = $request;
$this->service = $recipeService;
Expand Down Expand Up @@ -119,7 +121,7 @@
*
* @param $id The id of the recipe in question
* @return JSONResponse
* @todo Parameter id is never used. Fix that

Check warning on line 124 in lib/Controller/Implementation/RecipeImplementation.php

View workflow job for this annotation

GitHub Actions / Check for added todo messages

Found @todo Parameter id is never used. Fix that
*/
public function update($id) {
$this->dbCacheService->triggerCheck();
Expand Down Expand Up @@ -268,6 +270,44 @@
}
}

/**
* @todo Move this code into a dedicated class and service

Check warning on line 274 in lib/Controller/Implementation/RecipeImplementation.php

View workflow job for this annotation

GitHub Actions / Check for added todo messages

Found @todo Move this code into a dedicated class and service
*/
private function isDownloadUrlValid(string $url): bool {
$urlValid = filter_var($url, FILTER_VALIDATE_URL);
if ($urlValid === false) {
$this->logger->info('Download URL is not a valid URL: {url}', ['url' => $url]);
return false;
}

$parsedUrl = parse_url($url);

$scheme = $parsedUrl['scheme'] ?? '';
$allowedSchemes = ['http', 'https'];
if (!in_array($scheme, $allowedSchemes, true)) {
$this->logger->info('Download URL uses invalid scheme: {scheme}', ['scheme' => $scheme]);
return false;
}

$host = $parsedUrl['host'] ?? '';
if (empty($host)) {
$this->logger->info('Download URL does not have a valid host: {url}', ['url' => $url]);
return false;
}

if ($host === 'localhost') {
$this->logger->info('Download URL host is localhost, which is not allowed.');
return false;
}

if (filter_var($host, FILTER_VALIDATE_IP)) {
$this->logger->info('Download URL host is an IP address, which is not allowed: {host}', ['host' => $host]);
return false;
}

return true;
}

/**
* Trigger the import of a recipe.
*
Expand All @@ -282,6 +322,15 @@
return new JSONResponse('Field "url" is required', 400);
}

if (! $this->isDownloadUrlValid($data['url'])) {
$this->logger->warning('Attempt to download recipe by user {user} from invalid URL: {url}', ['url' => $data['url'], 'user' => $this->userId]);
// throw new InvalidDownloadURLException($this->l->t('The provided URL is not allowed.'));
$json = [
'msg' => $this->l->t('The provided URL is not allowed.'),
];
return new JSONResponse($json['msg'], 400);
}

try {
$recipe_file = $this->service->downloadRecipe($data['url']);
$recipe_json = $this->service->parseRecipeFile($recipe_file);
Expand Down
9 changes: 9 additions & 0 deletions lib/Exception/InvalidDownloadURLException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace OCA\Cookbook\Exception;

class InvalidDownloadURLException extends \Exception {
public function __construct($message = '', $code = 0, $previous = null) {
parent::__construct($message, $code, $previous);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@
$this->stubFilter,
$this->acceptHeaderParser,
$l,
$logger
$logger,
'testuser',
);
}

Expand Down Expand Up @@ -335,7 +336,7 @@

/**
* @dataProvider dpSearch
* @todo no implementation in controller

Check warning on line 339 in tests/Unit/Controller/Implementation/RecipeImplementationTest.php

View workflow job for this annotation

GitHub Actions / Check for added todo messages

Found @todo no implementation in controller
* @param mixed $query
* @param mixed $recipes
*/
Expand Down Expand Up @@ -590,8 +591,8 @@

/**
* @dataProvider dataProviderImage
* @todo Assert on image data/file name

Check warning on line 594 in tests/Unit/Controller/Implementation/RecipeImplementationTest.php

View workflow job for this annotation

GitHub Actions / Check for added todo messages

Found @todo Assert on image data/file name
* @todo Avoid business code in controller

Check warning on line 595 in tests/Unit/Controller/Implementation/RecipeImplementationTest.php

View workflow job for this annotation

GitHub Actions / Check for added todo messages

Found @todo Avoid business code in controller
* @param mixed $setSize
* @param mixed $size
* @param mixed $sizeToQuery
Expand Down Expand Up @@ -672,7 +673,7 @@

/**
* @dataProvider dataProviderIndex
* @todo no work on controller

Check warning on line 676 in tests/Unit/Controller/Implementation/RecipeImplementationTest.php

View workflow job for this annotation

GitHub Actions / Check for added todo messages

Found @todo no work on controller
* @param mixed $recipes
* @param mixed $setKeywords
* @param mixed $keywords
Expand Down Expand Up @@ -753,4 +754,34 @@
],
];
}

public static function dpInvalidImportURLs(): array {
return [
'emptyUrl' => [''],
'nonHttp' => ['ftp://example.com/recipe.html'],
'localUrl' => ['http://localhost/recipe.html'],
'internalIp' => ['http://192.168.2.3/recipe.html'],
'localhost' => ['http://127.0.0.1/recipe.html'],
'localhostWithPort' => ['http://127.0.0.1:22/recipe.html'],
'remoteIp' => ['http://8.8.8.8/recipe.html'],
'localhost_ipv6' => ['http://::1/recipe.html'],
];
}

/**
* @dataProvider dpInvalidImportURLs
* @param mixed $url
*/
public function testInvalidImports($url) {
$this->ensureCacheCheckTriggered();

$this->restParser->method('getParameters')->willReturn([ 'url' => $url ]);

/**
* @var JSONResponse $ret
*/
$ret = $this->sut->import();

$this->assertEquals(400, $ret->getStatus());
}
}
Loading