wahhh getting there

This commit is contained in:
tsmethurst 2021-05-06 14:01:43 +02:00
commit d4c919d273
12 changed files with 214 additions and 151 deletions

View file

@ -23,7 +23,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
) )
// UsersGETHandler should be served at https://example.org/users/:username. // 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) l.Tracef("negotiated format: %s", format)
// get the account the request is referring to // make a copy of the context to pass along so we don't break anything
requestedAccount := &gtsmodel.Account{} cp := c.Copy()
if err := m.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil { user, err := m.processor.GetAPUser(requestedUsername, cp.Request) // GetAPUser handles auth as well
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)
if err != nil { if err != nil {
l.Errorf("error creating transport for username %s: %s", requestedUsername, err) l.Info(err.Error())
// we'll just return not authorized here to avoid giving anything away c.JSON(err.Code(), gin.H{"error": err.Safe()})
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
return return
} }
// authenticate the request c.JSON(http.StatusOK, user)
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 := &gtsmodel.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)
} }

View file

@ -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. // 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) { 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, &gtsmodel.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 // 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 database has an entry for the IRI.
// The library makes this call only after acquiring a lock first. // The library makes this call only after acquiring a lock first.
func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) { 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 the id host isn't this instance host, we don't own this IRI
if id.Host != f.config.Host { if id.Host != f.config.Host {
return false, nil 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 // check if it's a status, eg /users/example_username/statuses/SOME_UUID_OF_A_STATUS
if util.IsStatusesPath(id) { if util.IsStatusesPath(id) {
username, uid, err := util.ParseStatusesPath(id) _, uid, err := util.ParseStatusesPath(id)
if err != nil { if err != nil {
return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err) return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err)
} }
acct := &gtsmodel.Account{} if err := f.db.GetWhere("uri", uid, &gtsmodel.Status{}); err != nil {
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 := &gtsmodel.Status{}
if err := f.db.GetByID(uid, status); err != nil {
if _, ok := err.(ErrNoEntries); ok { if _, ok := err.(ErrNoEntries); ok {
// there are no entries for this status // there are no entries for this status
return false, nil return false, nil
} }
// an actual error happened
return false, fmt.Errorf("database error fetching status with id %s: %s", uid, err) 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 return true, nil
} }
@ -172,34 +181,52 @@ func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) {
if err != nil { if err != nil {
return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err) return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err)
} }
acct := &gtsmodel.Account{} if err := f.db.GetLocalAccountByUsername(username, &gtsmodel.Account{}); err != nil {
if err := f.db.GetLocalAccountByUsername(username, acct); err != nil {
if _, ok := err.(ErrNoEntries); ok { if _, ok := err.(ErrNoEntries); ok {
// there are no entries for this username // there are no entries for this username
return false, nil return false, nil
} }
// an actual error happened
return false, fmt.Errorf("database error fetching account with username %s: %s", username, err) 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 return true, nil
} }
l.Info("could not match activityID") return false, fmt.Errorf("could not match activityID: %s", id.String())
return false, nil
} }
// ActorForOutbox fetches the actor's IRI for the given outbox IRI. // ActorForOutbox fetches the actor's IRI for the given outbox IRI.
// //
// The library makes this call only after acquiring a lock first. // 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) { 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 := &gtsmodel.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. // ActorForInbox fetches the actor's IRI for the given outbox IRI.
// //
// The library makes this call only after acquiring a lock first. // 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) { 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 := &gtsmodel.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 // 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. // 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) { 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 := &gtsmodel.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 // Exists returns true if the database has an entry for the specified

View file

