Implemented Permissions on ArticleEditor
This commit is contained in:
parent
424cb19b54
commit
4a6599d165
|
@ -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()
|
||||||
|
|
|
@ -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; } = [];
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in a new issue