From b4fea308f7772c1d59446b289519af24e504236c Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Thu, 22 Feb 2024 20:42:47 +0100 Subject: [PATCH] [backend/masto-api] Enforce renote/reply visibility for notifications --- .../Mastodon/NotificationController.cs | 4 +- .../Renderers/NotificationRenderer.cs | 11 ++---- .../Core/Database/Tables/Notification.cs | 6 +++ .../Core/Extensions/QueryableExtensions.cs | 37 +++++++++++++++++-- 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/Iceshrimp.Backend/Controllers/Mastodon/NotificationController.cs b/Iceshrimp.Backend/Controllers/Mastodon/NotificationController.cs index 71bca5ca..84f9e04d 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/NotificationController.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/NotificationController.cs @@ -50,6 +50,7 @@ public class NotificationController(DatabaseContext db, NotificationRenderer not .FilterBlocked(p => p.Notifier, user) .FilterBlocked(p => p.Note, user) .Paginate(query, ControllerContext) + .PrecomputeNoteVisibilities(user) .RenderAllForMastodonAsync(notificationRenderer, user); //TODO: handle mutes @@ -69,12 +70,13 @@ public class NotificationController(DatabaseContext db, NotificationRenderer not .IncludeCommonProperties() .Where(p => p.Notifiee == user && p.Id == id) .EnsureNoteVisibilityFor(p => p.Note, user) + .PrecomputeNoteVisibilities(user) .FirstOrDefaultAsync() ?? throw GracefulException.RecordNotFound(); //TODO: handle reply/renote visibility - var res = await notificationRenderer.RenderAsync(notification, user); + var res = await notificationRenderer.RenderAsync(notification.EnforceRenoteReplyVisibility(p => p.Note), user); return Ok(res); } diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NotificationRenderer.cs b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NotificationRenderer.cs index 024a3774..1749fd6d 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NotificationRenderer.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NotificationRenderer.cs @@ -18,19 +18,14 @@ public class NotificationRenderer(NoteRenderer noteRenderer, UserRenderer userRe ? notification.Note?.Renote : notification.Note; - if (notification.Note != null && targetNote == null) - throw new Exception("targetNote must not be null at this stage"); - - var note = notification.Note != null - ? statuses?.FirstOrDefault(p => p.Id == targetNote!.Id) ?? - await noteRenderer.RenderAsync(targetNote!, user, accounts) + var note = targetNote != null + ? statuses?.FirstOrDefault(p => p.Id == targetNote.Id) ?? + await noteRenderer.RenderAsync(targetNote, user, accounts) : null; var notifier = accounts?.FirstOrDefault(p => p.Id == dbNotifier.Id) ?? await userRenderer.RenderAsync(dbNotifier); - //TODO: specially handle quotes - var res = new NotificationEntity { Id = notification.Id, diff --git a/Iceshrimp.Backend/Core/Database/Tables/Notification.cs b/Iceshrimp.Backend/Core/Database/Tables/Notification.cs index 0dae4c1e..6749c62a 100644 --- a/Iceshrimp.Backend/Core/Database/Tables/Notification.cs +++ b/Iceshrimp.Backend/Core/Database/Tables/Notification.cs @@ -125,4 +125,10 @@ public class Notification : IEntity [Column("id")] [StringLength(32)] public string Id { get; set; } = null!; + + public Notification WithPrecomputedNoteVisibilities(bool reply, bool renote) + { + Note = Note?.WithPrecomputedVisibilities(reply, renote); + return this; + } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs index 3ede745c..9bc82aa8 100644 --- a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs @@ -176,6 +176,16 @@ public static class QueryableExtensions p.Renote.IsVisibleFor(user))); } + public static IQueryable PrecomputeNoteVisibilities(this IQueryable query, User user) + { + return query.Select(p => p.WithPrecomputedNoteVisibilities(p.Note != null && + p.Note.Reply != null && + p.Note.Reply.IsVisibleFor(user), + p.Note != null && + p.Note.Renote != null && + p.Note.Renote.IsVisibleFor(user))); + } + public static IQueryable PrecomputeRelationshipData(this IQueryable query, User user) { return query.Select(p => p.WithPrecomputedBlockStatus(p.IsBlocking(user), p.IsBlockedBy(user)) @@ -248,13 +258,32 @@ public static class QueryableExtensions return list.Select(EnforceRenoteReplyVisibility); } + public static T EnforceRenoteReplyVisibility(this T source, Expression> predicate) + { + var note = predicate.Compile().Invoke(source); + if (note == null) return source; + if (!note.PrecomputedIsReplyVisible ?? false) + note.Reply = null; + if (!note.PrecomputedIsRenoteVisible ?? false) + note.Renote = null; + + return source; + } + + public static IEnumerable EnforceRenoteReplyVisibility( + this IEnumerable list, Expression> predicate + ) + { + return list.Select(p => EnforceRenoteReplyVisibility(p, predicate)); + } + public static async Task> RenderAllForMastodonAsync( this IQueryable notes, NoteRenderer renderer, User? user ) { var list = (await notes.ToListAsync()) - .EnforceRenoteReplyVisibility() - .ToList(); + .EnforceRenoteReplyVisibility() + .ToList(); return (await renderer.RenderManyAsync(list, user)).ToList(); } @@ -270,7 +299,9 @@ public static class QueryableExtensions this IQueryable notifications, NotificationRenderer renderer, User user ) { - var list = await notifications.ToListAsync(); + var list = (await notifications.ToListAsync()) + .EnforceRenoteReplyVisibility(p => p.Note) + .ToList(); return (await renderer.RenderManyAsync(list, user)).ToList(); }