Merge pull request #59778 from nextcloud/bug/noid/avoid-undefined-array-key-sharing-request
Avoid undefined array key sharing requestpull/59843/head
commit
e1049a86fc
@ -0,0 +1,46 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCA\DAV\DAV\Security; |
||||
|
||||
use OCA\DAV\Connector\Sabre\Exception\TooManyRequests; |
||||
use OCP\IAppConfig; |
||||
use OCP\IUserSession; |
||||
use OCP\Security\RateLimiting\ILimiter; |
||||
use OCP\Security\RateLimiting\IRateLimitExceededException; |
||||
|
||||
class RateLimiting { |
||||
|
||||
public function __construct( |
||||
private readonly IUserSession $userSession, |
||||
private readonly IAppConfig $config, |
||||
private readonly ILimiter $limiter, |
||||
) { |
||||
} |
||||
|
||||
/** |
||||
* @throws TooManyRequests |
||||
*/ |
||||
public function check(): void { |
||||
$user = $this->userSession->getUser(); |
||||
if ($user === null) { |
||||
return; |
||||
} |
||||
|
||||
$identifier = 'share-addressbook-or-calendar'; |
||||
$userLimit = $this->config->getValueInt('dav', 'rateLimitShareAddressbookOrCalendar', 20); |
||||
$userPeriod = $this->config->getValueInt('dav', 'rateLimitPeriodShareAddressbookOrCalendar', 3600); |
||||
|
||||
try { |
||||
$this->limiter->registerUserRequest($identifier, $userLimit, $userPeriod, $user); |
||||
} catch (IRateLimitExceededException $e) { |
||||
throw new TooManyRequests('Too many addressbook or calendar share requests', 0, $e); |
||||
} |
||||
} |
||||
} |
||||
@ -1,62 +0,0 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
/** |
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc. |
||||
* SPDX-License-Identifier: AGPL-3.0-only |
||||
*/ |
||||
namespace OCA\DAV\Tests\unit\CardDAV\Sharing; |
||||
|
||||
use OCA\DAV\Connector\Sabre\Auth; |
||||
use OCA\DAV\DAV\Sharing\IShareable; |
||||
use OCA\DAV\DAV\Sharing\Plugin; |
||||
use OCP\IConfig; |
||||
use OCP\IRequest; |
||||
use PHPUnit\Framework\MockObject\MockObject; |
||||
use Sabre\DAV\Server; |
||||
use Sabre\DAV\SimpleCollection; |
||||
use Sabre\HTTP\Request; |
||||
use Sabre\HTTP\Response; |
||||
use Test\TestCase; |
||||
|
||||
class PluginTest extends TestCase { |
||||
private Plugin $plugin; |
||||
private Server $server; |
||||
private IShareable&MockObject $book; |
||||
|
||||
protected function setUp(): void { |
||||
parent::setUp(); |
||||
|
||||
$authBackend = $this->createMock(Auth::class); |
||||
$authBackend->method('isDavAuthenticated') |
||||
->willReturn(true); |
||||
$request = $this->createMock(IRequest::class); |
||||
$config = $this->createMock(IConfig::class); |
||||
$this->plugin = new Plugin($authBackend, $request, $config); |
||||
|
||||
$root = new SimpleCollection('root'); |
||||
$this->server = new \Sabre\DAV\Server($root); |
||||
$this->book = $this->createMock(IShareable::class); |
||||
$this->book->method('getName') |
||||
->willReturn('addressbook1.vcf'); |
||||
$root->addChild($this->book); |
||||
$this->plugin->initialize($this->server); |
||||
} |
||||
|
||||
public function testSharing(): void { |
||||
$this->book->expects($this->once())->method('updateShares')->with([[ |
||||
'href' => 'principal:principals/admin', |
||||
'commonName' => null, |
||||
'summary' => null, |
||||
'readOnly' => false |
||||
]], ['mailto:wilfredo@example.com']); |
||||
|
||||
// setup request |
||||
$request = new Request('POST', 'addressbook1.vcf'); |
||||
$request->addHeader('Content-Type', 'application/xml'); |
||||
$request->setBody('<?xml version="1.0" encoding="utf-8" ?><CS:share xmlns:D="DAV:" xmlns:CS="http://owncloud.org/ns"><CS:set><D:href>principal:principals/admin</D:href><CS:read-write/></CS:set> <CS:remove><D:href>mailto:wilfredo@example.com</D:href></CS:remove></CS:share>');
|
||||
$response = new Response(); |
||||
$this->plugin->httpPost($request, $response); |
||||
} |
||||
} |
||||
@ -0,0 +1,99 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCA\DAV\Tests\unit\DAV\Security; |
||||
|
||||
use OCA\DAV\Connector\Sabre\Exception\TooManyRequests; |
||||
use OCA\DAV\DAV\Security\RateLimiting; |
||||
use OCP\IAppConfig; |
||||
use OCP\IUser; |
||||
use OCP\IUserSession; |
||||
use OCP\Security\RateLimiting\ILimiter; |
||||
use OCP\Security\RateLimiting\IRateLimitExceededException; |
||||
use PHPUnit\Framework\MockObject\MockObject; |
||||
use Test\TestCase; |
||||
|
||||
class RateLimitingTest extends TestCase { |
||||
private IUserSession $userSession; |
||||
private IAppConfig&MockObject $config; |
||||
private ILimiter&MockObject $limiter; |
||||
private RateLimiting $rateLimiting; |
||||
private string $userId = 'user123'; |
||||
|
||||
protected function setUp(): void { |
||||
parent::setUp(); |
||||
|
||||
$this->userSession = $this->createMock(IUserSession::class); |
||||
$this->config = $this->createMock(IAppConfig::class); |
||||
$this->limiter = $this->createMock(ILimiter::class); |
||||
|
||||
$this->rateLimiting = new RateLimiting( |
||||
$this->userSession, |
||||
$this->config, |
||||
$this->limiter, |
||||
); |
||||
} |
||||
|
||||
public function testNoUserObject(): void { |
||||
$this->userSession->expects($this->once()) |
||||
->method('getUser') |
||||
->willReturn(null); |
||||
$this->limiter->expects($this->never()) |
||||
->method('registerUserRequest'); |
||||
|
||||
$this->rateLimiting->check(); |
||||
} |
||||
|
||||
public function testRegisterShareRequest(): void { |
||||
$user = $this->createMock(IUser::class); |
||||
$this->userSession->expects($this->once()) |
||||
->method('getUser') |
||||
->willReturn($user); |
||||
$this->config->method('getValueInt') |
||||
->willReturnCallback(static function (string $app, string $key, int $default): int { |
||||
return match ($key) { |
||||
'rateLimitShareAddressbookOrCalendar' => 7, |
||||
'rateLimitPeriodShareAddressbookOrCalendar' => 600, |
||||
default => $default, |
||||
}; |
||||
}); |
||||
$this->limiter->expects($this->once()) |
||||
->method('registerUserRequest') |
||||
->with( |
||||
'share-addressbook-or-calendar', |
||||
7, |
||||
600, |
||||
$user, |
||||
); |
||||
|
||||
$this->rateLimiting->check(); |
||||
} |
||||
|
||||
public function testShareRequestRateLimitExceeded(): void { |
||||
$user = $this->createMock(IUser::class); |
||||
$this->userSession->expects($this->once()) |
||||
->method('getUser') |
||||
->willReturn($user); |
||||
$this->config->method('getValueInt') |
||||
->willReturnArgument(2); |
||||
$this->limiter->expects($this->once()) |
||||
->method('registerUserRequest') |
||||
->with( |
||||
'share-addressbook-or-calendar', |
||||
20, |
||||
3600, |
||||
$user, |
||||
) |
||||
->willThrowException($this->createMock(IRateLimitExceededException::class)); |
||||
|
||||
$this->expectException(TooManyRequests::class); |
||||
|
||||
$this->rateLimiting->check(); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue