| | | 1 | | using System.Security.Claims; |
| | | 2 | | using Microsoft.AspNetCore.Http; |
| | | 3 | | using Microsoft.Extensions.DependencyInjection; |
| | | 4 | | using OzonRepositories.Authorization; |
| | | 5 | | |
| | | 6 | | namespace 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> |
| | | 22 | | public 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; |
| | 21 | 29 | | private object?[] _args = Array.Empty<object?>(); |
| | | 30 | | |
| | 21 | 31 | | public CurrentUserScopeProvider(IHttpContextAccessor http, IServiceProvider services) |
| | | 32 | | { |
| | 21 | 33 | | _http = http; |
| | 21 | 34 | | _services = services; |
| | 21 | 35 | | } |
| | | 36 | | |
| | | 37 | | /// <summary> |
| | | 38 | | /// Вызывается из <c>UserScopeInitializerMiddleware</c> один раз за запрос. |
| | | 39 | | /// Загружает DataScope-ы текущего пользователя асинхронно и кэширует их на время запроса. |
| | | 40 | | /// </summary> |
| | | 41 | | public async Task InitializeAsync(CancellationToken ct = default) |
| | | 42 | | { |
| | 21 | 43 | | if (_initialized) return; |
| | 19 | 44 | | _initialized = true; |
| | | 45 | | |
| | 19 | 46 | | var ctx = _http.HttpContext; |
| | 21 | 47 | | if (ctx?.User?.Identity?.IsAuthenticated != true) return; |
| | | 48 | | |
| | 17 | 49 | | if (ctx.User.IsInRole(AuthorizationConstants.SuperAdminRoleName) |
| | 48 | 50 | | || ctx.User.HasClaim(c => c.Type == "app_is_super_admin" && c.Value == "true")) |
| | 2 | 51 | | return; |
| | | 52 | | |
| | 15 | 53 | | var userId = ctx.User.FindFirstValue(ClaimTypes.NameIdentifier); |
| | 17 | 54 | | if (string.IsNullOrEmpty(userId)) return; |
| | | 55 | | |
| | 13 | 56 | | var permissions = _services.GetRequiredService<IPermissionService>(); |
| | 13 | 57 | | var bundle = await permissions.GetUserPermissionsAsync(userId, ct); |
| | 14 | 58 | | if (bundle.IsSuperAdmin) return; |
| | 14 | 59 | | if (bundle.DataScopes.Count == 0) return; |
| | | 60 | | |
| | 10 | 61 | | _byEntity = bundle.DataScopes |
| | 14 | 62 | | .Where(s => !string.IsNullOrWhiteSpace(s.FilterExpression)) |
| | 12 | 63 | | .GroupBy(s => s.EntityName.ToLowerInvariant()) |
| | 42 | 64 | | .ToDictionary(g => g.Key, g => g.Select(s => s.FilterExpression).ToList()); |
| | | 65 | | |
| | 10 | 66 | | var userName = ctx.User.Identity?.Name ?? userId; |
| | 10 | 67 | | _args = new object?[] { userId, userName }; |
| | 20 | 68 | | } |
| | | 69 | | |
| | | 70 | | public string? GetFilterExpression(string entityName) |
| | | 71 | | { |
| | 32 | 72 | | if (_byEntity is null || _byEntity.Count == 0) return null; |
| | 12 | 73 | | if (!_byEntity.TryGetValue(entityName.ToLowerInvariant(), out var exprs) || exprs.Count == 0) |
| | 2 | 74 | | return null; |
| | 22 | 75 | | return string.Join(" || ", exprs.Select(e => "(" + Substitute(e) + ")")); |
| | | 76 | | } |
| | | 77 | | |
| | 12 | 78 | | public object?[] GetFilterArguments() => _args; |
| | | 79 | | |
| | | 80 | | private static string Substitute(string expression) |
| | 12 | 81 | | => expression |
| | 12 | 82 | | .Replace("@" + AuthorizationConstants.UserIdParameter, "@0") |
| | 12 | 83 | | .Replace("@" + AuthorizationConstants.UserNameParameter, "@1"); |
| | | 84 | | } |