wowee some serious moving stuff around

This commit is contained in:
tsmethurst 2021-05-05 19:10:13 +02:00
commit 41e6e8ed10
35 changed files with 611 additions and 459 deletions

View file

@ -38,5 +38,5 @@ func (c *Clock) Now() time.Time {
}
func NewClock() pub.Clock {
return &Clock{}
return &Clock{}
}

View file

@ -26,33 +26,10 @@ import (
"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"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/transport"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
// commonBehavior implements the GTSCommonBehavior interface
type commonBehavior struct {
db db.DB
log *logrus.Logger
config *config.Config
transportController transport.Controller
}
// newCommonBehavior returns an implementation of the GTSCommonBehavior interface that uses the given db, log, config, and transportController.
// This interface is a superset of the pub.CommonBehavior interface, so it can be used anywhere that interface would be used.
func newCommonBehavior(db db.DB, log *logrus.Logger, config *config.Config, transportController transport.Controller) pub.CommonBehavior {
return &commonBehavior{
db: db,
log: log,
config: config,
transportController: transportController,
}
}
/*
GOFED COMMON BEHAVIOR INTERFACE
Contains functions required for both the Social API and Federating Protocol.
@ -79,7 +56,7 @@ func newCommonBehavior(db db.DB, log *logrus.Logger, config *config.Config, tran
// Finally, if the authentication and authorization succeeds, then
// authenticated must be true and error nil. The request will continue
// to be processed.
func (c *commonBehavior) AuthenticateGetInbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
func (f *federator) AuthenticateGetInbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
// IMPLEMENTATION NOTE: For GoToSocial, we serve outboxes and inboxes through
// the CLIENT API, not through the federation API, so we just do nothing here.
return nil, false, nil
@ -104,7 +81,7 @@ func (c *commonBehavior) AuthenticateGetInbox(ctx context.Context, w http.Respon
// Finally, if the authentication and authorization succeeds, then
// authenticated must be true and error nil. The request will continue
// to be processed.
func (c *commonBehavior) AuthenticateGetOutbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
func (f *federator) AuthenticateGetOutbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
// IMPLEMENTATION NOTE: For GoToSocial, we serve outboxes and inboxes through
// the CLIENT API, not through the federation API, so we just do nothing here.
return nil, false, nil
@ -118,7 +95,7 @@ func (c *commonBehavior) AuthenticateGetOutbox(ctx context.Context, w http.Respo
//
// Always called, regardless whether the Federated Protocol or Social
// API is enabled.
func (c *commonBehavior) GetOutbox(ctx context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) {
func (f *federator) GetOutbox(ctx context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) {
// IMPLEMENTATION NOTE: For GoToSocial, we serve outboxes and inboxes through
// the CLIENT API, not through the federation API, so we just do nothing here.
return nil, nil
@ -147,7 +124,7 @@ func (c *commonBehavior) GetOutbox(ctx context.Context, r *http.Request) (vocab.
// Note that the library will not maintain a long-lived pointer to the
// returned Transport so that any private credentials are able to be
// garbage collected.
func (c *commonBehavior) NewTransport(ctx context.Context, actorBoxIRI *url.URL, gofedAgent string) (pub.Transport, error) {
func (f *federator) NewTransport(ctx context.Context, actorBoxIRI *url.URL, gofedAgent string) (pub.Transport, error) {
var username string
var err error
@ -167,16 +144,9 @@ func (c *commonBehavior) NewTransport(ctx context.Context, actorBoxIRI *url.URL,
}
account := &gtsmodel.Account{}
if err := c.db.GetLocalAccountByUsername(username, account); err != nil {
if err := f.db.GetLocalAccountByUsername(username, account); err != nil {
return nil, fmt.Errorf("error getting account with username %s from the db: %s", username, err)
}
return c.transportController.NewTransport(account.PublicKeyURI, account.PrivateKey)
}
// GetUser returns the activitypub representation of the user specified in the path of r, eg https://example.org/users/example_user.
// AuthenticateGetUser should be called first, to make sure the requester has permission to view the requested user.
// The returned user should be a translation from a *gtsmodel.Account to a serializable ActivityStreamsPerson.
func (c *commonBehavior) GetUser(ctx context.Context, r *http.Request) (vocab.ActivityStreamsPerson, error) {
return nil, nil
return f.transportController.NewTransport(account.PublicKeyURI, account.PrivateKey)
}

View file

@ -28,34 +28,11 @@ import (
"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"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/transport"
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
// federatingProtocol implements the go-fed federating protocol interface
type federatingProtocol struct {
db db.DB
log *logrus.Logger
config *config.Config
transportController transport.Controller
typeConverter typeutils.TypeConverter
}
// newFederatingProtocol returns the gotosocial implementation of the GTSFederatingProtocol interface
func newFederatingProtocol(db db.DB, log *logrus.Logger, config *config.Config, transportController transport.Controller, typeConverter typeutils.TypeConverter) pub.FederatingProtocol {
return &federatingProtocol{
db: db,
log: log,
config: config,
transportController: transportController,
typeConverter: typeConverter,
}
}
/*
GO FED FEDERATING PROTOCOL INTERFACE
FederatingProtocol contains behaviors an application needs to satisfy for the
@ -82,7 +59,7 @@ func newFederatingProtocol(db db.DB, log *logrus.Logger, config *config.Config,
// PostInbox. In this case, the DelegateActor implementation must not
// write a response to the ResponseWriter as is expected that the caller
// to PostInbox will do so when handling the error.
func (f *federatingProtocol) PostInboxRequestBodyHook(ctx context.Context, r *http.Request, activity pub.Activity) (context.Context, error) {
func (f *federator) PostInboxRequestBodyHook(ctx context.Context, r *http.Request, activity pub.Activity) (context.Context, error) {
l := f.log.WithFields(logrus.Fields{
"func": "PostInboxRequestBodyHook",
"useragent": r.UserAgent(),
@ -115,7 +92,7 @@ func (f *federatingProtocol) PostInboxRequestBodyHook(ctx context.Context, r *ht
// Finally, if the authentication and authorization succeeds, then
// authenticated must be true and error nil. The request will continue
// to be processed.
func (f *federatingProtocol) AuthenticatePostInbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
l := f.log.WithFields(logrus.Fields{
"func": "AuthenticatePostInbox",
"useragent": r.UserAgent(),
@ -133,12 +110,7 @@ func (f *federatingProtocol) AuthenticatePostInbox(ctx context.Context, w http.R
return ctx, false, errors.New("requested account not parsebale from context")
}
transport, err := f.transportController.NewTransport(requestedAccount.PublicKeyURI, requestedAccount.PrivateKey)
if err != nil {
return ctx, false, fmt.Errorf("error creating transport: %s", err)
}
publicKeyOwnerURI, err := AuthenticateFederatedRequest(transport, r)
publicKeyOwnerURI, err := f.AuthenticateFederatedRequest(requestedAccount.Username, r)
if err != nil {
l.Debugf("request not authenticated: %s", err)
return ctx, false, fmt.Errorf("not authenticated: %s", err)
@ -151,9 +123,9 @@ func (f *federatingProtocol) AuthenticatePostInbox(ctx context.Context, w http.R
return ctx, false, fmt.Errorf("error getting requesting account with public key id %s: %s", publicKeyOwnerURI.String(), err)
}
// we just don't know this account (yet) so try to dereference it
// we don't know this account (yet) so let's dereference it right now
// TODO: slow-fed
person, err := DereferenceAccount(transport, publicKeyOwnerURI)
person, err := f.DereferenceRemoteAccount(requestedAccount.Username, publicKeyOwnerURI)
if err != nil {
return ctx, false, fmt.Errorf("error dereferencing account with public key id %s: %s", publicKeyOwnerURI.String(), err)
}
@ -182,7 +154,7 @@ func (f *federatingProtocol) AuthenticatePostInbox(ctx context.Context, w http.R
// Finally, if the authentication and authorization succeeds, then
// blocked must be false and error nil. The request will continue
// to be processed.
func (f *federatingProtocol) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, error) {
func (f *federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, error) {
// TODO
return false, nil
}
@ -206,7 +178,7 @@ func (f *federatingProtocol) Blocked(ctx context.Context, actorIRIs []*url.URL)
//
// Applications are not expected to handle every single ActivityStreams
// type and extension. The unhandled ones are passed to DefaultCallback.
func (f *federatingProtocol) FederatingCallbacks(ctx context.Context) (pub.FederatingWrappedCallbacks, []interface{}, error) {
func (f *federator) FederatingCallbacks(ctx context.Context) (pub.FederatingWrappedCallbacks, []interface{}, error) {
// TODO
return pub.FederatingWrappedCallbacks{}, nil, nil
}
@ -218,7 +190,7 @@ func (f *federatingProtocol) FederatingCallbacks(ctx context.Context) (pub.Feder
// Applications are not expected to handle every single ActivityStreams
// type and extension, so the unhandled ones are passed to
// DefaultCallback.
func (f *federatingProtocol) DefaultCallback(ctx context.Context, activity pub.Activity) error {
func (f *federator) DefaultCallback(ctx context.Context, activity pub.Activity) error {
l := f.log.WithFields(logrus.Fields{
"func": "DefaultCallback",
"aptype": activity.GetTypeName(),
@ -231,7 +203,7 @@ func (f *federatingProtocol) DefaultCallback(ctx context.Context, activity pub.A
// an activity to determine if inbox forwarding needs to occur.
//
// Zero or negative numbers indicate infinite recursion.
func (f *federatingProtocol) MaxInboxForwardingRecursionDepth(ctx context.Context) int {
func (f *federator) MaxInboxForwardingRecursionDepth(ctx context.Context) int {
// TODO
return 0
}
@ -241,7 +213,7 @@ func (f *federatingProtocol) MaxInboxForwardingRecursionDepth(ctx context.Contex
// delivery.
//
// Zero or negative numbers indicate infinite recursion.
func (f *federatingProtocol) MaxDeliveryRecursionDepth(ctx context.Context) int {
func (f *federator) MaxDeliveryRecursionDepth(ctx context.Context) int {
// TODO
return 0
}
@ -253,7 +225,7 @@ func (f *federatingProtocol) MaxDeliveryRecursionDepth(ctx context.Context) int
//
// The activity is provided as a reference for more intelligent
// logic to be used, but the implementation must not modify it.
func (f *federatingProtocol) FilterForwarding(ctx context.Context, potentialRecipients []*url.URL, a pub.Activity) ([]*url.URL, error) {
func (f *federator) FilterForwarding(ctx context.Context, potentialRecipients []*url.URL, a pub.Activity) ([]*url.URL, error) {
// TODO
return nil, nil
}
@ -266,7 +238,7 @@ func (f *federatingProtocol) FilterForwarding(ctx context.Context, potentialReci
//
// Always called, regardless whether the Federated Protocol or Social
// API is enabled.
func (f *federatingProtocol) GetInbox(ctx context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) {
func (f *federator) GetInbox(ctx context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) {
// IMPLEMENTATION NOTE: For GoToSocial, we serve outboxes and inboxes through
// the CLIENT API, not through the federation API, so we just do nothing here.
return nil, nil

View file

@ -19,11 +19,13 @@
package federation
import (
"net/http"
"net/url"
"github.com/go-fed/activity/pub"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/message"
"github.com/superseriousbusiness/gotosocial/internal/transport"
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
)
@ -32,35 +34,36 @@ import (
type Federator interface {
FederatingActor() pub.FederatingActor
TransportController() transport.Controller
FederatingProtocol() pub.FederatingProtocol
CommonBehavior() pub.CommonBehavior
AuthenticateFederatedRequest(username string, r *http.Request) (*url.URL, error)
pub.CommonBehavior
pub.FederatingProtocol
}
type federator struct {
actor pub.FederatingActor
processor message.Processor
federatingProtocol pub.FederatingProtocol
commonBehavior pub.CommonBehavior
config *config.Config
db db.DB
clock pub.Clock
typeConverter typeutils.TypeConverter
transportController transport.Controller
actor pub.FederatingActor
log *logrus.Logger
}
// NewFederator returns a new federator
func NewFederator(db db.DB, transportController transport.Controller, config *config.Config, log *logrus.Logger, processor message.Processor, typeConverter typeutils.TypeConverter) Federator {
func NewFederator(db db.DB, transportController transport.Controller, config *config.Config, log *logrus.Logger, typeConverter typeutils.TypeConverter) Federator {
clock := &Clock{}
federatingProtocol := newFederatingProtocol(db, log, config, transportController, typeConverter)
commonBehavior := newCommonBehavior(db, log, config, transportController)
actor := newFederatingActor(commonBehavior, federatingProtocol, db.Federation(), clock)
return &federator{
actor: actor,
processor: processor,
federatingProtocol: federatingProtocol,
commonBehavior: commonBehavior,
clock: clock,
f := &federator{
config: config,
db: db,
clock: &Clock{},
typeConverter: typeConverter,
transportController: transportController,
log: log,
}
actor := newFederatingActor(f, f, db.Federation(), clock)
f.actor = actor
return f
}
func (f *federator) FederatingActor() pub.FederatingActor {
@ -70,11 +73,3 @@ func (f *federator) FederatingActor() pub.FederatingActor {
func (f *federator) TransportController() transport.Controller {
return f.transportController
}
func (f *federator) FederatingProtocol() pub.FederatingProtocol {
return f.federatingProtocol
}
func (f *federator) CommonBehavior() pub.CommonBehavior {
return f.commonBehavior
}

View file

@ -39,7 +39,6 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/federation"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/message"
"github.com/superseriousbusiness/gotosocial/internal/storage"
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
"github.com/superseriousbusiness/gotosocial/internal/util"
@ -51,7 +50,6 @@ type ProtocolTestSuite struct {
config *config.Config
db db.DB
log *logrus.Logger
processor message.Processor
storage storage.Storage
typeConverter typeutils.TypeConverter
accounts map[string]*gtsmodel.Account
@ -65,7 +63,6 @@ func (suite *ProtocolTestSuite) SetupSuite() {
suite.db = testrig.NewTestDB()
suite.log = testrig.NewTestLog()
suite.storage = testrig.NewTestStorage()
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage)
suite.typeConverter = testrig.NewTestTypeConverter(suite.db)
suite.accounts = testrig.NewTestAccounts()
suite.activities = testrig.NewTestActivities(suite.accounts)
@ -92,7 +89,7 @@ func (suite *ProtocolTestSuite) TestPostInboxRequestBodyHook() {
return nil, nil
}))
// setup module being tested
federator := federation.NewFederator(suite.db, tc, suite.config, suite.log, suite.processor, suite.typeConverter)
federator := federation.NewFederator(suite.db, tc, suite.config, suite.log, suite.typeConverter)
// setup request
ctx := context.Background()
@ -158,7 +155,7 @@ func (suite *ProtocolTestSuite) TestAuthenticatePostInbox() {
}))
// now setup module being tested, with the mock transport controller
federator := federation.NewFederator(suite.db, tc, suite.config, suite.log, suite.processor, suite.typeConverter)
federator := federation.NewFederator(suite.db, tc, suite.config, suite.log, suite.typeConverter)
// setup request
ctx := context.Background()

View file

@ -101,15 +101,16 @@ func getPublicKeyFromResponse(c context.Context, b []byte, keyID *url.URL) (voca
// 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.
//
// The provided transport will be used to dereference the public key ID of the request signature. Ideally you should pass in a transport
// with the credentials of the user *being requested*, so that the remote server can decide how to handle the request based on who's making it.
// Ie., if the request on this server is for https://example.org/users/some_username then you should pass in a transport that's been initialized with
// the keys belonging to local user 'some_username'. The remote server will then know that this is the user making the
// dereferencing request, and they can decide to allow or deny the request depending on their settings.
// 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.
// Ie., if the request on this server is for https://example.org/users/some_username then you should pass in the username 'some_username'.
// The remote server will then know that this is the user making the dereferencing request, and they can decide to allow or deny the request depending on their settings.
//
// 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.
func AuthenticateFederatedRequest(transport pub.Transport, r *http.Request) (*url.URL, error) {
func (f *federator) AuthenticateFederatedRequest(username string, r *http.Request) (*url.URL, error) {
verifier, err := httpsig.NewVerifier(r)
if err != nil {
return nil, fmt.Errorf("could not create http sig verifier: %s", err)
@ -122,7 +123,12 @@ func AuthenticateFederatedRequest(transport pub.Transport, r *http.Request) (*ur
return nil, fmt.Errorf("could not parse key id into a url: %s", err)
}
// use the new transport to fetch the requesting public key from the remote server
transport, err := f.GetTransportForUser(username)
if err != nil {
return nil, fmt.Errorf("transport err: %s", err)
}
// The actual http call to the remote server is made right here in the Dereference function.
b, err := transport.Dereference(context.Background(), requestingPublicKeyID)
if err != nil {
return nil, fmt.Errorf("error deferencing key %s: %s", requestingPublicKeyID.String(), err)
@ -134,17 +140,13 @@ func AuthenticateFederatedRequest(transport pub.Transport, r *http.Request) (*ur
return nil, fmt.Errorf("error getting key %s from response %s: %s", requestingPublicKeyID.String(), string(b), err)
}
pkOwnerProp := requestingPublicKey.GetW3IDSecurityV1Owner()
if pkOwnerProp == nil || !pkOwnerProp.IsIRI() {
return nil, errors.New("publicKeyOwner property is not provided or it is not embedded as a value")
}
pkOwnerURI := pkOwnerProp.GetIRI()
// we should be able to get the actual key embedded in the vocab.W3IDSecurityV1PublicKey
pkPemProp := requestingPublicKey.GetW3IDSecurityV1PublicKeyPem()
if pkPemProp == nil || !pkPemProp.IsXMLSchemaString() {
return nil, errors.New("publicKeyPem property is not provided or it is not embedded as a value")
}
// and decode the PEM so that we can parse it as a golang public key
pubKeyPem := pkPemProp.Get()
block, _ := pem.Decode([]byte(pubKeyPem))
if block == nil || block.Type != "PUBLIC KEY" {
@ -162,14 +164,26 @@ func AuthenticateFederatedRequest(transport pub.Transport, r *http.Request) (*ur
return nil, fmt.Errorf("error verifying key %s: %s", requestingPublicKeyID.String(), err)
}
// all good!
// all good! we just need the URI of the key owner to return
pkOwnerProp := requestingPublicKey.GetW3IDSecurityV1Owner()
if pkOwnerProp == nil || !pkOwnerProp.IsIRI() {
return nil, errors.New("publicKeyOwner property is not provided or it is not embedded as a value")
}
pkOwnerURI := pkOwnerProp.GetIRI()
return pkOwnerURI, nil
}
func DereferenceAccount(transport pub.Transport, id *url.URL) (vocab.ActivityStreamsPerson, error) {
b, err := transport.Dereference(context.Background(), id)
func (f *federator) DereferenceRemoteAccount(username string, remoteAccountID *url.URL) (vocab.ActivityStreamsPerson, error) {
transport, err := f.GetTransportForUser(username)
if err != nil {
return nil, fmt.Errorf("error deferencing %s: %s", id.String(), err)
return nil, fmt.Errorf("transport err: %s", err)
}
b, err := transport.Dereference(context.Background(), remoteAccountID)
if err != nil {
return nil, fmt.Errorf("error deferencing %s: %s", remoteAccountID.String(), err)
}
m := make(map[string]interface{})
@ -195,3 +209,25 @@ func DereferenceAccount(transport pub.Transport, id *url.URL) (vocab.ActivityStr
return nil, fmt.Errorf("type name %s not supported", t.GetTypeName())
}
func (f *federator) GetTransportForUser(username string) (pub.Transport, error) {
// We need an account to use to create a transport for dereferecing the signature.
// If a username has been given, we can fetch the account with that username and use it.
// Otherwise, we can take the instance account and use those credentials to make the request.
ourAccount := &gtsmodel.Account{}
var u string
if username == "" {
u = f.config.Host
} else {
u = username
}
if err := f.db.GetLocalAccountByUsername(u, ourAccount); err != nil {
return nil, fmt.Errorf("error getting account %s from db: %s", username, err)
}
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
}