Skip to content

Commit

Permalink
[chore] Add interaction filter to complement existing visibility filter
Browse files Browse the repository at this point in the history
  • Loading branch information
tsmethurst committed Jul 17, 2024
1 parent 0aadc2d commit f48f9fe
Show file tree
Hide file tree
Showing 56 changed files with 1,474 additions and 592 deletions.
12 changes: 11 additions & 1 deletion cmd/gotosocial/action/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/api"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/cleaner"
"github.com/superseriousbusiness/gotosocial/internal/filter/interaction"
"github.com/superseriousbusiness/gotosocial/internal/filter/spam"
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
Expand Down Expand Up @@ -190,10 +191,19 @@ var Start action.GTSAction = func(ctx context.Context) error {
oauthServer := oauth.New(ctx, dbService)
typeConverter := typeutils.NewConverter(state)
visFilter := visibility.NewFilter(state)
intFilter := interaction.NewFilter(state)
spamFilter := spam.NewFilter(state)
federatingDB := federatingdb.New(state, typeConverter, visFilter, spamFilter)
transportController := transport.NewController(state, federatingDB, &federation.Clock{}, client)
federator := federation.NewFederator(state, federatingDB, transportController, typeConverter, visFilter, mediaManager)
federator := federation.NewFederator(
state,
federatingDB,
transportController,
typeConverter,
visFilter,
intFilter,
mediaManager,
)

// Decide whether to create a noop email
// sender (won't send emails) or a real one.
Expand Down
27 changes: 18 additions & 9 deletions internal/api/client/admin/reportsget_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,19 +532,22 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() {
"interaction_policy": {
"can_favourite": {
"always": [
"public"
"public",
"me"
],
"with_approval": []
},
"can_reply": {
"always": [
"public"
"public",
"me"
],
"with_approval": []
},
"can_reblog": {
"always": [
"public"
"public",
"me"
],
"with_approval": []
}
Expand Down Expand Up @@ -774,19 +777,22 @@ func (suite *ReportsGetTestSuite) TestReportsGetCreatedByAccount() {
"interaction_policy": {
"can_favourite": {
"always": [
"public"
"public",
"me"
],
"with_approval": []
},
"can_reply": {
"always": [
"public"
"public",
"me"
],
"with_approval": []
},
"can_reblog": {
"always": [
"public"
"public",
"me"
],
"with_approval": []
}
Expand Down Expand Up @@ -1016,19 +1022,22 @@ func (suite *ReportsGetTestSuite) TestReportsGetTargetAccount() {
"interaction_policy": {
"can_favourite": {
"always": [
"public"
"public",
"me"
],
"with_approval": []
},
"can_reply": {
"always": [
"public"
"public",
"me"
],
"with_approval": []
},
"can_reblog": {
"always": [
"public"
"public",
"me"
],
"with_approval": []
}
Expand Down
73 changes: 36 additions & 37 deletions internal/api/client/statuses/statusboost_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,43 +173,42 @@ func (suite *StatusBoostTestSuite) TestPostBoostOwnFollowersOnly() {
}

// try to boost a status that's not boostable / visible to us
// TODO: sort this out with new interaction policies
// func (suite *StatusBoostTestSuite) TestPostUnboostable() {
// t := suite.testTokens["local_account_1"]
// oauthToken := oauth.DBTokenToToken(t)

// targetStatus := suite.testStatuses["local_account_2_status_4"]

// // setup
// recorder := httptest.NewRecorder()
// ctx, _ := testrig.CreateGinTestContext(recorder, nil)
// ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
// ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
// ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
// ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
// ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting
// ctx.Request.Header.Set("accept", "application/json")

// // normally the router would populate these params from the path values,
// // but because we're calling the function directly, we need to set them manually.
// ctx.Params = gin.Params{
// gin.Param{
// Key: statuses.IDKey,
// Value: targetStatus.ID,
// },
// }

// suite.statusModule.StatusBoostPOSTHandler(ctx)

// // check response
// suite.Equal(http.StatusNotFound, recorder.Code) // we 404 unboostable statuses

// result := recorder.Result()
// defer result.Body.Close()
// b, err := ioutil.ReadAll(result.Body)
// suite.NoError(err)
// suite.Equal(`{"error":"Not Found"}`, string(b))
// }
func (suite *StatusBoostTestSuite) TestPostUnboostable() {
t := suite.testTokens["local_account_1"]
oauthToken := oauth.DBTokenToToken(t)

targetStatus := suite.testStatuses["local_account_2_status_4"]

// setup
recorder := httptest.NewRecorder()
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting
ctx.Request.Header.Set("accept", "application/json")

// normally the router would populate these params from the path values,
// but because we're calling the function directly, we need to set them manually.
ctx.Params = gin.Params{
gin.Param{
Key: statuses.IDKey,
Value: targetStatus.ID,
},
}

suite.statusModule.StatusBoostPOSTHandler(ctx)

// check response
suite.Equal(http.StatusForbidden, recorder.Code)

result := recorder.Result()
defer result.Body.Close()
b, err := ioutil.ReadAll(result.Body)
suite.NoError(err)
suite.Equal(`{"error":"Forbidden: you do not have permission to boost this status"}`, string(b))
}

// try to boost a status that's not visible to the user
func (suite *StatusBoostTestSuite) TestPostNotVisible() {
Expand Down
73 changes: 36 additions & 37 deletions internal/api/client/statuses/statusfave_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,43 +89,42 @@ func (suite *StatusFaveTestSuite) TestPostFave() {
}

// try to fave a status that's not faveable
// TODO: replace this when interaction policies enforced.
// func (suite *StatusFaveTestSuite) TestPostUnfaveable() {
// t := suite.testTokens["local_account_1"]
// oauthToken := oauth.DBTokenToToken(t)

// targetStatus := suite.testStatuses["local_account_2_status_3"] // this one is unlikeable and unreplyable

// // setup
// recorder := httptest.NewRecorder()
// ctx, _ := testrig.CreateGinTestContext(recorder, nil)
// ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
// ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
// ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
// ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
// ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.FavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting
// ctx.Request.Header.Set("accept", "application/json")

// // normally the router would populate these params from the path values,
// // but because we're calling the function directly, we need to set them manually.
// ctx.Params = gin.Params{
// gin.Param{
// Key: statuses.IDKey,
// Value: targetStatus.ID,
// },
// }

// suite.statusModule.StatusFavePOSTHandler(ctx)

// // check response
// suite.EqualValues(http.StatusForbidden, recorder.Code)

// result := recorder.Result()
// defer result.Body.Close()
// b, err := ioutil.ReadAll(result.Body)
// assert.NoError(suite.T(), err)
// assert.Equal(suite.T(), `{"error":"Forbidden: status is not faveable"}`, string(b))
// }
func (suite *StatusFaveTestSuite) TestPostUnfaveable() {
t := suite.testTokens["admin_account"]
oauthToken := oauth.DBTokenToToken(t)

targetStatus := suite.testStatuses["local_account_1_status_3"] // this one is unlikeable

// setup
recorder := httptest.NewRecorder()
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["admin_account"])
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["admin_account"])
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.FavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting
ctx.Request.Header.Set("accept", "application/json")

// normally the router would populate these params from the path values,
// but because we're calling the function directly, we need to set them manually.
ctx.Params = gin.Params{
gin.Param{
Key: statuses.IDKey,
Value: targetStatus.ID,
},
}

suite.statusModule.StatusFavePOSTHandler(ctx)

// check response
suite.EqualValues(http.StatusForbidden, recorder.Code)

result := recorder.Result()
defer result.Body.Close()
b, err := ioutil.ReadAll(result.Body)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), `{"error":"Forbidden: you do not have permission to fave this status"}`, string(b))
}

func TestStatusFaveTestSuite(t *testing.T) {
suite.Run(t, new(StatusFaveTestSuite))
Expand Down
18 changes: 12 additions & 6 deletions internal/api/client/statuses/statusmute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,19 +151,22 @@ func (suite *StatusMuteTestSuite) TestMuteUnmuteStatus() {
"interaction_policy": {
"can_favourite": {
"always": [
"public"
"public",
"me"
],
"with_approval": []
},
"can_reply": {
"always": [
"public"
"public",
"me"
],
"with_approval": []
},
"can_reblog": {
"always": [
"public"
"public",
"me"
],
"with_approval": []
}
Expand Down Expand Up @@ -236,19 +239,22 @@ func (suite *StatusMuteTestSuite) TestMuteUnmuteStatus() {
"interaction_policy": {
"can_favourite": {
"always": [
"public"
"public",
"me"
],
"with_approval": []
},
"can_reply": {
"always": [
"public"
"public",
"me"
],
"with_approval": []
},
"can_reblog": {
"always": [
"public"
"public",
"me"
],
"with_approval": []
}
Expand Down
24 changes: 18 additions & 6 deletions internal/federation/dereferencing/announce.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,6 @@ func (d *Dereferencer) EnrichAnnounce(
return nil, err
}

// Generate an ID for the boost wrapper status.
boost.ID, err = id.NewULIDFromTime(boost.CreatedAt)
if err != nil {
return nil, gtserror.Newf("error generating id: %w", err)
}

// Set boost_of_uri again in case the
// original URI was an indirect link.
boost.BoostOfURI = target.URI
Expand All @@ -93,6 +87,24 @@ func (d *Dereferencer) EnrichAnnounce(
boost.Visibility = target.Visibility
boost.Federated = target.Federated

// Ensure this Announce is permitted by the Announcee.
permit, err := d.isPermittedStatus(ctx, requestUser, nil, boost)
if err != nil {
return nil, gtserror.Newf("error checking permitted status %s: %w", boost.URI, err)
}

if !permit {
// Return a checkable error type that can be ignored.
err := gtserror.Newf("dropping unpermitted status: %s", boost.URI)
return nil, gtserror.SetNotPermitted(err)
}

// Generate an ID for the boost wrapper status.
boost.ID, err = id.NewULIDFromTime(boost.CreatedAt)
if err != nil {
return nil, gtserror.Newf("error generating id: %w", err)
}

// Store the boost wrapper status in database.
switch err = d.state.DB.PutStatus(ctx, boost); {
case err == nil:
Expand Down
Loading

0 comments on commit f48f9fe

Please sign in to comment.