Improved permissions and permission validation
This commit is contained in:
parent
d825ebdc4f
commit
3b64009ada
|
@ -3,9 +3,9 @@
|
||||||
|
|
||||||
@using Wave.Data
|
@using Wave.Data
|
||||||
@using Microsoft.AspNetCore.Identity
|
@using Microsoft.AspNetCore.Identity
|
||||||
|
@using System.Security.Claims
|
||||||
|
|
||||||
@rendermode InteractiveServer
|
@rendermode @(new InteractiveServerRenderMode(false))
|
||||||
@attribute [Authorize(Policy = "ArticleEditOrReviewPermissions")]
|
|
||||||
|
|
||||||
@inject UserManager<ApplicationUser> UserManager
|
@inject UserManager<ApplicationUser> UserManager
|
||||||
@inject IStringLocalizer<ArticleEditor> Localizer
|
@inject IStringLocalizer<ArticleEditor> Localizer
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
<h1 class="text-3xl lg:text-5xl font-light mb-6 text-primary">@Localizer["EditorTitle"]</h1>
|
<h1 class="text-3xl lg:text-5xl font-light mb-6 text-primary">@Localizer["EditorTitle"]</h1>
|
||||||
|
|
||||||
<Wave.Components.Pages.Partials.ArticleEditorPartial Id="@Id" User="@User" />
|
<Wave.Components.Pages.Partials.ArticleEditorPartial Id="@Id" User="@User" ClaimsUser="@ClaimsUser" />
|
||||||
</ChildContent>
|
</ChildContent>
|
||||||
<ErrorContent>
|
<ErrorContent>
|
||||||
<h1 class="text-3xl lg:text-5xl font-light mb-6">Not found</h1>
|
<h1 class="text-3xl lg:text-5xl font-light mb-6">Not found</h1>
|
||||||
|
@ -41,10 +41,12 @@
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Guid? Id { get; set; }
|
public Guid? Id { get; set; }
|
||||||
private ApplicationUser? User { get; set; }
|
private ApplicationUser? User { get; set; }
|
||||||
|
private ClaimsPrincipal? ClaimsUser { get; set; }
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync() {
|
protected override async Task OnInitializedAsync() {
|
||||||
if (AuthenticationState is null) throw new ApplicationException("???");
|
if (AuthenticationState is null) throw new ApplicationException("???");
|
||||||
var state = await AuthenticationState;
|
var state = await AuthenticationState;
|
||||||
|
ClaimsUser = state.User;
|
||||||
var user = await UserManager.GetUserAsync(state.User);
|
var user = await UserManager.GetUserAsync(state.User);
|
||||||
User = user ?? throw new ApplicationException("???2");
|
User = user ?? throw new ApplicationException("???2");
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,7 @@
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@using Wave.Data
|
@using Wave.Data
|
||||||
@using System.Security.Claims
|
@using System.Security.Claims
|
||||||
@using System.Diagnostics.CodeAnalysis
|
|
||||||
@using System.Globalization
|
@using System.Globalization
|
||||||
@using System.Net
|
|
||||||
@using Microsoft.AspNetCore.Identity
|
@using Microsoft.AspNetCore.Identity
|
||||||
@using Microsoft.Extensions.Options
|
@using Microsoft.Extensions.Options
|
||||||
@using Wave.Services
|
@using Wave.Services
|
||||||
|
@ -54,45 +52,41 @@
|
||||||
|
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
<AuthorizeView Policy="ArticleEditOrReviewPermissions">
|
@if (GetArticle(HttpContext.User) is {} article) {
|
||||||
<Authorized>
|
<ArticleComponent Article="@article"/>
|
||||||
<ArticleComponent Article="@GetArticleProtected(context.User)" />
|
|
||||||
<div class="flex gap-2 mt-3 flex-wrap">
|
<div class="flex gap-2 mt-3 flex-wrap">
|
||||||
@if (CanEdit) {
|
@if (article.AllowedToEdit(HttpContext.User)) {
|
||||||
<a class="btn btn-info w-full sm:btn-wide" href="article/@Article!.Id/edit"
|
<a class="btn btn-info w-full sm:btn-wide" href="article/@Article!.Id/edit"
|
||||||
data-enhance-nav="false">@Localizer["Edit"]</a>
|
data-enhance-nav="false">@Localizer["Edit"]</a>
|
||||||
}
|
}
|
||||||
@if (Article.Status is ArticleStatus.Draft) {
|
@if (article.AllowedToSubmitForReview(HttpContext.User)) {
|
||||||
<form @formname="submit-for-review" method="post" @onsubmit="SubmitForReview" class="max-sm:w-full">
|
<form @formname="submit-for-review" method="post" @onsubmit="SubmitForReview" class="max-sm:w-full">
|
||||||
<AntiforgeryToken />
|
<AntiforgeryToken/>
|
||||||
<button type="submit" class="btn btn-primary w-full sm:btn-wide">@Localizer["Review_Submit"]</button>
|
<button type="submit" class="btn btn-primary w-full sm:btn-wide">@Localizer["Review_Submit"]</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
@if (Article.Status is ArticleStatus.InReview || (Article.Status is ArticleStatus.Draft && context.User.IsInRole("Admin"))) {
|
@if (article.AllowedToPublish(HttpContext.User)) {
|
||||||
<form @formname="submit-for-publish" method="post" @onsubmit="SubmitForPublish" class="max-sm:w-full">
|
<form @formname="submit-for-publish" method="post" @onsubmit="SubmitForPublish" class="max-sm:w-full">
|
||||||
<AntiforgeryToken />
|
<AntiforgeryToken/>
|
||||||
<button type="submit" class="btn btn-primary w-full sm:btn-wide">@Localizer["Publish_Submit"]</button>
|
<button type="submit" class="btn btn-primary w-full sm:btn-wide">@Localizer["Publish_Submit"]</button>
|
||||||
@if (Features.Value.EmailSubscriptions) {
|
@if (Features.Value.EmailSubscriptions && HttpContext.User.IsInRole("Admin")) {
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label cursor-pointer">
|
<label class="label cursor-pointer">
|
||||||
<span class="label-text">@Localizer["Publish_Silent_Label"]</span>
|
<span class="label-text">@Localizer["Publish_Silent_Label"]</span>
|
||||||
<InputCheckbox @bind-Value="PublishSilently" class="checkbox" />
|
<InputCheckbox @bind-Value="PublishSilently" class="checkbox"/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
<AuthorizeView Policy="ArticleDeletePermissions" Context="_">
|
@if (article.AllowedToDelete(HttpContext.User)) {
|
||||||
<a class="btn btn-error w-full sm:btn-wide" href="/article/@Article.Id/delete">
|
<a class="btn btn-error w-full sm:btn-wide" href="/article/@article.Id/delete">
|
||||||
@Localizer["Delete_Submit"]
|
@Localizer["Delete_Submit"]
|
||||||
</a>
|
</a>
|
||||||
</AuthorizeView>
|
}
|
||||||
</div>
|
</div>
|
||||||
</Authorized>
|
}
|
||||||
<NotAuthorized>
|
|
||||||
<ArticleComponent Article="@GetArticlePublic()" />
|
|
||||||
</NotAuthorized>
|
|
||||||
</AuthorizeView>
|
|
||||||
</ChildContent>
|
</ChildContent>
|
||||||
<ErrorContent>
|
<ErrorContent>
|
||||||
<h1 class="text-3xl lg:text-5xl font-light mb-6 text-primary">@Localizer["NotFound_Title"]</h1>
|
<h1 class="text-3xl lg:text-5xl font-light mb-6 text-primary">@Localizer["NotFound_Title"]</h1>
|
||||||
|
@ -135,52 +129,11 @@
|
||||||
|
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public HttpContext HttpContext { get; set; } = default!;
|
public HttpContext HttpContext { get; set; } = default!;
|
||||||
|
|
||||||
private bool CanEdit { get; set; }
|
private Article GetArticle(ClaimsPrincipal principal) {
|
||||||
|
if (Article.AllowedToRead(principal)) return Article!;
|
||||||
private Article GetArticlePublic() {
|
|
||||||
if (Article is null) throw new ApplicationException("Article not found.");
|
throw new ApplicationException("Article not found or missing permissions.");
|
||||||
if (Article.Status >= ArticleStatus.Published && Article.PublishDate <= DateTimeOffset.UtcNow) {
|
|
||||||
return Article;
|
|
||||||
}
|
|
||||||
throw new ApplicationException("Article is not public.");
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "ConvertIfStatementToSwitchStatement")]
|
|
||||||
private Article GetArticleProtected(ClaimsPrincipal principal) {
|
|
||||||
if (Article is null) throw new ApplicationException("Article not found.");
|
|
||||||
|
|
||||||
// The Article is publicly available
|
|
||||||
if (Article.Status >= ArticleStatus.Published && Article.PublishDate <= DateTimeOffset.UtcNow) {
|
|
||||||
if (principal.IsInRole("Admin"))
|
|
||||||
CanEdit = true;
|
|
||||||
return Article;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Admins always get access
|
|
||||||
if (principal.IsInRole("Admin")) {
|
|
||||||
CanEdit = true;
|
|
||||||
return Article;
|
|
||||||
}
|
|
||||||
|
|
||||||
// You can only access your own drafts
|
|
||||||
if (Article.Status is ArticleStatus.Draft) {
|
|
||||||
if (Article.Author.Id == principal.FindFirst("Id")!.Value) {
|
|
||||||
CanEdit = true;
|
|
||||||
return Article;
|
|
||||||
}
|
|
||||||
throw new ApplicationException("Cannot access draft article without being author or admin.");
|
|
||||||
}
|
|
||||||
// InReview Articles can only be accessed by reviewers
|
|
||||||
if (Article.Status is ArticleStatus.InReview) {
|
|
||||||
if (principal.IsInRole("Reviewer")) {
|
|
||||||
CanEdit = true;
|
|
||||||
return Article;
|
|
||||||
}
|
|
||||||
throw new ApplicationException("Cannot access in-review article without being a reviewer or admin.");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ApplicationException("User does not have access to this article.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnInitialized() {
|
protected override void OnInitialized() {
|
||||||
|
@ -207,10 +160,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SubmitForReview() {
|
private async Task SubmitForReview() {
|
||||||
if (Article is null) return;
|
if (Article.AllowedToSubmitForReview(HttpContext.User) is false) return;
|
||||||
|
|
||||||
await using var context = await ContextFactory.CreateDbContextAsync();
|
await using var context = await ContextFactory.CreateDbContextAsync();
|
||||||
Article.Status = ArticleStatus.InReview;
|
Article!.Status = ArticleStatus.InReview;
|
||||||
context.Update(Article);
|
context.Update(Article);
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
Message.ShowSuccess(Localizer["Submit_Review_Success"]);
|
Message.ShowSuccess(Localizer["Submit_Review_Success"]);
|
||||||
|
@ -224,6 +177,8 @@
|
||||||
await EmailService.ConnectAsync(CancellationToken.None);
|
await EmailService.ConnectAsync(CancellationToken.None);
|
||||||
|
|
||||||
foreach (var reviewer in reviewers) {
|
foreach (var reviewer in reviewers) {
|
||||||
|
if (reviewer.Id == HttpContext.User.FindFirst("Id")!.Value) continue;
|
||||||
|
|
||||||
var email = await Email.CreateDefaultEmail(
|
var email = await Email.CreateDefaultEmail(
|
||||||
reviewer.Email!,
|
reviewer.Email!,
|
||||||
reviewer.Name,
|
reviewer.Name,
|
||||||
|
@ -245,6 +200,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SubmitForPublish() {
|
private async Task SubmitForPublish() {
|
||||||
|
if (Article.AllowedToPublish(HttpContext.User) is false) return;
|
||||||
|
|
||||||
await using var context = await ContextFactory.CreateDbContextAsync();
|
await using var context = await ContextFactory.CreateDbContextAsync();
|
||||||
Article!.Status = ArticleStatus.Published;
|
Article!.Status = ArticleStatus.Published;
|
||||||
string userId = HttpContext.User.FindFirst("Id")!.Value;
|
string userId = HttpContext.User.FindFirst("Id")!.Value;
|
||||||
|
@ -269,27 +226,30 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await EmailService.ConnectAsync(CancellationToken.None);
|
|
||||||
var author = Article.Author;
|
var author = Article.Author;
|
||||||
|
|
||||||
string publishMessage =
|
if (author.Id != HttpContext.User.FindFirst("Id")!.Value) {
|
||||||
(Article.PublishDate < DateTimeOffset.Now) ?
|
await EmailService.ConnectAsync(CancellationToken.None);
|
||||||
"Is is now publicly available." :
|
|
||||||
$"It is currently scheduled for {Article.PublishDate.ToString("f", CultureInfo.GetCultureInfo("en-US"))}.";
|
|
||||||
|
|
||||||
var email = await Email.CreateDefaultEmail(
|
string publishMessage =
|
||||||
author.Email!,
|
(Article.PublishDate < DateTimeOffset.Now) ?
|
||||||
author.Name,
|
"Is is now publicly available." :
|
||||||
"Article has been approved",
|
$"It is currently scheduled for {Article.PublishDate.ToString("f", CultureInfo.GetCultureInfo("en-US"))}.";
|
||||||
"Your Article is going to be published",
|
|
||||||
$"<p>The Article '{Article.Title}' has been checked and approved by a reviewer.</p>" +
|
|
||||||
$"<p>{publishMessage}</p>",
|
|
||||||
$"The Article '{Article.Title}' has been checked and approved by a reviewer. " +
|
|
||||||
publishMessage);
|
|
||||||
// TODO check if they enabled email notifications (property currently not implemented)
|
|
||||||
await EmailService.SendEmailAsync(email);
|
|
||||||
|
|
||||||
await EmailService.DisconnectAsync(CancellationToken.None);
|
var email = await Email.CreateDefaultEmail(
|
||||||
|
author.Email!,
|
||||||
|
author.Name,
|
||||||
|
"Article has been approved",
|
||||||
|
"Your Article is going to be published",
|
||||||
|
$"<p>The Article '{Article.Title}' has been checked and approved by a reviewer.</p>" +
|
||||||
|
$"<p>{publishMessage}</p>",
|
||||||
|
$"The Article '{Article.Title}' has been checked and approved by a reviewer. " +
|
||||||
|
publishMessage);
|
||||||
|
// TODO check if they enabled email notifications (property currently not implemented)
|
||||||
|
await EmailService.SendEmailAsync(email);
|
||||||
|
|
||||||
|
await EmailService.DisconnectAsync(CancellationToken.None);
|
||||||
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.LogError(ex, "Failed to send mail to author about article '{title}' being published.", Article.Title);
|
Logger.LogError(ex, "Failed to send mail to author about article '{title}' being published.", Article.Title);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
@using System.ComponentModel.DataAnnotations
|
@using System.ComponentModel.DataAnnotations
|
||||||
@using System.Diagnostics.CodeAnalysis
|
@using System.Diagnostics.CodeAnalysis
|
||||||
@using System.Net
|
@using System.Net
|
||||||
|
@using System.Security.Claims
|
||||||
@using Microsoft.AspNetCore.Identity
|
@using Microsoft.AspNetCore.Identity
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@using Wave.Services
|
@using Wave.Services
|
||||||
|
@ -231,6 +232,9 @@
|
||||||
public Guid? Id { get; set; }
|
public Guid? Id { get; set; }
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public required ApplicationUser User { get; set; }
|
public required ApplicationUser User { get; set; }
|
||||||
|
[Parameter]
|
||||||
|
public required ClaimsPrincipal ClaimsUser { get; set; }
|
||||||
|
|
||||||
[SupplyParameterFromForm]
|
[SupplyParameterFromForm]
|
||||||
private InputModel Model { get; set; } = new();
|
private InputModel Model { get; set; } = new();
|
||||||
|
|
||||||
|
@ -261,11 +265,12 @@
|
||||||
.Include(a => a.Images)
|
.Include(a => a.Images)
|
||||||
.FirstAsync(a => a.Id == Id);
|
.FirstAsync(a => a.Id == Id);
|
||||||
if (article is null) throw new ApplicationException("Article not found.");
|
if (article is null) throw new ApplicationException("Article not found.");
|
||||||
|
|
||||||
await HandleRoles(article, User);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (article is not null) {
|
if (article is not null) {
|
||||||
|
if (!article.AllowedToEdit(ClaimsUser))
|
||||||
|
throw new ApplicationException("You are not allowed to edit this article");
|
||||||
|
|
||||||
Model.Id ??= article.Id;
|
Model.Id ??= article.Id;
|
||||||
Model.Title ??= article.Title;
|
Model.Title ??= article.Title;
|
||||||
Model.Slug ??= article.Slug;
|
Model.Slug ??= article.Slug;
|
||||||
|
@ -278,6 +283,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnValidSubmit() {
|
private async Task OnValidSubmit() {
|
||||||
|
if (Article.AllowedToEdit(ClaimsUser) is false) {
|
||||||
|
Message.ShowError("Permission denied.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Saving = true;
|
Saving = true;
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
@using Wave.Components.Account.Shared
|
@using Wave.Components.Account.Shared
|
||||||
<Router AppAssembly="@typeof(Program).Assembly">
|
<Router AppAssembly="@typeof(Program).Assembly">
|
||||||
<Found Context="routeData">
|
<Found Context="routeData">
|
||||||
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)">
|
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)">
|
||||||
<NotAuthorized>
|
<NotAuthorized>
|
||||||
<RedirectToLogin />
|
<RedirectToLogin/>
|
||||||
</NotAuthorized>
|
</NotAuthorized>
|
||||||
</AuthorizeRouteView>
|
<Authorizing>
|
||||||
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
|
<div class="flex place-content-center">
|
||||||
</Found>
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
|
</div>
|
||||||
|
</Authorizing>
|
||||||
|
</AuthorizeRouteView>
|
||||||
|
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
|
||||||
|
</Found>
|
||||||
</Router>
|
</Router>
|
||||||
|
|
120
Wave/Utilities/Permissions.cs
Normal file
120
Wave/Utilities/Permissions.cs
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Wave.Data;
|
||||||
|
|
||||||
|
namespace Wave.Utilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Central location for assessing if a user has access to an article, or may modify them in specific ways
|
||||||
|
/// </summary>
|
||||||
|
public static class Permissions {
|
||||||
|
public static bool AllowedToRead(this Article? article, ClaimsPrincipal principal) {
|
||||||
|
if (article is null || article.IsDeleted) return false;
|
||||||
|
|
||||||
|
// The Article is publicly available
|
||||||
|
if (article.Status >= ArticleStatus.Published && article.PublishDate <= DateTimeOffset.UtcNow) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admins always get access
|
||||||
|
if (principal.IsInRole("Admin")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can only access your own drafts
|
||||||
|
if (article.Status is ArticleStatus.Draft && article.Author.Id == principal.FindFirst("Id")!.Value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reviewers can see in-review articles
|
||||||
|
if (article.Status is ArticleStatus.InReview && principal.IsInRole("Reviewer")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool AllowedToEdit(this Article? article, ClaimsPrincipal principal) {
|
||||||
|
if (article is null || article.IsDeleted) return false;
|
||||||
|
|
||||||
|
// Admins always can edit articles
|
||||||
|
if (principal.IsInRole("Admin")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can edit your own draft articles
|
||||||
|
if (article.Status is ArticleStatus.Draft && article.Author.Id == principal.FindFirst("Id")!.Value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reviewers can edit in-review articles
|
||||||
|
if (article.Status is ArticleStatus.InReview && principal.IsInRole("Reviewer")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moderators can edit published/-ing articles
|
||||||
|
if (article.Status is ArticleStatus.Published && principal.IsInRole("Moderator")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool AllowedToSubmitForReview(this Article? article, ClaimsPrincipal principal) {
|
||||||
|
if (article is null || article.IsDeleted) return false;
|
||||||
|
|
||||||
|
// Draft articles can be submitted by their authors (admins can publish them anyway, no need to submit)
|
||||||
|
if (article.Status is ArticleStatus.Draft && article.Author.Id == principal.FindFirst("Id")!.Value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool AllowedToPublish(this Article? article, ClaimsPrincipal principal) {
|
||||||
|
if (article is null || article.IsDeleted) return false;
|
||||||
|
|
||||||
|
// Admins can skip review and directly publish draft articles
|
||||||
|
if (article.Status is ArticleStatus.Draft && principal.IsInRole("Admin")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admins may always review articles
|
||||||
|
if (article.Status is ArticleStatus.InReview && principal.IsInRole("Admin")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reviewers can review in-review articles, as long as they are not their own
|
||||||
|
if (article.Status is ArticleStatus.InReview && principal.IsInRole("Reviewer") &&
|
||||||
|
article.Author.Id != principal.FindFirst("Id")!.Value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool AllowedToDelete(this Article? article, ClaimsPrincipal principal) {
|
||||||
|
if (article is null || article.IsDeleted) return false;
|
||||||
|
|
||||||
|
// Admins can delete articles whenever
|
||||||
|
if (principal.IsInRole("Admin")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can delete your drafts
|
||||||
|
if (article.Status is ArticleStatus.Draft && article.Author.Id == principal.FindFirst("Id")!.Value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reviewers can reject/delete in-review articles
|
||||||
|
if (article.Status is ArticleStatus.InReview && principal.IsInRole("Reviewer")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moderators can take down articles
|
||||||
|
if (article.Status is ArticleStatus.Published && principal.IsInRole("Moderator")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue