summaryrefslogtreecommitdiff
path: root/internal/api
diff options
context:
space:
mode:
Diffstat (limited to 'internal/api')
-rw-r--r--internal/api/client/admin/admin.go8
-rw-r--r--internal/api/client/admin/domainallowcreate.go128
-rw-r--r--internal/api/client/admin/domainallowdelete.go72
-rw-r--r--internal/api/client/admin/domainallowget.go67
-rw-r--r--internal/api/client/admin/domainallowsget.go73
-rw-r--r--internal/api/client/admin/domainblockcreate.go118
-rw-r--r--internal/api/client/admin/domainblockdelete.go42
-rw-r--r--internal/api/client/admin/domainblockget.go46
-rw-r--r--internal/api/client/admin/domainblocksget.go40
-rw-r--r--internal/api/client/admin/domainpermission.go295
-rw-r--r--internal/api/model/domain.go43
-rw-r--r--internal/api/util/parsequery.go14
12 files changed, 690 insertions, 256 deletions
diff --git a/internal/api/client/admin/admin.go b/internal/api/client/admin/admin.go
index 605c53731..3d8e88c42 100644
--- a/internal/api/client/admin/admin.go
+++ b/internal/api/client/admin/admin.go
@@ -31,6 +31,8 @@ const (
EmojiCategoriesPath = EmojiPath + "/categories"
DomainBlocksPath = BasePath + "/domain_blocks"
DomainBlocksPathWithID = DomainBlocksPath + "/:" + IDKey
+ DomainAllowsPath = BasePath + "/domain_allows"
+ DomainAllowsPathWithID = DomainAllowsPath + "/:" + IDKey
DomainKeysExpirePath = BasePath + "/domain_keys_expire"
AccountsPath = BasePath + "/accounts"
AccountsPathWithID = AccountsPath + "/:" + IDKey
@@ -84,6 +86,12 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H
attachHandler(http.MethodGet, DomainBlocksPathWithID, m.DomainBlockGETHandler)
attachHandler(http.MethodDelete, DomainBlocksPathWithID, m.DomainBlockDELETEHandler)
+ // domain allow stuff
+ attachHandler(http.MethodPost, DomainAllowsPath, m.DomainAllowsPOSTHandler)
+ attachHandler(http.MethodGet, DomainAllowsPath, m.DomainAllowsGETHandler)
+ attachHandler(http.MethodGet, DomainAllowsPathWithID, m.DomainAllowGETHandler)
+ attachHandler(http.MethodDelete, DomainAllowsPathWithID, m.DomainAllowDELETEHandler)
+
// domain maintenance stuff
attachHandler(http.MethodPost, DomainKeysExpirePath, m.DomainKeysExpirePOSTHandler)
diff --git a/internal/api/client/admin/domainallowcreate.go b/internal/api/client/admin/domainallowcreate.go
new file mode 100644
index 000000000..e8700f673
--- /dev/null
+++ b/internal/api/client/admin/domainallowcreate.go
@@ -0,0 +1,128 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package admin
+
+import (
+ "github.com/gin-gonic/gin"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+// DomainAllowsPOSTHandler swagger:operation POST /api/v1/admin/domain_allows domainAllowCreate
+//
+// Create one or more domain allows, from a string or a file.
+//
+// You have two options when using this endpoint: either you can set `import` to `true` and
+// upload a file containing multiple domain allows, JSON-formatted, or you can leave import as
+// `false`, and just add one domain allow.
+//
+// The format of the json file should be something like: `[{"domain":"example.org"},{"domain":"whatever.com","public_comment":"they smell"}]`
+//
+// ---
+// tags:
+// - admin
+//
+// consumes:
+// - multipart/form-data
+//
+// produces:
+// - application/json
+//
+// parameters:
+// -
+// name: import
+// in: query
+// description: >-
+// Signal that a list of domain allows is being imported as a file.
+// If set to `true`, then 'domains' must be present as a JSON-formatted file.
+// If set to `false`, then `domains` will be ignored, and `domain` must be present.
+// type: boolean
+// default: false
+// -
+// name: domains
+// in: formData
+// description: >-
+// JSON-formatted list of domain allows to import.
+// This is only used if `import` is set to `true`.
+// type: file
+// -
+// name: domain
+// in: formData
+// description: >-
+// Single domain to allow.
+// Used only if `import` is not `true`.
+// type: string
+// -
+// name: obfuscate
+// in: formData
+// description: >-
+// Obfuscate the name of the domain when serving it publicly.
+// Eg., `example.org` becomes something like `ex***e.org`.
+// Used only if `import` is not `true`.
+// type: boolean
+// -
+// name: public_comment
+// in: formData
+// description: >-
+// Public comment about this domain allow.
+// This will be displayed alongside the domain allow if you choose to share allows.
+// Used only if `import` is not `true`.
+// type: string
+// -
+// name: private_comment
+// in: formData
+// description: >-
+// Private comment about this domain allow. Will only be shown to other admins, so this
+// is a useful way of internally keeping track of why a certain domain ended up allowed.
+// Used only if `import` is not `true`.
+// type: string
+//
+// security:
+// - OAuth2 Bearer:
+// - admin
+//
+// responses:
+// '200':
+// description: >-
+// The newly created domain allow, if `import` != `true`.
+// If a list has been imported, then an `array` of newly created domain allows will be returned instead.
+// schema:
+// "$ref": "#/definitions/domainPermission"
+// '400':
+// description: bad request
+// '401':
+// description: unauthorized
+// '403':
+// description: forbidden
+// '404':
+// description: not found
+// '406':
+// description: not acceptable
+// '409':
+// description: >-
+// Conflict: There is already an admin action running that conflicts with this action.
+// Check the error message in the response body for more information. This is a temporary
+// error; it should be possible to process this action if you try again in a bit.
+// '500':
+// description: internal server error
+func (m *Module) DomainAllowsPOSTHandler(c *gin.Context) {
+ m.createDomainPermissions(c,
+ gtsmodel.DomainPermissionAllow,
+ m.processor.Admin().DomainPermissionCreate,
+ m.processor.Admin().DomainPermissionsImport,
+ )
+}
diff --git a/internal/api/client/admin/domainallowdelete.go b/internal/api/client/admin/domainallowdelete.go
new file mode 100644
index 000000000..6237e403f
--- /dev/null
+++ b/internal/api/client/admin/domainallowdelete.go
@@ -0,0 +1,72 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package admin
+
+import (
+ "github.com/gin-gonic/gin"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+// DomainAllowDELETEHandler swagger:operation DELETE /api/v1/admin/domain_allows/{id} domainAllowDelete
+//
+// Delete domain allow with the given ID.
+//
+// ---
+// tags:
+// - admin
+//
+// produces:
+// - application/json
+//
+// parameters:
+// -
+// name: id
+// type: string
+// description: The id of the domain allow.
+// in: path
+// required: true
+//
+// security:
+// - OAuth2 Bearer:
+// - admin
+//
+// responses:
+// '200':
+// description: The domain allow that was just deleted.
+// schema:
+// "$ref": "#/definitions/domainPermission"
+// '400':
+// description: bad request
+// '401':
+// description: unauthorized
+// '403':
+// description: forbidden
+// '404':
+// description: not found
+// '406':
+// description: not acceptable
+// '409':
+// description: >-
+// Conflict: There is already an admin action running that conflicts with this action.
+// Check the error message in the response body for more information. This is a temporary
+// error; it should be possible to process this action if you try again in a bit.
+// '500':
+// description: internal server error
+func (m *Module) DomainAllowDELETEHandler(c *gin.Context) {
+ m.deleteDomainPermission(c, gtsmodel.DomainPermissionAllow)
+}
diff --git a/internal/api/client/admin/domainallowget.go b/internal/api/client/admin/domainallowget.go
new file mode 100644
index 000000000..aa21743fa
--- /dev/null
+++ b/internal/api/client/admin/domainallowget.go
@@ -0,0 +1,67 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package admin
+
+import (
+ "github.com/gin-gonic/gin"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+// DomainAllowGETHandler swagger:operation GET /api/v1/admin/domain_allows/{id} domainAllowGet
+//
+// View domain allow with the given ID.
+//
+// ---
+// tags:
+// - admin
+//
+// produces:
+// - application/json
+//
+// parameters:
+// -
+// name: id
+// type: string
+// description: The id of the domain allow.
+// in: path
+// required: true
+//
+// security:
+// - OAuth2 Bearer:
+// - admin
+//
+// responses:
+// '200':
+// description: The requested domain allow.
+// schema:
+// "$ref": "#/definitions/domainPermission"
+// '400':
+// description: bad request
+// '401':
+// description: unauthorized
+// '403':
+// description: forbidden
+// '404':
+// description: not found
+// '406':
+// description: not acceptable
+// '500':
+// description: internal server error
+func (m *Module) DomainAllowGETHandler(c *gin.Context) {
+ m.getDomainPermission(c, gtsmodel.DomainPermissionAllow)
+}
diff --git a/internal/api/client/admin/domainallowsget.go b/internal/api/client/admin/domainallowsget.go
new file mode 100644
index 000000000..6391c7138
--- /dev/null
+++ b/internal/api/client/admin/domainallowsget.go
@@ -0,0 +1,73 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package admin
+
+import (
+ "github.com/gin-gonic/gin"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+// DomainAllowsGETHandler swagger:operation GET /api/v1/admin/domain_allows domainAllowsGet
+//
+// View all domain allows currently in place.
+//
+// ---
+// tags:
+// - admin
+//
+// produces:
+// - application/json
+//
+// parameters:
+// -
+// name: export
+// type: boolean
+// description: >-
+// If set to `true`, then each entry in the returned list of domain allows will only consist of
+// the fields `domain` and `public_comment`. This is perfect for when you want to save and share
+// a list of all the domains you have allowed on your instance, so that someone else can easily import them,
+// but you don't want them to see the database IDs of your allows, or private comments etc.
+// in: query
+// required: false
+//
+// security:
+// - OAuth2 Bearer:
+// - admin
+//
+// responses:
+// '200':
+// description: All domain allows currently in place.
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/domainPermission"
+// '400':
+// description: bad request
+// '401':
+// description: unauthorized
+// '403':
+// description: forbidden
+// '404':
+// description: not found
+// '406':
+// description: not acceptable
+// '500':
+// description: internal server error
+func (m *Module) DomainAllowsGETHandler(c *gin.Context) {
+ m.getDomainPermissions(c, gtsmodel.DomainPermissionAllow)
+}
diff --git a/internal/api/client/admin/domainblockcreate.go b/internal/api/client/admin/domainblockcreate.go
index 5cf9ea279..5234561cf 100644
--- a/internal/api/client/admin/domainblockcreate.go
+++ b/internal/api/client/admin/domainblockcreate.go
@@ -18,15 +18,8 @@
package admin
import (
- "errors"
- "fmt"
- "net/http"
-
"github.com/gin-gonic/gin"
- apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
- apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
- "github.com/superseriousbusiness/gotosocial/internal/gtserror"
- "github.com/superseriousbusiness/gotosocial/internal/oauth"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
// DomainBlocksPOSTHandler swagger:operation POST /api/v1/admin/domain_blocks domainBlockCreate
@@ -108,7 +101,7 @@ import (
// The newly created domain block, if `import` != `true`.
// If a list has been imported, then an `array` of newly created domain blocks will be returned instead.
// schema:
-// "$ref": "#/definitions/domainBlock"
+// "$ref": "#/definitions/domainPermission"
// '400':
// description: bad request
// '401':
@@ -127,108 +120,9 @@ import (
// '500':
// description: internal server error
func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) {
- authed, err := oauth.Authed(c, true, true, true, true)
- if err != nil {
- apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- if !*authed.User.Admin {
- err := fmt.Errorf("user %s not an admin", authed.User.ID)
- apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
- apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- importing, errWithCode := apiutil.ParseDomainBlockImport(c.Query(apiutil.DomainBlockImportKey), false)
- if errWithCode != nil {
- apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
- return
- }
-
- form := new(apimodel.DomainBlockCreateRequest)
- if err := c.ShouldBind(form); err != nil {
- apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- if err := validateCreateDomainBlock(form, importing); err != nil {
- err := fmt.Errorf("error validating form: %w", err)
- apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- if !importing {
- // Single domain block creation.
- domainBlock, _, errWithCode := m.processor.Admin().DomainBlockCreate(
- c.Request.Context(),
- authed.Account,
- form.Domain,
- form.Obfuscate,
- form.PublicComment,
- form.PrivateComment,
- "", // No sub ID for single block creation.
- )
- if errWithCode != nil {
- apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
- return
- }
-
- c.JSON(http.StatusOK, domainBlock)
- return
- }
-
- // We're importing multiple domain blocks,
- // so we're looking at a multi-status response.
- multiStatus, errWithCode := m.processor.Admin().DomainBlocksImport(
- c.Request.Context(),
- authed.Account,
- form.Domains, // Pass the file through.
+ m.createDomainPermissions(c,
+ gtsmodel.DomainPermissionBlock,
+ m.processor.Admin().DomainPermissionCreate,
+ m.processor.Admin().DomainPermissionsImport,
)
- if errWithCode != nil {
- apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
- return
- }
-
- // TODO: Return 207 and multiStatus data nicely
- // when supported by the admin panel.
-
- if multiStatus.Metadata.Failure != 0 {
- failures := make(map[string]any, multiStatus.Metadata.Failure)
- for _, entry := range multiStatus.Data {
- // nolint:forcetypeassert
- failures[entry.Resource.(string)] = entry.Message
- }
-
- err := fmt.Errorf("one or more errors importing domain blocks: %+v", failures)
- apiutil.ErrorHandler(c, gtserror.NewErrorUnprocessableEntity(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- // Success, return slice of domain blocks.
- domainBlocks := make([]any, 0, multiStatus.Metadata.Success)
- for _, entry := range multiStatus.Data {
- domainBlocks = append(domainBlocks, entry.Resource)
- }
-
- c.JSON(http.StatusOK, domainBlocks)
-}
-
-func validateCreateDomainBlock(form *apimodel.DomainBlockCreateRequest, imp bool) error {
- if imp {
- if form.Domains.Size == 0 {
- return errors.New("import was specified but list of domains is empty")
- }
- } else {
- // 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
index 9318bad87..a6f6619cd 100644
--- a/internal/api/client/admin/domainblockdelete.go
+++ b/internal/api/client/admin/domainblockdelete.go
@@ -18,14 +18,8 @@
package admin
import (
- "errors"
- "fmt"
- "net/http"
-
"github.com/gin-gonic/gin"
- apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
- "github.com/superseriousbusiness/gotosocial/internal/gtserror"
- "github.com/superseriousbusiness/gotosocial/internal/oauth"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
// DomainBlockDELETEHandler swagger:operation DELETE /api/v1/admin/domain_blocks/{id} domainBlockDelete
@@ -55,7 +49,7 @@ import (
// '200':
// description: The domain block that was just deleted.
// schema:
-// "$ref": "#/definitions/domainBlock"
+// "$ref": "#/definitions/domainPermission"
// '400':
// description: bad request
// '401':
@@ -74,35 +68,5 @@ import (
// '500':
// description: internal server error
func (m *Module) DomainBlockDELETEHandler(c *gin.Context) {
- authed, err := oauth.Authed(c, true, true, true, true)
- if err != nil {
- apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- if !*authed.User.Admin {
- err := fmt.Errorf("user %s not an admin", authed.User.ID)
- apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
- apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- domainBlockID := c.Param(IDKey)
- if domainBlockID == "" {
- err := errors.New("no domain block id specified")
- apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- domainBlock, _, errWithCode := m.processor.Admin().DomainBlockDelete(c.Request.Context(), authed.Account, domainBlockID)
- if errWithCode != nil {
- apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
- return
- }
-
- c.JSON(http.StatusOK, domainBlock)
+ m.deleteDomainPermission(c, gtsmodel.DomainPermissionBlock)
}
diff --git a/internal/api/client/admin/domainblockget.go b/internal/api/client/admin/domainblockget.go
index 87bb75a27..9e8d29905 100644
--- a/internal/api/client/admin/domainblockget.go
+++ b/internal/api/client/admin/domainblockget.go
@@ -18,13 +18,8 @@
package admin
import (
- "fmt"
- "net/http"
-
"github.com/gin-gonic/gin"
- apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
- "github.com/superseriousbusiness/gotosocial/internal/gtserror"
- "github.com/superseriousbusiness/gotosocial/internal/oauth"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
// DomainBlockGETHandler swagger:operation GET /api/v1/admin/domain_blocks/{id} domainBlockGet
@@ -54,7 +49,7 @@ import (
// '200':
// description: The requested domain block.
// schema:
-// "$ref": "#/definitions/domainBlock"
+// "$ref": "#/definitions/domainPermission"
// '400':
// description: bad request
// '401':
@@ -68,40 +63,5 @@ import (
// '500':
// description: internal server error
func (m *Module) DomainBlockGETHandler(c *gin.Context) {
- authed, err := oauth.Authed(c, true, true, true, true)
- if err != nil {
- apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- if !*authed.User.Admin {
- err := fmt.Errorf("user %s not an admin", authed.User.ID)
- apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
- apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- domainBlockID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
- if errWithCode != nil {
- apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
- return
- }
-
- export, errWithCode := apiutil.ParseDomainBlockExport(c.Query(apiutil.DomainBlockExportKey), false)
- if errWithCode != nil {
- apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
- return
- }
-
- domainBlock, errWithCode := m.processor.Admin().DomainBlockGet(c.Request.Context(), domainBlockID, export)
- if errWithCode != nil {
- apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
- return
- }
-
- c.JSON(http.StatusOK, domainBlock)
+ m.getDomainPermission(c, gtsmodel.DomainPermissionBlock)
}
diff --git a/internal/api/client/admin/domainblocksget.go b/internal/api/client/admin/domainblocksget.go
index 68947f471..bdcc03469 100644
--- a/internal/api/client/admin/domainblocksget.go
+++ b/internal/api/client/admin/domainblocksget.go
@@ -18,13 +18,8 @@
package admin
import (
- "fmt"
- "net/http"
-
"github.com/gin-gonic/gin"
- apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
- "github.com/superseriousbusiness/gotosocial/internal/gtserror"
- "github.com/superseriousbusiness/gotosocial/internal/oauth"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
// DomainBlocksGETHandler swagger:operation GET /api/v1/admin/domain_blocks domainBlocksGet
@@ -60,7 +55,7 @@ import (
// schema:
// type: array
// items:
-// "$ref": "#/definitions/domainBlock"
+// "$ref": "#/definitions/domainPermission"
// '400':
// description: bad request
// '401':
@@ -74,34 +69,5 @@ import (
// '500':
// description: internal server error
func (m *Module) DomainBlocksGETHandler(c *gin.Context) {
- authed, err := oauth.Authed(c, true, true, true, true)
- if err != nil {
- apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- if !*authed.User.Admin {
- err := fmt.Errorf("user %s not an admin", authed.User.ID)
- apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
- apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
- return
- }
-
- export, errWithCode := apiutil.ParseDomainBlockExport(c.Query(apiutil.DomainBlockExportKey), false)
- if errWithCode != nil {
- apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
- return
- }
-
- domainBlocks, errWithCode := m.processor.Admin().DomainBlocksGet(c.Request.Context(), authed.Account, export)
- if errWithCode != nil {
- apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
- return
- }
-
- c.JSON(http.StatusOK, domainBlocks)
+ m.getDomainPermissions(c, gtsmodel.DomainPermissionBlock)
}
diff --git a/internal/api/client/admin/domainpermission.go b/internal/api/client/admin/domainpermission.go
new file mode 100644
index 000000000..80aa05041
--- /dev/null
+++ b/internal/api/client/admin/domainpermission.go
@@ -0,0 +1,295 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package admin
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "mime/multipart"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+)
+
+type singleDomainPermCreate func(
+ context.Context,
+ gtsmodel.DomainPermissionType, // block/allow
+ *gtsmodel.Account, // admin account
+ string, // domain
+ bool, // obfuscate
+ string, // publicComment
+ string, // privateComment
+ string, // subscriptionID
+) (*apimodel.DomainPermission, string, gtserror.WithCode)
+
+type multiDomainPermCreate func(
+ context.Context,
+ gtsmodel.DomainPermissionType, // block/allow
+ *gtsmodel.Account, // admin account
+ *multipart.FileHeader, // domains
+) (*apimodel.MultiStatus, gtserror.WithCode)
+
+// createDomainPemissions either creates a single domain
+// permission entry (block/allow) or imports multiple domain
+// permission entries (multiple blocks, multiple allows)
+// using the given functions.
+//
+// Handling the creation of both types of permissions in
+// one function in this way reduces code duplication.
+func (m *Module) createDomainPermissions(
+ c *gin.Context,
+ permType gtsmodel.DomainPermissionType,
+ single singleDomainPermCreate,
+ multi multiDomainPermCreate,
+) {
+ authed, err := oauth.Authed(c, true, true, true, true)
+ if err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ if !*authed.User.Admin {
+ err := fmt.Errorf("user %s not an admin", authed.User.ID)
+ apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ importing, errWithCode := apiutil.ParseDomainPermissionImport(c.Query(apiutil.DomainPermissionImportKey), false)
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ // Parse + validate form.
+ form := new(apimodel.DomainPermissionRequest)
+ if err := c.ShouldBind(form); err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ if importing && form.Domains.Size == 0 {
+ err = errors.New("import was specified but list of domains is empty")
+ } else if form.Domain == "" {
+ err = errors.New("empty domain provided")
+ }
+
+ if err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ if !importing {
+ // Single domain permission creation.
+ domainBlock, _, errWithCode := single(
+ c.Request.Context(),
+ permType,
+ authed.Account,
+ form.Domain,
+ form.Obfuscate,
+ form.PublicComment,
+ form.PrivateComment,
+ "", // No sub ID for single perm creation.
+ )
+
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ c.JSON(http.StatusOK, domainBlock)
+ return
+ }
+
+ // We're importing multiple domain permissions,
+ // so we're looking at a multi-status response.
+ multiStatus, errWithCode := multi(
+ c.Request.Context(),
+ permType,
+ authed.Account,
+ form.Domains, // Pass the file through.
+ )
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ // TODO: Return 207 and multiStatus data nicely
+ // when supported by the admin panel.
+ if multiStatus.Metadata.Failure != 0 {
+ failures := make(map[string]any, multiStatus.Metadata.Failure)
+ for _, entry := range multiStatus.Data {
+ // nolint:forcetypeassert
+ failures[entry.Resource.(string)] = entry.Message
+ }
+
+ err := fmt.Errorf("one or more errors importing domain %ss: %+v", permType.String(), failures)
+ apiutil.ErrorHandler(c, gtserror.NewErrorUnprocessableEntity(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ // Success, return slice of newly-created domain perms.
+ domainPerms := make([]any, 0, multiStatus.Metadata.Success)
+ for _, entry := range multiStatus.Data {
+ domainPerms = append(domainPerms, entry.Resource)
+ }
+
+ c.JSON(http.StatusOK, domainPerms)
+}
+
+// deleteDomainPermission deletes a single domain permission (block or allow).
+func (m *Module) deleteDomainPermission(
+ c *gin.Context,
+ permType gtsmodel.DomainPermissionType, // block/allow
+) {
+ authed, err := oauth.Authed(c, true, true, true, true)
+ if err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ if !*authed.User.Admin {
+ err := fmt.Errorf("user %s not an admin", authed.User.ID)
+ apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ domainPermID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ domainPerm, _, errWithCode := m.processor.Admin().DomainPermissionDelete(
+ c.Request.Context(),
+ permType,
+ authed.Account,
+ domainPermID,
+ )
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ c.JSON(http.StatusOK, domainPerm)
+}
+
+// getDomainPermission gets a single domain permission (block or allow).
+func (m *Module) getDomainPermission(
+ c *gin.Context,
+ permType gtsmodel.DomainPermissionType,
+) {
+ authed, err := oauth.Authed(c, true, true, true, true)
+ if err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ if !*authed.User.Admin {
+ err := fmt.Errorf("user %s not an admin", authed.User.ID)
+ apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ domainPermID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ export, errWithCode := apiutil.ParseDomainPermissionExport(c.Query(apiutil.DomainPermissionExportKey), false)
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ domainPerm, errWithCode := m.processor.Admin().DomainPermissionGet(
+ c.Request.Context(),
+ permType,
+ domainPermID,
+ export,
+ )
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ c.JSON(http.StatusOK, domainPerm)
+}
+
+// getDomainPermissions gets all domain permissions of the given type (block, allow).
+func (m *Module) getDomainPermissions(
+ c *gin.Context,
+ permType gtsmodel.DomainPermissionType,
+) {
+ authed, err := oauth.Authed(c, true, true, true, true)
+ if err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ if !*authed.User.Admin {
+ err := fmt.Errorf("user %s not an admin", authed.User.ID)
+ apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
+ apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
+ return
+ }
+
+ export, errWithCode := apiutil.ParseDomainPermissionExport(c.Query(apiutil.DomainPermissionExportKey), false)
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ domainPerm, errWithCode := m.processor.Admin().DomainPermissionsGet(
+ c.Request.Context(),
+ permType,
+ authed.Account,
+ export,
+ )
+ if errWithCode != nil {
+ apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
+ return
+ }
+
+ c.JSON(http.StatusOK, domainPerm)
+}
diff --git a/internal/api/model/domain.go b/internal/api/model/domain.go
index c5f77c82f..a5e1ddf10 100644
--- a/internal/api/model/domain.go
+++ b/internal/api/model/domain.go
@@ -37,46 +37,53 @@ type Domain struct {
PublicComment string `form:"public_comment" json:"public_comment,omitempty"`
}
-// DomainBlock represents a block on one domain
+// DomainPermission represents a permission applied to one domain (explicit block/allow).
//
-// swagger:model domainBlock
-type DomainBlock struct {
+// swagger:model domainPermission
+type DomainPermission struct {
Domain
- // The ID of the domain block.
+ // The ID of the domain permission entry.
// example: 01FBW21XJA09XYX51KV5JVBW0F
// readonly: true
ID string `json:"id,omitempty"`
- // Obfuscate the domain name when serving this domain block publicly.
- // A useful anti-harassment tool.
+ // Obfuscate the domain name when serving this domain permission entry publicly.
// example: false
Obfuscate bool `json:"obfuscate,omitempty"`
- // Private comment for this block, visible to our instance admins only.
+ // Private comment for this permission entry, visible to this instance's admins only.
// example: they are poopoo
PrivateComment string `json:"private_comment,omitempty"`
- // The ID of the subscription that created/caused this domain block.
+ // If applicable, the ID of the subscription that caused this domain permission entry to be created.
// example: 01FBW25TF5J67JW3HFHZCSD23K
SubscriptionID string `json:"subscription_id,omitempty"`
- // ID of the account that created this domain block.
+ // ID of the account that created this domain permission entry.
// example: 01FBW2758ZB6PBR200YPDDJK4C
CreatedBy string `json:"created_by,omitempty"`
- // Time at which this block was created (ISO 8601 Datetime).
+ // Time at which the permission entry was created (ISO 8601 Datetime).
// example: 2021-07-30T09:20:25+00:00
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.
+// DomainPermissionRequest is the form submitted as a POST to create a new domain permission entry (allow/block).
//
-// swagger:model domainBlockCreateRequest
-type DomainBlockCreateRequest struct {
- // A list of domains to block. Only used if import=true is specified.
+// swagger:model domainPermissionCreateRequest
+type DomainPermissionRequest struct {
+ // A list of domains for which this permission request should apply.
+ // Only used if import=true is specified.
Domains *multipart.FileHeader `form:"domains" json:"domains" xml:"domains"`
- // hostname/domain to block
+ // A single domain for which this permission request should apply.
+ // Only used if import=true is NOT specified or if import=false.
+ // example: example.org
Domain string `form:"domain" json:"domain" xml:"domain"`
- // whether the domain should be obfuscated when being displayed publicly
+ // Obfuscate the domain name when displaying this permission entry publicly.
+ // Ie., instead of 'example.org' show something like 'e**mpl*.or*'.
+ // example: false
Obfuscate bool `form:"obfuscate" json:"obfuscate" xml:"obfuscate"`
- // private comment for other admins on why the domain was blocked
+ // Private comment for other admins on why this permission entry was created.
+ // example: don't like 'em!!!!
PrivateComment string `form:"private_comment" json:"private_comment" xml:"private_comment"`
- // public comment on the reason for the domain block
+ // Public comment on why this permission entry was created.
+ // Will be visible to requesters at /api/v1/instance/peers if this endpoint is exposed.
+ // example: foss dorks 😫
PublicComment string `form:"public_comment" json:"public_comment" xml:"public_comment"`
}
diff --git a/internal/api/util/parsequery.go b/internal/api/util/parsequery.go
index a87c77aeb..6a9116dcf 100644
--- a/internal/api/util/parsequery.go
+++ b/internal/api/util/parsequery.go
@@ -60,10 +60,10 @@ const (
WebUsernameKey = "username"
WebStatusIDKey = "status"
- /* Domain block keys */
+ /* Domain permission keys */
- DomainBlockExportKey = "export"
- DomainBlockImportKey = "import"
+ DomainPermissionExportKey = "export"
+ DomainPermissionImportKey = "import"
)
// parseError returns gtserror.WithCode set to 400 Bad Request, to indicate
@@ -121,12 +121,12 @@ func ParseSearchResolve(value string, defaultValue bool) (bool, gtserror.WithCod
return parseBool(value, defaultValue, SearchResolveKey)
}
-func ParseDomainBlockExport(value string, defaultValue bool) (bool, gtserror.WithCode) {
- return parseBool(value, defaultValue, DomainBlockExportKey)
+func ParseDomainPermissionExport(value string, defaultValue bool) (bool, gtserror.WithCode) {
+ return parseBool(value, defaultValue, DomainPermissionExportKey)
}
-func ParseDomainBlockImport(value string, defaultValue bool) (bool, gtserror.WithCode) {
- return parseBool(value, defaultValue, DomainBlockImportKey)
+func ParseDomainPermissionImport(value string, defaultValue bool) (bool, gtserror.WithCode) {
+ return parseBool(value, defaultValue, DomainPermissionImportKey)
}
/*