summaryrefslogtreecommitdiff
path: root/internal/processing
diff options
context:
space:
mode:
Diffstat (limited to 'internal/processing')
-rw-r--r--internal/processing/account/follow.go5
-rw-r--r--internal/processing/admin/rule.go9
-rw-r--r--internal/processing/application/create.go5
-rw-r--r--internal/processing/fedi/accept.go42
-rw-r--r--internal/processing/fedi/authorization.go57
-rw-r--r--internal/processing/fedi/common.go50
-rw-r--r--internal/processing/interactionrequests/accept.go14
-rw-r--r--internal/processing/interactionrequests/accept_test.go2
-rw-r--r--internal/processing/interactionrequests/reject.go4
-rw-r--r--internal/processing/search/get.go2
-rw-r--r--internal/processing/workers/federate.go18
-rw-r--r--internal/processing/workers/fromclientapi.go93
-rw-r--r--internal/processing/workers/fromfediapi.go503
-rw-r--r--internal/processing/workers/fromfediapi_test.go120
-rw-r--r--internal/processing/workers/util.go36
15 files changed, 767 insertions, 193 deletions
diff --git a/internal/processing/account/follow.go b/internal/processing/account/follow.go
index 26d552bbb..91955eaa7 100644
--- a/internal/processing/account/follow.go
+++ b/internal/processing/account/follow.go
@@ -82,10 +82,7 @@ func (p *Processor) FollowCreate(ctx context.Context, requestingAccount *gtsmode
// Neither follows nor follow requests, so
// create and store a new follow request.
- followID, err := id.NewRandomULID()
- if err != nil {
- return nil, gtserror.NewErrorInternalError(err)
- }
+ followID := id.NewRandomULID()
followURI := uris.GenerateURIForFollow(requestingAccount.Username, followID)
fr := &gtsmodel.FollowRequest{
diff --git a/internal/processing/admin/rule.go b/internal/processing/admin/rule.go
index a665da9a1..de19cba0b 100644
--- a/internal/processing/admin/rule.go
+++ b/internal/processing/admin/rule.go
@@ -64,17 +64,12 @@ func (p *Processor) RuleGet(ctx context.Context, id string) (*apimodel.AdminInst
// RuleCreate adds a new rule to the instance.
func (p *Processor) RuleCreate(ctx context.Context, form *apimodel.InstanceRuleCreateRequest) (*apimodel.AdminInstanceRule, gtserror.WithCode) {
- ruleID, err := id.NewRandomULID()
- if err != nil {
- return nil, gtserror.NewErrorInternalError(fmt.Errorf("error creating id for new instance rule: %s", err), "error creating rule ID")
- }
-
rule := &gtsmodel.Rule{
- ID: ruleID,
+ ID: id.NewRandomULID(),
Text: form.Text,
}
- if err = p.state.DB.PutRule(ctx, rule); err != nil {
+ if err := p.state.DB.PutRule(ctx, rule); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
diff --git a/internal/processing/application/create.go b/internal/processing/application/create.go
index 7ee6fb6b9..d63b682d6 100644
--- a/internal/processing/application/create.go
+++ b/internal/processing/application/create.go
@@ -86,10 +86,7 @@ func (p *Processor) Create(
}
// Generate random client ID.
- clientID, err := id.NewRandomULID()
- if err != nil {
- return nil, gtserror.NewErrorInternalError(err)
- }
+ clientID := id.NewRandomULID()
// Generate + store app
// to put in the database.
diff --git a/internal/processing/fedi/accept.go b/internal/processing/fedi/accept.go
index e63b460db..97e36fbb3 100644
--- a/internal/processing/fedi/accept.go
+++ b/internal/processing/fedi/accept.go
@@ -19,10 +19,8 @@ package fedi
import (
"context"
- "errors"
"code.superseriousbusiness.org/gotosocial/internal/ap"
- "code.superseriousbusiness.org/gotosocial/internal/db"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
)
@@ -34,44 +32,18 @@ import (
func (p *Processor) AcceptGet(
ctx context.Context,
requestedUser string,
- reqID string,
-) (interface{}, gtserror.WithCode) {
- // Authenticate incoming request, getting related accounts.
- auth, errWithCode := p.authenticate(ctx, requestedUser)
+ intReqID string,
+) (any, gtserror.WithCode) {
+ // Ensure valid request, intReq exists, etc.
+ intReq, errWithCode := p.validateIntReqRequest(ctx, requestedUser, intReqID)
if errWithCode != nil {
return nil, errWithCode
}
- if auth.handshakingURI != nil {
- // We're currently handshaking, which means
- // we don't know this account yet. This should
- // be a very rare race condition.
- err := gtserror.Newf("network race handshaking %s", auth.handshakingURI)
- return nil, gtserror.NewErrorInternalError(err)
- }
-
- receivingAcct := auth.receivingAcct
-
- req, err := p.state.DB.GetInteractionRequestByID(ctx, reqID)
- if err != nil && !errors.Is(err, db.ErrNoEntries) {
- err := gtserror.Newf("db error getting interaction request %s: %w", reqID, err)
- return nil, gtserror.NewErrorInternalError(err)
- }
-
- if req == nil || !req.IsAccepted() {
- // Request doesn't exist or hasn't been accepted.
- err := gtserror.Newf("interaction request %s not found", reqID)
- return nil, gtserror.NewErrorNotFound(err)
- }
-
- if req.TargetAccountID != receivingAcct.ID {
- const text = "interaction request does not belong to receiving account"
- return nil, gtserror.NewErrorNotFound(errors.New(text))
- }
-
- accept, err := p.converter.InteractionReqToASAccept(ctx, req)
+ // Convert + serialize the Accept.
+ accept, err := p.converter.InteractionReqToASAccept(ctx, intReq)
if err != nil {
- err := gtserror.Newf("error converting accept: %w", err)
+ err := gtserror.Newf("error converting to accept: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
diff --git a/internal/processing/fedi/authorization.go b/internal/processing/fedi/authorization.go
new file mode 100644
index 000000000..bbba6a2d8
--- /dev/null
+++ b/internal/processing/fedi/authorization.go
@@ -0,0 +1,57 @@
+// 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 fedi
+
+import (
+ "context"
+
+ "code.superseriousbusiness.org/gotosocial/internal/ap"
+ "code.superseriousbusiness.org/gotosocial/internal/gtserror"
+)
+
+// AuthorizationGet handles the getting of a fedi/activitypub
+// representation of a local interaction authorization.
+//
+// It performs appropriate authentication before
+// returning a JSON serializable interface.
+func (p *Processor) AuthorizationGet(
+ ctx context.Context,
+ requestedUser string,
+ intReqID string,
+) (any, gtserror.WithCode) {
+ // Ensure valid request, intReq exists, etc.
+ intReq, errWithCode := p.validateIntReqRequest(ctx, requestedUser, intReqID)
+ if errWithCode != nil {
+ return nil, errWithCode
+ }
+
+ // Convert + serialize the Authorization.
+ authorization, err := p.converter.InteractionReqToASAuthorization(ctx, intReq)
+ if err != nil {
+ err := gtserror.Newf("error converting to authorization: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ data, err := ap.Serialize(authorization)
+ if err != nil {
+ err := gtserror.Newf("error serializing accept: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ return data, nil
+}
diff --git a/internal/processing/fedi/common.go b/internal/processing/fedi/common.go
index 9059aef39..fc783f93e 100644
--- a/internal/processing/fedi/common.go
+++ b/internal/processing/fedi/common.go
@@ -20,6 +20,7 @@ package fedi
import (
"context"
"errors"
+ "fmt"
"net/url"
"code.superseriousbusiness.org/gotosocial/internal/db"
@@ -81,3 +82,52 @@ func (p *Processor) authenticate(ctx context.Context, requestedUser string) (*co
receivingAcct: receiver,
}, nil
}
+
+// validateIntReqRequest is a shortcut function
+// for returning an accepted interaction request
+// targeting `requestedUser`.
+func (p *Processor) validateIntReqRequest(
+ ctx context.Context,
+ requestedUser string,
+ intReqID string,
+) (*gtsmodel.InteractionRequest, gtserror.WithCode) {
+ // Authenticate incoming request, getting related accounts.
+ auth, errWithCode := p.authenticate(ctx, requestedUser)
+ if errWithCode != nil {
+ return nil, errWithCode
+ }
+
+ if auth.handshakingURI != nil {
+ // We're currently handshaking, which means we don't know
+ // this account yet. This should be a very rare race condition.
+ err := gtserror.Newf("network race handshaking %s", auth.handshakingURI)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ // Fetch interaction request with the given ID.
+ req, err := p.state.DB.GetInteractionRequestByID(ctx, intReqID)
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ err := gtserror.Newf("db error getting interaction request %s: %w", intReqID, err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ // Ensure that this is an existing
+ // and *accepted* interaction request.
+ if req == nil || !req.IsAccepted() {
+ const text = "interaction request not found"
+ return nil, gtserror.NewErrorNotFound(errors.New(text))
+ }
+
+ // Ensure interaction request was accepted
+ // by the account in the request path.
+ if req.TargetAccountID != auth.receivingAcct.ID {
+ text := fmt.Sprintf(
+ "account %s is not targeted by interaction request %s and therefore can't accept it",
+ requestedUser, intReqID,
+ )
+ return nil, gtserror.NewErrorNotFound(errors.New(text))
+ }
+
+ // All fine.
+ return req, nil
+}
diff --git a/internal/processing/interactionrequests/accept.go b/internal/processing/interactionrequests/accept.go
index ce682380b..7efd1f373 100644
--- a/internal/processing/interactionrequests/accept.go
+++ b/internal/processing/interactionrequests/accept.go
@@ -65,14 +65,16 @@ func (p *Processor) Accept(
defer unlock()
// Mark the request as accepted
- // and generate a URI for it.
+ // and generate URIs for it.
req.AcceptedAt = time.Now()
- req.URI = uris.GenerateURIForAccept(acct.Username, req.ID)
+ req.ResponseURI = uris.GenerateURIForAccept(acct.Username, req.ID)
+ req.AuthorizationURI = uris.GenerateURIForAuthorization(acct.Username, req.ID)
if err := p.state.DB.UpdateInteractionRequest(
ctx,
req,
"accepted_at",
- "uri",
+ "response_uri",
+ "authorization_uri",
); err != nil {
err := gtserror.Newf("db error updating interaction request: %w", err)
return nil, gtserror.NewErrorInternalError(err)
@@ -132,7 +134,7 @@ func (p *Processor) acceptLike(
// Update the Like.
req.Like.PendingApproval = util.Ptr(false)
req.Like.PreApproved = false
- req.Like.ApprovedByURI = req.URI
+ req.Like.ApprovedByURI = req.AuthorizationURI
if err := p.state.DB.UpdateStatusFave(
ctx,
req.Like,
@@ -173,7 +175,7 @@ func (p *Processor) acceptReply(
// Update the Reply.
req.Reply.PendingApproval = util.Ptr(false)
req.Reply.PreApproved = false
- req.Reply.ApprovedByURI = req.URI
+ req.Reply.ApprovedByURI = req.AuthorizationURI
if err := p.state.DB.UpdateStatus(
ctx,
req.Reply,
@@ -214,7 +216,7 @@ func (p *Processor) acceptAnnounce(
// Update the Announce.
req.Announce.PendingApproval = util.Ptr(false)
req.Announce.PreApproved = false
- req.Announce.ApprovedByURI = req.URI
+ req.Announce.ApprovedByURI = req.AuthorizationURI
if err := p.state.DB.UpdateStatus(
ctx,
req.Announce,
diff --git a/internal/processing/interactionrequests/accept_test.go b/internal/processing/interactionrequests/accept_test.go
index b48978f2c..cb4212c24 100644
--- a/internal/processing/interactionrequests/accept_test.go
+++ b/internal/processing/interactionrequests/accept_test.go
@@ -67,7 +67,7 @@ func (suite *AcceptTestSuite) TestAccept() {
suite.FailNow(err.Error())
}
suite.False(*dbStatus.PendingApproval)
- suite.Equal(dbReq.URI, dbStatus.ApprovedByURI)
+ suite.Equal(dbReq.AuthorizationURI, dbStatus.ApprovedByURI)
// Wait for a notification
// for interacting status.
diff --git a/internal/processing/interactionrequests/reject.go b/internal/processing/interactionrequests/reject.go
index 3ceaa47d9..4db52e260 100644
--- a/internal/processing/interactionrequests/reject.go
+++ b/internal/processing/interactionrequests/reject.go
@@ -66,12 +66,12 @@ func (p *Processor) Reject(
// Mark the request as rejected
// and generate a URI for it.
req.RejectedAt = time.Now()
- req.URI = uris.GenerateURIForReject(acct.Username, req.ID)
+ req.ResponseURI = uris.GenerateURIForReject(acct.Username, req.ID)
if err := p.state.DB.UpdateInteractionRequest(
ctx,
req,
"rejected_at",
- "uri",
+ "response_uri",
); err != nil {
err := gtserror.Newf("db error updating interaction request: %w", err)
return nil, gtserror.NewErrorInternalError(err)
diff --git a/internal/processing/search/get.go b/internal/processing/search/get.go
index 2e956b049..64aefd23c 100644
--- a/internal/processing/search/get.go
+++ b/internal/processing/search/get.go
@@ -524,7 +524,7 @@ func (p *Processor) byURI(
switch {
case gtserror.IsUnretrievable(err),
gtserror.IsWrongType(err),
- gtserror.NotPermitted(err):
+ gtserror.IsNotPermitted(err):
log.Debugf(ctx,
"semi-expected error type looking up %s as status: %v",
uri, err,
diff --git a/internal/processing/workers/federate.go b/internal/processing/workers/federate.go
index 3cc3d130c..7459f0114 100644
--- a/internal/processing/workers/federate.go
+++ b/internal/processing/workers/federate.go
@@ -115,7 +115,7 @@ func (f *federate) DeleteAccount(ctx context.Context, account *gtsmodel.Account)
// Address the delete CC public.
deleteCC := streams.NewActivityStreamsCcProperty()
- deleteCC.AppendIRI(ap.PublicURI())
+ deleteCC.AppendIRI(ap.PublicIRI())
delete.SetActivityStreamsCc(deleteCC)
// Send the Delete via the Actor's outbox.
@@ -491,12 +491,7 @@ func (f *federate) UndoAnnounce(ctx context.Context, boost *gtsmodel.Status) err
}
// Recreate the ActivityStreams Announce.
- asAnnounce, err := f.converter.BoostToAS(
- ctx,
- boost,
- boost.Account,
- boost.BoostOfAccount,
- )
+ asAnnounce, err := f.converter.BoostToAS(ctx, boost)
if err != nil {
return gtserror.Newf("error converting boost to AS: %w", err)
}
@@ -767,12 +762,7 @@ func (f *federate) Announce(ctx context.Context, boost *gtsmodel.Status) error {
}
// Create the ActivityStreams Announce.
- announce, err := f.converter.BoostToAS(
- ctx,
- boost,
- boost.Account,
- boost.BoostOfAccount,
- )
+ announce, err := f.converter.BoostToAS(ctx, boost)
if err != nil {
return gtserror.Newf("error converting boost to AS: %w", err)
}
@@ -1104,7 +1094,7 @@ func (f *federate) MoveAccount(ctx context.Context, account *gtsmodel.Account) e
ap.AppendTo(move, followersIRI)
// Address the move CC public.
- ap.AppendCc(move, ap.PublicURI())
+ ap.AppendCc(move, ap.PublicIRI())
// Send the Move via the Actor's outbox.
if _, err := f.FederatingActor().Send(
diff --git a/internal/processing/workers/fromclientapi.go b/internal/processing/workers/fromclientapi.go
index 22e7780f6..9cdbcc548 100644
--- a/internal/processing/workers/fromclientapi.go
+++ b/internal/processing/workers/fromclientapi.go
@@ -287,7 +287,7 @@ func (p *clientAPI) CreateStatus(ctx context.Context, cMsg *messages.FromClientA
// and/or notify the account that's being
// interacted with (if it's local): they can
// approve or deny the interaction later.
- if err := p.utils.requestReply(ctx, status); err != nil {
+ if err := p.utils.impoliteReplyRequest(ctx, status); err != nil {
return gtserror.Newf("error pending reply: %w", err)
}
@@ -310,19 +310,22 @@ func (p *clientAPI) CreateStatus(ctx context.Context, cMsg *messages.FromClientA
// URI attached.
// Store an already-accepted interaction request.
- id := id.NewULID()
+ requestID := id.NewULID()
approval := &gtsmodel.InteractionRequest{
- ID: id,
- StatusID: status.InReplyToID,
- TargetAccountID: status.InReplyToAccountID,
- TargetAccount: status.InReplyToAccount,
- InteractingAccountID: status.AccountID,
- InteractingAccount: status.Account,
- InteractionURI: status.URI,
- InteractionType: gtsmodel.InteractionReply,
- Reply: status,
- URI: uris.GenerateURIForAccept(status.InReplyToAccount.Username, id),
- AcceptedAt: time.Now(),
+ ID: requestID,
+ TargetStatusID: status.InReplyToID,
+ TargetAccountID: status.InReplyToAccountID,
+ TargetAccount: status.InReplyToAccount,
+ InteractingAccountID: status.AccountID,
+ InteractingAccount: status.Account,
+ InteractionRequestURI: gtsmodel.ForwardCompatibleInteractionRequestURI(status.URI, gtsmodel.ReplyRequestSuffix),
+ InteractionURI: status.URI,
+ InteractionType: gtsmodel.InteractionReply,
+ Polite: util.Ptr(false), // TODO: Change this in v0.21.0 when we only send out polite requests.
+ Reply: status,
+ ResponseURI: uris.GenerateURIForAccept(status.InReplyToAccount.Username, requestID),
+ AuthorizationURI: uris.GenerateURIForAuthorization(status.InReplyToAccount.Username, requestID),
+ AcceptedAt: time.Now(),
}
if err := p.state.DB.PutInteractionRequest(ctx, approval); err != nil {
return gtserror.Newf("db error putting pre-approved interaction request: %w", err)
@@ -331,7 +334,7 @@ func (p *clientAPI) CreateStatus(ctx context.Context, cMsg *messages.FromClientA
// Mark the status as now approved.
status.PendingApproval = util.Ptr(false)
status.PreApproved = false
- status.ApprovedByURI = approval.URI
+ status.ApprovedByURI = approval.AuthorizationURI
if err := p.state.DB.UpdateStatus(
ctx,
status,
@@ -494,7 +497,7 @@ func (p *clientAPI) CreateLike(ctx context.Context, cMsg *messages.FromClientAPI
// and/or notify the account that's being
// interacted with (if it's local): they can
// approve or deny the interaction later.
- if err := p.utils.requestFave(ctx, fave); err != nil {
+ if err := p.utils.impoliteFaveRequest(ctx, fave); err != nil {
return gtserror.Newf("error pending fave: %w", err)
}
@@ -517,19 +520,22 @@ func (p *clientAPI) CreateLike(ctx context.Context, cMsg *messages.FromClientAPI
// URI attached.
// Store an already-accepted interaction request.
- id := id.NewULID()
+ requestID := id.NewULID()
approval := &gtsmodel.InteractionRequest{
- ID: id,
- StatusID: fave.StatusID,
- TargetAccountID: fave.TargetAccountID,
- TargetAccount: fave.TargetAccount,
- InteractingAccountID: fave.AccountID,
- InteractingAccount: fave.Account,
- InteractionURI: fave.URI,
- InteractionType: gtsmodel.InteractionLike,
- Like: fave,
- URI: uris.GenerateURIForAccept(fave.TargetAccount.Username, id),
- AcceptedAt: time.Now(),
+ ID: requestID,
+ TargetStatusID: fave.StatusID,
+ TargetAccountID: fave.TargetAccountID,
+ TargetAccount: fave.TargetAccount,
+ InteractingAccountID: fave.AccountID,
+ InteractingAccount: fave.Account,
+ InteractionRequestURI: gtsmodel.ForwardCompatibleInteractionRequestURI(fave.URI, gtsmodel.LikeRequestSuffix),
+ InteractionURI: fave.URI,
+ InteractionType: gtsmodel.InteractionLike,
+ Polite: util.Ptr(false), // TODO: Change this in v0.21.0 when we only send out polite requests.
+ Like: fave,
+ ResponseURI: uris.GenerateURIForAccept(fave.TargetAccount.Username, requestID),
+ AuthorizationURI: uris.GenerateURIForAuthorization(fave.TargetAccount.Username, requestID),
+ AcceptedAt: time.Now(),
}
if err := p.state.DB.PutInteractionRequest(ctx, approval); err != nil {
return gtserror.Newf("db error putting pre-approved interaction request: %w", err)
@@ -538,7 +544,7 @@ func (p *clientAPI) CreateLike(ctx context.Context, cMsg *messages.FromClientAPI
// Mark the fave itself as now approved.
fave.PendingApproval = util.Ptr(false)
fave.PreApproved = false
- fave.ApprovedByURI = approval.URI
+ fave.ApprovedByURI = approval.AuthorizationURI
if err := p.state.DB.UpdateStatusFave(
ctx,
fave,
@@ -589,7 +595,7 @@ func (p *clientAPI) CreateAnnounce(ctx context.Context, cMsg *messages.FromClien
// and/or notify the account that's being
// interacted with (if it's local): they can
// approve or deny the interaction later.
- if err := p.utils.requestAnnounce(ctx, boost); err != nil {
+ if err := p.utils.impoliteAnnounceRequest(ctx, boost); err != nil {
return gtserror.Newf("error pending boost: %w", err)
}
@@ -612,19 +618,22 @@ func (p *clientAPI) CreateAnnounce(ctx context.Context, cMsg *messages.FromClien
// URI attached.
// Store an already-accepted interaction request.
- id := id.NewULID()
+ requestID := id.NewULID()
approval := &gtsmodel.InteractionRequest{
- ID: id,
- StatusID: boost.BoostOfID,
- TargetAccountID: boost.BoostOfAccountID,
- TargetAccount: boost.BoostOfAccount,
- InteractingAccountID: boost.AccountID,
- InteractingAccount: boost.Account,
- InteractionURI: boost.URI,
- InteractionType: gtsmodel.InteractionAnnounce,
- Announce: boost,
- URI: uris.GenerateURIForAccept(boost.BoostOfAccount.Username, id),
- AcceptedAt: time.Now(),
+ ID: requestID,
+ TargetStatusID: boost.BoostOfID,
+ TargetAccountID: boost.BoostOfAccountID,
+ TargetAccount: boost.BoostOfAccount,
+ InteractingAccountID: boost.AccountID,
+ InteractingAccount: boost.Account,
+ InteractionRequestURI: gtsmodel.ForwardCompatibleInteractionRequestURI(boost.URI, gtsmodel.AnnounceRequestSuffix),
+ InteractionURI: boost.URI,
+ InteractionType: gtsmodel.InteractionAnnounce,
+ Polite: util.Ptr(false), // TODO: Change this in v0.21.0 when we only send out polite requests.
+ Announce: boost,
+ ResponseURI: uris.GenerateURIForAccept(boost.BoostOfAccount.Username, requestID),
+ AuthorizationURI: uris.GenerateURIForAuthorization(boost.BoostOfAccount.Username, requestID),
+ AcceptedAt: time.Now(),
}
if err := p.state.DB.PutInteractionRequest(ctx, approval); err != nil {
return gtserror.Newf("db error putting pre-approved interaction request: %w", err)
@@ -633,7 +642,7 @@ func (p *clientAPI) CreateAnnounce(ctx context.Context, cMsg *messages.FromClien
// Mark the boost itself as now approved.
boost.PendingApproval = util.Ptr(false)
boost.PreApproved = false
- boost.ApprovedByURI = approval.URI
+ boost.ApprovedByURI = approval.AuthorizationURI
if err := p.state.DB.UpdateStatus(
ctx,
boost,
diff --git a/internal/processing/workers/fromfediapi.go b/internal/processing/workers/fromfediapi.go
index 09c1df480..797a2d9c6 100644
--- a/internal/processing/workers/fromfediapi.go
+++ b/internal/processing/workers/fromfediapi.go
@@ -88,6 +88,10 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF
case ap.ObjectNote:
return p.fediAPI.CreateStatus(ctx, fMsg)
+ // REQUEST TO REPLY TO A STATUS
+ case ap.ActivityReplyRequest:
+ return p.fediAPI.CreateReplyRequest(ctx, fMsg)
+
// CREATE FOLLOW (request)
case ap.ActivityFollow:
return p.fediAPI.CreateFollowReq(ctx, fMsg)
@@ -96,10 +100,18 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF
case ap.ActivityLike:
return p.fediAPI.CreateLike(ctx, fMsg)
+ // REQUEST TO LIKE A STATUS
+ case ap.ActivityLikeRequest:
+ return p.fediAPI.CreateLikeRequest(ctx, fMsg)
+
// CREATE ANNOUNCE/BOOST
case ap.ActivityAnnounce:
return p.fediAPI.CreateAnnounce(ctx, fMsg)
+ // REQUEST TO BOOST A STATUS
+ case ap.ActivityAnnounceRequest:
+ return p.fediAPI.CreateAnnounceRequest(ctx, fMsg)
+
// CREATE BLOCK
case ap.ActivityBlock:
return p.fediAPI.CreateBlock(ctx, fMsg)
@@ -146,11 +158,15 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF
case ap.ObjectNote:
return p.fediAPI.AcceptReply(ctx, fMsg)
+ // ACCEPT (pending) POLITE REPLY REQUEST
+ case ap.ActivityReplyRequest:
+ return p.fediAPI.AcceptPoliteReplyRequest(ctx, fMsg)
+
// ACCEPT (pending) ANNOUNCE
case ap.ActivityAnnounce:
return p.fediAPI.AcceptAnnounce(ctx, fMsg)
- // ACCEPT (remote) REPLY or ANNOUNCE
+ // ACCEPT (remote) IMPOLITE REPLY or ANNOUNCE
case ap.ObjectUnknown:
return p.fediAPI.AcceptRemoteStatus(ctx, fMsg)
}
@@ -219,6 +235,9 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF
return gtserror.Newf("unhandled: %s %s", fMsg.APActivityType, fMsg.APObjectType)
}
+// CreateStatus handles the creation of a status/post sent as a Create message.
+// It is also capable of handling impolite reply requests to local + remote statuses,
+// ie., replies sent directly without doing the ReplyRequest process first.
func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI) error {
var (
status *gtsmodel.Status
@@ -291,7 +310,7 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
// preapproved, then just notify the account
// that's being interacted with: they can
// approve or deny the interaction later.
- if err := p.utils.requestReply(ctx, status); err != nil {
+ if err := p.utils.impoliteReplyRequest(ctx, status); err != nil {
return gtserror.Newf("error pending reply: %w", err)
}
@@ -306,20 +325,24 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
// collection. Do the Accept immediately and
// then process everything else as normal.
- // Store an already-accepted interaction request.
- id := id.NewULID()
+ // Store an already-accepted
+ // impolite interaction request.
+ requestID := id.NewULID()
approval := &gtsmodel.InteractionRequest{
- ID: id,
- StatusID: status.InReplyToID,
- TargetAccountID: status.InReplyToAccountID,
- TargetAccount: status.InReplyToAccount,
- InteractingAccountID: status.AccountID,
- InteractingAccount: status.Account,
- InteractionURI: status.URI,
- InteractionType: gtsmodel.InteractionReply,
- Reply: status,
- URI: uris.GenerateURIForAccept(status.InReplyToAccount.Username, id),
- AcceptedAt: time.Now(),
+ ID: requestID,
+ TargetStatusID: status.InReplyToID,
+ TargetAccountID: status.InReplyToAccountID,
+ TargetAccount: status.InReplyToAccount,
+ InteractingAccountID: status.AccountID,
+ InteractingAccount: status.Account,
+ InteractionRequestURI: gtsmodel.ForwardCompatibleInteractionRequestURI(status.URI, gtsmodel.ReplyRequestSuffix),
+ InteractionURI: status.URI,
+ InteractionType: gtsmodel.InteractionReply,
+ Polite: util.Ptr(false),
+ Reply: status,
+ ResponseURI: uris.GenerateURIForAccept(status.InReplyToAccount.Username, requestID),
+ AuthorizationURI: uris.GenerateURIForAuthorization(status.InReplyToAccount.Username, requestID),
+ AcceptedAt: time.Now(),
}
if err := p.state.DB.PutInteractionRequest(ctx, approval); err != nil {
return gtserror.Newf("db error putting pre-approved interaction request: %w", err)
@@ -328,7 +351,7 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
// Mark the status as now approved.
status.PendingApproval = util.Ptr(false)
status.PreApproved = false
- status.ApprovedByURI = approval.URI
+ status.ApprovedByURI = approval.AuthorizationURI
if err := p.state.DB.UpdateStatus(
ctx,
status,
@@ -365,6 +388,118 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
return nil
}
+// CreateReplyRequest handles a polite ReplyRequest.
+// This is distinct from CreateStatus, which is capable
+// of handling both "normal" top-level status creation,
+// in addition to *impolite* reply requests.
+func (p *fediAPI) CreateReplyRequest(ctx context.Context, fMsg *messages.FromFediAPI) error {
+ // Extract the ap model Statusable
+ // set by the federating db.
+ statusable, ok := fMsg.APObject.(ap.Statusable)
+ if !ok {
+ return gtserror.Newf("cannot cast %T -> ap.Statusable", fMsg.APObject)
+ }
+
+ // Call RefreshStatus to parse and process the
+ // statusable. This will also check permissions.
+ replyURI := ap.GetJSONLDId(statusable).String()
+ reply, _, err := p.federate.RefreshStatus(ctx,
+ fMsg.Receiving.Username,
+ &gtsmodel.Status{
+ URI: replyURI,
+ Local: util.Ptr(false),
+ },
+ statusable,
+ // Force refresh within 5min window.
+ dereferencing.Fresh,
+ )
+
+ switch {
+ case err == nil:
+ // All fine.
+
+ case gtserror.IsNotPermitted(err):
+ // Reply is straight up not permitted by
+ // the interaction policy of the status
+ // it's replying to. Nothing more to do.
+ log.Debugf(ctx,
+ "dropping unpermitted ReplyRequest with instrument %s",
+ replyURI,
+ )
+ return nil
+
+ default:
+ // There's some real error.
+ return gtserror.Newf(
+ "error processing ReplyRequest with instrument %s: %w",
+ replyURI, err,
+ )
+ }
+
+ // The reply is permitted. Check if we
+ // should send out an Accept immediately.
+ manualApproval := *reply.PendingApproval && !reply.PreApproved
+ if manualApproval {
+ // The reply requires manual approval.
+ //
+ // Just notify target account about
+ // the requested interaction.
+ if err := p.surface.notifyPendingReply(ctx, reply); err != nil {
+ return gtserror.Newf("error notifying pending reply: %w", err)
+ }
+
+ return nil
+ }
+
+ // The reply is automatically approved,
+ // handle side effects of this.
+ req, ok := fMsg.GTSModel.(*gtsmodel.InteractionRequest)
+ if !ok {
+ return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", fMsg.GTSModel)
+ }
+
+ // Mark the request as accepted.
+ req.AcceptedAt = time.Now()
+ req.ResponseURI = uris.GenerateURIForAccept(
+ req.TargetAccount.Username, req.ID,
+ )
+ req.AuthorizationURI = uris.GenerateURIForAuthorization(
+ req.TargetAccount.Username, req.ID,
+ )
+
+ // Update in the db.
+ if err := p.state.DB.UpdateInteractionRequest(
+ ctx,
+ req,
+ "accepted_at",
+ "response_uri",
+ "authorization_uri",
+ ); err != nil {
+ return gtserror.Newf("db error updating interaction request: %w", err)
+ }
+
+ // Send out the accept.
+ if err := p.federate.AcceptInteraction(ctx, req); err != nil {
+ log.Errorf(ctx, "error federating accept: %v", err)
+ }
+
+ // Update stats for the replying account.
+ if err := p.utils.incrementStatusesCount(ctx, fMsg.Requesting, reply); err != nil {
+ log.Errorf(ctx, "error updating account stats: %v", err)
+ }
+
+ // Timeline the reply + notify recipient(s).
+ if err := p.surface.timelineAndNotifyStatus(ctx, reply); err != nil {
+ log.Errorf(ctx, "error timelining and notifying status: %v", err)
+ }
+
+ // Interaction counts changed on the replied status;
+ // uncache the prepared version from all timelines.
+ p.surface.invalidateStatusFromTimelines(reply.InReplyToID)
+
+ return nil
+}
+
func (p *fediAPI) CreatePollVote(ctx context.Context, fMsg *messages.FromFediAPI) error {
// Cast poll vote type from the worker message.
vote, ok := fMsg.GTSModel.(*gtsmodel.PollVote)
@@ -430,18 +565,18 @@ func (p *fediAPI) UpdatePollVote(ctx context.Context, fMsg *messages.FromFediAPI
}
// Get the origin status.
- status := vote.Poll.Status
+ reply := vote.Poll.Status
- if *status.Local {
+ if *reply.Local {
// These were poll votes in a local status, we need to
// federate the updated status model with latest vote counts.
- if err := p.federate.UpdateStatus(ctx, status); err != nil {
+ if err := p.federate.UpdateStatus(ctx, reply); err != nil {
log.Errorf(ctx, "error federating status update: %v", err)
}
}
// Interaction counts changed, uncache from timelines.
- p.surface.invalidateStatusFromTimelines(status.ID)
+ p.surface.invalidateStatusFromTimelines(reply.ID)
return nil
}
@@ -503,6 +638,8 @@ func (p *fediAPI) CreateFollowReq(ctx context.Context, fMsg *messages.FromFediAP
return nil
}
+// CreateLike handles an impolite Like, ie., a Like sent directly.
+// This is different from the CreateLikeRequest function, which handles polite LikeRequests.
func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) error {
fave, ok := fMsg.GTSModel.(*gtsmodel.StatusFave)
if !ok {
@@ -525,7 +662,7 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) er
// preapproved, then just notify the account
// that's being interacted with: they can
// approve or deny the interaction later.
- if err := p.utils.requestFave(ctx, fave); err != nil {
+ if err := p.utils.impoliteFaveRequest(ctx, fave); err != nil {
return gtserror.Newf("error pending fave: %w", err)
}
@@ -540,20 +677,24 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) er
// collection. Do the Accept immediately and
// then process everything else as normal.
- // Store an already-accepted interaction request.
- id := id.NewULID()
+ // Store an already-accepted
+ // impolite interaction request.
+ requestID := id.NewULID()
approval := &gtsmodel.InteractionRequest{
- ID: id,
- StatusID: fave.StatusID,
- TargetAccountID: fave.TargetAccountID,
- TargetAccount: fave.TargetAccount,
- InteractingAccountID: fave.AccountID,
- InteractingAccount: fave.Account,
- InteractionURI: fave.URI,
- InteractionType: gtsmodel.InteractionLike,
- Like: fave,
- URI: uris.GenerateURIForAccept(fave.TargetAccount.Username, id),
- AcceptedAt: time.Now(),
+ ID: requestID,
+ TargetStatusID: fave.StatusID,
+ TargetAccountID: fave.TargetAccountID,
+ TargetAccount: fave.TargetAccount,
+ InteractingAccountID: fave.AccountID,
+ InteractingAccount: fave.Account,
+ InteractionRequestURI: gtsmodel.ForwardCompatibleInteractionRequestURI(fave.URI, gtsmodel.LikeRequestSuffix),
+ InteractionURI: fave.URI,
+ InteractionType: gtsmodel.InteractionLike,
+ Polite: util.Ptr(false),
+ Like: fave,
+ ResponseURI: uris.GenerateURIForAccept(fave.TargetAccount.Username, requestID),
+ AuthorizationURI: uris.GenerateURIForAuthorization(fave.TargetAccount.Username, requestID),
+ AcceptedAt: time.Now(),
}
if err := p.state.DB.PutInteractionRequest(ctx, approval); err != nil {
return gtserror.Newf("db error putting pre-approved interaction request: %w", err)
@@ -562,7 +703,7 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) er
// Mark the fave itself as now approved.
fave.PendingApproval = util.Ptr(false)
fave.PreApproved = false
- fave.ApprovedByURI = approval.URI
+ fave.ApprovedByURI = approval.AuthorizationURI
if err := p.state.DB.UpdateStatusFave(
ctx,
fave,
@@ -591,6 +732,87 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) er
return nil
}
+// CreateLikeRequest handles a polite LikeRequest, as
+// opposed to CreateLike, which handles *impolite* like
+// requests (ie., Likes sent directly).
+func (p *fediAPI) CreateLikeRequest(ctx context.Context, fMsg *messages.FromFediAPI) error {
+ req, ok := fMsg.GTSModel.(*gtsmodel.InteractionRequest)
+ if !ok {
+ return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", fMsg.GTSModel)
+ }
+
+ // At this point the not-yet-approved
+ // interaction request, and the pending
+ // fave, are both in the database.
+
+ if !req.Like.PreApproved {
+ // The fave is *not* pre-approved, and
+ // therefore requires manual approval.
+ //
+ // Just notify target account about
+ // the requested interaction.
+ if err := p.surface.notifyPendingFave(ctx, req.Like); err != nil {
+ return gtserror.Newf("error notifying pending like: %w", err)
+ }
+
+ return nil
+ }
+
+ // If it's pre-approved on the other hand
+ // we can handle everything immediately.
+
+ // Mark the request as accepted.
+ req.AcceptedAt = time.Now()
+ req.ResponseURI = uris.GenerateURIForAccept(
+ req.TargetAccount.Username, req.ID,
+ )
+ req.AuthorizationURI = uris.GenerateURIForAuthorization(
+ req.TargetAccount.Username, req.ID,
+ )
+
+ // Update in the db.
+ if err := p.state.DB.UpdateInteractionRequest(
+ ctx,
+ req,
+ "accepted_at",
+ "response_uri",
+ "authorization_uri",
+ ); err != nil {
+ return gtserror.Newf("db error updating interaction request: %w", err)
+ }
+
+ // Send out the accept.
+ if err := p.federate.AcceptInteraction(ctx, req); err != nil {
+ log.Errorf(ctx, "error federating accept: %v", err)
+ }
+
+ // Mark the fave as approved.
+ req.Like.PendingApproval = util.Ptr(false)
+ req.Like.ApprovedByURI = req.AuthorizationURI
+ req.Like.PreApproved = false
+
+ // Update in the db.
+ if err := p.state.DB.UpdateStatusFave(
+ ctx,
+ req.Like,
+ "pending_approval",
+ "approved_by_uri",
+ ); err != nil {
+ return gtserror.Newf("db error updating status fave: %w", err)
+ }
+
+ // Notify the faved account.
+ if err := p.surface.notifyFave(ctx, req.Like); err != nil {
+ log.Errorf(ctx, "error notifying fave: %v", err)
+ }
+
+ // Interaction counts changed on the faved status;
+ // uncache the prepared version from all timelines.
+ p.surface.invalidateStatusFromTimelines(req.Like.StatusID)
+
+ return nil
+}
+
func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI) error {
boost, ok := fMsg.GTSModel.(*gtsmodel.Status)
if !ok {
@@ -610,7 +832,7 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
)
if err != nil {
if gtserror.IsUnretrievable(err) ||
- gtserror.NotPermitted(err) {
+ gtserror.IsNotPermitted(err) {
// Boosted status domain blocked, or
// otherwise not permitted, nothing to do.
log.Debugf(ctx, "skipping announce: %v", err)
@@ -632,7 +854,7 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
// preapproved, then just notify the account
// that's being interacted with: they can
// approve or deny the interaction later.
- if err := p.utils.requestAnnounce(ctx, boost); err != nil {
+ if err := p.utils.impoliteAnnounceRequest(ctx, boost); err != nil {
return gtserror.Newf("error pending boost: %w", err)
}
@@ -647,20 +869,24 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
// collection. Do the Accept immediately and
// then process everything else as normal.
- // Store an already-accepted interaction request.
- id := id.NewULID()
+ // Store an already-accepted
+ // impolite interaction request.
+ requestID := id.NewULID()
approval := &gtsmodel.InteractionRequest{
- ID: id,
- StatusID: boost.BoostOfID,
- TargetAccountID: boost.BoostOfAccountID,
- TargetAccount: boost.BoostOfAccount,
- InteractingAccountID: boost.AccountID,
- InteractingAccount: boost.Account,
- InteractionURI: boost.URI,
- InteractionType: gtsmodel.InteractionAnnounce,
- Announce: boost,
- URI: uris.GenerateURIForAccept(boost.BoostOfAccount.Username, id),
- AcceptedAt: time.Now(),
+ ID: requestID,
+ TargetStatusID: boost.BoostOfID,
+ TargetAccountID: boost.BoostOfAccountID,
+ TargetAccount: boost.BoostOfAccount,
+ InteractingAccountID: boost.AccountID,
+ InteractingAccount: boost.Account,
+ InteractionRequestURI: gtsmodel.ForwardCompatibleInteractionRequestURI(boost.URI, gtsmodel.AnnounceRequestSuffix),
+ InteractionURI: boost.URI,
+ InteractionType: gtsmodel.InteractionAnnounce,
+ Polite: util.Ptr(false),
+ Announce: boost,
+ ResponseURI: uris.GenerateURIForAccept(boost.BoostOfAccount.Username, requestID),
+ AuthorizationURI: uris.GenerateURIForAuthorization(boost.BoostOfAccount.Username, requestID),
+ AcceptedAt: time.Now(),
}
if err := p.state.DB.PutInteractionRequest(ctx, approval); err != nil {
return gtserror.Newf("db error putting pre-approved interaction request: %w", err)
@@ -669,7 +895,7 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
// Mark the boost itself as now approved.
boost.PendingApproval = util.Ptr(false)
boost.PreApproved = false
- boost.ApprovedByURI = approval.URI
+ boost.ApprovedByURI = approval.AuthorizationURI
if err := p.state.DB.UpdateStatus(
ctx,
boost,
@@ -708,6 +934,103 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
return nil
}
+func (p *fediAPI) CreateAnnounceRequest(ctx context.Context, fMsg *messages.FromFediAPI) error {
+ req, ok := fMsg.GTSModel.(*gtsmodel.InteractionRequest)
+ if !ok {
+ return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", fMsg.GTSModel)
+ }
+
+ // At this point the not-yet-handled interaction req
+ // is in the database, but the announce isn't yet.
+ //
+ // We can check permissions for the announce *and*
+ // put it in the db (if acceptable) by doing Enrich.
+ boost, err := p.federate.EnrichAnnounce(
+ ctx,
+ req.Announce,
+ fMsg.Receiving.Username,
+ )
+
+ switch {
+ case err == nil:
+ // All fine.
+
+ case gtserror.IsNotPermitted(err):
+ // Announce is straight up not permitted
+ // by the interaction policy of the status
+ // it's targeting. Nothing more to do.
+ log.Debugf(ctx,
+ "dropping unpermitted AnnounceRequest with instrument %s",
+ req.Announce.URI,
+ )
+ return nil
+
+ default:
+ // There's some real error.
+ return gtserror.Newf(
+ "error processing AnnounceRequest with instrument %s: %w",
+ req.Announce.URI, err,
+ )
+ }
+
+ // The announce is permitted. Check if we
+ // should send out an Accept immediately.
+ manualApproval := *boost.PendingApproval && !boost.PreApproved
+ if manualApproval {
+ // The announce requires manual approval.
+ //
+ // Just notify target account about
+ // the requested interaction.
+ if err := p.surface.notifyPendingAnnounce(ctx, boost); err != nil {
+ return gtserror.Newf("error notifying pending announce: %w", err)
+ }
+
+ return nil
+ }
+
+ // The announce is automatically approved,
+ // mark the request as accepted.
+ req.AcceptedAt = time.Now()
+ req.ResponseURI = uris.GenerateURIForAccept(
+ req.TargetAccount.Username, req.ID,
+ )
+ req.AuthorizationURI = uris.GenerateURIForAuthorization(
+ req.TargetAccount.Username, req.ID,
+ )
+
+ // Update in the db.
+ if err := p.state.DB.UpdateInteractionRequest(
+ ctx,
+ req,
+ "accepted_at",
+ "response_uri",
+ "authorization_uri",
+ ); err != nil {
+ return gtserror.Newf("db error updating interaction request: %w", err)
+ }
+
+ // Send out the accept.
+ if err := p.federate.AcceptInteraction(ctx, req); err != nil {
+ log.Errorf(ctx, "error federating accept: %v", err)
+ }
+
+ // Update stats for the boosting account.
+ if err := p.utils.incrementStatusesCount(ctx, fMsg.Requesting, boost); err != nil {
+ log.Errorf(ctx, "error updating account stats: %v", err)
+ }
+
+ // Timeline the boost + notify recipient(s).
+ if err := p.surface.timelineAndNotifyStatus(ctx, boost); err != nil {
+ log.Errorf(ctx, "error timelining and notifying status: %v", err)
+ }
+
+ // Interaction counts changed on the boosted status;
+ // uncache the prepared version from all timelines.
+ p.surface.invalidateStatusFromTimelines(boost.BoostOfID)
+
+ return nil
+}
+
func (p *fediAPI) CreateBlock(ctx context.Context, fMsg *messages.FromFediAPI) error {
block, ok := fMsg.GTSModel.(*gtsmodel.Block)
if !ok {
@@ -842,29 +1165,29 @@ func (p *fediAPI) AcceptLike(ctx context.Context, fMsg *messages.FromFediAPI) er
}
func (p *fediAPI) AcceptReply(ctx context.Context, fMsg *messages.FromFediAPI) error {
- status, ok := fMsg.GTSModel.(*gtsmodel.Status)
+ reply, ok := fMsg.GTSModel.(*gtsmodel.Status)
if !ok {
return gtserror.Newf("%T not parseable as *gtsmodel.Status", fMsg.GTSModel)
}
// Update stats for the actor account.
- if err := p.utils.incrementStatusesCount(ctx, status.Account, status); err != nil {
+ if err := p.utils.incrementStatusesCount(ctx, reply.Account, reply); err != nil {
log.Errorf(ctx, "error updating account stats: %v", err)
}
// Timeline and notify the status.
- if err := p.surface.timelineAndNotifyStatus(ctx, status); err != nil {
+ if err := p.surface.timelineAndNotifyStatus(ctx, reply); err != nil {
log.Errorf(ctx, "error timelining and notifying status: %v", err)
}
// Send out the reply again, fully this time.
- if err := p.federate.CreateStatus(ctx, status); err != nil {
+ if err := p.federate.CreateStatus(ctx, reply); err != nil {
log.Errorf(ctx, "error federating announce: %v", err)
}
// Interaction counts changed on the replied-to status;
// uncache the prepared version from all timelines.
- p.surface.invalidateStatusFromTimelines(status.InReplyToID)
+ p.surface.invalidateStatusFromTimelines(reply.InReplyToID)
return nil
}
@@ -893,9 +1216,9 @@ func (p *fediAPI) AcceptRemoteStatus(ctx context.Context, fMsg *messages.FromFed
// barebones status and insert it into the database,
// if indeed it's actually a status URI we can fetch.
//
- // This will also check whether the given AcceptIRI
+ // This will also check whether the given approvedByURI
// actually grants permission for this status.
- status, _, err := p.federate.RefreshStatus(ctx,
+ reply, _, err := p.federate.RefreshStatus(ctx,
fMsg.Receiving.Username,
bareStatus,
nil, nil,
@@ -906,20 +1229,70 @@ func (p *fediAPI) AcceptRemoteStatus(ctx context.Context, fMsg *messages.FromFed
// No error means it was indeed a remote status, and the
// given approvedByURI permitted it. Timeline and notify it.
- if err := p.surface.timelineAndNotifyStatus(ctx, status); err != nil {
+ if err := p.surface.timelineAndNotifyStatus(ctx, reply); err != nil {
log.Errorf(ctx, "error timelining and notifying status: %v", err)
}
// Interaction counts changed on the interacted status;
// uncache the prepared version from all timelines.
- if status.InReplyToID != "" {
- p.surface.invalidateStatusFromTimelines(status.InReplyToID)
+ if reply.InReplyToID != "" {
+ p.surface.invalidateStatusFromTimelines(reply.InReplyToID)
+ }
+
+ if reply.BoostOfID != "" {
+ p.surface.invalidateStatusFromTimelines(reply.BoostOfID)
+ }
+
+ return nil
+}
+
+func (p *fediAPI) AcceptPoliteReplyRequest(ctx context.Context, fMsg *messages.FromFediAPI) error {
+ if util.IsNil(fMsg.GTSModel) {
+ // If the interaction request is nil, this
+ // must be an accept of a remote ReplyRequest
+ // not targeting one of our statuses.
+ //
+ // Just pass it to the AcceptRemoteStatus
+ // func to do dereferencing + side effects.
+ log.Debug(ctx, "accepting remote ReplyRequest for remote reply")
+ return p.AcceptRemoteStatus(ctx, fMsg)
+ }
+
+ // If the interaction request is not nil, this will
+ // be an accept of one of our replies to a remote.
+ //
+ // Since the int req + reply have already been updated
+ // in the federatingDB, we just need to do side effects.
+ intReq, ok := fMsg.GTSModel.(*gtsmodel.InteractionRequest)
+ if !ok {
+ return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", fMsg.GTSModel)
}
- if status.BoostOfID != "" {
- p.surface.invalidateStatusFromTimelines(status.BoostOfID)
+ // Ensure reply populated.
+ reply := intReq.Reply
+ if err := p.state.DB.PopulateStatus(ctx, reply); err != nil {
+ return gtserror.Newf("error populating status: %w", err)
}
+ // Update stats for the actor account.
+ if err := p.utils.incrementStatusesCount(ctx, reply.Account, reply); err != nil {
+ log.Errorf(ctx, "error updating account stats: %v", err)
+ }
+
+ // Timeline and notify the status.
+ if err := p.surface.timelineAndNotifyStatus(ctx, reply); err != nil {
+ log.Errorf(ctx, "error timelining and notifying status: %v", err)
+ }
+
+ // Send out the reply with approval attached.
+ if err := p.federate.CreateStatus(ctx, reply); err != nil {
+ log.Errorf(ctx, "error federating announce: %v", err)
+ }
+
+ // Interaction counts changed on the replied-to status;
+ // uncache the prepared version from all timelines.
+ p.surface.invalidateStatusFromTimelines(reply.InReplyToID)
+
return nil
}
@@ -1169,7 +1542,7 @@ func (p *fediAPI) RejectReply(ctx context.Context, fMsg *messages.FromFediAPI) e
// be in the database, we just need to do side effects.
// Get the rejected status.
- status, err := p.state.DB.GetStatusByURI(
+ reply, err := p.state.DB.GetStatusByURI(
gtscontext.SetBarebones(ctx),
req.InteractionURI,
)
@@ -1189,7 +1562,7 @@ func (p *fediAPI) RejectReply(ctx context.Context, fMsg *messages.FromFediAPI) e
// Perform the actual status deletion.
if err := p.utils.wipeStatus(
ctx,
- status,
+ reply,
deleteAttachments,
copyToSinBin,
); err != nil {
diff --git a/internal/processing/workers/fromfediapi_test.go b/internal/processing/workers/fromfediapi_test.go
index 7811e9f3d..790c78b70 100644
--- a/internal/processing/workers/fromfediapi_test.go
+++ b/internal/processing/workers/fromfediapi_test.go
@@ -18,6 +18,7 @@
package workers_test
import (
+ "bytes"
"context"
"encoding/json"
"errors"
@@ -26,11 +27,13 @@ import (
"testing"
"time"
+ "code.superseriousbusiness.org/activity/streams/vocab"
"code.superseriousbusiness.org/gotosocial/internal/ap"
apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model"
"code.superseriousbusiness.org/gotosocial/internal/db"
"code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
+ "code.superseriousbusiness.org/gotosocial/internal/id"
"code.superseriousbusiness.org/gotosocial/internal/messages"
"code.superseriousbusiness.org/gotosocial/internal/stream"
"code.superseriousbusiness.org/gotosocial/internal/util"
@@ -781,6 +784,123 @@ func (suite *FromFediAPITestSuite) TestUpdateNote() {
}
}
+func (suite *FromFediAPITestSuite) TestCreateReplyRequest() {
+ var (
+ ctx = suite.T().Context()
+ testStructs = testrig.SetupTestStructs(rMediaPath, rTemplatePath)
+ requesting = suite.testAccounts["remote_account_1"]
+ receiving = suite.testAccounts["admin_account"]
+ testStatus = suite.testStatuses["admin_account_status_1"]
+ intReqURI = "http://fossbros-anonymous.io/requests/87fb1478-ac46-406a-8463-96ce05645219"
+ intURI = "http://fossbros-anonymous.io/users/foss_satan/statuses/87fb1478-ac46-406a-8463-96ce05645219"
+ jsonStr = `{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://gotosocial.org/ns",
+ {
+ "sensitive": "as:sensitive"
+ }
+ ],
+ "type": "ReplyRequest",
+ "id": "` + intReqURI + `",
+ "actor": "` + requesting.URI + `",
+ "object": "` + testStatus.URI + `",
+ "to": "` + receiving.URI + `",
+ "instrument": {
+ "attributedTo": "` + requesting.URI + `",
+ "cc": "` + requesting.FollowersURI + `",
+ "content": "\u003cp\u003ethis is a reply!\u003c/p\u003e",
+ "id": "` + intURI + `",
+ "inReplyTo": "` + testStatus.URI + `",
+ "tag": {
+ "href": "` + receiving.URI + `",
+ "name": "@` + receiving.Username + `@localhost:8080",
+ "type": "Mention"
+ },
+ "to": "https://www.w3.org/ns/activitystreams#Public",
+ "type": "Note"
+ }
+}`
+ )
+ defer testrig.TearDownTestStructs(testStructs)
+
+ suite.T().Logf("testing reply request:\n\n%s", jsonStr)
+
+ // Decode the reply request + embedded statusable.
+ t, err := ap.DecodeType(ctx, io.NopCloser(bytes.NewBufferString(jsonStr)))
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+ replyReq := t.(vocab.GoToSocialReplyRequest)
+ statusable := replyReq.GetActivityStreamsInstrument().At(0).GetActivityStreamsNote().(ap.Statusable)
+
+ // Create a pending interaction request in the
+ // database, as though the reply req had already
+ // passed through the federatingdb function.
+ intReq := &gtsmodel.InteractionRequest{
+ ID: id.NewULID(),
+ TargetStatusID: testStatus.ID,
+ TargetStatus: testStatus,
+ TargetAccountID: receiving.ID,
+ TargetAccount: receiving,
+ InteractingAccountID: requesting.ID,
+ InteractingAccount: requesting,
+ InteractionRequestURI: intReqURI,
+ InteractionURI: ap.GetJSONLDId(statusable).String(),
+ InteractionType: gtsmodel.InteractionReply,
+ Polite: util.Ptr(true),
+ Reply: nil, // Not settable yet.
+ }
+ if err := testStructs.State.DB.PutInteractionRequest(ctx, intReq); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ // Process the message.
+ if err = testStructs.Processor.Workers().ProcessFromFediAPI(
+ ctx,
+ &messages.FromFediAPI{
+ APObjectType: ap.ActivityReplyRequest,
+ APActivityType: ap.ActivityCreate,
+ GTSModel: intReq,
+ APObject: statusable,
+ Receiving: receiving,
+ Requesting: requesting,
+ },
+ ); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ // The interaction request should be accepted.
+ intReq, err = testStructs.State.DB.GetInteractionRequestByID(ctx, intReq.ID)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+ suite.WithinDuration(time.Now(), intReq.AcceptedAt, 1*time.Minute)
+ suite.NotEmpty(intReq.AuthorizationURI)
+ suite.NotEmpty(intReq.ResponseURI)
+
+ // Federator should send out an Accept that looks something like:
+ //
+ // {
+ // "@context": [
+ // "https://gotosocial.org/ns",
+ // "https://www.w3.org/ns/activitystreams"
+ // ],
+ // "actor": "http://localhost:8080/users/admin",
+ // "id": "http://localhost:8080/users/admin/accepts/01K2CV90660VRPZM39R35NMSG9",
+ // "object": {
+ // "actor": "http://fossbros-anonymous.io/users/foss_satan",
+ // "id": "http://fossbros-anonymous.io/requests/87fb1478-ac46-406a-8463-96ce05645219",
+ // "instrument": "http://fossbros-anonymous.io/users/foss_satan/statuses/87fb1478-ac46-406a-8463-96ce05645219",
+ // "object": "http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
+ // "type": "ReplyRequest"
+ // },
+ // "result": "http://localhost:8080/users/admin/authorizations/01K2CV90660VRPZM39R35NMSG9",
+ // "to": "http://fossbros-anonymous.io/users/foss_satan",
+ // "type": "Accept"
+ // }
+}
+
func TestFromFederatorTestSuite(t *testing.T) {
suite.Run(t, &FromFediAPITestSuite{})
}
diff --git a/internal/processing/workers/util.go b/internal/processing/workers/util.go
index 3c17eaaf5..6382887eb 100644
--- a/internal/processing/workers/util.go
+++ b/internal/processing/workers/util.go
@@ -526,9 +526,13 @@ func (u *utils) decrementFollowRequestsCount(
return nil
}
-// requestFave stores an interaction request
+// impoliteFaveRequest stores an interaction request
// for the given fave, and notifies the interactee.
-func (u *utils) requestFave(
+//
+// It should be used only when an actor has sent a Like
+// directly in response to a post that requires approval
+// for it, instead of sending a LikeRequest.
+func (u *utils) impoliteFaveRequest(
ctx context.Context,
fave *gtsmodel.StatusFave,
) error {
@@ -555,8 +559,8 @@ func (u *utils) requestFave(
return nil
}
- // Create + store new interaction request.
- req = typeutils.StatusFaveToInteractionRequest(fave)
+ // Create + store new impolite interaction request.
+ req = typeutils.StatusFaveToImpoliteInteractionRequest(fave)
if err := u.state.DB.PutInteractionRequest(ctx, req); err != nil {
return gtserror.Newf("db error storing interaction request: %w", err)
}
@@ -569,9 +573,13 @@ func (u *utils) requestFave(
return nil
}
-// requestReply stores an interaction request
+// impoliteReplyRequest stores an interaction request
// for the given reply, and notifies the interactee.
-func (u *utils) requestReply(
+//
+// It should be used only when an actor has sent a reply
+// directly in response to a post that requires approval
+// for it, instead of sending a ReplyRequest.
+func (u *utils) impoliteReplyRequest(
ctx context.Context,
reply *gtsmodel.Status,
) error {
@@ -598,8 +606,8 @@ func (u *utils) requestReply(
return nil
}
- // Create + store interaction request.
- req = typeutils.StatusToInteractionRequest(reply)
+ // Create + store impolite interaction request.
+ req = typeutils.StatusToImpoliteInteractionRequest(reply)
if err := u.state.DB.PutInteractionRequest(ctx, req); err != nil {
return gtserror.Newf("db error storing interaction request: %w", err)
}
@@ -612,9 +620,13 @@ func (u *utils) requestReply(
return nil
}
-// requestAnnounce stores an interaction request
+// impoliteAnnounceRequest stores an interaction request
// for the given announce, and notifies the interactee.
-func (u *utils) requestAnnounce(
+//
+// It should be used only when an actor has sent an Announce
+// directly in response to a post that requires approval
+// for it, instead of sending an AnnounceRequest.
+func (u *utils) impoliteAnnounceRequest(
ctx context.Context,
boost *gtsmodel.Status,
) error {
@@ -641,8 +653,8 @@ func (u *utils) requestAnnounce(
return nil
}
- // Create + store interaction request.
- req = typeutils.StatusToInteractionRequest(boost)
+ // Create + store impolite interaction request.
+ req = typeutils.StatusToImpoliteInteractionRequest(boost)
if err := u.state.DB.PutInteractionRequest(ctx, req); err != nil {
return gtserror.Newf("db error storing interaction request: %w", err)
}