diff options
| author | 2025-01-05 13:20:33 +0100 | |
|---|---|---|
| committer | 2025-01-05 13:20:33 +0100 | |
| commit | e9bb7ddd3aa11da5c48a75c4a600f8fe5cc1c990 (patch) | |
| tree | a2897775112a821aa093b6e2686044814912001f /internal/api/client | |
| parent | [chore] Update robots.txt with more AI bots (#3634) (diff) | |
| download | gotosocial-e9bb7ddd3aa11da5c48a75c4a600f8fe5cc1c990.tar.xz | |
[feature] Create/update/remove domain permission subscriptions (#3623)
* [feature] Create/update/remove domain permission subscriptions
* lint
* envparsing
* remove errant fmt.Println
* create drafts, subs, exclude, from snapshot models
* name etag column correctly
* remove count column
* lint
Diffstat (limited to 'internal/api/client')
10 files changed, 1148 insertions, 57 deletions
| diff --git a/internal/api/client/admin/admin.go b/internal/api/client/admin/admin.go index a33a6448a..68a088b4d 100644 --- a/internal/api/client/admin/admin.go +++ b/internal/api/client/admin/admin.go @@ -28,43 +28,47 @@ import (  )  const ( -	BasePath                           = "/v1/admin" -	EmojiPath                          = BasePath + "/custom_emojis" -	EmojiPathWithID                    = EmojiPath + "/:" + apiutil.IDKey -	EmojiCategoriesPath                = EmojiPath + "/categories" -	DomainBlocksPath                   = BasePath + "/domain_blocks" -	DomainBlocksPathWithID             = DomainBlocksPath + "/:" + apiutil.IDKey -	DomainAllowsPath                   = BasePath + "/domain_allows" -	DomainAllowsPathWithID             = DomainAllowsPath + "/:" + apiutil.IDKey -	DomainPermissionDraftsPath         = BasePath + "/domain_permission_drafts" -	DomainPermissionDraftsPathWithID   = DomainPermissionDraftsPath + "/:" + apiutil.IDKey -	DomainPermissionDraftAcceptPath    = DomainPermissionDraftsPathWithID + "/accept" -	DomainPermissionDraftRemovePath    = DomainPermissionDraftsPathWithID + "/remove" -	DomainPermissionExcludesPath       = BasePath + "/domain_permission_excludes" -	DomainPermissionExcludesPathWithID = DomainPermissionExcludesPath + "/:" + apiutil.IDKey -	DomainKeysExpirePath               = BasePath + "/domain_keys_expire" -	HeaderAllowsPath                   = BasePath + "/header_allows" -	HeaderAllowsPathWithID             = HeaderAllowsPath + "/:" + apiutil.IDKey -	HeaderBlocksPath                   = BasePath + "/header_blocks" -	HeaderBlocksPathWithID             = HeaderBlocksPath + "/:" + apiutil.IDKey -	AccountsV1Path                     = BasePath + "/accounts" -	AccountsV2Path                     = "/v2/admin/accounts" -	AccountsPathWithID                 = AccountsV1Path + "/:" + apiutil.IDKey -	AccountsActionPath                 = AccountsPathWithID + "/action" -	AccountsApprovePath                = AccountsPathWithID + "/approve" -	AccountsRejectPath                 = AccountsPathWithID + "/reject" -	MediaCleanupPath                   = BasePath + "/media_cleanup" -	MediaRefetchPath                   = BasePath + "/media_refetch" -	ReportsPath                        = BasePath + "/reports" -	ReportsPathWithID                  = ReportsPath + "/:" + apiutil.IDKey -	ReportsResolvePath                 = ReportsPathWithID + "/resolve" -	EmailPath                          = BasePath + "/email" -	EmailTestPath                      = EmailPath + "/test" -	InstanceRulesPath                  = BasePath + "/instance/rules" -	InstanceRulesPathWithID            = InstanceRulesPath + "/:" + apiutil.IDKey -	DebugPath                          = BasePath + "/debug" -	DebugAPUrlPath                     = DebugPath + "/apurl" -	DebugClearCachesPath               = DebugPath + "/caches/clear" +	BasePath                                 = "/v1/admin" +	EmojiPath                                = BasePath + "/custom_emojis" +	EmojiPathWithID                          = EmojiPath + "/:" + apiutil.IDKey +	EmojiCategoriesPath                      = EmojiPath + "/categories" +	DomainBlocksPath                         = BasePath + "/domain_blocks" +	DomainBlocksPathWithID                   = DomainBlocksPath + "/:" + apiutil.IDKey +	DomainAllowsPath                         = BasePath + "/domain_allows" +	DomainAllowsPathWithID                   = DomainAllowsPath + "/:" + apiutil.IDKey +	DomainPermissionDraftsPath               = BasePath + "/domain_permission_drafts" +	DomainPermissionDraftsPathWithID         = DomainPermissionDraftsPath + "/:" + apiutil.IDKey +	DomainPermissionDraftAcceptPath          = DomainPermissionDraftsPathWithID + "/accept" +	DomainPermissionDraftRemovePath          = DomainPermissionDraftsPathWithID + "/remove" +	DomainPermissionExcludesPath             = BasePath + "/domain_permission_excludes" +	DomainPermissionExcludesPathWithID       = DomainPermissionExcludesPath + "/:" + apiutil.IDKey +	DomainPermissionSubscriptionsPath        = BasePath + "/domain_permission_subscriptions" +	DomainPermissionSubscriptionsPathWithID  = DomainPermissionSubscriptionsPath + "/:" + apiutil.IDKey +	DomainPermissionSubscriptionsPreviewPath = DomainPermissionSubscriptionsPath + "/preview" +	DomainPermissionSubscriptionRemovePath   = DomainPermissionSubscriptionsPathWithID + "/remove" +	DomainKeysExpirePath                     = BasePath + "/domain_keys_expire" +	HeaderAllowsPath                         = BasePath + "/header_allows" +	HeaderAllowsPathWithID                   = HeaderAllowsPath + "/:" + apiutil.IDKey +	HeaderBlocksPath                         = BasePath + "/header_blocks" +	HeaderBlocksPathWithID                   = HeaderBlocksPath + "/:" + apiutil.IDKey +	AccountsV1Path                           = BasePath + "/accounts" +	AccountsV2Path                           = "/v2/admin/accounts" +	AccountsPathWithID                       = AccountsV1Path + "/:" + apiutil.IDKey +	AccountsActionPath                       = AccountsPathWithID + "/action" +	AccountsApprovePath                      = AccountsPathWithID + "/approve" +	AccountsRejectPath                       = AccountsPathWithID + "/reject" +	MediaCleanupPath                         = BasePath + "/media_cleanup" +	MediaRefetchPath                         = BasePath + "/media_refetch" +	ReportsPath                              = BasePath + "/reports" +	ReportsPathWithID                        = ReportsPath + "/:" + apiutil.IDKey +	ReportsResolvePath                       = ReportsPathWithID + "/resolve" +	EmailPath                                = BasePath + "/email" +	EmailTestPath                            = EmailPath + "/test" +	InstanceRulesPath                        = BasePath + "/instance/rules" +	InstanceRulesPathWithID                  = InstanceRulesPath + "/:" + apiutil.IDKey +	DebugPath                                = BasePath + "/debug" +	DebugAPUrlPath                           = DebugPath + "/apurl" +	DebugClearCachesPath                     = DebugPath + "/caches/clear"  	FilterQueryKey        = "filter"  	MaxShortcodeDomainKey = "max_shortcode_domain" @@ -118,6 +122,14 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H  	attachHandler(http.MethodGet, DomainPermissionExcludesPathWithID, m.DomainPermissionExcludeGETHandler)  	attachHandler(http.MethodDelete, DomainPermissionExcludesPathWithID, m.DomainPermissionExcludeDELETEHandler) +	// domain permission subscriptions stuff +	attachHandler(http.MethodPost, DomainPermissionSubscriptionsPath, m.DomainPermissionSubscriptionPOSTHandler) +	attachHandler(http.MethodGet, DomainPermissionSubscriptionsPath, m.DomainPermissionSubscriptionsGETHandler) +	attachHandler(http.MethodGet, DomainPermissionSubscriptionsPreviewPath, m.DomainPermissionSubscriptionsPreviewGETHandler) +	attachHandler(http.MethodGet, DomainPermissionSubscriptionsPathWithID, m.DomainPermissionSubscriptionGETHandler) +	attachHandler(http.MethodPatch, DomainPermissionSubscriptionsPathWithID, m.DomainPermissionSubscriptionPATCHHandler) +	attachHandler(http.MethodPost, DomainPermissionSubscriptionRemovePath, m.DomainPermissionSubscriptionRemovePOSTHandler) +  	// header filtering administration routes  	attachHandler(http.MethodGet, HeaderAllowsPathWithID, m.HeaderFilterAllowGET)  	attachHandler(http.MethodGet, HeaderBlocksPathWithID, m.HeaderFilterBlockGET) diff --git a/internal/api/client/admin/domainpermission.go b/internal/api/client/admin/domainpermission.go index 90c0eb4c0..5138be898 100644 --- a/internal/api/client/admin/domainpermission.go +++ b/internal/api/client/admin/domainpermission.go @@ -302,3 +302,45 @@ func (m *Module) getDomainPermissions(  	apiutil.JSON(c, http.StatusOK, domainPerm)  } + +// parseDomainPermissionType is a util function to parse i +// to a DomainPermissionType, or return a suitable error. +func parseDomainPermissionType(i string) ( +	permType gtsmodel.DomainPermissionType, +	errWithCode gtserror.WithCode, +) { +	if i == "" { +		const errText = "permission_type not set, must be one of block or allow" +		errWithCode = gtserror.NewErrorBadRequest(errors.New(errText), errText) +		return +	} + +	permType = gtsmodel.ParseDomainPermissionType(i) +	if permType == gtsmodel.DomainPermissionUnknown { +		var errText = fmt.Sprintf("permission_type %s not recognized, must be one of block or allow", i) +		errWithCode = gtserror.NewErrorBadRequest(errors.New(errText), errText) +	} + +	return +} + +// parseDomainPermSubContentType is a util function to parse i +// to a DomainPermSubContentType, or return a suitable error. +func parseDomainPermSubContentType(i string) ( +	contentType gtsmodel.DomainPermSubContentType, +	errWithCode gtserror.WithCode, +) { +	if i == "" { +		const errText = "content_type not set, must be one of text/csv, text/plain or application/json" +		errWithCode = gtserror.NewErrorBadRequest(errors.New(errText), errText) +		return +	} + +	contentType = gtsmodel.NewDomainPermSubContentType(i) +	if contentType == gtsmodel.DomainPermSubContentTypeUnknown { +		var errText = fmt.Sprintf("content_type %s not recognized, must be one of text/csv, text/plain or application/json", i) +		errWithCode = gtserror.NewErrorBadRequest(errors.New(errText), errText) +	} + +	return +} diff --git a/internal/api/client/admin/domainpermissiondraftcreate.go b/internal/api/client/admin/domainpermissiondraftcreate.go index d20842ebf..ec94f947b 100644 --- a/internal/api/client/admin/domainpermissiondraftcreate.go +++ b/internal/api/client/admin/domainpermissiondraftcreate.go @@ -26,7 +26,6 @@ import (  	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"  ) @@ -136,24 +135,8 @@ func (m *Module) DomainPermissionDraftsPOSTHandler(c *gin.Context) {  		return  	} -	var ( -		permType gtsmodel.DomainPermissionType -		errText  string -	) - -	switch pt := form.PermissionType; pt { -	case "block": -		permType = gtsmodel.DomainPermissionBlock -	case "allow": -		permType = gtsmodel.DomainPermissionAllow -	case "": -		errText = "permission_type not set, must be one of block or allow" -	default: -		errText = fmt.Sprintf("permission_type %s not recognized, must be one of block or allow", pt) -	} - -	if errText != "" { -		errWithCode := gtserror.NewErrorBadRequest(errors.New(errText), errText) +	permType, errWithCode := parseDomainPermissionType(form.PermissionType) +	if errWithCode != nil {  		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)  		return  	} diff --git a/internal/api/client/admin/domainpermissiondraftsget.go b/internal/api/client/admin/domainpermissiondraftsget.go index d63179afc..21ce5dc43 100644 --- a/internal/api/client/admin/domainpermissiondraftsget.go +++ b/internal/api/client/admin/domainpermissiondraftsget.go @@ -149,7 +149,7 @@ func (m *Module) DomainPermissionDraftsGETHandler(c *gin.Context) {  	permTypeStr := c.Query(apiutil.DomainPermissionPermTypeKey)  	permType := gtsmodel.ParseDomainPermissionType(permTypeStr) -	if permType == gtsmodel.DomainPermissionUnknown { +	if permTypeStr != "" && permType == gtsmodel.DomainPermissionUnknown {  		text := fmt.Sprintf(  			"permission_type %s not recognized, valid values are empty string, block, or allow",  			permTypeStr, diff --git a/internal/api/client/admin/domainpermissionsubscriptioncreate.go b/internal/api/client/admin/domainpermissionsubscriptioncreate.go new file mode 100644 index 000000000..dd0b43aca --- /dev/null +++ b/internal/api/client/admin/domainpermissionsubscriptioncreate.go @@ -0,0 +1,244 @@ +// 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 ( +	"errors" +	"fmt" +	"net/http" +	"net/url" + +	"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/util" +) + +// DomainPermissionSubscriptionPOSTHandler swagger:operation POST /api/v1/admin/domain_permission_subscriptions domainPermissionSubscriptionCreate +// +// Create a domain permission subscription with the given parameters. +// +//	--- +//	tags: +//	- admin +// +//	consumes: +//	- multipart/form-data +//	- application/json +// +//	produces: +//	- application/json +// +//	parameters: +//	- +//		name: priority +//		in: formData +//		description: >- +//			Priority of this subscription compared to others of the same permission type. +//			0-255 (higher = higher priority). Higher priority subscriptions will overwrite +//			permissions generated by lower priority subscriptions. When two subscriptions +//			have the same `priority` value, priority is indeterminate, so it's recommended +//			to always set this value manually. +//		type: number +//		minimum: 0 +//		maximum: 255 +//		default: 0 +//	- +//		name: title +//		in: formData +//		description: Optional title for this subscription. +//		type: string +//	- +//		name: permission_type +//		required: true +//		in: formData +//		description: >- +//			Type of permissions to create by parsing the targeted file/list. +//			One of "allow" or "block". +//		type: string +//	- +//		name: as_draft +//		in: formData +//		description: >- +//			If true, domain permissions arising from this subscription will be +//			created as drafts that must be approved by a moderator to take effect. +//			If false, domain permissions from this subscription will come into force immediately. +//			Defaults to "true". +//		type: boolean +//		default: true +//	- +//		name: adopt_orphans +//		in: formData +//		description: >- +//			If true, this domain permission subscription will "adopt" domain permissions +//			which already exist on the instance, and which meet the following conditions: +//			1) they have no subscription ID (ie., they're "orphaned") and 2) they are present +//			in the subscribed list. Such orphaned domain permissions will be given this +//			subscription's subscription ID value and be managed by this subscription. +//		type: boolean +//		default: false +//	- +//		name: uri +//		required: true +//		in: formData +//		description: URI to call in order to fetch the permissions list. +//		type: string +//	- +//		name: content_type +//		required: true +//		in: formData +//		description: >- +//			MIME content type to use when parsing the permissions list. +//			One of "text/plain", "text/csv", and "application/json". +//		type: string +//	- +//		name: fetch_username +//		in: formData +//		description: >- +//			Optional basic auth username to provide when fetching given uri. +//			If set, will be transmitted along with `fetch_password` when doing the fetch. +//		type: string +//	- +//		name: fetch_password +//		in: formData +//		description: >- +//			Optional basic auth password to provide when fetching given uri. +//			If set, will be transmitted along with `fetch_username` when doing the fetch. +//		type: string +// +//	security: +//	- OAuth2 Bearer: +//		- admin +// +//	responses: +//		'200': +//			description: The newly created domain permission subscription. +//			schema: +//				"$ref": "#/definitions/domainPermissionSubscription" +//		'400': +//			description: bad request +//		'401': +//			description: unauthorized +//		'403': +//			description: forbidden +//		'406': +//			description: not acceptable +//		'409': +//			description: conflict +//		'500': +//			description: internal server error +func (m *Module) DomainPermissionSubscriptionPOSTHandler(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 authed.Account.IsMoving() { +		apiutil.ForbiddenAfterMove(c) +		return +	} + +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	// Parse + validate form. +	form := new(apimodel.DomainPermissionSubscriptionRequest) +	if err := c.ShouldBind(form); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	// Check priority. +	// Default to 0. +	priority := util.PtrOrZero(form.Priority) +	if priority < 0 || priority > 255 { +		const errText = "priority must be a number in the range 0 to 255" +		errWithCode := gtserror.NewErrorBadRequest(errors.New(errText), errText) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	// Ensure URI is set. +	if form.URI == nil { +		const errText = "uri must be set" +		errWithCode := gtserror.NewErrorBadRequest(errors.New(errText), errText) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	// Ensure URI is parseable. +	uri, err := url.Parse(*form.URI) +	if err != nil { +		err := fmt.Errorf("invalid uri provided: %w", err) +		errWithCode := gtserror.NewErrorBadRequest(err, err.Error()) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	// Normalize URI by converting back to string. +	uriStr := uri.String() + +	// Content type must be set. +	contentTypeStr := util.PtrOrZero(form.ContentType) +	contentType, errWithCode := parseDomainPermSubContentType(contentTypeStr) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	// Permission type must be set. +	permTypeStr := util.PtrOrZero(form.PermissionType) +	permType, errWithCode := parseDomainPermissionType(permTypeStr) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	// Default `as_draft` to true. +	asDraft := util.PtrOrValue(form.AsDraft, true) + +	permSub, errWithCode := m.processor.Admin().DomainPermissionSubscriptionCreate( +		c.Request.Context(), +		authed.Account, +		uint8(priority),            // #nosec G115 -- Validated above. +		util.PtrOrZero(form.Title), // Optional. +		uriStr, +		contentType, +		permType, +		asDraft, +		util.PtrOrZero(form.FetchUsername), // Optional. +		util.PtrOrZero(form.FetchPassword), // Optional. +	) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	apiutil.JSON(c, http.StatusOK, permSub) +} diff --git a/internal/api/client/admin/domainpermissionsubscriptionget.go b/internal/api/client/admin/domainpermissionsubscriptionget.go new file mode 100644 index 000000000..841e37f24 --- /dev/null +++ b/internal/api/client/admin/domainpermissionsubscriptionget.go @@ -0,0 +1,104 @@ +// 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 ( +	"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" +) + +// DomainPermissionSubscriptionGETHandler swagger:operation GET /api/v1/admin/domain_permission_subscriptions/{id} domainPermissionSubscriptionGet +// +// Get domain permission subscription with the given ID. +// +//	--- +//	tags: +//	- admin +// +//	produces: +//	- application/json +// +//	parameters: +//	- +//		name: id +//		required: true +//		in: path +//		description: ID of the domain permission subscription. +//		type: string +// +//	security: +//	- OAuth2 Bearer: +//		- admin +// +//	responses: +//		'200': +//			description: Domain permission subscription. +//			schema: +//				"$ref": "#/definitions/domainPermissionSubscription" +//		'401': +//			description: unauthorized +//		'403': +//			description: forbidden +//		'404': +//			description: not found +//		'406': +//			description: not acceptable +//		'500': +//			description: internal server error +func (m *Module) DomainPermissionSubscriptionGETHandler(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 authed.Account.IsMoving() { +		apiutil.ForbiddenAfterMove(c) +		return +	} + +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	id, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	permSub, errWithCode := m.processor.Admin().DomainPermissionSubscriptionGet(c.Request.Context(), id) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	apiutil.JSON(c, http.StatusOK, permSub) +} diff --git a/internal/api/client/admin/domainpermissionsubscriptionremove.go b/internal/api/client/admin/domainpermissionsubscriptionremove.go new file mode 100644 index 000000000..97f226a31 --- /dev/null +++ b/internal/api/client/admin/domainpermissionsubscriptionremove.go @@ -0,0 +1,143 @@ +// 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 ( +	"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/util" +) + +// DomainPermissionSubscriptionRemovePOSTHandler swagger:operation POST /api/v1/admin/domain_permission_subscriptions/{id}/remove domainPermissionSubscriptionRemove +// +// Remove a domain permission subscription. +// +//	--- +//	tags: +//	- admin +// +//	consumes: +//	- multipart/form-data +//	- application/json +// +//	produces: +//	- application/json +// +//	parameters: +//	- +//		name: id +//		required: true +//		in: path +//		description: ID of the domain permission subscription. +//		type: string +//	- +//		name: remove_children +//		in: formData +//		description: >- +//			When removing the domain permission subscription, also +//			remove children of this subscription, ie., domain permissions +//			that are managed by this subscription. If false, then children +//			will instead be orphaned but not removed. +// +//			Note that removed permissions may end up being created again later +//			by another domain permission subscription of lower priority than +//			the removed subscription. Likewise, orphaned children may be later +//			adopted by another subscription. +//		type: boolean +//		default: true +// +//	security: +//	- OAuth2 Bearer: +//		- admin +// +//	responses: +//		'200': +//			description: The removed domain permission subscription. +//			schema: +//				"$ref": "#/definitions/domainPermissionSubscription" +//		'400': +//			description: bad request +//		'401': +//			description: unauthorized +//		'403': +//			description: forbidden +//		'406': +//			description: not acceptable +//		'409': +//			description: conflict +//		'500': +//			description: internal server error +func (m *Module) DomainPermissionSubscriptionRemovePOSTHandler(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 authed.Account.IsMoving() { +		apiutil.ForbiddenAfterMove(c) +		return +	} + +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	id, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	type RemoveForm struct { +		RemoveChildren *bool `json:"remove_children" form:"remove_children"` +	} + +	form := new(RemoveForm) +	if err := c.ShouldBind(form); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	// Default removeChildren to true. +	removeChildren := util.PtrOrValue(form.RemoveChildren, true) + +	permSub, errWithCode := m.processor.Admin().DomainPermissionSubscriptionRemove( +		c.Request.Context(), +		id, +		removeChildren, +	) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	apiutil.JSON(c, http.StatusOK, permSub) +} diff --git a/internal/api/client/admin/domainpermissionsubscriptionsget.go b/internal/api/client/admin/domainpermissionsubscriptionsget.go new file mode 100644 index 000000000..477013ec9 --- /dev/null +++ b/internal/api/client/admin/domainpermissionsubscriptionsget.go @@ -0,0 +1,177 @@ +// 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 ( +	"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/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/oauth" +	"github.com/superseriousbusiness/gotosocial/internal/paging" +) + +// DomainPermissionSubscriptionsGETHandler swagger:operation GET /api/v1/admin/domain_permission_subscriptions domainPermissionSubscriptionsGet +// +// View domain permission subscriptions. +// +// The subscriptions will be returned in descending chronological order (newest first), with sequential IDs (bigger = newer). +// +// The next and previous queries can be parsed from the returned Link header. +// +// Example: +// +// ``` +// <https://example.org/api/v1/admin/domain_permission_subscriptions?limit=20&max_id=01FC0SKA48HNSVR6YKZCQGS2V8>; rel="next", <https://example.org/api/v1/admin/domain_permission_subscriptions?limit=20&min_id=01FC0SKW5JK2Q4EVAV2B462YY0>; rel="prev" +// ```` +// +//	--- +//	tags: +//	- admin +// +//	produces: +//	- application/json +// +//	parameters: +//	- +//		name: permission_type +//		type: string +//		description: Filter on "block" or "allow" type subscriptions. +//		in: query +//	- +//		name: max_id +//		type: string +//		description: >- +//			Return only items *OLDER* than the given max ID (for paging downwards). +//			The item with the specified ID will not be included in the response. +//		in: query +//	- +//		name: since_id +//		type: string +//		description: >- +//			Return only items *NEWER* than the given since ID. +//			The item with the specified ID will not be included in the response. +//		in: query +//	- +//		name: min_id +//		type: string +//		description: >- +//			Return only items immediately *NEWER* than the given min ID (for paging upwards). +//			The item with the specified ID will not be included in the response. +//		in: query +//	- +//		name: limit +//		type: integer +//		description: Number of items to return. +//		default: 20 +//		minimum: 1 +//		maximum: 100 +//		in: query +// +//	security: +//	- OAuth2 Bearer: +//		- admin +// +//	responses: +//		'200': +//			description: Domain permission subscriptions. +//			schema: +//				type: array +//				items: +//					"$ref": "#/definitions/domainPermissionSubscription" +//			headers: +//				Link: +//					type: string +//					description: Links to the next and previous queries. +//		'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) DomainPermissionSubscriptionsGETHandler(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 authed.Account.IsMoving() { +		apiutil.ForbiddenAfterMove(c) +		return +	} + +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	permType := c.Query(apiutil.DomainPermissionPermTypeKey) +	switch permType { +	case "", "block", "allow": +		// No problem. + +	default: +		// Invalid. +		text := fmt.Sprintf( +			"permission_type %s not recognized, valid values are empty string, block, or allow", +			permType, +		) +		errWithCode := gtserror.NewErrorBadRequest(errors.New(text), text) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	page, errWithCode := paging.ParseIDPage(c, 1, 200, 20) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	resp, errWithCode := m.processor.Admin().DomainPermissionSubscriptionsGet( +		c.Request.Context(), +		gtsmodel.ParseDomainPermissionType(permType), +		page, +	) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	if resp.LinkHeader != "" { +		c.Header("Link", resp.LinkHeader) +	} + +	apiutil.JSON(c, http.StatusOK, resp.Items) +} diff --git a/internal/api/client/admin/domainpermissionsubscriptionspreviewget.go b/internal/api/client/admin/domainpermissionsubscriptionspreviewget.go new file mode 100644 index 000000000..dc46c159b --- /dev/null +++ b/internal/api/client/admin/domainpermissionsubscriptionspreviewget.go @@ -0,0 +1,132 @@ +// 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 ( +	"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/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/oauth" +) + +// DomainPermissionSubscriptionsPreviewGETHandler swagger:operation GET /api/v1/admin/domain_permission_subscriptions/preview domainPermissionSubscriptionsPreviewGet +// +// View all domain permission subscriptions of the given permission type, in priority order (highest to lowest). +// +// This view allows you to see the order in which domain permissions will actually be fetched and created. +// +//	--- +//	tags: +//	- admin +// +//	produces: +//	- application/json +// +//	parameters: +//	- +//		name: permission_type +//		type: string +//		description: Filter on "block" or "allow" type subscriptions. +//		in: query +//		required: true +// +//	security: +//	- OAuth2 Bearer: +//		- admin +// +//	responses: +//		'200': +//			description: Domain permission subscriptions. +//			schema: +//				type: array +//				items: +//					"$ref": "#/definitions/domainPermissionSubscription" +//		'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) DomainPermissionSubscriptionsPreviewGETHandler(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 authed.Account.IsMoving() { +		apiutil.ForbiddenAfterMove(c) +		return +	} + +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	permType := c.Query(apiutil.DomainPermissionPermTypeKey) +	switch permType { +	case "block", "allow": +		// No problem. + +	case "": +		// Not set. +		const text = "permission_type must be set, valid values are block or allow" +		errWithCode := gtserror.NewErrorBadRequest(errors.New(text), text) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return + +	default: +		// Invalid. +		text := fmt.Sprintf( +			"permission_type %s not recognized, valid values are block or allow", +			permType, +		) +		errWithCode := gtserror.NewErrorBadRequest(errors.New(text), text) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	resp, errWithCode := m.processor.Admin().DomainPermissionSubscriptionsGetByPriority( +		c.Request.Context(), +		gtsmodel.ParseDomainPermissionType(permType), +	) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	apiutil.JSON(c, http.StatusOK, resp) +} diff --git a/internal/api/client/admin/domainpermissionsubscriptionupdate.go b/internal/api/client/admin/domainpermissionsubscriptionupdate.go new file mode 100644 index 000000000..de73c4d3e --- /dev/null +++ b/internal/api/client/admin/domainpermissionsubscriptionupdate.go @@ -0,0 +1,254 @@ +// 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 ( +	"errors" +	"fmt" +	"net/http" +	"net/url" + +	"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" +	"github.com/superseriousbusiness/gotosocial/internal/util" +) + +// DomainPermissionSubscriptionPATCHHandler swagger:operation PATCH /api/v1/admin/domain_permission_subscriptions/${id} domainPermissionSubscriptionUpdate +// +// Update a domain permission subscription with the given parameters. +// +//	--- +//	tags: +//	- admin +// +//	consumes: +//	- multipart/form-data +//	- application/json +// +//	produces: +//	- application/json +// +//	parameters: +//	- +//		name: id +//		required: true +//		in: path +//		description: ID of the domain permission subscription. +//		type: string +//	- +//		name: priority +//		in: formData +//		description: >- +//			Priority of this subscription compared to others of the same permission type. +//			0-255 (higher = higher priority). Higher priority subscriptions will overwrite +//			permissions generated by lower priority subscriptions. When two subscriptions +//			have the same `priority` value, priority is indeterminate, so it's recommended +//			to always set this value manually. +//		type: number +//		minimum: 0 +//		maximum: 255 +//	- +//		name: title +//		in: formData +//		description: Optional title for this subscription. +//		type: string +//	- +//		name: uri +//		in: formData +//		description: URI to call in order to fetch the permissions list. +//		type: string +//	- +//		name: as_draft +//		in: formData +//		description: >- +//			If true, domain permissions arising from this subscription will be +//			created as drafts that must be approved by a moderator to take effect. +//			If false, domain permissions from this subscription will come into force immediately. +//			Defaults to "true". +//		type: boolean +//		default: true +//	- +//		name: adopt_orphans +//		in: formData +//		description: >- +//			If true, this domain permission subscription will "adopt" domain permissions +//			which already exist on the instance, and which meet the following conditions: +//			1) they have no subscription ID (ie., they're "orphaned") and 2) they are present +//			in the subscribed list. Such orphaned domain permissions will be given this +//			subscription's subscription ID value and be managed by this subscription. +//		type: boolean +//		default: false +//	- +//		name: content_type +//		in: formData +//		description: >- +//			MIME content type to use when parsing the permissions list. +//			One of "text/plain", "text/csv", and "application/json". +//		type: string +//	- +//		name: fetch_username +//		in: formData +//		description: >- +//			Optional basic auth username to provide when fetching given uri. +//			If set, will be transmitted along with `fetch_password` when doing the fetch. +//		type: string +//	- +//		name: fetch_password +//		in: formData +//		description: >- +//			Optional basic auth password to provide when fetching given uri. +//			If set, will be transmitted along with `fetch_username` when doing the fetch. +//		type: string +// +//	security: +//	- OAuth2 Bearer: +//		- admin +// +//	responses: +//		'200': +//			description: The updated domain permission subscription. +//			schema: +//				"$ref": "#/definitions/domainPermissionSubscription" +//		'400': +//			description: bad request +//		'401': +//			description: unauthorized +//		'403': +//			description: forbidden +//		'406': +//			description: not acceptable +//		'409': +//			description: conflict +//		'500': +//			description: internal server error +func (m *Module) DomainPermissionSubscriptionPATCHHandler(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 authed.Account.IsMoving() { +		apiutil.ForbiddenAfterMove(c) +		return +	} + +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	id, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	// Parse + validate form. +	form := new(apimodel.DomainPermissionSubscriptionRequest) +	if err := c.ShouldBind(form); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	// Normalize priority if set. +	var priority *uint8 +	if form.Priority != nil { +		prioInt := *form.Priority +		if prioInt < 0 || prioInt > 255 { +			const errText = "priority must be a number in the range 0 to 255" +			errWithCode := gtserror.NewErrorBadRequest(errors.New(errText), errText) +			apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +			return +		} + +		priority = util.Ptr(uint8(prioInt)) // #nosec G115 -- Just validated. +	} + +	// Validate URI if set. +	var uriStr *string +	if form.URI != nil { +		uri, err := url.Parse(*form.URI) +		if err != nil { +			err := fmt.Errorf("invalid uri provided: %w", err) +			errWithCode := gtserror.NewErrorBadRequest(err, err.Error()) +			apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +			return +		} + +		// Normalize URI by converting back to string. +		uriStr = util.Ptr(uri.String()) +	} + +	// Validate content type if set. +	var contentType *gtsmodel.DomainPermSubContentType +	if form.ContentType != nil { +		ct, errWithCode := parseDomainPermSubContentType(*form.ContentType) +		if errWithCode != nil { +			apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +			return +		} + +		contentType = &ct +	} + +	// Make sure at least one field is set, +	// otherwise we're trying to update nothing. +	if priority == nil && +		form.Title == nil && +		uriStr == nil && +		contentType == nil && +		form.AsDraft == nil && +		form.AdoptOrphans == nil && +		form.FetchUsername == nil && +		form.FetchPassword == nil { +		const errText = "no updateable fields set on request" +		errWithCode := gtserror.NewErrorBadRequest(errors.New(errText), errText) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	permSub, errWithCode := m.processor.Admin().DomainPermissionSubscriptionUpdate( +		c.Request.Context(), +		id, +		priority, +		form.Title, +		uriStr, +		contentType, +		form.AsDraft, +		form.AdoptOrphans, +		form.FetchUsername, +		form.FetchPassword, +	) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	apiutil.JSON(c, http.StatusOK, permSub) +} | 
