start adding remote instance dereference
This commit is contained in:
parent
b6c62309f2
commit
24262b11cf
@ -125,6 +125,18 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr
|
|||||||
return ctx, false, fmt.Errorf("not authenticated: %s", err)
|
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{}
|
requestingAccount := >smodel.Account{}
|
||||||
if err := f.db.GetWhere([]db.Where{{Key: "uri", Value: publicKeyOwnerURI.String()}}, requestingAccount); err != nil {
|
if err := f.db.GetWhere([]db.Where{{Key: "uri", Value: publicKeyOwnerURI.String()}}, requestingAccount); err != nil {
|
||||||
// there's been a proper error so return it
|
// there's been a proper error so return it
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-fed/activity/pub"
|
"github.com/go-fed/activity/pub"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
"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/federatingdb"
|
"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).
|
// 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.
|
// 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)
|
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.
|
// GetTransportForUser returns a new transport initialized with the key credentials belonging to the given username.
|
||||||
// This can be used for making signed http requests.
|
// 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/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/transport"
|
"github.com/superseriousbusiness/gotosocial/internal/transport"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
"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
|
var pkOwnerURI *url.URL
|
||||||
requestingRemoteAccount := >smodel.Account{}
|
requestingRemoteAccount := >smodel.Account{}
|
||||||
requestingLocalAccount := >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
|
// LOCAL ACCOUNT REQUEST
|
||||||
// the request is coming from INSIDE THE HOUSE so skip the remote dereferencing
|
// 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 {
|
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())
|
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) {
|
func (f *federator) GetTransportForUser(username string) (transport.Transport, error) {
|
||||||
// We need an account to use to create a transport for dereferecing the signature.
|
// 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.
|
// If a username has been given, we can fetch the account with that username and use it.
|
||||||
|
@ -3,6 +3,8 @@ package transport
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -12,13 +14,17 @@ import (
|
|||||||
"github.com/go-fed/activity/pub"
|
"github.com/go-fed/activity/pub"
|
||||||
"github.com/go-fed/httpsig"
|
"github.com/go-fed/httpsig"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Transport wraps the pub.Transport interface with some additional
|
// Transport wraps the pub.Transport interface with some additional
|
||||||
// functionality for fetching remote media.
|
// functionality for fetching remote media.
|
||||||
type Transport interface {
|
type Transport interface {
|
||||||
pub.Transport
|
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)
|
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 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)
|
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)
|
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