Skip to content

Commit ad3985f

Browse files
feat(sharereview): expose tables(table/view/context) shares to share-review apps via OCP\Share\ShareReview
Implement IShareReviewSource listing all tables(table/view/context) shares with their capabilities mapped to ShareReviewPermission entries, gate deletions behind the ShareReviewAccessCheckEvent authorization check including revocation of linked uploaded-files shares, and register the source via RegisterShareReviewSourceEvent. Assisted-by: Claude Code:claude-fable-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
1 parent 88a4ff7 commit ad3985f

13 files changed

Lines changed: 1050 additions & 0 deletions

lib/AppInfo/Application.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
use OCA\Tables\Search\SearchTablesProvider;
3535
use OCA\Tables\Service\Support\AuditLogServiceInterface;
3636
use OCA\Tables\Service\Support\DefaultAuditLogService;
37+
use OCA\Tables\ShareReview\ShareReviewListener;
3738
use OCA\Tables\UserMigration\TablesMigrator;
3839
use OCP\AppFramework\App;
3940
use OCP\AppFramework\Bootstrap\IBootContext;
@@ -44,6 +45,7 @@
4445
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent;
4546
use OCP\DB\Events\AddMissingIndicesEvent;
4647
use OCP\Group\Events\GroupDeletedEvent;
48+
use OCP\Share\ShareReview\RegisterShareReviewSourceEvent;
4749
use OCP\User\Events\BeforeUserDeletedEvent;
4850
use OCP\User\Events\UserDeletedEvent;
4951
use Psr\Container\ContainerInterface;
@@ -84,6 +86,7 @@ public function register(IRegistrationContext $context): void {
8486

8587
$context->registerEventListener(BeforeUserDeletedEvent::class, UserDeletedListener::class);
8688
$context->registerEventListener(DatasourceEvent::class, AnalyticsDatasourceListener::class);
89+
$context->registerEventListener(RegisterShareReviewSourceEvent::class, ShareReviewListener::class);
8790
$context->registerEventListener(RenderReferenceEvent::class, TablesReferenceListener::class);
8891
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
8992
$context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class);

lib/Db/ContextMapper.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,33 @@ public function findAllContainingNode(int $nodeType, int $nodeId, string $userId
279279
return $resultEntities;
280280
}
281281

282+
/**
283+
* Fetch a map of id → name for the given context IDs.
284+
*
285+
* @param int[] $ids
286+
* @return array<int, string>
287+
* @throws Exception
288+
*/
289+
public function findIdToNameMap(array $ids): array {
290+
if ($ids === []) {
291+
return [];
292+
}
293+
$qb = $this->db->getQueryBuilder();
294+
$qb->select('id', 'name')
295+
->from($this->table)
296+
->where($qb->expr()->in('id', $qb->createParameter('ids')));
297+
$map = [];
298+
foreach (array_chunk($ids, 1_000) as $chunk) {
299+
$qb->setParameter('ids', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
300+
$result = $qb->executeQuery();
301+
foreach ($result->fetchAll() as $row) {
302+
$map[(int)$row['id']] = (string)$row['name'];
303+
}
304+
$result->closeCursor();
305+
}
306+
return $map;
307+
}
308+
282309
protected function applyOwnedOrSharedQuery(IQueryBuilder $qb, string $userId): void {
283310
$sharedToConditions = $qb->expr()->orX();
284311

lib/Db/ShareMapper.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,16 @@ public function findAllSharesForNodeTo(string $nodeType, int $nodeId, string $re
191191
return $this->findEntities($qb);
192192
}
193193

194+
/**
195+
* @throws Exception
196+
*/
197+
public function deleteById(int $id): bool {
198+
$qb = $this->db->getQueryBuilder();
199+
$qb->delete($this->table)
200+
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
201+
return $qb->executeStatement() > 0;
202+
}
203+
194204
/**
195205
* @param int $nodeId
196206
* @param string $nodeType
@@ -236,6 +246,28 @@ public function findAllSharesForTablesAndContexts(array $tableIds, array $contex
236246
return $this->findEntities($qb);
237247
}
238248

249+
/**
250+
* Return all shares as raw associative arrays, ordered by id.
251+
*
252+
* @return list<array<string, mixed>>
253+
* @throws Exception
254+
*/
255+
public function findAllRaw(): array {
256+
$qb = $this->db->getQueryBuilder();
257+
$qb->select(
258+
'id', 'sender', 'receiver', 'receiver_type', 'node_id', 'node_type',
259+
'token', 'password',
260+
'permission_read', 'permission_create', 'permission_update',
261+
'permission_delete', 'permission_manage',
262+
'created_at', 'last_edit_at'
263+
)->from($this->table)
264+
->orderBy('id', 'ASC');
265+
$result = $qb->executeQuery();
266+
$rows = $result->fetchAll();
267+
$result->closeCursor();
268+
return $rows;
269+
}
270+
239271
/**
240272
* @throws Exception
241273
*/

lib/Db/TableMapper.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,31 @@ public function insert(Entity $entity): Table {
176176
public function getDbConnection() {
177177
return $this->db;
178178
}
179+
180+
/**
181+
* Fetch a map of id → title for the given table IDs.
182+
*
183+
* @param int[] $ids
184+
* @return array<int, string>
185+
* @throws Exception
186+
*/
187+
public function findIdToTitleMap(array $ids): array {
188+
if ($ids === []) {
189+
return [];
190+
}
191+
$qb = $this->db->getQueryBuilder();
192+
$qb->select('id', 'title')
193+
->from($this->table)
194+
->where($qb->expr()->in('id', $qb->createParameter('ids')));
195+
$map = [];
196+
foreach (array_chunk($ids, 1_000) as $chunk) {
197+
$qb->setParameter('ids', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
198+
$result = $qb->executeQuery();
199+
foreach ($result->fetchAll() as $row) {
200+
$map[(int)$row['id']] = (string)$row['title'];
201+
}
202+
$result->closeCursor();
203+
}
204+
return $map;
205+
}
179206
}

lib/Db/ViewMapper.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,31 @@ public function search(?string $term = null, ?string $userId = null, ?int $limit
178178

179179
return $this->findEntities($qb);
180180
}
181+
182+
/**
183+
* Fetch a map of id → title for the given view IDs.
184+
*
185+
* @param int[] $ids
186+
* @return array<int, string>
187+
* @throws Exception
188+
*/
189+
public function findIdToTitleMap(array $ids): array {
190+
if ($ids === []) {
191+
return [];
192+
}
193+
$qb = $this->db->getQueryBuilder();
194+
$qb->select('id', 'title')
195+
->from($this->table)
196+
->where($qb->expr()->in('id', $qb->createParameter('ids')));
197+
$map = [];
198+
foreach (array_chunk($ids, 1_000) as $chunk) {
199+
$qb->setParameter('ids', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
200+
$result = $qb->executeQuery();
201+
foreach ($result->fetchAll() as $row) {
202+
$map[(int)$row['id']] = (string)$row['title'];
203+
}
204+
$result->closeCursor();
205+
}
206+
return $map;
207+
}
181208
}

lib/Service/ShareService.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,24 @@ private function addReceiverDisplayNames(array $shares): array {
755755
return $shares;
756756
}
757757

758+
/**
759+
* Delete a share on behalf of a trusted share-review operation.
760+
*
761+
* PERMISSION_MANAGE is intentionally not checked. The caller must verify
762+
* operator access via ShareReviewAccessCheckEvent before invoking this
763+
* method. All other side effects are preserved so the deletion is auditable.
764+
*
765+
* @throws \OCP\AppFramework\Db\DoesNotExistException if $id does not exist
766+
* @throws Exception on database failure
767+
*/
768+
public function deleteForShareReview(int $id): void {
769+
$share = $this->mapper->find($id);
770+
$this->mapper->delete($share);
771+
if ($share->getNodeType() === 'context') {
772+
$this->contextNavigationMapper->deleteByShareId($share->getId());
773+
}
774+
}
775+
758776
public function deleteAllForTable(Table $table):void {
759777
try {
760778
$this->mapper->deleteByNode($table->getId(), 'table');
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Tables\ShareReview;
11+
12+
use OCP\EventDispatcher\Event;
13+
use OCP\EventDispatcher\IEventListener;
14+
use OCP\Share\ShareReview\RegisterShareReviewSourceEvent;
15+
16+
/** @template-implements IEventListener<RegisterShareReviewSourceEvent> */
17+
class ShareReviewListener implements IEventListener {
18+
public function __construct() {
19+
}
20+
21+
public function handle(Event $event): void {
22+
if (!$event instanceof RegisterShareReviewSourceEvent) {
23+
return;
24+
}
25+
$event->registerSource(ShareReviewSource::class);
26+
}
27+
}

0 commit comments

Comments
 (0)