mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-01-22 08:36:24 +01:00
[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:
parent
451803b230
commit
8daa4dae34
5 changed files with 184 additions and 53 deletions
|
@ -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 = >smodel.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{})
|
||||||
}
|
}
|
|
@ -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(
|
||||||
|
|
|
@ -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()
|
||||||
|
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()
|
body.Close()
|
||||||
err := gtserror.NewfAt(3, "unexpected column headers in csv: %+v", columnHeaders)
|
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" {
|
||||||
|
l.Warnf("skipping non-suspend record: %+v", record)
|
||||||
if severity != "suspend" {
|
continue
|
||||||
l.Warnf("skipping non-suspend record: %+v", record)
|
}
|
||||||
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 = >smodel.DomainAllow{Domain: domain}
|
perm = >smodel.DomainAllow{Domain: domain}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set remaining fields.
|
// Set remaining optional fields
|
||||||
perm.SetPublicComment(publicComment)
|
// if they're present in the CSV.
|
||||||
perm.SetObfuscate(&obfuscate)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
// We're done.
|
// We're done.
|
||||||
perms = append(perms, perm)
|
perms = append(perms, perm)
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue