mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-12-16 18:43:01 -06:00
check domain blocks way earlier on
This commit is contained in:
parent
99eb3bf564
commit
5bf7b46cf4
19 changed files with 255 additions and 77 deletions
|
|
@ -19,10 +19,12 @@
|
||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FollowersGETHandler returns a collection of URIs for followers of the target user, formatted so that other AP servers can understand it.
|
// FollowersGETHandler returns a collection of URIs for followers of the target user, formatted so that other AP servers can understand it.
|
||||||
|
|
@ -46,9 +48,14 @@ func (m *Module) FollowersGETHandler(c *gin.Context) {
|
||||||
}
|
}
|
||||||
l.Tracef("negotiated format: %s", format)
|
l.Tracef("negotiated format: %s", format)
|
||||||
|
|
||||||
// make a copy of the context to pass along so we don't break anything
|
// transfer the signature verifier from the gin context to the request context
|
||||||
cp := c.Copy()
|
ctx := c.Request.Context()
|
||||||
user, err := m.processor.GetFediFollowers(requestedUsername, cp.Request) // GetFediUser handles auth as well
|
verifier, signed := c.Get(string(util.APRequestingPublicKeyVerifier))
|
||||||
|
if signed {
|
||||||
|
ctx = context.WithValue(ctx, util.APRequestingPublicKeyVerifier, verifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := m.processor.GetFediFollowers(ctx, requestedUsername, c.Request.URL) // GetFediUser handles auth as well
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Info(err.Error())
|
l.Info(err.Error())
|
||||||
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,12 @@
|
||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FollowingGETHandler returns a collection of URIs for accounts that the target user follows, formatted so that other AP servers can understand it.
|
// FollowingGETHandler returns a collection of URIs for accounts that the target user follows, formatted so that other AP servers can understand it.
|
||||||
|
|
@ -46,9 +48,14 @@ func (m *Module) FollowingGETHandler(c *gin.Context) {
|
||||||
}
|
}
|
||||||
l.Tracef("negotiated format: %s", format)
|
l.Tracef("negotiated format: %s", format)
|
||||||
|
|
||||||
// make a copy of the context to pass along so we don't break anything
|
// transfer the signature verifier from the gin context to the request context
|
||||||
cp := c.Copy()
|
ctx := c.Request.Context()
|
||||||
user, err := m.processor.GetFediFollowing(requestedUsername, cp.Request) // handles auth as well
|
verifier, signed := c.Get(string(util.APRequestingPublicKeyVerifier))
|
||||||
|
if signed {
|
||||||
|
ctx = context.WithValue(ctx, util.APRequestingPublicKeyVerifier, verifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := m.processor.GetFediFollowing(ctx, requestedUsername, c.Request.URL) // handles auth as well
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Info(err.Error())
|
l.Info(err.Error())
|
||||||
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,13 @@
|
||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InboxPOSTHandler deals with incoming POST requests to an actor's inbox.
|
// InboxPOSTHandler deals with incoming POST requests to an actor's inbox.
|
||||||
|
|
@ -40,7 +42,14 @@ func (m *Module) InboxPOSTHandler(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
posted, err := m.processor.InboxPost(c.Request.Context(), c.Writer, c.Request)
|
// transfer the signature verifier from the gin context to the request context
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
verifier, signed := c.Get(string(util.APRequestingPublicKeyVerifier))
|
||||||
|
if signed {
|
||||||
|
ctx = context.WithValue(ctx, util.APRequestingPublicKeyVerifier, verifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
posted, err := m.processor.InboxPost(ctx, c.Writer, c.Request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if withCode, ok := err.(gtserror.WithCode); ok {
|
if withCode, ok := err.(gtserror.WithCode); ok {
|
||||||
l.Debug(withCode.Error())
|
l.Debug(withCode.Error())
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PublicKeyGETHandler should be served at eg https://example.org/users/:username/main-key.
|
// PublicKeyGETHandler should be served at eg https://example.org/users/:username/main-key.
|
||||||
|
|
@ -32,9 +34,14 @@ func (m *Module) PublicKeyGETHandler(c *gin.Context) {
|
||||||
}
|
}
|
||||||
l.Tracef("negotiated format: %s", format)
|
l.Tracef("negotiated format: %s", format)
|
||||||
|
|
||||||
// make a copy of the context to pass along so we don't break anything
|
// transfer the signature verifier from the gin context to the request context
|
||||||
cp := c.Copy()
|
ctx := c.Request.Context()
|
||||||
user, err := m.processor.GetFediUser(requestedUsername, cp.Request) // GetFediUser handles auth as well
|
verifier, signed := c.Get(string(util.APRequestingPublicKeyVerifier))
|
||||||
|
if signed {
|
||||||
|
ctx = context.WithValue(ctx, util.APRequestingPublicKeyVerifier, verifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := m.processor.GetFediUser(ctx, requestedUsername, c.Request.URL) // GetFediUser handles auth as well
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Info(err.Error())
|
l.Info(err.Error())
|
||||||
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StatusGETHandler serves the target status as an activitystreams NOTE so that other AP servers can parse it.
|
// StatusGETHandler serves the target status as an activitystreams NOTE so that other AP servers can parse it.
|
||||||
|
|
@ -34,9 +36,14 @@ func (m *Module) StatusGETHandler(c *gin.Context) {
|
||||||
}
|
}
|
||||||
l.Tracef("negotiated format: %s", format)
|
l.Tracef("negotiated format: %s", format)
|
||||||
|
|
||||||
// make a copy of the context to pass along so we don't break anything
|
// transfer the signature verifier from the gin context to the request context
|
||||||
cp := c.Copy()
|
ctx := c.Request.Context()
|
||||||
status, err := m.processor.GetFediStatus(requestedUsername, requestedStatusID, cp.Request) // handles auth as well
|
verifier, signed := c.Get(string(util.APRequestingPublicKeyVerifier))
|
||||||
|
if signed {
|
||||||
|
ctx = context.WithValue(ctx, util.APRequestingPublicKeyVerifier, verifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := m.processor.GetFediStatus(ctx, requestedUsername, requestedStatusID, c.Request.URL) // handles auth as well
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Info(err.Error())
|
l.Info(err.Error())
|
||||||
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,12 @@
|
||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UsersGETHandler should be served at https://example.org/users/:username.
|
// UsersGETHandler should be served at https://example.org/users/:username.
|
||||||
|
|
@ -54,9 +56,14 @@ func (m *Module) UsersGETHandler(c *gin.Context) {
|
||||||
}
|
}
|
||||||
l.Tracef("negotiated format: %s", format)
|
l.Tracef("negotiated format: %s", format)
|
||||||
|
|
||||||
// make a copy of the context to pass along so we don't break anything
|
// transfer the signature verifier from the gin context to the request context
|
||||||
cp := c.Copy()
|
ctx := c.Request.Context()
|
||||||
user, err := m.processor.GetFediUser(requestedUsername, cp.Request) // GetFediUser handles auth as well
|
verifier, signed := c.Get(string(util.APRequestingPublicKeyVerifier))
|
||||||
|
if signed {
|
||||||
|
ctx = context.WithValue(ctx, util.APRequestingPublicKeyVerifier, verifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := m.processor.GetFediUser(ctx, requestedUsername, c.Request.URL) // GetFediUser handles auth as well
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Info(err.Error())
|
l.Info(err.Error())
|
||||||
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,14 @@
|
||||||
package webfinger
|
package webfinger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WebfingerGETRequest handles requests to, for example, https://example.org/.well-known/webfinger?resource=acct:some_user@example.org
|
// WebfingerGETRequest handles requests to, for example, https://example.org/.well-known/webfinger?resource=acct:some_user@example.org
|
||||||
|
|
@ -68,7 +70,14 @@ func (m *Module) WebfingerGETRequest(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := m.processor.GetWebfingerAccount(username, c.Request)
|
// transfer the signature verifier from the gin context to the request context
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
verifier, signed := c.Get(string(util.APRequestingPublicKeyVerifier))
|
||||||
|
if signed {
|
||||||
|
ctx = context.WithValue(ctx, util.APRequestingPublicKeyVerifier, verifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := m.processor.GetWebfingerAccount(ctx, username, c.Request.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("aborting request with an error: %s", err.Error())
|
l.Debugf("aborting request with an error: %s", err.Error())
|
||||||
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/router"
|
"github.com/superseriousbusiness/gotosocial/internal/router"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -33,18 +34,21 @@ const robotsPath = "/robots.txt"
|
||||||
type Module struct {
|
type Module struct {
|
||||||
config *config.Config
|
config *config.Config
|
||||||
log *logrus.Logger
|
log *logrus.Logger
|
||||||
|
db db.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new security module
|
// New returns a new security module
|
||||||
func New(config *config.Config, log *logrus.Logger) api.ClientModule {
|
func New(config *config.Config, db db.DB, log *logrus.Logger) api.ClientModule {
|
||||||
return &Module{
|
return &Module{
|
||||||
config: config,
|
config: config,
|
||||||
log: log,
|
log: log,
|
||||||
|
db: db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route attaches security middleware to the given router
|
// Route attaches security middleware to the given router
|
||||||
func (m *Module) Route(s router.Router) error {
|
func (m *Module) Route(s router.Router) error {
|
||||||
|
s.AttachMiddleware(m.SignatureCheck)
|
||||||
s.AttachMiddleware(m.FlocBlock)
|
s.AttachMiddleware(m.FlocBlock)
|
||||||
s.AttachMiddleware(m.ExtraHeaders)
|
s.AttachMiddleware(m.ExtraHeaders)
|
||||||
s.AttachMiddleware(m.UserAgentBlock)
|
s.AttachMiddleware(m.UserAgentBlock)
|
||||||
|
|
|
||||||
66
internal/api/security/signaturecheck.go
Normal file
66
internal/api/security/signaturecheck.go
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
package security
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-fed/httpsig"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Module) SignatureCheck(c *gin.Context) {
|
||||||
|
l := m.log.WithField("func", "DomainBlockChecker")
|
||||||
|
|
||||||
|
// set this extra field for signature validation
|
||||||
|
c.Request.Header.Set("host", m.config.Host)
|
||||||
|
|
||||||
|
// create the verifier from the request
|
||||||
|
// if the request is signed, it will have a signature header
|
||||||
|
verifier, err := httpsig.NewVerifier(c.Request)
|
||||||
|
if err == nil {
|
||||||
|
// the request was signed!
|
||||||
|
|
||||||
|
// The key ID should be given in the signature so that we know where to fetch it from the remote server.
|
||||||
|
// This will be something like https://example.org/users/whatever_requesting_user#main-key
|
||||||
|
requestingPublicKeyID, err := url.Parse(verifier.KeyId())
|
||||||
|
if err == nil && requestingPublicKeyID != nil {
|
||||||
|
// we managed to parse the url!
|
||||||
|
|
||||||
|
// if the domain is blocked we want to bail as early as possible
|
||||||
|
blockedDomain, err := m.blockedDomain(requestingPublicKeyID.Host)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorf("could not tell if domain %s was blocked or not: %s", requestingPublicKeyID.Host, err)
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if blockedDomain {
|
||||||
|
l.Infof("domain %s is blocked", requestingPublicKeyID.Host)
|
||||||
|
c.AbortWithStatus(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the verifier on the context here to save some work further down the line
|
||||||
|
c.Set(string(util.APRequestingPublicKeyVerifier), verifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) blockedDomain(host string) (bool, error) {
|
||||||
|
b := >smodel.DomainBlock{}
|
||||||
|
err := m.db.GetWhere([]db.Where{{Key: "domain", Value: host, CaseInsensitive: true}}, b)
|
||||||
|
if err == nil {
|
||||||
|
// block exists
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := err.(db.ErrNoEntries); ok {
|
||||||
|
// there are no entries so there's no block
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// there's an actual error
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
@ -138,7 +138,7 @@ var Start cliactions.GTSAction = func(ctx context.Context, c *config.Config, log
|
||||||
fileServerModule := fileserver.New(c, processor, log)
|
fileServerModule := fileserver.New(c, processor, log)
|
||||||
adminModule := admin.New(c, processor, log)
|
adminModule := admin.New(c, processor, log)
|
||||||
statusModule := status.New(c, processor, log)
|
statusModule := status.New(c, processor, log)
|
||||||
securityModule := security.New(c, log)
|
securityModule := security.New(c, dbService, log)
|
||||||
streamingModule := streaming.New(c, processor, log)
|
streamingModule := streaming.New(c, processor, log)
|
||||||
|
|
||||||
apis := []api.ClientModule{
|
apis := []api.ClientModule{
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ var Start cliactions.GTSAction = func(ctx context.Context, _ *config.Config, log
|
||||||
fileServerModule := fileserver.New(c, processor, log)
|
fileServerModule := fileserver.New(c, processor, log)
|
||||||
adminModule := admin.New(c, processor, log)
|
adminModule := admin.New(c, processor, log)
|
||||||
statusModule := status.New(c, processor, log)
|
statusModule := status.New(c, processor, log)
|
||||||
securityModule := security.New(c, log)
|
securityModule := security.New(c, dbService, log)
|
||||||
streamingModule := streaming.New(c, processor, log)
|
streamingModule := streaming.New(c, processor, log)
|
||||||
|
|
||||||
apis := []api.ClientModule{
|
apis := []api.ClientModule{
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -35,6 +34,7 @@ import (
|
||||||
"github.com/go-fed/httpsig"
|
"github.com/go-fed/httpsig"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -115,35 +115,30 @@ func getPublicKeyFromResponse(c context.Context, b []byte, keyID *url.URL) (voca
|
||||||
//
|
//
|
||||||
// Also note that this function *does not* dereference the remote account that the signature key is associated with.
|
// Also note that this function *does not* dereference the remote account that the signature key is associated with.
|
||||||
// Other functions should use the returned URL to dereference the remote account, if required.
|
// Other functions should use the returned URL to dereference the remote account, if required.
|
||||||
func (f *federator) AuthenticateFederatedRequest(requestedUsername string, r *http.Request) (*url.URL, bool, error) {
|
func (f *federator) AuthenticateFederatedRequest(ctx context.Context, requestedUsername string) (*url.URL, bool, error) {
|
||||||
|
l := f.log.WithField("func", "AuthenticateFederatedRequest")
|
||||||
|
|
||||||
var publicKey interface{}
|
var publicKey interface{}
|
||||||
var pkOwnerURI *url.URL
|
var pkOwnerURI *url.URL
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// set this extra field for signature validation
|
// thanks to signaturecheck.go in the security package, we should already have a signature verifier set on the context
|
||||||
r.Header.Set("host", f.config.Host)
|
vi := ctx.Value(util.APRequestingPublicKeyVerifier)
|
||||||
|
if vi == nil {
|
||||||
verifier, err := httpsig.NewVerifier(r)
|
l.Debug("request wasn't signed")
|
||||||
if err != nil {
|
return nil, false, nil // request wasn't signed
|
||||||
return nil, false, fmt.Errorf("could not create http sig verifier: %s", err)
|
}
|
||||||
|
|
||||||
|
verifier, ok := vi.(httpsig.Verifier)
|
||||||
|
if !ok {
|
||||||
|
l.Debug("couldn't extract sig verifier")
|
||||||
|
return nil, false, nil // couldn't extract the verifier
|
||||||
}
|
}
|
||||||
|
|
||||||
// The key ID should be given in the signature so that we know where to fetch it from the remote server.
|
|
||||||
// This will be something like https://example.org/users/whatever_requesting_user#main-key
|
|
||||||
requestingPublicKeyID, err := url.Parse(verifier.KeyId())
|
requestingPublicKeyID, err := url.Parse(verifier.KeyId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, fmt.Errorf("could not parse key id into a url: %s", err)
|
l.Debug("couldn't parse public key URL")
|
||||||
}
|
return nil, false, nil // couldn't parse the public key ID url
|
||||||
|
|
||||||
// if the domain is blocked we want to make as few calls towards it as possible, so already bail here if that's the case!
|
|
||||||
blockedDomain, err := f.blockedDomain(requestingPublicKeyID.Host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, fmt.Errorf("could not tell if domain %s was blocked or not: %s", requestingPublicKeyID.Host, err)
|
|
||||||
}
|
|
||||||
if blockedDomain {
|
|
||||||
f.log.Infof("domain %s is blocked", requestingPublicKeyID.Host)
|
|
||||||
return nil, false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
requestingRemoteAccount := >smodel.Account{}
|
requestingRemoteAccount := >smodel.Account{}
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr
|
||||||
return nil, false, fmt.Errorf("could not fetch requested account with username %s: %s", username, err)
|
return nil, false, fmt.Errorf("could not fetch requested account with username %s: %s", username, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
publicKeyOwnerURI, authenticated, err := f.AuthenticateFederatedRequest(requestedAccount.Username, r)
|
publicKeyOwnerURI, authenticated, err := f.AuthenticateFederatedRequest(ctx, requestedAccount.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("request not authenticated: %s", err)
|
l.Debugf("request not authenticated: %s", err)
|
||||||
return ctx, false, err
|
return ctx, false, err
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
package federation
|
package federation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"context"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
|
@ -48,7 +48,7 @@ type Federator interface {
|
||||||
// If the request does not pass authentication, or there's a domain block, nil, false, nil will be returned.
|
// If the request does not pass authentication, or there's a domain block, nil, false, nil will be returned.
|
||||||
//
|
//
|
||||||
// If something goes wrong during authentication, nil, false, and an error will be returned.
|
// If something goes wrong during authentication, nil, false, and an error will be returned.
|
||||||
AuthenticateFederatedRequest(username string, r *http.Request) (*url.URL, bool, error)
|
AuthenticateFederatedRequest(ctx context.Context, username string) (*url.URL, bool, error)
|
||||||
// FingerRemoteAccount performs a webfinger lookup for a remote account, using the .well-known path. It will return the ActivityPub URI for that
|
// FingerRemoteAccount performs a webfinger lookup for a remote account, using the .well-known path. It will return the ActivityPub URI for that
|
||||||
// account, or an error if it doesn't exist or can't be retrieved.
|
// account, or an error if it doesn't exist or can't be retrieved.
|
||||||
FingerRemoteAccount(requestingUsername string, targetUsername string, targetDomain string) (*url.URL, error)
|
FingerRemoteAccount(requestingUsername string, targetUsername string, targetDomain string) (*url.URL, error)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ package processing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
@ -89,7 +90,7 @@ func (p *processor) dereferenceFediRequest(username string, requestingAccountURI
|
||||||
return requestingAccount, nil
|
return requestingAccount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) GetFediUser(requestedUsername string, request *http.Request) (interface{}, gtserror.WithCode) {
|
func (p *processor) GetFediUser(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) {
|
||||||
// get the account the request is referring to
|
// get the account the request is referring to
|
||||||
requestedAccount := >smodel.Account{}
|
requestedAccount := >smodel.Account{}
|
||||||
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
||||||
|
|
@ -98,17 +99,17 @@ func (p *processor) GetFediUser(requestedUsername string, request *http.Request)
|
||||||
|
|
||||||
var requestedPerson vocab.ActivityStreamsPerson
|
var requestedPerson vocab.ActivityStreamsPerson
|
||||||
var err error
|
var err error
|
||||||
if util.IsPublicKeyPath(request.URL) {
|
if util.IsPublicKeyPath(requestURL) {
|
||||||
// if it's a public key path, we don't need to authenticate but we'll only serve the bare minimum user profile needed for the public key
|
// if it's a public key path, we don't need to authenticate but we'll only serve the bare minimum user profile needed for the public key
|
||||||
requestedPerson, err = p.tc.AccountToASMinimal(requestedAccount)
|
requestedPerson, err = p.tc.AccountToASMinimal(requestedAccount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
} else if util.IsUserPath(request.URL) {
|
} else if util.IsUserPath(requestURL) {
|
||||||
// if it's a user path, we want to fully authenticate the request before we serve any data, and then we can serve a more complete profile
|
// if it's a user path, we want to fully authenticate the request before we serve any data, and then we can serve a more complete profile
|
||||||
requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request)
|
requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername)
|
||||||
if err != nil || !authenticated {
|
if err != nil || !authenticated {
|
||||||
return nil, gtserror.NewErrorNotAuthorized(err)
|
return nil, gtserror.NewErrorNotAuthorized(errors.New("not authorized"), "not authorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're already handshaking/dereferencing a remote account, we can skip the dereferencing part
|
// if we're already handshaking/dereferencing a remote account, we can skip the dereferencing part
|
||||||
|
|
@ -144,7 +145,7 @@ func (p *processor) GetFediUser(requestedUsername string, request *http.Request)
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) GetFediFollowers(requestedUsername string, request *http.Request) (interface{}, gtserror.WithCode) {
|
func (p *processor) GetFediFollowers(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) {
|
||||||
// get the account the request is referring to
|
// get the account the request is referring to
|
||||||
requestedAccount := >smodel.Account{}
|
requestedAccount := >smodel.Account{}
|
||||||
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
||||||
|
|
@ -152,9 +153,9 @@ func (p *processor) GetFediFollowers(requestedUsername string, request *http.Req
|
||||||
}
|
}
|
||||||
|
|
||||||
// authenticate the request
|
// authenticate the request
|
||||||
requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request)
|
requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername)
|
||||||
if err != nil || !authenticated {
|
if err != nil || !authenticated {
|
||||||
return nil, gtserror.NewErrorNotAuthorized(err)
|
return nil, gtserror.NewErrorNotAuthorized(errors.New("not authorized"), "not authorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
requestingAccount, err := p.dereferenceFediRequest(requestedUsername, requestingAccountURI)
|
requestingAccount, err := p.dereferenceFediRequest(requestedUsername, requestingAccountURI)
|
||||||
|
|
@ -189,7 +190,7 @@ func (p *processor) GetFediFollowers(requestedUsername string, request *http.Req
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) GetFediFollowing(requestedUsername string, request *http.Request) (interface{}, gtserror.WithCode) {
|
func (p *processor) GetFediFollowing(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) {
|
||||||
// get the account the request is referring to
|
// get the account the request is referring to
|
||||||
requestedAccount := >smodel.Account{}
|
requestedAccount := >smodel.Account{}
|
||||||
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
||||||
|
|
@ -197,9 +198,9 @@ func (p *processor) GetFediFollowing(requestedUsername string, request *http.Req
|
||||||
}
|
}
|
||||||
|
|
||||||
// authenticate the request
|
// authenticate the request
|
||||||
requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request)
|
requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername)
|
||||||
if err != nil || !authenticated {
|
if err != nil || !authenticated {
|
||||||
return nil, gtserror.NewErrorNotAuthorized(err)
|
return nil, gtserror.NewErrorNotAuthorized(errors.New("not authorized"), "not authorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
requestingAccount, err := p.dereferenceFediRequest(requestedUsername, requestingAccountURI)
|
requestingAccount, err := p.dereferenceFediRequest(requestedUsername, requestingAccountURI)
|
||||||
|
|
@ -234,7 +235,7 @@ func (p *processor) GetFediFollowing(requestedUsername string, request *http.Req
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) GetFediStatus(requestedUsername string, requestedStatusID string, request *http.Request) (interface{}, gtserror.WithCode) {
|
func (p *processor) GetFediStatus(ctx context.Context, requestedUsername string, requestedStatusID string, requestURL *url.URL) (interface{}, gtserror.WithCode) {
|
||||||
// get the account the request is referring to
|
// get the account the request is referring to
|
||||||
requestedAccount := >smodel.Account{}
|
requestedAccount := >smodel.Account{}
|
||||||
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
||||||
|
|
@ -242,9 +243,9 @@ func (p *processor) GetFediStatus(requestedUsername string, requestedStatusID st
|
||||||
}
|
}
|
||||||
|
|
||||||
// authenticate the request
|
// authenticate the request
|
||||||
requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request)
|
requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername)
|
||||||
if err != nil || !authenticated {
|
if err != nil || !authenticated {
|
||||||
return nil, gtserror.NewErrorNotAuthorized(err)
|
return nil, gtserror.NewErrorNotAuthorized(errors.New("not authorized"), "not authorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
requestingAccount, err := p.dereferenceFediRequest(requestedUsername, requestingAccountURI)
|
requestingAccount, err := p.dereferenceFediRequest(requestedUsername, requestingAccountURI)
|
||||||
|
|
@ -294,7 +295,7 @@ func (p *processor) GetFediStatus(requestedUsername string, requestedStatusID st
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) GetWebfingerAccount(requestedUsername string, request *http.Request) (*apimodel.WellKnownResponse, gtserror.WithCode) {
|
func (p *processor) GetWebfingerAccount(ctx context.Context, requestedUsername string, requestURL *url.URL) (*apimodel.WellKnownResponse, gtserror.WithCode) {
|
||||||
// get the account the request is referring to
|
// get the account the request is referring to
|
||||||
requestedAccount := >smodel.Account{}
|
requestedAccount := >smodel.Account{}
|
||||||
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ package processing
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
|
@ -158,22 +159,22 @@ type Processor interface {
|
||||||
|
|
||||||
// GetFediUser handles the getting of a fedi/activitypub representation of a user/account, performing appropriate authentication
|
// GetFediUser handles the getting of a fedi/activitypub representation of a user/account, performing appropriate authentication
|
||||||
// before returning a JSON serializable interface to the caller.
|
// before returning a JSON serializable interface to the caller.
|
||||||
GetFediUser(requestedUsername string, request *http.Request) (interface{}, gtserror.WithCode)
|
GetFediUser(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode)
|
||||||
|
|
||||||
// GetFediFollowers handles the getting of a fedi/activitypub representation of a user/account's followers, performing appropriate
|
// GetFediFollowers handles the getting of a fedi/activitypub representation of a user/account's followers, performing appropriate
|
||||||
// authentication before returning a JSON serializable interface to the caller.
|
// authentication before returning a JSON serializable interface to the caller.
|
||||||
GetFediFollowers(requestedUsername string, request *http.Request) (interface{}, gtserror.WithCode)
|
GetFediFollowers(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode)
|
||||||
|
|
||||||
// GetFediFollowing handles the getting of a fedi/activitypub representation of a user/account's following, performing appropriate
|
// GetFediFollowing handles the getting of a fedi/activitypub representation of a user/account's following, performing appropriate
|
||||||
// authentication before returning a JSON serializable interface to the caller.
|
// authentication before returning a JSON serializable interface to the caller.
|
||||||
GetFediFollowing(requestedUsername string, request *http.Request) (interface{}, gtserror.WithCode)
|
GetFediFollowing(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode)
|
||||||
|
|
||||||
// GetFediStatus handles the getting of a fedi/activitypub representation of a particular status, performing appropriate
|
// GetFediStatus handles the getting of a fedi/activitypub representation of a particular status, performing appropriate
|
||||||
// authentication before returning a JSON serializable interface to the caller.
|
// authentication before returning a JSON serializable interface to the caller.
|
||||||
GetFediStatus(requestedUsername string, requestedStatusID string, request *http.Request) (interface{}, gtserror.WithCode)
|
GetFediStatus(ctx context.Context, requestedUsername string, requestedStatusID string, requestURL *url.URL) (interface{}, gtserror.WithCode)
|
||||||
|
|
||||||
// GetWebfingerAccount handles the GET for a webfinger resource. Most commonly, it will be used for returning account lookups.
|
// GetWebfingerAccount handles the GET for a webfinger resource. Most commonly, it will be used for returning account lookups.
|
||||||
GetWebfingerAccount(requestedUsername string, request *http.Request) (*apimodel.WellKnownResponse, gtserror.WithCode)
|
GetWebfingerAccount(ctx context.Context, requestedUsername string, requestURL *url.URL) (*apimodel.WellKnownResponse, gtserror.WithCode)
|
||||||
|
|
||||||
// GetNodeInfoRel returns a well known response giving the path to node info.
|
// GetNodeInfoRel returns a well known response giving the path to node info.
|
||||||
GetNodeInfoRel(request *http.Request) (*apimodel.WellKnownResponse, gtserror.WithCode)
|
GetNodeInfoRel(request *http.Request) (*apimodel.WellKnownResponse, gtserror.WithCode)
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,8 @@ const (
|
||||||
// APRequestingActorIRI can be used to set and retrieve the actor of an incoming federation request.
|
// APRequestingActorIRI can be used to set and retrieve the actor of an incoming federation request.
|
||||||
// This will usually be the owner of whatever activity is being posted.
|
// This will usually be the owner of whatever activity is being posted.
|
||||||
APRequestingActorIRI APContextKey = "requestingActorIRI"
|
APRequestingActorIRI APContextKey = "requestingActorIRI"
|
||||||
// APRequestingPublicKeyID can be used to set and retrieve the public key ID of an incoming federation request.
|
// APRequestingPublicKeyVerifier can be used to set and retrieve the public key verifier of an incoming federation request.
|
||||||
APRequestingPublicKeyID APContextKey = "requestingPublicKeyID"
|
APRequestingPublicKeyVerifier APContextKey = "requestingPublicKeyVerifier"
|
||||||
// APFromFederatorChanKey can be used to pass a pointer to the fromFederator channel into the federator for use in callbacks.
|
// APFromFederatorChanKey can be used to pass a pointer to the fromFederator channel into the federator for use in callbacks.
|
||||||
APFromFederatorChanKey APContextKey = "fromFederatorChan"
|
APFromFederatorChanKey APContextKey = "fromFederatorChan"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package visibility
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
|
@ -17,21 +16,23 @@ func (f *filter) StatusVisible(targetStatus *gtsmodel.Status, requestingAccount
|
||||||
"statusID": targetStatus.ID,
|
"statusID": targetStatus.ID,
|
||||||
})
|
})
|
||||||
|
|
||||||
uri, err := url.Parse(targetStatus.URI)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("StatusVisible: error parsing uri: %s", targetStatus.URI)
|
|
||||||
}
|
|
||||||
if blocked, err := f.blockedDomain(uri.Host); blocked || err != nil {
|
|
||||||
l.Debugf("domain %s is blocked", uri.Host)
|
|
||||||
return blocked, err
|
|
||||||
}
|
|
||||||
aaaaaaaaaa
|
|
||||||
relevantAccounts, err := f.pullRelevantAccountsFromStatus(targetStatus)
|
relevantAccounts, err := f.pullRelevantAccountsFromStatus(targetStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error pulling relevant accounts for status %s: %s", targetStatus.ID, err)
|
l.Debugf("error pulling relevant accounts for status %s: %s", targetStatus.ID, err)
|
||||||
|
return false, fmt.Errorf("error pulling relevant accounts for status %s: %s", targetStatus.ID, err)
|
||||||
}
|
}
|
||||||
targetAccount := relevantAccounts.StatusAuthor
|
|
||||||
|
|
||||||
|
domainBlocked, err := f.blockedRelevant(relevantAccounts)
|
||||||
|
if err != nil {
|
||||||
|
l.Debugf("error checking domain block: %s", err)
|
||||||
|
return false, fmt.Errorf("error checking domain block: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if domainBlocked {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
targetAccount := relevantAccounts.StatusAuthor
|
||||||
// if target account is suspended then don't show the status
|
// if target account is suspended then don't show the status
|
||||||
if !targetAccount.SuspendedAt.IsZero() {
|
if !targetAccount.SuspendedAt.IsZero() {
|
||||||
l.Trace("target account suspended at is not zero")
|
l.Trace("target account suspended at is not zero")
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ type relevantAccounts struct {
|
||||||
MentionedAccounts []*gtsmodel.Account
|
MentionedAccounts []*gtsmodel.Account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// blockedDomain checks whether the given domain is blocked by us or not
|
||||||
func (f *filter) blockedDomain(host string) (bool, error) {
|
func (f *filter) blockedDomain(host string) (bool, error) {
|
||||||
b := >smodel.DomainBlock{}
|
b := >smodel.DomainBlock{}
|
||||||
err := f.db.GetWhere([]db.Where{{Key: "domain", Value: host, CaseInsensitive: true}}, b)
|
err := f.db.GetWhere([]db.Where{{Key: "domain", Value: host, CaseInsensitive: true}}, b)
|
||||||
|
|
@ -97,3 +98,59 @@ func (f *filter) blockedDomain(host string) (bool, error) {
|
||||||
// there's an actual error
|
// there's an actual error
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// blockedRelevant checks through all relevant accounts attached to a status
|
||||||
|
// to make sure none of them are domain blocked by this instance.
|
||||||
|
func (f *filter) blockedRelevant(r *relevantAccounts) (bool, error) {
|
||||||
|
if r.StatusAuthor != nil {
|
||||||
|
b, err := f.blockedDomain(r.StatusAuthor.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ReplyToAccount != nil {
|
||||||
|
b, err := f.blockedDomain(r.ReplyToAccount.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.BoostedAccount != nil {
|
||||||
|
b, err := f.blockedDomain(r.BoostedAccount.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.BoostedReplyToAccount != nil {
|
||||||
|
b, err := f.blockedDomain(r.BoostedReplyToAccount.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range r.MentionedAccounts {
|
||||||
|
b, err := f.blockedDomain(a.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue