🕵️ Add sharereview support#2711
Conversation
a74331b to
411e1f7
Compare
569f563 to
b35b188
Compare
Assisted-by: Claude Code:claude-sonnet-4-6 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Assisted-by: Claude Code:claude-sonnet-4-6 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
d53e2d8 to
d2e8862
Compare
enjeck
left a comment
There was a problem hiding this comment.
To be sure, it's intentional to be able to delete shares from resources that you don't own/manage? And that usually-secret public share tokens are listed too?
| private const SHARE_TABLE = 'tables_shares'; | ||
| private const TABLES_TABLE = 'tables_tables'; | ||
| private const VIEWS_TABLE = 'tables_views'; | ||
| private const CONTEXTS_TABLE = 'tables_contexts_context'; |
There was a problem hiding this comment.
I do not like that database resource names leak from their actual place, which are the mappers.
Alternative: make it a public class constant in the respective mapper.
| /** @return list<array<string, mixed>> */ | ||
| private function fetchAllShares(): array { | ||
| try { | ||
| $qb = $this->db->getQueryBuilder(); | ||
| $qb->select( | ||
| 'id', 'sender', 'receiver', 'receiver_type', 'node_id', 'node_type', | ||
| 'token', 'password', | ||
| 'permission_read', 'permission_create', 'permission_update', | ||
| 'permission_delete', 'permission_manage', | ||
| 'created_at', 'last_edit_at' | ||
| )->from(self::SHARE_TABLE) | ||
| ->orderBy('id', 'ASC'); | ||
| $result = $qb->executeQuery(); | ||
| $rows = $result->fetchAll(); | ||
| $result->closeCursor(); | ||
| return $rows; | ||
| } catch (Exception $e) { | ||
| $this->logger->error('Tables ShareReview: failed to fetch shares: {message}', ['message' => $e->getMessage()]); | ||
| return []; | ||
| } | ||
| } |
There was a problem hiding this comment.
Leaks responsibility, should be provided as a method on (Service and) Mapper level.
A think to consider: can be implemented as Generator (we do it to seldomly) for a smaller memory footprint. Especially since this one reads all shares. Think of big instances. In worst case (not sure it is likely, but depends) this can ran into a memory exhaustion – but also on the callee side if really everything is consumed at once.
| $qb = $this->db->getQueryBuilder(); | ||
| $qb->select('id', $nameColumn) | ||
| ->from($table) | ||
| ->where($qb->expr()->in('id', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY))); | ||
| $result = $qb->executeQuery(); | ||
| $rows = $result->fetchAll(); | ||
| $result->closeCursor(); | ||
| foreach ($rows as $row) { | ||
| $map[(int)$row['id']] = (string)$row[$nameColumn]; | ||
| } | ||
| } catch (Exception $e) { | ||
| $this->logger->error('Tables ShareReview: failed to fetch names from {table}: {message}', ['table' => $table, 'message' => $e->getMessage()]); | ||
| } |
There was a problem hiding this comment.
as mentioned, database queries should be done in the respectives mappers and exposed perhaps/ideally via Service.
|
|
||
| $formatted = []; | ||
| foreach ($rawShares as $share) { | ||
| $formatted[] = [ |
There was a problem hiding this comment.
Ideally this is not a random associated array, but a specific class (can be a DTO/data containers without further logic). This is way more maintainable and also has a better memory footprint.
A factory class or callback could be provided by the ShareReview app when calling, then you have it more formalized/API-like as well.
| return $this->l10n->t('%s (View)', [$viewNames[$nodeId] ?? $this->l10n->t('View %s', [$nodeId])]); | ||
| } | ||
| if ($nodeType === self::NODE_TYPE_CONTEXT) { | ||
| return $this->l10n->t('%s (Context)', [$contextNames[$nodeId] ?? $this->l10n->t('Context %s', [$nodeId])]); |
There was a problem hiding this comment.
Application is the user facing term, Context is only the internal-technical name.
| try { | ||
| $this->contextNavigationMapper->deleteByShareId((int)$shareId); | ||
| } catch (Exception $e) { | ||
| $this->logger->error('Tables ShareReview: failed to clean up context navigation for share {id}: {message}', ['id' => $shareId, 'message' => $e->getMessage()]); |
There was a problem hiding this comment.
This will be more rule than exception, as we can expect this being called for Tables and Views way more often than for Contexts. One or more entries will only be against shared contexts.
Hence it would result in confusing, at worst also spammy, log messages.
|
@enjeck regarding:
Yes BUT I will change the implementation in the way that it uses an event mechanism like in other places with similar calling patterns, to ensure the user would have that permission as checked then by the share review app. SO I reset the PR to draft, to reflect the need for further improvements. |
873b425 to
e8dc75a
Compare
Implement ShareReviewSource as an ISource integration for the Nextcloud ShareReview app, covering getName(), getShares(), and deleteShare(), plus the listener registration that wires the source to the SourceEvent. getShares() fetches all Tables shares via ShareMapper::findAllRaw() and resolves node titles and receiver display names through per-type mapper queries (TableMapper::findIdToTitleMap(), ViewMapper::findIdToTitleMap(), ContextMapper::findIdToNameMap()). Each share is represented as a ShareInfo value object and serialised via ShareInfo::toArray(). User-facing labels use 'Table', 'View', and 'Application' (never the internal 'Context' term). All strings are localised. Unknown node or receiver types trigger a warning log. The share time is the later of created_at and last_edit_at, surfacing recently-modified shares accurately. deleteShare() delegates to ShareMapper::delete() and conditionally removes context-navigation entries only for context-type shares, avoiding spurious cleanup on table and view share deletions. Psalm-clean, coding-standards-compliant, and covered by unit tests for all share types and edge cases. Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de> Assisted-by: ClaudeCode:claude-sonnet-4-6
e8dc75a to
affa711
Compare
…kEvent Before deleting a share's ACL, dispatch the canonical OCP\Share\Events\ShareReviewAccessCheckEvent provided by the server. If the event is cancelled, the deletion is aborted, allowing server-level policy hooks to block share review deletions without requiring Tables to know the policy details. ShareService::deleteShare() is the integration point. Tables no longer needs its own ShareReviewAccessCheckEvent class now that the server provides the canonical event type. Unit tests cover both the allowed and vetoed deletion paths. Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de> Assisted-by: ClaudeCode:claude-sonnet-4-6
affa711 to
049bfd7
Compare
🏁 Checklist
/backport to stableX.X🤖 AI (if applicable)