Merge branch 'main' into 2fa

This commit is contained in:
tobi 2025-04-07 12:57:02 +02:00
commit c6c212fb81
271 changed files with 7226 additions and 11019 deletions

View file

@ -107,9 +107,14 @@ func (p *Processor) Alias(
}
// Ensure we have account dereferenced.
//
// As this comes from user input, allow checking
// by URL to make things easier, not just to an
// exact AP URI (which a user might not even know).
targetAccount, _, err := p.federator.GetAccountByURI(ctx,
account.Username,
newAKA.uri,
true,
)
if err != nil {
err := fmt.Errorf(

View file

@ -528,7 +528,7 @@ func stubbifyAccount(account *gtsmodel.Account, origin string) []string {
account.Fields = nil
account.Note = ""
account.NoteRaw = ""
account.Memorial = util.Ptr(false)
account.MemorializedAt = never
account.AlsoKnownAsURIs = nil
account.MovedToURI = ""
account.Discoverable = util.Ptr(false)
@ -546,7 +546,7 @@ func stubbifyAccount(account *gtsmodel.Account, origin string) []string {
"fields",
"note",
"note_raw",
"memorial",
"memorialized_at",
"also_known_as_uris",
"moved_to_uri",
"discoverable",

View file

@ -64,7 +64,7 @@ func (suite *AccountDeleteTestSuite) TestAccountDeleteLocal() {
suite.Nil(updatedAccount.Fields)
suite.Zero(updatedAccount.Note)
suite.Zero(updatedAccount.NoteRaw)
suite.False(*updatedAccount.Memorial)
suite.Zero(updatedAccount.MemorializedAt)
suite.Empty(updatedAccount.AlsoKnownAsURIs)
suite.False(*updatedAccount.Discoverable)
suite.WithinDuration(time.Now(), updatedAccount.SuspendedAt, 1*time.Minute)

View file

@ -66,10 +66,13 @@ func (p *Processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account
// Perform a last-minute fetch of target account to
// ensure remote account header / avatar is cached.
//
// Match by URI only.
latest, _, err := p.federator.GetAccountByURI(
gtscontext.SetFastFail(ctx),
requestingAccount.Username,
targetAccountURI,
false,
)
if err != nil {
log.Errorf(ctx, "error fetching latest target account: %v", err)

View file

@ -119,11 +119,15 @@ func (p *Processor) MoveSelf(
unlock := p.state.ProcessingLocks.Lock(lockKey)
defer unlock()
// Ensure we have a valid, up-to-date representation of the target account.
// Ensure we have a valid, up-to-date
// representation of the target account.
//
// Match by uri only.
targetAcct, targetAcctable, err = p.federator.GetAccountByURI(
ctx,
originAcct.Username,
targetAcctURI,
false,
)
if err != nil {
const text = "error dereferencing moved_to_uri"

View file

@ -78,8 +78,8 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
}
if form.Bot != nil {
account.Bot = form.Bot
acctColumns = append(acctColumns, "bot")
account.ActorType = gtsmodel.AccountActorTypeService
acctColumns = append(acctColumns, "actor_type")
}
if form.Locked != nil {

View file

@ -60,7 +60,7 @@ func (p *Processor) createDomainAllow(
}
// Insert the new allow into the database.
if err := p.state.DB.CreateDomainAllow(ctx, domainAllow); err != nil {
if err := p.state.DB.PutDomainAllow(ctx, domainAllow); err != nil {
err = gtserror.Newf("db error putting domain allow %s: %w", domain, err)
return nil, "", gtserror.NewErrorInternalError(err)
}
@ -92,6 +92,54 @@ func (p *Processor) createDomainAllow(
return apiDomainAllow, action.ID, nil
}
func (p *Processor) updateDomainAllow(
ctx context.Context,
domainAllowID string,
obfuscate *bool,
publicComment *string,
privateComment *string,
subscriptionID *string,
) (*apimodel.DomainPermission, gtserror.WithCode) {
domainAllow, err := p.state.DB.GetDomainAllowByID(ctx, domainAllowID)
if err != nil {
if !errors.Is(err, db.ErrNoEntries) {
// Real error.
err = gtserror.Newf("db error getting domain allow: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
// There are just no entries for this ID.
err = fmt.Errorf("no domain allow entry exists with ID %s", domainAllowID)
return nil, gtserror.NewErrorNotFound(err, err.Error())
}
var columns []string
if obfuscate != nil {
domainAllow.Obfuscate = obfuscate
columns = append(columns, "obfuscate")
}
if publicComment != nil {
domainAllow.PublicComment = *publicComment
columns = append(columns, "public_comment")
}
if privateComment != nil {
domainAllow.PrivateComment = *privateComment
columns = append(columns, "private_comment")
}
if subscriptionID != nil {
domainAllow.SubscriptionID = *subscriptionID
columns = append(columns, "subscription_id")
}
// Update the domain allow.
if err := p.state.DB.UpdateDomainAllow(ctx, domainAllow, columns...); err != nil {
err = gtserror.Newf("db error updating domain allow: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
return p.apiDomainPerm(ctx, domainAllow, false)
}
func (p *Processor) deleteDomainAllow(
ctx context.Context,
adminAcct *gtsmodel.Account,

View file

@ -60,7 +60,7 @@ func (p *Processor) createDomainBlock(
}
// Insert the new block into the database.
if err := p.state.DB.CreateDomainBlock(ctx, domainBlock); err != nil {
if err := p.state.DB.PutDomainBlock(ctx, domainBlock); err != nil {
err = gtserror.Newf("db error putting domain block %s: %w", domain, err)
return nil, "", gtserror.NewErrorInternalError(err)
}
@ -93,6 +93,54 @@ func (p *Processor) createDomainBlock(
return apiDomainBlock, action.ID, nil
}
func (p *Processor) updateDomainBlock(
ctx context.Context,
domainBlockID string,
obfuscate *bool,
publicComment *string,
privateComment *string,
subscriptionID *string,
) (*apimodel.DomainPermission, gtserror.WithCode) {
domainBlock, err := p.state.DB.GetDomainBlockByID(ctx, domainBlockID)
if err != nil {
if !errors.Is(err, db.ErrNoEntries) {
// Real error.
err = gtserror.Newf("db error getting domain block: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
// There are just no entries for this ID.
err = fmt.Errorf("no domain block entry exists with ID %s", domainBlockID)
return nil, gtserror.NewErrorNotFound(err, err.Error())
}
var columns []string
if obfuscate != nil {
domainBlock.Obfuscate = obfuscate
columns = append(columns, "obfuscate")
}
if publicComment != nil {
domainBlock.PublicComment = *publicComment
columns = append(columns, "public_comment")
}
if privateComment != nil {
domainBlock.PrivateComment = *privateComment
columns = append(columns, "private_comment")
}
if subscriptionID != nil {
domainBlock.SubscriptionID = *subscriptionID
columns = append(columns, "subscription_id")
}
// Update the domain block.
if err := p.state.DB.UpdateDomainBlock(ctx, domainBlock, columns...); err != nil {
err = gtserror.Newf("db error updating domain block: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
return p.apiDomainPerm(ctx, domainBlock, false)
}
func (p *Processor) deleteDomainBlock(
ctx context.Context,
adminAcct *gtsmodel.Account,

View file

@ -18,6 +18,7 @@
package admin
import (
"cmp"
"context"
"encoding/json"
"errors"
@ -29,6 +30,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
// DomainPermissionCreate creates an instance-level permission
@ -84,6 +86,50 @@ func (p *Processor) DomainPermissionCreate(
}
}
// DomainPermissionUpdate updates a domain permission
// of the given permissionType, with the given ID.
func (p *Processor) DomainPermissionUpdate(
ctx context.Context,
permissionType gtsmodel.DomainPermissionType,
permID string,
obfuscate *bool,
publicComment *string,
privateComment *string,
subscriptionID *string,
) (*apimodel.DomainPermission, gtserror.WithCode) {
switch permissionType {
// Explicitly block a domain.
case gtsmodel.DomainPermissionBlock:
return p.updateDomainBlock(
ctx,
permID,
obfuscate,
publicComment,
privateComment,
subscriptionID,
)
// Explicitly allow a domain.
case gtsmodel.DomainPermissionAllow:
return p.updateDomainAllow(
ctx,
permID,
obfuscate,
publicComment,
privateComment,
subscriptionID,
)
// 🎵 Why don't we all strap bombs to our chests,
// and ride our bikes to the next G7 picnic?
// Seems easier with every clock-tick. 🎵
default:
err := gtserror.Newf("unrecognized permission type %d", permissionType)
return nil, gtserror.NewErrorInternalError(err)
}
}
// DomainPermissionDelete removes one domain block with the given ID,
// and processes side effects of removing the block asynchronously.
//
@ -153,14 +199,14 @@ func (p *Processor) DomainPermissionsImport(
}
defer file.Close()
// Parse file as slice of domain blocks.
domainPerms := make([]*apimodel.DomainPermission, 0)
if err := json.NewDecoder(file).Decode(&domainPerms); err != nil {
// Parse file as slice of domain permissions.
apiDomainPerms := make([]*apimodel.DomainPermission, 0)
if err := json.NewDecoder(file).Decode(&apiDomainPerms); err != nil {
err = gtserror.Newf("error parsing attachment as domain permissions: %w", err)
return nil, gtserror.NewErrorBadRequest(err, err.Error())
}
count := len(domainPerms)
count := len(apiDomainPerms)
if count == 0 {
err = gtserror.New("error importing domain permissions: 0 entries provided")
return nil, gtserror.NewErrorBadRequest(err, err.Error())
@ -170,52 +216,97 @@ func (p *Processor) DomainPermissionsImport(
// between successes and errors so that the caller can
// try failed imports again if desired.
multiStatusEntries := make([]apimodel.MultiStatusEntry, 0, count)
for _, domainPerm := range domainPerms {
var (
domain = domainPerm.Domain.Domain
obfuscate = domainPerm.Obfuscate
publicComment = domainPerm.PublicComment
privateComment = domainPerm.PrivateComment
subscriptionID = "" // No sub ID for imports.
errWithCode gtserror.WithCode
for _, apiDomainPerm := range apiDomainPerms {
multiStatusEntries = append(
multiStatusEntries,
p.importOrUpdateDomainPerm(
ctx,
permissionType,
account,
apiDomainPerm,
),
)
domainPerm, _, errWithCode = p.DomainPermissionCreate(
ctx,
permissionType,
account,
domain,
obfuscate,
publicComment,
privateComment,
subscriptionID,
)
var entry *apimodel.MultiStatusEntry
if errWithCode != nil {
entry = &apimodel.MultiStatusEntry{
// Use the failed domain entry as the resource value.
Resource: domain,
Message: errWithCode.Safe(),
Status: errWithCode.Code(),
}
} else {
entry = &apimodel.MultiStatusEntry{
// Use successfully created API model domain block as the resource value.
Resource: domainPerm,
Message: http.StatusText(http.StatusOK),
Status: http.StatusOK,
}
}
multiStatusEntries = append(multiStatusEntries, *entry)
}
return apimodel.NewMultiStatus(multiStatusEntries), nil
}
func (p *Processor) importOrUpdateDomainPerm(
ctx context.Context,
permType gtsmodel.DomainPermissionType,
account *gtsmodel.Account,
apiDomainPerm *apimodel.DomainPermission,
) apimodel.MultiStatusEntry {
var (
domain = apiDomainPerm.Domain.Domain
obfuscate = apiDomainPerm.Obfuscate
publicComment = cmp.Or(apiDomainPerm.PublicComment, apiDomainPerm.Comment)
privateComment = apiDomainPerm.PrivateComment
subscriptionID = "" // No sub ID for imports.
)
// Check if this domain
// perm already exists.
var (
domainPerm gtsmodel.DomainPermission
err error
)
if permType == gtsmodel.DomainPermissionBlock {
domainPerm, err = p.state.DB.GetDomainBlock(ctx, domain)
} else {
domainPerm, err = p.state.DB.GetDomainAllow(ctx, domain)
}
if err != nil && !errors.Is(err, db.ErrNoEntries) {
// Real db error.
return apimodel.MultiStatusEntry{
Resource: domain,
Message: "db error checking for existence of domain permission",
Status: http.StatusInternalServerError,
}
}
var errWithCode gtserror.WithCode
if domainPerm != nil {
// Permission already exists, update it.
apiDomainPerm, errWithCode = p.DomainPermissionUpdate(
ctx,
permType,
domainPerm.GetID(),
obfuscate,
publicComment,
privateComment,
nil,
)
} else {
// Permission didn't exist yet, create it.
apiDomainPerm, _, errWithCode = p.DomainPermissionCreate(
ctx,
permType,
account,
domain,
util.PtrOrZero(obfuscate),
util.PtrOrZero(publicComment),
util.PtrOrZero(privateComment),
subscriptionID,
)
}
if errWithCode != nil {
return apimodel.MultiStatusEntry{
Resource: domain,
Message: errWithCode.Safe(),
Status: errWithCode.Code(),
}
}
return apimodel.MultiStatusEntry{
Resource: apiDomainPerm,
Message: http.StatusText(http.StatusOK),
Status: http.StatusOK,
}
}
// DomainPermissionsGet returns all existing domain
// permissions of the requested type. If export is
// true, the format will be suitable for writing out

View file

@ -106,9 +106,9 @@ func (p *Processor) InstancePeersGet(ctx context.Context, includeSuspended bool,
}
domains = append(domains, &apimodel.Domain{
Domain: d,
SuspendedAt: util.FormatISO8601(domainBlock.CreatedAt),
PublicComment: domainBlock.PublicComment,
Domain: d,
SuspendedAt: util.FormatISO8601(domainBlock.CreatedAt),
Comment: &domainBlock.PublicComment,
})
}
}

View file

@ -490,7 +490,7 @@ func (p *Processor) byURI(
if includeAccounts(queryType) {
// Check if URI points to an account.
foundAccount, err := p.accountByURI(ctx, requestingAccount, uri, resolve)
foundAccounts, err := p.accountsByURI(ctx, requestingAccount, uri, resolve)
if err != nil {
// Check for semi-expected error types.
// On one of these, we can continue.
@ -508,7 +508,9 @@ func (p *Processor) byURI(
} else {
// Hit! Return early since it's extremely unlikely
// a status and an account will have the same URL.
appendAccount(foundAccount)
for _, foundAccount := range foundAccounts {
appendAccount(foundAccount)
}
return nil
}
}
@ -544,35 +546,42 @@ func (p *Processor) byURI(
return nil
}
// accountByURI looks for one account with the given URI.
// accountsByURI looks for one account with the given URI/ID,
// then if nothing is found, multiple accounts with the given URL.
//
// If resolve is false, it will only look in the database.
// If resolve is true, it will try to resolve the account
// from remote using the URI, if necessary.
//
// Will return either a hit, ErrNotRetrievable, ErrWrongType,
// or a real error that the caller should handle.
func (p *Processor) accountByURI(
func (p *Processor) accountsByURI(
ctx context.Context,
requestingAccount *gtsmodel.Account,
uri *url.URL,
resolve bool,
) (*gtsmodel.Account, error) {
) ([]*gtsmodel.Account, error) {
if resolve {
// We're allowed to resolve, leave the
// rest up to the dereferencer functions.
//
// Allow dereferencing by URL and not just URI;
// there are many cases where someone might
// paste a URL into the search bar.
account, _, err := p.federator.GetAccountByURI(
gtscontext.SetFastFail(ctx),
requestingAccount.Username,
uri,
true,
)
return account, err
return []*gtsmodel.Account{account}, err
}
// We're not allowed to resolve; search database only.
uriStr := uri.String() // stringify uri just once
// Search by ActivityPub URI.
// Search for single acct by ActivityPub URI.
account, err := p.state.DB.GetAccountByURI(ctx, uriStr)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
err = gtserror.Newf("error checking database for account using URI %s: %w", uriStr, err)
@ -581,22 +590,22 @@ func (p *Processor) accountByURI(
if account != nil {
// We got a hit! No need to continue.
return account, nil
return []*gtsmodel.Account{account}, nil
}
// No hit yet. Fallback to try by URL.
account, err = p.state.DB.GetAccountByURL(ctx, uriStr)
// No hit yet. Fallback to look for any accounts with URL.
accounts, err := p.state.DB.GetAccountsByURL(ctx, uriStr)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
err = gtserror.Newf("error checking database for account using URL %s: %w", uriStr, err)
err = gtserror.Newf("error checking database for accounts using URL %s: %w", uriStr, err)
return nil, err
}
if account != nil {
// We got a hit! No need to continue.
return account, nil
if len(accounts) != 0 {
// We got hits! No need to continue.
return accounts, nil
}
err = fmt.Errorf("account %s could not be retrieved locally and we cannot resolve", uriStr)
err = fmt.Errorf("account(s) %s could not be retrieved locally and we cannot resolve", uriStr)
return nil, gtserror.SetUnretrievable(err)
}

View file

@ -303,10 +303,13 @@ func (p *fediAPI) MoveAccount(ctx context.Context, fMsg *messages.FromFediAPI) e
}
// Account to which the Move is taking place.
//
// Match by uri only.
targetAcct, targetAcctable, err := p.federate.GetAccountByURI(
ctx,
fMsg.Receiving.Username,
targetAcctURI,
false,
)
if err != nil {
return gtserror.Newf(