gotosocial/internal/processing/admin/domainpermission_test.go
2024-10-28 14:52:09 +01:00

532 lines
15 KiB
Go

// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package admin_test
import (
"context"
"net/http"
"testing"
"github.com/stretchr/testify/suite"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/testrig"
)
type DomainBlockTestSuite struct {
AdminStandardTestSuite
}
type domainPermAction struct {
// 'create' or 'delete'
// the domain permission.
createOrDelete string
// Type of permission
// to create or delete.
permType gtsmodel.DomainPermissionType
// Domain to target
// with the permission.
domain string
// Expected result of this
// permission action on each
// account on the target domain.
// Eg., suite.Zero(account.SuspendedAt)
expected func(
context.Context,
*gtsmodel.Account,
) bool
}
type domainPermTest struct {
// Federation mode under which to
// run this test. This is important
// because it may effect which side
// effects are taken, if any.
instanceFederationMode string
// Series of actions to run as part
// of this test. After each action,
// expected will be called. This
// allows testers to run multiple
// actions in a row and check that
// the results after each action are
// what they expected, in light of
// previous actions.
actions []domainPermAction
}
// run a domainPermTest by running each of
// its actions in turn and checking results.
func (suite *DomainBlockTestSuite) runDomainPermTest(t domainPermTest) {
config.SetInstanceFederationMode(t.instanceFederationMode)
for _, action := range t.actions {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Run the desired action.
var actionID string
switch action.createOrDelete {
case "create":
_, actionID = suite.createDomainPerm(action.permType, action.domain)
case "delete":
_, actionID = suite.deleteDomainPerm(action.permType, action.domain)
default:
panic("createOrDelete was not 'create' or 'delete'")
}
// Let the action finish.
suite.awaitAction(actionID)
// Check expected results
// against each account.
accounts, err := suite.db.GetInstanceAccounts(
context.Background(),
action.domain,
"", 0,
)
if err != nil {
suite.FailNow("", "error getting instance accounts for %s: %v", action.domain, err)
}
for _, account := range accounts {
if !action.expected(ctx, account) {
suite.T().FailNow()
}
}
}
}
// create given permType with default values.
func (suite *DomainBlockTestSuite) createDomainPerm(
permType gtsmodel.DomainPermissionType,
domain string,
) (*apimodel.DomainPermission, string) {
ctx := context.Background()
apiPerm, actionID, errWithCode := suite.adminProcessor.DomainPermissionCreate(
ctx,
permType,
suite.testAccounts["admin_account"],
domain,
false,
"",
"",
"",
)
suite.NoError(errWithCode)
suite.NotNil(apiPerm)
suite.NotEmpty(actionID)
return apiPerm, actionID
}
// delete given permission type.
func (suite *DomainBlockTestSuite) deleteDomainPerm(
permType gtsmodel.DomainPermissionType,
domain string,
) (*apimodel.DomainPermission, string) {
var (
ctx = context.Background()
domainPermission gtsmodel.DomainPermission
)
// To delete the permission,
// first get it from the db.
switch permType {
case gtsmodel.DomainPermissionBlock:
domainPermission, _ = suite.db.GetDomainBlock(ctx, domain)
case gtsmodel.DomainPermissionAllow:
domainPermission, _ = suite.db.GetDomainAllow(ctx, domain)
default:
panic("unrecognized permission type")
}
if domainPermission == nil {
suite.FailNow("domain permission was nil")
}
// Now use the ID to delete it.
apiPerm, actionID, errWithCode := suite.adminProcessor.DomainPermissionDelete(
ctx,
permType,
suite.testAccounts["admin_account"],
domainPermission.GetID(),
)
suite.NoError(errWithCode)
suite.NotNil(apiPerm)
suite.NotEmpty(actionID)
return apiPerm, actionID
}
// waits for given actionID to be completed.
func (suite *DomainBlockTestSuite) awaitAction(actionID string) {
ctx := context.Background()
if !testrig.WaitFor(func() bool {
return suite.adminProcessor.Actions().TotalRunning() == 0
}) {
suite.FailNow("timed out waiting for admin action(s) to finish")
}
// Ensure action marked as
// completed in the database.
adminAction, err := suite.db.GetAdminAction(ctx, actionID)
if err != nil {
suite.FailNow(err.Error())
}
suite.NotZero(adminAction.CompletedAt)
suite.Empty(adminAction.Errors)
}
// shortcut to look up an account
// using the Search processor.
func (suite *DomainBlockTestSuite) lookupAccount(
ctx context.Context,
requestingAccount *gtsmodel.Account,
targetAccount *gtsmodel.Account,
) (*apimodel.Account, gtserror.WithCode) {
return suite.processor.Search().Lookup(
ctx,
requestingAccount,
"@"+targetAccount.Username+"@"+targetAccount.Domain,
)
}
// shortcut to look up target account's
// statuses using the Account processor.
func (suite *DomainBlockTestSuite) getStatuses(
ctx context.Context,
requestingAccount *gtsmodel.Account,
targetAccount *gtsmodel.Account,
) (*apimodel.PageableResponse, gtserror.WithCode) {
return suite.processor.Account().StatusesGet(
ctx,
requestingAccount,
targetAccount.ID,
0, // unlimited
false, // include replies
false, // include reblogs
id.Highest, // max ID
id.Lowest, // min ID
false, // don't filter on pinned
false, // don't filter on media
false, // don't filter on public
)
}
func (suite *DomainBlockTestSuite) TestBlockAndUnblockDomain() {
const domain = "fossbros-anonymous.io"
suite.runDomainPermTest(domainPermTest{
instanceFederationMode: config.InstanceFederationModeBlocklist,
actions: []domainPermAction{
{
createOrDelete: "create",
permType: gtsmodel.DomainPermissionBlock,
domain: domain,
expected: func(_ context.Context, account *gtsmodel.Account) bool {
// Domain was blocked, so each
// account should now be suspended.
return suite.NotZero(account.SuspendedAt)
},
},
{
createOrDelete: "delete",
permType: gtsmodel.DomainPermissionBlock,
domain: domain,
expected: func(_ context.Context, account *gtsmodel.Account) bool {
// Domain was unblocked, so each
// account should now be unsuspended.
return suite.Zero(account.SuspendedAt)
},
},
},
})
}
func (suite *DomainBlockTestSuite) TestBlockAndAllowDomain() {
const domain = "fossbros-anonymous.io"
// Use zork for checks within test.
var testAccount = suite.testAccounts["local_account_1"]
suite.runDomainPermTest(domainPermTest{
instanceFederationMode: config.InstanceFederationModeBlocklist,
actions: []domainPermAction{
{
createOrDelete: "create",
permType: gtsmodel.DomainPermissionBlock,
domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Domain was blocked, so each
// account should now be suspended.
if account.SuspendedAt.IsZero() {
suite.T().Logf("account %s should be suspended", account.Username)
return false
}
// Local account 1 should be able to see
// no statuses from suspended account.
statuses, err := suite.getStatuses(ctx, testAccount, account)
if err != nil {
suite.FailNow(err.Error())
}
if l := len(statuses.Items); l != 0 {
suite.T().Logf("expected statuses of len 0, was %d", l)
return false
}
// Lookup for this account should return 404.
lookupAcct, err := suite.lookupAccount(ctx, testAccount, account)
if err == nil || err.Code() != http.StatusNotFound {
suite.T().Logf("expected 404 error, got %v", err)
return false
}
if lookupAcct != nil {
suite.T().Logf("expected nil account lookup, got %v", lookupAcct)
return false
}
return true
},
},
{
createOrDelete: "create",
permType: gtsmodel.DomainPermissionAllow,
domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Domain was explicitly allowed, so each
// account should now be unsuspended, since
// the allow supercedes the block.
if !account.SuspendedAt.IsZero() {
suite.T().Logf("account %s should not be suspended", account.Username)
return false
}
// Local account 1 should be able to see
// no statuses from account, because any
// statuses were deleted by the block above.
statuses, err := suite.getStatuses(ctx, testAccount, account)
if err != nil {
suite.FailNow(err.Error())
}
if l := len(statuses.Items); l != 0 {
suite.T().Logf("expected statuses of len 0, was %d", l)
return false
}
// Lookup for this account should return OK.
lookupAcct, err := suite.lookupAccount(ctx, testAccount, account)
if err != nil {
suite.T().Logf("expected no error, got %v", err)
return false
}
if lookupAcct == nil {
suite.T().Log("expected not nil account lookup")
return false
}
return true
},
},
{
createOrDelete: "delete",
permType: gtsmodel.DomainPermissionAllow,
domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Deleting the allow now, while there's
// still a block in place, should cause
// the block to take effect again.
if account.SuspendedAt.IsZero() {
suite.T().Logf("account %s should be suspended", account.Username)
return false
}
// Lookup for this account should return 404.
lookupAcct, err := suite.lookupAccount(ctx, testAccount, account)
if err == nil || err.Code() != http.StatusNotFound {
suite.T().Logf("expected 404 error, got %v", err)
return false
}
if lookupAcct != nil {
suite.T().Logf("expected nil account lookup, got %v", lookupAcct)
return false
}
return true
},
},
{
createOrDelete: "delete",
permType: gtsmodel.DomainPermissionBlock,
domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Deleting the block now should
// unsuspend the accounts again.
if !account.SuspendedAt.IsZero() {
suite.T().Logf("account %s should not be suspended", account.Username)
return false
}
// Lookup for this account should return OK.
lookupAcct, err := suite.lookupAccount(ctx, testAccount, account)
if err != nil {
suite.T().Logf("expected no error, got %v", err)
return false
}
if lookupAcct == nil {
suite.T().Log("expected not nil account lookup")
return false
}
return true
},
},
},
})
}
func (suite *DomainBlockTestSuite) TestAllowAndBlockDomain() {
const domain = "fossbros-anonymous.io"
// Use zork for checks within test.
var testAccount = suite.testAccounts["local_account_1"]
suite.runDomainPermTest(domainPermTest{
instanceFederationMode: config.InstanceFederationModeBlocklist,
actions: []domainPermAction{
{
createOrDelete: "create",
permType: gtsmodel.DomainPermissionAllow,
domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Domain was explicitly allowed,
// nothing should be suspended.
if !account.SuspendedAt.IsZero() {
suite.T().Logf("account %s should not be suspended", account.Username)
return false
}
// Local account 1 should be able
// to see statuses from account.
statuses, err := suite.getStatuses(ctx, testAccount, account)
if err != nil {
suite.FailNow(err.Error())
}
if l := len(statuses.Items); l == 0 {
suite.T().Log("expected some statuses, but length was 0")
return false
}
// Lookup for this account should return OK.
lookupAcct, err := suite.lookupAccount(ctx, testAccount, account)
if err != nil {
suite.T().Logf("expected no error, got %v", err)
return false
}
if lookupAcct == nil {
suite.T().Log("expected not nil account lookup")
return false
}
return true
},
},
{
createOrDelete: "create",
permType: gtsmodel.DomainPermissionBlock,
domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Create a block. An allow existed, so
// block side effects should be witheld.
// In other words, we should have the same
// results as before we added the block.
if !account.SuspendedAt.IsZero() {
suite.T().Logf("account %s should not be suspended", account.Username)
return false
}
// Local account 1 should be able
// to see statuses from account.
statuses, err := suite.getStatuses(ctx, testAccount, account)
if err != nil {
suite.FailNow(err.Error())
}
if l := len(statuses.Items); l == 0 {
suite.T().Log("expected some statuses, but length was 0")
return false
}
// Lookup for this account should return OK.
lookupAcct, err := suite.lookupAccount(ctx, testAccount, account)
if err != nil {
suite.T().Logf("expected no error, got %v", err)
return false
}
if lookupAcct == nil {
suite.T().Log("expected not nil account lookup")
return false
}
return true
},
},
{
createOrDelete: "delete",
permType: gtsmodel.DomainPermissionAllow,
domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Deleting the allow now, while there's
// a block in place, should cause the
// block to take effect.
if account.SuspendedAt.IsZero() {
suite.T().Logf("account %s should be suspended", account.Username)
return false
}
// Lookup for this account should return 404.
lookupAcct, err := suite.lookupAccount(ctx, testAccount, account)
if err == nil || err.Code() != http.StatusNotFound {
suite.T().Logf("expected 404 error, got %v", err)
return false
}
if lookupAcct != nil {
suite.T().Logf("expected nil account lookup, got %v", lookupAcct)
return false
}
return true
},
},
},
})
}
func TestDomainBlockTestSuite(t *testing.T) {
suite.Run(t, new(DomainBlockTestSuite))
}