Skip to content

Commit 22cf843

Browse files
committed
merge(feat): database sink
2 parents da794cc + 23f1d38 commit 22cf843

24 files changed

+151
-95
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ GZ::CTF 是一个基于 ASP.NET Core 的开源 CTF 平台。
9999
- **湖南衡阳师范学院玄天网安实验室招新赛 HYNUCTF 2023**
100100
- **南阳师范学院招新赛 NYNUCTF S4**
101101
- **商丘师范学院首届网络安全新生挑战赛**
102+
- **苏州市职业大学 2023 年冬季新生赛 [SVUCTF-WINTER-2023](https://github.com/SVUCTF/SVUCTF-WINTER-2023)**
102103

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

docs/pages/thanks.zh.mdx

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
- **湖南衡阳师范学院玄天网安实验室招新赛 HYNUCTF 2023** (题目数量: 88, 参赛人数: 90+, 时长: 三周)
2424
- **南阳师范学院招新赛 NYNUCTF S4** (题目数量: 50, 参赛人数: 121, 时长: 9天)
2525
- **商丘师范学院首届网络安全新生赛 SQNU-TYCTF** (题目数量: 55, 参赛人数: 200+, 时长: 48小时)
26+
- **苏州市职业大学 2023 年冬季新生赛 SVUCTF-WINTER-2023** (题目数量: 20, 参数人数: 16, 时长: 一周)
2627

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

src/GZCTF/Controllers/GameController.cs

+15-12
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,8 @@ public async Task<IActionResult> CreateContainer([FromRoute] int id, [FromRoute]
916916

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

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

930931
return await gameInstanceRepository.CreateContainer(instance, context.Participation!.Team, context.User!,
931932
context.Game!.ContainerCountLimit, token) switch
932-
{
933-
null or (TaskStatus.Failed, null) => BadRequest(
934-
new RequestResponse(localizer[nameof(Resources.Program.Game_ContainerCreationFailed)])),
935-
(TaskStatus.Denied, null) => BadRequest(
936-
new RequestResponse(localizer[nameof(Resources.Program.Game_ContainerNumberLimitExceeded),
937-
context.Game.ContainerCountLimit])),
938-
(TaskStatus.Success, var x) => Ok(ContainerInfoModel.FromContainer(x!)),
939-
_ => throw new UnreachableException()
940-
};
933+
{
934+
null or (TaskStatus.Failed, null) => BadRequest(
935+
new RequestResponse(localizer[nameof(Resources.Program.Game_ContainerCreationFailed)])),
936+
(TaskStatus.Denied, null) => BadRequest(
937+
new RequestResponse(localizer[nameof(Resources.Program.Game_ContainerNumberLimitExceeded),
938+
context.Game.ContainerCountLimit])),
939+
(TaskStatus.Success, var x) => Ok(ContainerInfoModel.FromContainer(x!)),
940+
_ => throw new UnreachableException()
941+
};
941942
}
942943

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

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

10351037
var destroyId = instance.Container.ContainerId;
10361038

@@ -1064,7 +1066,8 @@ async Task<ContextInfo> GetContextInfo(int id, int challengeId = 0, bool withFla
10641066
{
10651067
ContextInfo res = new()
10661068
{
1067-
User = await userManager.GetUserAsync(User), Game = await gameRepository.GetGameById(id, token)
1069+
User = await userManager.GetUserAsync(User),
1070+
Game = await gameRepository.GetGameById(id, token)
10681071
};
10691072

10701073
if (res.Game is null)

src/GZCTF/Controllers/ProxyController.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ async Task<IActionResult> DoContainerProxy(Guid id, IPEndPoint client, IPEndPoin
173173
TaskStatus.Failed, LogLevel.Debug);
174174
return new JsonResult(new RequestResponse(
175175
localizer[nameof(Resources.Program.Proxy_ContainerConnectionFailed), e.SocketErrorCode],
176-
StatusCodes.Status418ImATeapot)) { StatusCode = StatusCodes.Status418ImATeapot };
176+
StatusCodes.Status418ImATeapot))
177+
{ StatusCode = StatusCodes.Status418ImATeapot };
177178
}
178179

179180
using WebSocket ws = await HttpContext.WebSockets.AcceptWebSocketAsync();

src/GZCTF/Controllers/TeamController.cs

+14-7
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ public async Task<IActionResult> UpdateTeam([FromRoute] int id, [FromBody] TeamU
143143

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

148149
team.UpdateInfo(model);
149150

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

183184
if (team.CaptainId != user!.Id)
184185
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
185-
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
186+
StatusCodes.Status403Forbidden))
187+
{ StatusCode = StatusCodes.Status403Forbidden };
186188

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

