Skip to content

Commit

Permalink
merge(feat): database sink
Browse files Browse the repository at this point in the history
  • Loading branch information
GZTimeWalker committed Dec 31, 2023
2 parents da794cc + 23f1d38 commit 22cf843
Show file tree
Hide file tree
Showing 24 changed files with 151 additions and 95 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ GZ::CTF 是一个基于 ASP.NET Core 的开源 CTF 平台。
- **湖南衡阳师范学院玄天网安实验室招新赛 HYNUCTF 2023**
- **南阳师范学院招新赛 NYNUCTF S4**
- **商丘师范学院首届网络安全新生挑战赛**
- **苏州市职业大学 2023 年冬季新生赛 [SVUCTF-WINTER-2023](https://github.com/SVUCTF/SVUCTF-WINTER-2023)**

_排名不分先后,欢迎提交 PR 进行补充。_

Expand Down
1 change: 1 addition & 0 deletions docs/pages/thanks.zh.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- **湖南衡阳师范学院玄天网安实验室招新赛 HYNUCTF 2023** (题目数量: 88, 参赛人数: 90+, 时长: 三周)
- **南阳师范学院招新赛 NYNUCTF S4** (题目数量: 50, 参赛人数: 121, 时长: 9天)
- **商丘师范学院首届网络安全新生赛 SQNU-TYCTF** (题目数量: 55, 参赛人数: 200+, 时长: 48小时)
- **苏州市职业大学 2023 年冬季新生赛 SVUCTF-WINTER-2023** (题目数量: 20, 参数人数: 16, 时长: 一周)

_排名不分先后,欢迎提交 PR 进行补充。_

Expand Down
27 changes: 15 additions & 12 deletions src/GZCTF/Controllers/GameController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -916,7 +916,8 @@ public async Task<IActionResult> CreateContainer([FromRoute] int id, [FromRoute]

if (DateTimeOffset.UtcNow - instance.LastContainerOperation < TimeSpan.FromSeconds(10))
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Game_OperationTooFrequent)],
StatusCodes.Status429TooManyRequests)) { StatusCode = StatusCodes.Status429TooManyRequests };
StatusCodes.Status429TooManyRequests))
{ StatusCode = StatusCodes.Status429TooManyRequests };

if (instance.Container is not null)
{
Expand All @@ -929,15 +930,15 @@ public async Task<IActionResult> CreateContainer([FromRoute] int id, [FromRoute]

return await gameInstanceRepository.CreateContainer(instance, context.Participation!.Team, context.User!,
context.Game!.ContainerCountLimit, token) switch
{
null or (TaskStatus.Failed, null) => BadRequest(
new RequestResponse(localizer[nameof(Resources.Program.Game_ContainerCreationFailed)])),
(TaskStatus.Denied, null) => BadRequest(
new RequestResponse(localizer[nameof(Resources.Program.Game_ContainerNumberLimitExceeded),
context.Game.ContainerCountLimit])),
(TaskStatus.Success, var x) => Ok(ContainerInfoModel.FromContainer(x!)),
_ => throw new UnreachableException()
};
{
null or (TaskStatus.Failed, null) => BadRequest(
new RequestResponse(localizer[nameof(Resources.Program.Game_ContainerCreationFailed)])),
(TaskStatus.Denied, null) => BadRequest(
new RequestResponse(localizer[nameof(Resources.Program.Game_ContainerNumberLimitExceeded),
context.Game.ContainerCountLimit])),
(TaskStatus.Success, var x) => Ok(ContainerInfoModel.FromContainer(x!)),
_ => throw new UnreachableException()
};
}

/// <summary>
Expand Down Expand Up @@ -1030,7 +1031,8 @@ public async Task<IActionResult> DeleteContainer([FromRoute] int id, [FromRoute]

if (DateTimeOffset.UtcNow - instance.LastContainerOperation < TimeSpan.FromSeconds(10))
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Game_OperationTooFrequent)],
StatusCodes.Status429TooManyRequests)) { StatusCode = StatusCodes.Status429TooManyRequests };
StatusCodes.Status429TooManyRequests))
{ StatusCode = StatusCodes.Status429TooManyRequests };

var destroyId = instance.Container.ContainerId;

Expand Down Expand Up @@ -1064,7 +1066,8 @@ async Task<ContextInfo> GetContextInfo(int id, int challengeId = 0, bool withFla
{
ContextInfo res = new()
{
User = await userManager.GetUserAsync(User), Game = await gameRepository.GetGameById(id, token)
User = await userManager.GetUserAsync(User),
Game = await gameRepository.GetGameById(id, token)
};

if (res.Game is null)
Expand Down
3 changes: 2 additions & 1 deletion src/GZCTF/Controllers/ProxyController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ async Task<IActionResult> DoContainerProxy(Guid id, IPEndPoint client, IPEndPoin
TaskStatus.Failed, LogLevel.Debug);
return new JsonResult(new RequestResponse(
localizer[nameof(Resources.Program.Proxy_ContainerConnectionFailed), e.SocketErrorCode],
StatusCodes.Status418ImATeapot)) { StatusCode = StatusCodes.Status418ImATeapot };
StatusCodes.Status418ImATeapot))
{ StatusCode = StatusCodes.Status418ImATeapot };
}

using WebSocket ws = await HttpContext.WebSockets.AcceptWebSocketAsync();
Expand Down
21 changes: 14 additions & 7 deletions src/GZCTF/Controllers/TeamController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ public async Task<IActionResult> UpdateTeam([FromRoute] int id, [FromBody] TeamU

if (team.CaptainId != user!.Id)
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
StatusCodes.Status403Forbidden))
{ StatusCode = StatusCodes.Status403Forbidden };

team.UpdateInfo(model);

Expand Down Expand Up @@ -182,7 +183,8 @@ public async Task<IActionResult> Transfer([FromRoute] int id, [FromBody] TeamTra

if (team.CaptainId != user!.Id)
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
StatusCodes.Status403Forbidden))
{ StatusCode = StatusCodes.Status403Forbidden };

if (team.Locked && await teamRepository.AnyActiveGame(team, token))
return BadRequest(new RequestResponse(localizer[nameof(Resources.Program.Team_Locked)]));
Expand Down Expand Up @@ -230,7 +232,8 @@ public async Task<IActionResult> InviteCode([FromRoute] int id, CancellationToke

if (team.CaptainId != user!.Id)
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
StatusCodes.Status403Forbidden))
{ StatusCode = StatusCodes.Status403Forbidden };

return Ok(team.InviteCode);
}
Expand Down Expand Up @@ -263,7 +266,8 @@ public async Task<IActionResult> UpdateInviteToken([FromRoute] int id, Cancellat

if (team.CaptainId != user!.Id)
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
StatusCodes.Status403Forbidden))
{ StatusCode = StatusCodes.Status403Forbidden };

team.UpdateInviteToken();

Expand Down Expand Up @@ -301,7 +305,8 @@ public async Task<IActionResult> KickUser([FromRoute] int id, [FromRoute] Guid u

if (team.CaptainId != user!.Id)
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
StatusCodes.Status403Forbidden))
{ StatusCode = StatusCodes.Status403Forbidden };

IDbContextTransaction trans = await teamRepository.BeginTransactionAsync(token);

Expand Down Expand Up @@ -487,7 +492,8 @@ public async Task<IActionResult> Avatar([FromRoute] int id, IFormFile file, Canc

if (team.CaptainId != user!.Id)
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
StatusCodes.Status403Forbidden))
{ StatusCode = StatusCodes.Status403Forbidden };

if (file.Length == 0)
return BadRequest(new RequestResponse(localizer[nameof(Resources.Program.File_SizeZero)]));
Expand Down Expand Up @@ -539,7 +545,8 @@ public async Task<IActionResult> DeleteTeam(int id, CancellationToken token)

if (team.CaptainId != user!.Id)
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
StatusCodes.Status403Forbidden))
{ StatusCode = StatusCodes.Status403Forbidden };

