You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
379 lines
14 KiB
379 lines
14 KiB
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* Copyright (C) Ascensio System SIA, 2009-2026
|
|
*
|
|
* This program is a free software product. You can redistribute it and/or
|
|
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
|
* version 3 as published by the Free Software Foundation, together with the
|
|
* additional terms provided in the LICENSE file.
|
|
*
|
|
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
|
* details, see the GNU AGPL at: https://www.gnu.org/licenses/agpl-3.0.html
|
|
*
|
|
* You can contact Ascensio System SIA by email at info@onlyoffice.com
|
|
* or by postal mail at 20A-6 Ernesta Birznieka-Upisha Street, Riga,
|
|
* LV-1050, Latvia, European Union.
|
|
*
|
|
* The interactive user interfaces in modified versions of the Program
|
|
* are required to display Appropriate Legal Notices in accordance with
|
|
* Section 5 of the GNU AGPL version 3.
|
|
*
|
|
* No trademark rights are granted under this License.
|
|
*
|
|
* All non-code elements of the Product, including illustrations,
|
|
* icon sets, and technical writing content, are licensed under the
|
|
* Creative Commons Attribution-ShareAlike 4.0 International License:
|
|
* https://creativecommons.org/licenses/by-sa/4.0/legalcode
|
|
*
|
|
* This license applies only to such non-code elements and does not
|
|
* modify or replace the licensing terms applicable to the Program's
|
|
* source code, which remains licensed under the GNU Affero General
|
|
* Public License v3.
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
namespace OCA\Onlyoffice\Tests\PHP;
|
|
|
|
use OCA\Files_Versions\Versions\IVersion;
|
|
use OCA\Onlyoffice\AppConfig;
|
|
use OCA\Onlyoffice\FileUtility;
|
|
use OCA\Onlyoffice\KeyManager;
|
|
use OCP\Constants;
|
|
use OCP\Files\File;
|
|
use OCP\Files\Folder;
|
|
use OCP\Files\NotFoundException;
|
|
use OCP\IL10N;
|
|
use OCP\ISession;
|
|
use OCP\Share\Exceptions\ShareNotFound;
|
|
use OCP\Share\IManager;
|
|
use OCP\Share\IShare;
|
|
use OCP\Share\IAttributes;
|
|
use PHPUnit\Framework\Attributes\CoversClass;
|
|
use PHPUnit\Framework\MockObject\Stub;
|
|
use Psr\Log\LoggerInterface;
|
|
use Test\TestCase;
|
|
|
|
#[CoversClass(FileUtility::class)]
|
|
class FileUtilityTest extends TestCase {
|
|
|
|
private IL10N&Stub $trans;
|
|
private IManager&Stub $shareManager;
|
|
private ISession&Stub $session;
|
|
private KeyManager&Stub $keyManager;
|
|
private AppConfig&Stub $appConfig;
|
|
private FileUtility $fileUtility;
|
|
|
|
protected function setUp(): void {
|
|
parent::setUp();
|
|
|
|
$this->trans = $this->createStub(IL10N::class);
|
|
$this->trans->method("t")->willReturnArgument(0);
|
|
|
|
$this->shareManager = $this->createStub(IManager::class);
|
|
$this->session = $this->createStub(ISession::class);
|
|
$this->keyManager = $this->createStub(KeyManager::class);
|
|
$this->appConfig = $this->createStub(AppConfig::class);
|
|
|
|
$this->fileUtility = new FileUtility(
|
|
$this->trans,
|
|
$this->createStub(LoggerInterface::class),
|
|
$this->appConfig,
|
|
$this->shareManager,
|
|
$this->session,
|
|
$this->keyManager,
|
|
);
|
|
}
|
|
|
|
private function makeShare(
|
|
string $id = "share1",
|
|
int $permissions = Constants::PERMISSION_READ,
|
|
?string $password = null,
|
|
?object $node = null
|
|
): IShare&Stub {
|
|
$share = $this->createStub(IShare::class);
|
|
$share->method("getId")->willReturn($id);
|
|
$share->method("getPermissions")->willReturn($permissions);
|
|
$share->method("getPassword")->willReturn($password);
|
|
$share->method("getNode")->willReturn($node ?? $this->createStub(File::class));
|
|
return $share;
|
|
}
|
|
|
|
/**
|
|
* Returns an error when the token is empty.
|
|
*/
|
|
public function testGetShareReturnsErrorForEmptyToken(): void {
|
|
[$share, $error] = $this->fileUtility->getShare("");
|
|
|
|
$this->assertNull($share);
|
|
$this->assertNotNull($error);
|
|
}
|
|
|
|
/**
|
|
* Returns an error when no share is found for the token.
|
|
*/
|
|
public function testGetShareReturnsErrorWhenShareNotFound(): void {
|
|
$this->shareManager->method("getShareByToken")->willThrowException(new ShareNotFound());
|
|
|
|
[$share, $error] = $this->fileUtility->getShare("invalid-token");
|
|
|
|
$this->assertNull($share);
|
|
$this->assertNotNull($error);
|
|
}
|
|
|
|
/**
|
|
* Returns the share when a valid unprotected token is given.
|
|
*/
|
|
public function testGetShareReturnsShareForValidToken(): void {
|
|
$expected = $this->makeShare();
|
|
$this->shareManager->method("getShareByToken")->willReturn($expected);
|
|
$this->session->method("get")->willReturn(null);
|
|
|
|
[$share, $error] = $this->fileUtility->getShare("valid-token");
|
|
|
|
$this->assertSame($expected, $share);
|
|
$this->assertNull($error);
|
|
}
|
|
|
|
/**
|
|
* Returns an error when the share is password-protected and the user is not authenticated.
|
|
*/
|
|
public function testGetShareReturnsErrorForPasswordProtectedShareWhenNotAuthenticated(): void {
|
|
$this->shareManager->method("getShareByToken")->willReturn($this->makeShare("share1", Constants::PERMISSION_READ, "secret"));
|
|
$this->session->method("get")->willReturn(null);
|
|
|
|
[$share, $error] = $this->fileUtility->getShare("token");
|
|
|
|
$this->assertNull($share);
|
|
$this->assertNotNull($error);
|
|
}
|
|
|
|
/**
|
|
* Returns the share when the user is authenticated via the array session format.
|
|
*/
|
|
public function testGetShareReturnsShareWhenAuthenticatedViaArray(): void {
|
|
$this->shareManager->method("getShareByToken")->willReturn($this->makeShare("share1", Constants::PERMISSION_READ, "secret"));
|
|
$this->session->method("get")->willReturn(["share1"]);
|
|
|
|
[$share, $error] = $this->fileUtility->getShare("token");
|
|
|
|
$this->assertNotNull($share);
|
|
$this->assertNull($error);
|
|
}
|
|
|
|
/**
|
|
* Returns the share when the user is authenticated via the legacy string session format.
|
|
*/
|
|
public function testGetShareReturnsShareWhenAuthenticatedViaString(): void {
|
|
$this->shareManager->method("getShareByToken")->willReturn($this->makeShare("share1", Constants::PERMISSION_READ, "secret"));
|
|
$this->session->method("get")->willReturn("share1");
|
|
|
|
[$share, $error] = $this->fileUtility->getShare("token");
|
|
|
|
$this->assertNotNull($share);
|
|
$this->assertNull($error);
|
|
}
|
|
|
|
/**
|
|
* Returns an error when the share has no read permission.
|
|
*/
|
|
public function testGetNodeByTokenReturnsErrorForNoReadPermission(): void {
|
|
$this->shareManager->method("getShareByToken")->willReturn($this->makeShare("s1", 0));
|
|
$this->session->method("get")->willReturn(null);
|
|
|
|
[$node, $error] = $this->fileUtility->getNodeByToken("token");
|
|
|
|
$this->assertNull($node);
|
|
$this->assertNotNull($error);
|
|
}
|
|
|
|
/**
|
|
* Returns an error when the shared node is not found.
|
|
*/
|
|
public function testGetNodeByTokenReturnsErrorWhenNodeNotFound(): void {
|
|
$share = $this->makeShare("s1", Constants::PERMISSION_READ);
|
|
$share->method("getNode")->willThrowException(new NotFoundException());
|
|
$this->shareManager->method("getShareByToken")->willReturn($share);
|
|
$this->session->method("get")->willReturn(null);
|
|
|
|
[$node, $error] = $this->fileUtility->getNodeByToken("token");
|
|
|
|
$this->assertNull($node);
|
|
$this->assertNotNull($error);
|
|
}
|
|
|
|
/**
|
|
* Returns the node and share when the token is valid and has read permission.
|
|
*/
|
|
public function testGetNodeByTokenReturnsNodeForValidShare(): void {
|
|
$file = $this->createStub(File::class);
|
|
$share = $this->makeShare("s1", Constants::PERMISSION_READ, null, $file);
|
|
$this->shareManager->method("getShareByToken")->willReturn($share);
|
|
$this->session->method("get")->willReturn(null);
|
|
|
|
[$node, $error, $returnedShare] = $this->fileUtility->getNodeByToken("token");
|
|
|
|
$this->assertSame($file, $node);
|
|
$this->assertNull($error);
|
|
$this->assertSame($share, $returnedShare);
|
|
}
|
|
|
|
/**
|
|
* Returns the file directly when the share node is a file.
|
|
*/
|
|
public function testGetFileByTokenReturnsFileNodeDirectly(): void {
|
|
$file = $this->createStub(File::class);
|
|
$share = $this->makeShare("s1", Constants::PERMISSION_READ, null, $file);
|
|
$this->shareManager->method("getShareByToken")->willReturn($share);
|
|
$this->session->method("get")->willReturn(null);
|
|
|
|
[$result, $error] = $this->fileUtility->getFileByToken(null, "token");
|
|
|
|
$this->assertSame($file, $result);
|
|
$this->assertNull($error);
|
|
}
|
|
|
|
/**
|
|
* Returns the file from a folder by file ID when the share node is a folder.
|
|
*/
|
|
public function testGetFileByTokenReturnsFolderChildById(): void {
|
|
$file = $this->createStub(File::class);
|
|
$folder = $this->createStub(Folder::class);
|
|
$folder->method("getById")->willReturn([$file]);
|
|
|
|
$share = $this->makeShare("s1", Constants::PERMISSION_READ, null, $folder);
|
|
$this->shareManager->method("getShareByToken")->willReturn($share);
|
|
$this->session->method("get")->willReturn(null);
|
|
|
|
[$result, $error] = $this->fileUtility->getFileByToken(42, "token");
|
|
|
|
$this->assertSame($file, $result);
|
|
$this->assertNull($error);
|
|
}
|
|
|
|
/**
|
|
* Returns the file from a folder by path when no file ID is given.
|
|
*/
|
|
public function testGetFileByTokenReturnsFolderChildByPath(): void {
|
|
$file = $this->createStub(File::class);
|
|
$folder = $this->createStub(Folder::class);
|
|
$folder->method("get")->willReturn($file);
|
|
|
|
$share = $this->makeShare("s1", Constants::PERMISSION_READ, null, $folder);
|
|
$this->shareManager->method("getShareByToken")->willReturn($share);
|
|
$this->session->method("get")->willReturn(null);
|
|
|
|
[$result, $error] = $this->fileUtility->getFileByToken(null, "token", "subdir/file.docx");
|
|
|
|
$this->assertSame($file, $result);
|
|
$this->assertNull($error);
|
|
}
|
|
|
|
/**
|
|
* Generates a new key, stores it and returns it when none exists.
|
|
*/
|
|
public function testGetKeyGeneratesAndStoresKeyWhenNoneExists(): void {
|
|
$file = $this->createStub(File::class);
|
|
$file->method("getId")->willReturn(1);
|
|
|
|
$keyManager = $this->createMock(KeyManager::class);
|
|
$keyManager->method("get")->willReturn("");
|
|
$keyManager->expects($this->once())->method("set");
|
|
|
|
$this->appConfig->method("getSystemValue")->willReturn("instance-id");
|
|
|
|
$fileUtility = new FileUtility(
|
|
$this->trans,
|
|
$this->createStub(LoggerInterface::class),
|
|
$this->appConfig,
|
|
$this->shareManager,
|
|
$this->session,
|
|
$keyManager,
|
|
);
|
|
|
|
$key = $fileUtility->getKey($file);
|
|
|
|
$this->assertStringStartsWith("instance-id_", $key);
|
|
}
|
|
|
|
/**
|
|
* Returns the existing stored key without generating a new one.
|
|
*/
|
|
public function testGetKeyReturnsExistingKeyWithoutGenerating(): void {
|
|
$file = $this->createStub(File::class);
|
|
$file->method("getId")->willReturn(1);
|
|
|
|
$keyManager = $this->createMock(KeyManager::class);
|
|
$keyManager->method("get")->willReturn("existing-key");
|
|
$keyManager->expects($this->never())->method("set");
|
|
|
|
$fileUtility = new FileUtility(
|
|
$this->trans,
|
|
$this->createStub(LoggerInterface::class),
|
|
$this->appConfig,
|
|
$this->shareManager,
|
|
$this->session,
|
|
$keyManager,
|
|
);
|
|
|
|
$this->assertSame("existing-key", $fileUtility->getKey($file));
|
|
}
|
|
|
|
/**
|
|
* Combines instance ID, file etag and revision ID into the version key.
|
|
*/
|
|
public function testGetVersionKeyCombinesInstanceIdEtagAndRevisionId(): void {
|
|
$this->appConfig->method("getSystemValue")->willReturn("inst123");
|
|
|
|
$sourceFile = $this->createStub(File::class);
|
|
$sourceFile->method("getEtag")->willReturn("etag-abc");
|
|
|
|
$version = $this->createStub(IVersion::class);
|
|
$version->method("getSourceFile")->willReturn($sourceFile);
|
|
$version->method("getRevisionId")->willReturn("1234567890");
|
|
|
|
$key = $this->fileUtility->getVersionKey($version);
|
|
|
|
$this->assertSame("inst123_etag-abc_1234567890", $key);
|
|
}
|
|
|
|
/**
|
|
* Returns true when no download attribute is set on the share.
|
|
*/
|
|
public function testCanShareDownloadReturnsTrueWhenNoAttributeSet(): void {
|
|
$share = $this->createStub(IShare::class);
|
|
$share->method("getAttributes")->willReturn(null);
|
|
|
|
$this->assertTrue(FileUtility::canShareDownload($share));
|
|
}
|
|
|
|
/**
|
|
* Returns false when the download attribute is explicitly set to false.
|
|
*/
|
|
public function testCanShareDownloadReturnsFalseWhenAttributeIsFalse(): void {
|
|
$attributes = $this->createStub(IAttributes::class);
|
|
$attributes->method("getAttribute")->willReturn(false);
|
|
|
|
$share = $this->createStub(IShare::class);
|
|
$share->method("getAttributes")->willReturn($attributes);
|
|
|
|
$this->assertFalse(FileUtility::canShareDownload($share));
|
|
}
|
|
|
|
/**
|
|
* Returns true when the download attribute is explicitly set to true.
|
|
*/
|
|
public function testCanShareDownloadReturnsTrueWhenAttributeIsTrue(): void {
|
|
$attributes = $this->createStub(IAttributes::class);
|
|
$attributes->method("getAttribute")->willReturn(true);
|
|
|
|
$share = $this->createStub(IShare::class);
|
|
$share->method("getAttributes")->willReturn($attributes);
|
|
|
|
$this->assertTrue(FileUtility::canShareDownload($share));
|
|
}
|
|
}
|
|
|