summaryrefslogtreecommitdiff
path: root/internal/federation
diff options
context:
space:
mode:
Diffstat (limited to 'internal/federation')
-rw-r--r--internal/federation/dereferencing/status.go17
-rw-r--r--internal/federation/federatingdb/accept.go69
-rw-r--r--internal/federation/federatingdb/create.go36
-rw-r--r--internal/federation/federatingdb/db.go1
-rw-r--r--internal/federation/federatingdb/question.go32
-rw-r--r--internal/federation/federatingdb/undo.go31
-rw-r--r--internal/federation/federatingdb/update.go57
-rw-r--r--internal/federation/federatingprotocol.go3
8 files changed, 157 insertions, 89 deletions
diff --git a/internal/federation/dereferencing/status.go b/internal/federation/dereferencing/status.go
index 5ec04175e..84316f3a9 100644
--- a/internal/federation/dereferencing/status.go
+++ b/internal/federation/dereferencing/status.go
@@ -288,8 +288,8 @@ func (d *deref) enrichStatus(
return nil, nil, gtserror.Newf("error populating mentions for status %s: %w", uri, err)
}
- // Ensure the status' tags are populated.
- if err := d.fetchStatusTags(ctx, requestUser, latestStatus); err != nil {
+ // Ensure the status' tags are populated, (changes are expected / okay).
+ if err := d.fetchStatusTags(ctx, latestStatus); err != nil {
return nil, nil, gtserror.Newf("error populating tags for status %s: %w", uri, err)
}
@@ -298,8 +298,8 @@ func (d *deref) enrichStatus(
return nil, nil, gtserror.Newf("error populating attachments for status %s: %w", uri, err)
}
- // Ensure the status' emoji attachments are populated, passing in existing to check for changes.
- if err := d.fetchStatusEmojis(ctx, requestUser, status, latestStatus); err != nil {
+ // Ensure the status' emoji attachments are populated, (changes are expected / okay).
+ if err := d.fetchStatusEmojis(ctx, requestUser, latestStatus); err != nil {
return nil, nil, gtserror.Newf("error populating emojis for status %s: %w", uri, err)
}
@@ -359,6 +359,8 @@ func (d *deref) fetchStatusMentions(ctx context.Context, requestUser string, exi
}
// Generate new ID according to status creation.
+ // TODO: update this to use "edited_at" when we add
+ // support for edited status revision history.
mention.ID, err = id.NewULIDFromTime(status.CreatedAt)
if err != nil {
log.Errorf(ctx, "invalid created at date: %v", err)
@@ -403,7 +405,7 @@ func (d *deref) fetchStatusMentions(ctx context.Context, requestUser string, exi
return nil
}
-func (d *deref) fetchStatusTags(ctx context.Context, requestUser string, status *gtsmodel.Status) error {
+func (d *deref) fetchStatusTags(ctx context.Context, status *gtsmodel.Status) error {
// Allocate new slice to take the yet-to-be determined tag IDs.
status.TagIDs = make([]string, len(status.Tags))
@@ -417,13 +419,14 @@ func (d *deref) fetchStatusTags(ctx context.Context, requestUser string, status
continue
}
- // No tag with this name yet, create it.
if tag == nil {
+ // Create new ID for tag name.
tag = &gtsmodel.Tag{
ID: id.NewULID(),
Name: placeholder.Name,
}
+ // Insert this tag with new name into the database.
if err := d.state.DB.PutTag(ctx, tag); err != nil {
log.Errorf(ctx, "db error putting tag %s: %v", tag.Name, err)
continue
@@ -516,7 +519,7 @@ func (d *deref) fetchStatusAttachments(ctx context.Context, tsport transport.Tra
return nil
}
-func (d *deref) fetchStatusEmojis(ctx context.Context, requestUser string, existing, status *gtsmodel.Status) error {
+func (d *deref) fetchStatusEmojis(ctx context.Context, requestUser string, status *gtsmodel.Status) error {
// Fetch the full-fleshed-out emoji objects for our status.
emojis, err := d.populateEmojis(ctx, status.Emojis, requestUser)
if err != nil {
diff --git a/internal/federation/federatingdb/accept.go b/internal/federation/federatingdb/accept.go
index 1c514d035..38b6b9300 100644
--- a/internal/federation/federatingdb/accept.go
+++ b/internal/federation/federatingdb/accept.go
@@ -46,28 +46,29 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
return nil // Already processed.
}
- acceptObject := accept.GetActivityStreamsObject()
- if acceptObject == nil {
- return errors.New("ACCEPT: no object set on vocab.ActivityStreamsAccept")
- }
+ // Iterate all provided objects in the activity.
+ for _, object := range ap.ExtractObjects(accept) {
+
+ // Check and handle any vocab.Type objects.
+ if objType := object.GetType(); objType != nil {
+ switch objType.GetTypeName() { //nolint:gocritic
- for iter := acceptObject.Begin(); iter != acceptObject.End(); iter = iter.Next() {
- // check if the object is an IRI
- if iter.IsIRI() {
- // we have just the URI of whatever is being accepted, so we need to find out what it is
- acceptedObjectIRI := iter.GetIRI()
- if uris.IsFollowPath(acceptedObjectIRI) {
- // ACCEPT FOLLOW
- followReq, err := f.state.DB.GetFollowRequestByURI(ctx, acceptedObjectIRI.String())
+ case ap.ActivityFollow:
+ // Cast the vocab.Type object to known AS type.
+ asFollow := objType.(vocab.ActivityStreamsFollow)
+
+ // convert the follow to something we can understand
+ gtsFollow, err := f.converter.ASFollowToFollow(ctx, asFollow)
if err != nil {
- return fmt.Errorf("ACCEPT: couldn't get follow request with id %s from the database: %s", acceptedObjectIRI.String(), err)
+ return fmt.Errorf("ACCEPT: error converting asfollow to gtsfollow: %s", err)
}
// make sure the addressee of the original follow is the same as whatever inbox this landed in
- if followReq.AccountID != receivingAccount.ID {
+ if gtsFollow.AccountID != receivingAccount.ID {
return errors.New("ACCEPT: follow object account and inbox account were not the same")
}
- follow, err := f.state.DB.AcceptFollowRequest(ctx, followReq.AccountID, followReq.TargetAccountID)
+
+ follow, err := f.state.DB.AcceptFollowRequest(ctx, gtsFollow.AccountID, gtsFollow.TargetAccountID)
if err != nil {
return err
}
@@ -78,31 +79,36 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
GTSModel: follow,
ReceivingAccount: receivingAccount,
})
-
- return nil
}
- }
- // check if iter is an AP object / type
- if iter.GetType() == nil {
continue
}
- if iter.GetType().GetTypeName() == ap.ActivityFollow {
- // ACCEPT FOLLOW
- asFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow)
- if !ok {
- return errors.New("ACCEPT: couldn't parse follow into vocab.ActivityStreamsFollow")
+
+ // Check and handle any
+ // IRI type objects.
+ if object.IsIRI() {
+
+ // Extract IRI from object.
+ iri := object.GetIRI()
+ if !uris.IsFollowPath(iri) {
+ continue
}
- // convert the follow to something we can understand
- gtsFollow, err := f.converter.ASFollowToFollow(ctx, asFollow)
+
+ // Serialize IRI.
+ iriStr := iri.String()
+
+ // ACCEPT FOLLOW
+ followReq, err := f.state.DB.GetFollowRequestByURI(ctx, iriStr)
if err != nil {
- return fmt.Errorf("ACCEPT: error converting asfollow to gtsfollow: %s", err)
+ return fmt.Errorf("ACCEPT: couldn't get follow request with id %s from the database: %s", iriStr, err)
}
+
// make sure the addressee of the original follow is the same as whatever inbox this landed in
- if gtsFollow.AccountID != receivingAccount.ID {
+ if followReq.AccountID != receivingAccount.ID {
return errors.New("ACCEPT: follow object account and inbox account were not the same")
}
- follow, err := f.state.DB.AcceptFollowRequest(ctx, gtsFollow.AccountID, gtsFollow.TargetAccountID)
+
+ follow, err := f.state.DB.AcceptFollowRequest(ctx, followReq.AccountID, followReq.TargetAccountID)
if err != nil {
return err
}
@@ -114,8 +120,9 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
ReceivingAccount: receivingAccount,
})
- return nil
+ continue
}
+
}
return nil
diff --git a/internal/federation/federatingdb/create.go b/internal/federation/federatingdb/create.go
index 12e324166..6cb230589 100644
--- a/internal/federation/federatingdb/create.go
+++ b/internal/federation/federatingdb/create.go
@@ -81,6 +81,7 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
// FLAG / REPORT SOMETHING
return f.activityFlag(ctx, asType, receivingAccount, requestingAccount)
}
+
return nil
}
@@ -111,6 +112,7 @@ func (f *federatingDB) activityBlock(ctx context.Context, asType vocab.Type, rec
GTSModel: block,
ReceivingAccount: receiving,
})
+
return nil
}
@@ -132,37 +134,19 @@ func (f *federatingDB) activityCreate(
return gtserror.Newf("could not convert asType %T to ActivityStreamsCreate", asType)
}
- // Create must have an Object.
- objectProp := create.GetActivityStreamsObject()
- if objectProp == nil {
- return gtserror.New("create had no Object")
- }
-
- // Iterate through the Object property and process FIRST provided statusable.
- // todo: https://github.com/superseriousbusiness/gotosocial/issues/1905
- for iter := objectProp.Begin(); iter != objectProp.End(); iter = iter.Next() {
- object := iter.GetType()
- if object == nil {
- // Can't do Create with Object that's just a URI.
- // Warn log this because it's an AP error.
- log.Warn(ctx, "object entry was not a type: %[1]T%[1]+v", iter)
+ for _, object := range ap.ExtractObjects(create) {
+ // Try to get object as vocab.Type,
+ // else skip handling (likely) IRI.
+ objType := object.GetType()
+ if objType == nil {
continue
}
- // Ensure given object type is a statusable.
- statusable, ok := object.(ap.Statusable)
- if !ok {
- // Can't (currently) Create anything other than a Statusable. ([1] is a format arg index)
- log.Debugf(ctx, "object entry type (currently) unsupported: %[1]T%[1]+v", object)
- continue
+ if statusable, ok := ap.ToStatusable(objType); ok {
+ return f.createStatusable(ctx, statusable, receivingAccount, requestingAccount)
}
- // Handle creation of statusable.
- return f.createStatusable(ctx,
- statusable,
- receivingAccount,
- requestingAccount,
- )
+ // TODO: handle CREATE of other types?
}
return nil
diff --git a/internal/federation/federatingdb/db.go b/internal/federation/federatingdb/db.go
index 3f35a96c3..c412ba3f8 100644
--- a/internal/federation/federatingdb/db.go
+++ b/internal/federation/federatingdb/db.go
@@ -34,6 +34,7 @@ type DB interface {
Accept(ctx context.Context, accept vocab.ActivityStreamsAccept) error
Reject(ctx context.Context, reject vocab.ActivityStreamsReject) error
Announce(ctx context.Context, announce vocab.ActivityStreamsAnnounce) error
+ Question(ctx context.Context, question vocab.ActivityStreamsQuestion) error
}
// FederatingDB uses the underlying DB interface to implement the go-fed pub.Database interface.
diff --git a/internal/federation/federatingdb/question.go b/internal/federation/federatingdb/question.go
new file mode 100644
index 000000000..85226d9ed
--- /dev/null
+++ b/internal/federation/federatingdb/question.go
@@ -0,0 +1,32 @@
+// 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 federatingdb
+
+import (
+ "context"
+
+ "github.com/superseriousbusiness/activity/streams/vocab"
+)
+
+func (f *federatingDB) Question(ctx context.Context, question vocab.ActivityStreamsQuestion) error {
+ receivingAccount, requestingAccount, internal := extractFromCtx(ctx)
+ if internal {
+ return nil // Already processed.
+ }
+ return f.createStatusable(ctx, question, receivingAccount, requestingAccount)
+}
diff --git a/internal/federation/federatingdb/undo.go b/internal/federation/federatingdb/undo.go
index 84a5bdd47..a7a0f077a 100644
--- a/internal/federation/federatingdb/undo.go
+++ b/internal/federation/federatingdb/undo.go
@@ -27,6 +27,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
)
@@ -48,31 +49,31 @@ func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo)
return nil // Already processed.
}
- undoObject := undo.GetActivityStreamsObject()
- if undoObject == nil {
- return errors.New("UNDO: no object set on vocab.ActivityStreamsUndo")
- }
+ var errs gtserror.MultiError
- for iter := undoObject.Begin(); iter != undoObject.End(); iter = iter.Next() {
- t := iter.GetType()
- if t == nil {
+ for _, object := range ap.ExtractObjects(undo) {
+ // Try to get object as vocab.Type,
+ // else skip handling (likely) IRI.
+ objType := object.GetType()
+ if objType == nil {
continue
}
- switch t.GetTypeName() {
+ switch objType.GetTypeName() {
case ap.ActivityFollow:
- if err := f.undoFollow(ctx, receivingAccount, undo, t); err != nil {
- return err
+ if err := f.undoFollow(ctx, receivingAccount, undo, objType); err != nil {
+ errs.Appendf("error undoing follow: %w", err)
}
case ap.ActivityLike:
- if err := f.undoLike(ctx, receivingAccount, undo, t); err != nil {
- return err
+ if err := f.undoLike(ctx, receivingAccount, undo, objType); err != nil {
+ errs.Appendf("error undoing like: %w", err)
}
case ap.ActivityAnnounce:
- // todo: undo boost / reblog / announce
+ // TODO: actually handle this !
+ log.Warn(ctx, "skipped undo announce")
case ap.ActivityBlock:
- if err := f.undoBlock(ctx, receivingAccount, undo, t); err != nil {
- return err
+ if err := f.undoBlock(ctx, receivingAccount, undo, objType); err != nil {
+ errs.Appendf("error undoing block: %w", err)
}
}
}
diff --git a/internal/federation/federatingdb/update.go b/internal/federation/federatingdb/update.go
index 8e452eb3c..5d3d4a0ff 100644
--- a/internal/federation/federatingdb/update.go
+++ b/internal/federation/federatingdb/update.go
@@ -56,21 +56,18 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
return nil // Already processed.
}
- switch asType.GetTypeName() {
- case ap.ActorApplication, ap.ActorGroup, ap.ActorOrganization, ap.ActorPerson, ap.ActorService:
- return f.updateAccountable(ctx, receivingAccount, requestingAccount, asType)
+ if accountable, ok := ap.ToAccountable(asType); ok {
+ return f.updateAccountable(ctx, receivingAccount, requestingAccount, accountable)
+ }
+
+ if statusable, ok := ap.ToStatusable(asType); ok {
+ return f.updateStatusable(ctx, receivingAccount, requestingAccount, statusable)
}
return nil
}
-func (f *federatingDB) updateAccountable(ctx context.Context, receivingAcct *gtsmodel.Account, requestingAcct *gtsmodel.Account, asType vocab.Type) error {
- // Ensure delivered asType is a valid Accountable model.
- accountable, ok := asType.(ap.Accountable)
- if !ok {
- return gtserror.Newf("could not convert vocab.Type %T to Accountable", asType)
- }
-
+func (f *federatingDB) updateAccountable(ctx context.Context, receivingAcct *gtsmodel.Account, requestingAcct *gtsmodel.Account, accountable ap.Accountable) error {
// Extract AP URI of the updated Accountable model.
idProp := accountable.GetJSONLDId()
if idProp == nil || !idProp.IsIRI() {
@@ -103,3 +100,43 @@ func (f *federatingDB) updateAccountable(ctx context.Context, receivingAcct *gts
return nil
}
+
+func (f *federatingDB) updateStatusable(ctx context.Context, receivingAcct *gtsmodel.Account, requestingAcct *gtsmodel.Account, statusable ap.Statusable) error {
+ // Extract AP URI of the updated model.
+ idProp := statusable.GetJSONLDId()
+ if idProp == nil || !idProp.IsIRI() {
+ return gtserror.New("invalid id prop")
+ }
+
+ // Get the status URI string for lookups.
+ statusURI := idProp.GetIRI()
+ statusURIStr := statusURI.String()
+
+ // Don't try to update local statuses.
+ if statusURI.Host == config.GetHost() {
+ return nil
+ }
+
+ // Get the status we have on file for this URI string.
+ status, err := f.state.DB.GetStatusByURI(ctx, statusURIStr)
+ if err != nil {
+ return gtserror.Newf("error fetching status from db: %w", err)
+ }
+
+ // Check that update was by the status author.
+ if status.AccountID != requestingAcct.ID {
+ return gtserror.Newf("update for %s was not requested by author", statusURIStr)
+ }
+
+ // Queue an UPDATE NOTE activity to our fedi API worker,
+ // this will handle necessary database insertions, etc.
+ f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
+ APObjectType: ap.ObjectNote,
+ APActivityType: ap.ActivityUpdate,
+ GTSModel: status, // original status
+ APObjectModel: statusable,
+ ReceivingAccount: receivingAcct,
+ })
+
+ return nil
+}
diff --git a/internal/federation/federatingprotocol.go b/internal/federation/federatingprotocol.go
index fb4e5bfb9..ea19eb651 100644
--- a/internal/federation/federatingprotocol.go
+++ b/internal/federation/federatingprotocol.go
@@ -522,6 +522,9 @@ func (f *federator) FederatingCallbacks(ctx context.Context) (wrapped pub.Federa
func(ctx context.Context, announce vocab.ActivityStreamsAnnounce) error {
return f.FederatingDB().Announce(ctx, announce)
},
+ func(ctx context.Context, question vocab.ActivityStreamsQuestion) error {
+ return f.FederatingDB().Question(ctx, question)
+ },
}
return