Implemented Permissions on ArticleEditor

This commit is contained in:
Mia Rose Winter 2024-01-16 14:30:52 +01:00
parent 424cb19b54
commit 4a6599d165
Signed by: miawinter
GPG key ID: 4B6F6A83178F595E
3 changed files with 49 additions and 25 deletions

View file

@ -8,7 +8,7 @@
@using Microsoft.AspNetCore.Identity @using Microsoft.AspNetCore.Identity
@attribute [Authorize(Policy = "ArticleEditPermissions")] @attribute [Authorize(Policy = "ArticleEditPermissions")]
@inject IDbContextFactory<ApplicationDbContext> ContextFactory; @inject IDbContextFactory<ApplicationDbContext> ContextFactory
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject UserManager<ApplicationUser> UserManager @inject UserManager<ApplicationUser> UserManager
@inject IStringLocalizer<ArticleEditor> Localizer @inject IStringLocalizer<ArticleEditor> Localizer
@ -29,7 +29,7 @@
<div class="label"> <div class="label">
<span class="label-text">@Localizer["Title_Label"]</span> <span class="label-text">@Localizer["Title_Label"]</span>
</div> </div>
<InputText class="input input-bordered w-full" maxlength="256" aria-required <InputText class="input input-bordered w-full" maxlength="256" aria-required disabled="@CannotEdit"
@bind-Value="@Model.Title" placeholder="@Localizer["Title_Placeholder"]" /> @bind-Value="@Model.Title" placeholder="@Localizer["Title_Placeholder"]" />
<div class="label"> <div class="label">
<span class="label-text-alt text-error"> <span class="label-text-alt text-error">
@ -41,7 +41,7 @@
<div class="label"> <div class="label">
<span class="label-text">@Localizer["PublishDate_Label"]</span> <span class="label-text">@Localizer["PublishDate_Label"]</span>
</div> </div>
<InputDate class="input input-bordered w-full" aria-required <InputDate class="input input-bordered w-full" disabled="@CannotEdit"
@bind-Value="@Model.PublishDate" placeholder="@Localizer["PublishDate_Placeholder"]" /> @bind-Value="@Model.PublishDate" placeholder="@Localizer["PublishDate_Placeholder"]" />
<div class="label"> <div class="label">
<span class="label-text-alt text-error"> <span class="label-text-alt text-error">
@ -53,7 +53,7 @@
<div class="label"> <div class="label">
<span class="label-text">@Localizer["Body_Label"]</span> <span class="label-text">@Localizer["Body_Label"]</span>
</div> </div>
<InputTextArea class="textarea textarea-bordered w-full" rows="10" aria-required <InputTextArea class="textarea textarea-bordered w-full" rows="10" aria-required disabled="@CannotEdit"
@bind-Value="@Model.Body" placeholder="@Localizer["Body_Placeholder"]" /> @bind-Value="@Model.Body" placeholder="@Localizer["Body_Placeholder"]" />
<div class="label"> <div class="label">
<span class="label-text-alt text-error"> <span class="label-text-alt text-error">
@ -62,7 +62,9 @@
</div> </div>
</label> </label>
<button type="submit" class="btn btn-primary btn-wide">@Localizer["EditorSubmit"]</button> <button type="submit" class="btn btn-primary btn-wide" disabled="@CannotEdit">
@Localizer["EditorSubmit"]
</button>
</EditForm> </EditForm>
@if (Article is not null) { @if (Article is not null) {
@ -88,8 +90,11 @@
[CascadingParameter] [CascadingParameter]
private Task<AuthenticationState>? AuthenticationState { get; set; } private Task<AuthenticationState>? AuthenticationState { get; set; }
private ApplicationUser User { get; set; } = null!;
private bool IsAdmin { get; set; }
private Article? Article { get; set; } private Article? Article { get; set; }
private MarkupString? Content => Article is null ? null : new MarkupString(Article.BodyHtml); private MarkupString? Content => Article is null ? null : new MarkupString(Article.BodyHtml);
private bool CannotEdit => User is null || !IsAdmin && Article is not null && Article.Author.Id != User.Id;
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
if (Id is not null) { if (Id is not null) {
@ -99,7 +104,6 @@
// ReSharper disable once MethodHasAsyncOverload // ReSharper disable once MethodHasAsyncOverload
await using var context = ContextFactory.CreateDbContext(); await using var context = ContextFactory.CreateDbContext();
// ReSharper disable once MethodHasAsyncOverload // ReSharper disable once MethodHasAsyncOverload
var now = DateTimeOffset.UtcNow;
Article = context.Set<Article>() Article = context.Set<Article>()
.Include(a => a.Author) .Include(a => a.Author)
.Include(a => a.Reviewer) .Include(a => a.Reviewer)
@ -114,16 +118,17 @@
Model.Title ??= Article?.Title; Model.Title ??= Article?.Title;
Model.Body ??= Article?.Body; Model.Body ??= Article?.Body;
Model.PublishDate ??= Article?.PublishDate; Model.PublishDate ??= Article?.PublishDate;
if (AuthenticationState is null) throw new ApplicationException("???");
var state = await AuthenticationState;
var user = await UserManager.GetUserAsync(state.User);
User = user ?? throw new ApplicationException("???2");
IsAdmin = await UserManager.IsInRoleAsync(User, "Admin");
} }
private async Task OnValidSubmit() { private async Task OnValidSubmit() {
await using var context = await ContextFactory.CreateDbContextAsync(); await using var context = await ContextFactory.CreateDbContextAsync();
if (AuthenticationState is null) throw new ApplicationException("???"); context.Entry(User).State = EntityState.Unchanged;
var state = await AuthenticationState;
var user = await UserManager.GetUserAsync(state.User);
if (user is null) throw new ApplicationException("???2");
context.Entry(user).State = EntityState.Unchanged;
Article article; Article article;
if (Model.Id is not null) { if (Model.Id is not null) {
@ -137,16 +142,18 @@
article = new Article { article = new Article {
Title = Model.Title!, Title = Model.Title!,
Body = Model.Body!, Body = Model.Body!,
Author = user, Author = User,
Status = ArticleStatus.Published // TODO remove Status = ArticleStatus.Published // TODO remove
}; };
await context.AddAsync(article); await context.AddAsync(article);
} }
if (Model.PublishDate is not null) article.PublishDate = Model.PublishDate.Value; if (Model.PublishDate is not null) article.PublishDate = Model.PublishDate.Value;
if (user.Id != article.Author.Id) if (User.Id != article.Author.Id && !IsAdmin)
throw new ApplicationException("You do not have permissions to edit this article"); throw new ApplicationException("You do not have permissions to edit this article");
if (User.Id != article.Author.Id) {
article.Reviewer = User; // If an admin edits this article, add them as reviewer
}
article.LastModified = DateTimeOffset.UtcNow; article.LastModified = DateTimeOffset.UtcNow;
var pipeline = new MarkdownPipelineBuilder() var pipeline = new MarkdownPipelineBuilder()

View file

@ -15,16 +15,33 @@
Welcome to your new app. Welcome to your new app.
<div> <div>
@foreach (Article article in Articles) { @foreach (Article article in Articles) {
<div> <div>
<a href="/article/@article.Id"> <a href="/article/@article.Id">
<p>@article.Title</p> <p>@article.Title</p>
<p>By @article.Author.Name</p> <p>By @article.Author.Name</p>
</a> </a>
</div> </div>
} }
</div> </div>
<AuthorizeView>
<Authorized>
<h3 class="text-2xl my-3">Claims</h3>
<dl>
<dt>Author?</dt>
<dd>@context.User.IsInRole("Author")</dd>
<dt>Reviewer?</dt>
<dd>@context.User.IsInRole("Reviewer")</dd>
<dt>Moderator?</dt>
<dd>@context.User.IsInRole("Moderator")</dd>
<dt>Admin?</dt>
<dd>@context.User.IsInRole("Admin")</dd>
</dl>
</Authorized>
</AuthorizeView>
@code { @code {
private List<Article> Articles { get; } = []; private List<Article> Articles { get; } = [];

View file

@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Server; using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -46,6 +45,7 @@
builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>() .AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager() .AddSignInManager()
.AddDefaultTokenProviders(); .AddDefaultTokenProviders();