mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-11-12 15:07:30 -06:00
[bugfix] Don't return Account or Status if new and dereferencing failed, other small fixes (#2563)
* tidy up account, status, webfingering logic a wee bit * go fmt * invert published check * alter resp initialization * get Published from account in typeutils * don't instantiate error for no darn good reason * shadow err * don't repeat error codes in wrapped errors * don't wrap error unnecessarily
This commit is contained in:
parent
ad6f75694e
commit
a3aa6042d7
12 changed files with 461 additions and 210 deletions
|
|
@ -52,7 +52,7 @@ func accountUpToDate(account *gtsmodel.Account, force bool) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
if !account.CreatedAt.IsZero() && account.IsInstance() {
|
||||
if account.IsInstance() && !account.IsNew() {
|
||||
// Existing instance account. No need for update.
|
||||
return true
|
||||
}
|
||||
|
|
@ -232,8 +232,8 @@ func (d *Dereferencer) getAccountByUsernameDomain(
|
|||
// Create and pass-through a new bare-bones model for dereferencing.
|
||||
account, accountable, err := d.enrichAccountSafely(ctx, requestUser, nil, >smodel.Account{
|
||||
ID: id.NewULID(),
|
||||
Username: username,
|
||||
Domain: domain,
|
||||
Username: username,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
|
@ -372,17 +372,18 @@ func (d *Dereferencer) enrichAccountSafely(
|
|||
|
||||
// By default use account.URI
|
||||
// as the per-URI deref lock.
|
||||
uriStr := account.URI
|
||||
|
||||
if uriStr == "" {
|
||||
var lockKey string
|
||||
if account.URI != "" {
|
||||
lockKey = account.URI
|
||||
} else {
|
||||
// No URI is set yet, instead generate a faux-one from user+domain.
|
||||
uriStr = "https://" + account.Domain + "/user/" + account.Username
|
||||
lockKey = "https://" + account.Domain + "/users/" + account.Username
|
||||
}
|
||||
|
||||
// Acquire per-URI deref lock, wraping unlock
|
||||
// to safely defer in case of panic, while still
|
||||
// performing more granular unlocks when needed.
|
||||
unlock := d.state.FedLocks.Lock(uriStr)
|
||||
unlock := d.state.FedLocks.Lock(lockKey)
|
||||
unlock = doOnce(unlock)
|
||||
defer unlock()
|
||||
|
||||
|
|
@ -394,10 +395,30 @@ func (d *Dereferencer) enrichAccountSafely(
|
|||
accountable,
|
||||
)
|
||||
|
||||
if gtserror.StatusCode(err) >= 400 {
|
||||
// Update fetch-at to slow re-attempts.
|
||||
if code := gtserror.StatusCode(err); code >= 400 {
|
||||
// No matter what, log the error
|
||||
// so instance admins have an idea
|
||||
// why something isn't working.
|
||||
log.Info(ctx, err)
|
||||
|
||||
if account.IsNew() {
|
||||
// This was a new account enrich
|
||||
// attempt which failed before we
|
||||
// got to store it, so we can't
|
||||
// return anything useful.
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// We had this account stored already
|
||||
// before this enrichment attempt.
|
||||
//
|
||||
// Update fetched_at to slow re-attempts
|
||||
// but don't return early. We can still
|
||||
// return the model we had stored already.
|
||||
account.FetchedAt = time.Now()
|
||||
_ = d.state.DB.UpdateAccount(ctx, account, "fetched_at")
|
||||
if err := d.state.DB.UpdateAccount(ctx, account, "fetched_at"); err != nil {
|
||||
log.Errorf(ctx, "error updating account fetched_at: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock now
|
||||
|
|
@ -414,7 +435,7 @@ func (d *Dereferencer) enrichAccountSafely(
|
|||
// in a call to db.Put(Account). Look again in DB by URI.
|
||||
latest, err = d.state.DB.GetAccountByURI(ctx, account.URI)
|
||||
if err != nil {
|
||||
err = gtserror.Newf("error getting account %s from database after race: %w", uriStr, err)
|
||||
err = gtserror.Newf("error getting account %s from database after race: %w", lockKey, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -440,32 +461,44 @@ func (d *Dereferencer) enrichAccount(
|
|||
if account.Username != "" {
|
||||
// A username was provided so we can attempt a webfinger, this ensures up-to-date accountdomain info.
|
||||
accDomain, accURI, err := d.fingerRemoteAccount(ctx, tsport, account.Username, account.Domain)
|
||||
if err != nil {
|
||||
if account.URI == "" {
|
||||
// this is a new account (to us) with username@domain
|
||||
// but failed webfinger, nothing more we can do.
|
||||
err := gtserror.Newf("error webfingering account: %w", err)
|
||||
return nil, nil, gtserror.SetUnretrievable(err)
|
||||
switch {
|
||||
|
||||
case err != nil && account.URI == "":
|
||||
// This is a new account (to us) with username@domain
|
||||
// but failed webfinger, nothing more we can do.
|
||||
err := gtserror.Newf("error webfingering account: %w", err)
|
||||
return nil, nil, gtserror.SetUnretrievable(err)
|
||||
|
||||
case err != nil:
|
||||
// Simply log this error and move on,
|
||||
// we already have an account URI.
|
||||
log.Errorf(ctx,
|
||||
"error webfingering[1] remote account %s@%s: %v",
|
||||
account.Username, account.Domain, err,
|
||||
)
|
||||
|
||||
case err == nil && account.Domain != accDomain:
|
||||
// After webfinger, we now have correct account domain from which we can do a final DB check.
|
||||
alreadyAcct, err := d.state.DB.GetAccountByUsernameDomain(ctx, account.Username, accDomain)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return nil, nil, gtserror.Newf("db err looking for account again after webfinger: %w", err)
|
||||
}
|
||||
|
||||
// Simply log this error and move on, we already have an account URI.
|
||||
log.Errorf(ctx, "error webfingering[1] remote account %s@%s: %v", account.Username, account.Domain, err)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if account.Domain != accDomain {
|
||||
// After webfinger, we now have correct account domain from which we can do a final DB check.
|
||||
alreadyAccount, err := d.state.DB.GetAccountByUsernameDomain(ctx, account.Username, accDomain)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return nil, nil, gtserror.Newf("db err looking for account again after webfinger: %w", err)
|
||||
}
|
||||
|
||||
if alreadyAccount != nil {
|
||||
// Enrich existing account.
|
||||
account = alreadyAccount
|
||||
}
|
||||
if alreadyAcct != nil {
|
||||
// We had this account stored under
|
||||
// the discovered accountDomain.
|
||||
// Proceed with this account.
|
||||
account = alreadyAcct
|
||||
}
|
||||
|
||||
// Whether we had the account or not, we
|
||||
// now have webfinger info relevant to the
|
||||
// account, so fallthrough to set webfinger
|
||||
// info on either the account we just found,
|
||||
// or the stub account we were passed.
|
||||
fallthrough
|
||||
|
||||
case err == nil:
|
||||
// Update account with latest info.
|
||||
account.URI = accURI.String()
|
||||
account.Domain = accDomain
|
||||
|
|
@ -474,13 +507,31 @@ func (d *Dereferencer) enrichAccount(
|
|||
}
|
||||
|
||||
if uri == nil {
|
||||
// No URI provided / found, must parse from account.
|
||||
// No URI provided / found,
|
||||
// must parse from account.
|
||||
uri, err = url.Parse(account.URI)
|
||||
if err != nil {
|
||||
return nil, nil, gtserror.Newf("invalid uri %q: %w", account.URI, err)
|
||||
return nil, nil, gtserror.Newf(
|
||||
"invalid uri %q: %w",
|
||||
account.URI, gtserror.SetUnretrievable(err),
|
||||
)
|
||||
}
|
||||
|
||||
if uri.Scheme != "http" && uri.Scheme != "https" {
|
||||
err = errors.New("account URI scheme must be http or https")
|
||||
return nil, nil, gtserror.Newf(
|
||||
"invalid uri %q: %w",
|
||||
account.URI, gtserror.SetUnretrievable(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
BY THIS POINT we must have an account URI set,
|
||||
either provided, pinned to the account, or
|
||||
obtained via webfinger call.
|
||||
*/
|
||||
|
||||
// Check whether this account URI is a blocked domain / subdomain.
|
||||
if blocked, err := d.state.DB.IsDomainBlocked(ctx, uri.Host); err != nil {
|
||||
return nil, nil, gtserror.Newf("error checking blocked domain: %w", err)
|
||||
|
|
@ -493,27 +544,45 @@ func (d *Dereferencer) enrichAccount(
|
|||
defer d.stopHandshake(requestUser, uri)
|
||||
|
||||
if apubAcc == nil {
|
||||
// We were not given any (partial) ActivityPub
|
||||
// version of this account as a parameter.
|
||||
// Dereference latest version of the account.
|
||||
b, err := tsport.Dereference(ctx, uri)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error deferencing %s: %w", uri, err)
|
||||
err := gtserror.Newf("error dereferencing %s: %w", uri, err)
|
||||
return nil, nil, gtserror.SetUnretrievable(err)
|
||||
}
|
||||
|
||||
// Attempt to resolve ActivityPub acc from data.
|
||||
apubAcc, err = ap.ResolveAccountable(ctx, b)
|
||||
if err != nil {
|
||||
return nil, nil, gtserror.Newf("error resolving accountable from data for account %s: %w", uri, err)
|
||||
// ResolveAccountable will set Unretrievable/WrongType
|
||||
// on the returned error, so we don't need to do it here.
|
||||
err = gtserror.Newf("error resolving accountable from data for account %s: %w", uri, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
BY THIS POINT we must have the ActivityPub
|
||||
representation of the account, either provided,
|
||||
or obtained via a dereference call.
|
||||
*/
|
||||
|
||||
// Convert the dereferenced AP account object to our GTS model.
|
||||
//
|
||||
// We put this in the variable latestAcc because we might want
|
||||
// to compare the provided account model with this fresh version,
|
||||
// in order to check if anything changed since we last saw it.
|
||||
latestAcc, err := d.converter.ASRepresentationToAccount(ctx,
|
||||
apubAcc,
|
||||
account.Domain,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, gtserror.Newf("error converting accountable to gts model for account %s: %w", uri, err)
|
||||
// ASRepresentationToAccount will set Malformed on the
|
||||
// returned error, so we don't need to do it here.
|
||||
err = gtserror.Newf("error converting accountable to gts model for account %s: %w", uri, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if account.Username == "" {
|
||||
|
|
@ -528,14 +597,15 @@ func (d *Dereferencer) enrichAccount(
|
|||
// from example.org to somewhere.else: we want to take somewhere.else
|
||||
// as the accountDomain then, not the example.org we were redirected from.
|
||||
|
||||
// Assume the host from the returned ActivityPub representation.
|
||||
idProp := apubAcc.GetJSONLDId()
|
||||
if idProp == nil || !idProp.IsIRI() {
|
||||
// Assume the host from the returned
|
||||
// ActivityPub representation.
|
||||
id := ap.GetJSONLDId(apubAcc)
|
||||
if id == nil {
|
||||
return nil, nil, gtserror.New("no id property found on person, or id was not an iri")
|
||||
}
|
||||
|
||||
// Get IRI host value.
|
||||
accHost := idProp.GetIRI().Host
|
||||
accHost := id.Host
|
||||
|
||||
latestAcc.Domain, _, err = d.fingerRemoteAccount(ctx,
|
||||
tsport,
|
||||
|
|
@ -553,7 +623,7 @@ func (d *Dereferencer) enrichAccount(
|
|||
}
|
||||
|
||||
if latestAcc.Domain == "" {
|
||||
// ensure we have a domain set by this point,
|
||||
// Ensure we have a domain set by this point,
|
||||
// otherwise it gets stored as a local user!
|
||||
//
|
||||
// TODO: there is probably a more granular way
|
||||
|
|
@ -562,7 +632,16 @@ func (d *Dereferencer) enrichAccount(
|
|||
return nil, nil, gtserror.Newf("empty domain for %s", uri)
|
||||
}
|
||||
|
||||
// Ensure ID is set and update fetch time.
|
||||
/*
|
||||
BY THIS POINT we have more or less a fullly-formed
|
||||
representation of the target account, derived from
|
||||
a combination of webfinger lookups and dereferencing.
|
||||
Further fetching beyond this point is for peripheral
|
||||
things like account avatar, header, emojis.
|
||||
*/
|
||||
|
||||
// Ensure internal db ID is
|
||||
// set and update fetch time.
|
||||
latestAcc.ID = account.ID
|
||||
latestAcc.FetchedAt = time.Now()
|
||||
|
||||
|
|
@ -581,12 +660,14 @@ func (d *Dereferencer) enrichAccount(
|
|||
log.Errorf(ctx, "error fetching remote emojis for account %s: %v", uri, err)
|
||||
}
|
||||
|
||||
if account.CreatedAt.IsZero() {
|
||||
// CreatedAt will be zero if no local copy was
|
||||
// found in one of the GetAccountBy___() functions.
|
||||
//
|
||||
// Set time of creation from the last-fetched date.
|
||||
latestAcc.CreatedAt = latestAcc.FetchedAt
|
||||
if account.IsNew() {
|
||||
// Prefer published/created time from
|
||||
// apubAcc, fall back to FetchedAt value.
|
||||
if latestAcc.CreatedAt.IsZero() {
|
||||
latestAcc.CreatedAt = latestAcc.FetchedAt
|
||||
}
|
||||
|
||||
// Set time of update from the last-fetched date.
|
||||
latestAcc.UpdatedAt = latestAcc.FetchedAt
|
||||
|
||||
// This is new, put it in the database.
|
||||
|
|
@ -595,11 +676,16 @@ func (d *Dereferencer) enrichAccount(
|
|||
return nil, nil, gtserror.Newf("error putting in database: %w", err)
|
||||
}
|
||||
} else {
|
||||
// Prefer published time from apubAcc,
|
||||
// fall back to previous stored value.
|
||||
if latestAcc.CreatedAt.IsZero() {
|
||||
latestAcc.CreatedAt = account.CreatedAt
|
||||
}
|
||||
|
||||
// Set time of update from the last-fetched date.
|
||||
latestAcc.UpdatedAt = latestAcc.FetchedAt
|
||||
|
||||
// Use existing account values.
|
||||
latestAcc.CreatedAt = account.CreatedAt
|
||||
// Carry over existing account language.
|
||||
latestAcc.Language = account.Language
|
||||
|
||||
// This is an existing account, update the model in the database.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue