< Summary

Information
Class: Services.Authorization.Services.CurrentUserScopeProvider
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/Authorization/Services/CurrentUserScopeProvider.cs
Line coverage
100%
Covered lines: 33
Uncovered lines: 0
Coverable lines: 33
Total lines: 84
Line coverage: 100%
Branch coverage
91%
Covered branches: 31
Total branches: 34
Branch coverage: 91.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
InitializeAsync()87.5%2424100%
GetFilterExpression(...)100%88100%
GetFilterArguments()100%11100%
Substitute(...)100%11100%

File(s)

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

#LineLine coverage
 1using System.Security.Claims;
 2using Microsoft.AspNetCore.Http;
 3using Microsoft.Extensions.DependencyInjection;
 4using OzonRepositories.Authorization;
 5
 6namespace Services.Authorization.Services;
 7
 8/// <summary>
 9/// Реализация <see cref="IUserScopeProvider"/>. Один экземпляр на HTTP-запрос.
 10/// <para>
 11/// Инициализируется асинхронно через <see cref="InitializeAsync"/> в middleware
 12/// (<c>UserScopeInitializerMiddleware</c>) — ДО того как контроллер запросит DbContext.
 13/// После инициализации <see cref="GetFilterExpression"/> и <see cref="GetFilterArguments"/>
 14/// работают синхронно из закэшированных данных без обращения к БД.
 15/// </para>
 16/// <para>
 17/// <see cref="IPermissionService"/> разрешается лениво через <see cref="IServiceProvider"/>,
 18/// чтобы разорвать цикл DI:
 19/// ApplicationDbContext → IUserScopeProvider → IPermissionService → ApplicationDbContext.
 20/// </para>
 21/// </summary>
 22public sealed class CurrentUserScopeProvider : IUserScopeProvider
 23{
 24    private readonly IHttpContextAccessor _http;
 25    private readonly IServiceProvider _services;
 26
 27    private bool _initialized;
 28    private Dictionary<string, List<string>>? _byEntity;
 2129    private object?[] _args = Array.Empty<object?>();
 30
 2131    public CurrentUserScopeProvider(IHttpContextAccessor http, IServiceProvider services)
 32    {
 2133        _http = http;
 2134        _services = services;
 2135    }
 36
 37    /// <summary>
 38    /// Вызывается из <c>UserScopeInitializerMiddleware</c> один раз за запрос.
 39    /// Загружает DataScope-ы текущего пользователя асинхронно и кэширует их на время запроса.
 40    /// </summary>
 41    public async Task InitializeAsync(CancellationToken ct = default)
 42    {
 2143        if (_initialized) return;
 1944        _initialized = true;
 45
 1946        var ctx = _http.HttpContext;
 2147        if (ctx?.User?.Identity?.IsAuthenticated != true) return;
 48
 1749        if (ctx.User.IsInRole(AuthorizationConstants.SuperAdminRoleName)
 4850            || ctx.User.HasClaim(c => c.Type == "app_is_super_admin" && c.Value == "true"))
 251            return;
 52
 1553        var userId = ctx.User.FindFirstValue(ClaimTypes.NameIdentifier);
 1754        if (string.IsNullOrEmpty(userId)) return;
 55
 1356        var permissions = _services.GetRequiredService<IPermissionService>();
 1357        var bundle = await permissions.GetUserPermissionsAsync(userId, ct);
 1458        if (bundle.IsSuperAdmin) return;
 1459        if (bundle.DataScopes.Count == 0) return;
 60
 1061        _byEntity = bundle.DataScopes
 1462            .Where(s => !string.IsNullOrWhiteSpace(s.FilterExpression))
 1263            .GroupBy(s => s.EntityName.ToLowerInvariant())
 4264            .ToDictionary(g => g.Key, g => g.Select(s => s.FilterExpression).ToList());
 65
 1066        var userName = ctx.User.Identity?.Name ?? userId;
 1067        _args = new object?[] { userId, userName };
 2068    }
 69
 70    public string? GetFilterExpression(string entityName)
 71    {
 3272        if (_byEntity is null || _byEntity.Count == 0) return null;
 1273        if (!_byEntity.TryGetValue(entityName.ToLowerInvariant(), out var exprs) || exprs.Count == 0)
 274            return null;
 2275        return string.Join(" || ", exprs.Select(e => "(" + Substitute(e) + ")"));
 76    }
 77
 1278    public object?[] GetFilterArguments() => _args;
 79
 80    private static string Substitute(string expression)
 1281        => expression
 1282            .Replace("@" + AuthorizationConstants.UserIdParameter, "@0")
 1283            .Replace("@" + AuthorizationConstants.UserNameParameter, "@1");
 84}