Skip to content

Commit

Permalink
Merge pull request #4310 from nextcloud/backport/4306/stable30
Browse files Browse the repository at this point in the history
[stable30] fix: do not treat guest as share owner
  • Loading branch information
elzody authored Dec 6, 2024
2 parents 57ab5f5 + 565f825 commit d6e2585
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 37 deletions.
19 changes: 15 additions & 4 deletions lib/Controller/DocumentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ public function createFromTemplate(int $templateId, string $fileName, string $di

$template = $this->templateManager->get($templateId);
$urlSrc = $this->tokenManager->getUrlSrc($file);
$wopi = $this->tokenManager->generateWopiTokenForTemplate($template, $this->userId, $file->getId());
$isGuest = $this->userId === null;
$wopi = $this->tokenManager->generateWopiTokenForTemplate($template, $file->getId(), $this->userId, $isGuest);

$params = [
'permissions' => $template->getPermissions(),
Expand Down Expand Up @@ -394,7 +395,8 @@ public function token(int $fileId, ?string $shareToken = null, ?string $path = n
]);
}

$wopi = $this->getToken($file, $share);
$isGuest = $guestName || !$this->userId;
$wopi = $this->getToken($file, $share, null, $isGuest);

$this->tokenManager->setGuestName($wopi, $guestName);

Expand Down Expand Up @@ -477,11 +479,20 @@ private function getFileForShare(IShare $share, ?int $fileId, ?string $path = nu
throw new NotFoundException();
}

private function getToken(File $file, ?IShare $share = null, ?int $version = null): Wopi {
private function getToken(File $file, ?IShare $share = null, ?int $version = null, bool $isGuest = false): Wopi {
// Pass through $version
$templateFile = $this->templateManager->getTemplateSource($file->getId());
if ($templateFile) {
return $this->tokenManager->generateWopiTokenForTemplate($templateFile, $share?->getShareOwner() ?? $this->userId, $file->getId());
$owneruid = $share?->getShareOwner() ?? $file->getOwner()->getUID();

return $this->tokenManager->generateWopiTokenForTemplate(
$templateFile,
$file->getId(),
$owneruid,
$isGuest,
false,
$share?->getPermissions()
);
}

return $this->tokenManager->generateWopiToken($this->getWopiFileId($file->getId(), $version), $share?->getToken(), $this->userId);
Expand Down
40 changes: 15 additions & 25 deletions lib/Controller/WopiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,8 @@ public function postFile(string $fileId, string $access_token): JSONResponse {

// Unless the editor is empty (public link) we modify the files as the current editor
$editor = $wopi->getEditorUid();
if ($editor === null && !$wopi->isRemoteToken()) {
$isPublic = $editor === null && !$wopi->isRemoteToken();
if ($isPublic) {
$editor = $wopi->getOwnerUid();
}

Expand All @@ -573,16 +574,10 @@ public function postFile(string $fileId, string $access_token): JSONResponse {
$file = $this->getFileForWopiToken($wopi);

$suggested = $this->request->getHeader('X-WOPI-RequestedName');

$suggested = mb_convert_encoding($suggested, 'utf-8', 'utf-7') . '.' . $file->getExtension();

if (strpos($suggested, '.') === 0) {
$path = dirname($file->getPath()) . '/New File' . $suggested;
} elseif (strpos($suggested, '/') !== 0) {
$path = dirname($file->getPath()) . '/' . $suggested;
} else {
$path = $userFolder->getPath() . $suggested;
}
$parent = $isPublic ? dirname($file->getPath()) : $userFolder->getPath();
$path = $this->normalizePath($suggested, $parent);

if ($path === '') {
return new JSONResponse([
Expand Down Expand Up @@ -611,20 +606,8 @@ public function postFile(string $fileId, string $access_token): JSONResponse {
$suggested = $this->request->getHeader('X-WOPI-SuggestedTarget');
$suggested = mb_convert_encoding($suggested, 'utf-8', 'utf-7');

if ($suggested[0] === '.') {
$path = dirname($file->getPath()) . '/New File' . $suggested;
} elseif ($suggested[0] !== '/') {
$path = dirname($file->getPath()) . '/' . $suggested;
} else {
$path = $userFolder->getPath() . $suggested;
}

if ($path === '') {
return new JSONResponse([
'status' => 'error',
'message' => 'Cannot create the file'
]);
}
$parent = $isPublic ? dirname($file->getPath()) : $userFolder->getPath();
$path = $this->normalizePath($suggested, $parent);

// create the folder first
if (!$this->rootFolder->nodeExists(dirname($path))) {
Expand All @@ -638,8 +621,8 @@ public function postFile(string $fileId, string $access_token): JSONResponse {

$content = fopen('php://input', 'rb');
// Set the user to register the change under his name
$this->userScopeService->setUserScope($wopi->getEditorUid());
$this->userScopeService->setFilesystemScope($wopi->getEditorUid());
$this->userScopeService->setUserScope($editor);
$this->userScopeService->setFilesystemScope($editor);

try {
$this->wrappedFilesystemOperation($wopi, function () use ($file, $content) {
Expand Down Expand Up @@ -668,6 +651,13 @@ public function postFile(string $fileId, string $access_token): JSONResponse {
}
}

private function normalizePath(string $path, ?string $parent = null): string {
$path = str_starts_with($path, '/') ? $path : '/' . $path;
$parent = is_null($parent) ? '' : rtrim($parent, '/');

return $parent . $path;
}

private function lock(Wopi $wopi, string $lock): JSONResponse {
try {
$lock = $this->lockManager->lock(new LockContext(
Expand Down
39 changes: 31 additions & 8 deletions lib/TokenManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use OCP\Share\IManager;
use OCP\Share\IShare;
use OCP\Util;
use Psr\Log\LoggerInterface;

class TokenManager {
public function __construct(
Expand All @@ -39,6 +40,7 @@ public function __construct(
private Helper $helper,
private PermissionManager $permissionManager,
private IEventDispatcher $eventDispatcher,
private LoggerInterface $logger,
) {
}

Expand Down Expand Up @@ -105,7 +107,7 @@ public function generateWopiToken(string $fileId, ?string $shareToken = null, ?s
// no active user login while generating the token
// this is required during WopiPutRelativeFile
if (is_null($editoruid)) {
\OC::$server->getLogger()->warning('Generating token for SaveAs without editoruid');
$this->logger->warning('Generating token for SaveAs without editoruid');
$updatable = true;
} else {
// Make sure we use the user folder if available since fetching all files by id from the root might be expensive
Expand Down Expand Up @@ -188,10 +190,17 @@ public function upgradeFromDirectInitiator(Direct $direct, Wopi $wopi) {
return $wopi;
}

public function generateWopiTokenForTemplate(File $templateFile, ?string $userId, int $targetFileId, bool $direct = false): Wopi {
$owneruid = $userId;
$editoruid = $userId;
$rootFolder = $this->rootFolder->getUserFolder($editoruid);
public function generateWopiTokenForTemplate(
File $templateFile,
int $targetFileId,
string $owneruid,
bool $isGuest,
bool $direct = false,
?int $sharePermissions = null,
): Wopi {
$editoruid = $isGuest ? null : $owneruid;

$rootFolder = $this->rootFolder->getUserFolder($owneruid);
$targetFile = $rootFolder->getFirstNodeById($targetFileId);
if (!$targetFile instanceof File) {
throw new NotFoundException();
Expand All @@ -202,12 +211,26 @@ public function generateWopiTokenForTemplate(File $templateFile, ?string $userId
throw new NotPermittedException();
}

$updatable = $targetFile->isUpdateable() && $this->permissionManager->userCanEdit($editoruid);
$updatable = $targetFile->isUpdateable();
if (!is_null($sharePermissions)) {
$shareUpdatable = (bool)($sharePermissions & \OCP\Constants::PERMISSION_UPDATE);
$updatable = $updatable && $shareUpdatable;
}

$serverHost = $this->urlGenerator->getAbsoluteURL('/');

return $this->wopiMapper->generateFileToken($targetFile->getId(), $owneruid, $editoruid, 0, $updatable, $serverHost, null,
false, $direct, $templateFile->getId());
return $this->wopiMapper->generateFileToken(
$targetFile->getId(),
$owneruid,
$editoruid,
0,
$updatable,
$serverHost,
$isGuest ? '' : null,
false,
$direct,
$templateFile->getId()
);
}

public function newInitiatorToken($sourceServer, ?Node $node = null, $shareToken = null, bool $direct = false, $userId = null): Wopi {
Expand Down
30 changes: 30 additions & 0 deletions tests/features/bootstrap/WopiContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@
use GuzzleHttp\Psr7\Utils;
use JuliusHaertl\NextcloudBehat\Context\FilesContext;
use JuliusHaertl\NextcloudBehat\Context\ServerContext;
use JuliusHaertl\NextcloudBehat\Context\SharingContext;
use PHPUnit\Framework\Assert;

class WopiContext implements Context {
/** @var ServerContext */
private $serverContext;
/** @var FilesContext */
private $filesContext;
/** @var SharingContext */
private $sharingContext;

private $downloadedFile;
private $response;
Expand All @@ -38,6 +41,7 @@ public function gatherContexts(BeforeScenarioScope $scope) {
$environment = $scope->getEnvironment();
$this->serverContext = $environment->getContext(ServerContext::class);
$this->filesContext = $environment->getContext(FilesContext::class);
$this->sharingContext = $environment->getContext(SharingContext::class);
}

public function getWopiEndpointBaseUrl() {
Expand Down Expand Up @@ -87,6 +91,32 @@ public function collaboraPuts($source) {
}
}

/**
* @Then /^Create new document as guest with file name "([^"]*)"$/
*/
public function createDocumentAsGuest(string $name) {
$client = new Client();
$options = [
'body' => json_encode([
'directoryPath' => '/',
'fileName' => $name,
'mimeType' => 'application/vnd.oasis.opendocument.text',
'shareToken' => $this->sharingContext->getLastShareData()['token'],
'templateId' => 0,
]),
'headers' => [
'Content-Type' => 'application/json',
'OCS-ApiRequest' => 'true'
],
];

try {
$this->response = $client->post($this->getWopiEndpointBaseUrl() . 'ocs/v2.php/apps/richdocuments/api/v1/file', $options);
} catch (\GuzzleHttp\Exception\ClientException $e) {
$this->response = $e->getResponse();
}
}

/**
* @Then /^the WOPI HTTP status code should be "([^"]*)"$/
* @param int $statusCode
Expand Down
16 changes: 16 additions & 0 deletions tests/features/wopi.feature
Original file line number Diff line number Diff line change
Expand Up @@ -342,3 +342,19 @@ Feature: WOPI
| UserFriendlyName | user2-displayname |
And Collabora downloads the file
And Collabora downloads the file and it is equal to "./../emptyTemplates/template.ods"

Scenario: Save as guest user to owner root
Given as user "user1"
And User "user1" creates a folder "SharedFolder"
And as "user1" create a share with
| path | /SharedFolder |
| shareType | 3 |
And Updating last share with
| permissions | 31 |
And Create new document as guest with file name "some-guest-document.odt"
And as "user1" the file "/SharedFolder/some-guest-document.odt" exists
And a guest opens the file "some-guest-document.odt" of the shared link
And Collabora fetches checkFileInfo
And Collabora saves the content of "./../emptyTemplates/template.ods" as "/saved-as-guest-document.odt"
And as "user1" the file "/SharedFolder/saved-as-guest-document.odt" exists
And as "user1" the file "/saved-as-guest-document.odt" does not exist

0 comments on commit d6e2585

Please sign in to comment.