532 lines
15 KiB
Go
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.
|
|
permissionType 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.permissionType, action.domain)
|
|
case "delete":
|
|
_, actionID = suite.deleteDomainPerm(action.permissionType, 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 permissionType with default values.
|
|
func (suite *DomainBlockTestSuite) createDomainPerm(
|
|
permissionType gtsmodel.DomainPermissionType,
|
|
domain string,
|
|
) (*apimodel.DomainPermission, string) {
|
|
ctx := context.Background()
|
|
|
|
apiPerm, actionID, errWithCode := suite.adminProcessor.DomainPermissionCreate(
|
|
ctx,
|
|
permissionType,
|
|
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(
|
|
permissionType 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 permissionType {
|
|
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,
|
|
permissionType,
|
|
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",
|
|
permissionType: 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",
|
|
permissionType: 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",
|
|
permissionType: 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",
|
|
permissionType: 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",
|
|
permissionType: 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",
|
|
permissionType: 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",
|
|
permissionType: 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",
|
|
permissionType: 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",
|
|
permissionType: 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))
|
|
}
|