diff options
author | 2023-09-21 12:12:04 +0200 | |
---|---|---|
committer | 2023-09-21 12:12:04 +0200 | |
commit | 183eaa5b298235acb8f25ba8f18b98e31471d965 (patch) | |
tree | 55f42887edeb5206122d92eb30e0eedf145a3615 /internal/processing/admin/domainallow.go | |
parent | [docs] Add a note on cluster support (#2214) (diff) | |
download | gotosocial-183eaa5b298235acb8f25ba8f18b98e31471d965.tar.xz |
[feature] Implement explicit domain allows + allowlist federation mode (#2200)
* love like winter! wohoah, wohoah
* domain allow side effects
* tests! logging! unallow!
* document federation modes
* linty linterson
* test
* further adventures in documentation
* finish up domain block documentation (i think)
* change wording a wee little bit
* docs, example
* consolidate shared domainPermission code
* call mode once
* fetch federation mode within domain blocked func
* read domain perm import in streaming manner
* don't use pointer to slice for domain perms
* don't bother copying blocks + allows before deleting
* admonish!
* change wording just a scooch
* update docs
Diffstat (limited to 'internal/processing/admin/domainallow.go')
-rw-r--r-- | internal/processing/admin/domainallow.go | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/internal/processing/admin/domainallow.go b/internal/processing/admin/domainallow.go new file mode 100644 index 000000000..bab54e308 --- /dev/null +++ b/internal/processing/admin/domainallow.go @@ -0,0 +1,255 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +package admin + +import ( + "context" + "errors" + "fmt" + + "codeberg.org/gruf/go-kv" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/id" + "github.com/superseriousbusiness/gotosocial/internal/log" + "github.com/superseriousbusiness/gotosocial/internal/text" +) + +func (p *Processor) createDomainAllow( + ctx context.Context, + adminAcct *gtsmodel.Account, + domain string, + obfuscate bool, + publicComment string, + privateComment string, + subscriptionID string, +) (*apimodel.DomainPermission, string, gtserror.WithCode) { + // Check if an allow already exists for this domain. + domainAllow, err := p.state.DB.GetDomainAllow(ctx, domain) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + // Something went wrong in the DB. + err = gtserror.Newf("db error getting domain allow %s: %w", domain, err) + return nil, "", gtserror.NewErrorInternalError(err) + } + + if domainAllow == nil { + // No allow exists yet, create it. + domainAllow = >smodel.DomainAllow{ + ID: id.NewULID(), + Domain: domain, + CreatedByAccountID: adminAcct.ID, + PrivateComment: text.SanitizeToPlaintext(privateComment), + PublicComment: text.SanitizeToPlaintext(publicComment), + Obfuscate: &obfuscate, + SubscriptionID: subscriptionID, + } + + // Insert the new allow into the database. + if err := p.state.DB.CreateDomainAllow(ctx, domainAllow); err != nil { + err = gtserror.Newf("db error putting domain allow %s: %w", domain, err) + return nil, "", gtserror.NewErrorInternalError(err) + } + } + + actionID := id.NewULID() + + // Process domain allow side + // effects asynchronously. + if errWithCode := p.actions.Run( + ctx, + >smodel.AdminAction{ + ID: actionID, + TargetCategory: gtsmodel.AdminActionCategoryDomain, + TargetID: domain, + Type: gtsmodel.AdminActionSuspend, + AccountID: adminAcct.ID, + Text: domainAllow.PrivateComment, + }, + func(ctx context.Context) gtserror.MultiError { + // Log start + finish. + l := log.WithFields(kv.Fields{ + {"domain", domain}, + {"actionID", actionID}, + }...).WithContext(ctx) + + l.Info("processing domain allow side effects") + defer func() { l.Info("finished processing domain allow side effects") }() + + return p.domainAllowSideEffects(ctx, domainAllow) + }, + ); errWithCode != nil { + return nil, actionID, errWithCode + } + + apiDomainAllow, errWithCode := p.apiDomainPerm(ctx, domainAllow, false) + if errWithCode != nil { + return nil, actionID, errWithCode + } + + return apiDomainAllow, actionID, nil +} + +func (p *Processor) domainAllowSideEffects( + ctx context.Context, + allow *gtsmodel.DomainAllow, +) gtserror.MultiError { + if config.GetInstanceFederationMode() == config.InstanceFederationModeAllowlist { + // We're running in allowlist mode, + // so there are no side effects to + // process here. + return nil + } + + // We're running in blocklist mode or + // some similar mode which necessitates + // domain allow side effects if a block + // was in place when the allow was created. + // + // So, check if there's a block. + block, err := p.state.DB.GetDomainBlock(ctx, allow.Domain) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + errs := gtserror.NewMultiError(1) + errs.Appendf("db error getting domain block %s: %w", allow.Domain, err) + return errs + } + + if block == nil { + // No block? + // No problem! + return nil + } + + // There was a block, over which the new + // allow ought to take precedence. To account + // for this, just run side effects as though + // the domain was being unblocked, while + // leaving the existing block in place. + // + // Any accounts that were suspended by + // the block will be unsuspended and be + // able to interact with the instance again. + return p.domainUnblockSideEffects(ctx, block) +} + +func (p *Processor) deleteDomainAllow( + ctx context.Context, + adminAcct *gtsmodel.Account, + domainAllowID string, +) (*apimodel.DomainPermission, string, 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()) + } + + // Prepare the domain allow to return, *before* the deletion goes through. + apiDomainAllow, errWithCode := p.apiDomainPerm(ctx, domainAllow, false) + if errWithCode != nil { + return nil, "", errWithCode + } + + // Delete the original domain allow. + if err := p.state.DB.DeleteDomainAllow(ctx, domainAllow.Domain); err != nil { + err = gtserror.Newf("db error deleting domain allow: %w", err) + return nil, "", gtserror.NewErrorInternalError(err) + } + + actionID := id.NewULID() + + // Process domain unallow side + // effects asynchronously. + if errWithCode := p.actions.Run( + ctx, + >smodel.AdminAction{ + ID: actionID, + TargetCategory: gtsmodel.AdminActionCategoryDomain, + TargetID: domainAllow.Domain, + Type: gtsmodel.AdminActionUnsuspend, + AccountID: adminAcct.ID, + }, + func(ctx context.Context) gtserror.MultiError { + // Log start + finish. + l := log.WithFields(kv.Fields{ + {"domain", domainAllow.Domain}, + {"actionID", actionID}, + }...).WithContext(ctx) + + l.Info("processing domain unallow side effects") + defer func() { l.Info("finished processing domain unallow side effects") }() + + return p.domainUnallowSideEffects(ctx, domainAllow) + }, + ); errWithCode != nil { + return nil, actionID, errWithCode + } + + return apiDomainAllow, actionID, nil +} + +func (p *Processor) domainUnallowSideEffects( + ctx context.Context, + allow *gtsmodel.DomainAllow, +) gtserror.MultiError { + if config.GetInstanceFederationMode() == config.InstanceFederationModeAllowlist { + // We're running in allowlist mode, + // so there are no side effects to + // process here. + return nil + } + + // We're running in blocklist mode or + // some similar mode which necessitates + // domain allow side effects if a block + // was in place when the allow was removed. + // + // So, check if there's a block. + block, err := p.state.DB.GetDomainBlock(ctx, allow.Domain) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + errs := gtserror.NewMultiError(1) + errs.Appendf("db error getting domain block %s: %w", allow.Domain, err) + return errs + } + + if block == nil { + // No block? + // No problem! + return nil + } + + // There was a block, over which the previous + // allow was taking precedence. Now that the + // allow has been removed, we should put the + // side effects of the block back in place. + // + // To do this, process the block side effects + // again as though the block were freshly + // created. This will mark all accounts from + // the blocked domain as suspended, and clean + // up their follows/following, media, etc. + return p.domainBlockSideEffects(ctx, block) +} |