mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 20:12:26 -05:00 
			
		
		
		
	[bugfix] Allow blocked accounts to show in precise search (#2321)
This commit is contained in:
		
					parent
					
						
							
								4dc0547dc0
							
						
					
				
			
			
				commit
				
					
						dd4b0241ea
					
				
			
		
					 8 changed files with 390 additions and 146 deletions
				
			
		|  | @ -38,6 +38,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/testrig" | 	"github.com/superseriousbusiness/gotosocial/testrig" | ||||||
| ) | ) | ||||||
|  | @ -1179,8 +1180,9 @@ func (suite *SearchGetTestSuite) TestSearchLocalInstanceAccountPartial() { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Query was a partial namestring from our | 	// Query was a partial namestring from our | ||||||
| 	// instance, so will return the instance account. | 	// instance, instance account should be | ||||||
| 	suite.Len(searchResult.Accounts, 1) | 	// excluded from results. | ||||||
|  | 	suite.Len(searchResult.Accounts, 0) | ||||||
| 	suite.Len(searchResult.Statuses, 0) | 	suite.Len(searchResult.Statuses, 0) | ||||||
| 	suite.Len(searchResult.Hashtags, 0) | 	suite.Len(searchResult.Hashtags, 0) | ||||||
| } | } | ||||||
|  | @ -1546,6 +1548,189 @@ func (suite *SearchGetTestSuite) TestSearchNotHashtagButWithTypeHashtag() { | ||||||
| 	suite.Len(searchResult.Hashtags, 1) | 	suite.Len(searchResult.Hashtags, 1) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (suite *SearchGetTestSuite) TestSearchBlockedAccountFullNamestring() { | ||||||
|  | 	var ( | ||||||
|  | 		requestingAccount          = suite.testAccounts["local_account_1"] | ||||||
|  | 		targetAccount              = suite.testAccounts["remote_account_1"] | ||||||
|  | 		token                      = suite.testTokens["local_account_1"] | ||||||
|  | 		user                       = suite.testUsers["local_account_1"] | ||||||
|  | 		maxID              *string = nil | ||||||
|  | 		minID              *string = nil | ||||||
|  | 		limit              *int    = nil | ||||||
|  | 		offset             *int    = nil | ||||||
|  | 		resolve            *bool   = func() *bool { i := true; return &i }() | ||||||
|  | 		query                      = "@" + targetAccount.Username + "@" + targetAccount.Domain | ||||||
|  | 		queryType          *string = func() *string { i := "accounts"; return &i }() | ||||||
|  | 		following          *bool   = nil | ||||||
|  | 		expectedHTTPStatus         = http.StatusOK | ||||||
|  | 		expectedBody               = "" | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	// Block the account | ||||||
|  | 	// we're about to search. | ||||||
|  | 	if err := suite.db.PutBlock( | ||||||
|  | 		context.Background(), | ||||||
|  | 		>smodel.Block{ | ||||||
|  | 			ID:              id.NewULID(), | ||||||
|  | 			URI:             "https://example.org/nooooooo", | ||||||
|  | 			AccountID:       requestingAccount.ID, | ||||||
|  | 			TargetAccountID: targetAccount.ID, | ||||||
|  | 		}, | ||||||
|  | 	); err != nil { | ||||||
|  | 		suite.FailNow(err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	searchResult, err := suite.getSearch( | ||||||
|  | 		requestingAccount, | ||||||
|  | 		token, | ||||||
|  | 		apiutil.APIv2, | ||||||
|  | 		user, | ||||||
|  | 		maxID, | ||||||
|  | 		minID, | ||||||
|  | 		limit, | ||||||
|  | 		offset, | ||||||
|  | 		query, | ||||||
|  | 		queryType, | ||||||
|  | 		resolve, | ||||||
|  | 		following, | ||||||
|  | 		expectedHTTPStatus, | ||||||
|  | 		expectedBody) | ||||||
|  | 	if err != nil { | ||||||
|  | 		suite.FailNow(err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Search was for full namestring; | ||||||
|  | 	// we should still be able to see | ||||||
|  | 	// the account we've blocked. | ||||||
|  | 	if !suite.Len(searchResult.Accounts, 1) { | ||||||
|  | 		suite.FailNow("expected 1 account in search results but got 0") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	gotAccount := searchResult.Accounts[0] | ||||||
|  | 	suite.NotNil(gotAccount) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (suite *SearchGetTestSuite) TestSearchBlockedAccountPartialNamestring() { | ||||||
|  | 	var ( | ||||||
|  | 		requestingAccount          = suite.testAccounts["local_account_1"] | ||||||
|  | 		targetAccount              = suite.testAccounts["remote_account_1"] | ||||||
|  | 		token                      = suite.testTokens["local_account_1"] | ||||||
|  | 		user                       = suite.testUsers["local_account_1"] | ||||||
|  | 		maxID              *string = nil | ||||||
|  | 		minID              *string = nil | ||||||
|  | 		limit              *int    = nil | ||||||
|  | 		offset             *int    = nil | ||||||
|  | 		resolve            *bool   = func() *bool { i := true; return &i }() | ||||||
|  | 		query                      = "@" + targetAccount.Username | ||||||
|  | 		queryType          *string = func() *string { i := "accounts"; return &i }() | ||||||
|  | 		following          *bool   = nil | ||||||
|  | 		expectedHTTPStatus         = http.StatusOK | ||||||
|  | 		expectedBody               = "" | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	// Block the account | ||||||
|  | 	// we're about to search. | ||||||
|  | 	if err := suite.db.PutBlock( | ||||||
|  | 		context.Background(), | ||||||
|  | 		>smodel.Block{ | ||||||
|  | 			ID:              id.NewULID(), | ||||||
|  | 			URI:             "https://example.org/nooooooo", | ||||||
|  | 			AccountID:       requestingAccount.ID, | ||||||
|  | 			TargetAccountID: targetAccount.ID, | ||||||
|  | 		}, | ||||||
|  | 	); err != nil { | ||||||
|  | 		suite.FailNow(err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	searchResult, err := suite.getSearch( | ||||||
|  | 		requestingAccount, | ||||||
|  | 		token, | ||||||
|  | 		apiutil.APIv2, | ||||||
|  | 		user, | ||||||
|  | 		maxID, | ||||||
|  | 		minID, | ||||||
|  | 		limit, | ||||||
|  | 		offset, | ||||||
|  | 		query, | ||||||
|  | 		queryType, | ||||||
|  | 		resolve, | ||||||
|  | 		following, | ||||||
|  | 		expectedHTTPStatus, | ||||||
|  | 		expectedBody) | ||||||
|  | 	if err != nil { | ||||||
|  | 		suite.FailNow(err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Search was for partial namestring; | ||||||
|  | 	// we should not be able to see | ||||||
|  | 	// the account we've blocked. | ||||||
|  | 	if !suite.Empty(searchResult.Accounts) { | ||||||
|  | 		suite.FailNow("expected 0 accounts in search results") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (suite *SearchGetTestSuite) TestSearchBlockedAccountURI() { | ||||||
|  | 	var ( | ||||||
|  | 		requestingAccount          = suite.testAccounts["local_account_1"] | ||||||
|  | 		targetAccount              = suite.testAccounts["remote_account_1"] | ||||||
|  | 		token                      = suite.testTokens["local_account_1"] | ||||||
|  | 		user                       = suite.testUsers["local_account_1"] | ||||||
|  | 		maxID              *string = nil | ||||||
|  | 		minID              *string = nil | ||||||
|  | 		limit              *int    = nil | ||||||
|  | 		offset             *int    = nil | ||||||
|  | 		resolve            *bool   = func() *bool { i := true; return &i }() | ||||||
|  | 		query                      = targetAccount.URI | ||||||
|  | 		queryType          *string = func() *string { i := "accounts"; return &i }() | ||||||
|  | 		following          *bool   = nil | ||||||
|  | 		expectedHTTPStatus         = http.StatusOK | ||||||
|  | 		expectedBody               = "" | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	// Block the account | ||||||
|  | 	// we're about to search. | ||||||
|  | 	if err := suite.db.PutBlock( | ||||||
|  | 		context.Background(), | ||||||
|  | 		>smodel.Block{ | ||||||
|  | 			ID:              id.NewULID(), | ||||||
|  | 			URI:             "https://example.org/nooooooo", | ||||||
|  | 			AccountID:       requestingAccount.ID, | ||||||
|  | 			TargetAccountID: targetAccount.ID, | ||||||
|  | 		}, | ||||||
|  | 	); err != nil { | ||||||
|  | 		suite.FailNow(err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	searchResult, err := suite.getSearch( | ||||||
|  | 		requestingAccount, | ||||||
|  | 		token, | ||||||
|  | 		apiutil.APIv2, | ||||||
|  | 		user, | ||||||
|  | 		maxID, | ||||||
|  | 		minID, | ||||||
|  | 		limit, | ||||||
|  | 		offset, | ||||||
|  | 		query, | ||||||
|  | 		queryType, | ||||||
|  | 		resolve, | ||||||
|  | 		following, | ||||||
|  | 		expectedHTTPStatus, | ||||||
|  | 		expectedBody) | ||||||
|  | 	if err != nil { | ||||||
|  | 		suite.FailNow(err.Error()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Search was for precise URI; | ||||||
|  | 	// we should still be able to see | ||||||
|  | 	// the account we've blocked. | ||||||
|  | 	if !suite.Len(searchResult.Accounts, 1) { | ||||||
|  | 		suite.FailNow("expected 1 account in search results but got 0") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	gotAccount := searchResult.Accounts[0] | ||||||
|  | 	suite.NotNil(gotAccount) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestSearchGetTestSuite(t *testing.T) { | func TestSearchGetTestSuite(t *testing.T) { | ||||||
| 	suite.Run(t, &SearchGetTestSuite{}) | 	suite.Run(t, &SearchGetTestSuite{}) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -52,8 +52,9 @@ func (p *Processor) StatusesGet( | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if blocked { | 		if blocked { | ||||||
| 			err := errors.New("block exists between accounts") | 			// Block exists between accounts. | ||||||
| 			return nil, gtserror.NewErrorNotFound(err) | 			// Just return empty statuses. | ||||||
|  | 			return util.EmptyPageableResponse(), nil | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -29,6 +29,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/id" | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/log" | 	"github.com/superseriousbusiness/gotosocial/internal/log" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Accounts does a partial search for accounts that | // Accounts does a partial search for accounts that | ||||||
|  | @ -56,6 +57,11 @@ func (p *Processor) Accounts( | ||||||
| 	// in their search results. | 	// in their search results. | ||||||
| 	const includeInstanceAccounts = false | 	const includeInstanceAccounts = false | ||||||
| 
 | 
 | ||||||
|  | 	// We *might* want to include blocked accounts | ||||||
|  | 	// in this search, but only if it's a search | ||||||
|  | 	// for a specific account. | ||||||
|  | 	includeBlockedAccounts := false | ||||||
|  | 
 | ||||||
| 	var ( | 	var ( | ||||||
| 		foundAccounts = make([]*gtsmodel.Account, 0, limit) | 		foundAccounts = make([]*gtsmodel.Account, 0, limit) | ||||||
| 		appendAccount = func(foundAccount *gtsmodel.Account) { foundAccounts = append(foundAccounts, foundAccount) } | 		appendAccount = func(foundAccount *gtsmodel.Account) { foundAccounts = append(foundAccounts, foundAccount) } | ||||||
|  | @ -95,20 +101,35 @@ func (p *Processor) Accounts( | ||||||
| 			requestingAccount, | 			requestingAccount, | ||||||
| 			foundAccounts, | 			foundAccounts, | ||||||
| 			includeInstanceAccounts, | 			includeInstanceAccounts, | ||||||
|  | 			includeBlockedAccounts, | ||||||
| 		) | 		) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Return all accounts we can find that match the | 	// See if we have something that looks like a namestring. | ||||||
| 	// provided query. If it's not a namestring, this | 	username, domain, err := util.ExtractNamestringParts(query) | ||||||
| 	// won't return an error, it'll just return 0 results. | 	if err != nil { | ||||||
| 	if _, err := p.accountsByNamestring( | 		log.Warnf(ctx, "couldn't parse '%s' as namestring: %v", query, err) | ||||||
|  | 	} else { | ||||||
|  | 		if domain != "" { | ||||||
|  | 			// Search was an exact namestring; | ||||||
|  | 			// we can safely assume caller is | ||||||
|  | 			// searching for a specific account, | ||||||
|  | 			// and show it to them even if they | ||||||
|  | 			// have it blocked. | ||||||
|  | 			includeBlockedAccounts = true | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Get all accounts we can find | ||||||
|  | 		// that match the provided query. | ||||||
|  | 		if err := p.accountsByNamestring( | ||||||
| 			ctx, | 			ctx, | ||||||
| 			requestingAccount, | 			requestingAccount, | ||||||
| 			id.Highest, | 			id.Highest, | ||||||
| 			id.Lowest, | 			id.Lowest, | ||||||
| 			limit, | 			limit, | ||||||
| 			offset, | 			offset, | ||||||
| 		query, | 			username, | ||||||
|  | 			domain, | ||||||
| 			resolve, | 			resolve, | ||||||
| 			following, | 			following, | ||||||
| 			appendAccount, | 			appendAccount, | ||||||
|  | @ -116,6 +137,7 @@ func (p *Processor) Accounts( | ||||||
| 			err = gtserror.Newf("error searching by namestring: %w", err) | 			err = gtserror.Newf("error searching by namestring: %w", err) | ||||||
| 			return nil, gtserror.NewErrorInternalError(err) | 			return nil, gtserror.NewErrorInternalError(err) | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// Return whatever we got (if anything). | 	// Return whatever we got (if anything). | ||||||
| 	return p.packageAccounts( | 	return p.packageAccounts( | ||||||
|  | @ -123,5 +145,6 @@ func (p *Processor) Accounts( | ||||||
| 		requestingAccount, | 		requestingAccount, | ||||||
| 		foundAccounts, | 		foundAccounts, | ||||||
| 		includeInstanceAccounts, | 		includeInstanceAccounts, | ||||||
|  | 		includeBlockedAccounts, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -77,6 +77,12 @@ func (p *Processor) Get( | ||||||
| 		// search in the database in the latter | 		// search in the database in the latter | ||||||
| 		// parts of this function. | 		// parts of this function. | ||||||
| 		includeInstanceAccounts = true | 		includeInstanceAccounts = true | ||||||
|  | 
 | ||||||
|  | 		// Assume caller doesn't want to see | ||||||
|  | 		// blocked accounts. This will change | ||||||
|  | 		// to 'true' if caller is searching | ||||||
|  | 		// for a specific account. | ||||||
|  | 		includeBlockedAccounts = false | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	// Validate query. | 	// Validate query. | ||||||
|  | @ -120,7 +126,9 @@ func (p *Processor) Get( | ||||||
| 			ctx, | 			ctx, | ||||||
| 			account, | 			account, | ||||||
| 			nil, nil, nil, // No results. | 			nil, nil, nil, // No results. | ||||||
| 			req.APIv1, includeInstanceAccounts, | 			req.APIv1, | ||||||
|  | 			includeInstanceAccounts, | ||||||
|  | 			includeBlockedAccounts, | ||||||
| 		) | 		) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -131,7 +139,6 @@ func (p *Processor) Get( | ||||||
| 		appendStatus  = func(s *gtsmodel.Status) { foundStatuses = append(foundStatuses, s) } | 		appendStatus  = func(s *gtsmodel.Status) { foundStatuses = append(foundStatuses, s) } | ||||||
| 		appendAccount = func(a *gtsmodel.Account) { foundAccounts = append(foundAccounts, a) } | 		appendAccount = func(a *gtsmodel.Account) { foundAccounts = append(foundAccounts, a) } | ||||||
| 		appendTag     = func(t *gtsmodel.Tag) { foundTags = append(foundTags, t) } | 		appendTag     = func(t *gtsmodel.Tag) { foundTags = append(foundTags, t) } | ||||||
| 		keepLooking   bool |  | ||||||
| 		err           error | 		err           error | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
|  | @ -152,15 +159,27 @@ func (p *Processor) Get( | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Search using what may or may not be a namestring. | 		// See if we have something that looks like a namestring. | ||||||
| 		keepLooking, err = p.accountsByNamestring( | 		username, domain, err := util.ExtractNamestringParts(queryC) | ||||||
|  | 		if err == nil { | ||||||
|  | 			// We managed to parse query as a namestring. | ||||||
|  | 			// If domain was set, this is a very specific | ||||||
|  | 			// search for a particular account, so show | ||||||
|  | 			// that account to the caller even if they | ||||||
|  | 			// have it blocked. They might be looking | ||||||
|  | 			// for it to unblock it again! | ||||||
|  | 			domainSet := (domain != "") | ||||||
|  | 			includeBlockedAccounts = domainSet | ||||||
|  | 
 | ||||||
|  | 			err = p.accountsByNamestring( | ||||||
| 				ctx, | 				ctx, | ||||||
| 				account, | 				account, | ||||||
| 				maxID, | 				maxID, | ||||||
| 				minID, | 				minID, | ||||||
| 				limit, | 				limit, | ||||||
| 				offset, | 				offset, | ||||||
| 			queryC, | 				username, | ||||||
|  | 				domain, | ||||||
| 				resolve, | 				resolve, | ||||||
| 				following, | 				following, | ||||||
| 				appendAccount, | 				appendAccount, | ||||||
|  | @ -170,8 +189,13 @@ func (p *Processor) Get( | ||||||
| 				return nil, gtserror.NewErrorInternalError(err) | 				return nil, gtserror.NewErrorInternalError(err) | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 		if !keepLooking { | 			// If domain was set, we know this is | ||||||
| 			// Return whatever we have. | 			// a full namestring, and not a url or | ||||||
|  | 			// just a username, so we should stop | ||||||
|  | 			// looking now and just return what we | ||||||
|  | 			// have (if anything). Otherwise we'll | ||||||
|  | 			// let the search keep going. | ||||||
|  | 			if domainSet { | ||||||
| 				return p.packageSearchResult( | 				return p.packageSearchResult( | ||||||
| 					ctx, | 					ctx, | ||||||
| 					account, | 					account, | ||||||
|  | @ -180,28 +204,38 @@ func (p *Processor) Get( | ||||||
| 					foundTags, | 					foundTags, | ||||||
| 					req.APIv1, | 					req.APIv1, | ||||||
| 					includeInstanceAccounts, | 					includeInstanceAccounts, | ||||||
|  | 					includeBlockedAccounts, | ||||||
| 				) | 				) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// Check if the query is a URI with a recognizable | 	// Check if we're searching by a known URI scheme. | ||||||
| 	// scheme and use it to look for accounts or statuses. | 	// (This might just be a weirdly-parsed URI, | ||||||
| 	keepLooking, err = p.byURI( | 	// since Go's url package tends to be a bit | ||||||
|  | 	// trigger-happy when deciding things are URIs). | ||||||
|  | 	uri, err := url.Parse(query) | ||||||
|  | 	if err == nil && (uri.Scheme == "https" || uri.Scheme == "http") { | ||||||
|  | 		// URI is pretty specific so we can safely assume | ||||||
|  | 		// caller wants to include blocked accounts too. | ||||||
|  | 		includeBlockedAccounts = true | ||||||
|  | 
 | ||||||
|  | 		if err := p.byURI( | ||||||
| 			ctx, | 			ctx, | ||||||
| 			account, | 			account, | ||||||
| 		query, | 			uri, | ||||||
| 			queryType, | 			queryType, | ||||||
| 			resolve, | 			resolve, | ||||||
| 			appendAccount, | 			appendAccount, | ||||||
| 			appendStatus, | 			appendStatus, | ||||||
| 	) | 		); err != nil && !errors.Is(err, db.ErrNoEntries) { | ||||||
| 	if err != nil && !errors.Is(err, db.ErrNoEntries) { |  | ||||||
| 			err = gtserror.Newf("error searching by URI: %w", err) | 			err = gtserror.Newf("error searching by URI: %w", err) | ||||||
| 			return nil, gtserror.NewErrorInternalError(err) | 			return nil, gtserror.NewErrorInternalError(err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	if !keepLooking { | 		// This was a URI, so at this point just return | ||||||
| 		// Return whatever we have. | 		// whatever we have. You can't search hashtags by | ||||||
|  | 		// URI, and shouldn't do full-text with a URI either. | ||||||
| 		return p.packageSearchResult( | 		return p.packageSearchResult( | ||||||
| 			ctx, | 			ctx, | ||||||
| 			account, | 			account, | ||||||
|  | @ -210,6 +244,7 @@ func (p *Processor) Get( | ||||||
| 			foundTags, | 			foundTags, | ||||||
| 			req.APIv1, | 			req.APIv1, | ||||||
| 			includeInstanceAccounts, | 			includeInstanceAccounts, | ||||||
|  | 			includeBlockedAccounts, | ||||||
| 		) | 		) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -226,7 +261,7 @@ func (p *Processor) Get( | ||||||
| 	// We know that none of the subsequent searches | 	// We know that none of the subsequent searches | ||||||
| 	// would show any good results either, and those | 	// would show any good results either, and those | ||||||
| 	// searches are *much* more expensive. | 	// searches are *much* more expensive. | ||||||
| 	keepLooking, err = p.hashtag( | 	keepLooking, err := p.hashtag( | ||||||
| 		ctx, | 		ctx, | ||||||
| 		maxID, | 		maxID, | ||||||
| 		minID, | 		minID, | ||||||
|  | @ -251,6 +286,7 @@ func (p *Processor) Get( | ||||||
| 			foundTags, | 			foundTags, | ||||||
| 			req.APIv1, | 			req.APIv1, | ||||||
| 			includeInstanceAccounts, | 			includeInstanceAccounts, | ||||||
|  | 			includeBlockedAccounts, | ||||||
| 		) | 		) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -291,15 +327,15 @@ func (p *Processor) Get( | ||||||
| 		foundTags, | 		foundTags, | ||||||
| 		req.APIv1, | 		req.APIv1, | ||||||
| 		includeInstanceAccounts, | 		includeInstanceAccounts, | ||||||
|  | 		includeBlockedAccounts, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // accountsByNamestring searches for accounts using the | // accountsByNamestring searches for accounts using the | ||||||
| // provided namestring query. If domain is not set in | // provided username and domain. If domain is not set, | ||||||
| // the namestring, it may return more than one result | // it may return more than one result by doing a text | ||||||
| // by doing a text search in the database for accounts | // search in the database for accounts matching the query. | ||||||
| // matching the query. Otherwise, it tries to return an | // Otherwise, it tries to return an exact match. | ||||||
| // exact match. |  | ||||||
| func (p *Processor) accountsByNamestring( | func (p *Processor) accountsByNamestring( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	requestingAccount *gtsmodel.Account, | 	requestingAccount *gtsmodel.Account, | ||||||
|  | @ -307,26 +343,18 @@ func (p *Processor) accountsByNamestring( | ||||||
| 	minID string, | 	minID string, | ||||||
| 	limit int, | 	limit int, | ||||||
| 	offset int, | 	offset int, | ||||||
| 	query string, | 	username string, | ||||||
|  | 	domain string, | ||||||
| 	resolve bool, | 	resolve bool, | ||||||
| 	following bool, | 	following bool, | ||||||
| 	appendAccount func(*gtsmodel.Account), | 	appendAccount func(*gtsmodel.Account), | ||||||
| ) (bool, error) { | ) error { | ||||||
| 	// See if we have something that looks like a namestring. |  | ||||||
| 	username, domain, err := util.ExtractNamestringParts(query) |  | ||||||
| 	if err != nil { |  | ||||||
| 		// No need to return error; just not a namestring |  | ||||||
| 		// we can search with. Caller should keep looking |  | ||||||
| 		// with another search method. |  | ||||||
| 		return true, nil //nolint:nilerr |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if domain == "" { | 	if domain == "" { | ||||||
| 		// No error, but no domain set. That means the query | 		// No error, but no domain set. That means the query | ||||||
| 		// looked like '@someone' which is not an exact search. | 		// looked like '@someone' which is not an exact search. | ||||||
| 		// Try to search for any accounts that match the query | 		// Try to search for any accounts that match the query | ||||||
| 		// string, and let the caller know they should stop. | 		// string, and let the caller know they should stop. | ||||||
| 		return false, p.accountsByText( | 		return p.accountsByText( | ||||||
| 			ctx, | 			ctx, | ||||||
| 			requestingAccount.ID, | 			requestingAccount.ID, | ||||||
| 			maxID, | 			maxID, | ||||||
|  | @ -355,18 +383,14 @@ func (p *Processor) accountsByNamestring( | ||||||
| 		// Check for semi-expected error types. | 		// Check for semi-expected error types. | ||||||
| 		// On one of these, we can continue. | 		// On one of these, we can continue. | ||||||
| 		if !gtserror.Unretrievable(err) && !gtserror.WrongType(err) { | 		if !gtserror.Unretrievable(err) && !gtserror.WrongType(err) { | ||||||
| 			err = gtserror.Newf("error looking up %s as account: %w", query, err) | 			err = gtserror.Newf("error looking up @%s@%s as account: %w", username, domain, err) | ||||||
| 			return false, gtserror.NewErrorInternalError(err) | 			return gtserror.NewErrorInternalError(err) | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		appendAccount(foundAccount) | 		appendAccount(foundAccount) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Regardless of whether we have a hit at this point, | 	return nil | ||||||
| 	// return false to indicate caller should stop looking; |  | ||||||
| 	// namestrings are a very specific format so it's unlikely |  | ||||||
| 	// the caller was looking for something other than an account. |  | ||||||
| 	return false, nil |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // accountByUsernameDomain looks for one account with the given | // accountByUsernameDomain looks for one account with the given | ||||||
|  | @ -443,38 +467,22 @@ func (p *Processor) accountByUsernameDomain( | ||||||
| func (p *Processor) byURI( | func (p *Processor) byURI( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	requestingAccount *gtsmodel.Account, | 	requestingAccount *gtsmodel.Account, | ||||||
| 	query string, | 	uri *url.URL, | ||||||
| 	queryType string, | 	queryType string, | ||||||
| 	resolve bool, | 	resolve bool, | ||||||
| 	appendAccount func(*gtsmodel.Account), | 	appendAccount func(*gtsmodel.Account), | ||||||
| 	appendStatus func(*gtsmodel.Status), | 	appendStatus func(*gtsmodel.Status), | ||||||
| ) (bool, error) { | ) error { | ||||||
| 	uri, err := url.Parse(query) |  | ||||||
| 	if err != nil { |  | ||||||
| 		// No need to return error; just not a URI |  | ||||||
| 		// we can search with. Caller should keep |  | ||||||
| 		// looking with another search method. |  | ||||||
| 		return true, nil //nolint:nilerr |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if !(uri.Scheme == "https" || uri.Scheme == "http") { |  | ||||||
| 		// This might just be a weirdly-parsed URI, |  | ||||||
| 		// since Go's url package tends to be a bit |  | ||||||
| 		// trigger-happy when deciding things are URIs. |  | ||||||
| 		// Indicate caller should keep looking. |  | ||||||
| 		return true, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	blocked, err := p.state.DB.IsURIBlocked(ctx, uri) | 	blocked, err := p.state.DB.IsURIBlocked(ctx, uri) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		err = gtserror.Newf("error checking domain block: %w", err) | 		err = gtserror.Newf("error checking domain block: %w", err) | ||||||
| 		return false, gtserror.NewErrorInternalError(err) | 		return gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if blocked { | 	if blocked { | ||||||
| 		// Don't search for blocked domains. | 		// Don't search for | ||||||
| 		// Caller should stop looking. | 		// blocked domains. | ||||||
| 		return false, nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if includeAccounts(queryType) { | 	if includeAccounts(queryType) { | ||||||
|  | @ -485,14 +493,13 @@ func (p *Processor) byURI( | ||||||
| 			// On one of these, we can continue. | 			// On one of these, we can continue. | ||||||
| 			if !gtserror.Unretrievable(err) && !gtserror.WrongType(err) { | 			if !gtserror.Unretrievable(err) && !gtserror.WrongType(err) { | ||||||
| 				err = gtserror.Newf("error looking up %s as account: %w", uri, err) | 				err = gtserror.Newf("error looking up %s as account: %w", uri, err) | ||||||
| 				return false, gtserror.NewErrorInternalError(err) | 				return gtserror.NewErrorInternalError(err) | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			// Hit; return false to indicate caller should | 			// Hit! Return early since it's extremely unlikely | ||||||
| 			// stop looking, since it's extremely unlikely |  | ||||||
| 			// a status and an account will have the same URL. | 			// a status and an account will have the same URL. | ||||||
| 			appendAccount(foundAccount) | 			appendAccount(foundAccount) | ||||||
| 			return false, nil | 			return nil | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -504,20 +511,19 @@ func (p *Processor) byURI( | ||||||
| 			// On one of these, we can continue. | 			// On one of these, we can continue. | ||||||
| 			if !gtserror.Unretrievable(err) && !gtserror.WrongType(err) { | 			if !gtserror.Unretrievable(err) && !gtserror.WrongType(err) { | ||||||
| 				err = gtserror.Newf("error looking up %s as status: %w", uri, err) | 				err = gtserror.Newf("error looking up %s as status: %w", uri, err) | ||||||
| 				return false, gtserror.NewErrorInternalError(err) | 				return gtserror.NewErrorInternalError(err) | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			// Hit; return false to indicate caller should | 			// Hit! Return early since it's extremely unlikely | ||||||
| 			// stop looking, since it's extremely unlikely |  | ||||||
| 			// a status and an account will have the same URL. | 			// a status and an account will have the same URL. | ||||||
| 			appendStatus(foundStatus) | 			appendStatus(foundStatus) | ||||||
| 			return false, nil | 			return nil | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// No errors, but no hits either; since this | 	// No errors, but no hits | ||||||
| 	// was a URI, caller should stop looking. | 	// either; that's fine. | ||||||
| 	return false, nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // accountByURI looks for one account with the given URI. | // accountByURI looks for one account with the given URI. | ||||||
|  |  | ||||||
|  | @ -51,6 +51,11 @@ func (p *Processor) Lookup( | ||||||
| 	// accident. | 	// accident. | ||||||
| 	const includeInstanceAccounts = true | 	const includeInstanceAccounts = true | ||||||
| 
 | 
 | ||||||
|  | 	// Since lookup is always for a specific | ||||||
|  | 	// account, it's fine to include a blocked | ||||||
|  | 	// account in the results. | ||||||
|  | 	const includeBlockedAccounts = true | ||||||
|  | 
 | ||||||
| 	// Validate query. | 	// Validate query. | ||||||
| 	query = strings.TrimSpace(query) | 	query = strings.TrimSpace(query) | ||||||
| 	if query == "" { | 	if query == "" { | ||||||
|  | @ -108,6 +113,7 @@ func (p *Processor) Lookup( | ||||||
| 		requestingAccount, | 		requestingAccount, | ||||||
| 		[]*gtsmodel.Account{account}, | 		[]*gtsmodel.Account{account}, | ||||||
| 		includeInstanceAccounts, | 		includeInstanceAccounts, | ||||||
|  | 		includeBlockedAccounts, | ||||||
| 	) | 	) | ||||||
| 	if errWithCode != nil { | 	if errWithCode != nil { | ||||||
| 		return nil, errWithCode | 		return nil, errWithCode | ||||||
|  |  | ||||||
|  | @ -49,6 +49,7 @@ func (p *Processor) packageAccounts( | ||||||
| 	requestingAccount *gtsmodel.Account, | 	requestingAccount *gtsmodel.Account, | ||||||
| 	accounts []*gtsmodel.Account, | 	accounts []*gtsmodel.Account, | ||||||
| 	includeInstanceAccounts bool, | 	includeInstanceAccounts bool, | ||||||
|  | 	includeBlockedAccounts bool, | ||||||
| ) ([]*apimodel.Account, gtserror.WithCode) { | ) ([]*apimodel.Account, gtserror.WithCode) { | ||||||
| 	apiAccounts := make([]*apimodel.Account, 0, len(accounts)) | 	apiAccounts := make([]*apimodel.Account, 0, len(accounts)) | ||||||
| 
 | 
 | ||||||
|  | @ -58,19 +59,26 @@ func (p *Processor) packageAccounts( | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Ensure requester can see result account. | 		// Check if block exists between searcher and searchee. | ||||||
| 		visible, err := p.filter.AccountVisible(ctx, requestingAccount, account) | 		blocked, err := p.state.DB.IsEitherBlocked(ctx, requestingAccount.ID, account.ID) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			err = gtserror.Newf("error checking visibility of account %s for account %s: %w", account.ID, requestingAccount.ID, err) | 			err = gtserror.Newf("error checking block between searching account %s and searched account %s: %w", requestingAccount.ID, account.ID, err) | ||||||
| 			return nil, gtserror.NewErrorInternalError(err) | 			return nil, gtserror.NewErrorInternalError(err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if !visible { | 		if blocked && !includeBlockedAccounts { | ||||||
| 			log.Debugf(ctx, "account %s is not visible to account %s, skipping this result", account.ID, requestingAccount.ID) | 			// Don't include | ||||||
|  | 			// this result. | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		apiAccount, err := p.converter.AccountToAPIAccountPublic(ctx, account) | 		var apiAccount *apimodel.Account | ||||||
|  | 		if blocked { | ||||||
|  | 			apiAccount, err = p.converter.AccountToAPIAccountBlocked(ctx, account) | ||||||
|  | 		} else { | ||||||
|  | 			apiAccount, err = p.converter.AccountToAPIAccountPublic(ctx, account) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Debugf(ctx, "skipping account %s because it couldn't be converted to its api representation: %s", account.ID, err) | 			log.Debugf(ctx, "skipping account %s because it couldn't be converted to its api representation: %s", account.ID, err) | ||||||
| 			continue | 			continue | ||||||
|  | @ -171,8 +179,15 @@ func (p *Processor) packageSearchResult( | ||||||
| 	tags []*gtsmodel.Tag, | 	tags []*gtsmodel.Tag, | ||||||
| 	v1 bool, | 	v1 bool, | ||||||
| 	includeInstanceAccounts bool, | 	includeInstanceAccounts bool, | ||||||
|  | 	includeBlockedAccounts bool, | ||||||
| ) (*apimodel.SearchResult, gtserror.WithCode) { | ) (*apimodel.SearchResult, gtserror.WithCode) { | ||||||
| 	apiAccounts, errWithCode := p.packageAccounts(ctx, requestingAccount, accounts, includeInstanceAccounts) | 	apiAccounts, errWithCode := p.packageAccounts( | ||||||
|  | 		ctx, | ||||||
|  | 		requestingAccount, | ||||||
|  | 		accounts, | ||||||
|  | 		includeInstanceAccounts, | ||||||
|  | 		includeBlockedAccounts, | ||||||
|  | 	) | ||||||
| 	if errWithCode != nil { | 	if errWithCode != nil { | ||||||
| 		return nil, errWithCode | 		return nil, errWithCode | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -277,7 +277,7 @@ func (c *Converter) AccountToAPIAccountBlocked(ctx context.Context, a *gtsmodel. | ||||||
| 		// de-punify it just in case. | 		// de-punify it just in case. | ||||||
| 		d, err := util.DePunify(a.Domain) | 		d, err := util.DePunify(a.Domain) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, fmt.Errorf("AccountToAPIAccountBlocked: error de-punifying domain %s for account id %s: %w", a.Domain, a.ID, err) | 			return nil, gtserror.Newf("error de-punifying domain %s for account id %s: %w", a.Domain, a.ID, err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		acct = a.Username + "@" + d | 		acct = a.Username + "@" + d | ||||||
|  | @ -288,7 +288,7 @@ func (c *Converter) AccountToAPIAccountBlocked(ctx context.Context, a *gtsmodel. | ||||||
| 		if !a.IsInstance() { | 		if !a.IsInstance() { | ||||||
| 			user, err := c.state.DB.GetUserByAccountID(ctx, a.ID) | 			user, err := c.state.DB.GetUserByAccountID(ctx, a.ID) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, fmt.Errorf("AccountToAPIAccountPublic: error getting user from database for account id %s: %w", a.ID, err) | 				return nil, gtserror.Newf("error getting user from database for account id %s: %w", a.ID, err) | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			switch { | 			switch { | ||||||
|  | @ -304,17 +304,25 @@ func (c *Converter) AccountToAPIAccountBlocked(ctx context.Context, a *gtsmodel. | ||||||
| 		acct = a.Username // omit domain | 		acct = a.Username // omit domain | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return &apimodel.Account{ | 	account := &apimodel.Account{ | ||||||
| 		ID:        a.ID, | 		ID:        a.ID, | ||||||
| 		Username:  a.Username, | 		Username:  a.Username, | ||||||
| 		Acct:      acct, | 		Acct:      acct, | ||||||
| 		DisplayName: a.DisplayName, |  | ||||||
| 		Bot:       *a.Bot, | 		Bot:       *a.Bot, | ||||||
| 		CreatedAt: util.FormatISO8601(a.CreatedAt), | 		CreatedAt: util.FormatISO8601(a.CreatedAt), | ||||||
| 		URL:       a.URL, | 		URL:       a.URL, | ||||||
| 		Suspended: !a.SuspendedAt.IsZero(), | 		Suspended: !a.SuspendedAt.IsZero(), | ||||||
| 		Role:      role, | 		Role:      role, | ||||||
| 	}, nil | 	} | ||||||
|  | 
 | ||||||
|  | 	// Don't show the account's actual | ||||||
|  | 	// avatar+header since it may be | ||||||
|  | 	// upsetting to the blocker. Just | ||||||
|  | 	// show generic avatar+header instead. | ||||||
|  | 	c.ensureAvatar(account) | ||||||
|  | 	c.ensureHeader(account) | ||||||
|  | 
 | ||||||
|  | 	return account, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Converter) AccountToAdminAPIAccount(ctx context.Context, a *gtsmodel.Account) (*apimodel.AdminAccountInfo, error) { | func (c *Converter) AccountToAdminAPIAccount(ctx context.Context, a *gtsmodel.Account) (*apimodel.AdminAccountInfo, error) { | ||||||
|  |  | ||||||
|  | @ -313,8 +313,8 @@ func (suite *InternalToFrontendTestSuite) TestLocalInstanceAccountToFrontendBloc | ||||||
|   "url": "http://localhost:8080/@localhost:8080", |   "url": "http://localhost:8080/@localhost:8080", | ||||||
|   "avatar": "", |   "avatar": "", | ||||||
|   "avatar_static": "", |   "avatar_static": "", | ||||||
|   "header": "", |   "header": "http://localhost:8080/assets/default_header.png", | ||||||
|   "header_static": "", |   "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|   "followers_count": 0, |   "followers_count": 0, | ||||||
|   "following_count": 0, |   "following_count": 0, | ||||||
|   "statuses_count": 0, |   "statuses_count": 0, | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue