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
7 changes: 6 additions & 1 deletion apps/files_sharing/appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,15 @@
'verb' => 'DELETE',
],
[
'name' => 'Accept#accept',
'name' => 'Accept#showAccept',
'url' => '/accept/{shareId}',
'verb' => 'GET',
],
[
'name' => 'Accept#accept',
'url' => '/accept/{shareId}',
'verb' => 'POST',
],
],
'ocs' => [
/*
Expand Down
22 changes: 22 additions & 0 deletions apps/files_sharing/css/accept-share.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
.accept-share {
text-align: center;
}

.accept-share h2,
.accept-share p {
text-align: center;
}

.accept-share .buttons {
display: flex;
justify-content: center;
margin-top: 1em;
}

.accept-share input[type="submit"] {
margin: 0;
}
54 changes: 54 additions & 0 deletions apps/files_sharing/lib/Controller/AcceptController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Files\NotFoundException;
use OCP\IGroupManager;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager as ShareManager;
use OCP\Share\IShare;

#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class AcceptController extends Controller {
Expand All @@ -31,12 +37,47 @@ public function __construct(
private ShareManager $shareManager,
private IUserSession $userSession,
private IURLGenerator $urlGenerator,
private IUserManager $userManager,
private IGroupManager $groupManager,
) {
parent::__construct(Application::APP_ID, $request);
}

#[NoAdminRequired]
#[NoCSRFRequired]
public function showAccept(string $shareId): Response {
try {
$share = $this->shareManager->getShareById($shareId);
} catch (ShareNotFound $e) {
return new NotFoundResponse();
}

$user = $this->userSession->getUser();
if ($user === null || !$this->isRecipient($share, $user)) {
return new NotFoundResponse();
}

try {
$filename = $share->getNode()->getName();
} catch (NotFoundException) {
return new NotFoundResponse();
}

$sharer = $this->userManager->get($share->getSharedBy());
$sharerDisplayName = $sharer !== null ? $sharer->getDisplayName() : $share->getSharedBy();

return new TemplateResponse(
Application::APP_ID,
'accept-share',
[
'filename' => $filename,
'sharerDisplayName' => $sharerDisplayName,
],
TemplateResponse::RENDER_AS_GUEST,
);
}

#[NoAdminRequired]
public function accept(string $shareId): Response {
try {
$share = $this->shareManager->getShareById($shareId);
Expand All @@ -59,4 +100,17 @@ public function accept(string $shareId): Response {

return new RedirectResponse($url);
}

private function isRecipient(IShare $share, IUser $user): bool {
if ($share->getShareType() === IShare::TYPE_USER) {
return $share->getSharedWith() === $user->getUID();
}

if ($share->getShareType() === IShare::TYPE_GROUP) {
$group = $this->groupManager->get($share->getSharedWith());
return $group !== null && $group->inGroup($user);
}

return false;
}
}
18 changes: 18 additions & 0 deletions apps/files_sharing/templates/accept-share.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
\OCP\Util::addStyle('files_sharing', 'accept-share');
?>

<div class="guest-box accept-share">
<form action="" method="post">
<h2><?php p($l->t('%1$s shared %2$s with you', [$_['sharerDisplayName'], $_['filename']])); ?></h2>
<p><?php p($l->t('Do you want to accept this share?')); ?></p>
<div class="buttons">
<input type="submit" class="primary" value="<?php p($l->t('Accept')); ?>">
</div>
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']); ?>">
</form>
</div>
59 changes: 56 additions & 3 deletions build/integration/features/bootstrap/Sharing.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use OCA\Files_Sharing\MountProvider;
use PHPUnit\Framework\Assert;
use Psr\Http\Message\ResponseInterface;
Expand Down Expand Up @@ -97,12 +98,64 @@ public function creatingShare(?TableNode $body): void {
}

/**
* @When /^accepting last share via the accept endpoint$/
* @When /^opening last share accept confirmation page$/
*/
public function acceptingLastShareViaAcceptEndpoint(): void {
public function openingLastShareAcceptConfirmationPage(): void {
$share_id = $this->lastShareData->data[0]->id;
$url = "/index.php/apps/files_sharing/accept/ocinternal:$share_id";
$this->sendingToDirectUrl('GET', $url);
$baseUrl = substr($this->baseUrl, 0, -5);
$client = new Client();
try {
$this->response = $client->get(
$baseUrl . $url,
[
'cookies' => $this->cookieJar,
]
);
} catch (ClientException $ex) {
$this->response = $ex->getResponse();
}
}

/**
* @When /^accepting last share via the accept endpoint with requesttoken$/
*/
public function acceptingLastShareViaAcceptEndpointWithRequesttoken(): void {
$share_id = $this->lastShareData->data[0]->id;
$url = "/index.php/apps/files_sharing/accept/ocinternal:$share_id";
$baseUrl = substr($this->baseUrl, 0, -5);
$client = new Client();
try {
$this->response = $client->post(
$baseUrl . $url,
[
'cookies' => $this->cookieJar,
'form_params' => [
'requesttoken' => $this->requestToken,
],
'headers' => [
'Origin' => $baseUrl,
],
]
);
} catch (ClientException $ex) {
$this->response = $ex->getResponse();
}
}

/**
* @When /^accepting last share via the accept endpoint$/
*/
public function acceptingLastShareViaAcceptEndpoint(): void {
$this->loggingInUsingWebAs($this->currentUser);
$this->openingLastShareAcceptConfirmationPage();

if ($this->response->getStatusCode() !== 200) {
return;
}

$this->theCsrfTokenIsExtractedFromThePreviousResponse();
$this->acceptingLastShareViaAcceptEndpointWithRequesttoken();
}

/**
Expand Down
6 changes: 5 additions & 1 deletion build/integration/sharing_features/sharing-accept.feature
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ Feature: sharing-accept
Then the OCS status code should be "100"
And the HTTP status code should be "200"
And As an "user1"
When accepting last share via the accept endpoint
And Logging in using web as "user1"
When opening last share accept confirmation page
Then the HTTP status code should be "200"
When the CSRF token is extracted from the previous response
And accepting last share via the accept endpoint with requesttoken
Then the HTTP status code should be "200"

Scenario: Accepting a share as a different user
Expand Down
2 changes: 1 addition & 1 deletion lib/private/Share20/DefaultShareProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -1562,7 +1562,7 @@ public function sendMailNotification(IShare $share): bool {
$this->sendUserShareMail(
$l,
$share->getNode()->getName(),
$this->urlGenerator->linkToRouteAbsolute('files_sharing.Accept.accept', ['shareId' => $share->getFullId()]),
$this->urlGenerator->linkToRouteAbsolute('files_sharing.Accept.showAccept', ['shareId' => $share->getFullId()]),
$share->getSharedBy(),
$emailAddress,
$share->getExpirationDate(),
Expand Down
Loading