incoming UNDO for follows now working

This commit is contained in:
tsmethurst 2021-05-18 23:06:20 +02:00
commit c7b4f847d8
18 changed files with 265 additions and 110 deletions

View file

@ -30,7 +30,7 @@
* [ ] /api/v1/accounts/:id/pin POST (Feature this account on profile) * [ ] /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/unpin POST (Remove this account from profile)
* [ ] /api/v1/accounts/:id/note POST (Make a personal note about this account) * [ ] /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) * [ ] /api/v1/accounts/search GET (Search for an account)
* [ ] Bookmarks * [ ] Bookmarks
* [ ] /api/v1/bookmarks GET (See bookmarked statuses) * [ ] /api/v1/bookmarks GET (See bookmarked statuses)
@ -177,6 +177,7 @@
* [ ] 'Greedy' federation * [ ] 'Greedy' federation
* [ ] No federation (insulate this instance from the Fediverse) * [ ] No federation (insulate this instance from the Fediverse)
* [ ] Allowlist * [ ] Allowlist
* [x] Secure HTTP signatures (creation and validation)
* [ ] Storage * [ ] Storage
* [x] Internal/statuses/preferences etc * [x] Internal/statuses/preferences etc
* [x] Postgres interface * [x] Postgres interface

View file

@ -22,7 +22,6 @@ import (
"context" "context"
"net" "net"
"github.com/go-fed/activity/pub"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
) )
@ -44,7 +43,7 @@ func (e ErrNoEntries) Error() string {
type DB interface { type DB interface {
// Federation returns an interface that's compatible with go-fed, for performing federation storage/retrieval functions. // 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 // 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 BASIC DB FUNCTIONALITY

View file

@ -30,7 +30,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/go-fed/activity/pub"
"github.com/go-pg/pg/extra/pgdebug" "github.com/go-pg/pg/extra/pgdebug"
"github.com/go-pg/pg/v10" "github.com/go-pg/pg/v10"
"github.com/go-pg/pg/v10/orm" "github.com/go-pg/pg/v10/orm"
@ -38,7 +37,6 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/federation"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/util"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -46,11 +44,11 @@ import (
// postgresService satisfies the DB interface // postgresService satisfies the DB interface
type postgresService struct { type postgresService struct {
config *config.Config config *config.Config
conn *pg.DB conn *pg.DB
log *logrus.Logger log *logrus.Logger
cancel context.CancelFunc cancel context.CancelFunc
federationDB pub.Database // federationDB pub.Database
} }
// NewPostgresService returns a postgresService derived from the provided config, which implements the go-fed DB interface. // 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, cancel: cancel,
} }
federatingDB := federation.NewFederatingDB(ps, c, log)
ps.federationDB = federatingDB
// we can confidently return this useable postgres service now // we can confidently return this useable postgres service now
return ps, nil return ps, nil
} }
@ -159,14 +154,6 @@ func derivePGOptions(c *config.Config) (*pg.Options, error) {
return options, nil return options, nil
} }
/*
FEDERATION FUNCTIONALITY
*/
func (ps *postgresService) Federation() pub.Database {
return ps.federationDB
}
/* /*
BASIC DB FUNCTIONALITY 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 { func (ps *postgresService) DeleteByID(id string, i interface{}) error {
if _, err := ps.conn.Model(i).Where("id = ?", id).Delete(); err != nil { if _, err := ps.conn.Model(i).Where("id = ?", id).Delete(); err != nil {
if err == pg.ErrNoRows { // if there are no rows *anyway* then that's fine
return db.ErrNoEntries{} // just return err if there's an actual error
if err != pg.ErrNoRows {
return err
} }
return err
} }
return nil return nil
} }
func (ps *postgresService) DeleteWhere(key string, value interface{}, i interface{}) error { 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 := ps.conn.Model(i).Where("? = ?", pg.Safe(key), value).Delete(); err != nil {
if err == pg.ErrNoRows { // if there are no rows *anyway* then that's fine
return db.ErrNoEntries{} // just return err if there's an actual error
if err != pg.ErrNoRows {
return err
} }
return err
} }
return nil return nil
} }

View file

@ -38,6 +38,11 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/util" "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. // 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. // It doesn't care what the underlying implementation of the DB interface is, as long as it works.
type federatingDB struct { type federatingDB struct {
@ -48,8 +53,8 @@ type federatingDB struct {
typeConverter typeutils.TypeConverter typeConverter typeutils.TypeConverter
} }
// NewFederatingDB returns a pub.Database interface using the given database, config, and logger. // NewFederatingDB returns a FederatingDB interface using the given database, config, and logger.
func NewFederatingDB(db db.DB, config *config.Config, log *logrus.Logger) pub.Database { func NewFederatingDB(db db.DB, config *config.Config, log *logrus.Logger) FederatingDB {
return &federatingDB{ return &federatingDB{
locks: new(sync.Map), locks: new(sync.Map),
db: db, db: db,
@ -405,7 +410,7 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
return nil return nil
} }
switch gtsmodel.ActivityStreamsActivity(asType.GetTypeName()) { switch asType.GetTypeName() {
case gtsmodel.ActivityStreamsCreate: case gtsmodel.ActivityStreamsCreate:
create, ok := asType.(vocab.ActivityStreamsCreate) create, ok := asType.(vocab.ActivityStreamsCreate)
if !ok { if !ok {
@ -413,7 +418,7 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
} }
object := create.GetActivityStreamsObject() object := create.GetActivityStreamsObject()
for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() { for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() {
switch gtsmodel.ActivityStreamsObject(objectIter.GetType().GetTypeName()) { switch objectIter.GetType().GetTypeName() {
case gtsmodel.ActivityStreamsNote: case gtsmodel.ActivityStreamsNote:
note := objectIter.GetActivityStreamsNote() note := objectIter.GetActivityStreamsNote()
status, err := f.typeConverter.ASStatusToStatus(note) status, err := f.typeConverter.ASStatusToStatus(note)
@ -425,9 +430,10 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
} }
fromFederatorChan <- gtsmodel.FromFederator{ fromFederatorChan <- gtsmodel.FromFederator{
APObjectType: gtsmodel.ActivityStreamsNote, APObjectType: gtsmodel.ActivityStreamsNote,
APActivityType: gtsmodel.ActivityStreamsCreate, APActivityType: gtsmodel.ActivityStreamsCreate,
GTSModel: status, GTSModel: status,
ReceivingAccount: targetAcct,
} }
} }
} }
@ -455,6 +461,98 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
return nil 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, &gtsmodel.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, &gtsmodel.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 // Update sets an existing entry to the database based on the value's
// id. // 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") 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: case gtsmodel.ActivityStreamsUpdate:
update, ok := asType.(vocab.ActivityStreamsCreate) update, ok := asType.(vocab.ActivityStreamsCreate)
if !ok { if !ok {

View file

@ -20,14 +20,12 @@ package federation
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"github.com/go-fed/activity/pub" "github.com/go-fed/activity/pub"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab" "github.com/go-fed/activity/streams/vocab"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
@ -271,7 +269,7 @@ func (f *federator) FederatingCallbacks(ctx context.Context) (wrapped pub.Federa
// override default undo behavior // override default undo behavior
other = []interface{}{ other = []interface{}{
func(ctx context.Context, undo vocab.ActivityStreamsUndo) error { func(ctx context.Context, undo vocab.ActivityStreamsUndo) error {
return f.typeConverter. return f.FederatingDB().Undo(ctx, undo)
}, },
} }

View file

@ -52,6 +52,7 @@ type Federator interface {
type federator struct { type federator struct {
config *config.Config config *config.Config
db db.DB db db.DB
federatingDB FederatingDB
clock pub.Clock clock pub.Clock
typeConverter typeutils.TypeConverter typeConverter typeutils.TypeConverter
transportController transport.Controller transportController transport.Controller
@ -60,18 +61,19 @@ type federator struct {
} }
// NewFederator returns a new federator // 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{} clock := &Clock{}
f := &federator{ f := &federator{
config: config, config: config,
db: db, db: db,
federatingDB: federatingDB,
clock: &Clock{}, clock: &Clock{},
typeConverter: typeConverter, typeConverter: typeConverter,
transportController: transportController, transportController: transportController,
log: log, log: log,
} }
actor := newFederatingActor(f, f, db.Federation(), clock) actor := newFederatingActor(f, f, federatingDB, clock)
f.actor = actor f.actor = actor
return f return f
} }
@ -79,3 +81,7 @@ func NewFederator(db db.DB, transportController transport.Controller, config *co
func (f *federator) FederatingActor() pub.FederatingActor { func (f *federator) FederatingActor() pub.FederatingActor {
return f.actor return f.actor
} }
func (f *federator) FederatingDB() FederatingDB {
return f.federatingDB
}

View file

@ -89,7 +89,7 @@ func (suite *ProtocolTestSuite) TestPostInboxRequestBodyHook() {
return nil, nil return nil, nil
})) }))
// setup module being tested // 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 // setup request
ctx := context.Background() ctx := context.Background()
@ -155,7 +155,7 @@ func (suite *ProtocolTestSuite) TestAuthenticatePostInbox() {
})) }))
// now setup module being tested, with the mock transport controller // 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 // setup request
ctx := context.Background() ctx := context.Background()

View file

@ -243,3 +243,23 @@ func (f *federator) GetTransportForUser(username string) (transport.Transport, e
} }
return transport, nil 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
}

View file

@ -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) return fmt.Errorf("error creating dbservice: %s", err)
} }
federatingDB := federation.NewFederatingDB(dbService, c, log)
router, err := router.New(c, log) router, err := router.New(c, log)
if err != nil { if err != nil {
return fmt.Errorf("error creating router: %s", err) 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) mediaHandler := media.New(c, dbService, storageBackend, log)
oauthServer := oauth.New(dbService, log) oauthServer := oauth.New(dbService, log)
transportController := transport.NewController(c, &federation.Clock{}, http.DefaultClient, 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) processor := message.NewProcessor(c, typeConverter, federator, oauthServer, mediaHandler, storageBackend, dbService, log)
if err := processor.Start(); err != nil { if err := processor.Start(); err != nil {
return fmt.Errorf("error starting processor: %s", err) return fmt.Errorf("error starting processor: %s", err)

View file

@ -107,7 +107,7 @@ type Account struct {
// URL for getting the featured collection list of this account // URL for getting the featured collection list of this account
FeaturedCollectionURI string `pg:",unique"` FeaturedCollectionURI string `pg:",unique"`
// What type of activitypub actor is this account? // What type of activitypub actor is this account?
ActorType ActivityStreamsActor ActorType string
// This account is associated with x account id // This account is associated with x account id
AlsoKnownAs string AlsoKnownAs string

View file

@ -18,110 +18,101 @@
package gtsmodel package gtsmodel
// ActivityStreamsObject refers to https://www.w3.org/TR/activitystreams-vocabulary/#object-types
type ActivityStreamsObject string
const ( const (
// ActivityStreamsArticle https://www.w3.org/TR/activitystreams-vocabulary/#dfn-article // 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 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 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 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 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 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 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 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 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 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 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 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 ( const (
// ActivityStreamsApplication https://www.w3.org/TR/activitystreams-vocabulary/#dfn-application // 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 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 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 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 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 ( const (
// ActivityStreamsAccept https://www.w3.org/TR/activitystreams-vocabulary/#dfn-accept // 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 https://www.w3.org/TR/activitystreams-vocabulary/#dfn-view
ActivityStreamsView ActivityStreamsActivity = "View" ActivityStreamsView = "View"
) )

View file

@ -9,8 +9,8 @@ package gtsmodel
// FromClientAPI wraps a message that travels from client API into the processor // FromClientAPI wraps a message that travels from client API into the processor
type FromClientAPI struct { type FromClientAPI struct {
APObjectType ActivityStreamsObject APObjectType string
APActivityType ActivityStreamsActivity APActivityType string
GTSModel interface{} GTSModel interface{}
} }
@ -23,8 +23,8 @@ type FromClientAPI struct {
// FromFederator wraps a message that travels from the federator into the processor // FromFederator wraps a message that travels from the federator into the processor
type FromFederator struct { type FromFederator struct {
APObjectType ActivityStreamsObject APObjectType string
APActivityType ActivityStreamsActivity APActivityType string
GTSModel interface{} GTSModel interface{}
ReceivingAccount *Account ReceivingAccount *Account
} }

View file

@ -66,7 +66,7 @@ type Status struct {
VisibilityAdvanced *VisibilityAdvanced VisibilityAdvanced *VisibilityAdvanced
// What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types // 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!. // Will probably almost always be Note but who knows!.
ActivityStreamsType ActivityStreamsObject ActivityStreamsType string
// Original text of the status without formatting // Original text of the status without formatting
Text string Text string
// Has this status been pinned by its owner? // Has this status been pinned by its owner?

View file

@ -90,7 +90,7 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable) (*gtsmode
} }
// check for bot and actor type // check for bot and actor type
switch gtsmodel.ActivityStreamsActor(accountable.GetTypeName()) { switch accountable.GetTypeName() {
case gtsmodel.ActivityStreamsPerson, gtsmodel.ActivityStreamsGroup, gtsmodel.ActivityStreamsOrganization: case gtsmodel.ActivityStreamsPerson, gtsmodel.ActivityStreamsGroup, gtsmodel.ActivityStreamsOrganization:
// people, groups, and organizations aren't bots // people, groups, and organizations aren't bots
acct.Bot = false acct.Bot = false
@ -101,7 +101,7 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable) (*gtsmode
// we don't know what this is! // we don't know what this is!
return nil, fmt.Errorf("type name %s not recognised or not convertible to gtsmodel.ActivityStreamsActor", accountable.GetTypeName()) 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 // 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 // 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 // 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 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 // we might be able to extract this from the contentMap field
// ActivityStreamsType // ActivityStreamsType
status.ActivityStreamsType = gtsmodel.ActivityStreamsObject(statusable.GetTypeName()) status.ActivityStreamsType = statusable.GetTypeName()
return status, nil return status, nil
} }
@ -341,6 +344,40 @@ func (c *converter) ASFollowToFollowRequest(followable Followable) (*gtsmodel.Fo
return followRequest, nil 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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.Follow{
URI: uri,
AccountID: originAccount.ID,
TargetAccountID: targetAccount.ID,
}
return follow, nil
}
func isPublic(tos []*url.URL) bool { func isPublic(tos []*url.URL) bool {
for _, entry := range tos { for _, entry := range tos {
if strings.EqualFold(entry.String(), "https://www.w3.org/ns/activitystreams#Public") { if strings.EqualFold(entry.String(), "https://www.w3.org/ns/activitystreams#Public") {

View file

@ -97,7 +97,9 @@ type TypeConverter interface {
ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, error) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, error)
// ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow request. // ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow request.
ASFollowToFollowRequest(followable Followable) (*gtsmodel.FollowRequest, error) 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 INTERNAL (gts) MODEL TO ACTIVITYSTREAMS MODEL
*/ */

View file

@ -48,6 +48,7 @@ import (
var Run action.GTSAction = func(ctx context.Context, _ *config.Config, log *logrus.Logger) error { var Run action.GTSAction = func(ctx context.Context, _ *config.Config, log *logrus.Logger) error {
c := NewTestConfig() c := NewTestConfig()
dbService := NewTestDB() dbService := NewTestDB()
federatingDB := NewTestFederatingDB(dbService)
router := NewTestRouter() router := NewTestRouter()
storageBackend := NewTestStorage() storageBackend := NewTestStorage()
@ -59,7 +60,7 @@ var Run action.GTSAction = func(ctx context.Context, _ *config.Config, log *logr
Body: r, Body: r,
}, nil }, nil
})) }))
federator := federation.NewFederator(dbService, transportController, c, log, typeConverter) federator := federation.NewFederator(dbService, federatingDB, transportController, c, log, typeConverter)
processor := NewTestProcessor(dbService, storageBackend, federator) processor := NewTestProcessor(dbService, storageBackend, federator)
if err := processor.Start(); err != nil { if err := processor.Start(); err != nil {
return fmt.Errorf("error starting processor: %s", err) return fmt.Errorf("error starting processor: %s", err)

11
testrig/federatingdb.go Normal file
View file

@ -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())
}

View file

@ -26,5 +26,5 @@ import (
// NewTestFederator returns a federator with the given database and (mock!!) transport controller. // NewTestFederator returns a federator with the given database and (mock!!) transport controller.
func NewTestFederator(db db.DB, tc transport.Controller) federation.Federator { 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))
} }