diff --git a/internal/api/s2s/user/followers.go b/internal/api/s2s/user/followers.go index 9ccf9c4d5..6e33407d0 100644 --- a/internal/api/s2s/user/followers.go +++ b/internal/api/s2s/user/followers.go @@ -19,10 +19,12 @@ package user import ( + "context" "net/http" "github.com/gin-gonic/gin" "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. @@ -46,9 +48,14 @@ func (m *Module) FollowersGETHandler(c *gin.Context) { } l.Tracef("negotiated format: %s", format) - // make a copy of the context to pass along so we don't break anything - cp := c.Copy() - user, err := m.processor.GetFediFollowers(requestedUsername, cp.Request) // GetFediUser handles auth as well + // 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) + } + + user, err := m.processor.GetFediFollowers(ctx, requestedUsername, c.Request.URL) // GetFediUser handles auth as well if err != nil { l.Info(err.Error()) c.JSON(err.Code(), gin.H{"error": err.Safe()}) diff --git a/internal/api/s2s/user/following.go b/internal/api/s2s/user/following.go index f19965c26..bdf815b05 100644 --- a/internal/api/s2s/user/following.go +++ b/internal/api/s2s/user/following.go @@ -19,10 +19,12 @@ package user import ( + "context" "net/http" "github.com/gin-gonic/gin" "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. @@ -46,9 +48,14 @@ func (m *Module) FollowingGETHandler(c *gin.Context) { } l.Tracef("negotiated format: %s", format) - // make a copy of the context to pass along so we don't break anything - cp := c.Copy() - user, err := m.processor.GetFediFollowing(requestedUsername, cp.Request) // handles auth as well + // 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) + } + + user, err := m.processor.GetFediFollowing(ctx, requestedUsername, c.Request.URL) // handles auth as well if err != nil { l.Info(err.Error()) c.JSON(err.Code(), gin.H{"error": err.Safe()}) diff --git a/internal/api/s2s/user/inboxpost.go b/internal/api/s2s/user/inboxpost.go index a51cd8add..98442af13 100644 --- a/internal/api/s2s/user/inboxpost.go +++ b/internal/api/s2s/user/inboxpost.go @@ -19,11 +19,13 @@ package user import ( + "context" "net/http" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/util" ) // InboxPOSTHandler deals with incoming POST requests to an actor's inbox. @@ -40,7 +42,14 @@ func (m *Module) InboxPOSTHandler(c *gin.Context) { 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 withCode, ok := err.(gtserror.WithCode); ok { l.Debug(withCode.Error()) diff --git a/internal/api/s2s/user/publickeyget.go b/internal/api/s2s/user/publickeyget.go index b6aadedb2..bb1844e0e 100644 --- a/internal/api/s2s/user/publickeyget.go +++ b/internal/api/s2s/user/publickeyget.go @@ -1,10 +1,12 @@ package user import ( + "context" "net/http" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" + "github.com/superseriousbusiness/gotosocial/internal/util" ) // 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) - // make a copy of the context to pass along so we don't break anything - cp := c.Copy() - user, err := m.processor.GetFediUser(requestedUsername, cp.Request) // GetFediUser handles auth as well + // 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) + } + + user, err := m.processor.GetFediUser(ctx, requestedUsername, c.Request.URL) // GetFediUser handles auth as well if err != nil { l.Info(err.Error()) c.JSON(err.Code(), gin.H{"error": err.Safe()}) diff --git a/internal/api/s2s/user/statusget.go b/internal/api/s2s/user/statusget.go index 22774ae2c..37621d1de 100644 --- a/internal/api/s2s/user/statusget.go +++ b/internal/api/s2s/user/statusget.go @@ -1,10 +1,12 @@ package user import ( + "context" "net/http" "github.com/gin-gonic/gin" "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. @@ -34,9 +36,14 @@ func (m *Module) StatusGETHandler(c *gin.Context) { } l.Tracef("negotiated format: %s", format) - // make a copy of the context to pass along so we don't break anything - cp := c.Copy() - status, err := m.processor.GetFediStatus(requestedUsername, requestedStatusID, cp.Request) // handles auth as well + // 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) + } + + status, err := m.processor.GetFediStatus(ctx, requestedUsername, requestedStatusID, c.Request.URL) // handles auth as well if err != nil { l.Info(err.Error()) c.JSON(err.Code(), gin.H{"error": err.Safe()}) diff --git a/internal/api/s2s/user/userget.go b/internal/api/s2s/user/userget.go index 9d268e121..ac49b1529 100644 --- a/internal/api/s2s/user/userget.go +++ b/internal/api/s2s/user/userget.go @@ -19,10 +19,12 @@ package user import ( + "context" "net/http" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" + "github.com/superseriousbusiness/gotosocial/internal/util" ) // 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) - // make a copy of the context to pass along so we don't break anything - cp := c.Copy() - user, err := m.processor.GetFediUser(requestedUsername, cp.Request) // GetFediUser handles auth as well + // 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) + } + + user, err := m.processor.GetFediUser(ctx, requestedUsername, c.Request.URL) // GetFediUser handles auth as well if err != nil { l.Info(err.Error()) c.JSON(err.Code(), gin.H{"error": err.Safe()}) diff --git a/internal/api/s2s/webfinger/webfingerget.go b/internal/api/s2s/webfinger/webfingerget.go index 30e089162..416a75f3b 100644 --- a/internal/api/s2s/webfinger/webfingerget.go +++ b/internal/api/s2s/webfinger/webfingerget.go @@ -19,12 +19,14 @@ package webfinger import ( + "context" "fmt" "net/http" "strings" "github.com/gin-gonic/gin" "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 @@ -68,7 +70,14 @@ func (m *Module) WebfingerGETRequest(c *gin.Context) { 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 { l.Debugf("aborting request with an error: %s", err.Error()) c.JSON(err.Code(), gin.H{"error": err.Safe()}) diff --git a/internal/api/security/security.go b/internal/api/security/security.go index 7298bc7cb..d8f6b0fe3 100644 --- a/internal/api/security/security.go +++ b/internal/api/security/security.go @@ -24,6 +24,7 @@ import ( "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/api" "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/router" ) @@ -33,18 +34,21 @@ const robotsPath = "/robots.txt" type Module struct { config *config.Config log *logrus.Logger + db db.DB } // 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{ config: config, log: log, + db: db, } } // Route attaches security middleware to the given router func (m *Module) Route(s router.Router) error { + s.AttachMiddleware(m.SignatureCheck) s.AttachMiddleware(m.FlocBlock) s.AttachMiddleware(m.ExtraHeaders) s.AttachMiddleware(m.UserAgentBlock) diff --git a/internal/api/security/signaturecheck.go b/internal/api/security/signaturecheck.go new file mode 100644 index 000000000..fdc2625a0 --- /dev/null +++ b/internal/api/security/signaturecheck.go @@ -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 +} diff --git a/internal/cliactions/server/server.go b/internal/cliactions/server/server.go index 0a416e4e7..4864dacb4 100644 --- a/internal/cliactions/server/server.go +++ b/internal/cliactions/server/server.go @@ -138,7 +138,7 @@ var Start cliactions.GTSAction = func(ctx context.Context, c *config.Config, log fileServerModule := fileserver.New(c, processor, log) adminModule := admin.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) apis := []api.ClientModule{ diff --git a/internal/cliactions/testrig/testrig.go b/internal/cliactions/testrig/testrig.go index e960d6691..a1d2d7af7 100644 --- a/internal/cliactions/testrig/testrig.go +++ b/internal/cliactions/testrig/testrig.go @@ -84,7 +84,7 @@ var Start cliactions.GTSAction = func(ctx context.Context, _ *config.Config, log fileServerModule := fileserver.New(c, processor, log) adminModule := admin.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) apis := []api.ClientModule{ diff --git a/internal/federation/authenticate.go b/internal/federation/authenticate.go index f7ca51ec3..0cb8db6dc 100644 --- a/internal/federation/authenticate.go +++ b/internal/federation/authenticate.go @@ -25,7 +25,6 @@ import ( "encoding/pem" "errors" "fmt" - "net/http" "net/url" "strings" @@ -35,6 +34,7 @@ import ( "github.com/go-fed/httpsig" "github.com/superseriousbusiness/gotosocial/internal/db" "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. // 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 pkOwnerURI *url.URL var err error - // set this extra field for signature validation - r.Header.Set("host", f.config.Host) - - verifier, err := httpsig.NewVerifier(r) - if err != nil { - return nil, false, fmt.Errorf("could not create http sig verifier: %s", err) + // thanks to signaturecheck.go in the security package, we should already have a signature verifier set on the context + vi := ctx.Value(util.APRequestingPublicKeyVerifier) + if vi == nil { + l.Debug("request wasn't signed") + return nil, false, nil // request wasn't signed + } + + 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()) if err != nil { - return nil, false, fmt.Errorf("could not parse key id into a url: %s", err) - } - - // 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 + l.Debug("couldn't parse public key URL") + return nil, false, nil // couldn't parse the public key ID url } requestingRemoteAccount := >smodel.Account{} diff --git a/internal/federation/federatingprotocol.go b/internal/federation/federatingprotocol.go index 299fcf8f6..429539e37 100644 --- a/internal/federation/federatingprotocol.go +++ b/internal/federation/federatingprotocol.go @@ -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) } - publicKeyOwnerURI, authenticated, err := f.AuthenticateFederatedRequest(requestedAccount.Username, r) + publicKeyOwnerURI, authenticated, err := f.AuthenticateFederatedRequest(ctx, requestedAccount.Username) if err != nil { l.Debugf("request not authenticated: %s", err) return ctx, false, err diff --git a/internal/federation/federator.go b/internal/federation/federator.go index 1a72fa348..a5ffb3de8 100644 --- a/internal/federation/federator.go +++ b/internal/federation/federator.go @@ -19,7 +19,7 @@ package federation import ( - "net/http" + "context" "net/url" "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 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 // account, or an error if it doesn't exist or can't be retrieved. FingerRemoteAccount(requestingUsername string, targetUsername string, targetDomain string) (*url.URL, error) diff --git a/internal/processing/federation.go b/internal/processing/federation.go index 41e6cf2fe..6299d5e7e 100644 --- a/internal/processing/federation.go +++ b/internal/processing/federation.go @@ -20,6 +20,7 @@ package processing import ( "context" + "errors" "fmt" "net/http" "net/url" @@ -89,7 +90,7 @@ func (p *processor) dereferenceFediRequest(username string, requestingAccountURI 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 requestedAccount := >smodel.Account{} 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 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 requestedPerson, err = p.tc.AccountToASMinimal(requestedAccount) if err != nil { 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 - requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request) + requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername) 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 @@ -144,7 +145,7 @@ func (p *processor) GetFediUser(requestedUsername string, request *http.Request) 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 requestedAccount := >smodel.Account{} 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 - requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request) + requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername) 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) @@ -189,7 +190,7 @@ func (p *processor) GetFediFollowers(requestedUsername string, request *http.Req 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 requestedAccount := >smodel.Account{} 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 - requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request) + requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername) 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) @@ -234,7 +235,7 @@ func (p *processor) GetFediFollowing(requestedUsername string, request *http.Req 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 requestedAccount := >smodel.Account{} if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil { @@ -242,9 +243,9 @@ func (p *processor) GetFediStatus(requestedUsername string, requestedStatusID st } // authenticate the request - requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request) + requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername) 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) @@ -294,7 +295,7 @@ func (p *processor) GetFediStatus(requestedUsername string, requestedStatusID st 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 requestedAccount := >smodel.Account{} if err := p.db.GetLocalAccountByUsername(requestedUsername, requestedAccount); err != nil { diff --git a/internal/processing/processor.go b/internal/processing/processor.go index ffc3ed156..9ede72967 100644 --- a/internal/processing/processor.go +++ b/internal/processing/processor.go @@ -21,6 +21,7 @@ package processing import ( "context" "net/http" + "net/url" "github.com/sirupsen/logrus" 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 // 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 // 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 // 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 // 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(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(request *http.Request) (*apimodel.WellKnownResponse, gtserror.WithCode) diff --git a/internal/util/uri.go b/internal/util/uri.go index 2bfdd6c40..5eb291628 100644 --- a/internal/util/uri.go +++ b/internal/util/uri.go @@ -66,8 +66,8 @@ const ( // 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. APRequestingActorIRI APContextKey = "requestingActorIRI" - // APRequestingPublicKeyID can be used to set and retrieve the public key ID of an incoming federation request. - APRequestingPublicKeyID APContextKey = "requestingPublicKeyID" + // APRequestingPublicKeyVerifier can be used to set and retrieve the public key verifier of an incoming federation request. + APRequestingPublicKeyVerifier APContextKey = "requestingPublicKeyVerifier" // APFromFederatorChanKey can be used to pass a pointer to the fromFederator channel into the federator for use in callbacks. APFromFederatorChanKey APContextKey = "fromFederatorChan" ) diff --git a/internal/visibility/statusvisible.go b/internal/visibility/statusvisible.go index 6c3b40500..e0b3a8d9e 100644 --- a/internal/visibility/statusvisible.go +++ b/internal/visibility/statusvisible.go @@ -2,7 +2,6 @@ package visibility import ( "errors" - "net/url" "fmt" @@ -17,21 +16,23 @@ func (f *filter) StatusVisible(targetStatus *gtsmodel.Status, requestingAccount "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) if err != nil { 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 !targetAccount.SuspendedAt.IsZero() { l.Trace("target account suspended at is not zero") diff --git a/internal/visibility/util.go b/internal/visibility/util.go index 47ced2884..dd4470f11 100644 --- a/internal/visibility/util.go +++ b/internal/visibility/util.go @@ -81,6 +81,7 @@ type relevantAccounts struct { MentionedAccounts []*gtsmodel.Account } +// blockedDomain checks whether the given domain is blocked by us or not func (f *filter) blockedDomain(host string) (bool, error) { b := >smodel.DomainBlock{} 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 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 +}