[feature] Enable federation in/out of profile PropertyValue fields (#1722)

Co-authored-by: kim <grufwub@gmail.com>
Co-authored-by: kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>
This commit is contained in:
tobi 2023-05-09 12:16:10 +02:00 committed by GitHub
commit 0e29f1f5bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
180 changed files with 9278 additions and 1550 deletions

View file

@ -38,7 +38,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/transport"
)
func (d *deref) GetAccountByURI(ctx context.Context, requestUser string, uri *url.URL, block bool) (*gtsmodel.Account, error) {
func (d *deref) GetAccountByURI(ctx context.Context, requestUser string, uri *url.URL) (*gtsmodel.Account, error) {
var (
account *gtsmodel.Account
uriStr = uri.String()
@ -70,11 +70,11 @@ func (d *deref) GetAccountByURI(ctx context.Context, requestUser string, uri *ur
ID: id.NewULID(),
Domain: uri.Host,
URI: uriStr,
}, false, true)
}, d.defaultFetchLatest, false)
}
// Try to update existing account model
enriched, err := d.enrichAccount(ctx, requestUser, uri, account, false, block)
enriched, err := d.enrichAccount(ctx, requestUser, uri, account, d.defaultFetchLatest, false)
if err != nil {
log.Errorf(ctx, "error enriching remote account: %v", err)
return account, nil // fall back to returning existing
@ -83,7 +83,7 @@ func (d *deref) GetAccountByURI(ctx context.Context, requestUser string, uri *ur
return enriched, nil
}
func (d *deref) GetAccountByUsernameDomain(ctx context.Context, requestUser string, username string, domain string, block bool) (*gtsmodel.Account, error) {
func (d *deref) GetAccountByUsernameDomain(ctx context.Context, requestUser string, username string, domain string) (*gtsmodel.Account, error) {
if domain == config.GetHost() || domain == config.GetAccountDomain() {
// We do local lookups using an empty domain,
// else it will fail the db search below.
@ -99,19 +99,24 @@ func (d *deref) GetAccountByUsernameDomain(ctx context.Context, requestUser stri
if account == nil {
// Check for failed local lookup.
if domain == "" {
return nil, NewErrNotRetrievable(err) // will be db.ErrNoEntries
return nil, NewErrNotRetrievable(err) // wrapped err will be db.ErrNoEntries
}
// Create and pass-through a new bare-bones model for dereferencing.
return d.enrichAccount(ctx, requestUser, nil, &gtsmodel.Account{
account = &gtsmodel.Account{
ID: id.NewULID(),
Username: username,
Domain: domain,
}, false, true)
}
// There's no known account to fall back on,
// so return error if we can't enrich account.
return d.enrichAccount(ctx, requestUser, nil, account, d.defaultFetchLatest, false)
}
// Try to update existing account model
enriched, err := d.enrichAccount(ctx, requestUser, nil, account, false, block)
// We knew about this account already;
// try to update existing account model.
enriched, err := d.enrichAccount(ctx, requestUser, nil, account, d.defaultFetchLatest, false)
if err != nil {
log.Errorf(ctx, "error enriching account from remote: %v", err)
return account, nil // fall back to returning unchanged existing account model
@ -120,12 +125,62 @@ func (d *deref) GetAccountByUsernameDomain(ctx context.Context, requestUser stri
return enriched, nil
}
func (d *deref) UpdateAccount(ctx context.Context, requestUser string, account *gtsmodel.Account, force bool) (*gtsmodel.Account, error) {
return d.enrichAccount(ctx, requestUser, nil, account, force, false)
func (d *deref) RefreshAccount(ctx context.Context, requestUser string, accountable ap.Accountable, account *gtsmodel.Account) (*gtsmodel.Account, error) {
// To avoid unnecessarily refetching multiple times from remote,
// we can just pass in the Accountable object that we received,
// if it was defined. If not, fall back to default fetch func.
var f fetchLatest
if accountable != nil {
f = func(
_ context.Context,
_ transport.Transport,
_ *url.URL,
_ string,
) (ap.Accountable, *gtsmodel.Account, error) {
return accountable, account, nil
}
} else {
f = d.defaultFetchLatest
}
// Set 'force' to 'true' to always fetch latest media etc.
return d.enrichAccount(ctx, requestUser, nil, account, f, true)
}
// fetchLatest defines a function for using a transport and uri to fetch the fetchLatest
// version of an account (and its AP representation) from a remote instance.
type fetchLatest func(ctx context.Context, transport transport.Transport, uri *url.URL, accountDomain string) (ap.Accountable, *gtsmodel.Account, error)
// defaultFetchLatest deduplicates latest fetching code that is used in several
// different functions. It simply calls the remote uri using the given transport,
// parses a returned AP representation into an account, and then returns both.
func (d *deref) defaultFetchLatest(ctx context.Context, transport transport.Transport, uri *url.URL, accountDomain string) (ap.Accountable, *gtsmodel.Account, error) {
// Dereference this account to get the latest available.
apubAcc, err := d.dereferenceAccountable(ctx, transport, uri)
if err != nil {
return nil, nil, fmt.Errorf("error dereferencing account %s: %w", uri, err)
}
// Convert the dereferenced AP account object to our GTS model.
latestAcc, err := d.typeConverter.ASRepresentationToAccount(
ctx, apubAcc, accountDomain,
)
if err != nil {
return nil, nil, fmt.Errorf("error converting accountable to gts model for account %s: %w", uri, err)
}
return apubAcc, latestAcc, nil
}
// enrichAccount will ensure the given account is the most up-to-date model of the account, re-webfingering and re-dereferencing if necessary.
func (d *deref) enrichAccount(ctx context.Context, requestUser string, uri *url.URL, account *gtsmodel.Account, force, block bool) (*gtsmodel.Account, error) {
func (d *deref) enrichAccount(
ctx context.Context,
requestUser string,
uri *url.URL,
account *gtsmodel.Account,
f fetchLatest,
force bool,
) (*gtsmodel.Account, error) {
if account.IsLocal() {
// Can't update local accounts.
return account, nil
@ -205,18 +260,10 @@ func (d *deref) enrichAccount(ctx context.Context, requestUser string, uri *url.
d.startHandshake(requestUser, uri)
defer d.stopHandshake(requestUser, uri)
// Dereference this account to get the latest available.
apubAcc, err := d.dereferenceAccountable(ctx, transport, uri)
// Fetch latest version of the account, dereferencing if necessary.
apubAcc, latestAcc, err := f(ctx, transport, uri, account.Domain)
if err != nil {
return nil, fmt.Errorf("enrichAccount: error dereferencing account %s: %w", uri, err)
}
// Convert the dereferenced AP account object to our GTS model.
latestAcc, err := d.typeConverter.ASRepresentationToAccount(
ctx, apubAcc, account.Domain,
)
if err != nil {
return nil, fmt.Errorf("enrichAccount: error converting accountable to gts model for account %s: %w", uri, err)
return nil, fmt.Errorf("enrichAccount: error calling fetchLatest function: %w", err)
}
if account.Username == "" {
@ -256,11 +303,11 @@ func (d *deref) enrichAccount(ctx context.Context, requestUser string, uri *url.
latestAcc.ID = account.ID
latestAcc.FetchedAt = time.Now()
// Use the existing account media attachments by default.
// Reuse the existing account media attachments by default.
latestAcc.AvatarMediaAttachmentID = account.AvatarMediaAttachmentID
latestAcc.HeaderMediaAttachmentID = account.HeaderMediaAttachmentID
if latestAcc.AvatarRemoteURL != account.AvatarRemoteURL {
if force || (latestAcc.AvatarRemoteURL != account.AvatarRemoteURL) {
// Reset the avatar media ID (handles removed).
latestAcc.AvatarMediaAttachmentID = ""
@ -281,7 +328,7 @@ func (d *deref) enrichAccount(ctx context.Context, requestUser string, uri *url.
}
}
if latestAcc.HeaderRemoteURL != account.HeaderRemoteURL {
if force || (latestAcc.HeaderRemoteURL != account.HeaderRemoteURL) {
// Reset the header media ID (handles removed).
latestAcc.HeaderMediaAttachmentID = ""

View file

@ -41,7 +41,6 @@ func (suite *AccountTestSuite) TestDereferenceGroup() {
context.Background(),
fetchingAccount.Username,
groupURL,
false,
)
suite.NoError(err)
suite.NotNil(group)
@ -66,7 +65,6 @@ func (suite *AccountTestSuite) TestDereferenceService() {
context.Background(),
fetchingAccount.Username,
serviceURL,
false,
)
suite.NoError(err)
suite.NotNil(service)
@ -99,7 +97,6 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountAsRemoteURL() {
context.Background(),
fetchingAccount.Username,
testrig.URLMustParse(targetAccount.URI),
false,
)
suite.NoError(err)
suite.NotNil(fetchedAccount)
@ -119,7 +116,6 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountAsRemoteURLNoSharedInb
context.Background(),
fetchingAccount.Username,
testrig.URLMustParse(targetAccount.URI),
false,
)
suite.NoError(err)
suite.NotNil(fetchedAccount)
@ -134,7 +130,6 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountAsUsername() {
context.Background(),
fetchingAccount.Username,
testrig.URLMustParse(targetAccount.URI),
false,
)
suite.NoError(err)
suite.NotNil(fetchedAccount)
@ -149,7 +144,6 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountAsUsernameDomain() {
context.Background(),
fetchingAccount.Username,
testrig.URLMustParse(targetAccount.URI),
false,
)
suite.NoError(err)
suite.NotNil(fetchedAccount)
@ -165,7 +159,6 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountAsUsernameDomainAndURL
fetchingAccount.Username,
targetAccount.Username,
config.GetHost(),
false,
)
suite.NoError(err)
suite.NotNil(fetchedAccount)
@ -180,7 +173,6 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountWithUnknownUsername()
fetchingAccount.Username,
"thisaccountdoesnotexist",
config.GetHost(),
false,
)
var errNotRetrievable *dereferencing.ErrNotRetrievable
suite.ErrorAs(err, &errNotRetrievable)
@ -196,7 +188,6 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountWithUnknownUsernameDom
fetchingAccount.Username,
"thisaccountdoesnotexist",
"localhost:8080",
false,
)
var errNotRetrievable *dereferencing.ErrNotRetrievable
suite.ErrorAs(err, &errNotRetrievable)
@ -211,7 +202,6 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountWithUnknownUserURI() {
context.Background(),
fetchingAccount.Username,
testrig.URLMustParse("http://localhost:8080/users/thisaccountdoesnotexist"),
false,
)
var errNotRetrievable *dereferencing.ErrNotRetrievable
suite.ErrorAs(err, &errNotRetrievable)

View file

@ -35,14 +35,15 @@ import (
type Dereferencer interface {
// GetAccountByURI will attempt to fetch an account by its URI, first checking the database and in the case of a remote account will either check the
// last_fetched (and updating if beyond fetch interval) or dereferencing for the first-time if this remote account has never been encountered before.
GetAccountByURI(ctx context.Context, requestUser string, uri *url.URL, block bool) (*gtsmodel.Account, error)
GetAccountByURI(ctx context.Context, requestUser string, uri *url.URL) (*gtsmodel.Account, error)
// GetAccountByUsernameDomain will attempt to fetch an account by username@domain, first checking the database and in the case of a remote account will either
// check the last_fetched (and updating if beyond fetch interval) or dereferencing for the first-time if this remote account has never been encountered before.
GetAccountByUsernameDomain(ctx context.Context, requestUser string, username string, domain string, block bool) (*gtsmodel.Account, error)
GetAccountByUsernameDomain(ctx context.Context, requestUser string, username string, domain string) (*gtsmodel.Account, error)
// UpdateAccount updates the given account if last_fetched is beyond fetch interval (or if force is set). An updated account model is returned, any media fetching is done async.
UpdateAccount(ctx context.Context, requestUser string, account *gtsmodel.Account, force bool) (*gtsmodel.Account, error)
// RefreshAccount forces a refresh of the given account by fetching the current/latest state of the account from the remote instance.
// An updated account model is returned, but not yet inserted/updated in the database; this is the caller's responsibility.
RefreshAccount(ctx context.Context, requestUser string, accountable ap.Accountable, account *gtsmodel.Account) (*gtsmodel.Account, error)
GetStatus(ctx context.Context, username string, remoteStatusID *url.URL, refetch, includeParent bool) (*gtsmodel.Status, ap.Statusable, error)

View file

@ -125,7 +125,7 @@ func (d *deref) GetStatus(ctx context.Context, username string, statusURI *url.U
}
// we need to get the author of the status else we can't serialize it properly
if _, err = d.GetAccountByURI(ctx, username, accountURI, true); err != nil {
if _, err = d.GetAccountByURI(ctx, username, accountURI); err != nil {
return nil, nil, newErrOther(fmt.Errorf("GetRemoteStatus: couldn't get status author: %s", err))
}
@ -278,7 +278,7 @@ func (d *deref) populateStatusMentions(ctx context.Context, status *gtsmodel.Sta
if targetAccount == nil {
// we didn't find the account in our database already
// check if we can get the account remotely (dereference it)
if a, err := d.GetAccountByURI(ctx, requestingUsername, targetAccountURI, false); err != nil {
if a, err := d.GetAccountByURI(ctx, requestingUsername, targetAccountURI); err != nil {
errs = append(errs, err.Error())
} else {
log.Debugf(ctx, "got target account %s with id %s through GetRemoteAccount", targetAccountURI, a.ID)