start adding remote instance dereference
This commit is contained in:
		| @ -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") | ||||||
|  | } | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user