[backend/masto-api] Enforce renote/reply visibility for notifications

This commit is contained in:
Laura Hausmann 2024-02-22 20:42:47 +01:00
parent 4d7d8ee34e
commit b4fea308f7
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
4 changed files with 46 additions and 12 deletions

View file

@ -50,6 +50,7 @@ public class NotificationController(DatabaseContext db, NotificationRenderer not
.FilterBlocked(p => p.Notifier, user) .FilterBlocked(p => p.Notifier, user)
.FilterBlocked(p => p.Note, user) .FilterBlocked(p => p.Note, user)
.Paginate(query, ControllerContext) .Paginate(query, ControllerContext)
.PrecomputeNoteVisibilities(user)
.RenderAllForMastodonAsync(notificationRenderer, user); .RenderAllForMastodonAsync(notificationRenderer, user);
//TODO: handle mutes //TODO: handle mutes
@ -69,12 +70,13 @@ public class NotificationController(DatabaseContext db, NotificationRenderer not
.IncludeCommonProperties() .IncludeCommonProperties()
.Where(p => p.Notifiee == user && p.Id == id) .Where(p => p.Notifiee == user && p.Id == id)
.EnsureNoteVisibilityFor(p => p.Note, user) .EnsureNoteVisibilityFor(p => p.Note, user)
.PrecomputeNoteVisibilities(user)
.FirstOrDefaultAsync() ?? .FirstOrDefaultAsync() ??
throw GracefulException.RecordNotFound(); throw GracefulException.RecordNotFound();
//TODO: handle reply/renote visibility //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); return Ok(res);
} }

View file

@ -18,19 +18,14 @@ public class NotificationRenderer(NoteRenderer noteRenderer, UserRenderer userRe
? notification.Note?.Renote ? notification.Note?.Renote
: notification.Note; : notification.Note;
if (notification.Note != null && targetNote == null) var note = targetNote != null
throw new Exception("targetNote must not be null at this stage"); ? statuses?.FirstOrDefault(p => p.Id == targetNote.Id) ??
await noteRenderer.RenderAsync(targetNote, user, accounts)
var note = notification.Note != null
? statuses?.FirstOrDefault(p => p.Id == targetNote!.Id) ??
await noteRenderer.RenderAsync(targetNote!, user, accounts)
: null; : null;
var notifier = accounts?.FirstOrDefault(p => p.Id == dbNotifier.Id) ?? var notifier = accounts?.FirstOrDefault(p => p.Id == dbNotifier.Id) ??
await userRenderer.RenderAsync(dbNotifier); await userRenderer.RenderAsync(dbNotifier);
//TODO: specially handle quotes
var res = new NotificationEntity var res = new NotificationEntity
{ {
Id = notification.Id, Id = notification.Id,

View file

@ -125,4 +125,10 @@ public class Notification : IEntity
[Column("id")] [Column("id")]
[StringLength(32)] [StringLength(32)]
public string Id { get; set; } = null!; public string Id { get; set; } = null!;
public Notification WithPrecomputedNoteVisibilities(bool reply, bool renote)
{
Note = Note?.WithPrecomputedVisibilities(reply, renote);
return this;
}
} }

View file

@ -176,6 +176,16 @@ public static class QueryableExtensions
p.Renote.IsVisibleFor(user))); p.Renote.IsVisibleFor(user)));
} }
public static IQueryable<Notification> PrecomputeNoteVisibilities(this IQueryable<Notification> 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<User> PrecomputeRelationshipData(this IQueryable<User> query, User user) public static IQueryable<User> PrecomputeRelationshipData(this IQueryable<User> query, User user)
{ {
return query.Select(p => p.WithPrecomputedBlockStatus(p.IsBlocking(user), p.IsBlockedBy(user)) return query.Select(p => p.WithPrecomputedBlockStatus(p.IsBlocking(user), p.IsBlockedBy(user))
@ -248,6 +258,25 @@ public static class QueryableExtensions
return list.Select(EnforceRenoteReplyVisibility); return list.Select(EnforceRenoteReplyVisibility);
} }
public static T EnforceRenoteReplyVisibility<T>(this T source, Expression<Func<T, Note?>> 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<T> EnforceRenoteReplyVisibility<T>(
this IEnumerable<T> list, Expression<Func<T, Note?>> predicate
)
{
return list.Select(p => EnforceRenoteReplyVisibility(p, predicate));
}
public static async Task<List<StatusEntity>> RenderAllForMastodonAsync( public static async Task<List<StatusEntity>> RenderAllForMastodonAsync(
this IQueryable<Note> notes, NoteRenderer renderer, User? user this IQueryable<Note> notes, NoteRenderer renderer, User? user
) )
@ -270,7 +299,9 @@ public static class QueryableExtensions
this IQueryable<Notification> notifications, NotificationRenderer renderer, User user this IQueryable<Notification> 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(); return (await renderer.RenderManyAsync(list, user)).ToList();
} }