mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 16:22:25 -05:00 
			
		
		
		
	[bugfix] harden checks for remotes masquerading as local, and return correct local account redirects early (#3706)
This commit is contained in:
		
					parent
					
						
							
								d16e4fa34d
							
						
					
				
			
			
				commit
				
					
						1ab960bf15
					
				
			
		
					 3 changed files with 146 additions and 22 deletions
				
			
		|  | @ -639,7 +639,16 @@ func (d *Dereferencer) enrichAccount( | ||||||
| 				return nil, nil, gtserror.Newf("db error getting account after redirects: %w", err) | 				return nil, nil, gtserror.Newf("db error getting account after redirects: %w", err) | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if alreadyAcc != nil { | 			switch { | ||||||
|  | 			case alreadyAcc == nil: | ||||||
|  | 				// nothing to do | ||||||
|  | 
 | ||||||
|  | 			case alreadyAcc.IsLocal(): | ||||||
|  | 				// Request eventually redirected to a | ||||||
|  | 				// local account. Return it as-is here. | ||||||
|  | 				return alreadyAcc, nil, nil | ||||||
|  | 
 | ||||||
|  | 			default: | ||||||
| 				// We had this account stored | 				// We had this account stored | ||||||
| 				// under discovered final URI. | 				// under discovered final URI. | ||||||
| 				// | 				// | ||||||
|  | @ -718,12 +727,6 @@ func (d *Dereferencer) enrichAccount( | ||||||
| 		latestAcc.Username = cmp.Or(latestAcc.Username, accUsername) | 		latestAcc.Username = cmp.Or(latestAcc.Username, accUsername) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if latestAcc.Domain == "" { |  | ||||||
| 		// Ensure we have a domain set by this point, |  | ||||||
| 		// otherwise it gets stored as a local user! |  | ||||||
| 		return nil, nil, gtserror.Newf("empty domain for %s", uri) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Ensure the final parsed account URI matches | 	// Ensure the final parsed account URI matches | ||||||
| 	// the input URI we fetched (or received) it as. | 	// the input URI we fetched (or received) it as. | ||||||
| 	if matches, err := util.URIMatches( | 	if matches, err := util.URIMatches( | ||||||
|  | @ -740,10 +743,16 @@ func (d *Dereferencer) enrichAccount( | ||||||
| 	} else if !matches { | 	} else if !matches { | ||||||
| 		return nil, nil, gtserror.Newf( | 		return nil, nil, gtserror.Newf( | ||||||
| 			"account uri %s does not match %s", | 			"account uri %s does not match %s", | ||||||
| 			latestAcc.URI, uri.String(), | 			latestAcc.URI, uri, | ||||||
| 		) | 		) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Ensure this isn't a local account, | ||||||
|  | 	// or a remote masquerading as such! | ||||||
|  | 	if latestAcc.IsLocal() { | ||||||
|  | 		return nil, nil, gtserror.Newf("cannot dereference local account %s", uri) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// Get current time. | 	// Get current time. | ||||||
| 	now := time.Now() | 	now := time.Now() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -18,11 +18,15 @@ | ||||||
| package dereferencing_test | package dereferencing_test | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"bytes" | ||||||
| 	"context" | 	"context" | ||||||
| 	"crypto/rsa" | 	"crypto/rsa" | ||||||
| 	"crypto/x509" | 	"crypto/x509" | ||||||
|  | 	"encoding/json" | ||||||
| 	"encoding/pem" | 	"encoding/pem" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  | @ -33,6 +37,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/ap" | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"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/dereferencing" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/testrig" | 	"github.com/superseriousbusiness/gotosocial/testrig" | ||||||
| ) | ) | ||||||
|  | @ -214,6 +219,111 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountWithUnknownUserURI() { | ||||||
| 	suite.Nil(fetchedAccount) | 	suite.Nil(fetchedAccount) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (suite *AccountTestSuite) TestDereferenceLocalAccountByRedirect() { | ||||||
|  | 	ctx, cncl := context.WithCancel(context.Background()) | ||||||
|  | 	defer cncl() | ||||||
|  | 
 | ||||||
|  | 	fetchingAccount := suite.testAccounts["local_account_1"] | ||||||
|  | 	targetAccount := suite.testAccounts["local_account_2"] | ||||||
|  | 
 | ||||||
|  | 	// Convert the target account to ActivityStreams model for dereference. | ||||||
|  | 	targetAccountable, err := suite.converter.AccountToAS(ctx, targetAccount) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotNil(targetAccountable) | ||||||
|  | 
 | ||||||
|  | 	// Serialize to "raw" JSON map for response. | ||||||
|  | 	rawJSON, err := ap.Serialize(targetAccountable) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 
 | ||||||
|  | 	// Finally serialize to actual bytes. | ||||||
|  | 	json, err := json.Marshal(rawJSON) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 
 | ||||||
|  | 	// Replace test HTTP client with one that always returns the target account AS model. | ||||||
|  | 	suite.client = testrig.NewMockHTTPClient(func(req *http.Request) (*http.Response, error) { | ||||||
|  | 		return &http.Response{ | ||||||
|  | 			Status:        http.StatusText(http.StatusOK), | ||||||
|  | 			StatusCode:    http.StatusOK, | ||||||
|  | 			ContentLength: int64(len(json)), | ||||||
|  | 			Header:        http.Header{"Content-Type": {"application/activity+json"}}, | ||||||
|  | 			Body:          io.NopCloser(bytes.NewReader(json)), | ||||||
|  | 			Request:       &http.Request{URL: testrig.URLMustParse(targetAccount.URI)}, | ||||||
|  | 		}, nil | ||||||
|  | 	}, "") | ||||||
|  | 
 | ||||||
|  | 	// Update dereferencer to use new test HTTP client. | ||||||
|  | 	suite.dereferencer = dereferencing.NewDereferencer( | ||||||
|  | 		&suite.state, | ||||||
|  | 		suite.converter, | ||||||
|  | 		testrig.NewTestTransportController(&suite.state, suite.client), | ||||||
|  | 		suite.visFilter, | ||||||
|  | 		suite.intFilter, | ||||||
|  | 		suite.media, | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	// Use any old input test URI, this doesn't actually matter what it is. | ||||||
|  | 	uri := testrig.URLMustParse("https://this-will-be-redirected.butts/") | ||||||
|  | 
 | ||||||
|  | 	// Try dereference the test URI, since it correctly redirects to us it should return our account. | ||||||
|  | 	account, accountable, err := suite.dereferencer.GetAccountByURI(ctx, fetchingAccount.Username, uri) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.Nil(accountable) | ||||||
|  | 	suite.NotNil(account) | ||||||
|  | 	suite.Equal(targetAccount.ID, account.ID) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (suite *AccountTestSuite) TestDereferenceMasqueradingLocalAccount() { | ||||||
|  | 	ctx, cncl := context.WithCancel(context.Background()) | ||||||
|  | 	defer cncl() | ||||||
|  | 
 | ||||||
|  | 	fetchingAccount := suite.testAccounts["local_account_1"] | ||||||
|  | 	targetAccount := suite.testAccounts["local_account_2"] | ||||||
|  | 
 | ||||||
|  | 	// Convert the target account to ActivityStreams model for dereference. | ||||||
|  | 	targetAccountable, err := suite.converter.AccountToAS(ctx, targetAccount) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotNil(targetAccountable) | ||||||
|  | 
 | ||||||
|  | 	// Serialize to "raw" JSON map for response. | ||||||
|  | 	rawJSON, err := ap.Serialize(targetAccountable) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 
 | ||||||
|  | 	// Finally serialize to actual bytes. | ||||||
|  | 	json, err := json.Marshal(rawJSON) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 
 | ||||||
|  | 	// Use any old input test URI, this doesn't actually matter what it is. | ||||||
|  | 	uri := testrig.URLMustParse("https://this-will-be-redirected.butts/") | ||||||
|  | 
 | ||||||
|  | 	// Replace test HTTP client with one that returns OUR account, but at their URI endpoint. | ||||||
|  | 	suite.client = testrig.NewMockHTTPClient(func(req *http.Request) (*http.Response, error) { | ||||||
|  | 		return &http.Response{ | ||||||
|  | 			Status:        http.StatusText(http.StatusOK), | ||||||
|  | 			StatusCode:    http.StatusOK, | ||||||
|  | 			ContentLength: int64(len(json)), | ||||||
|  | 			Header:        http.Header{"Content-Type": {"application/activity+json"}}, | ||||||
|  | 			Body:          io.NopCloser(bytes.NewReader(json)), | ||||||
|  | 			Request:       &http.Request{URL: uri}, | ||||||
|  | 		}, nil | ||||||
|  | 	}, "") | ||||||
|  | 
 | ||||||
|  | 	// Update dereferencer to use new test HTTP client. | ||||||
|  | 	suite.dereferencer = dereferencing.NewDereferencer( | ||||||
|  | 		&suite.state, | ||||||
|  | 		suite.converter, | ||||||
|  | 		testrig.NewTestTransportController(&suite.state, suite.client), | ||||||
|  | 		suite.visFilter, | ||||||
|  | 		suite.intFilter, | ||||||
|  | 		suite.media, | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	// Try dereference the test URI, since it correctly redirects to us it should return our account. | ||||||
|  | 	account, accountable, err := suite.dereferencer.GetAccountByURI(ctx, fetchingAccount.Username, uri) | ||||||
|  | 	suite.NotNil(err) | ||||||
|  | 	suite.Nil(account) | ||||||
|  | 	suite.Nil(accountable) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (suite *AccountTestSuite) TestDereferenceRemoteAccountWithNonMatchingURI() { | func (suite *AccountTestSuite) TestDereferenceRemoteAccountWithNonMatchingURI() { | ||||||
| 	fetchingAccount := suite.testAccounts["local_account_1"] | 	fetchingAccount := suite.testAccounts["local_account_1"] | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -26,6 +26,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/filter/interaction" | 	"github.com/superseriousbusiness/gotosocial/internal/filter/interaction" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/filter/visibility" | 	"github.com/superseriousbusiness/gotosocial/internal/filter/visibility" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/state" | 	"github.com/superseriousbusiness/gotosocial/internal/state" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/storage" | 	"github.com/superseriousbusiness/gotosocial/internal/storage" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
|  | @ -34,10 +35,14 @@ import ( | ||||||
| 
 | 
 | ||||||
| type DereferencerStandardTestSuite struct { | type DereferencerStandardTestSuite struct { | ||||||
| 	suite.Suite | 	suite.Suite | ||||||
| 	db      db.DB | 	db        db.DB | ||||||
| 	storage *storage.Driver | 	storage   *storage.Driver | ||||||
| 	state   state.State | 	state     state.State | ||||||
| 	client  *testrig.MockHTTPClient | 	client    *testrig.MockHTTPClient | ||||||
|  | 	converter *typeutils.Converter | ||||||
|  | 	visFilter *visibility.Filter | ||||||
|  | 	intFilter *interaction.Filter | ||||||
|  | 	media     *media.Manager | ||||||
| 
 | 
 | ||||||
| 	testRemoteStatuses    map[string]vocab.ActivityStreamsNote | 	testRemoteStatuses    map[string]vocab.ActivityStreamsNote | ||||||
| 	testRemotePeople      map[string]vocab.ActivityStreamsPerson | 	testRemotePeople      map[string]vocab.ActivityStreamsPerson | ||||||
|  | @ -67,12 +72,15 @@ func (suite *DereferencerStandardTestSuite) SetupTest() { | ||||||
| 
 | 
 | ||||||
| 	suite.db = testrig.NewTestDB(&suite.state) | 	suite.db = testrig.NewTestDB(&suite.state) | ||||||
| 
 | 
 | ||||||
| 	converter := typeutils.NewConverter(&suite.state) | 	suite.converter = typeutils.NewConverter(&suite.state) | ||||||
|  | 	suite.visFilter = visibility.NewFilter(&suite.state) | ||||||
|  | 	suite.intFilter = interaction.NewFilter(&suite.state) | ||||||
|  | 	suite.media = testrig.NewTestMediaManager(&suite.state) | ||||||
| 
 | 
 | ||||||
| 	testrig.StartTimelines( | 	testrig.StartTimelines( | ||||||
| 		&suite.state, | 		&suite.state, | ||||||
| 		visibility.NewFilter(&suite.state), | 		suite.visFilter, | ||||||
| 		converter, | 		suite.converter, | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	suite.client = testrig.NewMockHTTPClient(nil, "../../../testrig/media") | 	suite.client = testrig.NewMockHTTPClient(nil, "../../../testrig/media") | ||||||
|  | @ -81,19 +89,16 @@ func (suite *DereferencerStandardTestSuite) SetupTest() { | ||||||
| 	suite.state.AdminActions = admin.New(suite.state.DB, &suite.state.Workers) | 	suite.state.AdminActions = admin.New(suite.state.DB, &suite.state.Workers) | ||||||
| 	suite.state.Storage = suite.storage | 	suite.state.Storage = suite.storage | ||||||
| 
 | 
 | ||||||
| 	visFilter := visibility.NewFilter(&suite.state) |  | ||||||
| 	intFilter := interaction.NewFilter(&suite.state) |  | ||||||
| 	media := testrig.NewTestMediaManager(&suite.state) |  | ||||||
| 	suite.dereferencer = dereferencing.NewDereferencer( | 	suite.dereferencer = dereferencing.NewDereferencer( | ||||||
| 		&suite.state, | 		&suite.state, | ||||||
| 		converter, | 		suite.converter, | ||||||
| 		testrig.NewTestTransportController( | 		testrig.NewTestTransportController( | ||||||
| 			&suite.state, | 			&suite.state, | ||||||
| 			suite.client, | 			suite.client, | ||||||
| 		), | 		), | ||||||
| 		visFilter, | 		suite.visFilter, | ||||||
| 		intFilter, | 		suite.intFilter, | ||||||
| 		media, | 		suite.media, | ||||||
| 	) | 	) | ||||||
| 	testrig.StandardDBSetup(suite.db, nil) | 	testrig.StandardDBSetup(suite.db, nil) | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue