mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-01-03 04:38:52 +01:00
mmmmmmm!
This commit is contained in:
parent
378822e480
commit
20aa6aa9e6
@ -149,6 +149,7 @@ func (m *Module) DomainPermissionDraftsPOSTHandler(c *gin.Context) {
|
||||
form.Obfuscate,
|
||||
form.PublicComment,
|
||||
form.PrivateComment,
|
||||
"", // No subscription ID.
|
||||
)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
|
@ -125,6 +125,7 @@ func (p *Processor) DomainPermissionDraftCreate(
|
||||
obfuscate bool,
|
||||
publicComment string,
|
||||
privateComment string,
|
||||
subscriptionID string,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
permDraft := >smodel.DomainPermissionDraft{
|
||||
ID: id.NewULID(),
|
||||
@ -135,6 +136,7 @@ func (p *Processor) DomainPermissionDraftCreate(
|
||||
PrivateComment: privateComment,
|
||||
PublicComment: publicComment,
|
||||
Obfuscate: &obfuscate,
|
||||
SubscriptionID: subscriptionID,
|
||||
}
|
||||
|
||||
if err := p.state.DB.PutDomainPermissionDraft(ctx, permDraft); err != nil {
|
||||
|
511
internal/subscriptions/subscriptions.go
Normal file
511
internal/subscriptions/subscriptions.go
Normal file
@ -0,0 +1,511 @@
|
||||
// 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 subscriptions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"codeberg.org/gruf/go-kv"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing/admin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/transport"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
type Subscriptions struct {
|
||||
state *state.State
|
||||
transportController transport.Controller
|
||||
tc *typeutils.Converter
|
||||
admin *admin.Processor
|
||||
}
|
||||
|
||||
// ProcessDomainPermissionSubscriptions processes all domain permission
|
||||
// subscriptions of the given permission type by, in turn, calling the
|
||||
// URI of each subscription, parsing the result into a list of domain
|
||||
// permissions, and creating (or skipping) each permission as appropriate.
|
||||
func (s *Subscriptions) ProcessDomainPermissionSubscriptions(
|
||||
ctx context.Context,
|
||||
permType gtsmodel.DomainPermissionType,
|
||||
) {
|
||||
log.Info(ctx, "start")
|
||||
|
||||
// Get permission subscriptions in priority order (highest -> lowest).
|
||||
permSubs, err := s.state.DB.GetDomainPermissionSubscriptionsByPriority(ctx, permType)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
// Real db error.
|
||||
log.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(permSubs) == 0 {
|
||||
// No subscriptions of this
|
||||
// type, so nothing to do.
|
||||
return
|
||||
}
|
||||
|
||||
// Get a transport using the instance account,
|
||||
// we can reuse this for each HTTP call.
|
||||
tsport, err := s.transportController.NewTransportForUsername(ctx, "")
|
||||
if err != nil {
|
||||
log.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
for i, permSub := range permSubs {
|
||||
// Higher priority permission subs = everything
|
||||
// above this permission sub in the slice.
|
||||
getHigherPrios := func() ([]*gtsmodel.DomainPermissionSubscription, error) {
|
||||
return permSubs[:i], nil
|
||||
}
|
||||
|
||||
perms, err := s.ProcessDomainPermissionSubscription(
|
||||
ctx,
|
||||
permSub,
|
||||
tsport,
|
||||
getHigherPrios,
|
||||
)
|
||||
if err != nil {
|
||||
// Real db error.
|
||||
log.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Update count if necessary.
|
||||
if count := len(perms); count != 0 {
|
||||
permSub.Count = uint64(count)
|
||||
}
|
||||
|
||||
// Update this perm sub.
|
||||
err = s.state.DB.UpdateDomainPermissionSubscription(ctx, permSub)
|
||||
if err != nil {
|
||||
// Real db error.
|
||||
log.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Info(ctx, "finished")
|
||||
}
|
||||
|
||||
// ProcessDomainPermissionSubscription processes one domain permission
|
||||
// subscription by dereferencing the URI, parsing the response into a list
|
||||
// of permissions, and for each discovered permission either creating an
|
||||
// entry in the database, or ignoring it if it's excluded or already
|
||||
// covered by a higher-priority subscription.
|
||||
//
|
||||
// On success, the slice of discovered DomainPermissions will be returned.
|
||||
// In case of parsing error, or error on the remote side, permSub.Error
|
||||
// will be updated with the calling/parsing error, and `nil, nil` will be
|
||||
// returned. In case of an actual db error, `nil, err` will be returned and
|
||||
// the caller should handle it.
|
||||
//
|
||||
// getHigherPrios should be a function for returning a slice of domain
|
||||
// permission subscriptions with a higher priority than the given permSub.
|
||||
func (s *Subscriptions) ProcessDomainPermissionSubscription(
|
||||
ctx context.Context,
|
||||
permSub *gtsmodel.DomainPermissionSubscription,
|
||||
tsport transport.Transport,
|
||||
getHigherPrios func() ([]*gtsmodel.DomainPermissionSubscription, error),
|
||||
) ([]gtsmodel.DomainPermission, error) {
|
||||
l := log.
|
||||
WithContext(ctx).
|
||||
WithFields(kv.Fields{
|
||||
{"permType", permSub.PermissionType.String()},
|
||||
{"permSubURI", permSub.URI},
|
||||
}...)
|
||||
|
||||
// Set FetchedAt as we're
|
||||
// going to attempt this now.
|
||||
permSub.FetchedAt = time.Now()
|
||||
|
||||
// Call the URI but don't force.
|
||||
resp, err := tsport.DereferenceDomainPermissions(
|
||||
ctx, permSub, false,
|
||||
)
|
||||
if err != nil {
|
||||
// Couldn't get this one,
|
||||
// set error + return.
|
||||
errStr := err.Error()
|
||||
l.Warnf("couldn't dereference permSubURI: %+v", err)
|
||||
permSub.Error = errStr
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// If the permissions at URI weren't modified
|
||||
// since last time, just update some metadata
|
||||
// to indicate a successful fetch, and return.
|
||||
if resp.Unmodified {
|
||||
l.Debug("received 304 Not Modified from remote")
|
||||
permSub.SuccessfullyFetchedAt = time.Now()
|
||||
if permSub.ETag == "" && resp.ETag != "" {
|
||||
// We didn't have an ETag before but
|
||||
// we have one now: probably the remote
|
||||
// added ETag support in the meantime.
|
||||
permSub.ETag = resp.ETag
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// At this point we know we got a 200 OK
|
||||
// from the URI, so we've got a live body!
|
||||
// Try to parse the body as a list of perms.
|
||||
var perms []gtsmodel.DomainPermission
|
||||
|
||||
switch permSub.ContentType {
|
||||
|
||||
// text/csv
|
||||
case gtsmodel.DomainPermSubContentTypeCSV:
|
||||
perms, err = s.permsFromCSV(ctx, permSub.PermissionType, resp.Body)
|
||||
|
||||
// application/json
|
||||
case gtsmodel.DomainPermSubContentTypeJSON:
|
||||
perms, err = s.permsFromJSON(ctx, permSub.PermissionType, resp.Body)
|
||||
|
||||
// text/plain
|
||||
case gtsmodel.DomainPermSubContentTypePlain:
|
||||
perms, err = s.permsFromPlain(ctx, permSub.PermissionType, resp.Body)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// We retrieved the permissions from remote
|
||||
// but we couldn't parse them into anything
|
||||
// workable, or the connection died halfway
|
||||
// through transfer, or some other annoyance.
|
||||
// Just set error and return.
|
||||
errStr := err.Error()
|
||||
l.Warnf("couldn't parse results: %+v", err)
|
||||
permSub.Error = errStr
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
higherPrios, err := getHigherPrios()
|
||||
if err != nil {
|
||||
// Proper db error.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Process each domain permission.
|
||||
for _, perm := range perms {
|
||||
l = l.WithField("domain", perm.GetDomain())
|
||||
if err := s.processDomainPermission(
|
||||
ctx,
|
||||
l,
|
||||
perm,
|
||||
permSub,
|
||||
higherPrios,
|
||||
); err != nil {
|
||||
// Proper db error.
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return perms, nil
|
||||
}
|
||||
|
||||
// processDomainPermission processes one domain permission
|
||||
// discovered via a domain permission subscription's URI.
|
||||
//
|
||||
// Error will only be returned in case of an actual database
|
||||
// error, else the error will be logged and nil returned.
|
||||
func (s *Subscriptions) processDomainPermission(
|
||||
ctx context.Context,
|
||||
l log.Entry,
|
||||
perm gtsmodel.DomainPermission,
|
||||
permSub *gtsmodel.DomainPermissionSubscription,
|
||||
higherPrios []*gtsmodel.DomainPermissionSubscription,
|
||||
) error {
|
||||
// If domain contains wildcard/obfuscation
|
||||
// characters, we can't do anything with it.
|
||||
domain := perm.GetDomain()
|
||||
if strings.Contains(domain, "*") {
|
||||
l.Warn("'*' char(s) in domain, skipping")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Basic validation.
|
||||
if _, ok := dns.IsDomainName(domain); !ok {
|
||||
l.Warn("invalid domain, skipping")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert to punycode.
|
||||
domain, err := util.Punify(domain)
|
||||
if err != nil {
|
||||
l.Warnf("could not punify domain (%+v), skipping", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Domain looks good. Check if it's excluded.
|
||||
excluded, err := s.state.DB.IsDomainPermissionExcluded(ctx, domain)
|
||||
if err != nil {
|
||||
// Proper db error.
|
||||
return err
|
||||
}
|
||||
|
||||
if excluded {
|
||||
l.Debug("domain is excluded, skipping")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if a permission already exists for
|
||||
// this domain, and if it's covered already
|
||||
// by a higher-priority subscription.
|
||||
existingPerm, covered, err := s.existingCovered(
|
||||
ctx, permSub.PermissionType, domain, higherPrios,
|
||||
)
|
||||
if err != nil {
|
||||
// Proper db error.
|
||||
return err
|
||||
}
|
||||
|
||||
if covered {
|
||||
l.Debug("domain is covered by a higher-priority subscription, skipping")
|
||||
return nil
|
||||
}
|
||||
|
||||
existing := !util.IsNil(existingPerm)
|
||||
switch {
|
||||
|
||||
// No existing perm, create draft.
|
||||
case !existing && *permSub.AsDraft:
|
||||
_, err = s.admin.DomainPermissionDraftCreate(
|
||||
ctx,
|
||||
perm.GetCreatedByAccount(),
|
||||
domain,
|
||||
permSub.PermissionType,
|
||||
util.PtrOrZero(perm.GetObfuscate()),
|
||||
perm.GetPublicComment(),
|
||||
permSub.URI,
|
||||
permSub.ID,
|
||||
)
|
||||
|
||||
// No existing perm, create straight up.
|
||||
case !existing && !*permSub.AsDraft:
|
||||
_, _, err = s.admin.DomainPermissionCreate(
|
||||
ctx,
|
||||
permSub.PermissionType,
|
||||
perm.GetCreatedByAccount(),
|
||||
domain,
|
||||
util.PtrOrZero(perm.GetObfuscate()),
|
||||
perm.GetPublicComment(),
|
||||
permSub.URI,
|
||||
permSub.ID,
|
||||
)
|
||||
|
||||
// Exists but we should adopt/take it.
|
||||
case existingPerm.GetSubscriptionID() != "" || *permSub.AdoptOrphans:
|
||||
existingPerm.SetCreatedByAccountID(perm.GetCreatedByAccountID())
|
||||
existingPerm.SetCreatedByAccount(perm.GetCreatedByAccount())
|
||||
existingPerm.SetSubscriptionID(permSub.ID)
|
||||
existingPerm.SetObfuscate(perm.GetObfuscate())
|
||||
existingPerm.SetPrivateComment(perm.GetPrivateComment())
|
||||
existingPerm.SetPublicComment(perm.GetPublicComment())
|
||||
|
||||
switch p := existingPerm.(type) {
|
||||
case *gtsmodel.DomainBlock:
|
||||
err = s.state.DB.UpdateDomainBlock(ctx, p)
|
||||
case *gtsmodel.DomainAllow:
|
||||
err = s.state.DB.UpdateDomainAllow(ctx, p)
|
||||
}
|
||||
|
||||
// Exists but we should leave it alone.
|
||||
default:
|
||||
l.Debug("domain is covered by a higher-priority subscription, skipping")
|
||||
}
|
||||
|
||||
if err != nil && !errors.Is(err, db.ErrAlreadyExists) {
|
||||
// Proper db error.
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Subscriptions) permsFromCSV(
|
||||
ctx context.Context,
|
||||
permType gtsmodel.DomainPermissionType,
|
||||
body io.ReadCloser,
|
||||
) ([]gtsmodel.DomainPermission, error) {
|
||||
// Read body into memory as slice of CSV records.
|
||||
records, err := csv.NewReader(body).ReadAll()
|
||||
|
||||
// Whatever happened, we're
|
||||
// done with the body now.
|
||||
body.Close()
|
||||
|
||||
// Check if error reading body.
|
||||
if err != nil {
|
||||
return nil, gtserror.NewfAt(3, "error decoding into csv: %w", err)
|
||||
}
|
||||
|
||||
// Convert CSV records to perms.
|
||||
return s.tc.CSVToDomainPerms(ctx, records, permType)
|
||||
}
|
||||
|
||||
func (s *Subscriptions) permsFromJSON(
|
||||
_ context.Context,
|
||||
permType gtsmodel.DomainPermissionType,
|
||||
body io.ReadCloser,
|
||||
) ([]gtsmodel.DomainPermission, error) {
|
||||
var (
|
||||
dec = json.NewDecoder(body)
|
||||
apiPerms = make([]*apimodel.DomainPermission, 0)
|
||||
)
|
||||
|
||||
// Read body into memory as
|
||||
// slice of domain permissions.
|
||||
if err := dec.Decode(&apiPerms); err != nil {
|
||||
_ = body.Close() // ensure closed.
|
||||
return nil, gtserror.NewfAt(3, "error decoding into json: %w", err)
|
||||
}
|
||||
|
||||
// Perform a secondary decode just to ensure we drained the
|
||||
// entirety of the data source. Error indicates either extra
|
||||
// trailing garbage, or multiple JSON values (invalid data).
|
||||
if err := dec.Decode(&struct{}{}); err != io.EOF {
|
||||
_ = body.Close() // ensure closed.
|
||||
return nil, gtserror.NewfAt(3, "data remaining after json")
|
||||
}
|
||||
|
||||
// Done with body.
|
||||
_ = body.Close()
|
||||
|
||||
// Convert apimodel perms to barebones internal perms.
|
||||
perms := make([]gtsmodel.DomainPermission, 0, len(apiPerms))
|
||||
for _, apiPerm := range apiPerms {
|
||||
// Instantiate the permission
|
||||
// as either block or allow.
|
||||
var perm gtsmodel.DomainPermission
|
||||
if permType == gtsmodel.DomainPermissionBlock {
|
||||
perm = >smodel.DomainBlock{Domain: apiPerm.Domain.Domain}
|
||||
} else {
|
||||
perm = >smodel.DomainAllow{Domain: apiPerm.Domain.Domain}
|
||||
}
|
||||
|
||||
// Set remaining fields.
|
||||
perm.SetPublicComment(apiPerm.PublicComment)
|
||||
perm.SetObfuscate(&apiPerm.Obfuscate)
|
||||
|
||||
// We're done.
|
||||
perms = append(perms, perm)
|
||||
}
|
||||
|
||||
return perms, nil
|
||||
}
|
||||
|
||||
func (s *Subscriptions) permsFromPlain(
|
||||
_ context.Context,
|
||||
permType gtsmodel.DomainPermissionType,
|
||||
body io.ReadCloser,
|
||||
) ([]gtsmodel.DomainPermission, error) {
|
||||
// Read body into memory as bytes.
|
||||
b, err := io.ReadAll(body)
|
||||
|
||||
// Whatever happened, we're
|
||||
// done with the body now.
|
||||
body.Close()
|
||||
|
||||
// Check if error reading body.
|
||||
if err != nil {
|
||||
return nil, gtserror.NewfAt(3, "error decoding into plain: %w", err)
|
||||
}
|
||||
|
||||
// Coerce to newline-separated list of domains.
|
||||
domains := strings.Split(string(b), "\n")
|
||||
|
||||
// Convert raw domains to permissions.
|
||||
perms := make([]gtsmodel.DomainPermission, 0, len(domains))
|
||||
for _, domain := range domains {
|
||||
// Instantiate the permission
|
||||
// as either block or allow.
|
||||
var perm gtsmodel.DomainPermission
|
||||
if permType == gtsmodel.DomainPermissionBlock {
|
||||
perm = >smodel.DomainBlock{Domain: domain}
|
||||
} else {
|
||||
perm = >smodel.DomainAllow{Domain: domain}
|
||||
}
|
||||
|
||||
// We're done.
|
||||
perms = append(perms, perm)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *Subscriptions) existingCovered(
|
||||
ctx context.Context,
|
||||
permType gtsmodel.DomainPermissionType,
|
||||
domain string,
|
||||
higherPrios []*gtsmodel.DomainPermissionSubscription,
|
||||
) (
|
||||
existingPerm gtsmodel.DomainPermission,
|
||||
covered bool,
|
||||
err error,
|
||||
) {
|
||||
// Check for existing permission of appropriate type.
|
||||
var dbErr error
|
||||
if permType == gtsmodel.DomainPermissionBlock {
|
||||
existingPerm, dbErr = s.state.DB.GetDomainBlock(ctx, domain)
|
||||
} else {
|
||||
existingPerm, dbErr = s.state.DB.GetDomainAllow(ctx, domain)
|
||||
}
|
||||
if dbErr != nil && !errors.Is(dbErr, db.ErrNoEntries) {
|
||||
// Real db error.
|
||||
err = dbErr
|
||||
return
|
||||
}
|
||||
|
||||
if util.IsNil(existingPerm) {
|
||||
// Can't be covered if
|
||||
// no existing perm.
|
||||
return
|
||||
}
|
||||
|
||||
subscriptionID := existingPerm.GetSubscriptionID()
|
||||
if subscriptionID == "" {
|
||||
// Can't be covered if
|
||||
// no subscription ID.
|
||||
return
|
||||
}
|
||||
|
||||
// Covered if subscription ID is in the slice
|
||||
// of higher-priority permission subscriptions.
|
||||
covered = slices.ContainsFunc(
|
||||
higherPrios,
|
||||
func(permSub *gtsmodel.DomainPermissionSubscription) bool {
|
||||
return permSub.ID == subscriptionID
|
||||
},
|
||||
)
|
||||
|
||||
return
|
||||
}
|
123
internal/transport/derefdomainpermlist.go
Normal file
123
internal/transport/derefdomainpermlist.go
Normal file
@ -0,0 +1,123 @@
|
||||
// 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 transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
type DereferenceDomainPermissionsResp struct {
|
||||
// Set only if response was 200 OK.
|
||||
// It's up to the caller to close
|
||||
// this when they're done with it.
|
||||
Body io.ReadCloser
|
||||
|
||||
// True if response
|
||||
// was 304 Not Modified.
|
||||
Unmodified bool
|
||||
|
||||
// May be set
|
||||
// if 200 or 304.
|
||||
ETag string
|
||||
}
|
||||
|
||||
func (t *transport) DereferenceDomainPermissions(
|
||||
ctx context.Context,
|
||||
permSub *gtsmodel.DomainPermissionSubscription,
|
||||
force bool,
|
||||
) (*DereferenceDomainPermissionsResp, error) {
|
||||
// Prepare new HTTP request to endpoint
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", permSub.URI, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set basic auth header if necessary.
|
||||
if permSub.FetchUsername != "" || permSub.FetchPassword != "" {
|
||||
req.SetBasicAuth(permSub.FetchUsername, permSub.FetchPassword)
|
||||
}
|
||||
|
||||
// Set relevant Accept headers.
|
||||
// Allow fallback in case target doesn't
|
||||
// negotiate content type correctly.
|
||||
req.Header.Add("Accept-Charset", "utf-8")
|
||||
req.Header.Add("Accept", permSub.ContentType.String()+","+"*/*")
|
||||
|
||||
// If force is true, we want to skip setting Cache
|
||||
// headers so that we definitely don't get a 304 back.
|
||||
if !force {
|
||||
// If we've successfully fetched this list
|
||||
// before, set If-Modified-Since to last
|
||||
// success to make the request conditional.
|
||||
//
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since
|
||||
if !permSub.SuccessfullyFetchedAt.IsZero() {
|
||||
timeStr := permSub.SuccessfullyFetchedAt.Format(http.TimeFormat)
|
||||
req.Header.Add("If-Modified-Since", timeStr)
|
||||
}
|
||||
|
||||
// If we've got an ETag stored for this list, set
|
||||
// If-None-Match to make the request conditional.
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag#caching_of_unchanged_resources.
|
||||
if len(permSub.ETag) != 0 {
|
||||
req.Header.Add("If-None-Match", permSub.ETag)
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the HTTP request
|
||||
rsp, err := t.GET(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If we have an unexpected / error response,
|
||||
// wrap + return as error. This will also drain
|
||||
// and close the response body for us.
|
||||
if rsp.StatusCode != http.StatusOK &&
|
||||
rsp.StatusCode != http.StatusNotModified {
|
||||
err := gtserror.NewFromResponse(rsp)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check already if we were given an ETag
|
||||
// we can use, as ETag is often returned
|
||||
// even on 304 Not Modified responses.
|
||||
eTag := rsp.Header.Get("ETag")
|
||||
|
||||
if rsp.StatusCode == http.StatusNotModified {
|
||||
// Nothing has changed on the remote side
|
||||
// since we last fetched, so there's nothing
|
||||
// to do and we don't need to read the body.
|
||||
rsp.Body.Close()
|
||||
return &DereferenceDomainPermissionsResp{
|
||||
Unmodified: true,
|
||||
ETag: eTag,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Return the body + ETag to the caller.
|
||||
return &DereferenceDomainPermissionsResp{
|
||||
Body: rsp.Body,
|
||||
ETag: eTag,
|
||||
}, nil
|
||||
}
|
@ -78,6 +78,20 @@ type Transport interface {
|
||||
// DereferenceInstance dereferences remote instance information, first by checking /api/v1/instance, and then by checking /.well-known/nodeinfo.
|
||||
DereferenceInstance(ctx context.Context, iri *url.URL) (*gtsmodel.Instance, error)
|
||||
|
||||
// DereferenceDomainPermissions dereferences the
|
||||
// permissions list present at the given permSub's URI.
|
||||
//
|
||||
// If "force", then If-Modified-Since and If-None-Match
|
||||
// headers will *NOT* be sent with the outgoing request.
|
||||
//
|
||||
// If err == nil and Unmodified == false, then it's up
|
||||
// to the caller to close the returned io.ReadCloser.
|
||||
DereferenceDomainPermissions(
|
||||
ctx context.Context,
|
||||
permSub *gtsmodel.DomainPermissionSubscription,
|
||||
force bool,
|
||||
) (*DereferenceDomainPermissionsResp, error)
|
||||
|
||||
// Finger performs a webfinger request with the given username and domain, and returns the bytes from the response body.
|
||||
Finger(ctx context.Context, targetUsername string, targetDomain string) ([]byte, error)
|
||||
}
|
||||
|
@ -27,6 +27,7 @@
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
@ -553,3 +554,94 @@ func (c *Converter) CSVToBlocks(
|
||||
|
||||
return blocks, nil
|
||||
}
|
||||
|
||||
// CSVToDomainPerms converts a slice of Mastodon-style
|
||||
// CSV records to barebones gtsmodel.DomainPermission's,
|
||||
// ready for further processing.
|
||||
//
|
||||
// Expected column names are:
|
||||
//
|
||||
// #domain
|
||||
// #severity
|
||||
// #reject_media
|
||||
// #reject_reports
|
||||
// #public_comment
|
||||
// #obfuscate
|
||||
func (c *Converter) CSVToDomainPerms(
|
||||
ctx context.Context,
|
||||
records [][]string,
|
||||
permType gtsmodel.DomainPermissionType,
|
||||
) ([]gtsmodel.DomainPermission, error) {
|
||||
// Make sure we actually
|
||||
// have some records.
|
||||
if len(records) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Validate column headers.
|
||||
columnHeaders := records[0]
|
||||
if !slices.Equal(
|
||||
columnHeaders,
|
||||
[]string{
|
||||
"#domain",
|
||||
"#severity",
|
||||
"#reject_media",
|
||||
"#reject_reports",
|
||||
"#public_comment",
|
||||
"#obfuscate",
|
||||
},
|
||||
) {
|
||||
return nil, gtserror.Newf(
|
||||
"unexpected column headers in csv: %+v",
|
||||
columnHeaders,
|
||||
)
|
||||
}
|
||||
|
||||
// Trim off column headers
|
||||
// now they're validated.
|
||||
records = records[1:]
|
||||
|
||||
// Convert records to permissions slice.
|
||||
perms := make([]gtsmodel.DomainPermission, 0, len(records))
|
||||
for _, record := range records {
|
||||
if len(record) != 6 {
|
||||
log.Warnf(ctx, "skipping invalid-length record: %+v", record)
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
domain = record[0]
|
||||
severity = record[1]
|
||||
publicComment = record[4]
|
||||
obfuscate, err = strconv.ParseBool(record[5])
|
||||
)
|
||||
|
||||
if severity != "suspend" {
|
||||
log.Warnf(ctx, "skipping non-suspend record: %+v", record)
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(ctx, "couldn't parse obfuscate field of record: %+v", record)
|
||||
continue
|
||||
}
|
||||
|
||||
// Instantiate the permission
|
||||
// as either block or allow.
|
||||
var perm gtsmodel.DomainPermission
|
||||
if permType == gtsmodel.DomainPermissionBlock {
|
||||
perm = >smodel.DomainBlock{Domain: domain}
|
||||
} else {
|
||||
perm = >smodel.DomainAllow{Domain: domain}
|
||||
}
|
||||
|
||||
// Set remaining fields.
|
||||
perm.SetPublicComment(publicComment)
|
||||
perm.SetObfuscate(&obfuscate)
|
||||
|
||||
// We're done.
|
||||
perms = append(perms, perm)
|
||||
}
|
||||
|
||||
return perms, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user