Revert "feat(blog): add Visibility { Private, Public } to gate post reads"

This reverts commit 33ecfa7ebd.
This commit is contained in:
Paul Schneider 2026-08-18 15:40:52 +01:00
commit 42625f5ddd
Signed by: notazof
GPG key ID: 1DD5D838E5343B06
16 changed files with 46 additions and 5427 deletions

View file

@ -1,6 +1,5 @@
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
using Yavsc.Blogspot;
using Yavsc.Models;
using Yavsc.Models.Blog;
@ -24,21 +23,10 @@ namespace Yavsc.Server.Helpers
dbContext.Circle.Include(c => c.Members)
.Where(c => c.Members.Any(m => m.MemberId == readerId))
.Select(c => c.Id).ToArray();
// Mirror of BlogSpotService.Index for an
// authenticated reader: Private restricts to the
// author; Public is read-through-ACL.
return dbContext.BlogSpot.Include(
b => b.Author
).Include(p => p.ACL).Where(x => x.Author.Id == posterId &&
(
(x.Visibility == Visibility.Private && x.AuthorId == readerId)
|| (x.Visibility == Visibility.Public
&& (x.ACL == null
|| x.ACL.Count == 0
|| x.AuthorId == readerId
|| (readerCirclesMemberships != null
&& x.ACL.Any(a => readerCirclesMemberships.Contains(a.CircleId)))))
));
(x.ACL.Count == 0 || x.ACL.Any(a => readerCirclesMemberships.Contains(a.CircleId))));
}
}

View file

@ -13,7 +13,6 @@ namespace Yavsc.Models
using Bank;
using Billing;
using Blog;
using Blogspot;
using Chat;
using Drawing;
using Forms;
@ -223,17 +222,6 @@ namespace Yavsc.Models
.WithMany(u => u.Posts)
.HasForeignKey(b => b.AuthorId)
.OnDelete(DeleteBehavior.Restrict);
// Store Visibility as a plain int (NOT NULL, default
// 0 = Private) so existing rows land on the pre-ACL
// behaviour by default. System.Text.Json serialises
// the enum as its underlying int, so the wire shape
// is a plain number — no JsonConverter needed.
builder.Entity<BlogPost>()
.Property(b => b.Visibility)
.HasConversion<int>()
.HasDefaultValue(Visibility.Private)
.IsRequired();
builder.Entity<Comment>()
.HasOne(c => c.Author)
.WithMany(u => u.BlogComments)

View file

@ -30,21 +30,6 @@ namespace Yavsc.Models.Blog
[Display(Name = "Liste de contrôle d'accès")]
public virtual List<CircleAuthorizationToBlogPost>? ACL { get; set; }
/// <summary>
/// Visibility of this post.
/// <para><see cref="Visibility.Public"/> reads through the
/// ACL (open when the ACL is empty, narrowed by the ACL
/// when it is non-empty). <see cref="Visibility.Private"/>
/// ignores the ACL at read time and restricts to author +
/// administrators. The ACL list is preserved across
/// Private/Public flips so re-opening is lossless.</para>
/// <para>Configured as <c>int</c> with default
/// <see cref="Visibility.Private"/> in
/// <c>ApplicationDbContext.OnModelCreating</c>.</para>
/// </summary>
[Display(Name = "Visibilité")]
public Visibility Visibility { get; set; } = Visibility.Private;
[Display(Name = "Identifiant de l'auteur")]
[ForeignKey("Author")]
public string? AuthorId { get; set; }

View file

@ -200,46 +200,28 @@ public class BlogSpotService
Where(c => c.Members.Any(m => m.MemberId == viewerId))
.Select(c => c.Id).ToArrayAsync();
// Visibility drives the read gate:
// * Public : the ACL decides. Open if the ACL is
// empty, narrowed otherwise to author +
// ACL circles + admin.
// * Private : ACL is ignored at read time. Only the
// author (and administrators, checked
// elsewhere) can read.
// Admin reads (the Administrator role) go through
// IsInMsRole("Administrator") upstream in
// PermissionHandler; we don't repeat that here so the
// listing query stays role-agnostic.
posts = _context.BlogSpot
.Include(b => b.Author)
.Include(p => p.ACL)
.Include(p => p.Tags)
.Include(p => p.Comments)
.Where(p =>
(p.Visibility == Visibility.Private && p.AuthorId == viewerId)
|| (p.Visibility == Visibility.Public
&& (p.ACL == null
|| p.ACL.Count == 0
|| p.AuthorId == viewerId
|| (userCircles != null
&& p.ACL.Any(a => userCircles.Contains(a.CircleId))))));
.Where(p => p.ACL == null
|| p.ACL.Count == 0
|| (p.AuthorId == viewerId)
|| (userCircles != null &&
p.ACL.Any(a => userCircles.Contains(a.CircleId)))
);
}
else
{
// Anonymous callers only see Public posts with no
// ACL — anything else either requires membership
// (which we have no way to check without an
// identity) or is Private.
posts = _context.blogSpotPublications
.Include(p => p.BlogPost)
.Include(b => b.BlogPost.Author)
.Include(p => p.BlogPost.ACL)
.Include(p => p.BlogPost.Tags)
.Include(p => p.BlogPost.Comments)
.Where(p => p.BlogPost.Visibility == Visibility.Public
&& (p.BlogPost.ACL == null
|| p.BlogPost.ACL.Count == 0))
.Where(p => p.BlogPost.ACL == null
|| p.BlogPost.ACL.Count == 0)
.Select(p => p.BlogPost).ToArray();
}

View file

@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.EntityFrameworkCore;
using Yavsc.Blogspot;
using Yavsc.Models;
using Yavsc.Models.Blog;
using Yavsc.Server.Helpers;
@ -56,15 +55,9 @@ public class PermissionHandler : IAuthorizationHandler
{
if (resource is BlogPost blogPost)
{
// IsPublic is the authz twin of the Index/listing
// filter in BlogSpotService: a post is "publicly
// readable" (no membership required) iff its
// Visibility is Public and its ACL is empty.
// Visibility.Public + non-empty ACL is narrowed by
// the ACL, so it does NOT pass IsPublic here; the
// caller has to match IsSponsor for that.
return blogPost.Visibility == Visibility.Public
&& (blogPost.ACL == null || blogPost.ACL.Count == 0);
return
applicationDbContext.blogSpotPublications
.Any(p=>p.BlogpostId == blogPost.Id);
}
return false;
}