diff --git a/internal/federation/federatingdb/update.go b/internal/federation/federatingdb/update.go index 3ac5f26..1a14805 100644 --- a/internal/federation/federatingdb/update.go +++ b/internal/federation/federatingdb/update.go @@ -120,6 +120,12 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error { return fmt.Errorf("error converting to account: %s", err) } + if updatedAcct.Domain == f.config.Host { + // no need to update local accounts + // in fact, if we do this will break the shit out of things so do NOT + return nil + } + if requestingAcct.URI != updatedAcct.URI { return fmt.Errorf("update for account %s was requested by account %s, this is not valid", updatedAcct.URI, requestingAcct.URI) } diff --git a/internal/federation/federatingdb/util.go b/internal/federation/federatingdb/util.go index 53fe194..ff6ae50 100644 --- a/internal/federation/federatingdb/util.go +++ b/internal/federation/federatingdb/util.go @@ -143,6 +143,19 @@ func (f *federatingDB) NewID(c context.Context, t vocab.Type) (id *url.URL, err return idProp.GetIRI(), nil } } + case gtsmodel.ActivityStreamsUpdate: + // UPDATE + // ID might already be set on an update we've created, so check it here and return it if it is + update, ok := t.(vocab.ActivityStreamsUpdate) + if !ok { + return nil, errors.New("newid: fave couldn't be parsed into vocab.ActivityStreamsUpdate") + } + idProp := update.GetJSONLDId() + if idProp != nil { + if idProp.IsIRI() { + return idProp.GetIRI(), nil + } + } } // fallback default behavior: just return a random UUID after our protocol and host diff --git a/internal/message/accountprocess.go b/internal/message/accountprocess.go index 8847e57..ea03ab0 100644 --- a/internal/message/accountprocess.go +++ b/internal/message/accountprocess.go @@ -188,6 +188,13 @@ func (p *processor) AccountUpdate(authed *oauth.Auth, form *apimodel.UpdateCrede return nil, fmt.Errorf("could not fetch updated account %s: %s", authed.Account.ID, err) } + p.fromClientAPI <- gtsmodel.FromClientAPI{ + APObjectType: gtsmodel.ActivityStreamsProfile, + APActivityType: gtsmodel.ActivityStreamsUpdate, + GTSModel: updatedAccount, + OriginAccount: updatedAccount, + } + acctSensitive, err := p.tc.AccountToMastoSensitive(updatedAccount) if err != nil { return nil, fmt.Errorf("could not convert account into mastosensitive account: %s", err) diff --git a/internal/message/fromclientapiprocess.go b/internal/message/fromclientapiprocess.go index 1d30b52..40a2981 100644 --- a/internal/message/fromclientapiprocess.go +++ b/internal/message/fromclientapiprocess.go @@ -88,6 +88,16 @@ func (p *processor) processFromClientAPI(clientMsg gtsmodel.FromClientAPI) error } case gtsmodel.ActivityStreamsUpdate: // UPDATE + switch clientMsg.APObjectType { + case gtsmodel.ActivityStreamsProfile, gtsmodel.ActivityStreamsPerson: + // UPDATE ACCOUNT/PROFILE + account, ok := clientMsg.GTSModel.(*gtsmodel.Account) + if !ok { + return errors.New("account was not parseable as *gtsmodel.Account") + } + + return p.federateAccountUpdate(account, clientMsg.OriginAccount) + } case gtsmodel.ActivityStreamsAccept: // ACCEPT switch clientMsg.APObjectType { @@ -277,7 +287,27 @@ func (p *processor) federateAnnounce(boostWrapperStatus *gtsmodel.Status, boosti if err != nil { return fmt.Errorf("federateAnnounce: error parsing outboxURI %s: %s", boostingAccount.OutboxURI, err) } - + _, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, announce) return err } + +func (p *processor) federateAccountUpdate(updatedAccount *gtsmodel.Account, originAccount *gtsmodel.Account) error { + person, err := p.tc.AccountToAS(updatedAccount) + if err != nil { + return fmt.Errorf("federateAccountUpdate: error converting account to person: %s", err) + } + + update, err := p.tc.WrapPersonInUpdate(person, originAccount) + if err != nil { + return fmt.Errorf("federateAccountUpdate: error wrapping person in update: %s", err) + } + + outboxIRI, err := url.Parse(originAccount.OutboxURI) + if err != nil { + return fmt.Errorf("federateAnnounce: error parsing outboxURI %s: %s", originAccount.OutboxURI, err) + } + + _, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, update) + return err +} diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go index 93ac6bd..9cd7ad9 100644 --- a/internal/typeutils/converter.go +++ b/internal/typeutils/converter.go @@ -141,6 +141,13 @@ type TypeConverter interface { FollowRequestToFollow(f *gtsmodel.FollowRequest) *gtsmodel.Follow // StatusToBoost wraps the given status into a boosting status. StatusToBoost(s *gtsmodel.Status, boostingAccount *gtsmodel.Account) (*gtsmodel.Status, error) + + /* + WRAPPER CONVENIENCE FUNCTIONS + */ + + // WrapPersonInUpdate + WrapPersonInUpdate(person vocab.ActivityStreamsPerson, originAccount *gtsmodel.Account) (vocab.ActivityStreamsUpdate, error) } type converter struct { diff --git a/internal/typeutils/wrap.go b/internal/typeutils/wrap.go new file mode 100644 index 0000000..fde6fda --- /dev/null +++ b/internal/typeutils/wrap.go @@ -0,0 +1,61 @@ +package typeutils + +import ( + "fmt" + "net/url" + + "github.com/go-fed/activity/streams" + "github.com/go-fed/activity/streams/vocab" + "github.com/google/uuid" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/util" +) + +func (c *converter) WrapPersonInUpdate(person vocab.ActivityStreamsPerson, originAccount *gtsmodel.Account) (vocab.ActivityStreamsUpdate, error) { + + update := streams.NewActivityStreamsUpdate() + + // set the actor + actorURI, err := url.Parse(originAccount.URI) + if err != nil { + return nil, fmt.Errorf("WrapPersonInUpdate: error parsing url %s: %s", originAccount.URI, err) + } + actorProp := streams.NewActivityStreamsActorProperty() + actorProp.AppendIRI(actorURI) + update.SetActivityStreamsActor(actorProp) + + // set the ID + idString := util.GenerateURIForUpdate(originAccount.Username, c.config.Protocol, c.config.Host, uuid.NewString()) + idURI, err := url.Parse(idString) + if err != nil { + return nil, fmt.Errorf("WrapPersonInUpdate: error parsing url %s: %s", idString, err) + } + idProp := streams.NewJSONLDIdProperty() + idProp.SetIRI(idURI) + update.SetJSONLDId(idProp) + + // set the person as the object here + objectProp := streams.NewActivityStreamsObjectProperty() + objectProp.AppendActivityStreamsPerson(person) + update.SetActivityStreamsObject(objectProp) + + // to should be public + toURI, err := url.Parse(asPublicURI) + if err != nil { + return nil, fmt.Errorf("WrapPersonInUpdate: error parsing url %s: %s", asPublicURI, err) + } + toProp := streams.NewActivityStreamsToProperty() + toProp.AppendIRI(toURI) + update.SetActivityStreamsTo(toProp) + + // bcc followers + followersURI, err := url.Parse(originAccount.FollowersURI) + if err != nil { + return nil, fmt.Errorf("WrapPersonInUpdate: error parsing url %s: %s", originAccount.FollowersURI, err) + } + bccProp := streams.NewActivityStreamsBccProperty() + bccProp.AppendIRI(followersURI) + update.SetActivityStreamsBcc(bccProp) + + return update, nil +} diff --git a/internal/util/uri.go b/internal/util/uri.go index 8d64bdd..d8dce80 100644 --- a/internal/util/uri.go +++ b/internal/util/uri.go @@ -49,6 +49,8 @@ const ( PublicKeyPath = "main-key" // FollowPath used to generate the URI for an individual follow or follow request FollowPath = "follow" + // UpdatePath is used to generate the URI for an account update + UpdatePath = "updates" ) // APContextKey is a type used specifically for settings values on contexts within go-fed AP request chains @@ -108,13 +110,19 @@ type UserURIs struct { // GenerateURIForFollow returns the AP URI for a new follow -- something like: // https://example.org/users/whatever_user/follow/41c7f33f-1060-48d9-84df-38dcb13cf0d8 func GenerateURIForFollow(username string, protocol string, host string, thisFollowID string) string { - return fmt.Sprintf("%s://%s/%s/%s/%s", protocol, host, UsersPath, FollowPath, thisFollowID) + return fmt.Sprintf("%s://%s/%s/%s/%s/%s", protocol, host, UsersPath, username, FollowPath, thisFollowID) } // GenerateURIForFollow returns the AP URI for a new like/fave -- something like: // https://example.org/users/whatever_user/liked/41c7f33f-1060-48d9-84df-38dcb13cf0d8 func GenerateURIForLike(username string, protocol string, host string, thisFavedID string) string { - return fmt.Sprintf("%s://%s/%s/%s/%s", protocol, host, UsersPath, LikedPath, thisFavedID) + return fmt.Sprintf("%s://%s/%s/%s/%s/%s", protocol, host, UsersPath, username, LikedPath, thisFavedID) +} + +// GenerateURIForUpdate returns the AP URI for a new update activity -- something like: +// https://example.org/users/whatever_user#updates/41c7f33f-1060-48d9-84df-38dcb13cf0d8 +func GenerateURIForUpdate(username string, protocol string, host string, thisUpdateID string) string { + return fmt.Sprintf("%s://%s/%s/%s#%s/%s", protocol, host, UsersPath, username, UpdatePath, thisUpdateID) } // GenerateURIsForAccount throws together a bunch of URIs for the given username, with the given protocol and host.