forked from ddrilling/AsbCloudServer
#7582867 add UserConnectionsLimitMiddlware.
This commit is contained in:
parent
3b64968e77
commit
0d9f8b1819
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
}
|
||||
|
71
AsbCloudWebApi/Middlewares/UserConnectionsLimitMiddlware.cs
Normal file
71
AsbCloudWebApi/Middlewares/UserConnectionsLimitMiddlware.cs
Normal 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
|
||||
}
|
@ -117,7 +117,7 @@ namespace AsbCloudWebApi
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseMiddleware<PermissionsMiddlware>();
|
||||
app.UseMiddleware<PermissionsMiddlware>(Configuration);
|
||||
app.UseMiddleware<SimplifyExceptionsMiddleware>();
|
||||
app.UseMiddleware<RequerstTrackerMiddleware>();
|
||||
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user