[Fixes #7413] Refactor taxonomies and terms permissions (#7456)

Fixes #7413 

* Taxonomies:
 - AdminController: Added Actions to AdminController in order to manage Create/Edit: Only Users with Permission.CreateTaxonomy can Edit/Create a Taxonomy
 - Views/Admin/Index.cshtml: Conditional display of ActionLink Create/Edit in Admin/Index.cshtml based on Permission.CreateTaxonomy
 - Views/Admin/Index.cshtml: Conditional display of ActionLink Delete in Admin/Index.cshtml based on Permission.ManageTaxonomies
 - TaxonomyPartHandler: Adding context.Metadata.EditorRouteValues for Taxonomy
Terms:
 - Paring Action/Permission based on existing Permissions
 - Views/TermAdmin/Index.cshtml: Conditional display of ActionLink Create/Edit/Move/Delete based on right Permissions
 - TermPartHandler: Adding context.Metadata.EditorRouteValues for Taxonomy
AdminMenu:
 - The AdminMenu was shown only if a User had ManageTaxonomies Permission; In that way, Roles who have the Create/Edit/Delete Term Permission could not access Index/Edit forms: specializing permissions on Navigation grants access to users belonging to those Roles.

* Removed redundant check over SiteOwner permission
This commit is contained in:
Hermes Sbicego
2016-12-08 21:48:25 +01:00
committed by Sébastien Ros
parent 9f60edc7f5
commit bc66cdb36c
7 changed files with 247 additions and 40 deletions

View File

@@ -1,17 +1,28 @@
using Orchard.Localization;
using Orchard.Security;
using Orchard.UI.Navigation;
namespace Orchard.Taxonomies {
public class AdminMenu : INavigationProvider {
private readonly IOrchardServices _services;
public AdminMenu(IOrchardServices services) {
_services = services;
}
public Localizer T { get; set; }
public string MenuName { get { return "admin"; } }
public void GetNavigation(NavigationBuilder builder) {
builder
.AddImageSet("taxonomies")
.Add(T("Taxonomies"), "4", menu => menu
.Add(T("Manage Taxonomies"), "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.Taxonomies" }).Permission(Permissions.ManageTaxonomies))
);
.Add(T("Taxonomies"), "4", BuildMenu);
}
private void BuildMenu(NavigationItemBuilder menu) {
if (_services.Authorizer.Authorize(Permissions.MergeTerms) || _services.Authorizer.Authorize(Permissions.EditTerm) || _services.Authorizer.Authorize(Permissions.CreateTerm) || _services.Authorizer.Authorize(Permissions.DeleteTerm)) {
menu.Add(T("Manage Taxonomies"), "1.0", item => item.Action("Index", "Admin", new { area = "Orchard.Taxonomies" }));
}
}
}
}

View File

@@ -12,6 +12,14 @@ using Orchard.Taxonomies.Services;
using Orchard.UI.Navigation;
using Orchard.UI.Notify;
using Orchard.DisplayManagement;
using Orchard.ContentManagement.MetaData;
using Orchard.Data;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Contents.Settings;
using Orchard.Mvc.Html;
using Orchard.Utility.Extensions;
using Orchard.Mvc.Extensions;
using System.Web.Routing;
namespace Orchard.Taxonomies.Controllers {
@@ -19,16 +27,24 @@ namespace Orchard.Taxonomies.Controllers {
public class AdminController : Controller, IUpdateModel {
private readonly ITaxonomyService _taxonomyService;
private readonly ISiteService _siteService;
private readonly IContentDefinitionManager _contentDefinitionManager;
private readonly ITransactionManager _transactionManager;
private readonly IContentManager _contentManager;
private string contentType = "Taxonomy";
public AdminController(
IOrchardServices services,
IContentManager contentManager,
IContentDefinitionManager contentDefinitionManager,
ITransactionManager transactionManager,
ITaxonomyService taxonomyService,
ISiteService siteService,
IShapeFactory shapeFactory) {
Services = services;
_siteService = siteService;
_taxonomyService = taxonomyService;
_contentDefinitionManager = contentDefinitionManager;
_transactionManager = transactionManager;
_contentManager = contentManager;
T = NullLocalizer.Instance;
Shape = shapeFactory;
}
@@ -83,6 +99,142 @@ namespace Orchard.Taxonomies.Controllers {
return RedirectToAction("Index");
}
public ActionResult Create() {
var contentItem = _contentManager.New(contentType);
if (!Services.Authorizer.Authorize(Permissions.CreateTaxonomy, contentItem, T("Cannot create taxonomies")))
return new HttpUnauthorizedResult();
var model = _contentManager.BuildEditor(contentItem);
return View(model);
}
[HttpPost, ActionName("Create")]
[Mvc.FormValueRequired("submit.Save")]
public ActionResult CreatePOST(string returnUrl) {
return CreatePOST(returnUrl, contentItem => {
if (!contentItem.Has<IPublishingControlAspect>() && !contentItem.TypeDefinition.Settings.GetModel<ContentTypeSettings>().Draftable)
_contentManager.Publish(contentItem);
});
}
[HttpPost, ActionName("Create")]
[Mvc.FormValueRequired("submit.Publish")]
public ActionResult CreateAndPublishPOST(string returnUrl) {
// pass a dummy content to the authorization check to check for "own" variations
var dummyContent = _contentManager.New(contentType);
if (!Services.Authorizer.Authorize(Permissions.CreateTaxonomy, dummyContent, T("Couldn't create taxonomies")))
return new HttpUnauthorizedResult();
return CreatePOST(returnUrl, contentItem => _contentManager.Publish(contentItem));
}
private ActionResult CreatePOST(string returnUrl, Action<ContentItem> conditionallyPublish) {
var contentItem = _contentManager.New(contentType);
if (!Services.Authorizer.Authorize(Permissions.CreateTaxonomy, contentItem, T("Couldn't create taxonomies")))
return new HttpUnauthorizedResult();
_contentManager.Create(contentItem, VersionOptions.Draft);
var model = _contentManager.UpdateEditor(contentItem, this);
if (!ModelState.IsValid) {
_transactionManager.Cancel();
return View(model);
}
conditionallyPublish(contentItem);
Services.Notifier.Information(string.IsNullOrWhiteSpace(contentItem.TypeDefinition.DisplayName)
? T("Your content has been created.")
: T("Your {0} has been created.", contentItem.TypeDefinition.DisplayName));
if (!string.IsNullOrEmpty(returnUrl)) {
return this.RedirectLocal(returnUrl);
}
var adminRouteValues = _contentManager.GetItemMetadata(contentItem).AdminRouteValues;
return RedirectToRoute(adminRouteValues);
}
public ActionResult Edit(int id) {
var contentItem = _contentManager.Get(id, VersionOptions.Latest);
if (contentItem == null)
return HttpNotFound();
if (!Services.Authorizer.Authorize(Permissions.CreateTaxonomy, contentItem, T("Cannot edit content")))
return new HttpUnauthorizedResult();
var model = _contentManager.BuildEditor(contentItem);
return View(model);
}
[HttpPost, ActionName("Edit")]
[Mvc.FormValueRequired("submit.Save")]
public ActionResult EditPOST(int id, string returnUrl) {
return EditPOST(id, returnUrl, contentItem => {
if (!contentItem.Has<IPublishingControlAspect>() && !contentItem.TypeDefinition.Settings.GetModel<ContentTypeSettings>().Draftable)
_contentManager.Publish(contentItem);
});
}
[HttpPost, ActionName("Edit")]
[Mvc.FormValueRequired("submit.Publish")]
public ActionResult EditAndPublishPOST(int id, string returnUrl) {
var content = _contentManager.Get(id, VersionOptions.Latest);
if (content == null)
return HttpNotFound();
if (!Services.Authorizer.Authorize(Permissions.CreateTaxonomy, content, T("Couldn't publish content")))
return new HttpUnauthorizedResult();
return EditPOST(id, returnUrl, contentItem => _contentManager.Publish(contentItem));
}
private ActionResult EditPOST(int id, string returnUrl, Action<ContentItem> conditionallyPublish) {
var contentItem = _contentManager.Get(id, VersionOptions.DraftRequired);
if (contentItem == null)
return HttpNotFound();
if (!Services.Authorizer.Authorize(Permissions.CreateTaxonomy, contentItem, T("Couldn't edit content")))
return new HttpUnauthorizedResult();
string previousRoute = null;
if (contentItem.Has<IAliasAspect>()
&& !string.IsNullOrWhiteSpace(returnUrl)
&& Request.IsLocalUrl(returnUrl)
// only if the original returnUrl is the content itself
&& String.Equals(returnUrl, Url.ItemDisplayUrl(contentItem), StringComparison.OrdinalIgnoreCase)
) {
previousRoute = contentItem.As<IAliasAspect>().Path;
}
var model = _contentManager.UpdateEditor(contentItem, this);
if (!ModelState.IsValid) {
_transactionManager.Cancel();
return View("Edit", model);
}
conditionallyPublish(contentItem);
if (!string.IsNullOrWhiteSpace(returnUrl)
&& previousRoute != null
&& !String.Equals(contentItem.As<IAliasAspect>().Path, previousRoute, StringComparison.OrdinalIgnoreCase)) {
returnUrl = Url.ItemDisplayUrl(contentItem);
}
Services.Notifier.Information(string.IsNullOrWhiteSpace(contentItem.TypeDefinition.DisplayName)
? T("Your content has been saved.")
: T("Your {0} has been saved.", contentItem.TypeDefinition.DisplayName));
return this.RedirectLocal(returnUrl, () => RedirectToAction("Edit", new RouteValueDictionary { { "Id", contentItem.Id } }));
}
[HttpPost]
public ActionResult Delete(int id) {
if (!Services.Authorizer.Authorize(Permissions.ManageTaxonomies, T("Couldn't delete taxonomy")))

View File

@@ -76,7 +76,7 @@ namespace Orchard.Taxonomies.Controllers {
Services.Notifier.Information(T("No action selected."));
break;
case TermsAdminIndexBulkAction.Delete:
if (!Services.Authorizer.Authorize(Permissions.ManageTerms, T("Couldn't delete term")))
if (!Services.Authorizer.Authorize(Permissions.DeleteTerm, T("Couldn't delete term")))
return new HttpUnauthorizedResult();
if(!checkedEntries.Any()) {
@@ -93,7 +93,7 @@ namespace Orchard.Taxonomies.Controllers {
break;
case TermsAdminIndexBulkAction.Merge:
if (!Services.Authorizer.Authorize(Permissions.ManageTerms, T("Couldn't delete term")))
if (!Services.Authorizer.Authorize(Permissions.ManageTerms, T("Couldn't merge term")))
return new HttpUnauthorizedResult();
return RedirectToAction("Merge", new {
@@ -101,7 +101,7 @@ namespace Orchard.Taxonomies.Controllers {
termIds = string.Join(",", checkedEntries.Select(o => o.Id))
});
case TermsAdminIndexBulkAction.Move:
if (!Services.Authorizer.Authorize(Permissions.ManageTerms, T("Couldn't move terms")))
if (!Services.Authorizer.Authorize(Permissions.EditTerm, T("Couldn't move terms")))
return new HttpUnauthorizedResult();
return RedirectToAction("MoveTerm", new {
@@ -130,19 +130,19 @@ namespace Orchard.Taxonomies.Controllers {
return View(model);
}
return RedirectToAction("Create", new { taxonomyId, parentTermId = -1 });
return RedirectToAction("Create", new { taxonomyId, parentTermId = -1, ReturnUrl = Url.Action("Index", new { taxonomyId = taxonomyId }) });
}
[HttpPost]
public ActionResult SelectTerm(int taxonomyId, int selectedTermId) {
if (!Services.Authorizer.Authorize(Permissions.ManageTerms, T("Not allowed to select terms")))
if (!Services.Authorizer.Authorize(Permissions.CreateTerm, T("Not allowed to select terms")))
return new HttpUnauthorizedResult();
return RedirectToAction("Create", new { taxonomyId, parentTermId = selectedTermId });
return RedirectToAction("Create", new { taxonomyId, parentTermId = selectedTermId, ReturnUrl = Url.Action("Index", new { taxonomyId = taxonomyId }) });
}
public ActionResult MoveTerm(int taxonomyId, string termIds) {
if (!Services.Authorizer.Authorize(Permissions.CreateTerm, T("Not allowed to move terms")))
if (!Services.Authorizer.Authorize(Permissions.EditTerm, T("Not allowed to move terms")))
return new HttpUnauthorizedResult();
var terms = ResolveTermIds(termIds);
@@ -164,7 +164,7 @@ namespace Orchard.Taxonomies.Controllers {
[HttpPost]
public ActionResult MoveTerm(int taxonomyId, int selectedTermId, string termIds) {
if (!Services.Authorizer.Authorize(Permissions.ManageTerms, T("Not allowed to move terms")))
if (!Services.Authorizer.Authorize(Permissions.EditTerm, T("Not allowed to move terms")))
return new HttpUnauthorizedResult();
var taxonomy = _taxonomyService.GetTaxonomy(taxonomyId);
@@ -217,7 +217,7 @@ namespace Orchard.Taxonomies.Controllers {
public ActionResult Edit(int id) {
if (!Services.Authorizer.Authorize(Permissions.ManageTerms, T("Not allowed to manage terms")))
if (!Services.Authorizer.Authorize(Permissions.EditTerm, T("Not allowed to edit terms")))
return new HttpUnauthorizedResult();
var term = _taxonomyService.GetTerm(id);
@@ -230,7 +230,7 @@ namespace Orchard.Taxonomies.Controllers {
[HttpPost, ActionName("Edit")]
public ActionResult EditPost(int id) {
if (!Services.Authorizer.Authorize(Permissions.ManageTerms, T("Couldn't edit term")))
if (!Services.Authorizer.Authorize(Permissions.EditTerm, T("Couldn't edit term")))
return new HttpUnauthorizedResult();
var term = _taxonomyService.GetTerm(id);

View File

@@ -8,6 +8,7 @@ using Orchard.ContentManagement.Handlers;
using Orchard.Data;
using Orchard.Taxonomies.Settings;
using System;
using System.Web.Routing;
namespace Orchard.Taxonomies.Handlers {
public class TaxonomyPartHandler : ContentHandler {
@@ -51,5 +52,19 @@ namespace Orchard.Taxonomies.Handlers {
}
});
}
protected override void GetItemMetadata(GetContentItemMetadataContext context) {
var taxonomy = context.ContentItem.As<TaxonomyPart>();
if (taxonomy == null)
return;
context.Metadata.EditorRouteValues = new RouteValueDictionary {
{"Area", "Orchard.Taxonomies"},
{"Controller", "Admin"},
{"Action", "Edit"},
{"Id", taxonomy.Id}
};
}
}
}

View File

@@ -1,6 +1,8 @@
using Orchard.Taxonomies.Models;
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
using System.Web.Routing;
using Orchard.ContentManagement;
namespace Orchard.Taxonomies.Handlers {
public class TermPartHandler : ContentHandler {
@@ -8,5 +10,19 @@ namespace Orchard.Taxonomies.Handlers {
Filters.Add(StorageFilter.For(repository));
OnInitializing<TermPart>((context, part) => part.Selectable = true);
}
protected override void GetItemMetadata(GetContentItemMetadataContext context) {
var term = context.ContentItem.As<TermPart>();
if (term == null)
return;
context.Metadata.EditorRouteValues = new RouteValueDictionary {
{"Area", "Orchard.Taxonomies"},
{"Controller", "TermAdmin"},
{"Action", "Edit"},
{"Id", term.Id}
};
}
}
}

View File

@@ -1,4 +1,5 @@
@model TaxonomyAdminIndexViewModel
@using Orchard.Taxonomies;
@using Orchard.Taxonomies.ViewModels;
@{
@@ -16,7 +17,9 @@
</select>
<input class="button" type="submit" name="submit.BulkEdit" value="@T("Apply")" />
</fieldset>
<div class="manage">@Html.ActionLink(T("Add a taxonomy").Text, "Create", new { Area = "Contents", Id = "Taxonomy", ReturnUrl = Request.RawUrl }, new { @class = "button primaryAction" })</div>
if (Authorizer.Authorize(Permissions.CreateTaxonomy)) {
<div class="manage">@Html.ActionLink(T("Add a taxonomy").Text, "Create", new { Area = "Orchard.Taxonomies", ReturnUrl = Request.RawUrl }, new { @class = "button primaryAction" })</div>
}
<fieldset>
<table class="items" summary="@T("This is a table of the taxonomies in your application")">
<colgroup>
@@ -36,12 +39,12 @@
<tr class="@(taxonomyEntry.IsInternal ? "internal" : null)">
<td>
<input type="hidden" value="@Model.Taxonomies[taxonomyIndex].Id" name="@Html.NameOf(m => m.Taxonomies[ti].Id)" />
@if (!taxonomyEntry.IsInternal || Authorizer.Authorize(Orchard.Security.StandardPermissions.SiteOwner)) {
@if (!taxonomyEntry.IsInternal || Authorizer.Authorize(Permissions.ManageTaxonomies)) {
<input type="checkbox" value="true" name="@Html.NameOf(m => m.Taxonomies[ti].IsChecked)" />
}
</td>
<td>
@if (!taxonomyEntry.IsInternal || Authorizer.Authorize(Orchard.Security.StandardPermissions.SiteOwner)) {
@if (!taxonomyEntry.IsInternal || Authorizer.Authorize(Permissions.CreateTaxonomy)) {
@Html.ActionLink(taxonomyEntry.Name, "Index", "TermAdmin", new { taxonomyId = taxonomyEntry.Id }, new object { })
}
else {
@@ -49,15 +52,21 @@
}
</td>
<td>
@if (!taxonomyEntry.IsInternal || Authorizer.Authorize(Orchard.Security.StandardPermissions.SiteOwner)) {
@Html.ItemEditLink(T("Edit").Text, taxonomyEntry.ContentItem) <text>|</text>
@if (!taxonomyEntry.IsInternal) {
if (Authorizer.Authorize(Permissions.CreateTaxonomy)) {
@Html.ItemEditLink(T("Edit").Text, taxonomyEntry.ContentItem, new { ReturnUrl = Request.RawUrl }) <text>|</text>
}
@Html.ActionLink(T("Terms").Text, "Index", "TermAdmin", new { taxonomyId = taxonomyEntry.Id }, new object { }) <text>|</text>
@Html.ActionLink(T("Delete").Text, "Delete", new { id = taxonomyEntry.Id }, new { itemprop = "RemoveUrl UnsafeUrl" }) <text>|</text>
@Html.ActionLink(T("Import").Text, "Import", new { id = taxonomyEntry.Id }, new object { })
if (Authorizer.Authorize(Permissions.ManageTaxonomies)) {
@Html.ActionLink(T("Delete").Text, "Delete", new { id = taxonomyEntry.Id }, new { itemprop = "RemoveUrl UnsafeUrl" }) <text>|</text>
}
if (Authorizer.Authorize(Permissions.CreateTaxonomy)) {
@Html.ActionLink(T("Import").Text, "Import", new { id = taxonomyEntry.Id }, new object { })
}
}
</td>
</tr>
taxonomyIndex++;
taxonomyIndex++;
}
</table>
@Display(Model.Pager)

View File

@@ -1,6 +1,6 @@
@model TermAdminIndexViewModel
@using Orchard.Taxonomies
@using Orchard.Taxonomies.ViewModels;
@using Orchard.Taxonomies.Helpers;
@{
Style.Include("admin-taxonomy.css");
@@ -9,9 +9,9 @@
var termIndex = 0;
}
@using(Html.BeginFormAntiForgeryPost()) {
@using (Html.BeginFormAntiForgeryPost()) {
@Html.ValidationSummary()
@Html.HiddenFor(m=>m.TaxonomyId)
@Html.HiddenFor(m => m.TaxonomyId)
<fieldset class="bulk-actions">
<label for="publishActions">@T("Actions:")</label>
<select id="publishActions" name="@Html.NameOf(m => m.BulkAction)">
@@ -21,7 +21,9 @@
</select>
<input class="button" type="submit" name="submit.BulkEdit" value="@T("Apply")" />
</fieldset>
<div class="manage">@Html.ActionLink(T("Add a term").ToString(), "SelectTerm", new { taxonomyId = Model.Taxonomy.Id }, new { @class = "button primaryAction" })</div>
if (Authorizer.Authorize(Permissions.CreateTerm)) {
<div class="manage">@Html.ActionLink(T("Add a term").ToString(), "SelectTerm", new { taxonomyId = Model.Taxonomy.Id }, new { @class = "button primaryAction" })</div>
}
<fieldset>
<table class="items" summary="@T("This is a table of the terms in your application")">
<colgroup>
@@ -34,20 +36,22 @@
<th scope="col"></th>
</tr>
</thead>
@foreach ( var termEntry in Model.Terms) {
@foreach (var termEntry in Model.Terms) {
var ti = termIndex;
<tr>
<td>
<input type="hidden" value="@Model.Terms[termIndex].Id" name="@Html.NameOf(m => m.Terms[ti].Id)"/>
@* Tabs for levels *@ @for ( var i = 1; i <= termEntry.GetLevels(); i++ ) { <span class="gap">&nbsp;</span> }
<input type="checkbox" value="true" name="@Html.NameOf(m => m.Terms[ti].IsChecked)"/>
@Html.ItemDisplayLink(termEntry.Name, termEntry.ContentItem)
</td>
<td>
@Html.ItemEditLink(T("Edit").Text, termEntry.ContentItem, new { returnUrl = Url.Action("Index", "TermAdmin", new { taxonomyId = Model.Taxonomy.Id }) }) |
@Html.ActionLink(T("Move").ToString(), "MoveTerm", new { taxonomyId = Model.Taxonomy.Id, termIds = termEntry.Id })
</td>
</tr>
<tr>
<td>
<input type="hidden" value="@Model.Terms[termIndex].Id" name="@Html.NameOf(m => m.Terms[ti].Id)" />
@* Tabs for levels *@ @for (var i = 1; i <= termEntry.GetLevels(); i++) { <span class="gap">&nbsp;</span> }
<input type="checkbox" value="true" name="@Html.NameOf(m => m.Terms[ti].IsChecked)" />
@Html.ItemDisplayLink(termEntry.Name, termEntry.ContentItem)
</td>
<td>
@if (Authorizer.Authorize(Permissions.EditTerm)) {
@Html.ItemEditLink(T("Edit").Text, termEntry.ContentItem, new { returnUrl = Url.Action("Index", "TermAdmin", new { taxonomyId = Model.Taxonomy.Id }) }) <text>|</text>
@Html.ActionLink(T("Move").ToString(), "MoveTerm", new { taxonomyId = Model.Taxonomy.Id, termIds = termEntry.Id })
}
</td>
</tr>
termIndex++;
}
</table>