From 016614e69f134b9171afeaa02a6833a13e9816ff Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sat, 1 Feb 2025 03:00:11 +0000 Subject: [PATCH 1/9] Add leave command --- policyeval/commands.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/policyeval/commands.go b/policyeval/commands.go index fe98a00..534869d 100644 --- a/policyeval/commands.go +++ b/policyeval/commands.go @@ -30,6 +30,10 @@ func (pe *PolicyEvaluator) HandleCommand(ctx context.Context, evt *event.Event) zerolog.Ctx(ctx).Info().Str("command", cmd).Msg("Handling command") switch cmd { case "!join": + if len(args) == 0 { + pe.sendNotice(ctx, "Usage: `!join ...`") + return + } for _, arg := range args { _, err := pe.Bot.JoinRoom(ctx, arg, nil) if err != nil { @@ -39,6 +43,30 @@ func (pe *PolicyEvaluator) HandleCommand(ctx context.Context, evt *event.Event) } } pe.sendSuccessReaction(ctx, evt.ID) + case "!leave": + if len(args) == 0 { + pe.sendNotice(ctx, "Usage: `!leave ...`") + return + } + var target id.RoomID + if strings.HasPrefix(args[0], "#") { + rawTarget, err := pe.Bot.ResolveAlias(ctx, id.RoomAlias(args[0])) + if err != nil { + pe.sendNotice(ctx, "Failed to resolve alias %q: %v", args[0], err) + return + } + target = rawTarget.RoomID + } else { + target = id.RoomID(args[0]) + } + for _, arg := range args { + _, err := pe.Bot.LeaveRoom(ctx, target, nil) + if err != nil { + pe.sendNotice(ctx, "Failed to leave room %q: %v", arg, err) + } else { + pe.sendNotice(ctx, "Left room %q", arg) + } + } case "!redact": if len(args) < 1 { pe.sendNotice(ctx, "Usage: `!redact [reason]`") From d6efdf81de53f5e33f36b79434bfa3f6754af7c3 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sat, 1 Feb 2025 14:30:33 +0000 Subject: [PATCH 2/9] Add unban command --- policyeval/commands.go | 45 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/policyeval/commands.go b/policyeval/commands.go index 534869d..31ddfe2 100644 --- a/policyeval/commands.go +++ b/policyeval/commands.go @@ -148,6 +148,51 @@ func (pe *PolicyEvaluator) HandleCommand(ctx context.Context, evt *event.Event) Stringer("policy_event_id", resp.EventID). Msg("Sent ban policy from command") pe.sendSuccessReaction(ctx, evt.ID) + case "!unban", "!unban-user", "!unban-server": + if len(args) < 2 { + if cmd == "!unban-server" { + pe.sendNotice(ctx, "Usage: `!unban-server `") + } else { + pe.sendNotice(ctx, "Usage: `!unban `") + } + return + } + list := pe.FindListByShortcode(args[0]) + if list == nil { + pe.sendNotice(ctx, `List %q not found`, args[0]) + return + } + target := args[1] + var match policylist.Match + var entityType policylist.EntityType + if cmd == "!unban-server" { + entityType = policylist.EntityTypeServer + match = pe.Store.MatchServer(pe.GetWatchedLists(), target) + } else { + entityType = policylist.EntityTypeUser + match = pe.Store.MatchUser(pe.GetWatchedLists(), id.UserID(target)) + } + var existingStateKey string + if rec := match.Recommendations().BanOrUnban; rec != nil { + if rec.Recommendation == event.PolicyRecommendationUnban { + pe.sendNotice(ctx, "`%s` has an unban recommendation: %s", target, rec.Reason) + return + } else if rec.RoomID == list.RoomID { + existingStateKey = rec.StateKey + } + } + policy := &event.ModPolicyContent{} + resp, err := pe.SendPolicy(ctx, list.RoomID, entityType, existingStateKey, policy) + if err != nil { + pe.sendNotice(ctx, `Failed to send unban policy: %v`, err) + return + } + zerolog.Ctx(ctx).Info(). + Stringer("policy_list", list.RoomID). + Any("policy", policy). + Stringer("policy_event_id", resp.EventID). + Msg("Sent unban policy from command") + pe.sendSuccessReaction(ctx, evt.ID) case "!match": start := time.Now() match := pe.Store.MatchUser(nil, id.UserID(args[0])) From a468d3b0b8651a0ade24075d76b995305e863310 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sat, 1 Feb 2025 14:33:44 +0000 Subject: [PATCH 3/9] Explicitly send unban policy type --- policyeval/commands.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/policyeval/commands.go b/policyeval/commands.go index 31ddfe2..c377a11 100644 --- a/policyeval/commands.go +++ b/policyeval/commands.go @@ -181,7 +181,11 @@ func (pe *PolicyEvaluator) HandleCommand(ctx context.Context, evt *event.Event) existingStateKey = rec.StateKey } } - policy := &event.ModPolicyContent{} + policy := &event.ModPolicyContent{ + Entity: target, + Reason: strings.Join(args[2:], " "), + Recommendation: event.PolicyRecommendationUnban, + } resp, err := pe.SendPolicy(ctx, list.RoomID, entityType, existingStateKey, policy) if err != nil { pe.sendNotice(ctx, `Failed to send unban policy: %v`, err) From fc843623aa382bedd5ea08820d12b8c1afbec0ca Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sat, 1 Feb 2025 14:59:40 +0000 Subject: [PATCH 4/9] Only send unban policy if the user is not currently banned --- policyeval/commands.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/policyeval/commands.go b/policyeval/commands.go index c377a11..57fc1e7 100644 --- a/policyeval/commands.go +++ b/policyeval/commands.go @@ -165,6 +165,7 @@ func (pe *PolicyEvaluator) HandleCommand(ctx context.Context, evt *event.Event) target := args[1] var match policylist.Match var entityType policylist.EntityType + currentlyBanned := false if cmd == "!unban-server" { entityType = policylist.EntityTypeServer match = pe.Store.MatchServer(pe.GetWatchedLists(), target) @@ -174,6 +175,9 @@ func (pe *PolicyEvaluator) HandleCommand(ctx context.Context, evt *event.Event) } var existingStateKey string if rec := match.Recommendations().BanOrUnban; rec != nil { + if rec.Recommendation == event.PolicyRecommendationBan { + currentlyBanned = true + } if rec.Recommendation == event.PolicyRecommendationUnban { pe.sendNotice(ctx, "`%s` has an unban recommendation: %s", target, rec.Reason) return @@ -186,6 +190,9 @@ func (pe *PolicyEvaluator) HandleCommand(ctx context.Context, evt *event.Event) Reason: strings.Join(args[2:], " "), Recommendation: event.PolicyRecommendationUnban, } + if currentlyBanned { + policy = &event.ModPolicyContent{} // just remove the ban, don't prevent it + } resp, err := pe.SendPolicy(ctx, list.RoomID, entityType, existingStateKey, policy) if err != nil { pe.sendNotice(ctx, `Failed to send unban policy: %v`, err) From 1fdad86a2181f3875221d76ba07dfbde5d87c262 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sat, 1 Feb 2025 15:09:45 +0000 Subject: [PATCH 5/9] Specify which action is being taken when unbanning --- policyeval/commands.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/policyeval/commands.go b/policyeval/commands.go index 57fc1e7..5f2ad39 100644 --- a/policyeval/commands.go +++ b/policyeval/commands.go @@ -191,7 +191,10 @@ func (pe *PolicyEvaluator) HandleCommand(ctx context.Context, evt *event.Event) Recommendation: event.PolicyRecommendationUnban, } if currentlyBanned { + pe.sendNotice(ctx, `Removing the ban policy for %s`, target) policy = &event.ModPolicyContent{} // just remove the ban, don't prevent it + } else { + pe.sendNotice(ctx, `Preventing future bans for %s`, target) } resp, err := pe.SendPolicy(ctx, list.RoomID, entityType, existingStateKey, policy) if err != nil { From c88ccf28354e31e1f0c342122584eac8aafc8a12 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sat, 1 Feb 2025 15:34:58 +0000 Subject: [PATCH 6/9] Automatically unban from all rooms --- policyeval/execute.go | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/policyeval/execute.go b/policyeval/execute.go index 09689ca..9769afc 100644 --- a/policyeval/execute.go +++ b/policyeval/execute.go @@ -61,6 +61,9 @@ func (pe *PolicyEvaluator) ApplyPolicy(ctx context.Context, userID id.UserID, po // pe.sendNotice(ctx, "Database error in ApplyPolicy (GetAllByTargetUser): %v", err) // return //} + for _, room := range rooms { + pe.ApplyUnban(ctx, userID, room, recs.BanOrUnban) + } } } } @@ -108,6 +111,42 @@ func (pe *PolicyEvaluator) ApplyBan(ctx context.Context, userID id.UserID, roomI } } +func (pe *PolicyEvaluator) ApplyUnban(ctx context.Context, userID id.UserID, roomID id.RoomID, policy *policylist.Policy) { + ta := &database.TakenAction{ + TargetUser: userID, + InRoomID: roomID, + ActionType: database.TakenActionTypeBanOrUnban, + PolicyList: policy.RoomID, + RuleEntity: policy.Entity, + Action: policy.Recommendation, + TakenAt: time.Now(), + } + var err error + if !pe.DryRun { + _, err = pe.Bot.UnbanUser(ctx, roomID, &mautrix.ReqUnbanUser{ + Reason: filterReason(policy.Reason), + UserID: userID, + }) + } + if err != nil { + var respErr mautrix.HTTPError + if errors.As(err, &respErr) { + err = respErr + } + zerolog.Ctx(ctx).Err(err).Any("attempted_action", ta).Msg("Failed to unban user") + pe.sendNotice(ctx, "Failed to unban [%s](%s) in [%s](%s) for %s: %v", userID, userID.URI().MatrixToURL(), roomID, roomID.URI().MatrixToURL(), policy.Reason, err) + return + } + err = pe.DB.TakenAction.Put(ctx, ta) + if err != nil { + zerolog.Ctx(ctx).Err(err).Any("taken_action", ta).Msg("Failed to save taken action") + pe.sendNotice(ctx, "Unbanned [%s](%s) in [%s](%s) for %s, but failed to save to database: %v", userID, userID.URI().MatrixToURL(), roomID, roomID.URI().MatrixToURL(), policy.Reason, err) + } else { + zerolog.Ctx(ctx).Info().Any("taken_action", ta).Msg("Took action") + pe.sendNotice(ctx, "Unbanned [%s](%s) in [%s](%s) for %s", userID, userID.URI().MatrixToURL(), roomID, roomID.URI().MatrixToURL(), policy.Reason) + } +} + func pluralize(value int, unit string) string { if value == 1 { return "1 " + unit From 36b55c12aa2954e08464eaa5f003df913939448f Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sat, 1 Feb 2025 15:53:18 +0000 Subject: [PATCH 7/9] Split unban into add-unban and remove-policy --- policyeval/commands.go | 58 +++++++++++++++++++++++++++++------------- policyeval/execute.go | 4 +++ 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/policyeval/commands.go b/policyeval/commands.go index 5f2ad39..d8fe5ba 100644 --- a/policyeval/commands.go +++ b/policyeval/commands.go @@ -148,13 +148,9 @@ func (pe *PolicyEvaluator) HandleCommand(ctx context.Context, evt *event.Event) Stringer("policy_event_id", resp.EventID). Msg("Sent ban policy from command") pe.sendSuccessReaction(ctx, evt.ID) - case "!unban", "!unban-user", "!unban-server": + case "!remove-ban", "!remove-unban", "!remove-policy": if len(args) < 2 { - if cmd == "!unban-server" { - pe.sendNotice(ctx, "Usage: `!unban-server `") - } else { - pe.sendNotice(ctx, "Usage: `!unban `") - } + pe.sendNotice(ctx, "Usage: `!remove-policy `") return } list := pe.FindListByShortcode(args[0]) @@ -165,8 +161,7 @@ func (pe *PolicyEvaluator) HandleCommand(ctx context.Context, evt *event.Event) target := args[1] var match policylist.Match var entityType policylist.EntityType - currentlyBanned := false - if cmd == "!unban-server" { + if !strings.HasPrefix(target, "@") { entityType = policylist.EntityTypeServer match = pe.Store.MatchServer(pe.GetWatchedLists(), target) } else { @@ -175,11 +170,46 @@ func (pe *PolicyEvaluator) HandleCommand(ctx context.Context, evt *event.Event) } var existingStateKey string if rec := match.Recommendations().BanOrUnban; rec != nil { - if rec.Recommendation == event.PolicyRecommendationBan { - currentlyBanned = true + if rec.RoomID == list.RoomID { + existingStateKey = rec.StateKey } + } + policy := &event.ModPolicyContent{} + resp, err := pe.SendPolicy(ctx, list.RoomID, entityType, existingStateKey, policy) + if err != nil { + pe.sendNotice(ctx, `Failed to remove policy: %v`, err) + return + } + zerolog.Ctx(ctx).Info(). + Stringer("policy_list", list.RoomID). + Any("policy", policy). + Stringer("policy_event_id", resp.EventID). + Msg("Removed policy from command") + pe.sendSuccessReaction(ctx, evt.ID) + case "!add-unban": + if len(args) < 2 { + pe.sendNotice(ctx, "Usage: `!add-unban `") + return + } + list := pe.FindListByShortcode(args[0]) + if list == nil { + pe.sendNotice(ctx, `List %q not found`, args[0]) + return + } + target := args[1] + var match policylist.Match + var entityType policylist.EntityType + if !strings.HasPrefix(target, "@") { + entityType = policylist.EntityTypeServer + match = pe.Store.MatchServer(pe.GetWatchedLists(), target) + } else { + entityType = policylist.EntityTypeUser + match = pe.Store.MatchUser(pe.GetWatchedLists(), id.UserID(target)) + } + var existingStateKey string + if rec := match.Recommendations().BanOrUnban; rec != nil { if rec.Recommendation == event.PolicyRecommendationUnban { - pe.sendNotice(ctx, "`%s` has an unban recommendation: %s", target, rec.Reason) + pe.sendNotice(ctx, "`%s` already has an unban recommendation: %s", target, rec.Reason) return } else if rec.RoomID == list.RoomID { existingStateKey = rec.StateKey @@ -190,12 +220,6 @@ func (pe *PolicyEvaluator) HandleCommand(ctx context.Context, evt *event.Event) Reason: strings.Join(args[2:], " "), Recommendation: event.PolicyRecommendationUnban, } - if currentlyBanned { - pe.sendNotice(ctx, `Removing the ban policy for %s`, target) - policy = &event.ModPolicyContent{} // just remove the ban, don't prevent it - } else { - pe.sendNotice(ctx, `Preventing future bans for %s`, target) - } resp, err := pe.SendPolicy(ctx, list.RoomID, entityType, existingStateKey, policy) if err != nil { pe.sendNotice(ctx, `Failed to send unban policy: %v`, err) diff --git a/policyeval/execute.go b/policyeval/execute.go index 9769afc..69b9833 100644 --- a/policyeval/execute.go +++ b/policyeval/execute.go @@ -61,6 +61,10 @@ func (pe *PolicyEvaluator) ApplyPolicy(ctx context.Context, userID id.UserID, po // pe.sendNotice(ctx, "Database error in ApplyPolicy (GetAllByTargetUser): %v", err) // return //} + zerolog.Ctx(ctx).Info(). + Stringer("user_id", userID). + Any("matches", policy). + Msg("Applying unban recommendation") for _, room := range rooms { pe.ApplyUnban(ctx, userID, room, recs.BanOrUnban) } From 425b4e7d963f022b2dbed370438d28381de76ba9 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sat, 1 Feb 2025 16:24:09 +0000 Subject: [PATCH 8/9] Properly action unbans - needs cleaning up --- policyeval/evaluate.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/policyeval/evaluate.go b/policyeval/evaluate.go index 0ef6cbf..9e0b79b 100644 --- a/policyeval/evaluate.go +++ b/policyeval/evaluate.go @@ -91,5 +91,24 @@ func (pe *PolicyEvaluator) ReevaluateAffectedByLists(ctx context.Context, policy } func (pe *PolicyEvaluator) ReevaluateActions(ctx context.Context, actions []*database.TakenAction) { - + for _, action := range actions { + if action.TargetUser == "" { + zerolog.Ctx(ctx).Warn().Any("action", action).Msg("Action has no target user") + continue + } + // unban users that were previously banned by this rule + if action.ActionType == database.TakenActionTypeBanOrUnban && action.Action == event.PolicyRecommendationBan { + // ensure that the user is actually banned in the room + if pe.Bot.StateStore.IsMembership(ctx, action.InRoomID, action.TargetUser, event.MembershipBan) { + // This is hacky + policy := &policylist.Policy{ + RoomID: action.InRoomID, + ModPolicyContent: &event.ModPolicyContent{ + Entity: action.RuleEntity, + }, + } + pe.ApplyUnban(ctx, action.TargetUser, action.InRoomID, policy) + } + } + } } From c9677b0222e135794fcb4e499046f303b8441225 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sat, 1 Feb 2025 16:26:33 +0000 Subject: [PATCH 9/9] Remove log call --- policyeval/execute.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/policyeval/execute.go b/policyeval/execute.go index 69b9833..9769afc 100644 --- a/policyeval/execute.go +++ b/policyeval/execute.go @@ -61,10 +61,6 @@ func (pe *PolicyEvaluator) ApplyPolicy(ctx context.Context, userID id.UserID, po // pe.sendNotice(ctx, "Database error in ApplyPolicy (GetAllByTargetUser): %v", err) // return //} - zerolog.Ctx(ctx).Info(). - Stringer("user_id", userID). - Any("matches", policy). - Msg("Applying unban recommendation") for _, room := range rooms { pe.ApplyUnban(ctx, userID, room, recs.BanOrUnban) }