-
Notifications
You must be signed in to change notification settings - Fork 43
feat: add permissions for public link shares #2483
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| /** | ||
| * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors | ||
| * SPDX-License-Identifier: AGPL-3.0-or-later | ||
| */ | ||
|
|
||
| namespace OCA\Tables\Migration; | ||
|
|
||
| use OCA\Tables\AppInfo\Application; | ||
| use OCA\Tables\Constants\ShareReceiverType; | ||
| use OCP\DB\QueryBuilder\IQueryBuilder; | ||
| use OCP\IConfig; | ||
| use OCP\IDBConnection; | ||
| use OCP\Migration\IOutput; | ||
| use OCP\Migration\IRepairStep; | ||
|
|
||
| class ResetPublicSharePermissions implements IRepairStep { | ||
|
|
||
| public function __construct( | ||
| protected IConfig $config, | ||
| protected IDBConnection $dbc, | ||
| ) { | ||
| } | ||
|
|
||
| public function getName(): string { | ||
| return 'Reset public link share permissions to read-only for versions before 2.0.2'; | ||
| } | ||
|
|
||
| public function run(IOutput $output): void { | ||
| $appVersion = $this->config->getAppValue(Application::APP_ID, 'installed_version', '0.0'); | ||
| if (\version_compare($appVersion, '2.0.2', '>=')) { | ||
| $output->info('Not applicable, skipping.'); | ||
| return; | ||
| } | ||
|
|
||
| $qb = $this->dbc->getQueryBuilder(); | ||
| $qb->update('tables_shares') | ||
| ->set('permission_read', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT)) | ||
| ->set('permission_create', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)) | ||
| ->set('permission_update', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)) | ||
| ->set('permission_delete', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)) | ||
| ->set('permission_manage', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)) | ||
| ->where($qb->expr()->eq('receiver_type', $qb->createNamedParameter(ShareReceiverType::LINK, IQueryBuilder::PARAM_STR))) | ||
| ->executeStatement(); | ||
|
|
||
| $output->info('Reset public link share permissions to read-only.'); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,6 +45,8 @@ class PermissionsService { | |
|
|
||
| protected bool $isCli = false; | ||
|
|
||
| private bool $isPublicContext = false; | ||
|
|
||
| private ContextMapper $contextMapper; | ||
|
|
||
| public function __construct( | ||
|
|
@@ -549,6 +551,11 @@ public function getPermissionArrayForNodeFromContexts(int $nodeId, string $nodeT | |
| ); | ||
| } | ||
|
|
||
| public function setPublicContext(): void { | ||
| $this->userId = ''; | ||
|
benjaminfrueh marked this conversation as resolved.
|
||
| $this->isPublicContext = true; | ||
| } | ||
|
|
||
| private function hasPermission(int $existingPermissions, string $permissionName): bool { | ||
| $constantName = 'PERMISSION_' . strtoupper($permissionName); | ||
| try { | ||
|
|
@@ -634,7 +641,7 @@ private function basisCheck(Table|View|Context $element, string $nodeType, ?stri | |
| } | ||
|
|
||
| if ($userId === '') { | ||
| return true; | ||
| return $this->isCli || $this->isPublicContext; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i wonder if this breaks existing shares 🤔
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should not break existing shares, anything going through PublicRowOCSController, so also the existing getRows() will have isPublicContext set to true. I just wanted to be more explicit and safe here, in what cases a empty userId should be allowed. Was there any other case than CLI and isPublicContext when a empty userId should bypass the permission check? We can of course also revert this line and always return true here again, just to be sure. I think we should think about a way to refactor this later and maybe use something else than an empty userId to bypass the permission checks. |
||
| } | ||
|
|
||
| if ($this->userIsElementOwner($element, $userId, $nodeType)) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -178,7 +178,7 @@ public function find(int $rowId): Row2 { | |
| * @throws InternalError | ||
| */ | ||
| public function create(?int $tableId, ?int $viewId, RowDataInput|array $data): Row2 { | ||
| if ($this->userId === null || $this->userId === '') { | ||
| if ($this->userId === null) { | ||
| $e = new \Exception('No user id in context, but needed.'); | ||
| $this->logger->error($e->getMessage(), ['exception' => $e]); | ||
| throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': ' . $e->getMessage()); | ||
|
|
@@ -685,7 +685,7 @@ public function updateSet( | |
| * @throws PermissionError | ||
| * @noinspection DuplicatedCode | ||
| */ | ||
| public function delete(int $id, ?int $viewId, string $userId): Row2 { | ||
| public function delete(int $id, ?int $viewId, string $userId, ?int $tableId = null): Row2 { | ||
| try { | ||
| $item = $this->getRowById($id); | ||
| } catch (InternalError $e) { | ||
|
|
@@ -720,6 +720,12 @@ public function delete(int $id, ?int $viewId, string $userId): Row2 { | |
| throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': ' . $e->getMessage()); | ||
| } | ||
| } else { | ||
| if ($tableId !== null && $tableId !== $item->getTableId()) { | ||
| $e = new \Exception('Row does not belong to table with id ' . $tableId); | ||
| $this->logger->error($e->getMessage(), ['exception' => $e]); | ||
| throw new InternalError(get_class($this) . ' - ' . __FUNCTION__ . ': ' . $e->getMessage()); | ||
| } | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. theoretically both table and view can be null. Not from the calling Controller, given it is internally called after validation there is no much pressure… but it leaves a funny feeling in the tummy. #paranoia
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a defensive fix, I now added a simple check to the create, update and delete in the |
||
| // security | ||
| if (!$this->permissionsService->canReadRowsByElementId($item->getTableId(), 'table', $userId)) { | ||
| $e = new \Exception('Row not found.'); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.