diff options
Diffstat (limited to 'internal/api/client/admin')
| -rw-r--r-- | internal/api/client/admin/admin.go | 8 | ||||
| -rw-r--r-- | internal/api/client/admin/domainallowcreate.go | 128 | ||||
| -rw-r--r-- | internal/api/client/admin/domainallowdelete.go | 72 | ||||
| -rw-r--r-- | internal/api/client/admin/domainallowget.go | 67 | ||||
| -rw-r--r-- | internal/api/client/admin/domainallowsget.go | 73 | ||||
| -rw-r--r-- | internal/api/client/admin/domainblockcreate.go | 118 | ||||
| -rw-r--r-- | internal/api/client/admin/domainblockdelete.go | 42 | ||||
| -rw-r--r-- | internal/api/client/admin/domainblockget.go | 46 | ||||
| -rw-r--r-- | internal/api/client/admin/domainblocksget.go | 40 | ||||
| -rw-r--r-- | internal/api/client/admin/domainpermission.go | 295 | 
10 files changed, 658 insertions, 231 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) +} | 
