merged from gramsrv upstream
This commit is contained in:
parent
79c64ee916
commit
21a0856587
651 changed files with 54774 additions and 4590 deletions
|
|
@ -35,6 +35,10 @@ type moderationAccountDeleter interface {
|
|||
ExecuteAccountDeletion(ctx context.Context, userID int64, source domain.AccountDeletionSource, reason string, now time.Time) (domain.AccountDeletionResult, error)
|
||||
}
|
||||
|
||||
type moderationAccountDeletionNotifier interface {
|
||||
NotifyModerationAccountDeletion(ctx context.Context, result domain.AccountDeletionResult)
|
||||
}
|
||||
|
||||
type moderationAppealLinkIssuer interface {
|
||||
IssueAppealLink(ctx context.Context, caseID, appellantUserID int64, expiresAt, now time.Time) (string, error)
|
||||
}
|
||||
|
|
@ -44,6 +48,7 @@ type ActionExecutor struct {
|
|||
channels moderationChannelDeleter
|
||||
channelNotifier moderationChannelDeleteNotifier
|
||||
accounts moderationAccountDeleter
|
||||
accountNotifier moderationAccountDeletionNotifier
|
||||
appealLinks moderationAppealLinkIssuer
|
||||
publicBaseURL string
|
||||
now func() time.Time
|
||||
|
|
@ -51,6 +56,15 @@ type ActionExecutor struct {
|
|||
|
||||
type ActionExecutorOption func(*ActionExecutor)
|
||||
|
||||
// WithAccountDeletionNotifier installs the post-commit runtime boundary for a
|
||||
// moderation deletion. The deleter owns the durable tombstone transaction; the
|
||||
// notifier retires the returned authorizations from live RPC sessions/caches.
|
||||
func WithAccountDeletionNotifier(notifier moderationAccountDeletionNotifier) ActionExecutorOption {
|
||||
return func(executor *ActionExecutor) {
|
||||
executor.accountNotifier = notifier
|
||||
}
|
||||
}
|
||||
|
||||
func WithAppealLinks(issuer moderationAppealLinkIssuer, publicBaseURL string) ActionExecutorOption {
|
||||
return func(executor *ActionExecutor) {
|
||||
executor.appealLinks = issuer
|
||||
|
|
@ -206,17 +220,26 @@ func (e *ActionExecutor) Execute(ctx context.Context, detail domain.ModerationCa
|
|||
}
|
||||
return nil
|
||||
case domain.ModerationActionDeleteAccount:
|
||||
if e.accounts == nil || detail.Case.Target.Type != domain.PeerTypeUser {
|
||||
// The tombstone and live-session revocation are one application-level
|
||||
// outcome. Refuse to commit the durable half if this process cannot run the
|
||||
// post-commit half; otherwise an already-bound session keeps its cached user.
|
||||
if e.accounts == nil || e.accountNotifier == nil || detail.Case.Target.Type != domain.PeerTypeUser {
|
||||
return domain.ErrModerationActionInvalid
|
||||
}
|
||||
if err := decodeStrictActionPayload(action.Payload, &struct{}{}); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := e.accounts.ExecuteAccountDeletion(
|
||||
result, err := e.accounts.ExecuteAccountDeletion(
|
||||
ctx, detail.Case.Target.ID, domain.AccountDeletionManual,
|
||||
fmt.Sprintf("moderation case %d", detail.Case.ID), e.now().UTC(),
|
||||
)
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.Changed && e.accountNotifier != nil {
|
||||
e.accountNotifier.NotifyModerationAccountDeletion(ctx, result)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return domain.ErrModerationActionInvalid
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,25 @@ type captureModerationAdmin struct {
|
|||
frozen []admin.SetAccountFrozenRequest
|
||||
}
|
||||
|
||||
type captureModerationAccountDeleter struct {
|
||||
result domain.AccountDeletionResult
|
||||
err error
|
||||
calls int
|
||||
}
|
||||
|
||||
func (d *captureModerationAccountDeleter) ExecuteAccountDeletion(context.Context, int64, domain.AccountDeletionSource, string, time.Time) (domain.AccountDeletionResult, error) {
|
||||
d.calls++
|
||||
return d.result, d.err
|
||||
}
|
||||
|
||||
type captureModerationAccountDeletionNotifier struct {
|
||||
results []domain.AccountDeletionResult
|
||||
}
|
||||
|
||||
func (n *captureModerationAccountDeletionNotifier) NotifyModerationAccountDeletion(_ context.Context, result domain.AccountDeletionResult) {
|
||||
n.results = append(n.results, result)
|
||||
}
|
||||
|
||||
func (a *captureModerationAdmin) SetAccountFrozen(_ context.Context, req admin.SetAccountFrozenRequest) (admin.CommandResult, error) {
|
||||
a.frozen = append(a.frozen, req)
|
||||
return admin.CommandResult{}, nil
|
||||
|
|
@ -323,3 +342,46 @@ func TestActionExecutorFreezeDefaultsAndBoundsAppealLink(t *testing.T) {
|
|||
t.Fatalf("link expiry=%v want=%v", issuer.expiresAt, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionExecutorNotifiesCommittedAccountDeletion(t *testing.T) {
|
||||
revoked := domain.Authorization{AuthKeyID: [8]byte{7}, UserID: 20}
|
||||
result := domain.AccountDeletionResult{
|
||||
User: domain.User{ID: 20, Deleted: true},
|
||||
Changed: true,
|
||||
RevokedAuthorizations: []domain.Authorization{revoked},
|
||||
}
|
||||
accounts := &captureModerationAccountDeleter{result: result}
|
||||
notifier := &captureModerationAccountDeletionNotifier{}
|
||||
executor := NewActionExecutor(nil, nil, nil, accounts, WithAccountDeletionNotifier(notifier))
|
||||
detail := domain.ModerationCaseDetail{
|
||||
Case: domain.ModerationCase{ID: 10, Target: domain.Peer{Type: domain.PeerTypeUser, ID: 20}},
|
||||
Decisions: []domain.ModerationDecision{{ID: 30, Actor: "reviewer"}},
|
||||
}
|
||||
action := domain.ModerationAction{
|
||||
CaseID: 10, DecisionID: 30, Kind: domain.ModerationActionDeleteAccount,
|
||||
Payload: []byte(`{}`), CommandID: "delete-account:000",
|
||||
}
|
||||
if err := executor.Execute(context.Background(), detail, action); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if accounts.calls != 1 || len(notifier.results) != 1 {
|
||||
t.Fatalf("delete calls=%d notifications=%d, want 1/1", accounts.calls, len(notifier.results))
|
||||
}
|
||||
got := notifier.results[0]
|
||||
if !got.Changed || got.User.ID != result.User.ID || len(got.RevokedAuthorizations) != 1 || got.RevokedAuthorizations[0].AuthKeyID != revoked.AuthKeyID {
|
||||
t.Fatalf("notification result=%+v, want committed deletion", got)
|
||||
}
|
||||
|
||||
accounts.result = domain.AccountDeletionResult{User: result.User, Changed: false}
|
||||
if err := executor.Execute(context.Background(), detail, action); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(notifier.results) != 1 {
|
||||
t.Fatalf("notifications after unchanged deletion=%d, want 1", len(notifier.results))
|
||||
}
|
||||
|
||||
withoutNotifier := NewActionExecutor(nil, nil, nil, accounts)
|
||||
if err := withoutNotifier.Execute(context.Background(), detail, action); !errors.Is(err, domain.ErrModerationActionInvalid) {
|
||||
t.Fatalf("delete without runtime notifier err=%v, want ErrModerationActionInvalid", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue