diff options
author | 2021-10-16 13:27:43 +0200 | |
---|---|---|
committer | 2021-10-16 13:27:43 +0200 | |
commit | 15621f5324b4613d83efb94711c97eeaa83da2b3 (patch) | |
tree | b86c837dec89f5c74a7127f1bcd8e224bf6dd8a6 /internal/processing | |
parent | User password change (#280) (diff) | |
download | gotosocial-15621f5324b4613d83efb94711c97eeaa83da2b3.tar.xz |
Follow request improvements (#282)
* tiny doc update
* add rejectfollowrequest to db
* add follow request reject to processor
* add reject handler
* tidy up follow request api
* tidy up federation call
* regenerate swagger docs
* api endpoint tests
* processor test
* add reject federatingdb handler
* start writing reject tests
* test reject follow request
* go fmt
* increase sleep for slow test setups
* more relaxed time.sleep
Diffstat (limited to 'internal/processing')
-rw-r--r-- | internal/processing/followrequest.go | 43 | ||||
-rw-r--r-- | internal/processing/followrequest_test.go | 143 | ||||
-rw-r--r-- | internal/processing/fromclientapi.go | 113 | ||||
-rw-r--r-- | internal/processing/fromfederator.go | 11 | ||||
-rw-r--r-- | internal/processing/processor.go | 4 | ||||
-rw-r--r-- | internal/processing/processor_test.go | 34 |
6 files changed, 332 insertions, 16 deletions
diff --git a/internal/processing/followrequest.go b/internal/processing/followrequest.go index 74bffd693..20df80f57 100644 --- a/internal/processing/followrequest.go +++ b/internal/processing/followrequest.go @@ -99,6 +99,45 @@ func (p *processor) FollowRequestAccept(ctx context.Context, auth *oauth.Auth, a return r, nil } -func (p *processor) FollowRequestDeny(ctx context.Context, auth *oauth.Auth) gtserror.WithCode { - return nil +func (p *processor) FollowRequestReject(ctx context.Context, auth *oauth.Auth, accountID string) (*apimodel.Relationship, gtserror.WithCode) { + followRequest, err := p.db.RejectFollowRequest(ctx, accountID, auth.Account.ID) + if err != nil { + return nil, gtserror.NewErrorNotFound(err) + } + + if followRequest.Account == nil { + a, err := p.db.GetAccountByID(ctx, followRequest.AccountID) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + followRequest.Account = a + } + + if followRequest.TargetAccount == nil { + a, err := p.db.GetAccountByID(ctx, followRequest.TargetAccountID) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + followRequest.TargetAccount = a + } + + p.fromClientAPI <- messages.FromClientAPI{ + APObjectType: ap.ActivityFollow, + APActivityType: ap.ActivityReject, + GTSModel: followRequest, + OriginAccount: followRequest.Account, + TargetAccount: followRequest.TargetAccount, + } + + gtsR, err := p.db.GetRelationship(ctx, auth.Account.ID, accountID) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + r, err := p.tc.RelationshipToAPIRelationship(ctx, gtsR) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + + return r, nil } diff --git a/internal/processing/followrequest_test.go b/internal/processing/followrequest_test.go new file mode 100644 index 000000000..e3817de77 --- /dev/null +++ b/internal/processing/followrequest_test.go @@ -0,0 +1,143 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + 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 processing_test + +import ( + "context" + "encoding/json" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/suite" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +type FollowRequestTestSuite struct { + ProcessingStandardTestSuite +} + +func (suite *FollowRequestTestSuite) TestFollowRequestAccept() { + requestingAccount := suite.testAccounts["remote_account_2"] + targetAccount := suite.testAccounts["local_account_1"] + + // put a follow request in the database + fr := >smodel.FollowRequest{ + ID: "01FJ1S8DX3STJJ6CEYPMZ1M0R3", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + URI: fmt.Sprintf("%s/follow/01FJ1S8DX3STJJ6CEYPMZ1M0R3", requestingAccount.URI), + AccountID: requestingAccount.ID, + TargetAccountID: targetAccount.ID, + } + + err := suite.db.Put(context.Background(), fr) + suite.NoError(err) + + relationship, errWithCode := suite.processor.FollowRequestAccept(context.Background(), suite.testAutheds["local_account_1"], requestingAccount.ID) + suite.NoError(errWithCode) + suite.EqualValues(&apimodel.Relationship{ID: "01FHMQX3GAABWSM0S2VZEC2SWC", Following: false, ShowingReblogs: false, Notifying: false, FollowedBy: true, Blocking: false, BlockedBy: false, Muting: false, MutingNotifications: false, Requested: false, DomainBlocking: false, Endorsed: false, Note: ""}, relationship) + time.Sleep(1 * time.Second) + + // accept should be sent to some_user + sent, ok := suite.sentHTTPRequests[requestingAccount.InboxURI] + suite.True(ok) + + accept := &struct { + Actor string `json:"actor"` + ID string `json:"id"` + Object struct { + Actor string `json:"actor"` + ID string `json:"id"` + Object string `json:"object"` + To string `json:"to"` + Type string `json:"type"` + } + To string `json:"to"` + Type string `json:"type"` + }{} + err = json.Unmarshal(sent, accept) + suite.NoError(err) + + suite.Equal(targetAccount.URI, accept.Actor) + suite.Equal(requestingAccount.URI, accept.Object.Actor) + suite.Equal(fr.URI, accept.Object.ID) + suite.Equal(targetAccount.URI, accept.Object.Object) + suite.Equal(targetAccount.URI, accept.Object.To) + suite.Equal("Follow", accept.Object.Type) + suite.Equal(requestingAccount.URI, accept.To) + suite.Equal("Accept", accept.Type) +} + +func (suite *FollowRequestTestSuite) TestFollowRequestReject() { + requestingAccount := suite.testAccounts["remote_account_2"] + targetAccount := suite.testAccounts["local_account_1"] + + // put a follow request in the database + fr := >smodel.FollowRequest{ + ID: "01FJ1S8DX3STJJ6CEYPMZ1M0R3", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + URI: fmt.Sprintf("%s/follow/01FJ1S8DX3STJJ6CEYPMZ1M0R3", requestingAccount.URI), + AccountID: requestingAccount.ID, + TargetAccountID: targetAccount.ID, + } + + err := suite.db.Put(context.Background(), fr) + suite.NoError(err) + + relationship, errWithCode := suite.processor.FollowRequestReject(context.Background(), suite.testAutheds["local_account_1"], requestingAccount.ID) + suite.NoError(errWithCode) + suite.EqualValues(&apimodel.Relationship{ID: "01FHMQX3GAABWSM0S2VZEC2SWC", Following: false, ShowingReblogs: false, Notifying: false, FollowedBy: false, Blocking: false, BlockedBy: false, Muting: false, MutingNotifications: false, Requested: false, DomainBlocking: false, Endorsed: false, Note: ""}, relationship) + time.Sleep(1 * time.Second) + + // reject should be sent to some_user + sent, ok := suite.sentHTTPRequests[requestingAccount.InboxURI] + suite.True(ok) + + reject := &struct { + Actor string `json:"actor"` + ID string `json:"id"` + Object struct { + Actor string `json:"actor"` + ID string `json:"id"` + Object string `json:"object"` + To string `json:"to"` + Type string `json:"type"` + } + To string `json:"to"` + Type string `json:"type"` + }{} + err = json.Unmarshal(sent, reject) + suite.NoError(err) + + suite.Equal(targetAccount.URI, reject.Actor) + suite.Equal(requestingAccount.URI, reject.Object.Actor) + suite.Equal(fr.URI, reject.Object.ID) + suite.Equal(targetAccount.URI, reject.Object.Object) + suite.Equal(targetAccount.URI, reject.Object.To) + suite.Equal("Follow", reject.Object.Type) + suite.Equal(requestingAccount.URI, reject.To) + suite.Equal("Reject", reject.Type) +} + +func TestFollowRequestTestSuite(t *testing.T) { + suite.Run(t, &FollowRequestTestSuite{}) +} diff --git a/internal/processing/fromclientapi.go b/internal/processing/fromclientapi.go index e15299d70..d4e8f5fa5 100644 --- a/internal/processing/fromclientapi.go +++ b/internal/processing/fromclientapi.go @@ -140,7 +140,19 @@ func (p *processor) ProcessFromClientAPI(ctx context.Context, clientMsg messages return err } - return p.federateAcceptFollowRequest(ctx, follow, clientMsg.OriginAccount, clientMsg.TargetAccount) + return p.federateAcceptFollowRequest(ctx, follow) + } + case ap.ActivityReject: + // REJECT + switch clientMsg.APObjectType { + case ap.ActivityFollow: + // REJECT FOLLOW (request) + followRequest, ok := clientMsg.GTSModel.(*gtsmodel.FollowRequest) + if !ok { + return errors.New("reject was not parseable as *gtsmodel.FollowRequest") + } + + return p.federateRejectFollowRequest(ctx, followRequest) } case ap.ActivityUndo: // UNDO @@ -453,7 +465,30 @@ func (p *processor) federateUnannounce(ctx context.Context, boost *gtsmodel.Stat return err } -func (p *processor) federateAcceptFollowRequest(ctx context.Context, follow *gtsmodel.Follow, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error { +func (p *processor) federateAcceptFollowRequest(ctx context.Context, follow *gtsmodel.Follow) error { + if follow.Account == nil { + a, err := p.db.GetAccountByID(ctx, follow.AccountID) + if err != nil { + return err + } + follow.Account = a + } + originAccount := follow.Account + + if follow.TargetAccount == nil { + a, err := p.db.GetAccountByID(ctx, follow.TargetAccountID) + if err != nil { + return err + } + follow.TargetAccount = a + } + targetAccount := follow.TargetAccount + + // if target account isn't from our domain we shouldn't do anything + if targetAccount.Domain != "" { + return nil + } + // if both accounts are local there's nothing to do here if originAccount.Domain == "" && targetAccount.Domain == "" { return nil @@ -503,6 +538,80 @@ func (p *processor) federateAcceptFollowRequest(ctx context.Context, follow *gts return err } +func (p *processor) federateRejectFollowRequest(ctx context.Context, followRequest *gtsmodel.FollowRequest) error { + if followRequest.Account == nil { + a, err := p.db.GetAccountByID(ctx, followRequest.AccountID) + if err != nil { + return err + } + followRequest.Account = a + } + originAccount := followRequest.Account + + if followRequest.TargetAccount == nil { + a, err := p.db.GetAccountByID(ctx, followRequest.TargetAccountID) + if err != nil { + return err + } + followRequest.TargetAccount = a + } + targetAccount := followRequest.TargetAccount + + // if target account isn't from our domain we shouldn't do anything + if targetAccount.Domain != "" { + return nil + } + + // if both accounts are local there's nothing to do here + if originAccount.Domain == "" && targetAccount.Domain == "" { + return nil + } + + // recreate the AS follow + follow := p.tc.FollowRequestToFollow(ctx, followRequest) + asFollow, err := p.tc.FollowToAS(ctx, follow, originAccount, targetAccount) + if err != nil { + return fmt.Errorf("federateUnfollow: error converting follow to as format: %s", err) + } + + rejectingAccountURI, err := url.Parse(targetAccount.URI) + if err != nil { + return fmt.Errorf("error parsing uri %s: %s", targetAccount.URI, err) + } + + requestingAccountURI, err := url.Parse(originAccount.URI) + if err != nil { + return fmt.Errorf("error parsing uri %s: %s", targetAccount.URI, err) + } + + // create a Reject + reject := streams.NewActivityStreamsReject() + + // set the rejecting actor on it + acceptActorProp := streams.NewActivityStreamsActorProperty() + acceptActorProp.AppendIRI(rejectingAccountURI) + reject.SetActivityStreamsActor(acceptActorProp) + + // Set the recreated follow as the 'object' property. + acceptObject := streams.NewActivityStreamsObjectProperty() + acceptObject.AppendActivityStreamsFollow(asFollow) + reject.SetActivityStreamsObject(acceptObject) + + // Set the To of the reject as the originator of the follow + acceptTo := streams.NewActivityStreamsToProperty() + acceptTo.AppendIRI(requestingAccountURI) + reject.SetActivityStreamsTo(acceptTo) + + outboxIRI, err := url.Parse(targetAccount.OutboxURI) + if err != nil { + return fmt.Errorf("federateRejectFollowRequest: error parsing outboxURI %s: %s", originAccount.OutboxURI, err) + } + + // send off the reject using the rejecting account's outbox + _, err = p.federator.FederatingActor().Send(ctx, outboxIRI, reject) + return err +} + func (p *processor) federateFave(ctx context.Context, fave *gtsmodel.StatusFave, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error { // if both accounts are local there's nothing to do here if originAccount.Domain == "" && targetAccount.Domain == "" { diff --git a/internal/processing/fromfederator.go b/internal/processing/fromfederator.go index 8a36fb9b7..243c0f3d7 100644 --- a/internal/processing/fromfederator.go +++ b/internal/processing/fromfederator.go @@ -161,22 +161,13 @@ func (p *processor) processCreateFollowRequestFromFederator(ctx context.Context, return p.notifyFollowRequest(ctx, followRequest) } - if followRequest.Account == nil { - a, err := p.db.GetAccountByID(ctx, followRequest.AccountID) - if err != nil { - return err - } - followRequest.Account = a - } - originAccount := followRequest.Account - // if the target account isn't locked, we should already accept the follow and notify about the new follower instead follow, err := p.db.AcceptFollowRequest(ctx, followRequest.AccountID, followRequest.TargetAccountID) if err != nil { return err } - if err := p.federateAcceptFollowRequest(ctx, follow, originAccount, targetAccount); err != nil { + if err := p.federateAcceptFollowRequest(ctx, follow); err != nil { return err } diff --git a/internal/processing/processor.go b/internal/processing/processor.go index e61661dc9..8fd372735 100644 --- a/internal/processing/processor.go +++ b/internal/processing/processor.go @@ -118,8 +118,10 @@ type Processor interface { // FollowRequestsGet handles the getting of the authed account's incoming follow requests FollowRequestsGet(ctx context.Context, auth *oauth.Auth) ([]apimodel.Account, gtserror.WithCode) - // FollowRequestAccept handles the acceptance of a follow request from the given account ID + // FollowRequestAccept handles the acceptance of a follow request from the given account ID. FollowRequestAccept(ctx context.Context, auth *oauth.Auth, accountID string) (*apimodel.Relationship, gtserror.WithCode) + // FollowRequestReject handles the rejection of a follow request from the given account ID. + FollowRequestReject(ctx context.Context, auth *oauth.Auth, accountID string) (*apimodel.Relationship, gtserror.WithCode) // InstanceGet retrieves instance information for serving at api/v1/instance InstanceGet(ctx context.Context, domain string) (*apimodel.Instance, gtserror.WithCode) diff --git a/internal/processing/processor_test.go b/internal/processing/processor_test.go index f2a9e455e..beae5dba0 100644 --- a/internal/processing/processor_test.go +++ b/internal/processing/processor_test.go @@ -96,9 +96,9 @@ func (suite *ProcessingStandardTestSuite) SetupSuite() { } func (suite *ProcessingStandardTestSuite) SetupTest() { + testrig.InitTestLog() suite.config = testrig.NewTestConfig() suite.db = testrig.NewTestDB() - testrig.InitTestLog() suite.storage = testrig.NewTestStorage() suite.typeconverter = testrig.NewTestTypeConverter(suite.db) @@ -149,6 +149,38 @@ func (suite *ProcessingStandardTestSuite) SetupTest() { return response, nil } + if req.URL.String() == suite.testAccounts["remote_account_2"].URI { + // the request is for remote account 2 + someAccount := suite.testAccounts["remote_account_2"] + + someAccountAS, err := suite.typeconverter.AccountToAS(context.Background(), someAccount) + if err != nil { + panic(err) + } + + someAccountI, err := streams.Serialize(someAccountAS) + if err != nil { + panic(err) + } + someAccountJson, err := json.Marshal(someAccountI) + if err != nil { + panic(err) + } + responseType := "application/activity+json" + + reader := bytes.NewReader(someAccountJson) + readCloser := io.NopCloser(reader) + response := &http.Response{ + StatusCode: 200, + Body: readCloser, + ContentLength: int64(len(someAccountJson)), + Header: http.Header{ + "content-type": {responseType}, + }, + } + return response, nil + } + if req.URL.String() == "http://example.org/users/some_user/statuses/afaba698-5740-4e32-a702-af61aa543bc1" { // the request is for the forwarded message message := suite.testActivities["forwarded_message"].Activity.GetActivityStreamsObject().At(0).GetActivityStreamsNote() |