@ -578,6 +578,7 @@ func (ps *postgresService) GetAvatarForAccountID(avatar *gtsmodel.MediaAttachmen
} }
func (ps *postgresService) Blocked(account1 string, account2 string) (bool, error) { func (ps *postgresService) Blocked(account1 string, account2 string) (bool, error) {
// TODO: check domain blocks as well
var blocked bool var blocked bool
if err := ps.conn.Model(&gtsmodel.Block{}). if err := ps.conn.Model(&gtsmodel.Block{}).
Where("account_id = ?", account1).Where("target_account_id = ?", account2). Where("account_id = ?", account1).Where("target_account_id = ?", account2).

View file

@ -137,7 +137,9 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr
requestingAccount = a 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 // Blocked should determine whether to permit a set of actors given by

View file

@ -23,6 +23,7 @@ import (
"net/url" "net/url"
"github.com/go-fed/activity/pub" "github.com/go-fed/activity/pub"
"github.com/go-fed/activity/streams/vocab"
"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"
@ -32,9 +33,17 @@ import (
// Federator wraps various interfaces and functions to manage activitypub federation from gotosocial // Federator wraps various interfaces and functions to manage activitypub federation from gotosocial
type Federator interface { 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 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) 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.CommonBehavior
pub.FederatingProtocol pub.FederatingProtocol
} }
@ -69,7 +78,3 @@ 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) TransportController() transport.Controller {
return f.transportController
}

View file

@ -97,7 +97,7 @@ func (suite *ProtocolTestSuite) TestPostInboxRequestBodyHook() {
request.Header.Set("Signature", activity.SignatureHeader) request.Header.Set("Signature", activity.SignatureHeader)
// trigger the function being tested, and return the new context it creates // 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.NoError(suite.T(), err)
assert.NotNil(suite.T(), newContext) assert.NotNil(suite.T(), newContext)
@ -173,7 +173,7 @@ func (suite *ProtocolTestSuite) TestAuthenticatePostInbox() {
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
// trigger the function being tested, and return the new context it creates // 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.NoError(suite.T(), err)
assert.True(suite.T(), authed) assert.True(suite.T(), authed)

View file

@ -93,13 +93,13 @@ func getPublicKeyFromResponse(c context.Context, b []byte, keyID *url.URL) (voca
return pkpFound, nil return pkpFound, nil
} }
// AuthenticateFederatedRequest authenticates any kind of federated request from a remote server. This includes things like // AuthenticateFederatedRequest authenticates any kind of incoming 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. // 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.
// Error means the request did not pass authentication. No error means it's authentic.
// //
// Authenticate in this case is defined as just making sure that the http request is actually signed by whoever claims // 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. // 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. // 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 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 // Also note that this function *does not* dereference the remote account that the signature key is associated with.
// return the owner of the public key, so that other functions can dereference it with that, as required. // 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) { func (f *federator) AuthenticateFederatedRequest(username string, r *http.Request) (*url.URL, error) {
verifier, err := httpsig.NewVerifier(r) verifier, err := httpsig.NewVerifier(r)
if err != nil { 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) 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 { if err != nil {
return nil, fmt.Errorf("error creating transport for user %s: %s", username, err) return nil, fmt.Errorf("error creating transport for user %s: %s", username, err)
} }
return transport, nil 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
}

View file

@ -1,63 +1,70 @@
package message package message
import ( import (
"fmt"
"net/http" "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 := &gtsmodel.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 // authenticate the request
// requestedAccount := &gtsmodel.Account{} requestingAccountURI, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request)
// if err := m.db.GetLocalAccountByUsername(username, requestedAccount); err != nil { if err != nil {
// return nil, NewErrorNotAuthorized(fmt.Errorf("database error getting account with username %s: %s", username, err)) return nil, NewErrorNotAuthorized(err)
// } }
// // and create a transport for it requestingAccount := &gtsmodel.Account{}
// transport, err := p.federator.TransportController().NewTransport(requestedAccount.PublicKeyURI, requestedAccount.PrivateKey) err = p.db.GetWhere("uri", requestingAccountURI.String(), requestingAccount)
// if err != nil { if err != nil {
// l.Errorf("error creating transport for username %s: %s", requestedUsername, err) if _, ok := err.(db.ErrNoEntries); !ok {
// // we'll just return not authorized here to avoid giving anything away // we don't have an entry for this account yet
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"}) // what we do now should depend on our chosen federation method
// return // 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 blocked, err := p.db.Blocked(requestedAccount.ID, requestingAccount.ID)
// authentication, err := federation.AuthenticateFederatedRequest(transport, c.Request) if err != nil {
// if err != nil { return nil, NewErrorInternalError(err)
// l.Errorf("error authenticating GET user request: %s", err) }
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
// return
// }
// if !authentication.Authenticated { if blocked {
// l.Debug("request not authorized") return nil, NewErrorNotAuthorized(fmt.Errorf("block exists between accounts %s and %s", requestedAccount.ID, requestingAccount.ID))
// c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"}) }
// return
// }
// requestingAccount := &gtsmodel.Account{} requestedPerson, err := p.tc.AccountToAS(requestedAccount)
// if authentication.RequestingPublicKeyID != nil { if err != nil {
// if err := m.db.GetWhere("public_key_uri", authentication.RequestingPublicKeyID.String(), requestingAccount); err != nil { return nil, NewErrorInternalError(err)
}
// } data, err := streams.Serialize(requestedPerson)
// } if err != nil {
return nil, NewErrorInternalError(err)
}
// authorization, err := federation.AuthorizeFederatedRequest return data, nil
// 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
} }

View file

@ -19,6 +19,8 @@
package message package message
import ( import (
"net/http"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
@ -48,7 +50,7 @@ type Processor interface {
FromFederator() chan FromFederator 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 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 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 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 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) 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 starts the Processor, reading from its channels and passing messages back and forth.
Start() error Start() error
// Stop stops the processor cleanly, finishing handling any remaining messages before closing down. // Stop stops the processor cleanly, finishing handling any remaining messages before closing down.

View file

@ -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 // 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) return p.tc.StatusToMasto(newStatus, auth.Account, auth.Account, nil, newStatus.GTSReplyToAccount, nil)
if err != nil {
return nil, err
}
return mastoStatus, nil
} }
func (p *processor) StatusDelete(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) { func (p *processor) StatusDelete(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) {

View file

@ -28,6 +28,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
) )
// Converts a gts model account into an Activity Streams person type, following // 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/ // the spec laid out for mastodon here: https://docs.joinmastodon.org/spec/activitypub/
func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) { func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) {

View file

@ -23,6 +23,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/go-fed/activity/streams"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
@ -69,7 +70,7 @@ func (suite *InternalToASTestSuite) TestPostAccountToAS() {
asPerson, err := suite.typeconverter.AccountToAS(testAccount) asPerson, err := suite.typeconverter.AccountToAS(testAccount)
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
ser, err := asPerson.Serialize() ser, err := streams.Serialize(asPerson)
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
bytes, err := json.Marshal(ser) bytes, err := json.Marshal(ser)