[bugfix] More permissive CSV parsing for perm subs, text parse fix (#3638)

* [bugfix] More permissive CSV parsing for perm subs, text parse fix

* wee

* change the way dry works, slightly

* me oh my, i'm just a little guy

* we're just normal men
This commit is contained in:
tobi 2025-01-08 22:38:27 +01:00 committed by GitHub
parent 451803b230
commit 8daa4dae34
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 184 additions and 53 deletions

View file

@ -39,7 +39,7 @@ type DomainPermissionSubscriptionTestTestSuite struct {
AdminStandardTestSuite AdminStandardTestSuite
} }
func (suite *DomainPermissionSubscriptionTestTestSuite) TestDomainPermissionSubscriptionTest() { func (suite *DomainPermissionSubscriptionTestTestSuite) TestDomainPermissionSubscriptionTestCSV() {
var ( var (
ctx = context.Background() ctx = context.Background()
testAccount = suite.testAccounts["admin_account"] testAccount = suite.testAccounts["admin_account"]
@ -120,6 +120,85 @@ func (suite *DomainPermissionSubscriptionTestTestSuite) TestDomainPermissionSubs
suite.False(blocked) suite.False(blocked)
} }
func (suite *DomainPermissionSubscriptionTestTestSuite) TestDomainPermissionSubscriptionTestText() {
var (
ctx = context.Background()
testAccount = suite.testAccounts["admin_account"]
permSub = &gtsmodel.DomainPermissionSubscription{
ID: "01JGE681TQSBPAV59GZXPKE62H",
Priority: 255,
Title: "whatever!",
PermissionType: gtsmodel.DomainPermissionBlock,
AsDraft: util.Ptr(false),
AdoptOrphans: util.Ptr(true),
CreatedByAccountID: testAccount.ID,
CreatedByAccount: testAccount,
URI: "https://lists.example.org/baddies.txt",
ContentType: gtsmodel.DomainPermSubContentTypePlain,
}
)
// Create a subscription for a plaintext list of baddies.
err := suite.state.DB.PutDomainPermissionSubscription(ctx, permSub)
if err != nil {
suite.FailNow(err.Error())
}
// Prepare the request to the /test endpoint.
subPath := strings.ReplaceAll(
admin.DomainPermissionSubscriptionTestPath,
":id", permSub.ID,
)
path := "/api" + subPath
recorder := httptest.NewRecorder()
ginCtx := suite.newContext(recorder, http.MethodPost, nil, path, "application/json")
ginCtx.Params = gin.Params{
gin.Param{
Key: apiutil.IDKey,
Value: permSub.ID,
},
}
// Trigger the handler.
suite.adminModule.DomainPermissionSubscriptionTestPOSTHandler(ginCtx)
suite.Equal(http.StatusOK, recorder.Code)
// Read the body back.
b, err := io.ReadAll(recorder.Body)
if err != nil {
suite.FailNow(err.Error())
}
dst := new(bytes.Buffer)
if err := json.Indent(dst, b, "", " "); err != nil {
suite.FailNow(err.Error())
}
// Ensure expected.
suite.Equal(`[
{
"domain": "bumfaces.net"
},
{
"domain": "peepee.poopoo"
},
{
"domain": "nothanks.com"
}
]`, dst.String())
// No permissions should be created
// since this is a dry run / test.
blocked, err := suite.state.DB.AreDomainsBlocked(
ctx,
[]string{"bumfaces.net", "peepee.poopoo", "nothanks.com"},
)
if err != nil {
suite.FailNow(err.Error())
}
suite.False(blocked)
}
func TestDomainPermissionSubscriptionTestTestSuite(t *testing.T) { func TestDomainPermissionSubscriptionTestTestSuite(t *testing.T) {
suite.Run(t, &DomainPermissionSubscriptionTestTestSuite{}) suite.Run(t, &DomainPermissionSubscriptionTestTestSuite{})
} }

View file

@ -272,6 +272,12 @@ func (p *Processor) DomainPermissionSubscriptionRemove(
return nil, gtserror.NewErrorNotFound(err, err.Error()) return nil, gtserror.NewErrorNotFound(err, err.Error())
} }
// Convert to API perm sub *before* doing the deletion.
apiPermSub, errWithCode := p.apiDomainPermSub(ctx, permSub)
if errWithCode != nil {
return nil, errWithCode
}
// TODO in next PR: if removeChildren, then remove all // TODO in next PR: if removeChildren, then remove all
// domain permissions that are children of this domain // domain permissions that are children of this domain
// permission subscription. If not removeChildren, then // permission subscription. If not removeChildren, then
@ -282,7 +288,7 @@ func (p *Processor) DomainPermissionSubscriptionRemove(
return nil, gtserror.NewErrorInternalError(err) return nil, gtserror.NewErrorInternalError(err)
} }
return p.apiDomainPermSub(ctx, permSub) return apiPermSub, nil
} }
func (p *Processor) DomainPermissionSubscriptionTest( func (p *Processor) DomainPermissionSubscriptionTest(

View file

@ -345,6 +345,10 @@ func (s *Subscriptions) ProcessDomainPermissionSubscription(
// processDomainPermission processes one wanted domain // processDomainPermission processes one wanted domain
// permission discovered via a domain permission sub's URI. // permission discovered via a domain permission sub's URI.
// //
// If dry == true, then the returned boolean indicates whether
// the permission would actually be created. If dry == false,
// the bool indicates whether the permission was created or adopted.
//
// Error will only be returned in case of an actual database // Error will only be returned in case of an actual database
// error, else the error will be logged and nil returned. // error, else the error will be logged and nil returned.
func (s *Subscriptions) processDomainPermission( func (s *Subscriptions) processDomainPermission(
@ -355,22 +359,18 @@ func (s *Subscriptions) processDomainPermission(
higherPrios []*gtsmodel.DomainPermissionSubscription, higherPrios []*gtsmodel.DomainPermissionSubscription,
dry bool, dry bool,
) (bool, error) { ) (bool, error) {
// Set to true if domain permission
// actually (would be) created.
var created bool
// If domain is excluded from automatic // If domain is excluded from automatic
// permission creation, don't process it. // permission creation, don't process it.
domain := wantedPerm.GetDomain() domain := wantedPerm.GetDomain()
excluded, err := s.state.DB.IsDomainPermissionExcluded(ctx, domain) excluded, err := s.state.DB.IsDomainPermissionExcluded(ctx, domain)
if err != nil { if err != nil {
// Proper db error. // Proper db error.
return created, err return false, err
} }
if excluded { if excluded {
l.Debug("domain is excluded, skipping") l.Debug("domain is excluded, skipping")
return created, nil return false, err
} }
// Check if a permission already exists for // Check if a permission already exists for
@ -381,27 +381,27 @@ func (s *Subscriptions) processDomainPermission(
) )
if err != nil { if err != nil {
// Proper db error. // Proper db error.
return created, err return false, err
} }
if covered { if covered {
l.Debug("domain is covered by a higher-priority subscription, skipping") l.Debug("domain is covered by a higher-priority subscription, skipping")
return created, nil return false, err
} }
// At this point we know we // True if a perm already exists.
// should create the perm. // Note: != nil doesn't work because
created = true // of Go interface idiosyncracies.
existing := !util.IsNil(existingPerm)
if dry { if dry {
// Don't do creation or side // If this is a dry run, return
// effects if we're dry running. // now without doing any DB changes.
return created, nil return !existing, nil
} }
// Handle perm creation differently depending // Handle perm creation differently depending
// on whether or not a perm already existed. // on whether or not a perm already existed.
existing := !util.IsNil(existingPerm)
switch { switch {
case !existing && *permSub.AsDraft: case !existing && *permSub.AsDraft:
@ -512,11 +512,10 @@ func (s *Subscriptions) processDomainPermission(
if err != nil && !errors.Is(err, db.ErrAlreadyExists) { if err != nil && !errors.Is(err, db.ErrAlreadyExists) {
// Proper db error. // Proper db error.
return created, err return false, err
} }
created = true return true, nil
return created, nil
} }
func permsFromCSV( func permsFromCSV(
@ -533,19 +532,60 @@ func permsFromCSV(
return nil, gtserror.NewfAt(3, "error decoding csv column headers: %w", err) return nil, gtserror.NewfAt(3, "error decoding csv column headers: %w", err)
} }
if !slices.Equal( var (
columnHeaders, domainI *int
[]string{ severityI *int
"#domain", publicCommentI *int
"#severity", obfuscateI *int
"#reject_media", )
"#reject_reports",
"#public_comment", for i, columnHeader := range columnHeaders {
"#obfuscate", // Remove leading # if present.
}, normal := strings.TrimLeft(columnHeader, "#")
) {
// Find index of each column header we
// care about, ensuring no duplicates.
switch normal {
case "domain":
if domainI != nil {
body.Close() body.Close()
err := gtserror.NewfAt(3, "unexpected column headers in csv: %+v", columnHeaders) err := gtserror.NewfAt(3, "duplicate domain column header in csv: %+v", columnHeaders)
return nil, err
}
domainI = &i
case "severity":
if severityI != nil {
body.Close()
err := gtserror.NewfAt(3, "duplicate severity column header in csv: %+v", columnHeaders)
return nil, err
}
severityI = &i
case "public_comment":
if publicCommentI != nil {
body.Close()
err := gtserror.NewfAt(3, "duplicate public_comment column header in csv: %+v", columnHeaders)
return nil, err
}
publicCommentI = &i
case "obfuscate":
if obfuscateI != nil {
body.Close()
err := gtserror.NewfAt(3, "duplicate obfuscate column header in csv: %+v", columnHeaders)
return nil, err
}
obfuscateI = &i
}
}
// Ensure we have at least a domain
// index, as that's the bare minimum.
if domainI == nil {
body.Close()
err := gtserror.NewfAt(3, "no domain column header in csv: %+v", columnHeaders)
return nil, err return nil, err
} }
@ -576,25 +616,19 @@ func permsFromCSV(
continue continue
} }
var ( // Skip records that specify severity
domainRaw = record[0] // that's not "suspend" (we don't support
severity = record[1] // "silence" or "limit" or whatever yet).
publicComment = record[4] if severityI != nil {
obfuscateStr = record[5] severity := record[*severityI]
)
if severity != "suspend" { if severity != "suspend" {
l.Warnf("skipping non-suspend record: %+v", record) l.Warnf("skipping non-suspend record: %+v", record)
continue continue
} }
obfuscate, err := strconv.ParseBool(obfuscateStr)
if err != nil {
l.Warnf("couldn't parse obfuscate field of record: %+v", record)
continue
} }
// Normalize + validate domain. // Normalize + validate domain.
domainRaw := record[*domainI]
domain, err := validateDomain(domainRaw) domain, err := validateDomain(domainRaw)
if err != nil { if err != nil {
l.Warnf("skipping invalid domain %s: %+v", domainRaw, err) l.Warnf("skipping invalid domain %s: %+v", domainRaw, err)
@ -611,9 +645,21 @@ func permsFromCSV(
perm = &gtsmodel.DomainAllow{Domain: domain} perm = &gtsmodel.DomainAllow{Domain: domain}
} }
// Set remaining fields. // Set remaining optional fields
perm.SetPublicComment(publicComment) // if they're present in the CSV.
if publicCommentI != nil {
perm.SetPublicComment(record[*publicCommentI])
}
if obfuscateI != nil {
obfuscate, err := strconv.ParseBool(record[*obfuscateI])
if err != nil {
l.Warnf("couldn't parse obfuscate field of record: %+v", record)
continue
}
perm.SetObfuscate(&obfuscate) perm.SetObfuscate(&obfuscate)
}
// We're done. // We're done.
perms = append(perms, perm) perms = append(perms, perm)

View file

@ -472,7 +472,7 @@ func (suite *SubscriptionsTestSuite) TestDomainBlocksWrongContentTypeCSV() {
suite.Zero(count) suite.Zero(count)
suite.WithinDuration(time.Now(), permSub.FetchedAt, 1*time.Minute) suite.WithinDuration(time.Now(), permSub.FetchedAt, 1*time.Minute)
suite.Zero(permSub.SuccessfullyFetchedAt) suite.Zero(permSub.SuccessfullyFetchedAt)
suite.Equal(`ProcessDomainPermissionSubscription: unexpected column headers in csv: [bumfaces.net]`, permSub.Error) suite.Equal(`ProcessDomainPermissionSubscription: no domain column header in csv: [bumfaces.net]`, permSub.Error)
} }
func (suite *SubscriptionsTestSuite) TestDomainBlocksWrongContentTypePlain() { func (suite *SubscriptionsTestSuite) TestDomainBlocksWrongContentTypePlain() {

View file

@ -2115,7 +2115,7 @@ func (c *Converter) DomainPermToAPIDomainPerm(
} }
domainPerm.ID = d.GetID() domainPerm.ID = d.GetID()
domainPerm.Obfuscate = *d.GetObfuscate() domainPerm.Obfuscate = util.PtrOrZero(d.GetObfuscate())
domainPerm.PrivateComment = d.GetPrivateComment() domainPerm.PrivateComment = d.GetPrivateComment()
domainPerm.SubscriptionID = d.GetSubscriptionID() domainPerm.SubscriptionID = d.GetSubscriptionID()
domainPerm.CreatedBy = d.GetCreatedByAccountID() domainPerm.CreatedBy = d.GetCreatedByAccountID()