231233
if (team.CaptainId != user!.Id)
232234
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
233-
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
235+
StatusCodes.Status403Forbidden))
236+
{ StatusCode = StatusCodes.Status403Forbidden };
234237

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

264267
if (team.CaptainId != user!.Id)
265268
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
266-
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
269+
StatusCodes.Status403Forbidden))
270+
{ StatusCode = StatusCodes.Status403Forbidden };
267271

268272
team.UpdateInviteToken();
269273

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

302306
if (team.CaptainId != user!.Id)
303307
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
304-
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
308+
StatusCodes.Status403Forbidden))
309+
{ StatusCode = StatusCodes.Status403Forbidden };
305310

306311
IDbContextTransaction trans = await teamRepository.BeginTransactionAsync(token);
307312

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

488493
if (team.CaptainId != user!.Id)
489494
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
490-
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
495+
StatusCodes.Status403Forbidden))
496+
{ StatusCode = StatusCodes.Status403Forbidden };
491497

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

540546
if (team.CaptainId != user!.Id)
541547
return new JsonResult(new RequestResponse(localizer[nameof(Resources.Program.Auth_AccessForbidden)],
542-
StatusCodes.Status403Forbidden)) { StatusCode = StatusCodes.Status403Forbidden };
548+
StatusCodes.Status403Forbidden))
549+
{ StatusCode = StatusCodes.Status403Forbidden };
543550

544551
if (team.Locked && await teamRepository.AnyActiveGame(team, token))
545552
return BadRequest(new RequestResponse(localizer[nameof(Resources.Program.Team_Locked)]));

