#7582867 add UserConnectionsLimitMiddlware.

This commit is contained in:
ngfrolov 2022-11-01 17:14:19 +05:00
parent 3b64968e77
commit 0d9f8b1819
7 changed files with 191 additions and 18 deletions

View File

@ -0,0 +1,97 @@
using AsbCloudApp.Data;
using DocumentFormat.OpenXml.Drawing.Charts;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Org.BouncyCastle.Asn1.Pkcs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace AsbCloudWebApi.Tests.Middlware
{
public class UserConnectionsLimitMiddlwareTest
{
private readonly HttpClient httpClient;
public UserConnectionsLimitMiddlwareTest()
{
var host = Host.CreateDefaultBuilder(Array.Empty<string>())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.Build();
host.Start();
httpClient = new ();
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZGV2IiwiaWRDb21wYW55IjoiMSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6InJvb3QiLCJuYmYiOjE2NjY1ODY2MjAsImV4cCI6MTY5ODE0NDIyMCwiaXNzIjoiYSIsImF1ZCI6ImEifQ.zqBdR4nYB87-Xyzv025waasN47i43c9FJ23RfzIvUsM");
}
[Fact]
public async Task Send_n_requests_and_get_blocked()
{
//Данные в тестовой БД
//select
// tw.id,
// t_stat.minDate,
// t_stat.maxDate
//from(
// select
// id_telemetry,
// count(1) as count,
// min("date") as minDate,
// max("date") as maxDate
// from t_telemetry_data_saub
// group by id_telemetry
//) as t_stat
//join t_well tw on tw.id_telemetry = t_stat.id_telemetry
//where tw is not null
//order by t_stat.count
//limit 10;
var wells = new []
{
(191, new DateTime(2022, 09, 01, 21, 43, 00, DateTimeKind.Utc), new DateTime(2022, 09, 04, 07, 37, 31, DateTimeKind.Utc)),
(3 , new DateTime(2021, 09, 16, 06, 13, 33, DateTimeKind.Utc), new DateTime(2021, 09, 20, 00, 29, 28, DateTimeKind.Utc)),
(199, new DateTime(2022, 09, 15, 11, 27, 18, DateTimeKind.Utc), new DateTime(2022, 09, 20, 14, 00, 23, DateTimeKind.Utc)),
(6 , new DateTime(2021, 09, 20, 00, 35, 03, DateTimeKind.Utc), new DateTime(2021, 09, 25, 06, 46, 17, DateTimeKind.Utc)),
(41 , new DateTime(2021, 12, 10, 00, 59, 52, DateTimeKind.Utc), new DateTime(2022, 10, 31, 15, 29, 24, DateTimeKind.Utc)),
(100, new DateTime(2022, 04, 24, 03, 04, 05, DateTimeKind.Utc), new DateTime(2022, 04, 29, 11, 38, 36, DateTimeKind.Utc)),
(154, new DateTime(2022, 03, 28, 10, 09, 14, DateTimeKind.Utc), new DateTime(2022, 06, 14, 15, 01, 12, DateTimeKind.Utc)),
(5 , new DateTime(2021, 09, 25, 08, 09, 37, DateTimeKind.Utc), new DateTime(2021, 10, 01, 14, 39, 51, DateTimeKind.Utc)),
(1 , new DateTime(2021, 09, 10, 01, 32, 42, DateTimeKind.Utc), new DateTime(2021, 09, 18, 00, 35, 22, DateTimeKind.Utc)),
(112, new DateTime(2022, 04, 20, 16, 47, 51, DateTimeKind.Utc), new DateTime(2022, 04, 28, 15, 04, 33, DateTimeKind.Utc)),
};
var i = 0;
for (; i < 5; i++)
_ = Task.Run(async () =>
{
var well = wells[i];
var url = MakeUrl(well.Item1, well.Item2, well.Item3);
var response = await httpClient.GetAsync(url);
//await response.Content.ReadAsStringAsync();
await Task.Delay(1000);
});
var well = wells[i];
var url = MakeUrl(well.Item1, well.Item2, well.Item3);
var response = await httpClient.GetAsync(url);
Assert.Equal(System.Net.HttpStatusCode.TooManyRequests, response.StatusCode);
}
private static string MakeUrl(int idWell, DateTime dateBegin, DateTime dateEnd)
{
var interval = (dateEnd - dateBegin).TotalSeconds;
var dateBeginString = dateBegin.ToString("yyyy-MM-ddZ");
var url = $"http://127.0.0.1:5000/api/TelemetryDataSaub/{idWell}?begin={dateBeginString}&intervalSec={interval}&approxPointsCount={interval}";
return url;
}
}
}

View File

@ -23,7 +23,7 @@ public class EventServiceTest
cacheDb = new CacheDb();
var telemetryTracker = new Mock<ITelemetryTracker>();
var imezoneServiceMock = new Mock<ITimezoneService>();
var telemetryService = new TelemetryService(context, telemetryTracker.Object, imezoneServiceMock.Object, cacheDb);
var telemetryService = new TelemetryService(context, telemetryTracker.Object, imezoneServiceMock.Object);
service = new EventService(context, telemetryService);
}

View File

@ -42,7 +42,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
context = TestHelpter.MakeTestContext();
cacheDb = new CacheDb();
telemetryService = new TelemetryService(context, telemetryTracker.Object, timezoneService.Object, cacheDb);
telemetryService = new TelemetryService(context, telemetryTracker.Object, timezoneService.Object);
var info = new TelemetryInfoDto
{

View File

@ -2,19 +2,23 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading.Tasks;
namespace AsbCloudWebApi.Middlewares
{
#nullable enable
public class PermissionsMiddlware
{
private readonly RequestDelegate next;
private readonly UserConnectionsLimitMiddlware userConnectionsLimitMiddlware;
public PermissionsMiddlware(RequestDelegate next)
public PermissionsMiddlware(RequestDelegate next, IConfiguration configuration)
{
this.next = next;
userConnectionsLimitMiddlware = new UserConnectionsLimitMiddlware(next, configuration);
}
public async Task InvokeAsync(HttpContext context)
@ -30,53 +34,51 @@ namespace AsbCloudWebApi.Middlewares
var idUser = context.User.GetUserId();
if (idUser is null)
{
context.User = null;
await context.ForbidAsync();
return;
}
var controllerName = endpoint!.Metadata
.GetMetadata<Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor>()
?.ControllerName;
bool isAuthorized;
if (idUser == 1)
isAuthorized = true;
else
{
var permissionName = permission.Name;
if (string.IsNullOrEmpty(permissionName))
{
var controller = endpoint.Metadata
.GetMetadata<Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor>()
?.ControllerName;
var httpMethod = endpoint.Metadata
.GetMetadata<Microsoft.AspNetCore.Routing.HttpMethodMetadata>()
.HttpMethods[0]
?.HttpMethods[0]
.ToLower();
permissionName = httpMethod switch
{
"get" or "delete" => $"{controller}.{httpMethod}",
"post" or "put" or "patch" => $"{controller}.edit",
"get" or "delete" => $"{controllerName}.{httpMethod}",
"post" or "put" or "patch" => $"{controllerName}.edit",
_ => throw new NotImplementedException(),
};
PermissionAttribute.Registered.Add(permissionName);
}
else if (permissionName.Contains("[controller]"))
{
var controller = endpoint.Metadata
.GetMetadata<Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor>()
?.ControllerName;
permissionName = permissionName.Replace("[controller]", controller);
permissionName = permissionName.Replace("[controller]", controllerName);
PermissionAttribute.Registered.Add(permissionName);
}
var userService = context.RequestServices.GetRequiredService<IUserService>();
isAuthorized = userService.HasPermission((int)idUser, permissionName);
isAuthorized = userService.HasPermission(idUser!.Value, permissionName);
}
if (isAuthorized)
await next?.Invoke(context);
await userConnectionsLimitMiddlware.InvokeAsync(context, idUser!.Value, controllerName!);
else
await context.ForbidAsync();
}
}
#nullable disable
}

View File

@ -0,0 +1,71 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace AsbCloudWebApi.Middlewares
{
#nullable enable
/// <summary>
/// This is not real middleware it`s part of PermissionsMiddlware.
/// DO NOT register it in setup.cs as middleware.
/// </summary>
class UserConnectionsLimitMiddlware
{
private readonly RequestDelegate next;
private readonly int parallelRequestsToController;
private readonly byte[] body;
private readonly ConcurrentDictionary<int, ConcurrentDictionary<string, int>> stat = new ();
private readonly IEnumerable<string>? controllerNames;
public UserConnectionsLimitMiddlware(RequestDelegate next, IConfiguration configuration)
{
this.next = next;
var parallelRequestsToController = configuration.GetSection("userLimits")?.GetValue<int>("parallelRequestsToController") ?? 5;
this.parallelRequestsToController = parallelRequestsToController > 0
? parallelRequestsToController
: 5;
controllerNames = configuration.GetSection("userLimits")?.GetValue<IEnumerable<string>>("controllerNames");
var bodyText = $"<html><head><title>Too Many Requests</title></head><body><h1>Too Many Requests</h1><p>I only allow {parallelRequestsToController} parallel requests per user. Try again soon.</p></body></html>";
body = System.Text.Encoding.UTF8.GetBytes(bodyText);
}
public async Task InvokeAsync(HttpContext context, int idUser, string controllerName)
{
if(controllerNames?.Any(n => controllerName.StartsWith(n)) == false)
{
await next(context);
return;
}
var userStat = stat.GetOrAdd(idUser, idUser => new());
var count = userStat.AddOrUpdate(controllerName, 1, (key, value) => value + 1);
if(count < parallelRequestsToController)
{
try
{
await next(context);
}
finally
{
userStat[controllerName]--;
}
}
else
{
context.Response.Clear();
context.Response.StatusCode = (int)System.Net.HttpStatusCode.TooManyRequests;
context.Response.Headers.RetryAfter = "1000";
context.Response.Headers.ContentType = "text/html";
await context.Response.BodyWriter.WriteAsync(body);
}
}
}
#nullable disable
}

View File

@ -117,7 +117,7 @@ namespace AsbCloudWebApi
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<PermissionsMiddlware>();
app.UseMiddleware<PermissionsMiddlware>(Configuration);
app.UseMiddleware<SimplifyExceptionsMiddleware>();
app.UseMiddleware<RequerstTrackerMiddleware>();

View File

@ -13,6 +13,9 @@
"LocalConnection": "Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True"
},
"AllowedHosts": "*",
"userLimits": {
"parallelRequestsToController": 5
},
"email": {
"smtpServer": "smtp.timeweb.ru",
"sender": "bot@autodrilling.ru",