< Summary

Information
Class: OzonOrdersWeb.Authorization.FieldPermissionSanitizerFilter
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Authorization/FieldPermissionModelBinder.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 32
Coverable lines: 32
Total lines: 66
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 26
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
OnActionExecutionAsync()0%272160%
ZeroOutForbiddenProperties(...)0%110100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Authorization/FieldPermissionModelBinder.cs

#LineLine coverage
 1using System.Security.Claims;
 2using Microsoft.AspNetCore.Mvc.Filters;
 3using Microsoft.Extensions.DependencyInjection;
 4using OzonDomains.Models.Authorization.Enums;
 5using Services.Authorization.Services;
 6
 7namespace OzonOrdersWeb.Authorization;
 8
 9/// <summary>
 10/// ActionFilter, защищающий от mass assignment: после биндинга для аргументов, помеченных [DiscoverFields],
 11/// обнуляет поля, на которые у пользователя нет CanWrite.
 12/// </summary>
 13public sealed class FieldPermissionSanitizerFilter : IAsyncActionFilter
 14{
 15    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
 16    {
 017        var http = context.HttpContext;
 018        var userId = http.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
 19
 020        if (!string.IsNullOrWhiteSpace(userId))
 21        {
 022            var perms = http.RequestServices.GetRequiredService<IPermissionService>();
 023            var logger = http.RequestServices.GetRequiredService<ILogger<FieldPermissionSanitizerFilter>>();
 024            var sanitizedByEntity = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
 25
 026            foreach (var (name, arg) in context.ActionArguments)
 27            {
 028                if (arg is null) continue;
 029                var attr = (DiscoverFieldsAttribute?)Attribute.GetCustomAttribute(arg.GetType(), typeof(DiscoverFieldsAt
 030                if (attr is null) continue;
 31
 032                if (!sanitizedByEntity.TryGetValue(attr.EntityName, out var readOnly))
 33                {
 034                    var fields = await perms.GetUserFieldPermissionsAsync(userId!, attr.EntityName, http.RequestAborted)
 35                    readOnly = fields.Where(f => !f.CanWrite).Select(f => f.FieldName).ToHashSet(StringComparer.OrdinalI
 036                    sanitizedByEntity[attr.EntityName] = readOnly;
 37                }
 038                if (readOnly.Count == 0) continue;
 39
 040                ZeroOutForbiddenProperties(arg, readOnly, logger);
 041            }
 042        }
 43
 044        await next();
 045    }
 46
 47    private static void ZeroOutForbiddenProperties(
 48        object model, HashSet<string> forbiddenNames, ILogger<FieldPermissionSanitizerFilter> logger)
 49    {
 050        var type = model.GetType();
 051        foreach (var prop in type.GetProperties().Where(p => p.CanWrite && p.CanRead))
 52        {
 053            if (!forbiddenNames.Contains(prop.Name)) continue;
 054            var pt = prop.PropertyType;
 055            object? def = pt.IsValueType && Nullable.GetUnderlyingType(pt) == null
 056                ? Activator.CreateInstance(pt)
 057                : null;
 058            try { prop.SetValue(model, def); }
 059            catch (Exception exception)
 60            {
 061                logger.LogWarning(exception, "Не удалось очистить запрещённое поле {FieldName} у модели {ModelType}",
 062                    prop.Name, type.FullName);
 063            }
 64        }
 065    }
 66}