diff options
Diffstat (limited to 'internal/federation')
-rw-r--r-- | internal/federation/dereferencing/status.go | 17 | ||||
-rw-r--r-- | internal/federation/federatingdb/accept.go | 69 | ||||
-rw-r--r-- | internal/federation/federatingdb/create.go | 36 | ||||
-rw-r--r-- | internal/federation/federatingdb/db.go | 1 | ||||
-rw-r--r-- | internal/federation/federatingdb/question.go | 32 | ||||
-rw-r--r-- | internal/federation/federatingdb/undo.go | 31 | ||||
-rw-r--r-- | internal/federation/federatingdb/update.go | 57 | ||||
-rw-r--r-- | internal/federation/federatingprotocol.go | 3 |
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 = >smodel.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 |