about summary refs log tree commit diff
path: root/MatrixAntiDmSpam/PolicyExecutor.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MatrixAntiDmSpam/PolicyExecutor.cs')
-rw-r--r--MatrixAntiDmSpam/PolicyExecutor.cs150
1 files changed, 65 insertions, 85 deletions
diff --git a/MatrixAntiDmSpam/PolicyExecutor.cs b/MatrixAntiDmSpam/PolicyExecutor.cs

index a6ca404..65e7f9f 100644 --- a/MatrixAntiDmSpam/PolicyExecutor.cs +++ b/MatrixAntiDmSpam/PolicyExecutor.cs
@@ -1,112 +1,92 @@ +using System.Diagnostics; +using ArcaneLibs.Attributes; using ArcaneLibs.Extensions; using LibMatrix.EventTypes.Spec.State.Policy; -using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Helpers; using LibMatrix.Homeservers; using LibMatrix.RoomTypes; -using LibMatrix.Utilities.Bot.Services; +using LibMatrix.Utilities.Bot.Interfaces; namespace MatrixAntiDmSpam; public class PolicyExecutor( ILogger<PolicyExecutor> logger, AntiDmSpamConfiguration config, - InviteStore inviteStore, + RoomInviteHandler roomInviteHandler, PolicyStore policyStore, AuthenticatedHomeserverGeneric homeserver) : IHostedService { - private GenericRoom? _logRoom = string.IsNullOrWhiteSpace(config.LogRoom) ? null : homeserver.GetRoom(config.LogRoom); + private readonly GenericRoom? _logRoom = string.IsNullOrWhiteSpace(config.LogRoom) ? null : homeserver.GetRoom(config.LogRoom); - public async Task StartAsync(CancellationToken cancellationToken) { - inviteStore.OnInviteReceived.Add(CheckPoliciesAgainstInvite); - policyStore.OnPolicyUpdated.Add(CheckPolicyAgainstInvites); + public Task StartAsync(CancellationToken cancellationToken) { + roomInviteHandler.OnInviteReceived.Add(CheckPoliciesAgainstInvite); + policyStore.OnPolicyUpdated.Add(CheckPolicyAgainstOutstandingInvites); + return Task.CompletedTask; } - public async Task StopAsync(CancellationToken cancellationToken) { } - - public async Task CheckPoliciesAgainstInvite(InviteHandlerHostedService.InviteEventArgs invite) { - string roomName = await GetRoomNameAsync(invite); - - var roomPolicy = policyStore.RoomPolicies.Any(x => x.Value.EntityMatches(invite.RoomId)) - ? policyStore.RoomPolicies.First(x => x.Value.EntityMatches(invite.RoomId)).Value - : null; - - if (roomPolicy is not null) { - logger.LogWarning("Rejecting invite to {}, matching room policy {}", invite.RoomId, roomPolicy.ToJson(ignoreNull: true)); - - var message = new MessageBuilder() - .WithColoredBody("#FF0000", cb => cb.WithBody("Rejecting invite to ").WithMention(invite.RoomId, roomName).WithBody(", matching room policy.").WithNewline()) - .WithCollapsibleSection("Policy JSON", cb => cb.WithCodeBlock(roomPolicy.ToJson(ignoreNull: true), "json")) - .Build(); - - if (_logRoom is not null) - await _logRoom.SendMessageEventAsync(message); - - await homeserver.GetRoom(invite.RoomId).LeaveAsync(); - } - - var userPolicy = policyStore.UserPolicies.Any(x => x.Value.EntityMatches(invite.MemberEvent.Sender!)) - ? policyStore.UserPolicies.First(x => x.Value.EntityMatches(invite.MemberEvent.Sender!)).Value - : null; - - if (userPolicy is not null) { - logger.LogWarning("Rejecting invite to {}, matching user policy {}", invite.RoomId, userPolicy.ToJson(ignoreNull: true)); - - var message = new MessageBuilder() - .WithColoredBody("#FF0000", cb => cb.WithBody("Rejecting invite to ").WithMention(invite.RoomId, roomName).WithBody(", matching user policy.").WithNewline()) - .WithCollapsibleSection("Policy JSON", cb => cb.WithCodeBlock(userPolicy.ToJson(ignoreNull: true), "json")) - .Build(); - - if (_logRoom is not null) - await _logRoom.SendMessageEventAsync(message); - - await homeserver.GetRoom(invite.RoomId).LeaveAsync(); - } + public Task StopAsync(CancellationToken cancellationToken) { + return Task.CompletedTask; } - public async Task CheckPolicyAgainstInvites(PolicyRuleEventContent policy) { - Console.WriteLine("CheckPolicyAgainstInvites called!!!!"); - } + private Task CheckPoliciesAgainstInvite(RoomInviteContext invite) { + logger.LogInformation("Checking policies against invite"); + var sw = Stopwatch.StartNew(); - private async Task<string> GetRoomNameAsync(InviteHandlerHostedService.InviteEventArgs invite) { - // try to get room name from invite state - var name = invite.InviteData.InviteState?.Events? - .FirstOrDefault(evt => evt is { Type: RoomNameEventContent.EventId, StateKey: "" })? - .ContentAs<RoomNameEventContent>()?.Name; + // Technically not required, but helps with scaling against millions of policies + Parallel.ForEach(policyStore.AllPolicies.Values, (policy, loopState, idx) => { + if (CheckPolicyAgainstInvite(invite, policy) is not null) { + logger.LogInformation("Found matching policy after {} iterations ({})", idx, sw.Elapsed); + loopState.Break(); + } + }); - if (!string.IsNullOrWhiteSpace(name)) - return name; - - // try to get room alias - var alias = invite.InviteData.InviteState?.Events? - .FirstOrDefault(evt => evt is { Type: RoomCanonicalAliasEventContent.EventId, StateKey: "" })? - .ContentAs<RoomCanonicalAliasEventContent>()?.Alias; + return Task.CompletedTask; + } - if (!string.IsNullOrWhiteSpace(alias)) - return alias; + private async Task CheckPolicyAgainstOutstandingInvites(PolicyRuleEventContent policy) { + var tasks = roomInviteHandler.Invites + .Select(invite => CheckPolicyAgainstInvite(invite, policy)) + .Where(x => x is not null) + .Cast<Task>() // from Task? + .ToList(); - // try get room name via public previews - try { -#pragma warning disable CS0618 // Type or member is obsolete - name = await invite.Homeserver.GetRoom(invite.RoomId).GetNameOrFallbackAsync(); -#pragma warning restore CS0618 // Type or member is obsolete - if (name != invite.RoomId && !string.IsNullOrWhiteSpace(name)) - return name; - } - catch { - //ignored - } + await Task.WhenAll(tasks); + } - // fallback to room alias via public previews - try { - alias = (await invite.Homeserver.GetRoom(invite.RoomId).GetCanonicalAliasAsync())?.Alias; - if (!string.IsNullOrWhiteSpace(alias)) - return alias; - } - catch { - //ignored + private Task? CheckPolicyAgainstInvite(RoomInviteContext invite, PolicyRuleEventContent policy) { + if (policy.Recommendation != "m.ban") return null; + + var policyMatches = false; + switch (policy) { + case UserPolicyRuleEventContent userPolicy: + policyMatches = userPolicy.EntityMatches(invite.MemberEvent.Sender!); + break; + case ServerPolicyRuleEventContent serverPolicy: + policyMatches = serverPolicy.EntityMatches(invite.MemberEvent.Sender!); + break; + case RoomPolicyRuleEventContent roomPolicy: + policyMatches = roomPolicy.EntityMatches(invite.RoomId); + break; + default: + if (_logRoom is not null) + _ = _logRoom.SendMessageEventAsync(new MessageBuilder().WithColoredBody("#FF0000", "Unknown policy type " + policy.GetType().FullName).Build()); + break; } - // fall back to room ID - return invite.RoomId; + if (!policyMatches) return null; + logger.LogWarning("Rejecting invite to {}, matching {} {}", invite.RoomId, policy.GetType().GetFriendlyName(), policy.ToJson(ignoreNull: true)); + + return Task.Run(async () => { + if (_logRoom is not null) { + string roomName = await invite.TryGetRoomNameAsync(); + + await roomInviteHandler.RejectInvite(invite, new MessageBuilder() + .WithColoredBody("#FF0000", + cb => cb.WithBody("Rejecting invite to ").WithMention(invite.RoomId, roomName) + .WithBody($", matching {policy.GetType().GetFriendlyName().ToLowerInvariant()}.") + .WithNewline()) + .WithCollapsibleSection("Policy JSON", cb => cb.WithCodeBlock(policy.ToJson(ignoreNull: true), "json")) + ); + } + }); } } \ No newline at end of file