start adding remote instance dereference
This commit is contained in:
parent
c1e107266f
commit
18c39ad23d
@ -125,6 +125,18 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr
|
||||
return ctx, false, fmt.Errorf("not authenticated: %s", err)
|
||||
}
|
||||
|
||||
// authentication has passed, so add an instance entry for this instance if it hasn't been done already
|
||||
i := >smodel.Instance{}
|
||||
if err := f.db.GetWhere([]db.Where{{Key: "domain", Value: publicKeyOwnerURI.Host, CaseInsensitive: true}}, i); err != nil {
|
||||
if _, ok := err.(db.ErrNoEntries); !ok {
|
||||
// there's been an actual error
|
||||
return ctx, false, fmt.Errorf("error getting requesting account with public key id %s: %s", publicKeyOwnerURI.String(), err)
|
||||
}
|
||||
// we don't have an entry for this instance yet so create it
|
||||
var err error
|
||||
i, err := f.DereferenceRemoteInstance()
|
||||
}
|
||||
|
||||
requestingAccount := >smodel.Account{}
|
||||
if err := f.db.GetWhere([]db.Where{{Key: "uri", Value: publicKeyOwnerURI.String()}}, requestingAccount); err != nil {
|
||||
// there's been a proper error so return it
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
|
||||
"github.com/go-fed/activity/pub"
|
||||
"github.com/sirupsen/logrus"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb"
|
||||
@ -49,6 +50,8 @@ type Federator interface {
|
||||
// DereferenceRemoteStatus can be used to get the representation of a remote status, based on its 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.
|
||||
DereferenceRemoteStatus(username string, remoteStatusID *url.URL) (typeutils.Statusable, error)
|
||||
// DereferenceRemoteInstance
|
||||
DereferenceRemoteInstance(username string, remoteInstanceURI *url.URL) (*apimodel.Instance, 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.
|
||||
//
|
||||
|
@ -37,6 +37,7 @@ import (
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/transport"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -134,7 +135,8 @@ func (f *federator) AuthenticateFederatedRequest(username string, r *http.Reques
|
||||
var pkOwnerURI *url.URL
|
||||
requestingRemoteAccount := >smodel.Account{}
|
||||
requestingLocalAccount := >smodel.Account{}
|
||||
if strings.EqualFold(requestingPublicKeyID.Host, f.config.Host) {
|
||||
requestingHost := requestingPublicKeyID.Host
|
||||
if strings.EqualFold(requestingHost, f.config.Host) {
|
||||
// LOCAL ACCOUNT REQUEST
|
||||
// the request is coming from INSIDE THE HOUSE so skip the remote dereferencing
|
||||
if err := f.db.GetWhere([]db.Where{{Key: "public_key_uri", Value: requestingPublicKeyID.String()}}, requestingLocalAccount); err != nil {
|
||||
@ -340,6 +342,15 @@ func (f *federator) DereferenceRemoteStatus(username string, remoteStatusID *url
|
||||
return nil, fmt.Errorf("type name %s not supported", t.GetTypeName())
|
||||
}
|
||||
|
||||
func (f *federator) DereferenceRemoteInstance(username string, remoteInstanceURI *url.URL) (*apimodel.Instance, error) {
|
||||
transport, err := f.GetTransportForUser(username)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("transport err: %s", err)
|
||||
}
|
||||
|
||||
return transport.DereferenceInstance(context.Background(), remoteInstanceURI)
|
||||
}
|
||||
|
||||
func (f *federator) GetTransportForUser(username string) (transport.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.
|
||||
|
@ -3,6 +3,8 @@ package transport
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@ -12,13 +14,17 @@ import (
|
||||
"github.com/go-fed/activity/pub"
|
||||
"github.com/go-fed/httpsig"
|
||||
"github.com/sirupsen/logrus"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
)
|
||||
|
||||
// Transport wraps the pub.Transport interface with some additional
|
||||
// functionality for fetching remote media.
|
||||
type Transport interface {
|
||||
pub.Transport
|
||||
// DereferenceMedia fetches the bytes of the given media attachment IRI, with the expectedContentType.
|
||||
DereferenceMedia(c context.Context, iri *url.URL, expectedContentType string) ([]byte, error)
|
||||
// DereferenceInstance dereferences remote instance information, first by checking /api/v1/instance, and then by checking /.well-known/nodeinfo.
|
||||
DereferenceInstance(c context.Context, iri *url.URL) (*apimodel.Instance, error)
|
||||
// Finger performs a webfinger request with the given username and domain, and returns the bytes from the response body.
|
||||
Finger(c context.Context, targetUsername string, targetDomains string) ([]byte, error)
|
||||
}
|
||||
@ -124,3 +130,85 @@ func (t *transport) Finger(c context.Context, targetUsername string, targetDomai
|
||||
}
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func (t *transport) DereferenceInstance(c context.Context, iri *url.URL) (*apimodel.Instance, error) {
|
||||
l := t.log.WithField("func", "DereferenceInstance")
|
||||
|
||||
var i *apimodel.Instance
|
||||
var err error
|
||||
|
||||
// First try to dereference using /api/v1/instance.
|
||||
// This will provide the most complete picture of an instance, and avoid unnecessary api calls.
|
||||
//
|
||||
// This will only work with Mastodon-api compatible instances: Mastodon, some Pleroma instances, GoToSocial.
|
||||
l.Debugf("trying to dereference instance %s by /api/v1/instance", iri.Host)
|
||||
i, err = dereferenceByAPIV1Instance(t, c, iri)
|
||||
if err == nil {
|
||||
l.Debugf("successfully dereferenced instance using /api/v1/instance")
|
||||
return i, nil
|
||||
}
|
||||
l.Debugf("couldn't dereference instance using /api/v1/instance: %s", err)
|
||||
|
||||
// If that doesn't work, try to dereference using /.well-known/nodeinfo.
|
||||
// This will involve two API calls and return less info overall, but should be more widely compatible.
|
||||
l.Debugf("trying to dereference instance %s by /.well-known/nodeinfo", iri.Host)
|
||||
i, err = dereferenceByNodeInfo(t, c, iri)
|
||||
if err == nil {
|
||||
l.Debugf("successfully dereferenced instance using /.well-known/nodeinfo")
|
||||
return i, nil
|
||||
}
|
||||
l.Debugf("couldn't dereference instance using /.well-known/nodeinfo: %s", err)
|
||||
|
||||
return nil, fmt.Errorf("couldn't dereference instance %s using either /api/v1/instance or /.well-known/nodeinfo", iri.Host)
|
||||
}
|
||||
|
||||
func dereferenceByAPIV1Instance(t *transport, c context.Context, iri *url.URL) (*apimodel.Instance, error) {
|
||||
l := t.log.WithField("func", "dereferenceByAPIV1Instance")
|
||||
|
||||
cleanIRI := &url.URL{
|
||||
Scheme: iri.Scheme,
|
||||
Host: iri.Host,
|
||||
Path: "api/v1/instance",
|
||||
}
|
||||
|
||||
l.Debugf("performing GET to %s", cleanIRI.String())
|
||||
req, err := http.NewRequest("GET", cleanIRI.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(c)
|
||||
req.Header.Add("Accept", "application/json")
|
||||
req.Header.Add("Date", t.clock.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
|
||||
req.Header.Add("User-Agent", fmt.Sprintf("%s %s", t.appAgent, t.gofedAgent))
|
||||
req.Header.Set("Host", cleanIRI.Host)
|
||||
t.getSignerMu.Lock()
|
||||
err = t.getSigner.SignRequest(t.privkey, t.pubKeyID, req, nil)
|
||||
t.getSignerMu.Unlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := t.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("GET request to %s failed (%d): %s", cleanIRI.String(), resp.StatusCode, resp.Status)
|
||||
}
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// try to parse the returned bytes directly into an Instance model
|
||||
i := &apimodel.Instance{}
|
||||
if err := json.Unmarshal(b, i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func dereferenceByNodeInfo(t *transport, c context.Context, iri *url.URL) (*apimodel.Instance, error) {
|
||||
return nil, errors.New("not yet implemented")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user