< Summary

Information
Class: OzonOrdersWeb.Api.OzonHandoverApiKeyFilter
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Api/OzonHandoverApiKeyFilter.cs
Line coverage
95%
Covered lines: 19
Uncovered lines: 1
Coverable lines: 20
Total lines: 57
Line coverage: 95%
Branch coverage
90%
Covered branches: 9
Total branches: 10
Branch coverage: 90%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
OnActionExecuting(...)90%1010100%
OnActionExecuted(...)100%210%
FixedTimeEquals(...)100%11100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Api/OzonHandoverApiKeyFilter.cs

#LineLine coverage
 1using Microsoft.AspNetCore.Mvc;
 2using Microsoft.AspNetCore.Mvc.Filters;
 3using Microsoft.Extensions.Options;
 4
 5namespace OzonOrdersWeb.Api;
 6
 7/// <summary>
 8/// Action-фильтр, ограничивающий доступ к endpoint'у импорта выдачи Ozon значением заголовка
 9/// X-Api-Key, настроенным в OzonHandover:ApiKey. Endpoint вызывается внешним приложением
 10/// ("Telegram Lite") без пользовательской сессии, поэтому cookie-авторизация не подходит —
 11/// по аналогии с OzonIpWhitelistFilter для Ozon-вебхука.
 12/// </summary>
 13public sealed class OzonHandoverApiKeyFilter : IActionFilter
 14{
 15    private const string HeaderName = "X-Api-Key";
 16
 17    private readonly ILogger<OzonHandoverApiKeyFilter> _logger;
 18    private readonly OzonHandoverOptions _options;
 19
 320    public OzonHandoverApiKeyFilter(ILogger<OzonHandoverApiKeyFilter> logger, IOptions<OzonHandoverOptions> options)
 21    {
 322        _logger = logger;
 323        _options = options.Value;
 324    }
 25
 26    public void OnActionExecuting(ActionExecutingContext context)
 27    {
 328        var configuredKey = _options.ApiKey;
 329        var providedKey = context.HttpContext.Request.Headers[HeaderName].ToString();
 30
 331        if (string.IsNullOrEmpty(configuredKey) ||
 332            string.IsNullOrEmpty(providedKey) ||
 333            !FixedTimeEquals(configuredKey, providedKey))
 34        {
 235            var remoteIp = context.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
 236            _logger.LogInformation("Ozon handover import: запрос отклонён — неверный или отсутствующий {Header} (IP: {Ip
 37
 238            context.Result = new JsonResult(new { error = "unauthorized" })
 239            {
 240                StatusCode = StatusCodes.Status401Unauthorized
 241            };
 42        }
 343    }
 44
 45    public void OnActionExecuted(ActionExecutedContext context)
 46    {
 47        // Действий после выполнения не требуется
 048    }
 49
 50    /// <summary>Сравнение без утечки по времени — ключ приходит по HTTP, не полагаемся на String.Equals.</summary>
 51    private static bool FixedTimeEquals(string a, string b)
 52    {
 253        var bytesA = System.Text.Encoding.UTF8.GetBytes(a);
 254        var bytesB = System.Text.Encoding.UTF8.GetBytes(b);
 255        return System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(bytesA, bytesB);
 56    }
 57}