summaryrefslogtreecommitdiff
path: root/internal/api/client/admin/domainpermission.go
diff options
context:
space:
mode:
authorLibravatar tobi <31960611+tsmethurst@users.noreply.github.com>2023-09-21 12:12:04 +0200
committerLibravatar GitHub <noreply@github.com>2023-09-21 12:12:04 +0200
commit183eaa5b298235acb8f25ba8f18b98e31471d965 (patch)
tree55f42887edeb5206122d92eb30e0eedf145a3615 /internal/api/client/admin/domainpermission.go
parent[docs] Add a note on cluster support (#2214) (diff)
downloadgotosocial-183eaa5b298235acb8f25ba8f18b98e31471d965.tar.xz
[feature] Implement explicit domain allows + allowlist federation mode (#2200)
* love like winter! wohoah, wohoah * domain allow side effects * tests! logging! unallow! * document federation modes * linty linterson * test * further adventures in documentation * finish up domain block documentation (i think) * change wording a wee little bit * docs, example * consolidate shared domainPermission code * call mode once * fetch federation mode within domain blocked func * read domain perm import in streaming manner * don't use pointer to slice for domain perms * don't bother copying blocks + allows before deleting * admonish! * change wording just a scooch * update docs
Diffstat (limited to 'internal/api/client/admin/domainpermission.go')
-rw-r--r--internal/api/client/admin/domainpermission.go295
1 files changed, 295 insertions, 0 deletions
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)
+}