feat: add oc-ownerid and oc-permissions headers on PUT DAV requests
Signed-off-by: Salvatore Martire <4652631+salmart-dev@users.noreply.github.com>pull/54542/head
parent
343e8236a1
commit
0b577efcef
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCA\DAV\Connector\Sabre;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
use Sabre\DAV\Server;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Adds the "OC-OwnerId" and "OC-Permissions" after PUT requests so that
|
||||
* clients don't need to do a propfind after uploading a file to decide what
|
||||
* to display.
|
||||
*/
|
||||
class AddExtraHeadersPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
|
||||
private ?Server $server = null;
|
||||
|
||||
public function __construct(
|
||||
private LoggerInterface $logger,
|
||||
private bool $isPublic = false,
|
||||
) {
|
||||
}
|
||||
|
||||
public function initialize(Server $server): void {
|
||||
$this->server = $server;
|
||||
|
||||
$server->on('afterMethod:PUT', $this->afterPut(...));
|
||||
}
|
||||
|
||||
private function afterPut(RequestInterface $request, ResponseInterface $response): void {
|
||||
if ($this->server === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$node = null;
|
||||
try {
|
||||
$node = $this->server->tree->getNodeForPath($request->getPath());
|
||||
} catch (NotFound) {
|
||||
$this->logger->error("Cannot set extra headers for non-existing file '{$request->getPath()}'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$node instanceof Node) {
|
||||
$nodeType = get_debug_type($node);
|
||||
$this->logger->error("Cannot set extra headers for node of type {$nodeType} for file '{$request->getPath()}'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->isPublic) {
|
||||
$ownerId = $node->getOwner()?->getUID();
|
||||
if ($ownerId !== null) {
|
||||
$response->setHeader('X-NC-OwnerId', $ownerId);
|
||||
}
|
||||
}
|
||||
|
||||
$permissions = $this->isPublic ? $node->getPublicDavPermissions()
|
||||
: $node->getDavPermissions();
|
||||
|
||||
$response->setHeader('X-NC-Permissions', $permissions);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace unit\Connector\Sabre;
|
||||
|
||||
use LogicException;
|
||||
use OCA\DAV\Connector\Sabre\AddExtraHeadersPlugin;
|
||||
use OCA\DAV\Connector\Sabre\Node;
|
||||
use OCA\DAV\Connector\Sabre\Server;
|
||||
use OCP\IUser;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
use Sabre\DAV\Tree;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
use Test\TestCase;
|
||||
|
||||
class AddExtraHeadersPluginTest extends TestCase {
|
||||
|
||||
private AddExtraHeadersPlugin $plugin;
|
||||
private Server&MockObject $server;
|
||||
private LoggerInterface&MockObject $logger;
|
||||
private RequestInterface&MockObject $request;
|
||||
private ResponseInterface&MockObject $response;
|
||||
private Tree&MockObject $tree;
|
||||
|
||||
public static function afterPutData(): array {
|
||||
return [
|
||||
'owner and permissions present' => [
|
||||
'user', true, 'PERMISSIONS', true, 2
|
||||
],
|
||||
'permissions only' => [
|
||||
null, false, 'PERMISSIONS', true, 1
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function testAfterPutNotFoundException(): void {
|
||||
$afterPut = null;
|
||||
$this->server->expects($this->once())
|
||||
->method('on')
|
||||
->willReturnCallback(
|
||||
function ($method, $callback) use (&$afterPut) {
|
||||
$this->assertSame('afterMethod:PUT', $method);
|
||||
$afterPut = $callback;
|
||||
});
|
||||
|
||||
$this->plugin->initialize($this->server);
|
||||
$node = $this->createMock(Node::class);
|
||||
$this->tree->expects($this->once())->method('getNodeForPath')
|
||||
->willThrowException(new NotFound());
|
||||
|
||||
$this->logger->expects($this->once())->method('error');
|
||||
|
||||
$afterPut($this->request, $this->response);
|
||||
}
|
||||
|
||||
#[DataProvider('afterPutData')]
|
||||
public function testAfterPut(?string $ownerId, bool $expectOwnerIdHeader,
|
||||
?string $permissions, bool $expectPermissionsHeader,
|
||||
int $expectedInvocations): void {
|
||||
$afterPut = null;
|
||||
$this->server->expects($this->once())
|
||||
->method('on')
|
||||
->willReturnCallback(
|
||||
function ($method, $callback) use (&$afterPut) {
|
||||
$this->assertSame('afterMethod:PUT', $method);
|
||||
$afterPut = $callback;
|
||||
});
|
||||
|
||||
$this->plugin->initialize($this->server);
|
||||
$node = $this->createMock(Node::class);
|
||||
$this->tree->expects($this->once())->method('getNodeForPath')
|
||||
->willReturn($node);
|
||||
|
||||
$user = $this->createMock(IUser::class);
|
||||
$node->expects($this->once())->method('getOwner')->willReturn($user);
|
||||
$user->expects($this->once())->method('getUID')->willReturn($ownerId);
|
||||
$node->expects($this->once())->method('getDavPermissions')->willReturn($permissions);
|
||||
|
||||
$matcher = $this->exactly($expectedInvocations);
|
||||
$this->response->expects($matcher)->method('setHeader')
|
||||
->willReturnCallback(function ($name, $value) use (
|
||||
$expectedInvocations,
|
||||
$expectPermissionsHeader,
|
||||
$expectOwnerIdHeader,
|
||||
$matcher,
|
||||
$ownerId, $permissions) {
|
||||
$invocationNumber = $matcher->numberOfInvocations();
|
||||
if ($invocationNumber === 0) {
|
||||
throw new LogicException('No invocations were expected');
|
||||
}
|
||||
|
||||
if (($expectOwnerIdHeader && $expectedInvocations === 1)
|
||||
|| ($expectedInvocations
|
||||
=== 2 && $invocationNumber === 1)) {
|
||||
$this->assertEquals('X-NC-OwnerId', $name);
|
||||
$this->assertEquals($ownerId, $value);
|
||||
}
|
||||
|
||||
if (($expectPermissionsHeader && $expectedInvocations === 1)
|
||||
|| ($expectedInvocations
|
||||
=== 2 && $invocationNumber === 2)) {
|
||||
$this->assertEquals('X-NC-Permissions', $name);
|
||||
$this->assertEquals($permissions, $value);
|
||||
}
|
||||
});
|
||||
|
||||
$afterPut($this->request, $this->response);
|
||||
}
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->server = $this->createMock(Server::class);
|
||||
$this->tree = $this->createMock(Tree::class);
|
||||
$this->server->tree = $this->tree;
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$this->plugin = new AddExtraHeadersPlugin($this->logger, false);
|
||||
$this->request = $this->createMock(RequestInterface::class);
|
||||
$this->response = $this->createMock(ResponseInterface::class);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue