Skip to content

Commit

Permalink
PT-14717: permissions for properties (#716)
Browse files Browse the repository at this point in the history
  • Loading branch information
basilkot committed Jan 5, 2024
1 parent 3aa3744 commit 1bd3744
Show file tree
Hide file tree
Showing 15 changed files with 256 additions and 48 deletions.
6 changes: 6 additions & 0 deletions src/VirtoCommerce.CatalogModule.Core/ModuleConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public static class Permissions
public const string MeasuresRead = "measures:read";
public const string MeasuresUpdate = "measures:update";
public const string MeasuresDelete = "measures:delete";
public const string CatalogMetadataPropertyEdit = "catalog:metadata-property:edit";
public const string CatalogCustomPropertyEdit = "catalog:custom-property:edit";
public const string CatalogDictionaryPropertyEdit = "catalog:dictionary-property:edit";

public static string[] AllPermissions { get; } =
{
Expand All @@ -49,6 +52,9 @@ public static class Permissions
MeasuresRead,
MeasuresCreate,
MeasuresUpdate, MeasuresDelete,
CatalogMetadataPropertyEdit,
CatalogCustomPropertyEdit,
CatalogDictionaryPropertyEdit,
};
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using VirtoCommerce.CatalogModule.Core;
using VirtoCommerce.Platform.Security.Authorization;

namespace VirtoCommerce.CatalogModule.Data.Authorization
{
public class CustomPropertyRequirement : PermissionAuthorizationRequirement
{
public CustomPropertyRequirement() : base(ModuleConstants.Security.Permissions.CatalogCustomPropertyEdit)
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ protected override IQueryable<ItemEntity> BuildQuery(IRepository repository, Pro
query = query.Where(x => x.Name.Contains(criteria.Keyword));
}

if (!criteria.ObjectIds.IsNullOrEmpty())
{
query = query.Where(x => criteria.ObjectIds.Contains(x.Id));
}

if (!criteria.CategoryIds.IsNullOrEmpty())
{
var searchCategoryIds = criteria.CategoryIds;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext
if (!context.HasSucceeded)
{
var userPermissions = context.User.FindPermissions(requirement.Permission, _jsonOptions.SerializerSettings);

if (userPermissions.Count > 0)
{
var allowedCatalogIdsList = new List<string>();
Expand Down Expand Up @@ -103,6 +104,10 @@ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext
}
context.Succeed(requirement);
}
else if (context.Resource is IEnumerable<PropertyDictionaryItem>)
{
context.Succeed(requirement);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using VirtoCommerce.CatalogModule.Core.Model;
using VirtoCommerce.CatalogModule.Core.Model.Search;
using VirtoCommerce.CatalogModule.Core.Search;
using VirtoCommerce.CatalogModule.Data.Authorization;
using VirtoCommerce.Platform.Core.Security;
using VirtoCommerce.Platform.Security.Authorization;

namespace VirtoCommerce.CatalogModule.Web.Authorization
{
public sealed class CustomPropertyRequirementHandler : PermissionAuthorizationHandlerBase<CustomPropertyRequirement>
{
private readonly IProductSearchService _productSearch;
private readonly MvcNewtonsoftJsonOptions _jsonOptions;
public CustomPropertyRequirementHandler(IOptions<MvcNewtonsoftJsonOptions> jsonOptions, IProductSearchService productSearch)
{
_productSearch = productSearch;
_jsonOptions = jsonOptions.Value;
}

protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomPropertyRequirement requirement)
{
await base.HandleRequirementAsync(context, requirement);

if (!context.HasSucceeded)
{
var userPermissions = context.User.FindPermissions(requirement.Permission, _jsonOptions.SerializerSettings);

if (userPermissions.Count != 0)
{
return;
}
switch (context.Resource)
{
case IEnumerable<CatalogProduct> products when await CustomPropertyChanged(products):
case CatalogProduct product when await CustomPropertyChanged(new[] { product }):
context.Succeed(requirement);
break;
}
}
}

private async Task<bool> CustomPropertyChanged(IEnumerable<CatalogProduct> products)
{
var searchCriteria = new ProductSearchCriteria
{
ObjectIds = products.Select(x => x.Id).ToArray(),
SearchInVariations = true,
ResponseGroup = ItemResponseGroup.WithProperties.ToString(),
Take = products.Count()
};
var sourceProducts = (await _productSearch.SearchAsync(searchCriteria)).Results;

foreach (var changedProduct in products)
{
var sourceProduct = sourceProducts.FirstOrDefault(x => x.Id == changedProduct.Id);
if (sourceProduct == null || !CustomPropertiesChanged(sourceProduct.Properties, changedProduct.Properties))
{
return false;
}
}

return true;
}

private static bool CustomPropertiesChanged(IList<Property> source, IList<Property> changed)
{
if (source.Count(x => x.Id == null) != changed.Count(x => x.Id == null))
{
return false;
}

foreach (var sourceProperty in source.Where(x => x.Id == null))
{
var changedProperty = changed.FirstOrDefault(x => x.Name == sourceProperty.Name);
if (changedProperty == null || !CustomPropertyValuesChanged(sourceProperty, changedProperty))
{
return false;
}
}

return true;
}

private static bool CustomPropertyValuesChanged(Property source, Property changed)
{
if (source.Values.Count != changed.Values.Count)
{
return false;
}

foreach (var sourceValue in source.Values)
{
var changedValue = changed.Values.FirstOrDefault(x => x.Id == sourceValue.Id);
if (changedValue == null
|| changed.Name != sourceValue.PropertyName
|| changed.ValueType != sourceValue.ValueType)
{
return false;
}
}

return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,12 @@ public async Task<ActionResult<CatalogProduct>> SaveProduct([FromBody] CatalogPr
return Forbid();
}

var customPropertyResult = await _authorizationService.AuthorizeAsync(User, product, new CustomPropertyRequirement());
if (!customPropertyResult.Succeeded)
{
return Forbid();
}

var result = (await InnerSaveProducts(new[] { product })).FirstOrDefault();
if (result != null)
{
Expand All @@ -321,6 +327,12 @@ public async Task<ActionResult> SaveProducts([FromBody] CatalogProduct[] product
return Forbid();
}

var customPropertyResult = await _authorizationService.AuthorizeAsync(User, products, new CustomPropertyRequirement());
if (!customPropertyResult.Succeeded)
{
return Forbid();
}

await InnerSaveProducts(products);
return Ok();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public async Task<ActionResult<Property>> GetNewCategoryProperty(string category
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
public async Task<ActionResult> SaveProperty([FromBody] Property property)
{
var authorizationResult = await _authorizationService.AuthorizeAsync(User, property, new CatalogAuthorizationRequirement(ModuleConstants.Security.Permissions.Update));
var authorizationResult = await _authorizationService.AuthorizeAsync(User, property, new CatalogAuthorizationRequirement(ModuleConstants.Security.Permissions.CatalogMetadataPropertyEdit));
if (!authorizationResult.Succeeded)
{
return Forbid();
Expand Down Expand Up @@ -200,7 +200,7 @@ public async Task<ActionResult> DeleteProperty(string id, bool doDeleteValues =
{
var property = await _propertyService.GetByIdsAsync(new[] { id });

var authorizationResult = await _authorizationService.AuthorizeAsync(User, property, new CatalogAuthorizationRequirement(ModuleConstants.Security.Permissions.Delete));
var authorizationResult = await _authorizationService.AuthorizeAsync(User, property, new CatalogAuthorizationRequirement(ModuleConstants.Security.Permissions.CatalogMetadataPropertyEdit));
if (!authorizationResult.Succeeded)
{
return Forbid();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ public async Task<ActionResult<PropertyDictionaryItemSearchResult>> SearchProper
[Authorize(ModuleConstants.Security.Permissions.Create)]
public async Task<ActionResult> SaveChanges([FromBody] PropertyDictionaryItem[] propertyDictItems)
{
var authorizationResult = await _authorizationService.AuthorizeAsync(User, propertyDictItems, new CatalogAuthorizationRequirement(ModuleConstants.Security.Permissions.CatalogDictionaryPropertyEdit));
if (!authorizationResult.Succeeded)
{
return Forbid();
}
await _propertyDictionaryService.SaveChangesAsync(propertyDictItems.ToList());
return Ok();
}
Expand All @@ -68,6 +73,12 @@ public async Task<ActionResult> SaveChanges([FromBody] PropertyDictionaryItem[]
[Authorize(ModuleConstants.Security.Permissions.Delete)]
public async Task<ActionResult> DeletePropertyDictionaryItems([FromQuery] string[] ids)
{
var criteria = new PropertyDictionaryItemSearchCriteria { PropertyIds = ids };
var authorizationResult = await _authorizationService.AuthorizeAsync(User, criteria, new CatalogAuthorizationRequirement(ModuleConstants.Security.Permissions.CatalogDictionaryPropertyEdit));
if (!authorizationResult.Succeeded)
{
return Forbid();
}
await _propertyDictionaryService.DeleteAsync(ids.ToList());
return Ok();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1047,7 +1047,10 @@
"measures:create": "Create unit of measure related data",
"measures:read": "View unit of measure related data",
"measures:update": "Update unit of measure related data",
"measures:delete": "Delete unit of measure related data"
"measures:delete": "Delete unit of measure related data",
"catalog:metadata-property:edit": "Edit metadata of properties",
"catalog:custom-property:edit": "Edit product's custom properties",
"catalog:dictionary-property:edit": "Edit dictionary values of property"
},
"settings": {
"Catalog": {
Expand Down
1 change: 1 addition & 0 deletions src/VirtoCommerce.CatalogModule.Web/Module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ public void Initialize(IServiceCollection serviceCollection)
serviceCollection.AddTransient<CategorySearchRequestBuilder>();

serviceCollection.AddTransient<IAuthorizationHandler, CatalogAuthorizationHandler>();
serviceCollection.AddTransient<IAuthorizationHandler, CustomPropertyRequirementHandler>();

serviceCollection.AddTransient<ICatalogExportPagedDataSourceFactory, CatalogExportPagedDataSourceFactory>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ angular.module('virtoCommerce.catalogModule')
dialogService.showDialog(dialog, 'Modules/$(VirtoCommerce.Catalog)/Scripts/dialogs/deleteProperty-dialog.tpl.html', 'platformWebApp.confirmDialogController');
}

var hasEditDictionaryPermission = bladeNavigationService.checkPermission('catalog:dictionary-property:edit');

blade.canEditDictionary = function () {
return blade.currentEntity && blade.currentEntity.dictionary && !blade.currentEntity.isNew && hasEditDictionaryPermission;
}

blade.onClose = function (closeCallback) {
bladeNavigationService.showConfirmationIfNeeded(isDirty(), canSave(), blade, saveChanges, closeCallback, "catalog.dialogs.property-save.title", "catalog.dialogs.property-save.message");
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
<span class="list-t">{{ 'catalog.blades.property-validationRule.title'|translate }}</span>
<span class="list-descr">{{ 'catalog.blades.property-validationRule.subtitle'|translate }}</span>
</li>
<li class="list-item" ng-class="{'__selected': currentChild === 'dict', '__disabled': !blade.currentEntity.dictionary || blade.currentEntity.isNew }" ng-click='openChild("dict")'>
<li class="list-item" ng-class="{'__selected': currentChild === 'dict', '__disabled': !blade.canEditDictionary() }" ng-click='openChild("dict")'>
<span class="list-t">{{ 'catalog.blades.property-detail.labels.dictionary' | translate }}</span>
<span class="list-descr">{{ 'catalog.blades.property-detail.labels.manage-dictionary' | translate }}</span>
<span ng-if="blade.currentEntity.isNew" class="list-descr">{{ 'catalog.blades.property-detail.labels.new-dictionary-property-caution' | translate }}</span>
Expand Down
Loading

0 comments on commit 1bd3744

Please sign in to comment.