if (team.Locked && await teamRepository.AnyActiveGame(team, token))
return BadRequest(new RequestResponse(localizer[nameof(Resources.Program.Team_Locked)]));
Expand Down
4 changes: 3 additions & 1 deletion src/GZCTF/Extensions/CaptchaExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ public override async Task<bool> VerifyAsync(ModelWithCaptcha model, HttpContext

TurnstileRequestModel req = new()
{
Secret = Config.SecretKey, Response = model.Challenge, RemoteIp = ip.ToString()
Secret = Config.SecretKey,
Response = model.Challenge,
RemoteIp = ip.ToString()
};

const string api = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
Expand Down
77 changes: 54 additions & 23 deletions src/GZCTF/Extensions/DatabaseSinkExtension.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using Serilog;
using Serilog.Configuration;
using Serilog.Core;
Expand All @@ -12,15 +13,32 @@ public static LoggerConfiguration Database(this LoggerSinkConfiguration loggerCo
loggerConfiguration.Sink(new DatabaseSink(serviceProvider));
}

public class DatabaseSink(IServiceProvider serviceProvider) : ILogEventSink
public class DatabaseSink : ILogEventSink, IDisposable
{
static DateTimeOffset LastFlushTime = DateTimeOffset.FromUnixTimeSeconds(0);
static readonly List<LogModel> LockedLogBuffer = new();
static readonly List<LogModel> LogBuffer = new();
readonly IServiceProvider _serviceProvider;

DateTimeOffset _lastFlushTime = DateTimeOffset.FromUnixTimeSeconds(0);
readonly CancellationTokenSource _tokenSource = new();
readonly ConcurrentQueue<LogModel> _logBuffer = [];

public DatabaseSink(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
Task.Factory.StartNew(
() => WriteToDatabase(_tokenSource.Token),
_tokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}

public void Dispose()
{
_tokenSource.Cancel();
GC.SuppressFinalize(this);
}

public void Emit(LogEvent logEvent)
{
if (logEvent.Level < LogEventLevel.Information) return;
if (logEvent.Level < LogEventLevel.Information)
return;

LogModel logModel = new()
{
Expand All @@ -34,28 +52,41 @@ public void Emit(LogEvent logEvent)
Exception = logEvent.Exception?.ToString()
};

lock (LogBuffer)
{
LogBuffer.Add(logModel);
_logBuffer.Enqueue(logModel);
}

var needFlush = DateTimeOffset.Now - LastFlushTime > TimeSpan.FromSeconds(10);
if (!needFlush && LogBuffer.Count < 100) return;
async Task WriteToDatabase(CancellationToken token = default)
{
List<LogModel> lockedLogBuffer = [];

LockedLogBuffer.AddRange(LogBuffer);
LogBuffer.Clear();
try
{
while (!token.IsCancellationRequested)
{
while (_logBuffer.TryDequeue(out LogModel? logModel))
lockedLogBuffer.Add(logModel);

Task.Run(Flush);
}
}
if (lockedLogBuffer.Count > 50 || DateTimeOffset.Now - _lastFlushTime > TimeSpan.FromSeconds(10))
{
await using AsyncServiceScope scope = _serviceProvider.CreateAsyncScope();

async Task Flush()
{
using var scope = serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await dbContext.Logs.AddRangeAsync(LockedLogBuffer);
await dbContext.SaveChangesAsync();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await dbContext.Logs.AddRangeAsync(lockedLogBuffer, token);

LockedLogBuffer.Clear();
LastFlushTime = DateTimeOffset.Now;
try
{
await dbContext.SaveChangesAsync(token);
}
finally
{
lockedLogBuffer.Clear();
_lastFlushTime = DateTimeOffset.Now;
}
}

await Task.Delay(TimeSpan.FromSeconds(1), token);
}
}
catch (TaskCanceledException) { }
}
}
3 changes: 2 additions & 1 deletion src/GZCTF/Extensions/SignalRSinkExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public class SignalRSink(IServiceProvider serviceProvider) : ILogEventSink

public void Emit(LogEvent logEvent)
{
if (logEvent.Level < LogEventLevel.Information) return;
if (logEvent.Level < LogEventLevel.Information)
return;

_hubContext ??= serviceProvider.GetRequiredService<IHubContext<AdminHub, IAdminClient>>();

Expand Down
1 change: 0 additions & 1 deletion src/GZCTF/GZCTF.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
<PackageReference Include="Serilog.Sinks.Async" />
<PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="Serilog.Sinks.File.Archive" />
<PackageReference Include="Serilog.Sinks.PostgreSQL" />
<PackageReference Include="MailKit" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" />
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
TimeUtc = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
Level = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
Logger = table.Column<string>(type: "character varying(250)", maxLength: 250, nullable: false),
RemoteIP = table.Column<string>(type: "character varying(25)", maxLength: 25, nullable: true),
RemoteIP = table.Column<string>(type: "character varying(40)", maxLength: 40, nullable: true),
UserName = table.Column<string>(type: "character varying(25)", maxLength: 25, nullable: true),
Message = table.Column<string>(type: "text", nullable: false),
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: true),
Expand Down
4 changes: 2 additions & 2 deletions src/GZCTF/Migrations/AppDbContextModelSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -650,8 +650,8 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.HasColumnType("text");

b.Property<string>("RemoteIP")
.HasMaxLength(25)
.HasColumnType("character varying(25)");
.HasMaxLength(40)
.HasColumnType("character varying(40)");

b.Property<string>("Status")
.HasMaxLength(20)
Expand Down
2 changes: 1 addition & 1 deletion src/GZCTF/Models/Data/LogModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class LogModel
[MaxLength(250)]
public string Logger { get; set; } = string.Empty;

[MaxLength(25)]
[MaxLength(40)]
public string? RemoteIP { get; set; }

[MaxLength(25)]
Expand Down
14 changes: 8 additions & 6 deletions src/GZCTF/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ ___ ___ ___ ___
\ \:\/:/ | |::/ \ \:\/:/ \ \:\ \ \:\
\ \::/ | |:/ \ \::/ \__\/ \ \:\
\__\/ |__|/ \__\/ \__\/
""";
""" + "\n";
Console.WriteLine(banner);

var versionStr = "";
Expand All @@ -396,15 +396,17 @@ public static IActionResult InvalidModelStateHandler(ActionContext context)
return new JsonResult(
new RequestResponse(errors is [_, ..]
? errors
: StaticLocalizer[nameof(Resources.Program.Model_ValidationFailed)])) { StatusCode = 400 };
: StaticLocalizer[nameof(Resources.Program.Model_ValidationFailed)]))
{ StatusCode = 400 };

errors = (from val in context.ModelState.Values
where val.Errors.Count > 0
select val.Errors.FirstOrDefault()?.ErrorMessage).FirstOrDefault();
where val.Errors.Count > 0
select val.Errors.FirstOrDefault()?.ErrorMessage).FirstOrDefault();

return new JsonResult(new RequestResponse(errors is [_, ..]
? errors
: StaticLocalizer[nameof(Resources.Program.Model_ValidationFailed)])) { StatusCode = 400 };
: StaticLocalizer[nameof(Resources.Program.Model_ValidationFailed)]))
{ StatusCode = 400 };
}
}
}
}
Loading

0 comments on commit 22cf843

Please sign in to comment.