src/GZCTF/Extensions/CaptchaExtension.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ public override async Task<bool> VerifyAsync(ModelWithCaptcha model, HttpContext
8484

8585
TurnstileRequestModel req = new()
8686
{
87-
Secret = Config.SecretKey, Response = model.Challenge, RemoteIp = ip.ToString()
87+
Secret = Config.SecretKey,
88+
Response = model.Challenge,
89+
RemoteIp = ip.ToString()
8890
};
8991

9092
const string api = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
+54-23
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Concurrent;
12
using Serilog;
23
using Serilog.Configuration;
34
using Serilog.Core;
@@ -12,15 +13,32 @@ public static LoggerConfiguration Database(this LoggerSinkConfiguration loggerCo
1213
loggerConfiguration.Sink(new DatabaseSink(serviceProvider));
1314
}
1415

15-
public class DatabaseSink(IServiceProvider serviceProvider) : ILogEventSink
16+
public class DatabaseSink : ILogEventSink, IDisposable
1617
{
17-
static DateTimeOffset LastFlushTime = DateTimeOffset.FromUnixTimeSeconds(0);
18-
static readonly List<LogModel> LockedLogBuffer = new();
19-
static readonly List<LogModel> LogBuffer = new();
18+
readonly IServiceProvider _serviceProvider;
19+
20+
DateTimeOffset _lastFlushTime = DateTimeOffset.FromUnixTimeSeconds(0);
21+
readonly CancellationTokenSource _tokenSource = new();
22+
readonly ConcurrentQueue<LogModel> _logBuffer = [];
23+
24+
public DatabaseSink(IServiceProvider serviceProvider)
25+
{
26+
_serviceProvider = serviceProvider;
27+
Task.Factory.StartNew(
28+
() => WriteToDatabase(_tokenSource.Token),
29+
_tokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
30+
}
31+
32+
public void Dispose()
33+
{
34+
_tokenSource.Cancel();
35+
GC.SuppressFinalize(this);
36+
}
2037

2138
public void Emit(LogEvent logEvent)
2239
{
23-
if (logEvent.Level < LogEventLevel.Information) return;
40+
if (logEvent.Level < LogEventLevel.Information)
41+
return;
2442

2543
LogModel logModel = new()
2644
{
@@ -34,28 +52,41 @@ public void Emit(LogEvent logEvent)
3452
Exception = logEvent.Exception?.ToString()
3553
};
3654

37-
lock (LogBuffer)
38-
{
39-
LogBuffer.Add(logModel);
55+
_logBuffer.Enqueue(logModel);
56+
}
4057

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

44-
LockedLogBuffer.AddRange(LogBuffer);
45-
LogBuffer.Clear();
62+
try
63+
{
64+
while (!token.IsCancellationRequested)
65+
{
66+
while (_logBuffer.TryDequeue(out LogModel? logModel))
67+
lockedLogBuffer.Add(logModel);
4668

47-
Task.Run(Flush);
48-
}
49-
}
69+
if (lockedLogBuffer.Count > 50 || DateTimeOffset.Now - _lastFlushTime > TimeSpan.FromSeconds(10))
70+
{
71+
await using AsyncServiceScope scope = _serviceProvider.CreateAsyncScope();
5072

51-
async Task Flush()
52-
{
53-
using var scope = serviceProvider.CreateScope();
54-
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
55-
await dbContext.Logs.AddRangeAsync(LockedLogBuffer);
56-
await dbContext.SaveChangesAsync();
73+
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
74+
await dbContext.Logs.AddRangeAsync(lockedLogBuffer, token);
5775

58-
LockedLogBuffer.Clear();
59-
LastFlushTime = DateTimeOffset.Now;
76+
try
77+
{
78+
await dbContext.SaveChangesAsync(token);
79+
}
80+
finally
81+
{
82+
lockedLogBuffer.Clear();
83+
_lastFlushTime = DateTimeOffset.Now;
84+
}
85+
}
86+
87+
await Task.Delay(TimeSpan.FromSeconds(1), token);
88+
}
89+
}
90+
catch (TaskCanceledException) { }
6091
}
6192
}

src/GZCTF/Extensions/SignalRSinkExtension.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ public class SignalRSink(IServiceProvider serviceProvider) : ILogEventSink
2222

2323
public void Emit(LogEvent logEvent)
2424
{
25-
if (logEvent.Level < LogEventLevel.Information) return;
25+
if (logEvent.Level < LogEventLevel.Information)
26+
return;
2627

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

src/GZCTF/GZCTF.csproj

-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
<PackageReference Include="Serilog.Sinks.Async" />
5656
<PackageReference Include="Serilog.Sinks.File" />
5757
<PackageReference Include="Serilog.Sinks.File.Archive" />
58-
<PackageReference Include="Serilog.Sinks.PostgreSQL" />
5958
<PackageReference Include="MailKit" />
6059
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />
6160
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" />

src/GZCTF/Migrations/20231224074254_Initialize.Designer.cs src/GZCTF/Migrations/20231231202138_Initialize.Designer.cs

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/GZCTF/Migrations/20231224074254_Initialize.cs src/GZCTF/Migrations/20231231202138_Initialize.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
166166
TimeUtc = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
167167
Level = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
168168
Logger = table.Column<string>(type: "character varying(250)", maxLength: 250, nullable: false),
169-
RemoteIP = table.Column<string>(type: "character varying(25)", maxLength: 25, nullable: true),
169+
RemoteIP = table.Column<string>(type: "character varying(40)", maxLength: 40, nullable: true),
170170
UserName = table.Column<string>(type: "character varying(25)", maxLength: 25, nullable: true),
171171
Message = table.Column<string>(type: "text", nullable: false),
172172
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: true),

src/GZCTF/Migrations/AppDbContextModelSnapshot.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -650,8 +650,8 @@ protected override void BuildModel(ModelBuilder modelBuilder)
650650
.HasColumnType("text");
651651

652652
b.Property<string>("RemoteIP")
653-
.HasMaxLength(25)
654-
.HasColumnType("character varying(25)");
653+
.HasMaxLength(40)
654+
.HasColumnType("character varying(40)");
655655

656656
b.Property<string>("Status")
657657
.HasMaxLength(20)

src/GZCTF/Models/Data/LogModel.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public class LogModel
1818
[MaxLength(250)]
1919
public string Logger { get; set; } = string.Empty;
2020

21-
[MaxLength(25)]
21+
[MaxLength(40)]
2222
public string? RemoteIP { get; set; }
2323

2424
[MaxLength(25)]

src/GZCTF/Program.cs

+8-6
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ ___ ___ ___ ___
369369
\ \:\/:/ | |::/ \ \:\/:/ \ \:\ \ \:\
370370
\ \::/ | |:/ \ \::/ \__\/ \ \:\
371371
\__\/ |__|/ \__\/ \__\/
372-
""";
372+
""" + "\n";
373373
Console.WriteLine(banner);
374374

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

401402
errors = (from val in context.ModelState.Values
402-
where val.Errors.Count > 0
403-
select val.Errors.FirstOrDefault()?.ErrorMessage).FirstOrDefault();
403+
where val.Errors.Count > 0
404+
select val.Errors.FirstOrDefault()?.ErrorMessage).FirstOrDefault();
404405

405406
return new JsonResult(new RequestResponse(errors is [_, ..]
406407
? errors
407-
: StaticLocalizer[nameof(Resources.Program.Model_ValidationFailed)])) { StatusCode = 400 };
408+
: StaticLocalizer[nameof(Resources.Program.Model_ValidationFailed)]))
409+
{ StatusCode = 400 };
408410
}
409411
}
410-
}
412+
}

0 commit comments

Comments
 (0)