summaryrefslogtreecommitdiff
path: root/internal/api
diff options
context:
space:
mode:
Diffstat (limited to 'internal/api')
-rw-r--r--internal/api/client/account/accountupdate_test.go2
-rw-r--r--internal/api/client/account/statuses.go6
-rw-r--r--internal/api/client/admin/admin.go17
-rw-r--r--internal/api/client/admin/domainblockcreate.go70
-rw-r--r--internal/api/client/admin/domainblockdelete.go47
-rw-r--r--internal/api/client/admin/domainblockget.go60
-rw-r--r--internal/api/client/admin/domainblocksget.go54
-rw-r--r--internal/api/client/fileserver/servefile_test.go2
-rw-r--r--internal/api/client/instance/instancepatch.go1
-rw-r--r--internal/api/client/media/mediacreate_test.go2
-rw-r--r--internal/api/client/status/statusboost_test.go2
-rw-r--r--internal/api/client/status/statuscreate_test.go2
-rw-r--r--internal/api/client/status/statusfave_test.go2
-rw-r--r--internal/api/client/status/statusfavedby_test.go2
-rw-r--r--internal/api/client/status/statusget_test.go2
-rw-r--r--internal/api/client/status/statusunfave_test.go2
-rw-r--r--internal/api/model/domainblock.go43
-rw-r--r--internal/api/s2s/user/followers.go13
-rw-r--r--internal/api/s2s/user/following.go13
-rw-r--r--internal/api/s2s/user/inboxpost.go11
-rw-r--r--internal/api/s2s/user/publickeyget.go13
-rw-r--r--internal/api/s2s/user/statusget.go13
-rw-r--r--internal/api/s2s/user/userget.go13
-rw-r--r--internal/api/s2s/user/userget_test.go4
-rw-r--r--internal/api/s2s/webfinger/webfingerget.go11
-rw-r--r--internal/api/security/security.go6
-rw-r--r--internal/api/security/signaturecheck.go69
27 files changed, 448 insertions, 34 deletions
diff --git a/internal/api/client/account/accountupdate_test.go b/internal/api/client/account/accountupdate_test.go
index ba7faa794..341b865ff 100644
--- a/internal/api/client/account/accountupdate_test.go
+++ b/internal/api/client/account/accountupdate_test.go
@@ -53,7 +53,7 @@ func (suite *AccountUpdateTestSuite) SetupTest() {
suite.db = testrig.NewTestDB()
suite.storage = testrig.NewTestStorage()
suite.log = testrig.NewTestLog()
- suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
+ suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)), suite.storage)
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
suite.accountModule = account.New(suite.config, suite.processor, suite.log).(*account.Module)
testrig.StandardDBSetup(suite.db)
diff --git a/internal/api/client/account/statuses.go b/internal/api/client/account/statuses.go
index f03a942f3..c92e85cee 100644
--- a/internal/api/client/account/statuses.go
+++ b/internal/api/client/account/statuses.go
@@ -82,7 +82,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
maxID = maxIDString
}
- pinned := false
+ pinnedOnly := false
pinnedString := c.Query(PinnedKey)
if pinnedString != "" {
i, err := strconv.ParseBool(pinnedString)
@@ -91,7 +91,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse pinned query param"})
return
}
- pinned = i
+ pinnedOnly = i
}
mediaOnly := false
@@ -106,7 +106,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
mediaOnly = i
}
- statuses, errWithCode := m.processor.AccountStatusesGet(authed, targetAcctID, limit, excludeReplies, maxID, pinned, mediaOnly)
+ statuses, errWithCode := m.processor.AccountStatusesGet(authed, targetAcctID, limit, excludeReplies, maxID, pinnedOnly, mediaOnly)
if errWithCode != nil {
l.Debugf("error from processor account statuses get: %s", errWithCode)
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
diff --git a/internal/api/client/admin/admin.go b/internal/api/client/admin/admin.go
index b33813a7d..b8b94be76 100644
--- a/internal/api/client/admin/admin.go
+++ b/internal/api/client/admin/admin.go
@@ -29,10 +29,19 @@ import (
)
const (
- // BasePath is the base API path for this module
+ // BasePath is the base API path for this module.
BasePath = "/api/v1/admin"
- // EmojiPath is used for posting/deleting custom emojis
+ // EmojiPath is used for posting/deleting custom emojis.
EmojiPath = BasePath + "/custom_emojis"
+ // DomainBlocksPath is used for posting domain blocks.
+ DomainBlocksPath = BasePath + "/domain_blocks"
+ // DomainBlockPath is used for interacting with a single domain block.
+ DomainBlockPath = DomainBlocksPath + "/:" + IDKey
+
+ // ExportQueryKey is for requesting a public export of some data.
+ ExportQueryKey = "export"
+ // IDKey specifies the ID of a single item being interacted with.
+ IDKey = "id"
)
// Module implements the ClientAPIModule interface for admin-related actions (reports, emojis, etc)
@@ -54,5 +63,9 @@ func New(config *config.Config, processor processing.Processor, log *logrus.Logg
// Route attaches all routes from this module to the given router
func (m *Module) Route(r router.Router) error {
r.AttachHandler(http.MethodPost, EmojiPath, m.emojiCreatePOSTHandler)
+ r.AttachHandler(http.MethodPost, DomainBlocksPath, m.DomainBlocksPOSTHandler)
+ r.AttachHandler(http.MethodGet, DomainBlocksPath, m.DomainBlocksGETHandler)
+ r.AttachHandler(http.MethodGet, DomainBlockPath, m.DomainBlockGETHandler)
+ r.AttachHandler(http.MethodDelete, DomainBlockPath, m.DomainBlockDELETEHandler)
return nil
}
diff --git a/internal/api/client/admin/domainblockcreate.go b/internal/api/client/admin/domainblockcreate.go
new file mode 100644
index 000000000..5d3df58de
--- /dev/null
+++ b/internal/api/client/admin/domainblockcreate.go
@@ -0,0 +1,70 @@
+package admin
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+ "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+)
+
+// DomainBlocksPOSTHandler deals with the creation of a new domain block.
+func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) {
+ l := m.log.WithFields(logrus.Fields{
+ "func": "DomainBlocksPOSTHandler",
+ "request_uri": c.Request.RequestURI,
+ "user_agent": c.Request.UserAgent(),
+ "origin_ip": c.ClientIP(),
+ })
+
+ // make sure we're authed with an admin account
+ authed, err := oauth.Authed(c, true, true, true, true)
+ if err != nil {
+ l.Debugf("couldn't auth: %s", err)
+ c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
+ return
+ }
+ if !authed.User.Admin {
+ l.Debugf("user %s not an admin", authed.User.ID)
+ c.JSON(http.StatusForbidden, gin.H{"error": "not an admin"})
+ return
+ }
+
+ // extract the media create form from the request context
+ l.Tracef("parsing request form: %+v", c.Request.Form)
+ form := &model.DomainBlockCreateRequest{}
+ if err := c.ShouldBind(form); err != nil {
+ l.Debugf("error parsing form %+v: %s", c.Request.Form, err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("could not parse form: %s", err)})
+ return
+ }
+
+ // Give the fields on the request form a first pass to make sure the request is superficially valid.
+ l.Tracef("validating form %+v", form)
+ if err := validateCreateDomainBlock(form); err != nil {
+ l.Debugf("error validating form: %s", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ domainBlock, err := m.processor.AdminDomainBlockCreate(authed, form)
+ if err != nil {
+ l.Debugf("error creating domain block: %s", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ c.JSON(http.StatusOK, domainBlock)
+}
+
+func validateCreateDomainBlock(form *model.DomainBlockCreateRequest) error {
+ // add some more validation here later if necessary
+ if form.Domain == "" {
+ return errors.New("empty domain provided")
+ }
+
+ return nil
+}
diff --git a/internal/api/client/admin/domainblockdelete.go b/internal/api/client/admin/domainblockdelete.go
new file mode 100644
index 000000000..d8f4586f9
--- /dev/null
+++ b/internal/api/client/admin/domainblockdelete.go
@@ -0,0 +1,47 @@
+package admin
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+)
+
+// DomainBlockDELETEHandler deals with the delete of an existing domain block.
+func (m *Module) DomainBlockDELETEHandler(c *gin.Context) {
+ l := m.log.WithFields(logrus.Fields{
+ "func": "DomainBlockDELETEHandler",
+ "request_uri": c.Request.RequestURI,
+ "user_agent": c.Request.UserAgent(),
+ "origin_ip": c.ClientIP(),
+ })
+
+ // make sure we're authed with an admin account
+ authed, err := oauth.Authed(c, true, true, true, true)
+ if err != nil {
+ l.Debugf("couldn't auth: %s", err)
+ c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
+ return
+ }
+ if !authed.User.Admin {
+ l.Debugf("user %s not an admin", authed.User.ID)
+ c.JSON(http.StatusForbidden, gin.H{"error": "not an admin"})
+ return
+ }
+
+ domainBlockID := c.Param(IDKey)
+ if domainBlockID == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "no domain block id provided"})
+ return
+ }
+
+ domainBlock, errWithCode := m.processor.AdminDomainBlockDelete(authed, domainBlockID)
+ if errWithCode != nil {
+ l.Debugf("error deleting domain block: %s", errWithCode.Error())
+ c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
+ return
+ }
+
+ c.JSON(http.StatusOK, domainBlock)
+}
diff --git a/internal/api/client/admin/domainblockget.go b/internal/api/client/admin/domainblockget.go
new file mode 100644
index 000000000..009794f8a
--- /dev/null
+++ b/internal/api/client/admin/domainblockget.go
@@ -0,0 +1,60 @@
+package admin
+
+import (
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+)
+
+// DomainBlockGETHandler returns one existing domain block, identified by its id.
+func (m *Module) DomainBlockGETHandler(c *gin.Context) {
+ l := m.log.WithFields(logrus.Fields{
+ "func": "DomainBlockGETHandler",
+ "request_uri": c.Request.RequestURI,
+ "user_agent": c.Request.UserAgent(),
+ "origin_ip": c.ClientIP(),
+ })
+
+ // make sure we're authed with an admin account
+ authed, err := oauth.Authed(c, true, true, true, true)
+ if err != nil {
+ l.Debugf("couldn't auth: %s", err)
+ c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
+ return
+ }
+ if !authed.User.Admin {
+ l.Debugf("user %s not an admin", authed.User.ID)
+ c.JSON(http.StatusForbidden, gin.H{"error": "not an admin"})
+ return
+ }
+
+ domainBlockID := c.Param(IDKey)
+ if domainBlockID == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "no domain block id provided"})
+ return
+ }
+
+ export := false
+ exportString := c.Query(ExportQueryKey)
+ if exportString != "" {
+ i, err := strconv.ParseBool(exportString)
+ if err != nil {
+ l.Debugf("error parsing export string: %s", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse export query param"})
+ return
+ }
+ export = i
+ }
+
+ domainBlock, err := m.processor.AdminDomainBlockGet(authed, domainBlockID, export)
+ if err != nil {
+ l.Debugf("error getting domain block: %s", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ c.JSON(http.StatusOK, domainBlock)
+}
diff --git a/internal/api/client/admin/domainblocksget.go b/internal/api/client/admin/domainblocksget.go
new file mode 100644
index 000000000..1e873a302
--- /dev/null
+++ b/internal/api/client/admin/domainblocksget.go
@@ -0,0 +1,54 @@
+package admin
+
+import (
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+)
+
+// DomainBlocksGETHandler returns a list of all existing domain blocks.
+func (m *Module) DomainBlocksGETHandler(c *gin.Context) {
+ l := m.log.WithFields(logrus.Fields{
+ "func": "DomainBlocksGETHandler",
+ "request_uri": c.Request.RequestURI,
+ "user_agent": c.Request.UserAgent(),
+ "origin_ip": c.ClientIP(),
+ })
+
+ // make sure we're authed with an admin account
+ authed, err := oauth.Authed(c, true, true, true, true)
+ if err != nil {
+ l.Debugf("couldn't auth: %s", err)
+ c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
+ return
+ }
+ if !authed.User.Admin {
+ l.Debugf("user %s not an admin", authed.User.ID)
+ c.JSON(http.StatusForbidden, gin.H{"error": "not an admin"})
+ return
+ }
+
+ export := false
+ exportString := c.Query(ExportQueryKey)
+ if exportString != "" {
+ i, err := strconv.ParseBool(exportString)
+ if err != nil {
+ l.Debugf("error parsing export string: %s", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse export query param"})
+ return
+ }
+ export = i
+ }
+
+ domainBlocks, err := m.processor.AdminDomainBlocksGet(authed, export)
+ if err != nil {
+ l.Debugf("error getting domain blocks: %s", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ c.JSON(http.StatusOK, domainBlocks)
+}
diff --git a/internal/api/client/fileserver/servefile_test.go b/internal/api/client/fileserver/servefile_test.go
index 2646da24d..cb503facb 100644
--- a/internal/api/client/fileserver/servefile_test.go
+++ b/internal/api/client/fileserver/servefile_test.go
@@ -78,7 +78,7 @@ func (suite *ServeFileTestSuite) SetupSuite() {
suite.db = testrig.NewTestDB()
suite.log = testrig.NewTestLog()
suite.storage = testrig.NewTestStorage()
- suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
+ suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)), suite.storage)
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
suite.tc = testrig.NewTestTypeConverter(suite.db)
suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage)
diff --git a/internal/api/client/instance/instancepatch.go b/internal/api/client/instance/instancepatch.go
index ace7674c0..250e836be 100644
--- a/internal/api/client/instance/instancepatch.go
+++ b/internal/api/client/instance/instancepatch.go
@@ -8,6 +8,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
+// InstanceUpdatePATCHHandler allows an admin to update the instance information served at /api/v1/instance
func (m *Module) InstanceUpdatePATCHHandler(c *gin.Context) {
l := m.log.WithField("func", "InstanceUpdatePATCHHandler")
authed, err := oauth.Authed(c, true, true, true, true)
diff --git a/internal/api/client/media/mediacreate_test.go b/internal/api/client/media/mediacreate_test.go
index bed588017..89a77a729 100644
--- a/internal/api/client/media/mediacreate_test.go
+++ b/internal/api/client/media/mediacreate_test.go
@@ -84,7 +84,7 @@ func (suite *MediaCreateTestSuite) SetupSuite() {
suite.tc = testrig.NewTestTypeConverter(suite.db)
suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage)
suite.oauthServer = testrig.NewTestOauthServer(suite.db)
- suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
+ suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)), suite.storage)
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
// setup module being tested
diff --git a/internal/api/client/status/statusboost_test.go b/internal/api/client/status/statusboost_test.go
index 24ed8c361..9400aeddc 100644
--- a/internal/api/client/status/statusboost_test.go
+++ b/internal/api/client/status/statusboost_test.go
@@ -52,7 +52,7 @@ func (suite *StatusBoostTestSuite) SetupTest() {
suite.db = testrig.NewTestDB()
suite.storage = testrig.NewTestStorage()
suite.log = testrig.NewTestLog()
- suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
+ suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)), suite.storage)
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
suite.statusModule = status.New(suite.config, suite.processor, suite.log).(*status.Module)
testrig.StandardDBSetup(suite.db)
diff --git a/internal/api/client/status/statuscreate_test.go b/internal/api/client/status/statuscreate_test.go
index a78374fe8..b19323869 100644
--- a/internal/api/client/status/statuscreate_test.go
+++ b/internal/api/client/status/statuscreate_test.go
@@ -57,7 +57,7 @@ func (suite *StatusCreateTestSuite) SetupTest() {
suite.db = testrig.NewTestDB()
suite.storage = testrig.NewTestStorage()
suite.log = testrig.NewTestLog()
- suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
+ suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)), suite.storage)
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
suite.statusModule = status.New(suite.config, suite.processor, suite.log).(*status.Module)
testrig.StandardDBSetup(suite.db)
diff --git a/internal/api/client/status/statusfave_test.go b/internal/api/client/status/statusfave_test.go
index 2f779baed..b1cafc2fb 100644
--- a/internal/api/client/status/statusfave_test.go
+++ b/internal/api/client/status/statusfave_test.go
@@ -55,7 +55,7 @@ func (suite *StatusFaveTestSuite) SetupTest() {
suite.db = testrig.NewTestDB()
suite.storage = testrig.NewTestStorage()
suite.log = testrig.NewTestLog()
- suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
+ suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)), suite.storage)
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
suite.statusModule = status.New(suite.config, suite.processor, suite.log).(*status.Module)
testrig.StandardDBSetup(suite.db)
diff --git a/internal/api/client/status/statusfavedby_test.go b/internal/api/client/status/statusfavedby_test.go
index 7b72df7bc..b6e1591e0 100644
--- a/internal/api/client/status/statusfavedby_test.go
+++ b/internal/api/client/status/statusfavedby_test.go
@@ -55,7 +55,7 @@ func (suite *StatusFavedByTestSuite) SetupTest() {
suite.db = testrig.NewTestDB()
suite.storage = testrig.NewTestStorage()
suite.log = testrig.NewTestLog()
- suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
+ suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)), suite.storage)
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
suite.statusModule = status.New(suite.config, suite.processor, suite.log).(*status.Module)
testrig.StandardDBSetup(suite.db)
diff --git a/internal/api/client/status/statusget_test.go b/internal/api/client/status/statusget_test.go
index b31acebca..1bbf48a91 100644
--- a/internal/api/client/status/statusget_test.go
+++ b/internal/api/client/status/statusget_test.go
@@ -45,7 +45,7 @@ func (suite *StatusGetTestSuite) SetupTest() {
suite.db = testrig.NewTestDB()
suite.storage = testrig.NewTestStorage()
suite.log = testrig.NewTestLog()
- suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
+ suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)), suite.storage)
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
suite.statusModule = status.New(suite.config, suite.processor, suite.log).(*status.Module)
testrig.StandardDBSetup(suite.db)
diff --git a/internal/api/client/status/statusunfave_test.go b/internal/api/client/status/statusunfave_test.go
index 44b1dd3a6..36144c5ce 100644
--- a/internal/api/client/status/statusunfave_test.go
+++ b/internal/api/client/status/statusunfave_test.go
@@ -55,7 +55,7 @@ func (suite *StatusUnfaveTestSuite) SetupTest() {
suite.db = testrig.NewTestDB()
suite.storage = testrig.NewTestStorage()
suite.log = testrig.NewTestLog()
- suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
+ suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)), suite.storage)
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
suite.statusModule = status.New(suite.config, suite.processor, suite.log).(*status.Module)
testrig.StandardDBSetup(suite.db)
diff --git a/internal/api/model/domainblock.go b/internal/api/model/domainblock.go
new file mode 100644
index 000000000..aaa28f34d
--- /dev/null
+++ b/internal/api/model/domainblock.go
@@ -0,0 +1,43 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package model
+
+// DomainBlock represents a block on one domain
+type DomainBlock struct {
+ ID string `json:"id,omitempty"`
+ Domain string `json:"domain"`
+ Obfuscate bool `json:"obfuscate,omitempty"`
+ PrivateComment string `json:"private_comment,omitempty"`
+ PublicComment string `json:"public_comment,omitempty"`
+ SubscriptionID string `json:"subscription_id,omitempty"`
+ CreatedBy string `json:"created_by,omitempty"`
+ CreatedAt string `json:"created_at,omitempty"`
+}
+
+// DomainBlockCreateRequest is the form submitted as a POST to /api/v1/admin/domain_blocks to create a new block.
+type DomainBlockCreateRequest struct {
+ // hostname/domain to block
+ Domain string `form:"domain" json:"domain" xml:"domain" validation:"required"`
+ // whether the domain should be obfuscated when being displayed publicly
+ Obfuscate bool `form:"obfuscate" json:"obfuscate" xml:"obfuscate"`
+ // private comment for other admins on why the domain was blocked
+ PrivateComment string `form:"private_comment" json:"private_comment" xml:"private_comment"`
+ // public comment on the reason for the domain block
+ PublicComment string `form:"public_comment" json:"public_comment" xml:"public_comment"`
+}
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/user/userget_test.go b/internal/api/s2s/user/userget_test.go
index fab490767..d20148802 100644
--- a/internal/api/s2s/user/userget_test.go
+++ b/internal/api/s2s/user/userget_test.go
@@ -42,7 +42,7 @@ func (suite *UserGetTestSuite) SetupTest() {
suite.tc = testrig.NewTestTypeConverter(suite.db)
suite.storage = testrig.NewTestStorage()
suite.log = testrig.NewTestLog()
- suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)))
+ suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil)), suite.storage)
suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator)
suite.userModule = user.New(suite.config, suite.processor, suite.log).(*user.Module)
testrig.StandardDBSetup(suite.db)
@@ -98,7 +98,7 @@ func (suite *UserGetTestSuite) TestGetUser() {
}, nil
}))
// get this transport controller embedded right in the user module we're testing
- federator := testrig.NewTestFederator(suite.db, tc)
+ federator := testrig.NewTestFederator(suite.db, tc, suite.storage)
processor := testrig.NewTestProcessor(suite.db, suite.storage, federator)
userModule := user.New(suite.config, processor, suite.log).(*user.Module)
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..b852c92ab
--- /dev/null
+++ b/internal/api/security/signaturecheck.go
@@ -0,0 +1,69 @@
+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"
+)
+
+// SignatureCheck checks whether an incoming http request has been signed. If so, it will check if the domain
+// that signed the request is permitted to access the server. If it is permitted, the handler will set the key
+// verifier in the gin context for use down the line.
+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
+}