diff --git a/internal/api/s2s/user/userget.go b/internal/api/s2s/user/userget.go index d62b4b5e6..4828cbbd3 100644 --- a/internal/api/s2s/user/userget.go +++ b/internal/api/s2s/user/userget.go @@ -23,7 +23,6 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) // UsersGETHandler should be served at https://example.org/users/:username. @@ -55,60 +54,14 @@ func (m *Module) UsersGETHandler(c *gin.Context) { } l.Tracef("negotiated format: %s", format) - // get the account the request is referring to - requestedAccount := >smodel.Account{} - if err := m.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil { - l.Errorf("database error getting account with username %s: %s", requestedUsername, err) - // we'll just return not authorized here to avoid giving anything away - c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"}) - return - } - - // and create a transport for it - transport, err := m.federator.TransportController().NewTransport(requestedAccount.PublicKeyURI, requestedAccount.PrivateKey) + // make a copy of the context to pass along so we don't break anything + cp := c.Copy() + user, err := m.processor.GetAPUser(requestedUsername, cp.Request) // GetAPUser handles auth as well if err != nil { - l.Errorf("error creating transport for username %s: %s", requestedUsername, err) - // we'll just return not authorized here to avoid giving anything away - c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"}) + l.Info(err.Error()) + c.JSON(err.Code(), gin.H{"error": err.Safe()}) return } - // authenticate the request - authentication, err := federation.AuthenticateFederatedRequest(transport, c.Request) - if err != nil { - l.Errorf("error authenticating GET user request: %s", err) - c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"}) - return - } - - if !authentication.Authenticated { - l.Debug("request not authorized") - c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"}) - return - } - - requestingAccount := >smodel.Account{} - if authentication.RequestingPublicKeyID != nil { - if err := m.db.GetWhere("public_key_uri", authentication.RequestingPublicKeyID.String(), requestingAccount); err != nil { - - } - } - - authorization, err := federation.AuthorizeFederatedRequest - - person, err := m.tc.AccountToAS(requestedAccount) - if err != nil { - l.Errorf("error converting account to ap person: %s", err) - c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"}) - return - } - - data, err := person.Serialize() - if err != nil { - l.Errorf("error serializing user: %s", err) - c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"}) - return - } - - c.JSON(http.StatusOK, data) + c.JSON(http.StatusOK, user) } diff --git a/internal/db/federating_db.go b/internal/db/federating_db.go index a1746cd2d..ab66b19de 100644 --- a/internal/db/federating_db.go +++ b/internal/db/federating_db.go @@ -104,7 +104,30 @@ func (f *federatingDB) Unlock(c context.Context, id *url.URL) error { // // The library makes this call only after acquiring a lock first. func (f *federatingDB) InboxContains(c context.Context, inbox, id *url.URL) (contains bool, err error) { - return false, nil + + if !util.IsInboxPath(inbox) { + return false, fmt.Errorf("%s is not an inbox URI", inbox.String()) + } + + if !util.IsStatusesPath(id) { + return false, fmt.Errorf("%s is not a status URI", id.String()) + } + _, statusID, err := util.ParseStatusesPath(inbox) + if err != nil { + return false, fmt.Errorf("status URI %s was not parseable: %s", id.String(), err) + } + + if err := f.db.GetByID(statusID, >smodel.Status{}); err != nil { + if _, ok := err.(ErrNoEntries); ok { + // we don't have it + return false, nil + } + // actual error + return false, fmt.Errorf("error getting status from db: %s", err) + } + + // we must have it + return true, nil } // GetInbox returns the first ordered collection page of the outbox at @@ -128,11 +151,6 @@ func (f *federatingDB) SetInbox(c context.Context, inbox vocab.ActivityStreamsOr // the database has an entry for the IRI. // The library makes this call only after acquiring a lock first. func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) { - l := f.log.WithFields(logrus.Fields{ - "func": "Owns", - "activityID": id.String(), - }) - // if the id host isn't this instance host, we don't own this IRI if id.Host != f.config.Host { return false, nil @@ -142,27 +160,18 @@ func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) { // check if it's a status, eg /users/example_username/statuses/SOME_UUID_OF_A_STATUS if util.IsStatusesPath(id) { - username, uid, err := util.ParseStatusesPath(id) + _, uid, err := util.ParseStatusesPath(id) if err != nil { return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err) } - acct := >smodel.Account{} - if err := f.db.GetLocalAccountByUsername(username, acct); err != nil { - if _, ok := err.(ErrNoEntries); ok { - // there are no entries for this username - return false, nil - } - return false, fmt.Errorf("database error fetching account with username %s: %s", username, err) - } - status := >smodel.Status{} - if err := f.db.GetByID(uid, status); err != nil { + if err := f.db.GetWhere("uri", uid, >smodel.Status{}); err != nil { if _, ok := err.(ErrNoEntries); ok { // there are no entries for this status return false, nil } + // an actual error happened return false, fmt.Errorf("database error fetching status with id %s: %s", uid, err) } - // the user exists, the status exists, we own both, we're good return true, nil } @@ -172,34 +181,52 @@ func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) { if err != nil { return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err) } - acct := >smodel.Account{} - if err := f.db.GetLocalAccountByUsername(username, acct); err != nil { + if err := f.db.GetLocalAccountByUsername(username, >smodel.Account{}); err != nil { if _, ok := err.(ErrNoEntries); ok { // there are no entries for this username return false, nil } + // an actual error happened return false, fmt.Errorf("database error fetching account with username %s: %s", username, err) } - // the user exists, we own it, we're good return true, nil } - l.Info("could not match activityID") - return false, nil + return false, fmt.Errorf("could not match activityID: %s", id.String()) } // ActorForOutbox fetches the actor's IRI for the given outbox IRI. // // The library makes this call only after acquiring a lock first. func (f *federatingDB) ActorForOutbox(c context.Context, outboxIRI *url.URL) (actorIRI *url.URL, err error) { - return nil, nil + if !util.IsOutboxPath(outboxIRI) { + return nil, fmt.Errorf("%s is not an outbox URI", outboxIRI.String()) + } + acct := >smodel.Account{} + if err := f.db.GetWhere("outbox_uri", outboxIRI.String(), acct); err != nil { + if _, ok := err.(ErrNoEntries); ok { + return nil, fmt.Errorf("no actor found that corresponds to outbox %s", outboxIRI.String()) + } + return nil, fmt.Errorf("db error searching for actor with outbox %s", outboxIRI.String()) + } + return url.Parse(acct.URI) } // ActorForInbox fetches the actor's IRI for the given outbox IRI. // // The library makes this call only after acquiring a lock first. func (f *federatingDB) ActorForInbox(c context.Context, inboxIRI *url.URL) (actorIRI *url.URL, err error) { - return nil, nil + if !util.IsInboxPath(inboxIRI) { + return nil, fmt.Errorf("%s is not an inbox URI", inboxIRI.String()) + } + acct := >smodel.Account{} + if err := f.db.GetWhere("inbox_uri", inboxIRI.String(), acct); err != nil { + if _, ok := err.(ErrNoEntries); ok { + return nil, fmt.Errorf("no actor found that corresponds to inbox %s", inboxIRI.String()) + } + return nil, fmt.Errorf("db error searching for actor with inbox %s", inboxIRI.String()) + } + return url.Parse(acct.URI) } // OutboxForInbox fetches the corresponding actor's outbox IRI for the @@ -207,7 +234,17 @@ func (f *federatingDB) ActorForInbox(c context.Context, inboxIRI *url.URL) (acto // // The library makes this call only after acquiring a lock first. func (f *federatingDB) OutboxForInbox(c context.Context, inboxIRI *url.URL) (outboxIRI *url.URL, err error) { - return nil, nil + if !util.IsInboxPath(inboxIRI) { + return nil, fmt.Errorf("%s is not an inbox URI", inboxIRI.String()) + } + acct := >smodel.Account{} + if err := f.db.GetWhere("inbox_uri", inboxIRI.String(), acct); err != nil { + if _, ok := err.(ErrNoEntries); ok { + return nil, fmt.Errorf("no actor found that corresponds to inbox %s", inboxIRI.String()) + } + return nil, fmt.Errorf("db error searching for actor with inbox %s", inboxIRI.String()) + } + return url.Parse(acct.OutboxURI) } // Exists returns true if the database has an entry for the specified diff --git a/internal/db/pg.go b/internal/db/pg.go index c4f1280ea..647285032 100644 --- a/internal/db/pg.go +++ b/internal/db/pg.go @@ -578,6 +578,7 @@ func (ps *postgresService) GetAvatarForAccountID(avatar *gtsmodel.MediaAttachmen } func (ps *postgresService) Blocked(account1 string, account2 string) (bool, error) { + // TODO: check domain blocks as well var blocked bool if err := ps.conn.Model(>smodel.Block{}). Where("account_id = ?", account1).Where("target_account_id = ?", account2). diff --git a/internal/federation/federatingprotocol.go b/internal/federation/federatingprotocol.go index 299b82586..b30d60350 100644 --- a/internal/federation/federatingprotocol.go +++ b/internal/federation/federatingprotocol.go @@ -137,7 +137,9 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr requestingAccount = a } - return nil, true, nil + contextWithRequestingAccount := context.WithValue(ctx, util.APRequestingAccount, requestingAccount) + + return contextWithRequestingAccount, true, nil } // Blocked should determine whether to permit a set of actors given by diff --git a/internal/federation/federator.go b/internal/federation/federator.go index f280de828..7d25b1ea6 100644 --- a/internal/federation/federator.go +++ b/internal/federation/federator.go @@ -23,6 +23,7 @@ import ( "net/url" "github.com/go-fed/activity/pub" + "github.com/go-fed/activity/streams/vocab" "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -32,9 +33,17 @@ import ( // Federator wraps various interfaces and functions to manage activitypub federation from gotosocial type Federator interface { + // FederatingActor returns the underlying pub.FederatingActor, which can be used to send activities, and serve actors at inboxes/outboxes. FederatingActor() pub.FederatingActor - TransportController() transport.Controller + // AuthenticateFederatedRequest can be used to check the authenticity of incoming http-signed requests for federating resources. + // The given username will be used to create a transport for making outgoing requests. See the implementation for more detailed comments. AuthenticateFederatedRequest(username string, r *http.Request) (*url.URL, error) + // DereferenceRemoteAccount can be used to get the ActivityStreamsPerson representation of a remote account, based on the account ID (which is a URI). + // The given username will be used to create a transport for making outgoing requests. See the implementation for more detailed comments. + DereferenceRemoteAccount(username string, remoteAccountID *url.URL) (vocab.ActivityStreamsPerson, error) + // GetTransportForUser returns a new transport initialized with the key credentials belonging to the given username. + // This can be used for making signed http requests. + GetTransportForUser(username string) (pub.Transport, error) pub.CommonBehavior pub.FederatingProtocol } @@ -69,7 +78,3 @@ func NewFederator(db db.DB, transportController transport.Controller, config *co func (f *federator) FederatingActor() pub.FederatingActor { return f.actor } - -func (f *federator) TransportController() transport.Controller { - return f.transportController -} diff --git a/internal/federation/federator_test.go b/internal/federation/federator_test.go index 93989bfd8..2eab09507 100644 --- a/internal/federation/federator_test.go +++ b/internal/federation/federator_test.go @@ -97,7 +97,7 @@ func (suite *ProtocolTestSuite) TestPostInboxRequestBodyHook() { request.Header.Set("Signature", activity.SignatureHeader) // trigger the function being tested, and return the new context it creates - newContext, err := federator.FederatingProtocol().PostInboxRequestBodyHook(ctx, request, activity.Activity) + newContext, err := federator.PostInboxRequestBodyHook(ctx, request, activity.Activity) assert.NoError(suite.T(), err) assert.NotNil(suite.T(), newContext) @@ -173,7 +173,7 @@ func (suite *ProtocolTestSuite) TestAuthenticatePostInbox() { recorder := httptest.NewRecorder() // trigger the function being tested, and return the new context it creates - newContext, authed, err := federator.FederatingProtocol().AuthenticatePostInbox(ctxWithActivity, recorder, request) + newContext, authed, err := federator.AuthenticatePostInbox(ctxWithActivity, recorder, request) assert.NoError(suite.T(), err) assert.True(suite.T(), authed) diff --git a/internal/federation/util.go b/internal/federation/util.go index 1efaa54b5..493335df4 100644 --- a/internal/federation/util.go +++ b/internal/federation/util.go @@ -93,13 +93,13 @@ func getPublicKeyFromResponse(c context.Context, b []byte, keyID *url.URL) (voca return pkpFound, nil } -// AuthenticateFederatedRequest authenticates any kind of federated request from a remote server. This includes things like -// GET requests for dereferencing users or statuses etc and POST requests for delivering new Activities. -// -// Error means the request did not pass authentication. No error means it's authentic. +// AuthenticateFederatedRequest authenticates any kind of incoming federated request from a remote server. This includes things like +// GET requests for dereferencing our users or statuses etc, and POST requests for delivering new Activities. The function returns +// the URL of the owner of the public key used in the http signature. // // Authenticate in this case is defined as just making sure that the http request is actually signed by whoever claims -// to have signed it, by fetching the public key from the signature and checking it against the remote public key. +// to have signed it, by fetching the public key from the signature and checking it against the remote public key. This function +// *does not* check whether the request is authorized, only whether it's authentic. // // The provided username will be used to generate a transport for making remote requests/derefencing the public key ID of the request signature. // Ideally you should pass in the username of the user *being requested*, so that the remote server can decide how to handle the request based on who's making it. @@ -108,8 +108,8 @@ func getPublicKeyFromResponse(c context.Context, b []byte, keyID *url.URL) (voca // // Note that it is also valid to pass in an empty string here, in which case the keys of the instance account will be used. // -// Note that this function *does not* dereference the remote account that the signature key is associated with, but it will -// return the owner of the public key, so that other functions can dereference it with that, as required. +// Also note that this function *does not* dereference the remote account that the signature key is associated with. +// Other functions should use the returned URL to dereference the remote account, if required. func (f *federator) AuthenticateFederatedRequest(username string, r *http.Request) (*url.URL, error) { verifier, err := httpsig.NewVerifier(r) if err != nil { @@ -225,9 +225,56 @@ func (f *federator) GetTransportForUser(username string) (pub.Transport, error) return nil, fmt.Errorf("error getting account %s from db: %s", username, err) } - transport, err := f.TransportController().NewTransport(ourAccount.PublicKeyURI, ourAccount.PrivateKey) + transport, err := f.transportController.NewTransport(ourAccount.PublicKeyURI, ourAccount.PrivateKey) if err != nil { return nil, fmt.Errorf("error creating transport for user %s: %s", username, err) } return transport, nil } + +const ( + activityStreamsContext = "https://www.w3.org/ns/activitystreams" + w3idContext = "https://w3id.org/security/v1" + tootContext = "http://joinmastodon.org/ns#" + schemaContext = "http://schema.org#" +) + +// ActivityStreamsContext returns the url representation of https://www.w3.org/ns/activitystreams +func ActivityStreamsContext() *url.URL { + u, err := url.Parse(activityStreamsContext) + if err != nil { + panic(err) + } + return u +} + +// W3IDContext returns the url representation of https://w3id.org/security/v1 +func W3IDContext() *url.URL { + u, err := url.Parse(w3idContext) + if err != nil { + panic(err) + } + return u +} + +// TootContext returns the url representation of http://joinmastodon.org/ns# +func TootContext() *url.URL { + u, err := url.Parse(tootContext) + if err != nil { + panic(err) + } + return u +} + +// SchemaContext returns the url representation of http://schema.org# +func SchemaContext() *url.URL { + u, err := url.Parse(schemaContext) + if err != nil { + panic(err) + } + return u +} + +func StandardContexts() vocab.ActivityStreamsContextProperty { + return nil +} diff --git a/internal/message/apuserprocess.go b/internal/message/apuserprocess.go index ac85b22e4..dc4b861e6 100644 --- a/internal/message/apuserprocess.go +++ b/internal/message/apuserprocess.go @@ -1,63 +1,70 @@ package message import ( + "fmt" "net/http" + + "github.com/go-fed/activity/streams" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) -func (p *processor) GetAPUser(requestHeaders http.Header, username string) (interface{}, error) { +func (p *processor) GetAPUser(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode) { + // get the account the request is referring to + requestedAccount := >smodel.Account{} + if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil { + return nil, NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err)) + } - // // get the account the request is referring to - // requestedAccount := >smodel.Account{} - // if err := m.db.GetLocalAccountByUsername(username, requestedAccount); err != nil { - // return nil, NewErrorNotAuthorized(fmt.Errorf("database error getting account with username %s: %s", username, err)) - // } + // authenticate the request + requestingAccountURI, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request) + if err != nil { + return nil, NewErrorNotAuthorized(err) + } - // // and create a transport for it - // transport, err := p.federator.TransportController().NewTransport(requestedAccount.PublicKeyURI, requestedAccount.PrivateKey) - // if err != nil { - // l.Errorf("error creating transport for username %s: %s", requestedUsername, err) - // // we'll just return not authorized here to avoid giving anything away - // c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"}) - // return - // } + requestingAccount := >smodel.Account{} + err = p.db.GetWhere("uri", requestingAccountURI.String(), requestingAccount) + if err != nil { + if _, ok := err.(db.ErrNoEntries); !ok { + // we don't have an entry for this account yet + // what we do now should depend on our chosen federation method + // for now though, we'll just dereference it + // TODO: slow-fed + requestingPerson, err := p.federator.DereferenceRemoteAccount(requestedUsername, requestingAccountURI) + if err != nil { + return nil, NewErrorInternalError(err) + } + requestedAccount, err = p.tc.ASPersonToAccount(requestingPerson) + if err != nil { + return nil, NewErrorInternalError(err) + } + if err := p.db.Put(requestingAccount); err != nil { + return nil, NewErrorInternalError(err) + } + } else { + // something has actually gone wrong + return nil, NewErrorInternalError(err) + } + } - // // authenticate the request - // authentication, err := federation.AuthenticateFederatedRequest(transport, c.Request) - // if err != nil { - // l.Errorf("error authenticating GET user request: %s", err) - // c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"}) - // return - // } + blocked, err := p.db.Blocked(requestedAccount.ID, requestingAccount.ID) + if err != nil { + return nil, NewErrorInternalError(err) + } - // if !authentication.Authenticated { - // l.Debug("request not authorized") - // c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"}) - // return - // } + if blocked { + return nil, NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID)) + } - // requestingAccount := >smodel.Account{} - // if authentication.RequestingPublicKeyID != nil { - // if err := m.db.GetWhere("public_key_uri", authentication.RequestingPublicKeyID.String(), requestingAccount); err != nil { + requestedPerson, err := p.tc.AccountToAS(requestedAccount) + if err != nil { + return nil, NewErrorInternalError(err) + } - // } - // } + data, err := streams.Serialize(requestedPerson) + if err != nil { + return nil, NewErrorInternalError(err) + } - // authorization, err := federation.AuthorizeFederatedRequest - - // person, err := m.tc.AccountToAS(requestedAccount) - // if err != nil { - // l.Errorf("error converting account to ap person: %s", err) - // c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"}) - // return - // } - - // data, err := person.Serialize() - // if err != nil { - // l.Errorf("error serializing user: %s", err) - // c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"}) - // return - // } - - // c.JSON(http.StatusOK, data) - return nil, nil + return data, nil } diff --git a/internal/message/processor.go b/internal/message/processor.go index 2e59a0231..d8620f662 100644 --- a/internal/message/processor.go +++ b/internal/message/processor.go @@ -19,6 +19,8 @@ package message import ( + "net/http" + "github.com/sirupsen/logrus" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" @@ -48,7 +50,7 @@ type Processor interface { FromFederator() chan FromFederator /* - API-FACING PROCESSING FUNCTIONS + CLIENT API-FACING PROCESSING FUNCTIONS These functions are intended to be called when the API client needs an immediate (ie., synchronous) reply to an HTTP request. As such, they will only do the bare-minimum of work necessary to give a properly formed reply. For more intensive (and time-consuming) calls, where you don't require an immediate @@ -82,6 +84,16 @@ type Processor interface { // AdminEmojiCreate handles the creation of a new instance emoji by an admin, using the given form. AdminEmojiCreate(authed *oauth.Auth, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, error) + /* + FEDERATION API-FACING PROCESSING FUNCTIONS + These functions are intended to be called when the federating client needs an immediate (ie., synchronous) reply + to an HTTP request. As such, they will only do the bare-minimum of work necessary to give a properly + formed reply. For more intensive (and time-consuming) calls, where you don't require an immediate + response, pass work to the processor using a channel instead. + */ + + GetAPUser(requestedUsername string, request *http.Request) (interface{}, ErrorWithCode) + // Start starts the Processor, reading from its channels and passing messages back and forth. Start() error // Stop stops the processor cleanly, finishing handling any remaining messages before closing down. diff --git a/internal/message/statusprocess.go b/internal/message/statusprocess.go index 8c8e5f7c0..b7237fecf 100644 --- a/internal/message/statusprocess.go +++ b/internal/message/statusprocess.go @@ -82,11 +82,7 @@ func (p *processor) StatusCreate(auth *oauth.Auth, form *apimodel.AdvancedStatus } // return the frontend representation of the new status to the submitter - mastoStatus, err := p.tc.StatusToMasto(newStatus, auth.Account, auth.Account, nil, newStatus.GTSReplyToAccount, nil) - if err != nil { - return nil, err - } - return mastoStatus, nil + return p.tc.StatusToMasto(newStatus, auth.Account, auth.Account, nil, newStatus.GTSReplyToAccount, nil) } func (p *processor) StatusDelete(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) { diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go index 33697917c..1d1242a6e 100644 --- a/internal/typeutils/internaltoas.go +++ b/internal/typeutils/internaltoas.go @@ -28,6 +28,8 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) + + // Converts a gts model account into an Activity Streams person type, following // the spec laid out for mastodon here: https://docs.joinmastodon.org/spec/activitypub/ func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) { diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go index b6a7717d2..4e9236e88 100644 --- a/internal/typeutils/internaltoas_test.go +++ b/internal/typeutils/internaltoas_test.go @@ -23,6 +23,7 @@ import ( "fmt" "testing" + "github.com/go-fed/activity/streams" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -69,7 +70,7 @@ func (suite *InternalToASTestSuite) TestPostAccountToAS() { asPerson, err := suite.typeconverter.AccountToAS(testAccount) assert.NoError(suite.T(), err) - ser, err := asPerson.Serialize() + ser, err := streams.Serialize(asPerson) assert.NoError(suite.T(), err) bytes, err := json.Marshal(ser)