< Summary

Information
Class: Services.Authorization.Services.DataScopeService
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/Authorization/Services/DataScopeService.cs
Line coverage
100%
Covered lines: 38
Uncovered lines: 0
Coverable lines: 38
Total lines: 96
Line coverage: 100%
Branch coverage
100%
Covered branches: 20
Total branches: 20
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ApplyDataScopeAsync()100%1616100%
ValidateExpression(...)100%44100%
SubstitutePositional(...)100%22100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/Authorization/Services/DataScopeService.cs

#LineLine coverage
 1using System.Linq.Dynamic.Core;
 2using Microsoft.Extensions.Logging;
 3
 4namespace Services.Authorization.Services;
 5
 6/// <summary>
 7/// Применяет Dynamic LINQ фильтры из DataScope к IQueryable.
 8/// Выражения получают в контексте параметры @userId и @userName.
 9/// </summary>
 10public sealed class DataScopeService : IDataScopeService
 11{
 12    private readonly IPermissionService _permissions;
 13    private readonly ILogger<DataScopeService> _logger;
 14
 3315    public DataScopeService(IPermissionService permissions, ILogger<DataScopeService> logger)
 16    {
 3317        _permissions = permissions;
 3318        _logger = logger;
 3319    }
 20
 21    public async Task<IQueryable<T>> ApplyDataScopeAsync<T>(
 22        IQueryable<T> query,
 23        string userId,
 24        string entityName,
 25        IDictionary<string, object?>? extraParameters = null,
 26        CancellationToken ct = default)
 27    {
 2428        if (string.IsNullOrWhiteSpace(userId)) return query;
 29
 1830        var bundle = await _permissions.GetUserPermissionsAsync(userId, ct);
 1931        if (bundle.IsSuperAdmin) return query;
 32
 1733        var exprs = bundle.DataScopes
 2034            .Where(s => string.Equals(s.EntityName, entityName, StringComparison.OrdinalIgnoreCase))
 1835            .Select(s => s.FilterExpression)
 1836            .Where(s => !string.IsNullOrWhiteSpace(s))
 1737            .ToList();
 38
 2039        if (exprs.Count == 0) return query;
 40
 2941        var combined = string.Join(" OR ", exprs.Select(e => $"({e})"));
 42
 43        try
 44        {
 1445            var paramNames = new List<string> { AuthorizationConstants.UserIdParameter, AuthorizationConstants.UserNameP
 1446            var paramValues = new List<object?> { userId, userId };
 47
 1448            if (extraParameters is not null)
 49            {
 650                foreach (var kv in extraParameters)
 51                {
 252                    paramNames.Add(kv.Key);
 253                    paramValues.Add(kv.Value);
 54                }
 55            }
 56
 1457            var parsingConfig = new ParsingConfig { };
 1458            combined = SubstitutePositional(combined, paramNames);
 59
 1460            return query.Where(parsingConfig, combined, paramValues.ToArray());
 61        }
 362        catch (Exception ex)
 63        {
 364            _logger.LogError(ex, "Не удалось применить DataScope для пользователя {UserId}, сущность {Entity}: {Expr}", 
 65            // fail-closed: если фильтр сломан, возвращаем пустой результат (безопаснее чем вернуть всё)
 366            return query.Where("1 == 0");
 67        }
 2168    }
 69
 70    public string? ValidateExpression<T>(string expression)
 71    {
 1572        if (string.IsNullOrWhiteSpace(expression)) return "Выражение пустое";
 73        try
 74        {
 975            var paramNames = new[] { AuthorizationConstants.UserIdParameter, AuthorizationConstants.UserNameParameter };
 976            var substituted = SubstitutePositional(expression, paramNames.ToList());
 977            var q = Array.Empty<T>().AsQueryable();
 2778            _ = q.Where(substituted, paramNames.Select(_ => (object?)"test").ToArray());
 679            return null;
 80        }
 81        catch (Exception ex)
 82        {
 383            return ex.Message;
 84        }
 985    }
 86
 87    /// <summary>
 88    /// Преобразует «@paramName» в позиционные плейсхолдеры Dynamic LINQ («@0», «@1», …).
 89    /// </summary>
 90    private static string SubstitutePositional(string expression, IList<string> paramNames)
 91    {
 14292        for (int i = 0; i < paramNames.Count; i++)
 4893            expression = expression.Replace("@" + paramNames[i], "@" + i);
 2394        return expression;
 95    }
 96}