diff --git a/PROGRESS.md b/PROGRESS.md index f00d9b25f..95bb3fa8c 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -30,7 +30,7 @@ * [ ] /api/v1/accounts/:id/pin POST (Feature this account on profile) * [ ] /api/v1/accounts/:id/unpin POST (Remove this account from profile) * [ ] /api/v1/accounts/:id/note POST (Make a personal note about this account) - * [ ] /api/v1/accounts/relationships GET (Check relationships with accounts) + * [x] /api/v1/accounts/relationships GET (Check relationships with accounts) * [ ] /api/v1/accounts/search GET (Search for an account) * [ ] Bookmarks * [ ] /api/v1/bookmarks GET (See bookmarked statuses) @@ -177,6 +177,7 @@ * [ ] 'Greedy' federation * [ ] No federation (insulate this instance from the Fediverse) * [ ] Allowlist + * [x] Secure HTTP signatures (creation and validation) * [ ] Storage * [x] Internal/statuses/preferences etc * [x] Postgres interface diff --git a/internal/db/db.go b/internal/db/db.go index 375641502..509ff4e26 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -22,7 +22,6 @@ import ( "context" "net" - "github.com/go-fed/activity/pub" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) @@ -44,7 +43,7 @@ func (e ErrNoEntries) Error() string { type DB interface { // Federation returns an interface that's compatible with go-fed, for performing federation storage/retrieval functions. // See: https://pkg.go.dev/github.com/go-fed/activity@v1.0.0/pub?utm_source=gopls#Database - Federation() pub.Database + // Federation() federatingdb.FederatingDB /* BASIC DB FUNCTIONALITY diff --git a/internal/db/pg/pg.go b/internal/db/pg/pg.go index cd629a7eb..8ebe662aa 100644 --- a/internal/db/pg/pg.go +++ b/internal/db/pg/pg.go @@ -30,7 +30,6 @@ import ( "strings" "time" - "github.com/go-fed/activity/pub" "github.com/go-pg/pg/extra/pgdebug" "github.com/go-pg/pg/v10" "github.com/go-pg/pg/v10/orm" @@ -38,7 +37,6 @@ import ( "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/util" "golang.org/x/crypto/bcrypt" @@ -46,11 +44,11 @@ import ( // postgresService satisfies the DB interface type postgresService struct { - config *config.Config - conn *pg.DB - log *logrus.Logger - cancel context.CancelFunc - federationDB pub.Database + config *config.Config + conn *pg.DB + log *logrus.Logger + cancel context.CancelFunc + // federationDB pub.Database } // NewPostgresService returns a postgresService derived from the provided config, which implements the go-fed DB interface. @@ -97,9 +95,6 @@ func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logge cancel: cancel, } - federatingDB := federation.NewFederatingDB(ps, c, log) - ps.federationDB = federatingDB - // we can confidently return this useable postgres service now return ps, nil } @@ -159,14 +154,6 @@ func derivePGOptions(c *config.Config) (*pg.Options, error) { return options, nil } -/* - FEDERATION FUNCTIONALITY -*/ - -func (ps *postgresService) Federation() pub.Database { - return ps.federationDB -} - /* BASIC DB FUNCTIONALITY */ @@ -285,20 +272,22 @@ func (ps *postgresService) UpdateOneByID(id string, key string, value interface{ func (ps *postgresService) DeleteByID(id string, i interface{}) error { if _, err := ps.conn.Model(i).Where("id = ?", id).Delete(); err != nil { - if err == pg.ErrNoRows { - return db.ErrNoEntries{} + // if there are no rows *anyway* then that's fine + // just return err if there's an actual error + if err != pg.ErrNoRows { + return err } - return err } return nil } func (ps *postgresService) DeleteWhere(key string, value interface{}, i interface{}) error { if _, err := ps.conn.Model(i).Where("? = ?", pg.Safe(key), value).Delete(); err != nil { - if err == pg.ErrNoRows { - return db.ErrNoEntries{} + // if there are no rows *anyway* then that's fine + // just return err if there's an actual error + if err != pg.ErrNoRows { + return err } - return err } return nil } diff --git a/internal/federation/federating_db.go b/internal/federation/federating_db.go index b8c7ec9db..0c0206008 100644 --- a/internal/federation/federating_db.go +++ b/internal/federation/federating_db.go @@ -38,6 +38,11 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/util" ) +type FederatingDB interface { + pub.Database + Undo(c context.Context, asType vocab.Type) error +} + // FederatingDB uses the underlying DB interface to implement the go-fed pub.Database interface. // It doesn't care what the underlying implementation of the DB interface is, as long as it works. type federatingDB struct { @@ -48,8 +53,8 @@ type federatingDB struct { typeConverter typeutils.TypeConverter } -// NewFederatingDB returns a pub.Database interface using the given database, config, and logger. -func NewFederatingDB(db db.DB, config *config.Config, log *logrus.Logger) pub.Database { +// NewFederatingDB returns a FederatingDB interface using the given database, config, and logger. +func NewFederatingDB(db db.DB, config *config.Config, log *logrus.Logger) FederatingDB { return &federatingDB{ locks: new(sync.Map), db: db, @@ -405,7 +410,7 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { return nil } - switch gtsmodel.ActivityStreamsActivity(asType.GetTypeName()) { + switch asType.GetTypeName() { case gtsmodel.ActivityStreamsCreate: create, ok := asType.(vocab.ActivityStreamsCreate) if !ok { @@ -413,7 +418,7 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { } object := create.GetActivityStreamsObject() for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() { - switch gtsmodel.ActivityStreamsObject(objectIter.GetType().GetTypeName()) { + switch objectIter.GetType().GetTypeName() { case gtsmodel.ActivityStreamsNote: note := objectIter.GetActivityStreamsNote() status, err := f.typeConverter.ASStatusToStatus(note) @@ -425,9 +430,10 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { } fromFederatorChan <- gtsmodel.FromFederator{ - APObjectType: gtsmodel.ActivityStreamsNote, - APActivityType: gtsmodel.ActivityStreamsCreate, - GTSModel: status, + APObjectType: gtsmodel.ActivityStreamsNote, + APActivityType: gtsmodel.ActivityStreamsCreate, + GTSModel: status, + ReceivingAccount: targetAcct, } } } @@ -455,6 +461,98 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { return nil } +func (f *federatingDB) Undo(ctx context.Context, asType vocab.Type) error { + l := f.log.WithFields( + logrus.Fields{ + "func": "Undo", + "asType": asType.GetTypeName(), + }, + ) + m, err := streams.Serialize(asType) + if err != nil { + return err + } + b, err := json.Marshal(m) + if err != nil { + return err + } + l.Debugf("received UNDO asType %s", string(b)) + + targetAcctI := ctx.Value(util.APAccount) + if targetAcctI == nil { + l.Error("UNDO: target account wasn't set on context") + return nil + } + targetAcct, ok := targetAcctI.(*gtsmodel.Account) + if !ok { + l.Error("UNDO: target account was set on context but couldn't be parsed") + return nil + } + + // fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey) + // if fromFederatorChanI == nil { + // l.Error("from federator channel wasn't set on context") + // return nil + // } + // fromFederatorChan, ok := fromFederatorChanI.(chan gtsmodel.FromFederator) + // if !ok { + // l.Error("from federator channel was set on context but couldn't be parsed") + // return nil + // } + + switch asType.GetTypeName() { + // UNDO + case gtsmodel.ActivityStreamsUndo: + undo, ok := asType.(vocab.ActivityStreamsUndo) + if !ok { + return errors.New("UNDO: couldn't parse UNDO into vocab.ActivityStreamsUndo") + } + undoObject := undo.GetActivityStreamsObject() + if undoObject == nil { + return errors.New("UNDO: no object set on vocab.ActivityStreamsUndo") + } + + for iter := undoObject.Begin(); iter != undoObject.End(); iter = iter.Next() { + switch iter.GetType().GetTypeName() { + case string(gtsmodel.ActivityStreamsFollow): + // UNDO FOLLOW + ASFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow) + if !ok { + return errors.New("UNDO: couldn't parse follow into vocab.ActivityStreamsFollow") + } + // make sure the actor owns the follow + if !sameActor(undo.GetActivityStreamsActor(), ASFollow.GetActivityStreamsActor()) { + return errors.New("UNDO: follow actor and activity actor not the same") + } + // convert the follow to something we can understand + gtsFollow, err := f.typeConverter.ASFollowToFollow(ASFollow) + if err != nil { + return fmt.Errorf("UNDO: 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 gtsFollow.TargetAccountID != targetAcct.ID { + return errors.New("UNDO: follow object account and inbox account were not the same") + } + // delete any existing FOLLOW + if err := f.db.DeleteWhere("uri", gtsFollow.URI, >smodel.Follow{}); err != nil { + return fmt.Errorf("UNDO: db error removing follow: %s", err) + } + // delete any existing FOLLOW REQUEST + if err := f.db.DeleteWhere("uri", gtsFollow.URI, >smodel.FollowRequest{}); err != nil { + return fmt.Errorf("UNDO: db error removing follow request: %s", err) + } + l.Debug("follow undone") + return nil + case string(gtsmodel.ActivityStreamsLike): + // UNDO LIKE + case string(gtsmodel.ActivityStreamsAnnounce): + // UNDO BOOST/REBLOG/ANNOUNCE + } + } + } + return nil +} + // Update sets an existing entry to the database based on the value's // id. // @@ -500,7 +598,7 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error { l.Error("from federator channel was set on context but couldn't be parsed") } - switch gtsmodel.ActivityStreamsActivity(asType.GetTypeName()) { + switch asType.GetTypeName() { case gtsmodel.ActivityStreamsUpdate: update, ok := asType.(vocab.ActivityStreamsCreate) if !ok { diff --git a/internal/federation/federatingprotocol.go b/internal/federation/federatingprotocol.go index 9941ecc0c..ca475f9f9 100644 --- a/internal/federation/federatingprotocol.go +++ b/internal/federation/federatingprotocol.go @@ -20,14 +20,12 @@ package federation import ( "context" - "encoding/json" "errors" "fmt" "net/http" "net/url" "github.com/go-fed/activity/pub" - "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -271,7 +269,7 @@ func (f *federator) FederatingCallbacks(ctx context.Context) (wrapped pub.Federa // override default undo behavior other = []interface{}{ func(ctx context.Context, undo vocab.ActivityStreamsUndo) error { - return f.typeConverter. + return f.FederatingDB().Undo(ctx, undo) }, } diff --git a/internal/federation/federator.go b/internal/federation/federator.go index a3b1386e4..c1cf21ab1 100644 --- a/internal/federation/federator.go +++ b/internal/federation/federator.go @@ -52,6 +52,7 @@ type Federator interface { type federator struct { config *config.Config db db.DB + federatingDB FederatingDB clock pub.Clock typeConverter typeutils.TypeConverter transportController transport.Controller @@ -60,18 +61,19 @@ type federator struct { } // NewFederator returns a new federator -func NewFederator(db db.DB, transportController transport.Controller, config *config.Config, log *logrus.Logger, typeConverter typeutils.TypeConverter) Federator { +func NewFederator(db db.DB, federatingDB FederatingDB, transportController transport.Controller, config *config.Config, log *logrus.Logger, typeConverter typeutils.TypeConverter) Federator { clock := &Clock{} f := &federator{ config: config, db: db, + federatingDB: federatingDB, clock: &Clock{}, typeConverter: typeConverter, transportController: transportController, log: log, } - actor := newFederatingActor(f, f, db.Federation(), clock) + actor := newFederatingActor(f, f, federatingDB, clock) f.actor = actor return f } @@ -79,3 +81,7 @@ func NewFederator(db db.DB, transportController transport.Controller, config *co func (f *federator) FederatingActor() pub.FederatingActor { return f.actor } + +func (f *federator) FederatingDB() FederatingDB { + return f.federatingDB +} diff --git a/internal/federation/federator_test.go b/internal/federation/federator_test.go index 2eab09507..e5d42b53d 100644 --- a/internal/federation/federator_test.go +++ b/internal/federation/federator_test.go @@ -89,7 +89,7 @@ func (suite *ProtocolTestSuite) TestPostInboxRequestBodyHook() { return nil, nil })) // setup module being tested - federator := federation.NewFederator(suite.db, tc, suite.config, suite.log, suite.typeConverter) + federator := federation.NewFederator(suite.db, testrig.NewTestFederatingDB(suite.db), tc, suite.config, suite.log, suite.typeConverter) // setup request ctx := context.Background() @@ -155,7 +155,7 @@ func (suite *ProtocolTestSuite) TestAuthenticatePostInbox() { })) // now setup module being tested, with the mock transport controller - federator := federation.NewFederator(suite.db, tc, suite.config, suite.log, suite.typeConverter) + federator := federation.NewFederator(suite.db, testrig.NewTestFederatingDB(suite.db), tc, suite.config, suite.log, suite.typeConverter) // setup request ctx := context.Background() diff --git a/internal/federation/util.go b/internal/federation/util.go index 14ceaeb1d..6949d27c2 100644 --- a/internal/federation/util.go +++ b/internal/federation/util.go @@ -243,3 +243,23 @@ func (f *federator) GetTransportForUser(username string) (transport.Transport, e } return transport, nil } + +func sameActor(activityActor vocab.ActivityStreamsActorProperty, followActor vocab.ActivityStreamsActorProperty) bool { + if activityActor == nil || followActor == nil { + return false + } + for aIter := activityActor.Begin(); aIter != activityActor.End(); aIter = aIter.Next() { + for fIter := followActor.Begin(); fIter != followActor.End(); fIter = fIter.Next() { + if aIter.GetIRI() == nil { + return false + } + if fIter.GetIRI() == nil { + return false + } + if aIter.GetIRI().String() == fIter.GetIRI().String() { + return true + } + } + } + return false +} diff --git a/internal/gotosocial/actions.go b/internal/gotosocial/actions.go index 94b29b883..557a39626 100644 --- a/internal/gotosocial/actions.go +++ b/internal/gotosocial/actions.go @@ -83,6 +83,8 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr return fmt.Errorf("error creating dbservice: %s", err) } + federatingDB := federation.NewFederatingDB(dbService, c, log) + router, err := router.New(c, log) if err != nil { return fmt.Errorf("error creating router: %s", err) @@ -100,7 +102,7 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr mediaHandler := media.New(c, dbService, storageBackend, log) oauthServer := oauth.New(dbService, log) transportController := transport.NewController(c, &federation.Clock{}, http.DefaultClient, log) - federator := federation.NewFederator(dbService, transportController, c, log, typeConverter) + federator := federation.NewFederator(dbService, federatingDB, transportController, c, log, typeConverter) processor := message.NewProcessor(c, typeConverter, federator, oauthServer, mediaHandler, storageBackend, dbService, log) if err := processor.Start(); err != nil { return fmt.Errorf("error starting processor: %s", err) diff --git a/internal/gtsmodel/account.go b/internal/gtsmodel/account.go index dce0795fe..d6ce95cc9 100644 --- a/internal/gtsmodel/account.go +++ b/internal/gtsmodel/account.go @@ -107,7 +107,7 @@ type Account struct { // URL for getting the featured collection list of this account FeaturedCollectionURI string `pg:",unique"` // What type of activitypub actor is this account? - ActorType ActivityStreamsActor + ActorType string // This account is associated with x account id AlsoKnownAs string diff --git a/internal/gtsmodel/activitystreams.go b/internal/gtsmodel/activitystreams.go index f852340bb..3fe6107b5 100644 --- a/internal/gtsmodel/activitystreams.go +++ b/internal/gtsmodel/activitystreams.go @@ -18,110 +18,101 @@ package gtsmodel -// ActivityStreamsObject refers to https://www.w3.org/TR/activitystreams-vocabulary/#object-types -type ActivityStreamsObject string - const ( // ActivityStreamsArticle https://www.w3.org/TR/activitystreams-vocabulary/#dfn-article - ActivityStreamsArticle ActivityStreamsObject = "Article" + ActivityStreamsArticle = "Article" // ActivityStreamsAudio https://www.w3.org/TR/activitystreams-vocabulary/#dfn-audio - ActivityStreamsAudio ActivityStreamsObject = "Audio" + ActivityStreamsAudio = "Audio" // ActivityStreamsDocument https://www.w3.org/TR/activitystreams-vocabulary/#dfn-document - ActivityStreamsDocument ActivityStreamsObject = "Event" + ActivityStreamsDocument = "Event" // ActivityStreamsEvent https://www.w3.org/TR/activitystreams-vocabulary/#dfn-event - ActivityStreamsEvent ActivityStreamsObject = "Event" + ActivityStreamsEvent = "Event" // ActivityStreamsImage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-image - ActivityStreamsImage ActivityStreamsObject = "Image" + ActivityStreamsImage = "Image" // ActivityStreamsNote https://www.w3.org/TR/activitystreams-vocabulary/#dfn-note - ActivityStreamsNote ActivityStreamsObject = "Note" + ActivityStreamsNote = "Note" // ActivityStreamsPage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-page - ActivityStreamsPage ActivityStreamsObject = "Page" + ActivityStreamsPage = "Page" // ActivityStreamsPlace https://www.w3.org/TR/activitystreams-vocabulary/#dfn-place - ActivityStreamsPlace ActivityStreamsObject = "Place" + ActivityStreamsPlace = "Place" // ActivityStreamsProfile https://www.w3.org/TR/activitystreams-vocabulary/#dfn-profile - ActivityStreamsProfile ActivityStreamsObject = "Profile" + ActivityStreamsProfile = "Profile" // ActivityStreamsRelationship https://www.w3.org/TR/activitystreams-vocabulary/#dfn-relationship - ActivityStreamsRelationship ActivityStreamsObject = "Relationship" + ActivityStreamsRelationship = "Relationship" // ActivityStreamsTombstone https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone - ActivityStreamsTombstone ActivityStreamsObject = "Tombstone" + ActivityStreamsTombstone = "Tombstone" // ActivityStreamsVideo https://www.w3.org/TR/activitystreams-vocabulary/#dfn-video - ActivityStreamsVideo ActivityStreamsObject = "Video" + ActivityStreamsVideo = "Video" ) -// ActivityStreamsActor refers to https://www.w3.org/TR/activitystreams-vocabulary/#actor-types -type ActivityStreamsActor string - const ( // ActivityStreamsApplication https://www.w3.org/TR/activitystreams-vocabulary/#dfn-application - ActivityStreamsApplication ActivityStreamsActor = "Application" + ActivityStreamsApplication = "Application" // ActivityStreamsGroup https://www.w3.org/TR/activitystreams-vocabulary/#dfn-group - ActivityStreamsGroup ActivityStreamsActor = "Group" + ActivityStreamsGroup = "Group" // ActivityStreamsOrganization https://www.w3.org/TR/activitystreams-vocabulary/#dfn-organization - ActivityStreamsOrganization ActivityStreamsActor = "Organization" + ActivityStreamsOrganization = "Organization" // ActivityStreamsPerson https://www.w3.org/TR/activitystreams-vocabulary/#dfn-person - ActivityStreamsPerson ActivityStreamsActor = "Person" + ActivityStreamsPerson = "Person" // ActivityStreamsService https://www.w3.org/TR/activitystreams-vocabulary/#dfn-service - ActivityStreamsService ActivityStreamsActor = "Service" + ActivityStreamsService = "Service" ) -// ActivityStreamsActivity refers to https://www.w3.org/TR/activitystreams-vocabulary/#activity-types -type ActivityStreamsActivity string - const ( // ActivityStreamsAccept https://www.w3.org/TR/activitystreams-vocabulary/#dfn-accept - ActivityStreamsAccept ActivityStreamsActivity = "Accept" + ActivityStreamsAccept = "Accept" // ActivityStreamsAdd https://www.w3.org/TR/activitystreams-vocabulary/#dfn-add - ActivityStreamsAdd ActivityStreamsActivity = "Add" + ActivityStreamsAdd = "Add" // ActivityStreamsAnnounce https://www.w3.org/TR/activitystreams-vocabulary/#dfn-announce - ActivityStreamsAnnounce ActivityStreamsActivity = "Announce" + ActivityStreamsAnnounce = "Announce" // ActivityStreamsArrive https://www.w3.org/TR/activitystreams-vocabulary/#dfn-arrive - ActivityStreamsArrive ActivityStreamsActivity = "Arrive" + ActivityStreamsArrive = "Arrive" // ActivityStreamsBlock https://www.w3.org/TR/activitystreams-vocabulary/#dfn-block - ActivityStreamsBlock ActivityStreamsActivity = "Block" + ActivityStreamsBlock = "Block" // ActivityStreamsCreate https://www.w3.org/TR/activitystreams-vocabulary/#dfn-create - ActivityStreamsCreate ActivityStreamsActivity = "Create" + ActivityStreamsCreate = "Create" // ActivityStreamsDelete https://www.w3.org/TR/activitystreams-vocabulary/#dfn-delete - ActivityStreamsDelete ActivityStreamsActivity = "Delete" + ActivityStreamsDelete = "Delete" // ActivityStreamsDislike https://www.w3.org/TR/activitystreams-vocabulary/#dfn-dislike - ActivityStreamsDislike ActivityStreamsActivity = "Dislike" + ActivityStreamsDislike = "Dislike" // ActivityStreamsFlag https://www.w3.org/TR/activitystreams-vocabulary/#dfn-flag - ActivityStreamsFlag ActivityStreamsActivity = "Flag" + ActivityStreamsFlag = "Flag" // ActivityStreamsFollow https://www.w3.org/TR/activitystreams-vocabulary/#dfn-follow - ActivityStreamsFollow ActivityStreamsActivity = "Follow" + ActivityStreamsFollow = "Follow" // ActivityStreamsIgnore https://www.w3.org/TR/activitystreams-vocabulary/#dfn-ignore - ActivityStreamsIgnore ActivityStreamsActivity = "Ignore" + ActivityStreamsIgnore = "Ignore" // ActivityStreamsInvite https://www.w3.org/TR/activitystreams-vocabulary/#dfn-invite - ActivityStreamsInvite ActivityStreamsActivity = "Invite" + ActivityStreamsInvite = "Invite" // ActivityStreamsJoin https://www.w3.org/TR/activitystreams-vocabulary/#dfn-join - ActivityStreamsJoin ActivityStreamsActivity = "Join" + ActivityStreamsJoin = "Join" // ActivityStreamsLeave https://www.w3.org/TR/activitystreams-vocabulary/#dfn-leave - ActivityStreamsLeave ActivityStreamsActivity = "Leave" + ActivityStreamsLeave = "Leave" // ActivityStreamsLike https://www.w3.org/TR/activitystreams-vocabulary/#dfn-like - ActivityStreamsLike ActivityStreamsActivity = "Like" + ActivityStreamsLike = "Like" // ActivityStreamsListen https://www.w3.org/TR/activitystreams-vocabulary/#dfn-listen - ActivityStreamsListen ActivityStreamsActivity = "Listen" + ActivityStreamsListen = "Listen" // ActivityStreamsMove https://www.w3.org/TR/activitystreams-vocabulary/#dfn-move - ActivityStreamsMove ActivityStreamsActivity = "Move" + ActivityStreamsMove = "Move" // ActivityStreamsOffer https://www.w3.org/TR/activitystreams-vocabulary/#dfn-offer - ActivityStreamsOffer ActivityStreamsActivity = "Offer" + ActivityStreamsOffer = "Offer" // ActivityStreamsQuestion https://www.w3.org/TR/activitystreams-vocabulary/#dfn-question - ActivityStreamsQuestion ActivityStreamsActivity = "Question" + ActivityStreamsQuestion = "Question" // ActivityStreamsReject https://www.w3.org/TR/activitystreams-vocabulary/#dfn-reject - ActivityStreamsReject ActivityStreamsActivity = "Reject" + ActivityStreamsReject = "Reject" // ActivityStreamsRead https://www.w3.org/TR/activitystreams-vocabulary/#dfn-read - ActivityStreamsRead ActivityStreamsActivity = "Read" + ActivityStreamsRead = "Read" // ActivityStreamsRemove https://www.w3.org/TR/activitystreams-vocabulary/#dfn-remove - ActivityStreamsRemove ActivityStreamsActivity = "Remove" + ActivityStreamsRemove = "Remove" // ActivityStreamsTentativeReject https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tentativereject - ActivityStreamsTentativeReject ActivityStreamsActivity = "TentativeReject" + ActivityStreamsTentativeReject = "TentativeReject" // ActivityStreamsTentativeAccept https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tentativeaccept - ActivityStreamsTentativeAccept ActivityStreamsActivity = "TentativeAccept" + ActivityStreamsTentativeAccept = "TentativeAccept" // ActivityStreamsTravel https://www.w3.org/TR/activitystreams-vocabulary/#dfn-travel - ActivityStreamsTravel ActivityStreamsActivity = "Travel" + ActivityStreamsTravel = "Travel" // ActivityStreamsUndo https://www.w3.org/TR/activitystreams-vocabulary/#dfn-undo - ActivityStreamsUndo ActivityStreamsActivity = "Undo" + ActivityStreamsUndo = "Undo" // ActivityStreamsUpdate https://www.w3.org/TR/activitystreams-vocabulary/#dfn-update - ActivityStreamsUpdate ActivityStreamsActivity = "Update" + ActivityStreamsUpdate = "Update" // ActivityStreamsView https://www.w3.org/TR/activitystreams-vocabulary/#dfn-view - ActivityStreamsView ActivityStreamsActivity = "View" + ActivityStreamsView = "View" ) diff --git a/internal/gtsmodel/messages.go b/internal/gtsmodel/messages.go index 5c2275461..4dd2a0594 100644 --- a/internal/gtsmodel/messages.go +++ b/internal/gtsmodel/messages.go @@ -9,8 +9,8 @@ package gtsmodel // FromClientAPI wraps a message that travels from client API into the processor type FromClientAPI struct { - APObjectType ActivityStreamsObject - APActivityType ActivityStreamsActivity + APObjectType string + APActivityType string GTSModel interface{} } @@ -23,8 +23,8 @@ type FromClientAPI struct { // FromFederator wraps a message that travels from the federator into the processor type FromFederator struct { - APObjectType ActivityStreamsObject - APActivityType ActivityStreamsActivity + APObjectType string + APActivityType string GTSModel interface{} ReceivingAccount *Account } diff --git a/internal/gtsmodel/status.go b/internal/gtsmodel/status.go index d0d479520..b5ac8def1 100644 --- a/internal/gtsmodel/status.go +++ b/internal/gtsmodel/status.go @@ -66,7 +66,7 @@ type Status struct { VisibilityAdvanced *VisibilityAdvanced // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types // Will probably almost always be Note but who knows!. - ActivityStreamsType ActivityStreamsObject + ActivityStreamsType string // Original text of the status without formatting Text string // Has this status been pinned by its owner? diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go index 4aa6e2b19..d6541719d 100644 --- a/internal/typeutils/astointernal.go +++ b/internal/typeutils/astointernal.go @@ -90,7 +90,7 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable) (*gtsmode } // check for bot and actor type - switch gtsmodel.ActivityStreamsActor(accountable.GetTypeName()) { + switch accountable.GetTypeName() { case gtsmodel.ActivityStreamsPerson, gtsmodel.ActivityStreamsGroup, gtsmodel.ActivityStreamsOrganization: // people, groups, and organizations aren't bots acct.Bot = false @@ -101,7 +101,7 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable) (*gtsmode // we don't know what this is! return nil, fmt.Errorf("type name %s not recognised or not convertible to gtsmodel.ActivityStreamsActor", accountable.GetTypeName()) } - acct.ActorType = gtsmodel.ActivityStreamsActor(accountable.GetTypeName()) + acct.ActorType = accountable.GetTypeName() // TODO: locked aka manuallyApprovesFollowers @@ -281,7 +281,10 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e // if it's CC'ed to public, it's public or unlocked // mentioned SPECIFIC ACCOUNTS also get added to CC'es if it's not a direct message - if isPublic(cc) || isPublic(to) { + if isPublic(cc) { + visibility = gtsmodel.VisibilityUnlocked + } + if isPublic(to) { visibility = gtsmodel.VisibilityPublic } @@ -301,7 +304,7 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e // we might be able to extract this from the contentMap field // ActivityStreamsType - status.ActivityStreamsType = gtsmodel.ActivityStreamsObject(statusable.GetTypeName()) + status.ActivityStreamsType = statusable.GetTypeName() return status, nil } @@ -341,6 +344,40 @@ func (c *converter) ASFollowToFollowRequest(followable Followable) (*gtsmodel.Fo return followRequest, nil } +func (c *converter) ASFollowToFollow(followable Followable) (*gtsmodel.Follow, error) { + idProp := followable.GetJSONLDId() + if idProp == nil || !idProp.IsIRI() { + return nil, errors.New("no id property set on follow, or was not an iri") + } + uri := idProp.GetIRI().String() + + origin, err := extractActor(followable) + if err != nil { + return nil, errors.New("error extracting actor property from follow") + } + originAccount := >smodel.Account{} + if err := c.db.GetWhere("uri", origin.String(), originAccount); err != nil { + return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) + } + + target, err := extractObject(followable) + if err != nil { + return nil, errors.New("error extracting object property from follow") + } + targetAccount := >smodel.Account{} + if err := c.db.GetWhere("uri", target.String(), targetAccount); err != nil { + return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) + } + + follow := >smodel.Follow{ + URI: uri, + AccountID: originAccount.ID, + TargetAccountID: targetAccount.ID, + } + + return follow, nil +} + func isPublic(tos []*url.URL) bool { for _, entry := range tos { if strings.EqualFold(entry.String(), "https://www.w3.org/ns/activitystreams#Public") { diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go index 415b6f4a6..8ff2b1878 100644 --- a/internal/typeutils/converter.go +++ b/internal/typeutils/converter.go @@ -97,7 +97,9 @@ type TypeConverter interface { ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, error) // ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow request. ASFollowToFollowRequest(followable Followable) (*gtsmodel.FollowRequest, error) - + // ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow. + ASFollowToFollow(followable Followable) (*gtsmodel.Follow, error) + /* INTERNAL (gts) MODEL TO ACTIVITYSTREAMS MODEL */ diff --git a/testrig/actions.go b/testrig/actions.go index 7ed75b18f..aa78799b8 100644 --- a/testrig/actions.go +++ b/testrig/actions.go @@ -48,6 +48,7 @@ import ( var Run action.GTSAction = func(ctx context.Context, _ *config.Config, log *logrus.Logger) error { c := NewTestConfig() dbService := NewTestDB() + federatingDB := NewTestFederatingDB(dbService) router := NewTestRouter() storageBackend := NewTestStorage() @@ -59,7 +60,7 @@ var Run action.GTSAction = func(ctx context.Context, _ *config.Config, log *logr Body: r, }, nil })) - federator := federation.NewFederator(dbService, transportController, c, log, typeConverter) + federator := federation.NewFederator(dbService, federatingDB, transportController, c, log, typeConverter) processor := NewTestProcessor(dbService, storageBackend, federator) if err := processor.Start(); err != nil { return fmt.Errorf("error starting processor: %s", err) diff --git a/testrig/federatingdb.go b/testrig/federatingdb.go new file mode 100644 index 000000000..5cce24752 --- /dev/null +++ b/testrig/federatingdb.go @@ -0,0 +1,11 @@ +package testrig + +import ( + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/federation" +) + +// NewTestFederatingDB returns a federating DB with the underlying db +func NewTestFederatingDB(db db.DB) federation.FederatingDB { + return federation.NewFederatingDB(db, NewTestConfig(), NewTestLog()) +} diff --git a/testrig/federator.go b/testrig/federator.go index c2d86fd21..e113c43b4 100644 --- a/testrig/federator.go +++ b/testrig/federator.go @@ -26,5 +26,5 @@ import ( // NewTestFederator returns a federator with the given database and (mock!!) transport controller. func NewTestFederator(db db.DB, tc transport.Controller) federation.Federator { - return federation.NewFederator(db, tc, NewTestConfig(), NewTestLog(), NewTestTypeConverter(db)) + return federation.NewFederator(db, NewTestFederatingDB(db), tc, NewTestConfig(), NewTestLog(), NewTestTypeConverter(db)) }