refactoring
All checks were successful
Dotnet build and test / log-the-inputs (push) Successful in 7s
Dotnet build and test / build (push) Successful in 1m48s

This commit is contained in:
Paul Schneider
2025-06-29 16:12:16 +01:00
parent 0e0a79c6cd
commit 2002b8b232
5 changed files with 225 additions and 144 deletions

View File

@ -0,0 +1,17 @@
namespace Yavsc.Server.Exceptions;
[Serializable]
public class AuthorizationFailureException : Exception
{
public AuthorizationFailureException(Microsoft.AspNetCore.Authorization.AuthorizationResult auth) : base(auth?.Failure?.ToString()??auth?.ToString()??"AuthorizationResult failure")
{
}
public AuthorizationFailureException(string? message) : base(message)
{
}
public AuthorizationFailureException(string? message, Exception? innerException) : base(message, innerException)
{
}
}

View File

@ -24,7 +24,7 @@ namespace Yavsc.Helpers
return user.Identity.IsAuthenticated;
}
public static IEnumerable<BlogPost> UserPosts(this ApplicationDbContext dbContext, string posterId, string readerId)
public static IEnumerable<BlogPost> UserPosts(this ApplicationDbContext dbContext, string posterId, string? readerId)
{
if (readerId == null)
{

View File

@ -0,0 +1,162 @@
using System.Diagnostics;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Differencing;
using Microsoft.EntityFrameworkCore;
using Org.BouncyCastle.Tls;
using Yavsc.Helpers;
using Yavsc.Models;
using Yavsc.Models.Blog;
using Yavsc.Server.Exceptions;
using Yavsc.ViewModels.Auth;
using Yavsc.ViewModels.Blog;
public class BlogSpotService
{
private readonly ApplicationDbContext _context;
private readonly IAuthorizationService _authorizationService;
public BlogSpotService(ApplicationDbContext context,
IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
_context = context;
}
public BlogPost Create(string userId, BlogPostInputViewModel blogInput)
{
BlogPost post = new BlogPost
{
Title = blogInput.Title,
Content = blogInput.Content,
Photo = blogInput.Photo,
AuthorId = userId
};
_context.BlogSpot.Add(post);
_context.SaveChanges(userId);
return post;
}
public async Task<BlogPost> GetPostForEdition(ClaimsPrincipal user, long blogPostId)
{
var blog = await _context.BlogSpot.Include(x => x.Author).Include(x => x.ACL).SingleAsync(m => m.Id == blogPostId);
var auth = await _authorizationService.AuthorizeAsync(user, blog, new EditPermission());
if (!auth.Succeeded)
{
throw new AuthorizationFailureException(auth);
}
return blog;
}
public async Task<BlogPost> Details(ClaimsPrincipal user, long blogPostId)
{
BlogPost blog = await _context.BlogSpot
.Include(p => p.Author)
.Include(p => p.Tags)
.Include(p => p.Comments)
.Include(p => p.ACL)
.SingleAsync(m => m.Id == blogPostId);
if (blog == null)
{
return null;
}
var auth = await _authorizationService.AuthorizeAsync(user, blog, new ReadPermission());
if (!auth.Succeeded)
{
throw new AuthorizationFailureException(auth);
}
foreach (var c in blog.Comments)
{
c.Author = _context.Users.First(u => u.Id == c.AuthorId);
}
return blog;
}
public async Task Modify(ClaimsPrincipal user, BlogPostEditViewModel blogEdit)
{
var blog = _context.BlogSpot.SingleOrDefault(b => b.Id == blogEdit.Id);
Debug.Assert(blog != null);
var auth = await _authorizationService.AuthorizeAsync(user, blog, new EditPermission());
if (!auth.Succeeded)
{
throw new AuthorizationFailureException(auth);
}
blog.Content = blogEdit.Content;
blog.Title = blogEdit.Title;
blog.Photo = blogEdit.Photo;
blog.ACL = blogEdit.ACL;
// saves the change
_context.Update(blog);
_context.SaveChanges(user.GetUserId());
}
public async Task<IEnumerable<IGrouping<String, BlogPost>>> IndexByTitle(ClaimsPrincipal user, string id, int skip = 0, int take = 25)
{
IEnumerable<BlogPost> posts;
if (user.Identity.IsAuthenticated)
{
string viewerId = user.GetUserId();
long[] usercircles = await _context.Circle.Include(c => c.Members).
Where(c => c.Members.Any(m => m.MemberId == viewerId))
.Select(c => c.Id).ToArrayAsync();
posts = _context.BlogSpot
.Include(b => b.Author)
.Include(p => p.ACL)
.Include(p => p.Tags)
.Include(p => p.Comments)
.Where(p => (p.ACL.Count == 0)
|| (p.AuthorId == viewerId)
|| (usercircles != null && p.ACL.Any(a => usercircles.Contains(a.CircleId)))
);
}
else
{
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.ACL.Count == 0).Select(p => p.BlogPost).ToArray();
}
var data = posts.OrderByDescending(p => p.DateCreated);
var grouped = data.GroupBy(p => p.Title).Skip(skip).Take(take);
return grouped;
}
public async Task Delete(ClaimsPrincipal user, long id)
{
var uid = user.GetUserId();
BlogPost blog = _context.BlogSpot.Single(m => m.Id == id);
_context.BlogSpot.Remove(blog);
_context.SaveChanges(user.GetUserId());
}
public async Task<IEnumerable<BlogPost>> UserPosts(
string posterName,
string? readerId,
int pageLen = 10,
int pageNum = 0)
{
string? posterId = (await _context.Users.SingleOrDefaultAsync(u => u.UserName == posterName))?.Id ?? null;
if (posterId == null) return Array.Empty<BlogPost>();
return _context.UserPosts(posterId, readerId);
}
public object? ByTitle(string title)
{
return _context.BlogSpot.Include(
b => b.Author
).Where(x => x.Title == title).OrderByDescending(
x => x.DateCreated
).ToList();
}
}

View File

@ -1,4 +1,4 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Yavsc.Models;
@ -9,6 +9,7 @@ using Yavsc.Helpers;
using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore;
using Yavsc.ViewModels.Blog;
using Yavsc.Server.Exceptions;
// For more information on enabling Web API for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
@ -21,112 +22,63 @@ namespace Yavsc.Controllers
private readonly IAuthorizationService _authorizationService;
readonly RequestLocalizationOptions _localisationOptions;
readonly BlogSpotService blogSpotService;
public BlogspotController(
ApplicationDbContext context,
ILoggerFactory loggerFactory,
IAuthorizationService authorizationService,
IOptions<RequestLocalizationOptions> localisationOptions)
IOptions<RequestLocalizationOptions> localisationOptions,
BlogSpotService blogSpotService)
{
_context = context;
_logger = loggerFactory.CreateLogger<AccountController>();
_authorizationService = authorizationService;
_localisationOptions = localisationOptions.Value;
this.blogSpotService = blogSpotService;
}
// GET: Blog
[AllowAnonymous]
public async Task<IActionResult> Index(string id, int skip=0, int take=25)
public async Task<IActionResult> Index(string id, int skip = 0, int take = 25)
{
if (!string.IsNullOrEmpty(id)) {
return View("UserPosts", await UserPosts(id));
}
IEnumerable<BlogPost> posts;
if (User.Identity.IsAuthenticated)
if (!string.IsNullOrEmpty(id))
{
string viewerId = User.GetUserId();
long[] usercircles = await _context.Circle.Include(c=>c.Members).
Where(c=>c.Members.Any(m=>m.MemberId == viewerId))
.Select(c=>c.Id).ToArrayAsync();
posts = _context.BlogSpot
.Include(b => b.Author)
.Include(p=>p.ACL)
.Include(p=>p.Tags)
.Include(p=>p.Comments)
.Where(p =>(p.ACL.Count == 0)
|| (p.AuthorId == viewerId)
|| (usercircles != null && p.ACL.Any(a => usercircles.Contains(a.CircleId)))
);
return View("UserPosts",
await blogSpotService.UserPosts(id, User.GetUserId(),
skip, take));
}
else
{
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.ACL.Count == 0 ).Select(p=>p.BlogPost).ToArray();
}
var data = posts.OrderByDescending( p=> p.DateCreated);
var grouped = data.GroupBy(p=> p.Title).Skip(skip).Take(take);
return View(grouped);
var byTitle = await this.blogSpotService.IndexByTitle(User, id, skip, take);
return View(byTitle);
}
[Route("~/Title/{id?}")]
[AllowAnonymous]
public IActionResult Title(string id)
{
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
ViewData["Title"] = id;
return View("Title", _context.BlogSpot.Include(
b => b.Author
).Where(x => x.Title == id && (x.AuthorId == uid )).OrderByDescending(
x => x.DateCreated
).ToList());
return View("Title", blogSpotService.ByTitle(id));
}
private async Task<IEnumerable<BlogPost>> UserPosts(string userName, int pageLen=10, int pageNum=0)
private async Task<IEnumerable<BlogPost>> UserPosts(string userName, int pageLen = 10, int pageNum = 0)
{
string posterId = (await _context.Users.SingleOrDefaultAsync(u=>u.UserName == userName))?.Id ?? null ;
return _context.UserPosts(posterId, User.Identity.Name);
return await blogSpotService.UserPosts(userName, User.GetUserId(), pageLen, pageNum);
}
// GET: Blog/Details/5
[AllowAnonymous]
public async Task<IActionResult> Details(long? id)
{
if (id == null)
{
return NotFound();
}
if (id == null) return this.NotFound();
BlogPost blog = _context.BlogSpot
.Include(p => p.Author)
.Include(p => p.Tags)
.Include(p => p.Comments)
.Include(p => p.ACL)
.Single(m => m.Id == id);
if (blog == null)
{
return NotFound();
}
if ( _authorizationService.AuthorizeAsync(User, blog, new ReadPermission()).IsFaulted)
{
return new ChallengeResult();
}
foreach (var c in blog.Comments) {
c.Author = _context.Users.First(u=>u.Id==c.AuthorId);
}
var blog = await blogSpotService.Details(User, id.Value);
ViewData["apicmtctlr"] = "/api/blogcomments";
ViewData["moderatoFlag"] = User.IsInRole(Constants.BlogModeratorGroupName);
return View(blog);
}
void SetLangItems()
{
ViewBag.LangItems = _localisationOptions.SupportedUICultures.Select
ViewBag.LangItems = _localisationOptions.SupportedUICultures?.Select
(
sc => new SelectListItem { Value = sc.IetfLanguageTag, Text = sc.NativeName, Selected = System.Globalization.CultureInfo.CurrentUICulture == sc }
);
@ -136,9 +88,10 @@ namespace Yavsc.Controllers
[Authorize()]
public IActionResult Create(string title)
{
var result = new BlogPostInputViewModel{Title=title
var result = new BlogPostInputViewModel
{
Title = title
};
ViewData["PostTarget"]="Create";
SetLangItems();
return View(result);
}
@ -149,20 +102,11 @@ namespace Yavsc.Controllers
{
if (ModelState.IsValid)
{
BlogPost post = new BlogPost
{
Title = blogInput.Title,
Content = blogInput.Content,
Photo = blogInput.Photo,
AuthorId = User.GetUserId()
};
_context.BlogSpot.Add(post);
_context.SaveChanges(User.GetUserId());
BlogPost post = blogSpotService.Create(User.GetUserId(),
blogInput);
return RedirectToAction("Index");
}
ModelState.AddModelError("Unknown","Invalid Blog posted ...");
ViewData["PostTarget"]="Create";
return View("Edit",blogInput);
return View("Edit", blogInput);
}
[Authorize()]
@ -173,26 +117,13 @@ namespace Yavsc.Controllers
{
return NotFound();
}
ViewData["PostTarget"]="Edit";
BlogPost blog = _context.BlogSpot.Include(x => x.Author).Include(x => x.ACL).Single(m => m.Id == id);
if (blog == null)
try
{
return NotFound();
}
if (!_authorizationService.AuthorizeAsync(User, blog, new EditPermission()).IsFaulted)
{
ViewBag.ACL = _context.Circle.Where(
c=>c.OwnerId == blog.AuthorId)
.Select(
c => new SelectListItem
{
Text = c.Name,
Value = c.Id.ToString(),
Selected = blog.AuthorizeCircle(c.Id)
} 
);
BlogPost blog = await blogSpotService.GetPostForEdition(User, id.Value);
if (blog == null)
{
return NotFound();
}
SetLangItems();
return View(new BlogPostEditViewModel
{
@ -201,9 +132,10 @@ namespace Yavsc.Controllers
Content = blog.Content,
ACL = blog.ACL,
Photo = blog.Photo
});
});
}
else
catch (AuthorizationFailureException)
{
return new ChallengeResult();
}
@ -211,65 +143,41 @@ namespace Yavsc.Controllers
// POST: Blog/Edit/5
[HttpPost]
[ValidateAntiForgeryToken,Authorize()]
[ValidateAntiForgeryToken, Authorize()]
public async Task<IActionResult> Edit(BlogPostEditViewModel blogEdit)
{
if (ModelState.IsValid)
{
var blog = _context.BlogSpot.SingleOrDefault(b=>b.Id == blogEdit.Id);
if (blog == null) {
ModelState.AddModelError("Id", "not found");
return View();
}
if (!(await _authorizationService.AuthorizeAsync(User, blog, new EditPermission())).Succeeded) {
ViewData["StatusMessage"] = "Accès restreint";
return new ChallengeResult();
}
blog.Content=blogEdit.Content;
blog.Title = blogEdit.Title;
blog.Photo = blogEdit.Photo;
blog.ACL = blogEdit.ACL;
// saves the change
_context.Update(blog);
_context.SaveChanges(User.GetUserId());
await blogSpotService.Modify(User, blogEdit);
ViewData["StatusMessage"] = "Post modified";
return RedirectToAction("Index");
}
ViewData["PostTarget"]="Edit";
return View(blogEdit);
}
// GET: Blog/Delete/5
[ActionName("Delete"),Authorize()]
public IActionResult Delete(long? id)
[ActionName("Delete"), Authorize()]
public async Task<IActionResult> Delete(long? id)
{
if (id == null)
{
return NotFound();
}
BlogPost blog = _context.BlogSpot.Include(
b => b.Author
).Single(m => m.Id == id);
BlogPost blog = await blogSpotService.GetPostForEdition(User, id.Value);
if (blog == null)
{
return NotFound();
}
return View(blog);
}
// POST: Blog/Delete/5
[HttpPost, ActionName("Delete"), Authorize("IsTheAuthor")]
[ValidateAntiForgeryToken]
public IActionResult DeleteConfirmed(long id)
public async Task<IActionResult> DeleteConfirmed(long id)
{
var uid = User.GetUserId();
BlogPost blog = _context.BlogSpot.Single(m => m.Id == id);
_context.BlogSpot.Remove(blog);
_context.SaveChanges(User.GetUserId());
await blogSpotService.Delete(User, id);
return RedirectToAction("Index");
}
}

View File

@ -39,7 +39,6 @@ namespace Yavsc.Extensions;
public static class HostingExtensions
{
#region files config
public static IApplicationBuilder ConfigureFileServerApp(this IApplicationBuilder app,
bool enableDirectoryBrowsing = false)
{
@ -91,16 +90,10 @@ public static class HostingExtensions
return app;
}
#endregion
internal static WebApplication ConfigureWebAppServices(this WebApplicationBuilder builder)
{
IServiceCollection services = LoadConfiguration(builder);
//services.AddRazorPages();
services.AddSession();
// TODO .AddServerSideSessionStore<YavscServerSideSessionStore>()
@ -148,7 +141,8 @@ public static class HostingExtensions
.AddTransient<IYavscMessageSender, YavscMessageSender>()
.AddTransient<IBillingService, BillingService>()
.AddTransient<IDataStore, FileDataStore>((sp) => new FileDataStore("googledatastore", false))
.AddTransient<ICalendarManager, CalendarManager>();
.AddTransient<ICalendarManager, CalendarManager>()
.AddTransient<BlogSpotService>();
// TODO for SMS: services.AddTransient<ISmsSender, AuthMessageSender>();