check domain blocks way earlier on

This commit is contained in:
tsmethurst 2021-06-30 14:22:46 +02:00
commit 5bf7b46cf4
19 changed files with 255 additions and 77 deletions

View file

@ -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()})

View file

@ -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()})

View file

@ -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())

View file

@ -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()})

View file

@ -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()})

View file

@ -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()})

View file

@ -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()})

View file

@ -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)

View 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 := &gtsmodel.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
}

View file

@ -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{

View file

@ -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{

View file

@ -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 := &gtsmodel.Account{} requestingRemoteAccount := &gtsmodel.Account{}

View file

@ -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

View file

@ -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)

View file

@ -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 := &gtsmodel.Account{} requestedAccount := &gtsmodel.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 := &gtsmodel.Account{} requestedAccount := &gtsmodel.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 := &gtsmodel.Account{} requestedAccount := &gtsmodel.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 := &gtsmodel.Account{} requestedAccount := &gtsmodel.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 := &gtsmodel.Account{} requestedAccount := &gtsmodel.Account{}
if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil { if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil {

View file

@ -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)

View file

@ -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"
) )

View file

@ -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")

View file

@ -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 := &gtsmodel.DomainBlock{} b := &gtsmodel.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
}