diff options
author | 2023-09-21 12:12:04 +0200 | |
---|---|---|
committer | 2023-09-21 12:12:04 +0200 | |
commit | 183eaa5b298235acb8f25ba8f18b98e31471d965 (patch) | |
tree | 55f42887edeb5206122d92eb30e0eedf145a3615 /internal/api/client/admin/domainpermission.go | |
parent | [docs] Add a note on cluster support (#2214) (diff) | |
download | gotosocial-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.go | 295 |
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) +} |